parent
3cb56a083f
commit
a879b00b44
@ -1,3 +1,577 @@ |
||||
import { namedNode } from "@rdfjs/data-model"; |
||||
import { ContainerBatchedRequester } from "../requester/ContainerBatchedRequester"; |
||||
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 type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; |
||||
import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; |
||||
import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess"; |
||||
import { getParentUri, ldpContains } from "../util/rdfUtils"; |
||||
import { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; |
||||
import type { SharedStatuses } from "./SolidResource"; |
||||
import { SolidResource } from "./SolidResource"; |
||||
import type { |
||||
SolidContainerSlug, |
||||
SolidContainerUri, |
||||
SolidLeafSlug, |
||||
} from "../types"; |
||||
import { AggregateSuccess } from "@ldo/connected"; |
||||
import { |
||||
Unfetched, |
||||
type ConnectedContext, |
||||
AggregateError, |
||||
} from "@ldo/connected"; |
||||
import type { SolidConnectedPlugin } from "../SolidConnectedPlugin"; |
||||
import type { SolidLeaf } from "./SolidLeaf"; |
||||
|
||||
export class SolidContainer extends SolidResource {} |
||||
/** |
||||
* Represents the current status of a specific container on a Pod as known by |
||||
* LDO. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* ``` |
||||
*/ |
||||
export class SolidContainer extends SolidResource { |
||||
/** |
||||
* The URI of the container |
||||
*/ |
||||
readonly uri: SolidContainerUri; |
||||
|
||||
/** |
||||
* @internal |
||||
* Batched Requester for the Container |
||||
*/ |
||||
protected requester: ContainerBatchedRequester; |
||||
|
||||
/** |
||||
* @internal |
||||
* True if this is the root container, false if not, undefined if unknown |
||||
*/ |
||||
protected rootContainer: boolean | undefined; |
||||
|
||||
/** |
||||
* Indicates that this resource is a container resource |
||||
*/ |
||||
readonly type = "container" as const; |
||||
|
||||
/** |
||||
* Indicates that this resource is not an error |
||||
*/ |
||||
readonly isError = false as const; |
||||
|
||||
/** |
||||
* The status of the last request made for this container |
||||
*/ |
||||
status: |
||||
| SharedStatuses<this> |
||||
| ReadContainerResult |
||||
| ContainerCreateAndOverwriteResult |
||||
| ContainerCreateIfAbsentResult |
||||
| CheckRootResult; |
||||
|
||||
/** |
||||
* @param uri - The uri of the container |
||||
* @param context - SolidLdoDatasetContext for the parent dataset |
||||
*/ |
||||
constructor( |
||||
uri: SolidContainerUri, |
||||
context: ConnectedContext<SolidConnectedPlugin[]>, |
||||
) { |
||||
super(context); |
||||
this.uri = uri; |
||||
this.requester = new ContainerBatchedRequester(this, context); |
||||
this.status = new Unfetched(this); |
||||
} |
||||
|
||||
/** |
||||
* Checks if this container is a root container |
||||
* @returns true if this container is a root container, false if not, and |
||||
* undefined if this is unknown at the moment. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* // Returns "undefined" when the container is unfetched
|
||||
* console.log(container.isRootContainer()); |
||||
* const result = await container.read(); |
||||
* if (!result.isError) { |
||||
* // Returns true or false
|
||||
* console.log(container.isRootContainer()); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
isRootContainer(): boolean | undefined { |
||||
return this.rootContainer; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* READ METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* @internal |
||||
* A helper method updates this container's internal state upon read success |
||||
* @param result - the result of the read success |
||||
*/ |
||||
protected updateWithReadSuccess( |
||||
result: ContainerReadSuccess | AbsentReadSuccess<this>, |
||||
): void { |
||||
super.updateWithReadSuccess(result); |
||||
if (result.type === "containerReadSuccess") { |
||||
this.rootContainer = result.isRootContainer; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Reads the container |
||||
* @returns A read result |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await container.read(); |
||||
* if (result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async read(): Promise<ReadContainerResult> { |
||||
const result = (await this.handleRead()) as ReadContainerResult; |
||||
if (result.isError) return result; |
||||
return { ...result, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* @internal |
||||
* Converts the current state of this container to a readResult |
||||
* @returns a ReadContainerResult |
||||
*/ |
||||
protected toReadResult(): ReadContainerResult { |
||||
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, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Makes a request to read this container if it hasn't been fetched yet. If it |
||||
* has, return the cached informtation |
||||
* @returns a ReadContainerResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await container.read(); |
||||
* if (!result.isError) { |
||||
* // Will execute without making a request
|
||||
* const result2 = await container.readIfUnfetched(); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async readIfUnfetched(): Promise<ReadContainerResult> { |
||||
return super.readIfUnfetched() as Promise<ReadContainerResult>; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* PARENT CONTAINER METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* @internal |
||||
* Checks if this container is a root container by making a request |
||||
* @returns CheckRootResult |
||||
*/ |
||||
private async checkIfIsRootContainer(): Promise<CheckRootResult> { |
||||
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 }; |
||||
} |
||||
|
||||
/** |
||||
* Gets the root container of this container. If this container is the root |
||||
* container, this function returns itself. |
||||
* @returns The root container for this container or undefined if there is no |
||||
* root container. |
||||
* |
||||
* @example |
||||
* Suppose the root container is at `https://example.com/` |
||||
* |
||||
* ```typescript
|
||||
* const container = ldoSolidDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* const rootContainer = await container.getRootContainer(); |
||||
* if (!rootContainer.isError) { |
||||
* // logs "https://example.com/"
|
||||
* console.log(rootContainer.uri); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async getRootContainer(): Promise< |
||||
SolidContainer | CheckRootResultError | NoRootContainerError<SolidContainer> |
||||
> { |
||||
const parentContainerResult = await this.getParentContainer(); |
||||
if (parentContainerResult?.isError) return parentContainerResult; |
||||
if (!parentContainerResult) { |
||||
return this.isRootContainer() ? this : new NoRootContainerError(this); |
||||
} |
||||
return parentContainerResult.getRootContainer(); |
||||
} |
||||
|
||||
/** |
||||
* Gets the parent container for this container by making a request |
||||
* @returns The parent container or undefined if there is no parent container |
||||
* because this container is the root container |
||||
* |
||||
* @example |
||||
* Suppose the root container is at `https://example.com/` |
||||
* |
||||
* ```typescript
|
||||
* const root = solidLdoDataset.getResource("https://example.com/"); |
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container"); |
||||
* const rootParent = await root.getParentContainer(); |
||||
* console.log(rootParent); // Logs "undefined"
|
||||
* const containerParent = await container.getParentContainer(); |
||||
* if (!containerParent.isError) { |
||||
* // Logs "https://example.com/"
|
||||
* console.log(containerParent.uri); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async getParentContainer(): Promise< |
||||
SolidContainer | CheckRootResultError | undefined |
||||
> { |
||||
if (this.rootContainer === undefined) { |
||||
const checkResult = await this.checkIfIsRootContainer(); |
||||
if (checkResult.isError) return checkResult; |
||||
} |
||||
if (this.rootContainer) return undefined; |
||||
const parentUri = getParentUri(this.uri); |
||||
if (!parentUri) { |
||||
return undefined; |
||||
} |
||||
return this.context.dataset.getResource(parentUri); |
||||
} |
||||
|
||||
/** |
||||
* Lists the currently cached children of this container (no request is made) |
||||
* @returns An array of children |
||||
* |
||||
* ```typescript
|
||||
* const result = await container.read(); |
||||
* if (!result.isError) { |
||||
* const children = container.children(); |
||||
* children.forEach((child) => { |
||||
* console.log(child.uri); |
||||
* }); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
children(): (SolidContainer | SolidLeaf)[] { |
||||
const childQuads = this.context.dataset.match( |
||||
namedNode(this.uri), |
||||
ldpContains, |
||||
null, |
||||
namedNode(this.uri), |
||||
); |
||||
return childQuads.toArray().map((childQuad) => { |
||||
return this.context.dataset.getResource(childQuad.object.value) as |
||||
| SolidContainer |
||||
| SolidLeaf; |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Returns a child resource with a given name (slug) |
||||
* @param slug - the given name for that child resource |
||||
* @returns the child resource (either a Leaf or Container depending on the |
||||
* name) |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const root = solidLdoDataset.getResource("https://example.com/"); |
||||
* const container = solidLdoDataset.child("container/"); |
||||
* // Logs "https://example.com/container/"
|
||||
* console.log(container.uri); |
||||
* const resource = container.child("resource.ttl"); |
||||
* // Logs "https://example.com/container/resource.ttl"
|
||||
* console.log(resource.uri); |
||||
* ``` |
||||
*/ |
||||
child(slug: SolidContainerSlug): SolidContainer; |
||||
child(slug: SolidLeafSlug): SolidLeaf; |
||||
child(slug: string): SolidLeaf | SolidContainer; |
||||
child(slug: string): SolidLeaf | SolidContainer { |
||||
return this.context.dataset.getResource(`${this.uri}${slug}`) as |
||||
| SolidLeaf |
||||
| SolidContainer; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* CHILD CREATORS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Creates a resource and overwrites any existing resource that existed at the |
||||
* URI |
||||
* |
||||
* @param slug - the name of the resource |
||||
* @return the result of creating that resource |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* cosnt result = await container.createChildAndOverwrite("resource.ttl"); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
createChildAndOverwrite( |
||||
slug: SolidContainerSlug, |
||||
): Promise<ContainerCreateAndOverwriteResult>; |
||||
createChildAndOverwrite( |
||||
slug: SolidLeafSlug, |
||||
): Promise<LeafCreateAndOverwriteResult>; |
||||
createChildAndOverwrite( |
||||
slug: string, |
||||
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult>; |
||||
createChildAndOverwrite( |
||||
slug: string, |
||||
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult> { |
||||
return this.child(slug).createAndOverwrite(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a resource only if that resource doesn't already exist on the Solid |
||||
* Pod |
||||
* |
||||
* @param slug - the name of the resource |
||||
* @return the result of creating that resource |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* cosnt result = await container.createChildIfAbsent("resource.ttl"); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
createChildIfAbsent( |
||||
slug: SolidContainerSlug, |
||||
): Promise<ContainerCreateIfAbsentResult>; |
||||
createChildIfAbsent(slug: SolidLeafSlug): Promise<LeafCreateIfAbsentResult>; |
||||
createChildIfAbsent( |
||||
slug: string, |
||||
): Promise<ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult>; |
||||
createChildIfAbsent( |
||||
slug: string, |
||||
): Promise<ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult> { |
||||
return this.child(slug).createIfAbsent(); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new binary resource and overwrites any existing resource that |
||||
* existed at the URI |
||||
* |
||||
* @param slug - the name of the resource |
||||
* @return the result of creating that resource |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* cosnt result = await container.uploadChildAndOverwrite( |
||||
* "resource.txt", |
||||
* new Blob("some text."), |
||||
* "text/txt", |
||||
* ); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async uploadChildAndOverwrite( |
||||
slug: SolidLeafSlug, |
||||
blob: Blob, |
||||
mimeType: string, |
||||
): Promise<LeafCreateAndOverwriteResult> { |
||||
return this.child(slug).uploadAndOverwrite(blob, mimeType); |
||||
} |
||||
|
||||
/** |
||||
* Creates a new binary resource and overwrites any existing resource that |
||||
* existed at the URI |
||||
* |
||||
* @param slug - the name of the resource |
||||
* @return the result of creating that resource |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const container = solidLdoDataset |
||||
* .getResource("https://example.com/container/"); |
||||
* cosnt result = await container.uploadChildIfAbsent( |
||||
* "resource.txt", |
||||
* new Blob("some text."), |
||||
* "text/txt", |
||||
* ); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async uploadChildIfAbsent( |
||||
slug: SolidLeafSlug, |
||||
blob: Blob, |
||||
mimeType: string, |
||||
): Promise<LeafCreateIfAbsentResult> { |
||||
return this.child(slug).uploadIfAbsent(blob, mimeType); |
||||
} |
||||
|
||||
/** |
||||
* Deletes all contents in this container |
||||
* @returns An AggregateSuccess or Aggregate error corresponding with all the |
||||
* deleted resources |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = container.clear(); |
||||
* if (!result.isError) { |
||||
* console.log("All deleted resources:"); |
||||
* result.results.forEach((result) => console.log(result.uri)); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async clear(): Promise< |
||||
| AggregateSuccess<DeleteSuccess<SolidContainer | SolidLeaf>> |
||||
| AggregateError< |
||||
| DeleteResultError<SolidContainer | SolidLeaf> |
||||
| ReadResultError<SolidContainer | SolidLeaf> |
||||
> |
||||
> { |
||||
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.isError); |
||||
if (errors.length > 0) { |
||||
return new AggregateError(errors); |
||||
} |
||||
return new AggregateSuccess( |
||||
results as DeleteSuccess<SolidContainer | SolidLeaf>[], |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Deletes this container and all its contents |
||||
* @returns A Delete result for this container |
||||
* |
||||
* ```typescript
|
||||
* const result = await container.delete(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async delete(): Promise< |
||||
| DeleteResult<this> |
||||
| AggregateError< |
||||
| DeleteResultError<SolidContainer | SolidLeaf> |
||||
| ReadResultError<SolidContainer | SolidLeaf> |
||||
> |
||||
> { |
||||
const clearResult = await this.clear(); |
||||
if (clearResult.isError) return clearResult; |
||||
const deleteResult = await this.handleDelete(); |
||||
if (deleteResult.isError) return deleteResult; |
||||
return { ...deleteResult, resource: this }; |
||||
} |
||||
|
||||
protected async handleDelete(): Promise<DeleteResult<this>> { |
||||
return super.handleDelete() as Promise<DeleteResult<this>>; |
||||
} |
||||
|
||||
/** |
||||
* Creates a container at this URI and overwrites any that already exists |
||||
* @returns ContainerCreateAndOverwriteResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await container.createAndOverwrite(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async createAndOverwrite(): Promise<ContainerCreateAndOverwriteResult> { |
||||
const createResult = |
||||
(await this.handleCreateAndOverwrite()) as ContainerCreateAndOverwriteResult; |
||||
if (createResult.isError) return createResult; |
||||
return { ...createResult, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* Creates a container at this URI if the container doesn't already exist |
||||
* @returns ContainerCreateIfAbsentResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await container.createIfAbsent(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async createIfAbsent(): Promise<ContainerCreateIfAbsentResult> { |
||||
const createResult = |
||||
(await this.handleCreateIfAbsent()) as ContainerCreateIfAbsentResult; |
||||
if (createResult.isError) return createResult; |
||||
return { ...createResult, resource: this }; |
||||
} |
||||
} |
||||
|
@ -1,14 +1,560 @@ |
||||
import type { Resource } from "@ldo/connected"; |
||||
import type { DatasetChanges } from "@ldo/rdf-utils"; |
||||
import type { Quad } from "@rdfjs/types"; |
||||
import { LeafBatchedRequester } from "../requester/LeafBatchedRequester"; |
||||
import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; |
||||
import type { |
||||
LeafCreateAndOverwriteResult, |
||||
LeafCreateIfAbsentResult, |
||||
} from "../requester/requests/createDataResource"; |
||||
import type { DeleteResult } from "../requester/requests/deleteResource"; |
||||
import type { ReadLeafResult } from "../requester/requests/readResource"; |
||||
import type { UpdateResult } from "../requester/requests/updateDataResource"; |
||||
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; |
||||
import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; |
||||
import type { |
||||
BinaryReadSuccess, |
||||
DataReadSuccess, |
||||
} from "../requester/results/success/ReadSuccess"; |
||||
import { getParentUri } from "../util/rdfUtils"; |
||||
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; |
||||
import type { SharedStatuses } from "./SolidResource"; |
||||
import { SolidResource } from "./SolidResource"; |
||||
import type { SolidContainerUri, SolidLeafUri } from "../types"; |
||||
import type { SolidLeafUri } from "../types"; |
||||
import type { ResourceSuccess } from "@ldo/connected"; |
||||
import { Unfetched, type ConnectedContext } from "@ldo/connected"; |
||||
import type { SolidConnectedPlugin } from "../SolidConnectedPlugin"; |
||||
import type { SolidContainer } from "./SolidContainer"; |
||||
|
||||
export class SolidLeaf |
||||
extends SolidResource |
||||
implements Resource<SolidLeafUri> { |
||||
public uri: SolidLeafUri; |
||||
/** |
||||
* Represents the current status of a specific Leaf on a Pod as known by LDO. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const leaf = solidLdoDataset |
||||
* .getResource("https://example.com/container/resource.ttl"); |
||||
* ``` |
||||
*/ |
||||
export class SolidLeaf extends SolidResource { |
||||
/** |
||||
* The URI of the leaf |
||||
*/ |
||||
readonly uri: SolidLeafUri; |
||||
|
||||
constructor() { |
||||
super() |
||||
/** |
||||
* @internal |
||||
* Batched Requester for the Leaf |
||||
*/ |
||||
protected requester: LeafBatchedRequester; |
||||
|
||||
/** |
||||
* Indicates that this resource is a leaf resource |
||||
*/ |
||||
readonly type = "leaf" as const; |
||||
|
||||
/** |
||||
* Indicates that this resource is not an error |
||||
*/ |
||||
readonly isError = false as const; |
||||
|
||||
/** |
||||
* The status of the last request made for this leaf |
||||
*/ |
||||
status: |
||||
| SharedStatuses<this> |
||||
| ReadLeafResult |
||||
| LeafCreateAndOverwriteResult |
||||
| LeafCreateIfAbsentResult |
||||
| UpdateResult<this>; |
||||
|
||||
/** |
||||
* @internal |
||||
* The raw binary data if this leaf is a Binary resource |
||||
*/ |
||||
protected binaryData: { blob: Blob; mimeType: string } | undefined; |
||||
|
||||
/** |
||||
* @param uri - The uri of the leaf |
||||
* @param context - SolidLdoDatasetContext for the parent dataset |
||||
*/ |
||||
constructor( |
||||
uri: SolidLeafUri, |
||||
context: ConnectedContext<SolidConnectedPlugin[]>, |
||||
) { |
||||
super(context); |
||||
const uriObject = new URL(uri); |
||||
uriObject.hash = ""; |
||||
this.uri = uriObject.toString() as SolidLeafUri; |
||||
this.requester = new LeafBatchedRequester(this, context); |
||||
this.status = new Unfetched(this); |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* GETTERS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Checks to see if the resource is currently uploading data |
||||
* @returns true if the current resource is uploading |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* leaf.uploadAndOverwrite(new Blob("some text"), "text/txt").then(() => { |
||||
* // Logs "false"
|
||||
* console.log(leaf.isUploading()) |
||||
* }); |
||||
* // Logs "true"
|
||||
* console.log(leaf.isUploading()); |
||||
* ``` |
||||
*/ |
||||
isUploading(): boolean { |
||||
return this.requester.isUploading(); |
||||
} |
||||
|
||||
/** |
||||
* Checks to see if the resource is currently updating data |
||||
* @returns true if the current resource is updating |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* leaf.update(datasetChanges).then(() => { |
||||
* // Logs "false"
|
||||
* console.log(leaf.isUpdating()) |
||||
* }); |
||||
* // Logs "true"
|
||||
* console.log(leaf.isUpdating()); |
||||
* ``` |
||||
*/ |
||||
isUpdating(): boolean { |
||||
return this.requester.isUpdating(); |
||||
} |
||||
|
||||
/** |
||||
* If this resource is a binary resource, returns the mime type |
||||
* @returns The mime type if this resource is a binary resource, undefined |
||||
* otherwise |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* // Logs "text/txt"
|
||||
* console.log(leaf.getMimeType()); |
||||
* ``` |
||||
*/ |
||||
getMimeType(): string | undefined { |
||||
return this.binaryData?.mimeType; |
||||
} |
||||
|
||||
/** |
||||
* If this resource is a binary resource, returns the Blob |
||||
* @returns The Blob if this resource is a binary resource, undefined |
||||
* otherwise |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* // Logs "some text."
|
||||
* console.log(leaf.getBlob()?.toString()); |
||||
* ``` |
||||
*/ |
||||
getBlob(): Blob | undefined { |
||||
return this.binaryData?.blob; |
||||
} |
||||
|
||||
/** |
||||
* Check if this resource is a binary resource |
||||
* @returns True if this resource is a binary resource, false if not, |
||||
* undefined if unknown |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* // Logs "undefined"
|
||||
* console.log(leaf.isBinary()); |
||||
* const result = await leaf.read(); |
||||
* if (!result.isError) { |
||||
* // Logs "true"
|
||||
* console.log(leaf.isBinary()); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
isBinary(): boolean | undefined { |
||||
if (!this.didInitialFetch) { |
||||
return undefined; |
||||
} |
||||
return !!this.binaryData; |
||||
} |
||||
|
||||
/** |
||||
* Check if this resource is a data (RDF) resource |
||||
* @returns True if this resource is a data resource, false if not, undefined |
||||
* if unknown |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* // Logs "undefined"
|
||||
* console.log(leaf.isDataResource()); |
||||
* const result = await leaf.read(); |
||||
* if (!result.isError) { |
||||
* // Logs "true"
|
||||
* console.log(leaf.isDataResource()); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
isDataResource(): boolean | undefined { |
||||
if (!this.didInitialFetch) { |
||||
return undefined; |
||||
} |
||||
return !this.binaryData; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* READ METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* @internal |
||||
* A helper method updates this leaf's internal state upon read success |
||||
* @param result - the result of the read success |
||||
*/ |
||||
protected updateWithReadSuccess( |
||||
result: BinaryReadSuccess | DataReadSuccess | AbsentReadSuccess<this>, |
||||
): void { |
||||
super.updateWithReadSuccess(result); |
||||
if (result.type === "binaryReadSuccess") { |
||||
this.binaryData = { blob: result.blob, mimeType: result.mimeType }; |
||||
} else { |
||||
this.binaryData = undefined; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Reads the leaf by making a request |
||||
* @returns A read result |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.read(); |
||||
* if (result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async read(): Promise<ReadLeafResult> { |
||||
const result = (await this.handleRead()) as ReadLeafResult; |
||||
if (result.isError) return result; |
||||
return { ...result, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* @internal |
||||
* Converts the current state of this leaf to a readResult |
||||
* @returns a ReadLeafResult |
||||
*/ |
||||
protected toReadResult(): ReadLeafResult { |
||||
if (this.isAbsent()) { |
||||
return { |
||||
isError: false, |
||||
type: "absentReadSuccess", |
||||
uri: this.uri, |
||||
recalledFromMemory: true, |
||||
resource: this, |
||||
}; |
||||
} else if (this.isBinary()) { |
||||
return { |
||||
isError: false, |
||||
type: "binaryReadSuccess", |
||||
uri: this.uri, |
||||
recalledFromMemory: true, |
||||
blob: this.binaryData!.blob, |
||||
mimeType: this.binaryData!.mimeType, |
||||
resource: this, |
||||
}; |
||||
} else { |
||||
return { |
||||
isError: false, |
||||
type: "dataReadSuccess", |
||||
uri: this.uri, |
||||
recalledFromMemory: true, |
||||
resource: this, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Makes a request to read this leaf if it hasn't been fetched yet. If it has, |
||||
* return the cached informtation |
||||
* @returns a ReadLeafResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.read(); |
||||
* if (!result.isError) { |
||||
* // Will execute without making a request
|
||||
* const result2 = await leaf.readIfUnfetched(); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async readIfUnfetched(): Promise<ReadLeafResult> { |
||||
return super.readIfUnfetched() as Promise<ReadLeafResult>; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* PARENT CONTAINER METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Gets the parent container for this leaf by making a request |
||||
* @returns The parent container |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const leaf = solidLdoDataset |
||||
* .getResource("https://example.com/container/resource.ttl"); |
||||
* const leafParent = await leaf.getParentContainer(); |
||||
* if (!leafParent.isError) { |
||||
* // Logs "https://example.com/container/"
|
||||
* console.log(leafParent.uri); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async getParentContainer(): Promise<SolidContainer> { |
||||
const parentUri = getParentUri(this.uri)!; |
||||
return this.context.dataset.getResource(parentUri); |
||||
} |
||||
|
||||
/** |
||||
* Gets the root container for this leaf. |
||||
* @returns The root container for this leaf |
||||
* |
||||
* @example |
||||
* Suppose the root container is at `https://example.com/` |
||||
* |
||||
* ```typescript
|
||||
* const leaf = ldoSolidDataset |
||||
* .getResource("https://example.com/container/resource.ttl"); |
||||
* const rootContainer = await leaf.getRootContainer(); |
||||
* if (!rootContainer.isError) { |
||||
* // logs "https://example.com/"
|
||||
* console.log(rootContainer.uri); |
||||
* } |
||||
* ``` |
||||
*/ |
||||
async getRootContainer(): Promise< |
||||
SolidContainer | CheckRootResultError | NoRootContainerError<SolidContainer> |
||||
> { |
||||
// Check to see if this document has a pim:storage if so, use that
|
||||
|
||||
// If not, traverse the tree
|
||||
const parent = await this.getParentContainer(); |
||||
return parent.getRootContainer(); |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* DELETE METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* @internal |
||||
* A helper method updates this leaf's internal state upon delete success |
||||
* @param result - the result of the delete success |
||||
*/ |
||||
public updateWithDeleteSuccess(result: DeleteSuccess<this>) { |
||||
super.updateWithDeleteSuccess(result); |
||||
this.binaryData = undefined; |
||||
} |
||||
|
||||
/** |
||||
* Deletes this leaf and all its contents |
||||
* @returns A Delete result for this leaf |
||||
* |
||||
* ```typescript
|
||||
* const result = await container.leaf(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async delete(): Promise<DeleteResult<this>> { |
||||
return this.handleDelete(); |
||||
} |
||||
|
||||
protected async handleDelete(): Promise<DeleteResult<this>> { |
||||
return super.handleDelete() as Promise<DeleteResult<this>>; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* CREATE METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* A helper method updates this leaf's internal state upon create success |
||||
* @param _result - the result of the create success |
||||
*/ |
||||
protected updateWithCreateSuccess(_result: ResourceSuccess<this>): void { |
||||
this.binaryData = undefined; |
||||
} |
||||
|
||||
/** |
||||
* Creates a leaf at this URI and overwrites any that already exists |
||||
* @returns LeafCreateAndOverwriteResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.createAndOverwrite(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async createAndOverwrite(): Promise<LeafCreateAndOverwriteResult> { |
||||
const createResult = |
||||
(await this.handleCreateAndOverwrite()) as LeafCreateAndOverwriteResult; |
||||
if (createResult.isError) return createResult; |
||||
return { ...createResult, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* Creates a leaf at this URI if the leaf doesn't already exist |
||||
* @returns LeafCreateIfAbsentResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.createIfAbsent(); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async createIfAbsent(): Promise<LeafCreateIfAbsentResult> { |
||||
const createResult = |
||||
(await this.handleCreateIfAbsent()) as LeafCreateIfAbsentResult; |
||||
if (createResult.isError) return createResult; |
||||
return { ...createResult, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* UPLOAD METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Uploads a binary resource to this URI. If there is already a resource |
||||
* present at this URI, it will be overwritten |
||||
* |
||||
* @param blob - the Blob of the binary |
||||
* @param mimeType - the MimeType of the binary |
||||
* @returns A LeafCreateAndOverwriteResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.uploadAndOverwrite( |
||||
* new Blob("some text."), |
||||
* "text/txt", |
||||
* ); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async uploadAndOverwrite( |
||||
blob: Blob, |
||||
mimeType: string, |
||||
): Promise<LeafCreateAndOverwriteResult> { |
||||
const result = await this.requester.upload(blob, mimeType, true); |
||||
this.status = result; |
||||
if (result.isError) return result; |
||||
super.updateWithCreateSuccess(result); |
||||
this.binaryData = { blob, mimeType }; |
||||
this.emitThisAndParent(); |
||||
return { ...result, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* Uploads a binary resource to this URI tf there not is already a resource |
||||
* present at this URI. |
||||
* |
||||
* @param blob - the Blob of the binary |
||||
* @param mimeType - the MimeType of the binary |
||||
* @returns A LeafCreateIfAbsentResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const result = await leaf.uploadIfAbsent( |
||||
* new Blob("some text."), |
||||
* "text/txt", |
||||
* ); |
||||
* if (!result.isError) { |
||||
* // Do something
|
||||
* } |
||||
* ``` |
||||
*/ |
||||
async uploadIfAbsent( |
||||
blob: Blob, |
||||
mimeType: string, |
||||
): Promise<LeafCreateIfAbsentResult> { |
||||
const result = await this.requester.upload(blob, mimeType); |
||||
this.status = result; |
||||
if (result.isError) return result; |
||||
super.updateWithCreateSuccess(result); |
||||
this.binaryData = { blob, mimeType }; |
||||
this.emitThisAndParent(); |
||||
return { ...result, resource: this }; |
||||
} |
||||
|
||||
/** |
||||
* =========================================================================== |
||||
* UPDATE METHODS |
||||
* =========================================================================== |
||||
*/ |
||||
|
||||
/** |
||||
* Updates a data resource with the changes provided |
||||
* @param changes - Dataset changes that will be applied to the resoruce |
||||
* @returns An UpdateResult |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* import { |
||||
* updateDataResource, |
||||
* transactionChanges, |
||||
* changeData, |
||||
* createSolidLdoDataset, |
||||
* } from "@ldo/solid"; |
||||
* |
||||
* //...
|
||||
* |
||||
* // Get a Linked Data Object
|
||||
* const profile = solidLdoDataset |
||||
* .usingType(ProfileShapeType) |
||||
* .fromSubject("https://example.com/profile#me"); |
||||
* cosnt resource = solidLdoDataset |
||||
* .getResource("https://example.com/profile"); |
||||
* // Create a transaction to change data
|
||||
* const cProfile = changeData(profile, resource); |
||||
* cProfile.name = "John Doe"; |
||||
* // Get data in "DatasetChanges" form
|
||||
* const datasetChanges = transactionChanges(someLinkedDataObject); |
||||
* // Use "update" to apply the changes
|
||||
* cosnt result = resource.update(datasetChanges); |
||||
* ``` |
||||
*/ |
||||
async update( |
||||
changes: DatasetChanges<Quad>, |
||||
): Promise<UpdateResult<SolidLeaf>> { |
||||
const result = await this.requester.updateDataResource(changes); |
||||
this.status = result; |
||||
if (result.isError) return result; |
||||
this.binaryData = undefined; |
||||
this.absent = false; |
||||
this.emitThisAndParent(); |
||||
return { ...result, resource: this }; |
||||
} |
||||
|
||||
} |
||||
|
Loading…
Reference in new issue