diff --git a/packages/demo-react/src/App-old.tsx b/packages/demo-react/src/App-old.tsx
new file mode 100644
index 0000000..721b93e
--- /dev/null
+++ b/packages/demo-react/src/App-old.tsx
@@ -0,0 +1,13 @@
+import type { FunctionComponent } from "react";
+import React from "react";
+import { Router } from "./Layout";
+import { BrowserSolidLdoProvider } from "@ldo/solid-react";
+
+const ProfileApp: FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
+export default ProfileApp;
diff --git a/packages/demo-react/src/App.tsx b/packages/demo-react/src/App.tsx
index 721b93e..4d00635 100644
--- a/packages/demo-react/src/App.tsx
+++ b/packages/demo-react/src/App.tsx
@@ -1,13 +1,65 @@
import type { FunctionComponent } from "react";
-import React from "react";
-import { Router } from "./Layout";
-import { BrowserSolidLdoProvider } from "@ldo/solid-react";
+import React, { useCallback } from "react";
+import {
+ BrowserSolidLdoProvider,
+ useResource,
+ useSolidAuth,
+ useSubject,
+} from "@ldo/solid-react";
+import { SolidProfileShapeShapeType } from "./.ldo/solidProfile.shapeTypes";
+import { changeData, commitData } from "@ldo/solid";
-const ProfileApp: FunctionComponent = () => {
+// The base component for the app
+const App: FunctionComponent = () => {
return (
+ /* The application should be surrounded with the BrowserSolidLdoProvider
+ this will set up all the underlying infrastructure for the application */
-
+
);
};
-export default ProfileApp;
+
+// A component that handles login
+const Login: FunctionComponent = () => {
+ // Get login information using the "useSolidAuth" hook
+ const { login, logout, session } = useSolidAuth();
+
+ const onLogin = useCallback(() => {
+ const issuer = prompt("What is your Solid IDP?");
+ // Call the "login" function to initiate login
+ if (issuer) login(issuer);
+ }, []);
+
+ // You can use session.isLoggedIn to check if the user is logged in
+ if (session.isLoggedIn) {
+ return (
+
+ {/* Get the user's webId from session.webId */}
+
Logged in as {session.webId}
+ {/* Use the logout function to log out */}
+
+
+
+ );
+ }
+ return ;
+};
+
+const Profile: FunctionComponent = () => {
+ const { session } = useSolidAuth();
+ const resource = useResource(session.webId);
+ const profile = useSubject(SolidProfileShapeShapeType, session.webId);
+
+ const onNameChange = useCallback(async (e) => {
+ // Ensure that the
+ if (!profile || !resource) return;
+ const cProfile = changeData(profile, resource);
+ cProfile.name = e.target.value;
+ await commitData(cProfile);
+ }, []);
+
+ return ;
+};
+
+export default App;
diff --git a/packages/solid-react/src/util/TrackingProxyContext.ts b/packages/solid-react/src/util/TrackingProxyContext.ts
index d354a13..de94629 100644
--- a/packages/solid-react/src/util/TrackingProxyContext.ts
+++ b/packages/solid-react/src/util/TrackingProxyContext.ts
@@ -9,10 +9,20 @@ import type { SubscribableDataset } from "@ldo/subscribable-dataset";
import { namedNode } from "@rdfjs/data-model";
import type { Quad } from "@rdfjs/types";
+/**
+ * @internal
+ * Options to be passed to the tracking proxy
+ */
export interface TrackingProxyContextOptions extends ProxyContextOptions {
dataset: SubscribableDataset;
}
+/**
+ * @internal
+ * This proxy exists to ensure react components rerender at the right time. It
+ * keeps track of every key accessed in a Linked Data Object and only when the
+ * dataset is updated with that key does it rerender the react component.
+ */
export class TrackingProxyContext extends ProxyContext {
private listener: () => void;
private subscribableDataset: SubscribableDataset;
diff --git a/packages/solid-react/src/util/createWrapperProxy.ts b/packages/solid-react/src/util/createWrapperProxy.ts
deleted file mode 100644
index 244c891..0000000
--- a/packages/solid-react/src/util/createWrapperProxy.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { Resource } from "@ldo/solid";
-
-export function createWrapperProxy(
- target: ResourceType,
-): ResourceType {
- return new Proxy(target, {});
-}
diff --git a/packages/solid-react/src/util/useForceReload.ts b/packages/solid-react/src/util/useForceReload.ts
deleted file mode 100644
index 6082c84..0000000
--- a/packages/solid-react/src/util/useForceReload.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useState, useCallback } from "react";
-
-export function useForceReload() {
- const [, setValue] = useState(0);
- return useCallback(() => setValue((value) => value + 1), []);
-}
diff --git a/packages/solid/README.md b/packages/solid/README.md
index 1670088..ecdeab6 100644
--- a/packages/solid/README.md
+++ b/packages/solid/README.md
@@ -1,6 +1,228 @@
# @ldo/solid
+@ldo/solid is a client that implements the Solid specification with the use of Linked Data Objects.
+## Installation
+
+Navigate into your project's root folder and run the following command:
+```
+cd my_project/
+npx run @ldo/cli init
+```
+
+Now install the @ldo/solid library
+
+```
+npm i @ldo/solid
+```
+
+### Manual Installation
+
+If you already have generated ShapeTypes, you may install the `@ldo/ldo` and `@ldo/solid` libraries independently.
+
+```
+npm i @ldo/ldo @ldo/solid
+```
+
+## Simple Examples
+
+Below is a simple example of @ldo/solid. Assume that a ShapeType was previously generated and placed at `./.ldo/foafProfile.shapeTypes`. Also assume we have a shape type for social media at `./.ldo/socialMediaPost.shapeTypes`
+
+```typescript
+import { changeData, commitData, createSolidLdoDataset } from "@ldo/solid";
+import { fetch, getDefaultSession } from "@inrupt/solid-client-authn-browser";
+import { FoafProfileShapeType } from "./.ldo/foafProfile.shapeTypes";
+import { SocialMediaPostShapeType } from "./.ldo/socialMediaPost.shapeTypes";
+
+async function main() {
+ /**
+ * ===========================================================================
+ * READING DATA FROM A POD
+ * ===========================================================================
+ */
+
+ // Before we begin using @ldo/solid. Let's get the WebId of the current user
+ const webIdUri = getDefaultSession().info.webId;
+ if (!webIdUri) throw new Error("User is not logged in");
+
+ // Now let's proceed with @ldo/solid. Our first step is setting up a
+ // SolidLdoDataset. You can think of this dataset as a local store for all the
+ // information in the Solidverse. Don't forget to pass the authenticated fetch
+ // function to do your queries!
+ const solidLdoDataset = createSolidLdoDataset({ fetch });
+
+ // We'll start with getting a representation of our WebId's resource
+ const webIdResource = solidLdoDataset.getResource(webIdUri);
+
+ // This resource is currently unfetched
+ console.log(webIdResource.isUnfetched()); // Logs true
+
+ // So let's fetch it! Running the `read` command will make a request to get
+ // the WebId.
+ const readResult = await webIdResource.read();
+
+ // @ldo/solid will never throw an error. Instead, it will return errors. This
+ // design decision was made to force you to handle any errors. It may seem a
+ // bit annoying at first, but it will result in more resiliant code. You can
+ // easily follow intellisense tooltips to see what kinds of errors each action
+ // can throw.
+ if (readResult.isError) {
+ switch (readResult.type) {
+ case "serverError":
+ console.error("The solid server had an error:", readResult.message);
+ return;
+ case "noncompliantPodError":
+ console.error("The Pod responded in a way not compliant with the spec");
+ return;
+ default:
+ console.error("Some other error was detected:", readResult.message);
+ }
+ }
+
+ // When fetching a data resource, read triples will automatically be added to
+ // the solidLdoDataset. You can access them using Linked Data Objects. In
+ // the following example we're using a Profile Linked Data Object that was
+ // generated with the init step.
+ const profile = solidLdoDataset
+ .usingType(FoafProfileShapeType)
+ .fromSubject(webIdUri);
+
+ // Now you can read "profile" like any JSON.
+ console.log(profile.name);
+
+ /**
+ * ===========================================================================
+ * MODIFYING DATA
+ * ===========================================================================
+ */
+
+ // When we want to modify data the first step is to use the `changeData`
+ // function. We pass in an object that we want to change (in this case,
+ // "profile") as well an a list of any resources to which we want those
+ // changes to be applied (in this case, just the webIdResource). This gives
+ // us a new variable (conventionally named with a c for "changed") that we can
+ // write changes to.
+ const cProfile = changeData(profile, webIdResource);
+
+ // We can make changes just like it's regular JSON
+ cProfile.name = "Captain Cool Dude";
+
+ // Committing data is as easy as running the "commitData" function.
+ const commitResult = await commitData(cProfile);
+
+ // Remember to check for and handle errors! We'll keep it short this time.
+ if (commitResult.isError) throw commitResult;
+
+ /**
+ * ===========================================================================
+ * CREATING NEW RESOURCES
+ * ===========================================================================
+ */
+
+ // Let's create some social media posts to be stored on the Solid Pod!
+ // Our first step is going to be finding where to place these posts. In the
+ // future, there will be advanced ways to determine the location of resources
+ // but for now, let's throw it in the root folder.
+
+ // But, first, let's find out where the root folder is. We can take our WebId
+ // resource and call `getRootContainer`. Let's assume the root container has
+ // a URI "https://example.com/"
+ const rootContainer = await webIdResource.getRootContainer();
+ if (rootContainer.isError) throw rootContainer;
+
+ // Now, let's create a container for our posts
+ const createPostContainerResult =
+ await rootContainer.createChildIfAbsent("social-posts/");
+ if (createPostContainerResult.isError) throw createPostContainerResult;
+
+ // Most results store the affected resource in the "resource" field. This
+ // container has the URI "https://example.com/social-posts/"
+ const postContainer = createPostContainerResult.resource;
+
+ // Now that we have our container, let's make a Post resource! This is a data
+ // resource, which means we can put raw Solid Data (RDF) into it.
+ const postResourceResult =
+ await postContainer.createChildAndOverwrite("post1.ttl");
+ if (postResourceResult.isError) throw postResourceResult;
+ const postResource = postResourceResult.resource;
+
+ // We can also create binary resources with things like images
+ const imageResourceResult = await postContainer.uploadChildAndOverwrite(
+ // name of the binary
+ "image1.svg",
+ // A blob for the binary
+ new Blob([``]),
+ // mime type of the binary
+ "image/svg+xml",
+ );
+ if (imageResourceResult.isError) throw imageResourceResult;
+ const imageResource = imageResourceResult.resource;
+
+ /**
+ * ===========================================================================
+ * CREATING NEW DATA
+ * ===========================================================================
+ */
+
+ // We create data in a similar way to the way we modify data. We can use the
+ // "createData" method.
+ const cPost = solidLdoDataset.createData(
+ // An LDO ShapeType saying that this is a social media psot
+ SocialMediaPostShapeType,
+ // The URI of the post (in this case we'll make it the same as the resource)
+ postResource.uri,
+ // The resource we should write it to
+ postResource,
+ );
+
+ // We can add new data
+ cPost.text = "Check out this bad svg:";
+ cPost.image = { "@id": imageResource.uri };
+
+ // And now we commit data
+ const newDataResult = await commitData(cPost);
+ if (newDataResult.isError) throw newDataResult;
+
+ /**
+ * ===========================================================================
+ * DELETING RESOURCES
+ * ===========================================================================
+ */
+
+ // Deleting resources can be done with a single method call. In this case,
+ // the container will be deleted along with all its contained resources
+ const deleteResult = await postContainer.delete();
+ if (deleteResult.isError) throw deleteResult;
+}
+main();
+```
+
+## API Details
+
+SolidLdoDataset
+
+ - [createSolidLdoDataset](https://ldo.js.org/api/solid/functions/createSolidLdoDataset/)
+ - [SolidLdoDataset](https://ldo.js.org/api/solid/classes/SolidLdoDataset/)
+
+Resources (Manage batching requests)
+
+ - [LeafUri](https://ldo.js.org/api/solid/types/LeafUri/)
+ - [ContainerUri](https://ldo.js.org/api/solid/types/ContainerUri/)
+ - [Leaf](https://ldo.js.org/api/solid/classes/Leaf/)
+ - [Container](https://ldo.js.org/api/solid/classes/Container/)
+
+Standalone Functions
+
+ - [checkRootContainter](https://ldo.js.org/api/solid/functions/checkRootContainer/)
+ - [createDataResource](https://ldo.js.org/api/solid/functions/createDataResource/)
+ - [deleteResource](https://ldo.js.org/api/solid/functions/deleteResource/)
+ - [readResource](https://ldo.js.org/api/solid/functions/readResource/)
+ - [updateResource](https://ldo.js.org/api/solid/functions/updateResource/)
+ - [uploadResource](https://ldo.js.org/api/solid/functions/uploadResource/)
+
+Data Functions
+ - [changeData](https://ldo.js.org/api/solid/functions/changeData/)
+ - [commitData](https://ldo.js.org/api/solid/functions/commitData/)
## Sponsorship
This project was made possible by a grant from NGI Zero Entrust via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/SolidUsableApps/).
diff --git a/packages/solid/src/ResourceStore.ts b/packages/solid/src/ResourceStore.ts
index b3fba0f..5ecbafe 100644
--- a/packages/solid/src/ResourceStore.ts
+++ b/packages/solid/src/ResourceStore.ts
@@ -4,19 +4,52 @@ import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext";
import type { ContainerUri, LeafUri } from "./util/uriTypes";
import { isContainerUri } from "./util/uriTypes";
+/**
+ * Options for getting a resource
+ */
export interface ResourceGetterOptions {
+ /**
+ * If autoLoad is set to true and the resource is unfetched, `read` will be called.
+ *
+ * @default false
+ */
autoLoad?: boolean;
}
+/**
+ * @internal
+ * A store of Solid resources
+ */
export class ResourceStore {
+ /**
+ * @internal
+ *
+ * A mapping between a resource URI and a Solid resource
+ */
protected resourceMap: Map;
+ /**
+ * @internal
+ *
+ * Context about the SolidLdoDataset
+ */
protected context: SolidLdoDatasetContext;
+ /**
+ * @param context - A SolidLdoDatasetContext of the parent SolidLdoDataset
+ */
constructor(context: SolidLdoDatasetContext) {
this.resourceMap = new Map();
this.context = context;
}
+ /**
+ * Gets a resource representation
+ *
+ * @param uri - The URI of the resource
+ * @param options - ResourceGetterOptions
+ *
+ * @returns The resource representation
+ */
get(uri: ContainerUri, options?: ResourceGetterOptions): Container;
get(uri: LeafUri, options?: ResourceGetterOptions): Leaf;
get(uri: string, options?: ResourceGetterOptions): Leaf | Container;
diff --git a/packages/solid/src/SolidLdoDataset.ts b/packages/solid/src/SolidLdoDataset.ts
index 61ef235..53c2e02 100644
--- a/packages/solid/src/SolidLdoDataset.ts
+++ b/packages/solid/src/SolidLdoDataset.ts
@@ -22,11 +22,43 @@ import { splitChangesByGraph } from "./util/splitChangesByGraph";
import type { ContainerUri, LeafUri } from "./util/uriTypes";
import { isContainerUri } from "./util/uriTypes";
import type { Resource } from "./resource/Resource";
-import { quad as createQuad } from "@rdfjs/data-model";
+/**
+ * A SolidLdoDataset has all the functionality of an LdoDataset with the added
+ * functionality of keeping track of fetched Solid Resources.
+ *
+ * It is recommended to use the { @link createSolidLdoDataset } to initialize
+ * this class
+ *
+ * @example
+ * ```typescript
+ * import { createSolidLdoDataset } from "@ldo/solid";
+ * import { ProfileShapeType } from "./.ldo/profile.shapeTypes.ts"
+ *
+ * // ...
+ *
+ * const solidLdoDataset = createSolidLdoDataset();
+ *
+ * const profileDocument = solidLdoDataset
+ * .getResource("https://example.com/profile");
+ * await profileDocument.read();
+ *
+ * const profile = solidLdoDataset
+ * .using(ProfileShapeType)
+ * .fromSubject("https://example.com/profile#me");
+ * ```
+ */
export class SolidLdoDataset extends LdoDataset {
+ /**
+ * @internal
+ */
public context: SolidLdoDatasetContext;
+ /**
+ * @param context - SolidLdoDatasetContext
+ * @param datasetFactory - An optional dataset factory
+ * @param initialDataset - A set of triples to initialize this dataset
+ */
constructor(
context: SolidLdoDatasetContext,
datasetFactory: DatasetFactory,
@@ -36,6 +68,23 @@ export class SolidLdoDataset extends LdoDataset {
this.context = context;
}
+ /**
+ * Retireves a representation (either a LeafResource or a ContainerResource)
+ * of a Solid Resource at the given URI. This resource represents the
+ * current state of the resource: whether it is currently fetched or in the
+ * process of fetching as well as some information about it.
+ *
+ * @param uri - the URI of the resource
+ * @param options - Special options for getting the resource
+ *
+ * @returns a Leaf or Container Resource
+ *
+ * @example
+ * ```typescript
+ * const profileDocument = solidLdoDataset
+ * .getResource("https://example.com/profile");
+ * ```
+ */
getResource(uri: ContainerUri, options?: ResourceGetterOptions): Container;
getResource(uri: LeafUri, options?: ResourceGetterOptions): Leaf;
getResource(uri: string, options?: ResourceGetterOptions): Leaf | Container;
@@ -44,7 +93,27 @@ export class SolidLdoDataset extends LdoDataset {
}
/**
- * commitChangesToPod
+ * Given dataset changes, commit all changes made to the proper place
+ * on Solid Pods.
+ *
+ * @param changes - A set of changes that should be applied to Solid Pods
+ *
+ * @returns an AggregateSuccess if successful and an AggregateError if not
+ *
+ * @example
+ * ```typescript
+ * const result = await solidLdoDataset.commitChangesToPod({
+ * added: createDataset([
+ * quad(namedNode("a"), namedNode("b"), namedNode("d"));
+ * ]),
+ * removed: createDataset([
+ * quad(namedNode("a"), namedNode("b"), namedNode("c"));
+ * ])
+ * });
+ * if (result.isError()) {
+ * // handle error
+ * }
+ * ```
*/
async commitChangesToPod(
changes: DatasetChanges,
@@ -98,21 +167,6 @@ export class SolidLdoDataset extends LdoDataset {
const errors = results.filter((result) => result[2].isError);
if (errors.length > 0) {
- // // Rollback errors
- // errors.forEach((error) => {
- // // Add the graph back to the quads
- // const added = error[1].added?.map((quad) =>
- // createQuad(quad.subject, quad.predicate, quad.object, error[0]),
- // );
- // const removed = error[1].removed?.map((quad) =>
- // createQuad(quad.subject, quad.predicate, quad.object, error[0]),
- // );
- // this.bulk({
- // added: removed,
- // removed: added,
- // });
- // });
-
return new AggregateError(
errors.map(
(result) => result[2] as UpdateResultError | InvalidUriError,
@@ -137,9 +191,9 @@ export class SolidLdoDataset extends LdoDataset {
* .usingType(shapeType)
* .write(...resources.map((r) => r.uri))
* .fromSubject(subject);
- * @param shapeType The shapetype to represent the data
- * @param subject A subject URI
- * @param resources The resources changes to should written to
+ * @param shapeType - The shapetype to represent the data
+ * @param subject - A subject URI
+ * @param resources - The resources changes to should written to
*/
createData(
shapeType: ShapeType,
diff --git a/packages/solid/src/SolidLdoDatasetContext.ts b/packages/solid/src/SolidLdoDatasetContext.ts
index 2a31044..c78b50b 100644
--- a/packages/solid/src/SolidLdoDatasetContext.ts
+++ b/packages/solid/src/SolidLdoDatasetContext.ts
@@ -1,16 +1,20 @@
-// import type TypedEmitter from "typed-emitter";
import type { ResourceStore } from "./ResourceStore";
import type { SolidLdoDataset } from "./SolidLdoDataset";
-// import type { DocumentError } from "./document/errors/DocumentError";
-
-// export type OnDocumentErrorCallback = (error: DocumentError) => void;
-
-// export type DocumentEventEmitter = TypedEmitter<{
-// documentError: OnDocumentErrorCallback;
-// }>;
+/**
+ * Context to be shared between aspects of a SolidLdoDataset
+ */
export interface SolidLdoDatasetContext {
+ /**
+ * A pointer to the parent SolidLdoDataset
+ */
solidLdoDataset: SolidLdoDataset;
+ /**
+ * The resource store of the SolidLdoDataset
+ */
resourceStore: ResourceStore;
+ /**
+ * Http fetch function
+ */
fetch: typeof fetch;
}
diff --git a/packages/solid/src/createSolidLdoDataset.ts b/packages/solid/src/createSolidLdoDataset.ts
index f9df1c7..3a58a84 100644
--- a/packages/solid/src/createSolidLdoDataset.ts
+++ b/packages/solid/src/createSolidLdoDataset.ts
@@ -6,12 +6,40 @@ import { createDataset, createDatasetFactory } from "@ldo/dataset";
import { ResourceStore } from "./ResourceStore";
import { guaranteeFetch } from "./util/guaranteeFetch";
+/**
+ * Options for createSolidDataset
+ */
export interface CreateSolidLdoDatasetOptions {
+ /**
+ * A fetch function. Most often, this is the fetch function from @inrupt/solid-clieht-authn-js
+ */
fetch?: typeof fetch;
+ /**
+ * An initial dataset
+ * @default A blank dataset
+ */
dataset?: Dataset;
+ /**
+ * An RDFJS DatasetFactory
+ * @default An extended RDFJS DatasetFactory
+ */
datasetFactory?: DatasetFactory;
}
+/**
+ * Creates a SolidLdoDataset
+ *
+ * @param options - CreateSolidLdoDatasetOptions
+ * @returns A SolidLdoDataset
+ *
+ * @example
+ * ```typescript
+ * import { createSolidLdoDataset } from "@ldo/solid";
+ * import { fetch } from "@inrupt/solid-client-authn-browswer";
+ *
+ * const solidLdoDataset = createSolidLdoDataset({ fetch });
+ * ```
+ */
export function createSolidLdoDataset(
options?: CreateSolidLdoDatasetOptions,
): SolidLdoDataset {
diff --git a/packages/solid/src/methods.ts b/packages/solid/src/methods.ts
index 8c94b64..5c4333d 100644
--- a/packages/solid/src/methods.ts
+++ b/packages/solid/src/methods.ts
@@ -13,9 +13,29 @@ import { _proxyContext, getProxyFromObject } from "@ldo/jsonld-dataset-proxy";
import type { SubscribableDataset } from "@ldo/subscribable-dataset";
/**
- * Begins tracking changes to eventually commit
- * @param input A linked data object to track changes on
- * @param resources
+ * Begins tracking changes to eventually commit.
+ *
+ * @param input - A linked data object to track changes on
+ * @param resource - A resource that all additions will eventually be committed to
+ * @param additionalResources - Any additional resources that changes will eventually be committed to
+ *
+ * @returns A transactable Linked Data Object
+ *
+ * @example
+ * ```typescript
+ * import { changeData } from "@ldo/solid";
+ *
+ * // ...
+ *
+ * const profile = solidLdoDataset
+ * .using(ProfileShapeType)
+ * .fromSubject("https://example.com/proifle#me");
+ * const resource = solidLdoDataset.getResource("https://example.com/profile");
+ *
+ * const cProfile = changeData(profile, resource);
+ * cProfile.name = "My New Name";
+ * await commitData(cProfile);
+ * ```
*/
export function changeData(
input: Type,
@@ -36,6 +56,24 @@ export function changeData(
/**
* Commits the transaction to the global dataset, syncing all subscribing
* components and Solid Pods
+ *
+ * @param input - A transactable linked data object
+ *
+ * @example
+ * ```typescript
+ * import { changeData } from "@ldo/solid";
+ *
+ * // ...
+ *
+ * const profile = solidLdoDataset
+ * .using(ProfileShapeType)
+ * .fromSubject("https://example.com/proifle#me");
+ * const resource = solidLdoDataset.getResource("https://example.com/profile");
+ *
+ * const cProfile = changeData(profile, resource);
+ * cProfile.name = "My New Name";
+ * await commitData(cProfile);
+ * ```
*/
export function commitData(
input: LdoBase,
diff --git a/packages/solid/src/requester/Requester.ts b/packages/solid/src/requester/BatchedRequester.ts
similarity index 76%
rename from packages/solid/src/requester/Requester.ts
rename to packages/solid/src/requester/BatchedRequester.ts
index 3d9229a..7c0b5a8 100644
--- a/packages/solid/src/requester/Requester.ts
+++ b/packages/solid/src/requester/BatchedRequester.ts
@@ -20,32 +20,72 @@ const READ_KEY = "read";
const CREATE_KEY = "createDataResource";
const DELETE_KEY = "delete";
-export abstract class Requester {
+/**
+ * @internal
+ *
+ * A singleton for handling batched requests
+ */
+export abstract class BatchedRequester {
+ /**
+ * @internal
+ * A request batcher to maintain state for ongoing requests
+ */
protected readonly requestBatcher = new RequestBatcher();
- // All intance variables
+ /**
+ * The uri of the resource
+ */
abstract readonly uri: string;
+
+ /**
+ * @internal
+ * SolidLdoDatasetContext for the parent SolidLdoDataset
+ */
protected context: SolidLdoDatasetContext;
+ /**
+ * @param context - SolidLdoDatasetContext for the parent SolidLdoDataset
+ */
constructor(context: SolidLdoDatasetContext) {
this.context = context;
}
+ /**
+ * Checks if the resource is currently making any request
+ * @returns true if the resource is making any requests
+ */
isLoading(): boolean {
return this.requestBatcher.isLoading(ANY_KEY);
}
+
+ /**
+ * Checks if the resource is currently executing a create request
+ * @returns true if the resource is currently executing a create request
+ */
isCreating(): boolean {
return this.requestBatcher.isLoading(CREATE_KEY);
}
+
+ /**
+ * Checks if the resource is currently executing a read request
+ * @returns true if the resource is currently executing a read request
+ */
isReading(): boolean {
return this.requestBatcher.isLoading(READ_KEY);
}
+
+ /**
+ * Checks if the resource is currently executing a delete request
+ * @returns true if the resource is currently executing a delete request
+ */
isDeletinng(): boolean {
return this.requestBatcher.isLoading(DELETE_KEY);
}
/**
* Read this resource.
+ * @returns A ReadLeafResult or a ReadContainerResult depending on the uri of
+ * this resource
*/
async read(): Promise {
const transaction = this.context.solidLdoDataset.startTransaction();
@@ -65,6 +105,7 @@ export abstract class Requester {
/**
* Delete this resource
+ * @returns A DeleteResult
*/
async delete(): Promise {
const transaction = this.context.solidLdoDataset.startTransaction();
@@ -84,8 +125,10 @@ export abstract class Requester {
/**
* Creates a Resource
- * @param overwrite: If true, this will orverwrite the resource if it already
+ * @param overwrite - If true, this will orverwrite the resource if it already
* exists
+ * @returns A ContainerCreateAndOverwriteResult or a
+ * LeafCreateAndOverwriteResult depending on this resource's URI
*/
createDataResource(
overwrite: true,
diff --git a/packages/solid/src/requester/ContainerRequester.ts b/packages/solid/src/requester/ContainerBatchedRequester.ts
similarity index 72%
rename from packages/solid/src/requester/ContainerRequester.ts
rename to packages/solid/src/requester/ContainerBatchedRequester.ts
index 8adc6a7..42d744b 100644
--- a/packages/solid/src/requester/ContainerRequester.ts
+++ b/packages/solid/src/requester/ContainerBatchedRequester.ts
@@ -1,6 +1,6 @@
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type { ContainerUri } from "../util/uriTypes";
-import { Requester } from "./Requester";
+import { BatchedRequester } from "./BatchedRequester";
import type { CheckRootResult } from "./requests/checkRootContainer";
import { checkRootContainer } from "./requests/checkRootContainer";
import type {
@@ -12,18 +12,39 @@ import { modifyQueueByMergingEventsWithTheSameKeys } from "./util/modifyQueueFun
export const IS_ROOT_CONTAINER_KEY = "isRootContainer";
-export class ContainerRequester extends Requester {
+/**
+ * @internal
+ *
+ * A singleton to handle batched requests for containers
+ */
+export class ContainerBatchedRequester extends BatchedRequester {
+ /**
+ * The URI of the container
+ */
readonly uri: ContainerUri;
+ /**
+ * @param uri - The URI of the container
+ * @param context - SolidLdoDatasetContext of the parent dataset
+ */
constructor(uri: ContainerUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
}
+ /**
+ * Reads the container
+ * @returns A ReadContainerResult
+ */
read(): Promise {
return super.read() as Promise;
}
+ /**
+ * Creates the container
+ * @param overwrite - If true, this will orverwrite the resource if it already
+ * exists
+ */
createDataResource(
overwrite: true,
): Promise;
@@ -41,6 +62,10 @@ export class ContainerRequester extends Requester {
>;
}
+ /**
+ * Checks to see if this container is a root container
+ * @returns A CheckRootResult
+ */
async isRootContainer(): Promise {
return this.requestBatcher.queueProcess({
name: IS_ROOT_CONTAINER_KEY,
diff --git a/packages/solid/src/requester/LeafRequester.ts b/packages/solid/src/requester/LeafBatchedRequester.ts
similarity index 79%
rename from packages/solid/src/requester/LeafRequester.ts
rename to packages/solid/src/requester/LeafBatchedRequester.ts
index 284ee23..c40b63a 100644
--- a/packages/solid/src/requester/LeafRequester.ts
+++ b/packages/solid/src/requester/LeafBatchedRequester.ts
@@ -3,7 +3,7 @@ import { mergeDatasetChanges } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type { LeafUri } from "../util/uriTypes";
-import { Requester } from "./Requester";
+import { BatchedRequester } from "./BatchedRequester";
import type {
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
@@ -16,26 +16,55 @@ import { uploadResource } from "./requests/uploadResource";
export const UPDATE_KEY = "update";
export const UPLOAD_KEY = "upload";
-export class LeafRequester extends Requester {
+/**
+ * @internal
+ *
+ * A singleton to handle batched requests for leafs
+ */
+export class LeafBatchedRequester extends BatchedRequester {
+ /**
+ * The URI of the leaf
+ */
readonly uri: LeafUri;
+ /**
+ * @param uri - the URI of the leaf
+ * @param context - SolidLdoDatasetContext of the parent dataset
+ */
constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
}
+ /**
+ * Checks if the resource is currently executing an update request
+ * @returns true if the resource is currently executing an update request
+ */
isUpdating(): boolean {
return this.requestBatcher.isLoading(UPDATE_KEY);
}
+ /**
+ * Checks if the resource is currently executing an upload request
+ * @returns true if the resource is currently executing an upload request
+ */
isUploading(): boolean {
return this.requestBatcher.isLoading(UPLOAD_KEY);
}
+ /**
+ * Reads the leaf
+ * @returns A ReadLeafResult
+ */
async read(): Promise {
return super.read() as Promise;
}
+ /**
+ * Creates the leaf as a data resource
+ * @param overwrite - If true, this will orverwrite the resource if it already
+ * exists
+ */
createDataResource(overwrite: true): Promise;
createDataResource(overwrite?: false): Promise;
createDataResource(
@@ -51,7 +80,7 @@ export class LeafRequester extends Requester {
/**
* Update the data on this resource
- * @param changes
+ * @param changes - DatasetChanges that should be applied to the Pod
*/
async updateDataResource(
changes: DatasetChanges,
@@ -78,9 +107,9 @@ export class LeafRequester extends Requester {
}
/**
- * Upload a binary
- * @param blob
- * @param mimeType
+ * Upload a binary at this resource's URI
+ * @param blob - A binary blob
+ * @param mimeType - the mime type of the blob
* @param overwrite: If true, will overwrite an existing file
*/
upload(
diff --git a/packages/solid/src/requester/requests/checkRootContainer.ts b/packages/solid/src/requester/requests/checkRootContainer.ts
index 8900f05..18f4988 100644
--- a/packages/solid/src/requester/requests/checkRootContainer.ts
+++ b/packages/solid/src/requester/requests/checkRootContainer.ts
@@ -11,13 +11,29 @@ import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import type { ContainerUri } from "../../util/uriTypes";
+/**
+ * checkRootContainer result
+ */
export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError;
+
+/**
+ * All possible errors checkRootResult can return
+ */
export type CheckRootResultError =
| HttpErrorResultType
| NoncompliantPodError
| UnexpectedHttpError
| UnexpectedResourceError;
+/**
+ * @internal
+ * Checks provided headers to see if a given URI is a root container as defined
+ * in the [solid specification section 4.1](https://solidproject.org/TR/protocol#storage-resource)
+ *
+ * @param uri - the URI of the container resource
+ * @param headers - headers returned when making a GET request to the resource
+ * @returns CheckRootContainerSuccess if there is not error
+ */
export function checkHeadersForRootContainer(
uri: ContainerUri,
headers: Headers,
@@ -39,6 +55,26 @@ export function checkHeadersForRootContainer(
};
}
+/**
+ * Performs a request to the Pod to check if the given URI is a root container
+ * as defined in the [solid specification section 4.1](https://solidproject.org/TR/protocol#storage-resource)
+ *
+ * @param uri - the URI of the container resource
+ * @param options - options variable to pass a fetch function
+ * @returns CheckResourceSuccess if there is no error
+ *
+ * @example
+ * ```typescript
+ * import { checkRootContainer } from "@ldo/solid";
+ * import { fetch } from "@inrupt/solid-client-authn-browser";
+ *
+ * const result = await checkRootContainer("https://example.com/", { fetch });
+ * if (!result.isError) {
+ * // true if the container is a root container
+ * console.log(result.isRootContainer);
+ * }
+ * ```
+ */
export async function checkRootContainer(
uri: ContainerUri,
options?: BasicRequestOptions,
diff --git a/packages/solid/src/requester/requests/createDataResource.ts b/packages/solid/src/requester/requests/createDataResource.ts
index b75386f..4c5bd5c 100644
--- a/packages/solid/src/requester/requests/createDataResource.ts
+++ b/packages/solid/src/requester/requests/createDataResource.ts
@@ -21,25 +21,117 @@ import type {
import { readResource } from "./readResource";
import type { DatasetRequestOptions } from "./requestOptions";
+/**
+ * All possible return values when creating and overwriting a container
+ */
export type ContainerCreateAndOverwriteResult =
| CreateSuccess
| CreateAndOverwriteResultErrors;
+
+/**
+ * All possible return values when creating and overwriting a leaf
+ */
export type LeafCreateAndOverwriteResult =
| CreateSuccess
| CreateAndOverwriteResultErrors;
+
+/**
+ * All possible return values when creating a container if absent
+ */
export type ContainerCreateIfAbsentResult =
| CreateSuccess
| Exclude
| CreateIfAbsentResultErrors;
+
+/**
+ * All possible return values when creating a leaf if absent
+ */
export type LeafCreateIfAbsentResult =
| CreateSuccess
| Exclude
| CreateIfAbsentResultErrors;
+/**
+ * All possible errors returned by creating and overwriting a resource
+ */
export type CreateAndOverwriteResultErrors = DeleteResultError | CreateErrors;
+
+/**
+ * All possible errors returned by creating a resource if absent
+ */
export type CreateIfAbsentResultErrors = ReadResultError | CreateErrors;
+
+/**
+ * All possible errors returned by creating a resource
+ */
export type CreateErrors = HttpErrorResultType | UnexpectedResourceError;
+/**
+ * Creates a data resource (RDF resource) at the provided URI. This resource
+ * could also be a container.
+ *
+ * @param uri - The URI of the resource
+ * @param overwrite - If true, the request will overwrite any previous resource
+ * at this URI.
+ * @param options - Options to provide a fetch function and a local dataset to
+ * update.
+ * @returns One of many create results depending on the input
+ *
+ * @example
+ * `createDataResource` can be used to create containers.
+ *
+ * ```typescript
+ * import { createDataResource } from "@ldo/solid";
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const result = await createDataResource(
+ * "https://example.com/container/",
+ * true,
+ * { fetch },
+ * );
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ *
+ * @example
+ * `createDataResource` can also create a blank data resource at the provided
+ * URI.
+ *
+ * ```typescript
+ * import { createDataResource } from "@ldo/solid";
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const result = await createDataResource(
+ * "https://example.com/container/someResource.ttl",
+ * true,
+ * { fetch },
+ * );
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ *
+ * @example
+ * Any local RDFJS dataset passed to the `options` field will be updated with
+ * any new RDF data from the create process.
+ *
+ * ```typescript
+ * import { createDataResource } from "@ldo/solid";
+ * import { createDataset } from "@ldo/dataset"
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const localDataset = createDataset();
+ * const result = await createDataResource(
+ * "https://example.com/container/someResource.ttl",
+ * true,
+ * { fetch, dataset: localDataset },
+ * );
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
export function createDataResource(
uri: ContainerUri,
overwrite: true,
diff --git a/packages/solid/src/requester/requests/deleteResource.ts b/packages/solid/src/requester/requests/deleteResource.ts
index 0479d05..d3de47e 100644
--- a/packages/solid/src/requester/requests/deleteResource.ts
+++ b/packages/solid/src/requester/requests/deleteResource.ts
@@ -8,9 +8,44 @@ import { HttpErrorResult } from "../results/error/HttpErrorResult";
import type { DeleteSuccess } from "../results/success/DeleteSuccess";
import type { DatasetRequestOptions } from "./requestOptions";
+/**
+ * All possible return values for deleteResource
+ */
export type DeleteResult = DeleteSuccess | DeleteResultError;
+
+/**
+ * All possible errors that can be returned by deleteResource
+ */
export type DeleteResultError = HttpErrorResultType | UnexpectedResourceError;
+/**
+ * Deletes a resource on a Pod at a given URL.
+ *
+ * @param uri - The URI for the resource that should be deleted
+ * @param options - Options to provide a fetch function and a local dataset to
+ * update.
+ * @returns a DeleteResult
+ *
+ * @example
+ * `deleteResource` will send a request to a Solid Pod using the provided fetch
+ * function. A local dataset can also be provided. It will be updated with any
+ * new information from the delete.
+ *
+ * ```typescript
+ * import { deleteResource } from "@ldo/solid";
+ * import { createDataset } from "@ldo/dataset"
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const localDataset = createDataset();
+ * const result = await deleteResource(
+ * "https://example.com/container/someResource.ttl",
+ * { fetch, dataset: localDataset },
+ * );
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
export async function deleteResource(
uri: string,
options?: DatasetRequestOptions,
diff --git a/packages/solid/src/requester/requests/readResource.ts b/packages/solid/src/requester/requests/readResource.ts
index 1643ff3..fbfb4db 100644
--- a/packages/solid/src/requester/requests/readResource.ts
+++ b/packages/solid/src/requester/requests/readResource.ts
@@ -21,21 +21,65 @@ import { guaranteeFetch } from "../../util/guaranteeFetch";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { checkHeadersForRootContainer } from "./checkRootContainer";
+/**
+ * All possible return values for reading a leaf
+ */
export type ReadLeafResult =
| BinaryReadSuccess
| DataReadSuccess
| AbsentReadSuccess
| ReadResultError;
+
+/**
+ * All possible return values for reading a container
+ */
export type ReadContainerResult =
| ContainerReadSuccess
| AbsentReadSuccess
| ReadResultError;
+
+/**
+ * All possible errors the readResource function can return
+ */
export type ReadResultError =
| HttpErrorResultType
| NoncompliantPodError
| UnexpectedHttpError
| UnexpectedResourceError;
+/**
+ * Reads resource at a provided URI and returns the result
+ *
+ * @param uri - The URI of the resource
+ * @param options - Options to provide a fetch function and a local dataset to
+ * update.
+ * @returns ReadResult
+ *
+ * @example
+ * ```typescript
+ * import { deleteResource } from "@ldo/solid";
+ * import { createDataset } from "@ldo/dataset"
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const dataset = createDataset();
+ * const result = await readResource(
+ * "https://example.com/container/someResource.ttl",
+ * { fetch, dataset },
+ * );
+ * if (!result.isError) {
+ * if (result.type === "absentReadSuccess") {
+ * // There was no problem reading the resource, but it doesn't exist
+ * } else if (result.type === "dataReadSuccess") {
+ * // The resource was read and it is an RDF resource. The dataset provided
+ * // dataset will also be loaded with the data from the resource
+ * } else if (result.type === "binaryReadSuccess") {
+ * // The resource is a binary
+ * console.log(result.blob);
+ * console.log(result.mimeType);
+ * }
+ * }
+ * ```
+ */
export async function readResource(
uri: LeafUri,
options?: DatasetRequestOptions,
diff --git a/packages/solid/src/requester/requests/requestOptions.ts b/packages/solid/src/requester/requests/requestOptions.ts
index 830e04d..3232911 100644
--- a/packages/solid/src/requester/requests/requestOptions.ts
+++ b/packages/solid/src/requester/requests/requestOptions.ts
@@ -1,10 +1,22 @@
import type { BulkEditableDataset } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
+/**
+ * Request Options to be passed to request functions
+ */
export interface BasicRequestOptions {
+ /**
+ * A fetch function usually imported from @inrupt/solid-client-authn-js
+ */
fetch?: typeof fetch;
}
+/**
+ * Request options with a dataset component
+ */
export interface DatasetRequestOptions extends BasicRequestOptions {
+ /**
+ * A dataset to be modified with any new information obtained from a request
+ */
dataset?: BulkEditableDataset;
}
diff --git a/packages/solid/src/requester/requests/updateDataResource.ts b/packages/solid/src/requester/requests/updateDataResource.ts
index a053fde..9b0237f 100644
--- a/packages/solid/src/requester/requests/updateDataResource.ts
+++ b/packages/solid/src/requester/requests/updateDataResource.ts
@@ -9,9 +9,57 @@ import { HttpErrorResult } from "../results/error/HttpErrorResult";
import type { UpdateSuccess } from "../results/success/UpdateSuccess";
import type { DatasetRequestOptions } from "./requestOptions";
+/**
+ * All return values for updateDataResource
+ */
export type UpdateResult = UpdateSuccess | UpdateResultError;
+
+/**
+ * All errors updateDataResource can return
+ */
export type UpdateResultError = HttpErrorResultType | UnexpectedResourceError;
+/**
+ * Updates a specific data resource with the provided dataset changes
+ *
+ * @param uri - the URI of the data resource
+ * @param datasetChanges - A set of triples added and removed from this dataset
+ * @param options - Options to provide a fetch function and a local dataset to
+ * update.
+ * @returns An UpdateResult
+ *
+ * @example
+ * ```typescript
+ * import {
+ * updateDataResource,
+ * transactionChanges,
+ * changeData,
+ * createSolidLdoDataset,
+ * } from "@ldo/solid";
+ * import { fetch } from "@inrupt/solid-client-authn-browser";
+ *
+ * // Initialize an LDO dataset
+ * const solidLdoDataset = createSolidLdoDataset();
+ * // Get a Linked Data Object
+ * const profile = solidLdoDataset
+ * .usingType(ProfileShapeType)
+ * .fromSubject("https://example.com/profile#me");
+ * // Create a transaction to change data
+ * const cProfile = changeData(
+ * profile,
+ * solidLdoDataset.getResource("https://example.com/profile"),
+ * );
+ * cProfile.name = "John Doe";
+ * // Get data in "DatasetChanges" form
+ * const datasetChanges = transactionChanges(someLinkedDataObject);
+ * // Use "updateDataResource" to apply the changes
+ * const result = await updateDataResource(
+ * "https://example.com/profile",
+ * datasetChanges,
+ * { fetch, dataset: solidLdoDataset },
+ * );
+ * ```
+ */
export async function updateDataResource(
uri: LeafUri,
datasetChanges: DatasetChanges,
diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts
index 742ba7b..fcce355 100644
--- a/packages/solid/src/requester/requests/uploadResource.ts
+++ b/packages/solid/src/requester/requests/uploadResource.ts
@@ -15,6 +15,38 @@ import { deleteResource } from "./deleteResource";
import { readResource } from "./readResource";
import type { DatasetRequestOptions } from "./requestOptions";
+/**
+ * Uploads a binary resource at the provided URI
+ *
+ * @param uri - The URI of the resource
+ * @param overwrite - If true, the request will overwrite any previous resource
+ * at this URI.
+ * @param options - Options to provide a fetch function and a local dataset to
+ * update.
+ * @returns One of many create results depending on the input
+ *
+ * @example
+ * Any local RDFJS dataset passed to the `options` field will be updated with
+ * any new RDF data from the create process.
+ *
+ * ```typescript
+ * import { createDataResource } from "@ldo/solid";
+ * import { createDataset } from "@ldo/dataset"
+ * import { fetch } from "@inrupt/solid-client-autn-js";
+ *
+ * const localDataset = createDataset();
+ * const result = await uploadResource(
+ * "https://example.com/container/someResource.txt",
+ * new Blob("some text."),
+ * "text/txt",
+ * true,
+ * { fetch, dataset: localDataset },
+ * );
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
export function uploadResource(
uri: LeafUri,
blob: Blob,
diff --git a/packages/solid/src/requester/results/RequesterResult.ts b/packages/solid/src/requester/results/RequesterResult.ts
index 978ad6d..2c51cfb 100644
--- a/packages/solid/src/requester/results/RequesterResult.ts
+++ b/packages/solid/src/requester/results/RequesterResult.ts
@@ -1,3 +1,6 @@
+/**
+ * A type returned by all request functions
+ */
export interface RequesterResult {
type: string;
isError: boolean;
diff --git a/packages/solid/src/requester/results/error/AccessControlError.ts b/packages/solid/src/requester/results/error/AccessControlError.ts
index ba08ccf..a4c5a18 100644
--- a/packages/solid/src/requester/results/error/AccessControlError.ts
+++ b/packages/solid/src/requester/results/error/AccessControlError.ts
@@ -1,9 +1,17 @@
/* istanbul ignore file */
import { ResourceError } from "./ErrorResult";
+/**
+ * An error: Could not fetch access rules
+ */
export class AccessRuleFetchError extends ResourceError {
readonly type = "accessRuleFetchError" as const;
+ /**
+ * @param uri - The uri of the resource for which access rules couldn't be
+ * fetched
+ * @param message - A custom message for the error
+ */
constructor(uri: string, message?: string) {
super(uri, message || `${uri} had trouble fetching access rules.`);
}
diff --git a/packages/solid/src/requester/results/error/ErrorResult.ts b/packages/solid/src/requester/results/error/ErrorResult.ts
index 2efaa56..84a29ad 100644
--- a/packages/solid/src/requester/results/error/ErrorResult.ts
+++ b/packages/solid/src/requester/results/error/ErrorResult.ts
@@ -1,27 +1,61 @@
import type { RequesterResult } from "../RequesterResult";
+/**
+ * A result indicating that the request failed in some kind of way
+ */
export abstract class ErrorResult extends Error implements RequesterResult {
+ /**
+ * Indicates the specific type of error
+ */
abstract type: string;
+
+ /**
+ * Always true
+ */
readonly isError = true as const;
+ /**
+ * @param message - a custom message for the error
+ */
constructor(message?: string) {
super(message || "An unkown error was encountered.");
}
}
+/**
+ * An error for a specific resource
+ */
export abstract class ResourceError extends ErrorResult {
+ /**
+ * The URI of the resource
+ */
readonly uri: string;
+ /**
+ * @param uri - The URI of the resource
+ * @param message - A custom message for the error
+ */
constructor(uri: string, message?: string) {
super(message || `An unkown error for ${uri}`);
this.uri = uri;
}
}
+/**
+ * An error that aggregates many errors
+ */
export class AggregateError extends ErrorResult {
readonly type = "aggregateError" as const;
+
+ /**
+ * A list of all errors returned
+ */
readonly errors: ErrorType[];
+ /**
+ * @param errors - List of all errors returned
+ * @param message - A custom message for the error
+ */
constructor(
errors: (ErrorType | AggregateError)[],
message?: string,
@@ -47,15 +81,35 @@ export class AggregateError extends ErrorResult {
}
}
+/**
+ * Represents some error that isn't handled under other errors. This is usually
+ * returned when something threw an error that LDO did not expect.
+ */
export class UnexpectedResourceError extends ResourceError {
readonly type = "unexpectedResourceError" as const;
+
+ /**
+ * The error that was thrown
+ */
error: Error;
+ /**
+ * @param uri - URI of the resource
+ * @param error - The error that was thrown
+ */
constructor(uri: string, error: Error) {
super(uri, error.message);
this.error = error;
}
+ /**
+ * @internal
+ *
+ * Creates an UnexpectedResourceError from a thrown error
+ * @param uri - The URI of the resource
+ * @param err - The thrown error
+ * @returns an UnexpectedResourceError
+ */
static fromThrown(uri: string, err: unknown) {
if (err instanceof Error) {
return new UnexpectedResourceError(uri, err);
diff --git a/packages/solid/src/requester/results/error/HttpErrorResult.ts b/packages/solid/src/requester/results/error/HttpErrorResult.ts
index 94653c5..283bfe9 100644
--- a/packages/solid/src/requester/results/error/HttpErrorResult.ts
+++ b/packages/solid/src/requester/results/error/HttpErrorResult.ts
@@ -1,15 +1,37 @@
import { ResourceError } from "./ErrorResult";
+/**
+ * A set of standard errors that can be returned as a result of an HTTP request
+ */
export type HttpErrorResultType =
| ServerHttpError
| UnexpectedHttpError
| UnauthenticatedHttpError;
+/**
+ * An error caused by an HTTP request
+ */
export abstract class HttpErrorResult extends ResourceError {
+ /**
+ * The status of the HTTP request
+ */
public readonly status: number;
+
+ /**
+ * Headers returned by the HTTP request
+ */
public readonly headers: Headers;
+
+ /**
+ * Response returned by the HTTP request
+ */
public readonly response: Response;
+ /**
+ * @param uri - URI of the resource
+ * @param response - The response returned by the HTTP requests
+ * @param message - A custom message for the error
+ */
constructor(uri: string, response: Response, message?: string) {
super(
uri,
@@ -21,6 +43,11 @@ export abstract class HttpErrorResult extends ResourceError {
this.response = response;
}
+ /**
+ * Checks to see if a given response does not constitute an HTTP Error
+ * @param response - The response of the request
+ * @returns true if the response does not constitute an HTTP Error
+ */
static isnt(response: Response) {
return (
!(response.status >= 200 && response.status < 300) &&
@@ -29,6 +56,13 @@ export abstract class HttpErrorResult extends ResourceError {
);
}
+ /**
+ * Checks a given response to see if it is a ServerHttpError, an
+ * UnauthenticatedHttpError or a some unexpected error.
+ * @param uri - The uri of the request
+ * @param response - The response of the request
+ * @returns An error if the response calls for it. Undefined if not.
+ */
static checkResponse(uri: string, response: Response) {
if (ServerHttpError.is(response)) {
return new ServerHttpError(uri, response);
@@ -43,21 +77,43 @@ export abstract class HttpErrorResult extends ResourceError {
}
}
+/**
+ * An unexpected error as a result of an HTTP request. This is usually returned
+ * when the HTTP request returns a status code LDO does not recognize.
+ */
export class UnexpectedHttpError extends HttpErrorResult {
readonly type = "unexpectedHttpError" as const;
}
+/**
+ * An UnauthenticatedHttpError triggers when a Solid server returns a 401 status
+ * indicating that the request is not authenticated.
+ */
export class UnauthenticatedHttpError extends HttpErrorResult {
readonly type = "unauthenticatedError" as const;
+ /**
+ * Indicates if a specific response constitutes an UnauthenticatedHttpError
+ * @param response - The request response
+ * @returns true if this response constitutes an UnauthenticatedHttpError
+ */
static is(response: Response) {
return response.status === 401;
}
}
+/**
+ * A ServerHttpError triggers when a Solid server returns a 5XX status,
+ * indicating that an error happened on the server.
+ */
export class ServerHttpError extends HttpErrorResult {
readonly type = "serverError" as const;
+ /**
+ * Indicates if a specific response constitutes a ServerHttpError
+ * @param response - The request response
+ * @returns true if this response constitutes a ServerHttpError
+ */
static is(response: Response) {
return response.status >= 500 && response.status < 600;
}
diff --git a/packages/solid/src/requester/results/error/InvalidUriError.ts b/packages/solid/src/requester/results/error/InvalidUriError.ts
index 9d4ae6d..e1c3201 100644
--- a/packages/solid/src/requester/results/error/InvalidUriError.ts
+++ b/packages/solid/src/requester/results/error/InvalidUriError.ts
@@ -1,5 +1,9 @@
import { ResourceError } from "./ErrorResult";
+/**
+ * An InvalidUriError is returned when a URI was provided that is not a valid
+ * URI.
+ */
export class InvalidUriError extends ResourceError {
readonly type = "invalidUriError" as const;
diff --git a/packages/solid/src/requester/results/error/NoncompliantPodError.ts b/packages/solid/src/requester/results/error/NoncompliantPodError.ts
index df87f5b..b981414 100644
--- a/packages/solid/src/requester/results/error/NoncompliantPodError.ts
+++ b/packages/solid/src/requester/results/error/NoncompliantPodError.ts
@@ -1,7 +1,16 @@
import { ResourceError } from "./ErrorResult";
+/**
+ * A NoncompliantPodError is returned when the server responded in a way that is
+ * not compliant with the Solid specification.
+ */
export class NoncompliantPodError extends ResourceError {
readonly type = "noncompliantPodError" as const;
+
+ /**
+ * @param uri - the URI of the requested resource
+ * @param message - a custom message for the error
+ */
constructor(uri: string, message?: string) {
super(
uri,
diff --git a/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts b/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
index 5f75605..e2e12cb 100644
--- a/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
+++ b/packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
@@ -1,6 +1,13 @@
import type { ResourceSuccess } from "./SuccessResult";
+/**
+ * Indicates that the request to check if a resource is the root container was
+ * a success.
+ */
export interface CheckRootContainerSuccess extends ResourceSuccess {
type: "checkRootContainerSuccess";
+ /**
+ * True if this resoure is the root container
+ */
isRootContainer: boolean;
}
diff --git a/packages/solid/src/requester/results/success/CreateSuccess.ts b/packages/solid/src/requester/results/success/CreateSuccess.ts
index 190209f..3d83b9f 100644
--- a/packages/solid/src/requester/results/success/CreateSuccess.ts
+++ b/packages/solid/src/requester/results/success/CreateSuccess.ts
@@ -1,6 +1,13 @@
import type { ResourceSuccess } from "./SuccessResult";
+/**
+ * Indicates that the request to create the resource was a success.
+ */
export interface CreateSuccess extends ResourceSuccess {
type: "createSuccess";
+ /**
+ * True if there was a resource that existed before at the given URI that was
+ * overwritten
+ */
didOverwrite: boolean;
}
diff --git a/packages/solid/src/requester/results/success/DeleteSuccess.ts b/packages/solid/src/requester/results/success/DeleteSuccess.ts
index 4cb131f..0345a1c 100644
--- a/packages/solid/src/requester/results/success/DeleteSuccess.ts
+++ b/packages/solid/src/requester/results/success/DeleteSuccess.ts
@@ -1,6 +1,14 @@
import type { ResourceSuccess } from "./SuccessResult";
+/**
+ * Indicates that the request to delete a resource was a success.
+ */
export interface DeleteSuccess extends ResourceSuccess {
type: "deleteSuccess";
+
+ /**
+ * True if there was a resource at the provided URI that was deleted. False if
+ * a resource didn't exist.
+ */
resourceExisted: boolean;
}
diff --git a/packages/solid/src/requester/results/success/ReadSuccess.ts b/packages/solid/src/requester/results/success/ReadSuccess.ts
index 419c984..756642a 100644
--- a/packages/solid/src/requester/results/success/ReadSuccess.ts
+++ b/packages/solid/src/requester/results/success/ReadSuccess.ts
@@ -1,28 +1,66 @@
import type { ResourceSuccess, SuccessResult } from "./SuccessResult";
+/**
+ * Indicates that the request to read a resource was a success
+ */
export interface ReadSuccess extends ResourceSuccess {
+ /**
+ * True if the resource was recalled from local memory rather than a recent
+ * request
+ */
recalledFromMemory: boolean;
}
+/**
+ * Indicates that the read request was successful and that the resource
+ * retrieved was a binary resource.
+ */
export interface BinaryReadSuccess extends ReadSuccess {
type: "binaryReadSuccess";
+ /**
+ * The raw data for the binary resource
+ */
blob: Blob;
+ /**
+ * The mime type of the binary resource
+ */
mimeType: string;
}
+/**
+ * Indicates that the read request was successful and that the resource
+ * retrieved was a data (RDF) resource.
+ */
export interface DataReadSuccess extends ReadSuccess {
type: "dataReadSuccess";
}
+/**
+ * Indicates that the read request was successful and that the resource
+ * retrieved was a container resource.
+ */
export interface ContainerReadSuccess extends ReadSuccess {
type: "containerReadSuccess";
+ /**
+ * True if this container is a root container
+ */
isRootContainer: boolean;
}
+/**
+ * Indicates that the read request was successful, but no resource exists at
+ * the provided URI.
+ */
export interface AbsentReadSuccess extends ReadSuccess {
type: "absentReadSuccess";
}
+/**
+ * A helper function that checks to see if a result is a ReadSuccess result
+ *
+ * @param result - the result to check
+ * @returns true if the result is a ReadSuccessResult result
+ */
export function isReadSuccess(result: SuccessResult): result is ReadSuccess {
return (
result.type === "binaryReadSuccess" ||
diff --git a/packages/solid/src/requester/results/success/SuccessResult.ts b/packages/solid/src/requester/results/success/SuccessResult.ts
index f79275c..35b891c 100644
--- a/packages/solid/src/requester/results/success/SuccessResult.ts
+++ b/packages/solid/src/requester/results/success/SuccessResult.ts
@@ -1,15 +1,31 @@
import type { RequesterResult } from "../RequesterResult";
+/**
+ * Indicates that some action taken by LDO was a success
+ */
export interface SuccessResult extends RequesterResult {
isError: false;
}
+/**
+ * Indicates that a request to a resource was aa success
+ */
export interface ResourceSuccess extends SuccessResult {
+ /**
+ * The URI of the resource
+ */
uri: string;
}
+/**
+ * A grouping of multiple successes as a result of an action
+ */
export interface AggregateSuccess
extends SuccessResult {
type: "aggregateSuccess";
+
+ /**
+ * An array of all successesses
+ */
results: SuccessType[];
}
diff --git a/packages/solid/src/requester/results/success/Unfetched.ts b/packages/solid/src/requester/results/success/Unfetched.ts
index 6eacacb..9d8db38 100644
--- a/packages/solid/src/requester/results/success/Unfetched.ts
+++ b/packages/solid/src/requester/results/success/Unfetched.ts
@@ -1,5 +1,8 @@
import type { ResourceSuccess } from "./SuccessResult";
+/**
+ * Indicates that a specific resource is unfetched
+ */
export interface Unfetched extends ResourceSuccess {
type: "unfetched";
}
diff --git a/packages/solid/src/requester/results/success/UpdateSuccess.ts b/packages/solid/src/requester/results/success/UpdateSuccess.ts
index 9bbea45..a22a06c 100644
--- a/packages/solid/src/requester/results/success/UpdateSuccess.ts
+++ b/packages/solid/src/requester/results/success/UpdateSuccess.ts
@@ -1,9 +1,16 @@
import type { ResourceSuccess } from "./SuccessResult";
+/**
+ * Indicates that an update request to a resource was successful
+ */
export interface UpdateSuccess extends ResourceSuccess {
type: "updateSuccess";
}
+/**
+ * Indicates that an update request to the default graph was successful. This
+ * data was not written to a Pod. It was only written locally.
+ */
export interface UpdateDefaultGraphSuccess extends ResourceSuccess {
type: "updateDefaultGraphSuccess";
}
diff --git a/packages/solid/src/requester/util/modifyQueueFuntions.ts b/packages/solid/src/requester/util/modifyQueueFuntions.ts
index 8304934..780b5d1 100644
--- a/packages/solid/src/requester/util/modifyQueueFuntions.ts
+++ b/packages/solid/src/requester/util/modifyQueueFuntions.ts
@@ -1,6 +1,16 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { WaitingProcess } from "../../util/RequestBatcher";
+/**
+ * @internal
+ *
+ * A helper function for a common way to modify the batch queue. This merges
+ * the incoming request with the currently executing request or the last request
+ * in the queue if its keys are the same.
+ *
+ * @param key - the key of the incoming request
+ * @returns a modifyQueue function
+ */
export function modifyQueueByMergingEventsWithTheSameKeys(key: string) {
return (
queue: WaitingProcess[],
diff --git a/packages/solid/src/resource/Container.ts b/packages/solid/src/resource/Container.ts
index f6ecc2b..e21fcde 100644
--- a/packages/solid/src/resource/Container.ts
+++ b/packages/solid/src/resource/Container.ts
@@ -1,5 +1,5 @@
import { namedNode } from "@rdfjs/data-model";
-import { ContainerRequester } from "../requester/ContainerRequester";
+import { ContainerBatchedRequester } from "../requester/ContainerBatchedRequester";
import type {
CheckRootResult,
CheckRootResultError,
@@ -32,12 +32,47 @@ import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult";
+/**
+ * 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 Container extends Resource {
+ /**
+ * The URI of the container
+ */
readonly uri: ContainerUri;
- protected requester: ContainerRequester;
+
+ /**
+ * @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
| ReadContainerResult
@@ -45,18 +80,48 @@ export class Container extends Resource {
| ContainerCreateIfAbsentResult
| CheckRootResult;
+ /**
+ * @param uri - The uri of the container
+ * @param context - SolidLdoDatasetContext for the parent dataset
+ */
constructor(uri: ContainerUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
- this.requester = new ContainerRequester(uri, context);
+ this.requester = new ContainerBatchedRequester(uri, context);
this.status = { isError: false, type: "unfetched", uri };
}
+ /**
+ * 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
+ /**
+ * ===========================================================================
+ * 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,
): void {
@@ -66,12 +131,29 @@ export class Container extends Resource {
}
}
+ /**
+ * Reads the container
+ * @returns A read result
+ *
+ * @example
+ * ```typescript
+ * const result = await container.read();
+ * if (result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
async read(): Promise> {
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(): ResourceResult {
if (this.isAbsent()) {
return {
@@ -93,6 +175,20 @@ export class Container extends Resource {
}
}
+ /**
+ * 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<
ResourceResult
> {
@@ -101,7 +197,17 @@ export class Container extends Resource {
>;
}
- // Parent Container Methods
+ /**
+ * ===========================================================================
+ * PARENT CONTAINER METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * @internal
+ * Checks if this container is a root container by making a request
+ * @returns CheckRootResult
+ */
private async checkIfIsRootContainer(): Promise<
ResourceResult
> {
@@ -113,6 +219,24 @@ export class Container extends Resource {
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
+ *
+ * @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 {
const parentContainerResult = await this.getParentContainer();
if (parentContainerResult?.isError) return parentContainerResult;
@@ -122,6 +246,27 @@ export class Container extends Resource {
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<
Container | CheckRootResultError | undefined
> {
@@ -140,6 +285,20 @@ export class Container extends Resource {
return this.context.resourceStore.get(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(): (Leaf | Container)[] {
const childQuads = this.context.solidLdoDataset.match(
namedNode(this.uri),
@@ -152,6 +311,23 @@ export class Container extends Resource {
});
}
+ /**
+ * 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: ContainerUri): Container;
child(slug: LeafUri): Leaf;
child(slug: string): Leaf | Container;
@@ -159,7 +335,29 @@ export class Container extends Resource {
return this.context.resourceStore.get(`${this.uri}${slug}`);
}
- // Child Creators
+ /**
+ * ===========================================================================
+ * 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: ContainerUri,
): Promise>;
@@ -185,6 +383,23 @@ export class Container extends Resource {
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: ContainerUri,
): Promise>;
@@ -210,6 +425,27 @@ export class Container extends Resource {
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: LeafUri,
blob: Blob,
@@ -218,6 +454,27 @@ export class Container extends Resource {
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: LeafUri,
blob: Blob,
@@ -226,6 +483,20 @@ export class Container extends Resource {
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<
ResourceResult<
| AggregateSuccess>
@@ -260,6 +531,17 @@ export class Container extends Resource {
};
}
+ /**
+ * 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<
ResourceResult<
DeleteResult | AggregateError,
@@ -273,6 +555,18 @@ export class Container extends Resource {
return { ...deleteResult, resource: 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<
ResourceResult
> {
@@ -282,6 +576,18 @@ export class Container extends Resource {
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<
ResourceResult
> {
diff --git a/packages/solid/src/resource/Leaf.ts b/packages/solid/src/resource/Leaf.ts
index 11ca125..91b5329 100644
--- a/packages/solid/src/resource/Leaf.ts
+++ b/packages/solid/src/resource/Leaf.ts
@@ -1,6 +1,6 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
import type { Quad } from "@rdfjs/types";
-import { LeafRequester } from "../requester/LeafRequester";
+import { LeafBatchedRequester } from "../requester/LeafBatchedRequester";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type {
LeafCreateAndOverwriteResult,
@@ -24,11 +24,40 @@ import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult";
+/**
+ * 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 Leaf extends Resource {
+ /**
+ * The URI of the leaf
+ */
readonly uri: LeafUri;
- protected requester: LeafRequester;
+
+ /**
+ * @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
| ReadLeafResult
@@ -36,34 +65,134 @@ export class Leaf extends Resource {
| LeafCreateIfAbsentResult
| UpdateResult;
+ /**
+ * @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: LeafUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
- this.requester = new LeafRequester(uri, context);
+ this.requester = new LeafBatchedRequester(uri, context);
this.status = { isError: false, type: "unfetched", uri };
}
- // Getters
+ /**
+ * ===========================================================================
+ * 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;
@@ -71,7 +200,17 @@ export class Leaf extends Resource {
return !this.binaryData;
}
- // Read Methods
+ /**
+ * ===========================================================================
+ * 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,
): void {
@@ -83,12 +222,29 @@ export class Leaf extends Resource {
}
}
+ /**
+ * 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> {
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(): ResourceResult {
if (this.isAbsent()) {
return {
@@ -119,33 +275,185 @@ export class Leaf extends Resource {
}
}
+ /**
+ * 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> {
return super.readIfUnfetched() as Promise<
ResourceResult
>;
}
- // Parent Container Methods
+ /**
+ * ===========================================================================
+ * 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 = leaf.getParentContainer();
+ * if (!leafParent.isError) {
+ * // Logs "https://example.com/container/"
+ * console.log(leafParent.uri);
+ * }
+ * ```
+ */
getParentContainer(): Container {
const parentUri = getParentUri(this.uri)!;
return this.context.resourceStore.get(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);
+ * }
+ * ```
+ */
getRootContainer(): Promise {
const parent = this.getParentContainer();
return parent.getRootContainer();
}
- // Delete Methods
+ /**
+ * ===========================================================================
+ * DELETE METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * @internal
+ * A helper method updates this leaf's internal state upon delete success
+ * @param result - the result of the delete success
+ */
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
this.binaryData = undefined;
}
- // Create Methods
+ /**
+ * 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 {
+ return this.handleDelete();
+ }
+
+ /**
+ * ===========================================================================
+ * 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): void {
this.binaryData = undefined;
}
- // Upload Methods
+ /**
+ * 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<
+ ResourceResult
+ > {
+ 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<
+ ResourceResult
+ > {
+ 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,
@@ -159,6 +467,25 @@ export class Leaf extends Resource {
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,
@@ -172,6 +499,43 @@ export class Leaf extends Resource {
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,
): Promise> {
@@ -183,26 +547,4 @@ export class Leaf extends Resource {
this.emitThisAndParent();
return { ...result, resource: this };
}
-
- async delete(): Promise {
- return this.handleDelete();
- }
-
- async createAndOverwrite(): Promise<
- ResourceResult
- > {
- const createResult =
- (await this.handleCreateAndOverwrite()) as LeafCreateAndOverwriteResult;
- if (createResult.isError) return createResult;
- return { ...createResult, resource: this };
- }
-
- async createIfAbsent(): Promise<
- ResourceResult
- > {
- const createResult =
- (await this.handleCreateIfAbsent()) as LeafCreateIfAbsentResult;
- if (createResult.isError) return createResult;
- return { ...createResult, resource: this };
- }
}
diff --git a/packages/solid/src/resource/Resource.ts b/packages/solid/src/resource/Resource.ts
index 2b3e87b..e03f91c 100644
--- a/packages/solid/src/resource/Resource.ts
+++ b/packages/solid/src/resource/Resource.ts
@@ -9,7 +9,7 @@ import type {
ReadContainerResult,
ReadLeafResult,
} from "../requester/requests/readResource";
-import type { Requester } from "../requester/Requester";
+import type { BatchedRequester } from "../requester/BatchedRequester";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type { AccessRule } from "../requester/results/success/AccessRule";
import type { SetAccessRulesResult } from "../requester/requests/setAccessRules";
@@ -29,60 +29,271 @@ import type { ResourceResult } from "./resourceResult/ResourceResult";
import type { Container } from "./Container";
import type { Leaf } from "./Leaf";
+/**
+ * Statuses shared between both Leaf and Container
+ */
export type SharedStatuses = Unfetched | DeleteResult | CreateSuccess;
+/**
+ * Represents the current status of a specific Resource on a Pod as known by LDO.
+ */
export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
update: () => void;
}>) {
- // All intance variables
+ /**
+ * @internal
+ * The SolidLdoDatasetContext from the Parent Dataset
+ */
protected readonly context: SolidLdoDatasetContext;
+
+ /**
+ * The uri of the resource
+ */
abstract readonly uri: string;
+
+ /**
+ * The type of resource (leaf or container)
+ */
abstract readonly type: string;
+
+ /**
+ * The status of the last request made for this resource
+ */
abstract status: RequesterResult;
- protected abstract readonly requester: Requester;
+
+ /**
+ * @internal
+ * Batched Requester for the Resource
+ */
+ protected abstract readonly requester: BatchedRequester;
+
+ /**
+ * @internal
+ * True if this resource has been fetched at least once
+ */
protected didInitialFetch: boolean = false;
+
+ /**
+ * @internal
+ * True if this resource has been fetched but does not exist
+ */
protected absent: boolean | undefined;
+ /**
+ * @param context - SolidLdoDatasetContext for the parent dataset
+ */
constructor(context: SolidLdoDatasetContext) {
super();
this.context = context;
}
- // Loading Methods
+ /**
+ * ===========================================================================
+ * GETTERS
+ * ===========================================================================
+ */
+
+ /**
+ * Checks to see if this resource is loading in any way
+ * @returns true if the resource is currently loading
+ *
+ * @example
+ * ```typescript
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isLoading())
+ * });
+ * // Logs "true"
+ * console.log(resource.isLoading());
+ * ```
+ */
isLoading(): boolean {
return this.requester.isLoading();
}
+
+ /**
+ * Checks to see if this resource is being created
+ * @returns true if the resource is currently being created
+ *
+ * @example
+ * ```typescript
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isCreating())
+ * });
+ * // Logs "true"
+ * console.log(resource.isCreating());
+ * ```
+ */
isCreating(): boolean {
return this.requester.isCreating();
}
+
+ /**
+ * Checks to see if this resource is being read
+ * @returns true if the resource is currently being read
+ *
+ * @example
+ * ```typescript
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isReading())
+ * });
+ * // Logs "true"
+ * console.log(resource.isReading());
+ * ```
+ */
isReading(): boolean {
return this.requester.isReading();
}
+
+ /**
+ * Checks to see if this resource is being deleted
+ * @returns true if the resource is currently being deleted
+ *
+ * @example
+ * ```typescript
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isDeleting())
+ * });
+ * // Logs "true"
+ * console.log(resource.isDeleting());
+ * ```
+ */
isDeleting(): boolean {
return this.requester.isDeletinng();
}
+
+ /**
+ * Checks to see if this resource is being read for the first time
+ * @returns true if the resource is currently being read for the first time
+ *
+ * @example
+ * ```typescript
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isDoingInitialFetch())
+ * });
+ * // Logs "true"
+ * console.log(resource.isDoingInitialFetch());
+ * ```
+ */
isDoingInitialFetch(): boolean {
return this.isReading() && !this.isFetched();
}
+
+ /**
+ * Checks to see if this resource is being read for a subsequent time
+ * @returns true if the resource is currently being read for a subsequent time
+ *
+ * @example
+ * ```typescript
+ * await resource.read();
+ * resource.read().then(() => {
+ * // Logs "false"
+ * console.log(resource.isCreating())
+ * });
+ * // Logs "true"
+ * console.log(resource.isCreating());
+ * ```
+ */
isReloading(): boolean {
return this.isReading() && this.isFetched();
}
- // Checkers
+ /**
+ * ===========================================================================
+ * CHECKERS
+ * ===========================================================================
+ */
+
+ /**
+ * Check to see if this resource has been fetched
+ * @returns true if this resource has been fetched before
+ *
+ * @example
+ * ```typescript
+ * // Logs "false"
+ * console.log(resource.isFetched());
+ * const result = await resource.read();
+ * if (!result.isError) {
+ * // Logs "true"
+ * console.log(resource.isFetched());
+ * }
+ * ```
+ */
isFetched(): boolean {
return this.didInitialFetch;
}
+
+ /**
+ * Check to see if this resource is currently unfetched
+ * @returns true if the resource is currently unfetched
+ *
+ * @example
+ * ```typescript
+ * // Logs "true"
+ * console.log(resource.isUnetched());
+ * const result = await resource.read();
+ * if (!result.isError) {
+ * // Logs "false"
+ * console.log(resource.isUnfetched());
+ * }
+ * ```
+ */
isUnfetched(): boolean {
return !this.didInitialFetch;
}
+
+ /**
+ * Is this resource currently absent (it does not exist)
+ * @returns true if the resource is absent, false if not, undefined if unknown
+ *
+ * @example
+ * ```typescript
+ * // Logs "undefined"
+ * console.log(resource.isAbsent());
+ * const result = resource.read();
+ * if (!result.isError) {
+ * // False if the resource exists, true if it does not
+ * console.log(resource.isAbsent());
+ * }
+ * ```
+ */
isAbsent(): boolean | undefined {
return this.absent;
}
+
+ /**
+ * Is this resource currently present on the Pod
+ * @returns false if the resource is absent, true if not, undefined if unknown
+ *
+ * @example
+ * ```typescript
+ * // Logs "undefined"
+ * console.log(resource.isPresent());
+ * const result = resource.read();
+ * if (!result.isError) {
+ * // True if the resource exists, false if it does not
+ * console.log(resource.isPresent());
+ * }
+ * ```
+ */
isPresent(): boolean | undefined {
return this.absent === undefined ? undefined : !this.absent;
}
- // Helper Methods
+ /**
+ * ===========================================================================
+ * HELPER METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * @internal
+ * Emits an update event for both this resource and the parent
+ */
protected emitThisAndParent() {
this.emit("update");
const parentUri = getParentUri(this.uri);
@@ -92,12 +303,27 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
}
}
- // Read Methods
+ /**
+ * ===========================================================================
+ * READ METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * @internal
+ * A helper method updates this resource's internal state upon read success
+ * @param result - the result of the read success
+ */
protected updateWithReadSuccess(result: ReadSuccess) {
this.absent = result.type === "absentReadSuccess";
this.didInitialFetch = true;
}
+ /**
+ * @internal
+ * A helper method that handles the core functions for reading
+ * @returns ReadResult
+ */
protected async handleRead(): Promise {
const result = await this.requester.read();
this.status = result;
@@ -107,15 +333,27 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return result;
}
+ /**
+ * @internal
+ * Converts the current state of this resource to a readResult
+ * @returns a ReadResult
+ */
protected abstract toReadResult(): ResourceResult<
ReadLeafResult | ReadContainerResult,
Container | Leaf
>;
+ /**
+ * Reads the resource
+ */
abstract read(): Promise<
ResourceResult
>;
+ /**
+ * Reads the resource if it isn't fetched yet
+ * @returns a ReadResult
+ */
async readIfUnfetched(): Promise<
ResourceResult
> {
@@ -127,12 +365,27 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return this.read();
}
- // Delete Methods
+ /**
+ * ===========================================================================
+ * DELETE METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * @internal
+ * A helper method updates this resource's internal state upon delete success
+ * @param result - the result of the delete success
+ */
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
this.absent = true;
this.didInitialFetch = true;
}
+ /**
+ * @internal
+ * Helper method that handles the core functions for deleting a resource
+ * @returns DeleteResult
+ */
protected async handleDelete(): Promise {
const result = await this.requester.delete();
this.status = result;
@@ -142,7 +395,16 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return result;
}
- // Create Methods
+ /**
+ * ===========================================================================
+ * CREATE METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * A helper method updates this resource's internal state upon create success
+ * @param _result - the result of the create success
+ */
protected updateWithCreateSuccess(result: ResourceSuccess) {
this.absent = false;
this.didInitialFetch = true;
@@ -151,6 +413,18 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
}
}
+ /**
+ * Creates a resource at this URI and overwrites any that already exists
+ * @returns CreateAndOverwriteResult
+ *
+ * @example
+ * ```typescript
+ * const result = await resource.createAndOverwrite();
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
abstract createAndOverwrite(): Promise<
ResourceResult<
ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult,
@@ -158,6 +432,12 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
>
>;
+ /**
+ * @internal
+ * Helper method that handles the core functions for creating and overwriting
+ * a resource
+ * @returns DeleteResult
+ */
protected async handleCreateAndOverwrite(): Promise<
ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult
> {
@@ -169,6 +449,18 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return result;
}
+ /**
+ * Creates a resource at this URI if the resource doesn't already exist
+ * @returns CreateIfAbsentResult
+ *
+ * @example
+ * ```typescript
+ * const result = await leaf.createIfAbsent();
+ * if (!result.isError) {
+ * // Do something
+ * }
+ * ```
+ */
abstract createIfAbsent(): Promise<
ResourceResult<
ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult,
@@ -176,6 +468,12 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
>
>;
+ /**
+ * @internal
+ * Helper method that handles the core functions for creating a resource if
+ * absent
+ * @returns DeleteResult
+ */
protected async handleCreateIfAbsent(): Promise<
ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult
> {
@@ -187,7 +485,29 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return result;
}
- // Parent Container Methods -- Remember to change for Container
+ /**
+ * ===========================================================================
+ * PARENT CONTAINER METHODS
+ * ===========================================================================
+ */
+
+ /**
+ * Gets the root container for this resource.
+ * @returns The root container for this resource
+ *
+ * @example
+ * Suppose the root container is at `https://example.com/`
+ *
+ * ```typescript
+ * const resource = ldoSolidDataset
+ * .getResource("https://example.com/container/resource.ttl");
+ * const rootContainer = await resource.getRootContainer();
+ * if (!rootContainer.isError) {
+ * // logs "https://example.com/"
+ * console.log(rootContainer.uri);
+ * }
+ * ```
+ */
abstract getRootContainer(): Promise;
// Access Rules Methods
diff --git a/packages/solid/src/resource/resourceResult/ResourceResult.ts b/packages/solid/src/resource/resourceResult/ResourceResult.ts
index 4b411e8..3132fd1 100644
--- a/packages/solid/src/resource/resourceResult/ResourceResult.ts
+++ b/packages/solid/src/resource/resourceResult/ResourceResult.ts
@@ -2,11 +2,17 @@ import type { RequesterResult } from "../../requester/results/RequesterResult";
import type { Container } from "../Container";
import type { Leaf } from "../Leaf";
+/**
+ * Adds an additional field "resource" to SuccessResults.
+ */
export type ResourceSuccess<
Result extends RequesterResult,
ResourceType extends Leaf | Container,
> = Result & { resource: ResourceType };
+/**
+ * Adds an additional field "resource" to Results.
+ */
export type ResourceResult<
Result extends RequesterResult,
ResourceType extends Leaf | Container,
diff --git a/packages/solid/src/util/RequestBatcher.ts b/packages/solid/src/util/RequestBatcher.ts
index d2b0fc0..cb78d66 100644
--- a/packages/solid/src/util/RequestBatcher.ts
+++ b/packages/solid/src/util/RequestBatcher.ts
@@ -10,16 +10,32 @@ export interface WaitingProcess {
export const ANY_KEY = "any";
+/**
+ * Options for processes that are waiting to execute
+ */
export interface WaitingProcessOptions {
+ /**
+ * The name of the process like "read" or "delete"
+ */
name: string;
+ /**
+ * The arguements supplied to the process
+ */
args: Args;
+ /**
+ * A function that will be triggered when it's time to execute this process
+ * @param args - arguments supplied to the process
+ * @returns a return type
+ */
perform: (...args: Args) => Promise;
/**
- *
- * @param processQueue The current process queue
- * @param currentlyProcessing: The Process that is currently executing
- * @param args provided args
- * @returns A WaitingProcess that this request should listen to, or undefined if it should create its own
+ * A custom function to modify the queue based on the current state of the
+ * queue
+ * @param processQueue - The current process queue
+ * @param currentlyProcessing - The Process that is currently executing
+ * @param args - provided args
+ * @returns A WaitingProcess that this request should listen to, or undefined
+ * if it should create its own
*/
modifyQueue: (
processQueue: WaitingProcess[],
@@ -30,15 +46,35 @@ export interface WaitingProcessOptions {
}
/**
- * Request Batcher
+ * @internal
+ * A utility for batching a request
*/
export class RequestBatcher {
+ /**
+ * A mapping between a process key and the last time in UTC a process of that
+ * key was executed.
+ */
private lastRequestTimestampMap: Record = {};
+
+ /**
+ * A pointer to the current process the batcher is working on
+ */
private currentlyProcessing: WaitingProcess | undefined =
undefined;
+
+ /**
+ * A queue of upcoming processes
+ */
private processQueue: WaitingProcess[] = [];
+
+ /**
+ * The amount of time (in milliseconds) between requests of the same key
+ */
public batchMillis: number;
+ /**
+ * @param options - options, including the value for batchMillis
+ */
constructor(
options?: Partial<{
batchMillis: number;
@@ -47,11 +83,21 @@ export class RequestBatcher {
this.batchMillis = options?.batchMillis || 1000;
}
+ /**
+ * Check if the request batcher is currently working on a process
+ * @param key - the key of the process to check
+ * @returns true if the batcher is currently working on the provided process
+ */
public isLoading(key: string): boolean {
if (key === ANY_KEY) return !!this.currentlyProcessing;
return this.currentlyProcessing?.name === key;
}
+ /**
+ * Triggers the next process in the queue or triggers a timeout to wait to
+ * execute the next process in the queue if not enough time has passed since
+ * the last process was triggered.
+ */
private triggerOrWaitProcess() {
if (!this.processQueue[0]) {
return;
@@ -103,6 +149,11 @@ export class RequestBatcher {
}
}
+ /**
+ * Adds a process to the queue and waits for the process to be complete
+ * @param options - WaitingProcessOptions
+ * @returns A promise that resolves when the process resolves
+ */
public async queueProcess(
options: WaitingProcessOptions,
): Promise {
diff --git a/packages/solid/src/util/guaranteeFetch.ts b/packages/solid/src/util/guaranteeFetch.ts
index 3d210de..987b7b4 100644
--- a/packages/solid/src/util/guaranteeFetch.ts
+++ b/packages/solid/src/util/guaranteeFetch.ts
@@ -1,5 +1,12 @@
import crossFetch from "cross-fetch";
+/**
+ * @internal
+ * Guantees that some kind of fetch is available
+ *
+ * @param fetchInput - A potential fetch object
+ * @returns a proper fetch object. Cross-fetch is default
+ */
export function guaranteeFetch(fetchInput?: typeof fetch): typeof fetch {
return fetchInput || crossFetch;
}
diff --git a/packages/solid/src/util/rdfUtils.ts b/packages/solid/src/util/rdfUtils.ts
index f7f8d30..e458f55 100644
--- a/packages/solid/src/util/rdfUtils.ts
+++ b/packages/solid/src/util/rdfUtils.ts
@@ -16,6 +16,13 @@ export const ldpBasicContainer = namedNode(
"http://www.w3.org/ns/ldp#BasicContainer",
);
+/**
+ * @internal
+ * Gets the URI of a parent according the the Solid Spec
+ *
+ * @param uri - the child URI
+ * @returns A parent URI or undefined if not possible
+ */
export function getParentUri(uri: string): ContainerUri | undefined {
const urlObject = new URL(uri);
const pathItems = urlObject.pathname.split("/");
@@ -33,12 +40,26 @@ export function getParentUri(uri: string): ContainerUri | undefined {
return urlObject.toString() as ContainerUri;
}
+/**
+ * @internal
+ * Gets the slug (last part of the path) for a given URI
+ *
+ * @param uri - the full URI
+ * @returns the slug of the URI
+ */
export function getSlug(uri: string): string {
const urlObject = new URL(uri);
const pathItems = urlObject.pathname.split("/");
return pathItems[pathItems.length - 1] || pathItems[pathItems.length - 2];
}
+/**
+ * @internal
+ * Deletes mention of a resource from the provided dataset
+ *
+ * @param resourceUri - the resource to delete
+ * @param dataset - dataset to modify
+ */
export function deleteResourceRdfFromContainer(
resourceUri: string,
dataset: Dataset,
@@ -54,6 +75,13 @@ export function deleteResourceRdfFromContainer(
}
}
+/**
+ * @internal
+ * Adds a resource to a container in an RDF dataset
+ *
+ * @param resourceUri - the resource to add
+ * @param dataset - the dataset to modify
+ */
export function addResourceRdfToContainer(
resourceUri: string,
dataset: Dataset,
@@ -74,6 +102,14 @@ export function addResourceRdfToContainer(
}
}
+/**
+ * @internal
+ * Adds raw turtle to the provided dataset
+ * @param rawTurtle - String of raw turtle
+ * @param dataset - the dataset to modify
+ * @param baseUri - base URI to parsing turtle
+ * @returns Undefined if successful, noncompliantPodError if not
+ */
export async function addRawTurtleToDataset(
rawTurtle: string,
dataset: Dataset,
diff --git a/packages/solid/src/util/splitChangesByGraph.ts b/packages/solid/src/util/splitChangesByGraph.ts
index 326c292..007253f 100644
--- a/packages/solid/src/util/splitChangesByGraph.ts
+++ b/packages/solid/src/util/splitChangesByGraph.ts
@@ -3,16 +3,35 @@ import type { GraphNode, DatasetChanges } from "@ldo/rdf-utils";
import type { Quad } from "@rdfjs/types";
import { defaultGraph, namedNode, quad as createQuad } from "@rdfjs/data-model";
+/**
+ * @internal
+ * Converts an RDFJS Graph Node to a string hash
+ * @param graphNode - the node to convert
+ * @returns a unique string corresponding to the node
+ */
export function graphNodeToString(graphNode: GraphNode): string {
return graphNode.termType === "DefaultGraph"
? "defaultGraph()"
: graphNode.value;
}
+/**
+ * @internal
+ * Converts a unique string to a GraphNode
+ * @param input - the unique string
+ * @returns A graph node
+ */
export function stringToGraphNode(input: string): GraphNode {
return input === "defaultGraph()" ? defaultGraph() : namedNode(input);
}
+/**
+ * Splits all changes in a DatasetChanges into individual DatasetChanges grouped
+ * by the quad graph.
+ * @param changes - Changes to split
+ * @returns A map between the quad graph and the changes associated with that
+ * graph
+ */
export function splitChangesByGraph(
changes: DatasetChanges,
): Map> {
diff --git a/packages/solid/src/util/uriTypes.ts b/packages/solid/src/util/uriTypes.ts
index 24e4d58..aa15152 100644
--- a/packages/solid/src/util/uriTypes.ts
+++ b/packages/solid/src/util/uriTypes.ts
@@ -1,21 +1,47 @@
+/**
+ * A LeafUri 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 ContainerUri = `${string}/${NonPathnameEnding}` & {};
+
+/**
+ * A LeafUri is any URI that does not have a pahtname that ends in a "/". It
+ * represents a data resource or a binary resource. Not a container.
+ */
export type LeafUri =
// The & {} allows for alias preservation
// eslint-disable-next-line @typescript-eslint/ban-types
`${string}${EveryLegalPathnameCharacterOtherThanSlash}${NonPathnameEnding}` & {};
+/**
+ * Checks if a provided string is a Container URI
+ * @param uri - the string to check
+ * @returns true if the string is a container URI
+ */
export function isContainerUri(uri: string): uri is ContainerUri {
const url = new URL(uri);
return url.pathname.endsWith("/");
}
+/**
+ * Checks if a provided string is a leaf URI
+ * @param uri - the string to check
+ * @returns true if the string is a leaf URI
+ */
export function isLeafUri(uri: string): uri is LeafUri {
return !isContainerUri(uri);
}
+/**
+ * @internal
+ */
type NonPathnameEnding = "" | `?${string}` | `#${string}`;
+
+/**
+ * @internal
+ */
type EveryLegalPathnameCharacterOtherThanSlash =
| "A"
| "B"
diff --git a/packages/solid/typedoc.json b/packages/solid/typedoc.json
index 57a416c..c0e7b5d 100644
--- a/packages/solid/typedoc.json
+++ b/packages/solid/typedoc.json
@@ -2,5 +2,6 @@
"entryPoints": ["src/index.ts"],
"out": "docs",
"allReflectionsHaveOwnDocument": true,
- "hideInPageTOC": true
+ "hideInPageTOC": true,
+ "hideBreadcrumbs": true,
}
\ No newline at end of file