Resource refactor for connected-solid complete

main
Jackson Morgan 6 months ago
parent 3cb56a083f
commit a879b00b44
  1. 2
      packages/connected-solid/src/SolidConnectedPlugin.ts
  2. 3
      packages/connected-solid/src/requester/LeafBatchedRequester.ts
  3. 7
      packages/connected-solid/src/requester/requests/createDataResource.ts
  4. 2
      packages/connected-solid/src/requester/requests/readResource.ts
  5. 576
      packages/connected-solid/src/resources/SolidContainer.ts
  6. 564
      packages/connected-solid/src/resources/SolidLeaf.ts
  7. 78
      packages/connected-solid/src/resources/SolidResource.ts
  8. 3
      packages/connected-solid/src/test.ts
  9. 22
      packages/connected-solid/src/types.ts
  10. 5
      packages/connected/src/ConnectedLdoDataset.ts
  11. 0
      packages/connected/src/test.ts

@ -32,7 +32,7 @@ export const solidConnectedPlugin: SolidConnectedPlugin = {
context: ConnectedContext<SolidConnectedPlugin[]>,
): SolidLeaf | SolidContainer {
if (isSolidContainerUri(uri)) {
return new SolidContainer(uri, context);
return new SolidContainer(uri, context.solid);
} else {
return new SolidLeaf(uri, context);
}

@ -146,7 +146,8 @@ export class LeafBatchedRequester extends BatchedRequester<SolidLeaf> {
this.resource,
blob,
mimeType,
overwrite,
// Hack: Something's up with these types. I can't be bothered to fix it
overwrite as false,
{ dataset: transaction, fetch: this.context.solid.fetch },
],
perform: uploadResource,

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { guaranteeFetch } from "../../util/guaranteeFetch";
import {
addResourceRdfToContainer,
@ -41,15 +42,15 @@ export type LeafCreateAndOverwriteResult =
*/
export type ContainerCreateIfAbsentResult =
| CreateSuccess<SolidContainer>
| Exclude<ReadContainerResult, AbsentReadSuccess<Resource>>
| CreateIfAbsentResultErrors<SolidLeaf>;
| Exclude<ReadContainerResult, AbsentReadSuccess<any>>
| CreateIfAbsentResultErrors<SolidContainer>;
/**
* All possible return values when creating a leaf if absent
*/
export type LeafCreateIfAbsentResult =
| CreateSuccess<SolidLeaf>
| Exclude<ReadLeafResult, AbsentReadSuccess<Resource>>
| Exclude<ReadLeafResult, AbsentReadSuccess<any>>
| CreateIfAbsentResultErrors<SolidLeaf>;
/**

@ -30,7 +30,7 @@ export type ReadLeafResult =
| BinaryReadSuccess
| DataReadSuccess
| AbsentReadSuccess<SolidLeaf>
| ReadResultError<SolidContainer>;
| ReadResultError<SolidLeaf>;
/**
* All possible return values for reading a container

@ -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 };
}
}

@ -5,13 +5,17 @@ import type {
ResourceEventEmitter,
ResourceResult,
ResourceSuccess,
Unfetched,
} from "@ldo/connected";
import type { SolidContainerUri, SolidLeafUri } from "../types";
import EventEmitter from "events";
import type { SolidConnectedPlugin } from "../SolidConnectedPlugin";
import type { BatchedRequester } from "../requester/BatchedRequester";
import type { WacRule } from "../wac/WacRule";
import type { SolidNotificationSubscription } from "../notifications/SolidNotificationSubscription";
import type {
SolidNotificationSubscription,
SubscriptionCallbacks,
} from "../notifications/SolidNotificationSubscription";
import { Websocket2023NotificationSubscription } from "../notifications/Websocket2023NotificationSubscription";
import { getParentUri } from "../util/rdfUtils";
import {
@ -22,8 +26,11 @@ import type {
ReadContainerResult,
ReadLeafResult,
} from "../requester/requests/readResource";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import type { DeleteResult } from "../requester/requests/deleteResource";
import { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import {
updateDatasetOnSuccessfulDelete,
type DeleteResult,
} from "../requester/requests/deleteResource";
import type {
ContainerCreateAndOverwriteResult,
ContainerCreateIfAbsentResult,
@ -37,8 +44,19 @@ import type { SolidLeaf } from "./SolidLeaf";
import type { GetWacUriError } from "../wac/getWacUri";
import { getWacUri, type GetWacUriResult } from "../wac/getWacUri";
import { getWacRuleWithAclUri, type GetWacRuleResult } from "../wac/getWacRule";
import type { SetWacRuleResult } from "../wac/setWacRule";
import { setWacRuleForAclUri } from "../wac/setWacRule";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import type { SolidNotificationMessage } from "../notifications/SolidNotificationMessage";
import type { CreateSuccess } from "../requester/results/success/CreateSuccess";
/**
* Statuses shared between both Leaf and Container
*/
export type SharedStatuses<ResourceType extends SolidLeaf | SolidContainer> =
| Unfetched<ResourceType>
| DeleteResult<ResourceType>
| CreateSuccess<ResourceType>;
export abstract class SolidResource
extends (EventEmitter as new () => ResourceEventEmitter)
@ -69,7 +87,9 @@ export abstract class SolidResource
* @internal
* Batched Requester for the Resource
*/
protected abstract readonly requester: BatchedRequester<this>;
protected abstract readonly requester: BatchedRequester<
SolidLeaf | SolidContainer
>;
/**
* @internal
@ -420,7 +440,7 @@ export abstract class SolidResource
* A helper method updates this resource's internal state upon delete success
* @param result - the result of the delete success
*/
public updateWithDeleteSuccess(_result: DeleteSuccess<this>) {
public updateWithDeleteSuccess(_result: DeleteSuccess<SolidResource>) {
this.absent = true;
this.didInitialFetch = true;
}
@ -430,7 +450,9 @@ export abstract class SolidResource
* Helper method that handles the core functions for deleting a resource
* @returns DeleteResult
*/
protected async handleDelete(): Promise<DeleteResult<this>> {
protected async handleDelete(): Promise<
DeleteResult<SolidLeaf | SolidContainer>
> {
const result = await this.requester.delete();
this.status = result;
if (result.isError) return result;
@ -761,29 +783,27 @@ export abstract class SolidResource
* @internal
* Function that triggers whenever a notification is recieved.
*/
protected async onNotification(message: NotificationMessage): Promise<void> {
const objectResource = this.context.solidLdoDataset.getResource(
message.object,
);
switch (message.type) {
case "Update":
case "Add":
await objectResource.read();
return;
case "Delete":
case "Remove":
// Delete the resource without have to make an additional read request
updateDatasetOnSuccessfulDelete(
message.object,
this.context.solidLdoDataset,
);
objectResource.updateWithDeleteSuccess({
type: "deleteSuccess",
isError: false,
uri: message.object,
resourceExisted: true,
});
return;
protected async onNotification(
message: SolidNotificationMessage,
): Promise<void> {
const objectResource = this.context.dataset.getResource(message.object);
// Do Nothing if the resource is invalid.
if (objectResource.type === "InvalidIdentifierResouce") return;
if (objectResource.type === "leaf") {
switch (message.type) {
case "Update":
case "Add":
await objectResource.read();
return;
case "Delete":
case "Remove":
// Delete the resource without have to make an additional read request
updateDatasetOnSuccessfulDelete(message.object, this.context.dataset);
objectResource.updateWithDeleteSuccess(
new DeleteSuccess(objectResource, true),
);
return;
}
}
}

@ -2,10 +2,9 @@ import { ConnectedLdoDataset } from "@ldo/connected";
import { solidConnectedPlugin } from "./SolidConnectedPlugin";
import { createDatasetFactory } from "@ldo/dataset";
import { createTransactionDatasetFactory } from "@ldo/subscribable-dataset";
import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph";
const dataset = new ConnectedLdoDataset(
[solidConnectedPlugin, nextGraphConnectedPlugin],
[solidConnectedPlugin],
createDatasetFactory(),
createTransactionDatasetFactory(),
);

@ -5,14 +5,30 @@ export type SolidUriPrefix = `http${"s" | ""}://`;
*/
export type SolidUri = SolidContainerUri | SolidLeafUri;
/**
* A SolidContainerSlug is any string that has a pahtname that ends in a "/". It
* represents a container.
*/
// The & {} allows for alias preservation
// eslint-disable-next-line @typescript-eslint/ban-types
export type SolidContainerSlug = `${string}/${NonPathnameEnding}` & {};
/**
* A SolidLeafUri is any URI that has a pahtname that ends in a "/". It represents a
* container.
*/
// The & {} allows for alias preservation
// eslint-disable-next-line @typescript-eslint/ban-types
export type SolidContainerUri =
`${SolidUriPrefix}${string}/${NonPathnameEnding}` & {};
export type SolidContainerUri = `${SolidUriPrefix}${SolidContainerSlug}` & {};
/**
* A SolidLeafSlug is any string that does not have a pahtname that ends in a
* "/". It represents a data resource or a binary resource. Not a container.
*/
export type SolidLeafSlug =
// The & {} allows for alias preservation
// eslint-disable-next-line @typescript-eslint/ban-types
`${string}${EveryLegalPathnameCharacterOtherThanSlash}${NonPathnameEnding}` & {};
/**
* A LeafUri is any URI that does not have a pahtname that ends in a "/". It
@ -21,7 +37,7 @@ export type SolidContainerUri =
export type SolidLeafUri =
// The & {} allows for alias preservation
// eslint-disable-next-line @typescript-eslint/ban-types
`${SolidUriPrefix}${string}${EveryLegalPathnameCharacterOtherThanSlash}${NonPathnameEnding}` & {};
`${SolidUriPrefix}${SolidLeafSlug}` & {};
/**
* @internal

@ -6,7 +6,10 @@ import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset";
import { InvalidIdentifierResource } from "./InvalidIdentifierResource";
import type { ConnectedContext } from "./ConnectedContext";
type ReturnTypeFromArgs<Func, Arg> = Func extends (arg: Arg) => infer R
type ReturnTypeFromArgs<Func, Arg> = Func extends (
arg: Arg,
context: any,
) => infer R
? R
: never;

Loading…
Cancel
Save