diff --git a/package.json b/package.json index 64e1c1f..2f638b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-logic", - "version": "6.0.0-0", + "version": "6.0.0-1", "description": "Core business logic of SolidOS", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", diff --git a/src/resource/resourceLogic.ts b/src/resource/resourceLogic.ts index 7d39f00..4e866b5 100644 --- a/src/resource/resourceLogic.ts +++ b/src/resource/resourceLogic.ts @@ -1,11 +1,11 @@ -import { NamedNode, sym } from 'rdflib' +import { NamedNode, sym, LiveStore } from 'rdflib' import { ACL_LINK } from '../acl/aclLogic' import { ns } from '../util/ns' import { assertSuccessfulHttpResponse, isMissingError } from './resourceHttp' import { readWacAccessInfo } from './resourceMetadata' import { type AclLogic, type ResourceAccess, type ResourceAccessWithDelete, type ResourceDeleteOptions, type ResourceLogic, type ResourceMetadata, type ResourceMetadataWithDelete, type TypeIndexLogic } from '../types' -export function createResourceLogic(store, aclLogic: AclLogic, containerLogic, typeIndexLogic: TypeIndexLogic): ResourceLogic { +export function createResourceLogic(store: LiveStore, aclLogic: AclLogic, containerLogic, typeIndexLogic: TypeIndexLogic): ResourceLogic { function createContainer(url: string) { return containerLogic.createContainer(url) } @@ -168,11 +168,33 @@ export function createResourceLogic(store, aclLogic: AclLogic, containerLogic, t await recursiveDelete(resourceNode, { deleteTypeIndexes: true, user }) } + async function checkAndRefreshEditable(resourceNode: NamedNode | null | undefined): Promise { + if (!resourceNode || !store.updater || !store.fetcher || typeof store.fetcher.refresh !== 'function') return false + + const resourceUri = resourceNode.uri || resourceNode.value || '' + if (!resourceUri) return false + + const editable = store.updater.editable(resourceUri, store) + if (editable !== false && editable !== undefined) { + return true + } + + try { + await store.fetcher.refresh(resourceNode) + } catch (error) { + throw error instanceof Error ? error : new Error(`Failed to refresh <${resourceUri}>`) + } + + const editableAfterRefresh = store.updater.editable(resourceUri, store) + return editableAfterRefresh !== false && editableAfterRefresh !== undefined + } + return { recursiveDelete, deleteResourceAndTypeIndexIfExists, fetchMetadata, fetchMetadataWithDelete, + checkAndRefreshEditable, createContainer, isContainer, getContainerMemberCount diff --git a/src/types.ts b/src/types.ts index 58ebd61..427ff97 100644 --- a/src/types.ts +++ b/src/types.ts @@ -124,6 +124,7 @@ export interface ResourceLogic { deleteResourceAndTypeIndexIfExists: (resource: NamedNode, user?: NamedNode | null) => Promise, fetchMetadata: (subject: NamedNode) => Promise, fetchMetadataWithDelete: (subject: NamedNode) => Promise, + checkAndRefreshEditable: (resource: NamedNode | null | undefined) => Promise, createContainer: (url: string) => Promise, isContainer: (resource: NamedNode) => boolean, getContainerMemberCount: (resource: NamedNode) => number diff --git a/test/resourceLogic.test.ts b/test/resourceLogic.test.ts index 62007fe..73643d3 100644 --- a/test/resourceLogic.test.ts +++ b/test/resourceLogic.test.ts @@ -103,4 +103,21 @@ describe('resourceLogic', () => { await expect(resourceLogic.recursiveDelete(resource)).resolves.toBeUndefined() expect(store.removeDocument).toHaveBeenCalledWith(resource) }) + + it('refreshes a resource when editability is stale and returns the refreshed editability', async () => { + const resourceLogic = createResourceLogic(store, aclLogic, containerLogic, typeIndexLogic) + const resource = sym('https://example.com/profile/card') + const editable = vi.fn().mockReturnValueOnce(false).mockReturnValueOnce(true) + const refresh = vi.fn().mockResolvedValue(undefined) + + store.updater = { + editable + } as unknown as LiveStore['updater'] + store.fetcher.refresh = refresh as unknown as Fetcher['refresh'] + + await expect(resourceLogic.checkAndRefreshEditable(resource)).resolves.toBe(true) + expect(editable).toHaveBeenNthCalledWith(1, resource.uri, store) + expect(refresh).toHaveBeenCalledWith(resource) + expect(editable).toHaveBeenNthCalledWith(2, resource.uri, store) + }) })