import { namedNode } from "@rdfjs/data-model"; import { ContainerRequester } from "../requester/ContainerRequester"; import type { CheckRootResult, CheckRootResultError, } from "../requester/requests/checkRootContainer"; import type { ContainerCreateAndOverwriteResult, ContainerCreateIfAbsentResult, LeafCreateAndOverwriteResult, LeafCreateIfAbsentResult, } from "../requester/requests/createDataResource"; import type { DeleteResult, DeleteResultError, } from "../requester/requests/deleteResource"; import type { ReadContainerResult, ReadResultError, } from "../requester/requests/readResource"; import { AggregateError } from "../requester/results/error/ErrorResult"; import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess"; import type { AggregateSuccess } from "../requester/results/success/SuccessResult"; import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; import { getParentUri, ldpContains } from "../util/rdfUtils"; import type { ContainerUri, LeafUri } from "../util/uriTypes"; import type { Leaf } from "./Leaf"; import type { SharedStatuses } from "./Resource"; import { Resource } from "./Resource"; import type { ResourceResult } from "./resourceResult/ResourceResult"; export class Container extends Resource { readonly uri: ContainerUri; protected requester: ContainerRequester; protected rootContainer: boolean | undefined; readonly type = "container" as const; readonly isError = false as const; status: | SharedStatuses | ReadContainerResult | ContainerCreateAndOverwriteResult | ContainerCreateIfAbsentResult | CheckRootResult; constructor(uri: ContainerUri, context: SolidLdoDatasetContext) { super(context); this.uri = uri; this.requester = new ContainerRequester(uri, context); this.status = { isError: false, type: "unfetched", uri }; } isRootContainer(): boolean | undefined { return this.rootContainer; } // Read Methods protected updateWithReadSuccess( result: ContainerReadSuccess | AbsentReadSuccess, ): void { if (result.type === "containerReadSuccess") { this.rootContainer = result.isRootContainer; } } async read(): Promise> { const result = (await this.handleRead()) as ReadContainerResult; if (result.isError) return result; return { ...result, resource: this }; } protected toReadResult(): ResourceResult { if (this.isAbsent()) { return { isError: false, type: "absentReadSuccess", uri: this.uri, recalledFromMemory: true, resource: this, }; } else { return { isError: false, type: "containerReadSuccess", uri: this.uri, recalledFromMemory: true, isRootContainer: this.isRootContainer()!, resource: this, }; } } async readIfUnfetched(): Promise< ResourceResult > { return super.readIfUnfetched() as Promise< ResourceResult >; } // Parent Container Methods private async checkIfIsRootContainer(): Promise< ResourceResult > { const rootContainerResult = await this.requester.isRootContainer(); this.status = rootContainerResult; if (rootContainerResult.isError) return rootContainerResult; this.rootContainer = rootContainerResult.isRootContainer; this.emit("update"); return { ...rootContainerResult, resource: this }; } async getRootContainer(): Promise { const checkResult = await this.checkIfIsRootContainer(); if (checkResult.isError) return checkResult; if (this.rootContainer) { return this; } const parentUri = getParentUri(this.uri); if (!parentUri) { return new NoncompliantPodError( this.uri, "Resource does not have a root container", ); } return this.context.resourceStore.get(parentUri).getRootContainer(); } async getParentContainer(): Promise< Container | CheckRootResultError | undefined > { const checkResult = await this.checkIfIsRootContainer(); if (checkResult.isError) return checkResult; if (this.rootContainer) return undefined; const parentUri = getParentUri(this.uri); if (!parentUri) { return new NoncompliantPodError( this.uri, `${this.uri} is not root does not have a parent container`, ); } return this.context.resourceStore.get(parentUri); } children(): (Leaf | Container)[] { const childQuads = this.context.solidLdoDataset.match( namedNode(this.uri), ldpContains, null, namedNode(this.uri), ); return childQuads.toArray().map((childQuad) => { return this.context.resourceStore.get(childQuad.object.value); }); } child(slug: ContainerUri): Container; child(slug: LeafUri): Leaf; child(slug: string): Leaf | Container; child(slug: string): Leaf | Container { return this.context.resourceStore.get(`${this.uri}${slug}`); } // Child Creators createChildAndOverwrite( slug: ContainerUri, ): Promise>; createChildAndOverwrite( slug: LeafUri, ): Promise>; createChildAndOverwrite( slug: string, ): Promise< ResourceResult< ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult, Leaf | Container > >; createChildAndOverwrite( slug: string, ): Promise< ResourceResult< ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult, Leaf | Container > > { return this.child(slug).createAndOverwrite(); } createChildIfAbsent( slug: ContainerUri, ): Promise>; createChildIfAbsent( slug: LeafUri, ): Promise>; createChildIfAbsent( slug: string, ): Promise< ResourceResult< ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult, Leaf | Container > >; createChildIfAbsent( slug: string, ): Promise< ResourceResult< ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult, Leaf | Container > > { return this.child(slug).createIfAbsent(); } async uploadChildAndOverwrite( slug: LeafUri, blob: Blob, mimeType: string, ): Promise> { return this.child(slug).uploadAndOverwrite(blob, mimeType); } async uploadChildIfAbsent( slug: LeafUri, blob: Blob, mimeType: string, ): Promise> { return this.child(slug).uploadIfAbsent(blob, mimeType); } async clear(): Promise< ResourceResult< | AggregateSuccess> | AggregateError, Container > > { const readResult = await this.read(); if (readResult.isError) return new AggregateError([readResult]); const results = ( await Promise.all( this.children().map(async (child) => { return child.delete(); }), ) ).flat(); const errors = results.filter( ( value, ): value is | DeleteResultError | AggregateError => value.isError, ); if (errors.length > 0) { return new AggregateError(errors); } return { isError: false, type: "aggregateSuccess", results: results as ResourceResult[], resource: this, }; } async delete(): Promise< ResourceResult< DeleteResult | AggregateError, Container > > { const clearResult = await this.clear(); if (clearResult.isError) return clearResult; const deleteResult = await this.handleDelete(); if (deleteResult.isError) return deleteResult; return { ...deleteResult, resource: this }; } async createAndOverwrite(): Promise< ResourceResult > { const createResult = (await this.handleCreateAndOverwrite()) as ContainerCreateAndOverwriteResult; if (createResult.isError) return createResult; return { ...createResult, resource: this }; } async createIfAbsent(): Promise< ResourceResult > { const createResult = (await this.handleCreateIfAbsent()) as ContainerCreateIfAbsentResult; if (createResult.isError) return createResult; return { ...createResult, resource: this }; } }