Before interface refactor

main
Ailin Luca 2 years ago
parent 8eceab9f3d
commit 50c44354dd
  1. 10
      packages/demo-react/src/dashboard/BuildMainContainer.tsx
  2. 33
      packages/solid/src/SolidLdoDataset.ts
  3. 37
      packages/solid/src/requester/ContainerRequester.ts
  4. 93
      packages/solid/src/requester/LeafRequester.ts
  5. 151
      packages/solid/src/requester/Requester.ts
  6. 9
      packages/solid/src/requester/requestResults/AbsentResult.ts
  7. 51
      packages/solid/src/requester/requestResults/AccessRule.ts
  8. 18
      packages/solid/src/requester/requestResults/BinaryResult.ts
  9. 12
      packages/solid/src/requester/requestResults/CommitChangesSuccess.ts
  10. 27
      packages/solid/src/requester/requestResults/DataResult.ts
  11. 7
      packages/solid/src/requester/requestResults/RequesterResult.ts
  12. 69
      packages/solid/src/requester/requests/checkRootContainer.ts
  13. 128
      packages/solid/src/requester/requests/createDataResource.ts
  14. 48
      packages/solid/src/requester/requests/deleteResource.ts
  15. 10
      packages/solid/src/requester/requests/getAccessRules.ts
  16. 105
      packages/solid/src/requester/requests/readResource.ts
  17. 9
      packages/solid/src/requester/requests/requestOptions.ts
  18. 10
      packages/solid/src/requester/requests/requestParams.ts
  19. 28
      packages/solid/src/requester/requests/setAccessRules.ts
  20. 47
      packages/solid/src/requester/requests/updateDataResource.ts
  21. 73
      packages/solid/src/requester/requests/uploadResource.ts
  22. 8
      packages/solid/src/requester/results/RequesterResult.ts
  23. 68
      packages/solid/src/requester/results/error/ErrorResult.ts
  24. 10
      packages/solid/src/requester/results/error/HttpErrorResult.ts
  25. 5
      packages/solid/src/requester/results/error/InvalidUriError.ts
  26. 11
      packages/solid/src/requester/results/error/NoncompliantPodError.ts
  27. 20
      packages/solid/src/requester/results/success/AccessRule.ts
  28. 11
      packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
  29. 11
      packages/solid/src/requester/results/success/CreateSuccess.ts
  30. 11
      packages/solid/src/requester/results/success/DeleteSuccess.ts
  31. 56
      packages/solid/src/requester/results/success/ReadSuccess.ts
  32. 30
      packages/solid/src/requester/results/success/SuccessResult.ts
  33. 5
      packages/solid/src/requester/results/success/Unfetched.ts
  34. 5
      packages/solid/src/requester/results/success/UpdateSuccess.ts
  35. 212
      packages/solid/src/resource/Container.ts
  36. 157
      packages/solid/src/resource/Leaf.ts
  37. 150
      packages/solid/src/resource/Resource.ts
  38. 10
      packages/solid/src/resource/ResourceResult.ts
  39. 14
      packages/solid/src/resource/resourceResults/CreateResourceSuccess.ts
  40. 14
      packages/solid/src/resource/resourceResults/DeleteResourceSuccess.ts
  41. 12
      packages/solid/src/resource/resourceResults/GetRootContainerSuccess.ts
  42. 72
      packages/solid/src/resource/resourceResults/ReadResourceSuccess.ts
  43. 11
      packages/solid/src/resource/resourceResults/UpdateResourceSuccess.ts
  44. 5
      packages/solid/src/util/guaranteeFetch.ts
  45. 12
      packages/solid/src/util/rdfUtils.ts

@ -18,12 +18,14 @@ export const BuildMainContainer: FunctionComponent<{
useEffect(() => {
if (session.webId) {
const webIdResource = getResource(session.webId as LeafUri);
webIdResource.getRootContainer().then(async (rootContainer) => {
if (rootContainer.type === "error") {
alert(rootContainer.message);
webIdResource.getRootContainer().then(async (rootContainerResult) => {
if (rootContainerResult.isError) {
alert(rootContainerResult.message);
return;
}
const mainContainer = getResource(`${rootContainer.uri}demo-react/`);
const mainContainer = getResource(
`${rootContainerResult.rootContainer.uri}demo-react/`,
);
setMainContainer(mainContainer);
await mainContainer.read();
if (mainContainer.isAbsent()) {

@ -1,10 +1,14 @@
import { LdoDataset } from "@ldo/ldo";
import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils";
import type { Dataset, DatasetFactory, Quad } from "@rdfjs/types";
import { CommitChangesSuccess } from "./requester/requestResults/CommitChangesSuccess";
import { InvalidUriError } from "./requester/requestResults/DataResult";
import { AggregateError } from "./requester/requestResults/ErrorResult";
import type { UpdateResultError } from "./requester/requests/updateDataResource";
import type {
UpdateResult,
UpdateResultError,
} from "./requester/requests/updateDataResource";
import { AggregateError } from "./requester/results/error/ErrorResult";
import { InvalidUriError } from "./requester/results/error/InvalidUriError";
import { AggregateSuccess } from "./requester/results/success/SuccessResult";
import type { UpdateSuccess } from "./requester/results/success/UpdateSuccess";
import type { Container } from "./resource/Container";
import type { Leaf } from "./resource/Leaf";
import type { ResourceGetterOptions } from "./ResourceStore";
@ -35,20 +39,25 @@ export class SolidLdoDataset extends LdoDataset {
async commitChangesToPod(
changes: DatasetChanges<Quad>,
): Promise<
CommitChangesSuccess | AggregateError<UpdateResultError | InvalidUriError>
| AggregateSuccess<UpdateSuccess>
| AggregateError<UpdateResultError | InvalidUriError>
> {
const changesByGraph = splitChangesByGraph(changes);
const results: [
GraphNode,
DatasetChanges<Quad>,
UpdateResultError | InvalidUriError | Leaf | { type: "defaultGraph" },
UpdateResult | InvalidUriError | { type: "defaultGraph"; isError: false },
][] = await Promise.all(
Array.from(changesByGraph.entries()).map(
async ([graph, datasetChanges]) => {
if (graph.termType === "DefaultGraph") {
// Undefined means that this is the default graph
this.bulk(datasetChanges);
return [graph, datasetChanges, { type: "defaultGraph" }];
return [
graph,
datasetChanges,
{ type: "defaultGraph", isError: false },
];
}
if (isContainerUri(graph.value)) {
return [
@ -67,20 +76,20 @@ export class SolidLdoDataset extends LdoDataset {
);
// If one has errored, return error
const errors = results.filter((result) => result[2].type === "error");
const errors = results.filter((result) => result[2].isError);
if (errors.length > 0) {
return new AggregateError(
"",
errors.map(
(result) => result[2] as UpdateResultError | InvalidUriError,
),
);
}
return new CommitChangesSuccess(
"",
return new AggregateSuccess(
results
.map((result) => result[2])
.filter((result): result is Leaf => result.type === "leaf"),
.filter(
(result): result is UpdateSuccess => result.type === "updateSuccess",
),
);
}
}

@ -1,14 +1,49 @@
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type { ContainerUri } from "../util/uriTypes";
import { Requester } from "./Requester";
import type { CheckRootResult } from "./requests/checkRootContainer";
import { checkRootContainer } from "./requests/checkRootContainer";
import type {
ContainerCreateAndOverwriteResult,
ContainerCreateIfAbsentResult,
} from "./requests/createDataResource";
import type { ReadContainerResult } from "./requests/readResource";
export const IS_ROOT_CONTAINER_KEY = "isRootContainer";
export class ContainerRequester extends Requester {
readonly uri: ContainerUri;
constructor(uri: ContainerUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
}
read(): Promise<ReadContainerResult> {
return super.read() as Promise<ReadContainerResult>;
}
createDataResource(
overwrite: true,
): Promise<ContainerCreateAndOverwriteResult>;
createDataResource(overwrite?: false): Promise<ContainerCreateIfAbsentResult>;
createDataResource(
overwrite?: boolean,
): Promise<ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult>;
createDataResource(
overwrite?: boolean,
): Promise<
ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult
> {
return super.createDataResource(overwrite) as Promise<
ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult
>;
}
async isRootContainer(): Promise<CheckRootResult> {
return this.requestBatcher.queueProcess({
name: IS_ROOT_CONTAINER_KEY,
args: [{ uri: this.uri, fetch: this.context.fetch }],
args: [this.uri as ContainerUri, { fetch: this.context.fetch }],
perform: checkRootContainer,
modifyQueue: (queue, isLoading) => {
if (queue.length === 0) {

@ -1,17 +1,54 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
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 type {
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "./requests/createDataResource";
import type { ReadLeafResult } from "./requests/readResource";
import type { UpdateResult } from "./requests/updateDataResource";
import { updateDataResource } from "./requests/updateDataResource";
import { uploadResource } from "./requests/uploadResource";
export const UPDATE_KEY = "update";
export const UPLOAD_KEY = "upload";
export class LeafRequester extends Requester {
readonly uri: LeafUri;
constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
}
isUpdating(): boolean {
return this.requestBatcher.isLoading(UPDATE_KEY);
}
isUploading(): boolean {
return this.requestBatcher.isLoading(UPLOAD_KEY);
}
async read(): Promise<ReadLeafResult> {
return super.read() as Promise<ReadLeafResult>;
}
createDataResource(overwrite: true): Promise<LeafCreateAndOverwriteResult>;
createDataResource(overwrite?: false): Promise<LeafCreateIfAbsentResult>;
createDataResource(
overwrite?: boolean,
): Promise<LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult>;
createDataResource(
overwrite?: boolean,
): Promise<LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult> {
return super.createDataResource(overwrite) as Promise<
LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult
>;
}
/**
* Update the data on this resource
* @param changes
@ -22,9 +59,9 @@ export class LeafRequester extends Requester {
const result = await this.requestBatcher.queueProcess({
name: UPDATE_KEY,
args: [
{ uri: this.uri, fetch: this.context.fetch },
this.uri,
changes,
this.context.solidLdoDataset,
{ fetch: this.context.fetch, dataset: this.context.solidLdoDataset },
],
perform: updateDataResource,
modifyQueue: (queue, isLoading, [, changes]) => {
@ -39,4 +76,56 @@ export class LeafRequester extends Requester {
});
return result;
}
/**
* Upload a binary
* @param blob
* @param mimeType
* @param overwrite: If true, will overwrite an existing file
*/
upload(
blob: Blob,
mimeType: string,
overwrite: true,
): Promise<LeafCreateAndOverwriteResult>;
upload(
blob: Blob,
mimeType: string,
overwrite?: false,
): Promise<LeafCreateIfAbsentResult>;
upload(
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<LeafCreateAndOverwriteResult | LeafCreateIfAbsentResult>;
async upload(
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<LeafCreateAndOverwriteResult | LeafCreateIfAbsentResult> {
const transaction = this.context.solidLdoDataset.startTransaction();
const result = await this.requestBatcher.queueProcess({
name: UPLOAD_KEY,
args: [
this.uri,
blob,
mimeType,
overwrite,
{ dataset: transaction, fetch: this.context.fetch },
],
perform: uploadResource,
modifyQueue: (queue, isLoading, args) => {
const lastElementInQueue = queue[queue.length - 1];
return (
lastElementInQueue &&
lastElementInQueue.name === UPLOAD_KEY &&
!!lastElementInQueue.args[3] === !!args[3]
);
},
});
if (!result.isError) {
transaction.commit();
}
return result;
}
}

@ -1,34 +1,32 @@
import { ANY_KEY, RequestBatcher } from "../util/RequestBatcher";
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type {
CreateResult,
CreateResultWithoutOverwrite,
ContainerCreateAndOverwriteResult,
ContainerCreateIfAbsentResult,
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "./requests/createDataResource";
import { createDataResource } from "./requests/createDataResource";
import type { ReadResult } from "./requests/readResource";
import { readResource } from "./requests/readResource";
import type {
UploadResult,
UploadResultWithoutOverwrite,
} from "./requests/uploadResource";
import { uploadResource } from "./requests/uploadResource";
ReadContainerResult,
ReadLeafResult,
} from "./requests/readResource";
import { readResource } from "./requests/readResource";
import type { DeleteResult } from "./requests/deleteResource";
import { deleteResource } from "./requests/deleteResource";
const READ_KEY = "read";
const CREATE_KEY = "createDataResource";
const UPLOAD_KEY = "upload";
const DELETE_KEY = "delete";
export abstract class Requester {
protected readonly requestBatcher = new RequestBatcher();
// All intance variables
readonly uri: string;
abstract readonly uri: string;
protected context: SolidLdoDatasetContext;
constructor(uri: string, context: SolidLdoDatasetContext) {
this.uri = uri;
constructor(context: SolidLdoDatasetContext) {
this.context = context;
}
@ -38,9 +36,6 @@ export abstract class Requester {
isCreating(): boolean {
return this.requestBatcher.isLoading(CREATE_KEY);
}
isUploading(): boolean {
return this.requestBatcher.isLoading(UPLOAD_KEY);
}
isReading(): boolean {
return this.requestBatcher.isLoading(READ_KEY);
}
@ -51,11 +46,11 @@ export abstract class Requester {
/**
* Read this resource.
*/
async read(): Promise<ReadResult> {
async read(): Promise<ReadLeafResult | ReadContainerResult> {
const transaction = this.context.solidLdoDataset.startTransaction();
const result = await this.requestBatcher.queueProcess({
name: READ_KEY,
args: [{ uri: this.uri, transaction, fetch: this.context.fetch }],
args: [this.uri, { dataset: transaction, fetch: this.context.fetch }],
perform: readResource,
modifyQueue: (queue, isLoading) => {
if (queue.length === 0) {
@ -65,119 +60,81 @@ export abstract class Requester {
}
},
});
if (result.type !== "error") {
if (!result.isError) {
transaction.commit();
}
return result;
}
/**
* Creates a Resource
* @param overwrite: If true, this will orverwrite the resource if it already
* exists
* Delete this resource
*/
async createDataResource(
overwrite?: false,
): Promise<CreateResultWithoutOverwrite>;
async createDataResource(overwrite: true): Promise<CreateResult>;
async createDataResource(
overwrite?: boolean,
): Promise<CreateResultWithoutOverwrite | CreateResult>;
async createDataResource(
overwrite?: boolean,
): Promise<CreateResultWithoutOverwrite> {
async delete(): Promise<DeleteResult> {
const transaction = this.context.solidLdoDataset.startTransaction();
const result = await this.requestBatcher.queueProcess({
name: CREATE_KEY,
args: [
{ uri: this.uri, transaction, fetch: this.context.fetch },
overwrite,
],
perform: createDataResource,
modifyQueue: (queue, isLoading, args) => {
const lastElementInQueue = queue[queue.length - 1];
return (
lastElementInQueue &&
lastElementInQueue.name === CREATE_KEY &&
!!lastElementInQueue.args[1] === !!args[1]
);
name: DELETE_KEY,
args: [this.uri, { dataset: transaction, fetch: this.context.fetch }],
perform: deleteResource,
modifyQueue: (queue, isLoading) => {
if (queue.length === 0) {
return isLoading[DELETE_KEY];
} else {
return queue[queue.length - 1].name === DELETE_KEY;
}
},
});
if (result.type !== "error") {
if (!result.isError) {
transaction.commit();
}
return result;
}
/**
* Upload a binary
* @param blob
* @param mimeType
* @param overwrite: If true, will overwrite an existing file
* Creates a Resource
* @param overwrite: If true, this will orverwrite the resource if it already
* exists
*/
async upload(
blob: Blob,
mimeType: string,
overwrite?: false,
): Promise<UploadResultWithoutOverwrite>;
async upload(
blob: Blob,
mimeType: string,
createDataResource(
overwrite: true,
): Promise<UploadResult>;
async upload(
blob: Blob,
mimeType: string,
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult>;
createDataResource(
overwrite?: false,
): Promise<ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult>;
createDataResource(
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite | UploadResult>;
async upload(
blob: Blob,
mimeType: string,
): Promise<
| ContainerCreateAndOverwriteResult
| LeafCreateAndOverwriteResult
| ContainerCreateIfAbsentResult
| LeafCreateIfAbsentResult
>;
async createDataResource(
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite | UploadResult> {
): Promise<
| ContainerCreateAndOverwriteResult
| LeafCreateAndOverwriteResult
| ContainerCreateIfAbsentResult
| LeafCreateIfAbsentResult
> {
const transaction = this.context.solidLdoDataset.startTransaction();
const result = await this.requestBatcher.queueProcess({
name: UPLOAD_KEY,
name: CREATE_KEY,
args: [
{ uri: this.uri, transaction, fetch: this.context.fetch },
blob,
mimeType,
this.uri,
overwrite,
{ dataset: transaction, fetch: this.context.fetch },
],
perform: uploadResource,
perform: createDataResource,
modifyQueue: (queue, isLoading, args) => {
const lastElementInQueue = queue[queue.length - 1];
return (
lastElementInQueue &&
lastElementInQueue.name === UPLOAD_KEY &&
!!lastElementInQueue.args[3] === !!args[3]
lastElementInQueue.name === CREATE_KEY &&
!!lastElementInQueue.args[1] === !!args[1]
);
},
});
if (result.type !== "error") {
transaction.commit();
}
return result;
}
/**
* Delete this resource
*/
async delete(): Promise<DeleteResult> {
const transaction = this.context.solidLdoDataset.startTransaction();
const result = await this.requestBatcher.queueProcess({
name: DELETE_KEY,
args: [{ uri: this.uri, transaction, fetch: this.context.fetch }],
perform: deleteResource,
modifyQueue: (queue, isLoading) => {
if (queue.length === 0) {
return isLoading[DELETE_KEY];
} else {
return queue[queue.length - 1].name === DELETE_KEY;
}
},
});
if (result.type !== "error") {
if (!result.isError) {
transaction.commit();
}
return result;

@ -1,9 +0,0 @@
import { RequesterResult } from "./RequesterResult";
export class AbsentResult extends RequesterResult {
type = "absent" as const;
static is(response: Response): boolean {
return response.status === 404;
}
}

@ -1,51 +0,0 @@
import type { Access } from "@inrupt/solid-client";
import { ErrorResult } from "./ErrorResult";
import { RequesterResult } from "./RequesterResult";
export interface AccessRule {
public?: Access;
agent?: Record<string, Access>;
}
export class AccessRuleChangeResult
extends RequesterResult
implements AccessRule
{
type = "accessRuleChange" as const;
readonly public?: Access;
readonly agent?: Record<string, Access>;
constructor(
uri: string,
publicRules?: Access,
agentRules?: Record<string, Access>,
) {
super(uri);
this.public = publicRules;
this.agent = agentRules;
}
}
export class AccessRuleResult extends RequesterResult implements AccessRule {
type = "accessRule" as const;
readonly public: Access;
readonly agent: Record<string, Access>;
constructor(
uri: string,
publicRules: Access,
agentRules: Record<string, Access>,
) {
super(uri);
this.public = publicRules;
this.agent = agentRules;
}
}
export class AccessRuleFetchError extends ErrorResult {
readonly errorType = "accessRuleFetch" as const;
constructor(uri: string, message?: string) {
super(uri, message || `Cannot get access rules for ${uri}.`);
}
}

@ -1,18 +0,0 @@
import { RequesterResult } from "./RequesterResult";
export class BinaryResult extends RequesterResult {
type = "binary" as const;
readonly blob: Blob;
readonly mimeType: string;
constructor(uri: string, blob: Blob, mimeType: string) {
super(uri);
this.blob = blob;
this.mimeType = mimeType;
}
static is(response: Response): boolean {
const contentType = response.headers.get("content-type");
return !contentType || contentType !== "text/turtle";
}
}

@ -1,12 +0,0 @@
import type { Container } from "../../resource/Container";
import type { Leaf } from "../../resource/Leaf";
import { RequesterResult } from "./RequesterResult";
export class CommitChangesSuccess extends RequesterResult {
readonly type = "commitChangesSuccess" as const;
readonly affectedResources: (Leaf | Container)[];
constructor(uri: string, affectedResources: (Leaf | Container)[]) {
super(uri);
this.affectedResources = affectedResources;
}
}

@ -1,27 +0,0 @@
import { ErrorResult } from "./ErrorResult";
import { RequesterResult } from "./RequesterResult";
export class DataResult extends RequesterResult {
type = "data" as const;
static is(response: Response): boolean {
const contentType = response.headers.get("content-type");
return !!contentType && contentType === "text/turtle";
}
}
export class TurtleFormattingError extends ErrorResult {
errorType = "turtleFormatting" as const;
constructor(uri: string, message?: string) {
super(uri, message || `Problem parsing turtle for ${uri}`);
}
}
export class InvalidUriError extends ErrorResult {
errorType = "invalidUri" as const;
constructor(uri: string, message?: string) {
super(uri, message || `${uri} is not a valid uri.`);
}
}

@ -1,7 +0,0 @@
export abstract class RequesterResult {
readonly uri: string;
abstract readonly type: string;
constructor(uri: string) {
this.uri = uri;
}
}

@ -1,35 +1,52 @@
import {
UnexpectedHttpError,
type HttpErrorResultType,
} from "../requestResults/HttpErrorResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import type { SimpleRequestParams } from "./requestParams";
import type { BasicRequestOptions } from "./requestOptions";
import { parse as parseLinkHeader } from "http-link-header";
import { NoncompliantPodError } from "../results/error/NoncompliantPodError";
import { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess";
import type {
HttpErrorResultType,
UnexpectedHttpError,
} from "../results/error/HttpErrorResult";
import { HttpErrorResult } from "../results/error/HttpErrorResult";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import type { ContainerUri } from "../../util/uriTypes";
export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError;
export type CheckRootResultError =
| HttpErrorResultType
| NoncompliantPodError
| UnexpectedHttpError
| UnexpectedResourceError;
export type CheckRootResult = boolean | CheckRootResultError;
export type CheckRootResultError = HttpErrorResultType | UnexpectedError;
export function checkHeadersForRootContainer(
uri: ContainerUri,
headers: Headers,
): CheckRootContainerSuccess | NoncompliantPodError {
const linkHeader = headers.get("link");
if (!linkHeader) {
return new NoncompliantPodError(uri, "No link header present in request.");
}
const parsedLinkHeader = parseLinkHeader(linkHeader);
const types = parsedLinkHeader.get("rel", "type");
const isRoot = types.some(
(type) => type.uri === "http://www.w3.org/ns/pim/space#Storage",
);
return new CheckRootContainerSuccess(uri, isRoot);
}
export async function checkRootContainer({
uri,
fetch,
}: SimpleRequestParams): Promise<CheckRootResult> {
export async function checkRootContainer(
uri: ContainerUri,
options?: BasicRequestOptions,
): Promise<CheckRootResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
const response = await fetch(uri, { method: "HEAD" });
const linkHeader = response.headers.get("link");
if (!linkHeader) {
return new UnexpectedHttpError(
uri,
response,
"No link header present in request.",
);
}
const parsedLinkHeader = parseLinkHeader(linkHeader);
const types = parsedLinkHeader.get("rel", "type");
return types.some(
(type) => type.uri === "http://www.w3.org/ns/pim/space#Storage",
);
const httpErrorResult = HttpErrorResult.checkResponse(uri, response);
if (httpErrorResult) return httpErrorResult;
return checkHeadersForRootContainer(uri, response.headers);
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -1,58 +1,116 @@
import { guaranteeFetch } from "../../util/guaranteeFetch";
import {
addResourceRdfToContainer,
getParentUri,
getSlug,
} from "../../util/rdfUtils";
import type { ContainerUri, LeafUri } from "../../util/uriTypes";
import { isContainerUri } from "../../util/uriTypes";
import type { BinaryResult } from "../requestResults/BinaryResult";
import type { TurtleFormattingError } from "../requestResults/DataResult";
import { DataResult } from "../requestResults/DataResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import type { HttpErrorResultType } from "../requestResults/HttpErrorResult";
import { HttpErrorResult } from "../requestResults/HttpErrorResult";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import type { HttpErrorResultType } from "../results/error/HttpErrorResult";
import { HttpErrorResult } from "../results/error/HttpErrorResult";
import { CreateSuccess } from "../results/success/CreateSuccess";
import type { AbsentReadSuccess } from "../results/success/ReadSuccess";
import type { DeleteResultError } from "./deleteResource";
import { deleteResource } from "./deleteResource";
import type {
ReadContainerResult,
ReadLeafResult,
ReadResultError,
} from "./readResource";
import { readResource } from "./readResource";
import type { RequestParams } from "./requestParams";
import type { DatasetRequestOptions } from "./requestOptions";
export type CreateResult = DataResult | CreateResultErrors;
export type CreateResultErrors = HttpErrorResultType | UnexpectedError;
export type CreateResultWithoutOverwrite =
| CreateResult
| CreateResultWithoutOverwriteErrors
| BinaryResult;
export type CreateResultWithoutOverwriteErrors =
| TurtleFormattingError
| CreateResultErrors;
export type ContainerCreateAndOverwriteResult =
| CreateSuccess
| CreateAndOverwriteResultErrors;
export type LeafCreateAndOverwriteResult =
| CreateSuccess
| CreateAndOverwriteResultErrors;
export type ContainerCreateIfAbsentResult =
| CreateSuccess
| Exclude<ReadContainerResult, AbsentReadSuccess>
| CreateIfAbsentResultErrors;
export type LeafCreateIfAbsentResult =
| CreateSuccess
| Exclude<ReadLeafResult, AbsentReadSuccess>
| CreateIfAbsentResultErrors;
export type CreateAndOverwriteResultErrors = DeleteResultError | CreateErrors;
export type CreateIfAbsentResultErrors = ReadResultError | CreateErrors;
export type CreateErrors = HttpErrorResultType | UnexpectedResourceError;
export function createDataResource(
uri: ContainerUri,
overwrite: true,
options?: DatasetRequestOptions,
): Promise<ContainerCreateAndOverwriteResult>;
export function createDataResource(
uri: LeafUri,
overwrite: true,
options?: DatasetRequestOptions,
): Promise<LeafCreateAndOverwriteResult>;
export function createDataResource(
uri: ContainerUri,
overwrite?: false,
options?: DatasetRequestOptions,
): Promise<ContainerCreateIfAbsentResult>;
export function createDataResource(
params: RequestParams,
uri: LeafUri,
overwrite?: false,
): Promise<CreateResultWithoutOverwrite>;
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult>;
export function createDataResource(
params: RequestParams,
uri: ContainerUri,
overwrite?: boolean,
options?: DatasetRequestOptions,
): Promise<ContainerCreateIfAbsentResult | ContainerCreateAndOverwriteResult>;
export function createDataResource(
uri: LeafUri,
overwrite?: boolean,
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult>;
export function createDataResource(
uri: string,
overwrite: true,
): Promise<CreateResult>;
options?: DatasetRequestOptions,
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult>;
export function createDataResource(
params: RequestParams,
uri: string,
overwrite?: false,
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult | LeafCreateIfAbsentResult>;
export function createDataResource(
uri: string,
overwrite?: boolean,
): Promise<CreateResultWithoutOverwrite | CreateResult>;
options?: DatasetRequestOptions,
): Promise<
| ContainerCreateAndOverwriteResult
| LeafCreateAndOverwriteResult
| ContainerCreateIfAbsentResult
| LeafCreateIfAbsentResult
>;
export async function createDataResource(
params: RequestParams,
uri: string,
overwrite?: boolean,
): Promise<CreateResultWithoutOverwrite> {
const { uri, transaction, fetch } = params;
options?: DatasetRequestOptions,
): Promise<
| ContainerCreateAndOverwriteResult
| LeafCreateAndOverwriteResult
| ContainerCreateIfAbsentResult
| LeafCreateIfAbsentResult
> {
try {
const fetch = guaranteeFetch(options?.fetch);
if (overwrite) {
const deleteResult = await deleteResource(params);
const deleteResult = await deleteResource(uri, options);
// Return if it wasn't deleted
if (deleteResult.type !== "absent") {
return deleteResult;
}
if (deleteResult.isError) return deleteResult;
} else {
// Perform a read to check if it exists
const readResult = await readResource(params);
const readResult = await readResource(uri, options);
// If it does exist stop and return.
if (readResult.type !== "absent") {
if (readResult.type !== "absentReadSuccess") {
return readResult;
}
}
@ -73,9 +131,11 @@ export async function createDataResource(
const httpError = HttpErrorResult.checkResponse(uri, response);
if (httpError) return httpError;
addResourceRdfToContainer(uri, transaction);
return new DataResult(uri);
if (options?.dataset) {
addResourceRdfToContainer(uri, options.dataset);
}
return new CreateSuccess(uri, !!overwrite);
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -1,39 +1,45 @@
import { namedNode } from "@rdfjs/data-model";
import { AbsentResult } from "../requestResults/AbsentResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import type { HttpErrorResultType } from "../requestResults/HttpErrorResult";
import { UnexpectedHttpError } from "../requestResults/HttpErrorResult";
import type { RequestParams } from "./requestParams";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import { deleteResourceRdfFromContainer } from "../../util/rdfUtils";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import type { HttpErrorResultType } from "../results/error/HttpErrorResult";
import { UnexpectedHttpError } from "../results/error/HttpErrorResult";
import { HttpErrorResult } from "../results/error/HttpErrorResult";
import { DeleteSuccess } from "../results/success/DeleteSuccess";
import type { DatasetRequestOptions } from "./requestOptions";
export type DeleteResult = AbsentResult | DeleteResultError;
export type DeleteResultError = HttpErrorResultType | UnexpectedError;
export type DeleteResult = DeleteSuccess | DeleteResultError;
export type DeleteResultError = HttpErrorResultType | UnexpectedResourceError;
export async function deleteResource({
uri,
fetch,
transaction,
}: RequestParams): Promise<DeleteResult> {
export async function deleteResource(
uri: string,
options?: DatasetRequestOptions,
): Promise<DeleteResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
const response = await fetch(uri, {
method: "delete",
});
const errorResult = HttpErrorResult.checkResponse(uri, response);
if (errorResult) return errorResult;
// Specifically check for a 205. Annoyingly, the server will return 200 even
// if it hasn't been deleted when you're unauthenticated. 404 happens when
// the document never existed
if (response.status === 205 || response.status === 404) {
transaction.deleteMatches(
undefined,
undefined,
undefined,
namedNode(uri),
);
deleteResourceRdfFromContainer(uri, transaction);
return new AbsentResult(uri);
if (options?.dataset) {
options.dataset.deleteMatches(
undefined,
undefined,
undefined,
namedNode(uri),
);
deleteResourceRdfFromContainer(uri, options.dataset);
}
return new DeleteSuccess(uri, response.status === 205);
}
return new UnexpectedHttpError(uri, response);
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -1,12 +1,6 @@
import type {
AccessRuleFetchError,
AccessRuleResult,
} from "../requestResults/AccessRule";
import type { SimpleRequestParams } from "./requestParams";
import type { AccessRuleFetchError } from "../results/success/AccessRule";
export async function getAccessRules(
_params: SimpleRequestParams,
): Promise<AccessRuleResult | AccessRuleFetchError> {
export async function getAccessRules(): Promise<AccessRuleFetchError> {
throw new Error("Not Implemented");
// const [publicAccess, agentAccess] = await Promise.all([
// universalAccess.getPublicAccess(uri, { fetch }),

@ -1,64 +1,103 @@
import { DataResult } from "../requestResults/DataResult";
import type { TurtleFormattingError } from "../requestResults/DataResult";
import type { UnexpectedHttpError } from "../results/error/HttpErrorResult";
import {
HttpErrorResult,
ServerHttpError,
type HttpErrorResultType,
} from "../requestResults/HttpErrorResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import { AbsentResult } from "../requestResults/AbsentResult";
import { BinaryResult } from "../requestResults/BinaryResult";
} from "../results/error/HttpErrorResult";
import {
addRawTurtleToDataset,
addResourceRdfToContainer,
} from "../../util/rdfUtils";
import type { RequestParams } from "./requestParams";
import type { DatasetRequestOptions } from "./requestOptions";
import type { ContainerUri, LeafUri } from "../../util/uriTypes";
import { isContainerUri } from "../../util/uriTypes";
import { BinaryReadSuccess } from "../results/success/ReadSuccess";
import {
ContainerReadSuccess,
DataReadSuccess,
} from "../results/success/ReadSuccess";
import { AbsentReadSuccess } from "../results/success/ReadSuccess";
import { NoncompliantPodError } from "../results/error/NoncompliantPodError";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { checkHeadersForRootContainer } from "./checkRootContainer";
export type ReadResult =
| AbsentResult
| DataResult
| BinaryResult
export type ReadLeafResult =
| BinaryReadSuccess
| DataReadSuccess
| AbsentReadSuccess
| ReadResultError;
export type ReadContainerResult =
| ContainerReadSuccess
| AbsentReadSuccess
| ReadResultError;
export type ReadResultError =
| HttpErrorResultType
| TurtleFormattingError
| UnexpectedError;
| NoncompliantPodError
| UnexpectedHttpError
| UnexpectedResourceError;
export async function readResource({
uri,
fetch,
transaction,
}: RequestParams): Promise<ReadResult> {
export async function readResource(
uri: LeafUri,
options?: DatasetRequestOptions,
): Promise<ReadLeafResult>;
export async function readResource(
uri: ContainerUri,
options?: DatasetRequestOptions,
): Promise<ReadContainerResult>;
export async function readResource(
uri: string,
options?: DatasetRequestOptions,
): Promise<ReadLeafResult | ReadContainerResult>;
export async function readResource(
uri: string,
options?: DatasetRequestOptions,
): Promise<ReadLeafResult | ReadContainerResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
const response = await fetch(uri);
if (AbsentResult.is(response)) {
return new AbsentResult(uri);
if (response.status === 404) {
return new AbsentReadSuccess(uri, false);
}
const httpErrorResult = HttpErrorResult.checkResponse(uri, response);
if (httpErrorResult) return httpErrorResult;
// Add this resource to the container
addResourceRdfToContainer(uri, transaction);
if (options?.dataset) {
addResourceRdfToContainer(uri, options.dataset);
}
const contentType = response.headers.get("content-type");
if (!contentType) {
return new NoncompliantPodError(
uri,
"Resource requests must return a content-type header.",
);
}
if (DataResult.is(response)) {
if (contentType === "text/turtle") {
// Parse Turtle
const rawTurtle = await response.text();
return addRawTurtleToDataset(rawTurtle, transaction, uri);
} else {
// Load Blob
const contentType = response.headers.get("content-type");
if (!contentType) {
return new ServerHttpError(
if (options?.dataset) {
const result = await addRawTurtleToDataset(
rawTurtle,
options.dataset,
uri,
response,
"Server provided no content-type",
);
if (result) return result;
}
if (isContainerUri(uri)) {
const result = checkHeadersForRootContainer(uri, response.headers);
if (result.isError) return result;
return new ContainerReadSuccess(uri, false, result.isRootContainer);
}
return new DataReadSuccess(uri, false);
} else {
// Load Blob
const blob = await response.blob();
return new BinaryResult(uri, blob, contentType);
return new BinaryReadSuccess(uri, false, blob, contentType);
}
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -0,0 +1,9 @@
import type { Dataset, Quad } from "@rdfjs/types";
export interface BasicRequestOptions {
fetch?: typeof fetch;
}
export interface DatasetRequestOptions extends BasicRequestOptions {
dataset?: Dataset<Quad>;
}

@ -1,10 +0,0 @@
import type { TransactionalDataset } from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
export interface RequestParams {
uri: string;
fetch: typeof fetch;
transaction: TransactionalDataset<Quad>;
}
export type SimpleRequestParams = Omit<RequestParams, "transaction">;

@ -1,6 +1,4 @@
import type { AclDataset, WithChangeLog } from "@inrupt/solid-client";
import { getAgentAccessAll } from "@inrupt/solid-client";
import { getPublicAccess } from "@inrupt/solid-client";
import {
getSolidDatasetWithAcl,
hasResourceAcl,
@ -14,17 +12,25 @@ import {
setPublicResourceAccess,
setAgentDefaultAccess,
} from "@inrupt/solid-client";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import { isContainerUri } from "../../util/uriTypes";
import type { AccessRule } from "../requestResults/AccessRule";
import { AccessRuleChangeResult } from "../requestResults/AccessRule";
import { AccessRuleFetchError } from "../requestResults/AccessRule";
import type { SimpleRequestParams } from "./requestParams";
import type { AccessRule } from "../results/success/AccessRule";
import { SetAccessRuleSuccess } from "../results/success/AccessRule";
import { AccessRuleFetchError } from "../results/success/AccessRule";
import type { BasicRequestOptions } from "./requestOptions";
export type SetAccessRulesResult =
| SetAccessRuleSuccess
| SetAccessRulesResultError;
export type SetAccessRulesResultError = AccessRuleFetchError;
export async function setAccessRules(
{ uri, fetch }: SimpleRequestParams,
uri: string,
newAccessRules: AccessRule,
): Promise<AccessRuleChangeResult | AccessRuleFetchError> {
options?: BasicRequestOptions,
): Promise<SetAccessRulesResult> {
console.warn("Access Control is stil underdeveloped. Use with caution.");
const fetch = guaranteeFetch(options?.fetch);
const isContainer = isContainerUri(uri);
// Code Copied from https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/manage-wac/
@ -72,9 +78,5 @@ export async function setAccessRules(
// Now save the ACL:
await saveAclFor(myDatasetWithAcl, updatedAcl, { fetch });
return new AccessRuleChangeResult(
uri,
getPublicAccess(myDatasetWithAcl) || undefined,
getAgentAccessAll(myDatasetWithAcl) || undefined,
);
return new SetAccessRuleSuccess(uri);
}

@ -1,28 +1,37 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
import { changesToSparqlUpdate } from "@ldo/rdf-utils";
import { DataResult } from "../requestResults/DataResult";
import type { HttpErrorResultType } from "../requestResults/HttpErrorResult";
import { HttpErrorResult } from "../requestResults/HttpErrorResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import type { SimpleRequestParams } from "./requestParams";
import type { SubscribableDataset } from "@ldo/subscribable-dataset";
import type {
SubscribableDataset,
TransactionalDataset,
} from "@ldo/subscribable-dataset";
import type { Quad } from "@rdfjs/types";
import { guaranteeFetch } from "../../util/guaranteeFetch";
import type { LeafUri } from "../../util/uriTypes";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import type { HttpErrorResultType } from "../results/error/HttpErrorResult";
import { HttpErrorResult } from "../results/error/HttpErrorResult";
import { UpdateSuccess } from "../results/success/UpdateSuccess";
import type { BasicRequestOptions } from "./requestOptions";
export type UpdateResult = DataResult | UpdateResultError;
export type UpdateResultError = HttpErrorResultType | UnexpectedError;
export type UpdateResult = UpdateSuccess | UpdateResultError;
export type UpdateResultError = HttpErrorResultType | UnexpectedResourceError;
export async function updateDataResource(
{ uri, fetch }: SimpleRequestParams,
uri: LeafUri,
datasetChanges: DatasetChanges<Quad>,
mainDataset: SubscribableDataset<Quad>,
options?: BasicRequestOptions & { dataset?: SubscribableDataset<Quad> },
): Promise<UpdateResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
// Put Changes in transactional dataset
const transaction = mainDataset.startTransaction();
transaction.addAll(datasetChanges.added || []);
datasetChanges.removed?.forEach((quad) => transaction.delete(quad));
// Commit data optimistically
transaction.commit();
let transaction: TransactionalDataset<Quad> | undefined;
if (options?.dataset) {
transaction = options.dataset.startTransaction();
transaction.addAll(datasetChanges.added || []);
datasetChanges.removed?.forEach((quad) => transaction!.delete(quad));
// Commit data optimistically
transaction.commit();
}
// Make request
const sparqlUpdate = await changesToSparqlUpdate(datasetChanges);
const response = await fetch(uri, {
@ -35,11 +44,13 @@ export async function updateDataResource(
const httpError = HttpErrorResult.checkResponse(uri, response);
if (httpError) {
// Handle error rollback
transaction.rollback();
if (transaction) {
transaction.rollback();
}
return httpError;
}
return new DataResult(uri);
return new UpdateSuccess(uri);
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -1,69 +1,60 @@
import { guaranteeFetch } from "../../util/guaranteeFetch";
import {
addResourceRdfToContainer,
getParentUri,
getSlug,
} from "../../util/rdfUtils";
import { BinaryResult } from "../requestResults/BinaryResult";
import type { LeafUri } from "../../util/uriTypes";
import { UnexpectedResourceError } from "../results/error/ErrorResult";
import { HttpErrorResult } from "../results/error/HttpErrorResult";
import { CreateSuccess } from "../results/success/CreateSuccess";
import type {
DataResult,
TurtleFormattingError,
} from "../requestResults/DataResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import {
HttpErrorResult,
type HttpErrorResultType,
} from "../requestResults/HttpErrorResult";
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "./createDataResource";
import { deleteResource } from "./deleteResource";
import { readResource } from "./readResource";
import type { RequestParams } from "./requestParams";
export type UploadResult = BinaryResult | UploadResultError;
export type UploadResultError = HttpErrorResultType | UnexpectedError;
export type UploadResultWithoutOverwrite =
| UploadResult
| UploadResultWithoutOverwriteError
| DataResult;
export type UploadResultWithoutOverwriteError =
| UploadResultError
| TurtleFormattingError;
import type { DatasetRequestOptions } from "./requestOptions";
export function uploadResource(
params: RequestParams,
uri: LeafUri,
blob: Blob,
mimeType: string,
overwrite?: false,
): Promise<UploadResultWithoutOverwrite>;
overwrite: true,
options?: DatasetRequestOptions,
): Promise<LeafCreateAndOverwriteResult>;
export function uploadResource(
params: RequestParams,
uri: LeafUri,
blob: Blob,
mimeType: string,
overwrite: true,
): Promise<UploadResult>;
overwrite?: false,
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult>;
export function uploadResource(
params: RequestParams,
uri: LeafUri,
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite>;
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult>;
export async function uploadResource(
params: RequestParams,
uri: LeafUri,
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite> {
const { uri, transaction, fetch } = params;
options?: DatasetRequestOptions,
): Promise<LeafCreateIfAbsentResult | LeafCreateAndOverwriteResult> {
try {
const fetch = guaranteeFetch(options?.fetch);
if (overwrite) {
const deleteResult = await deleteResource(params);
const deleteResult = await deleteResource(uri, options);
// Return if it wasn't deleted
if (deleteResult.type !== "absent") {
return deleteResult;
}
if (deleteResult.isError) return deleteResult;
} else {
// Perform a read to check if it exists
const readResult = await readResource(params);
const readResult = await readResource(uri, options);
// If it does exist stop and return.
if (readResult.type !== "absent") {
if (readResult.type !== "absentReadSuccess") {
return readResult;
}
}
@ -81,9 +72,11 @@ export async function uploadResource(
const httpError = HttpErrorResult.checkResponse(uri, response);
if (httpError) return httpError;
addResourceRdfToContainer(uri, transaction);
return new BinaryResult(uri, blob, mimeType);
if (options?.dataset) {
addResourceRdfToContainer(uri, options.dataset);
}
return new CreateSuccess(uri, !!overwrite);
} catch (err) {
return UnexpectedError.fromThrown(uri, err);
return UnexpectedResourceError.fromThrown(uri, err);
}
}

@ -0,0 +1,8 @@
import type { Container } from "../../resource/Container";
import type { Leaf } from "../../resource/Leaf";
export interface RequesterResult {
type: string;
isError: boolean;
resource?: Leaf | Container;
}

@ -1,43 +1,31 @@
export abstract class ErrorResult extends Error {
readonly type = "error" as const;
readonly uri: string;
abstract readonly errorType: string;
import type { Container } from "../../../resource/Container";
import type { Leaf } from "../../../resource/Leaf";
import type { RequesterResult } from "../RequesterResult";
constructor(uri: string, message?: string) {
super(message || "An error unkown error was encountered during a request.");
this.uri = uri;
export abstract class ErrorResult extends Error implements RequesterResult {
abstract type: string;
readonly isError = true as const;
resource?: Leaf | Container;
constructor(message?: string) {
super(message || "An error unkown error was encountered.");
}
}
export class UnexpectedError extends ErrorResult {
error: Error;
readonly errorType = "unexpected" as const;
constructor(uri: string, error: Error) {
super(uri, error.message);
this.error = error;
}
export abstract class ResourceError extends ErrorResult {
readonly uri: string;
static fromThrown(uri: string, err: unknown) {
if (err instanceof Error) {
return new UnexpectedError(uri, err);
} else if (typeof err === "string") {
return new UnexpectedError(uri, new Error(err));
} else {
return new UnexpectedError(
uri,
new Error(`Error of type ${typeof err} thrown: ${err}`),
);
}
constructor(uri: string, message?: string) {
super(message || `An error unkown error for ${uri}`);
this.uri = uri;
}
}
export class AggregateError<ErrorType extends ErrorResult> extends ErrorResult {
readonly errorType = "aggregate" as const;
readonly type = "aggregateError" as const;
readonly errors: ErrorType[];
constructor(
uri: string,
errors: (ErrorType | AggregateError<ErrorType>)[],
message?: string,
) {
@ -52,7 +40,6 @@ export class AggregateError<ErrorType extends ErrorResult> extends ErrorResult {
}
});
super(
uri,
message ||
`Encountered multiple errors:${allErrors.reduce(
(agg, cur) => `${agg}\n${cur}`,
@ -62,3 +49,26 @@ export class AggregateError<ErrorType extends ErrorResult> extends ErrorResult {
this.errors = allErrors;
}
}
export class UnexpectedResourceError extends ResourceError {
readonly type = "unexpectedResourceError" as const;
error: Error;
constructor(uri: string, error: Error) {
super(uri, error.message);
this.error = error;
}
static fromThrown(uri: string, err: unknown) {
if (err instanceof Error) {
return new UnexpectedResourceError(uri, err);
} else if (typeof err === "string") {
return new UnexpectedResourceError(uri, new Error(err));
} else {
return new UnexpectedResourceError(
uri,
new Error(`Error of type ${typeof err} thrown: ${err}`),
);
}
}
}

@ -1,11 +1,11 @@
import { ErrorResult } from "./ErrorResult";
import { ResourceError } from "./ErrorResult";
export type HttpErrorResultType =
| ServerHttpError
| UnexpectedHttpError
| UnauthenticatedHttpError;
export abstract class HttpErrorResult extends ErrorResult {
export abstract class HttpErrorResult extends ResourceError {
public readonly status: number;
public readonly headers: Headers;
public readonly response: Response;
@ -47,11 +47,11 @@ export abstract class HttpErrorResult extends ErrorResult {
}
export class UnexpectedHttpError extends HttpErrorResult {
errorType = "unexpectedHttp" as const;
readonly type = "unexpectedHttpError" as const;
}
export class UnauthenticatedHttpError extends HttpErrorResult {
errorType = "unauthenticated" as const;
readonly type = "unauthenticatedError" as const;
static is(response: Response) {
return response.status === 401;
@ -59,7 +59,7 @@ export class UnauthenticatedHttpError extends HttpErrorResult {
}
export class ServerHttpError extends HttpErrorResult {
errorType = "server" as const;
readonly type = "serverError" as const;
static is(response: Response) {
return response.status >= 500 && response.status < 600;

@ -0,0 +1,5 @@
import { ResourceError } from "./ErrorResult";
export class InvalidUriError extends ResourceError {
readonly type = "invalidUriError" as const;
}

@ -0,0 +1,11 @@
import { ResourceError } from "./ErrorResult";
export class NoncompliantPodError extends ResourceError {
readonly type = "noncompliantPodError" as const;
constructor(uri: string, message?: string) {
super(
uri,
`Response from ${uri} is not compliant with the Solid Specification: ${message}`,
);
}
}

@ -0,0 +1,20 @@
import type { Access } from "@inrupt/solid-client";
import { ResourceError } from "../error/ErrorResult";
import { ResourceSuccess } from "./SuccessResult";
export interface AccessRule {
public?: Access;
agent?: Record<string, Access>;
}
export class SetAccessRuleSuccess extends ResourceSuccess {
type = "setAccessRuleSuccess" as const;
}
export class AccessRuleFetchError extends ResourceError {
readonly type = "accessRuleFetchError" as const;
constructor(uri: string, message?: string) {
super(uri, message || `Cannot get access rules for ${uri}.`);
}
}

@ -0,0 +1,11 @@
import { ResourceSuccess } from "./SuccessResult";
export class CheckRootContainerSuccess extends ResourceSuccess {
readonly type = "checkRootContainerSuccess" as const;
readonly isRootContainer: boolean;
constructor(uri: string, isRootContainer: boolean) {
super(uri);
this.isRootContainer = isRootContainer;
}
}

@ -0,0 +1,11 @@
import { ResourceSuccess } from "./SuccessResult";
export class CreateSuccess extends ResourceSuccess {
readonly type = "createSuccess";
readonly didOverwrite: boolean;
constructor(uri: string, didOverwrite: boolean) {
super(uri);
this.didOverwrite = didOverwrite;
}
}

@ -0,0 +1,11 @@
import { ResourceSuccess } from "./SuccessResult";
export class DeleteSuccess extends ResourceSuccess {
readonly type = "deleteSuccess" as const;
readonly resourceExisted: boolean;
constructor(uri: string, resourceExisted: boolean) {
super(uri);
this.resourceExisted = resourceExisted;
}
}

@ -0,0 +1,56 @@
import { ResourceSuccess } from "./SuccessResult";
export abstract class ReadSuccess extends ResourceSuccess {
recalledFromMemory: boolean;
constructor(uri: string, recalledFromMemory: boolean) {
super(uri);
this.recalledFromMemory = recalledFromMemory;
}
}
export class BinaryReadSuccess extends ReadSuccess {
readonly type = "binaryReadSuccess" as const;
readonly blob: Blob;
readonly mimeType: string;
constructor(
uri: string,
recalledFromMemory: boolean,
blob: Blob,
mimeType: string,
) {
super(uri, recalledFromMemory);
this.blob = blob;
this.mimeType = mimeType;
}
}
export class DataReadSuccess extends ReadSuccess {
readonly type = "dataReadSuccess" as const;
constructor(uri: string, recalledFromMemory: boolean) {
super(uri, recalledFromMemory);
}
}
export class ContainerReadSuccess extends ReadSuccess {
readonly type = "containerReadSuccess" as const;
readonly isRootContainer: boolean;
constructor(
uri: string,
recalledFromMemory: boolean,
isRootContainer: boolean,
) {
super(uri, recalledFromMemory);
this.isRootContainer = isRootContainer;
}
}
export class AbsentReadSuccess extends ReadSuccess {
readonly type = "absentReadSuccess" as const;
constructor(uri: string, recalledFromMemory: boolean) {
super(uri, recalledFromMemory);
}
}

@ -0,0 +1,30 @@
import type { Container } from "../../../resource/Container";
import type { Leaf } from "../../../resource/Leaf";
import type { RequesterResult } from "../RequesterResult";
export abstract class SuccessResult implements RequesterResult {
readonly isError = false as const;
abstract readonly type: string;
resource?: Leaf | Container;
}
export abstract class ResourceSuccess extends SuccessResult {
readonly uri: string;
constructor(uri: string) {
super();
this.uri = uri;
}
}
export class AggregateSuccess<
SuccessType extends SuccessResult,
> extends SuccessResult {
readonly type = "aggregateError" as const;
readonly results: SuccessType[];
constructor(results: SuccessType[]) {
super();
this.results = results;
}
}

@ -0,0 +1,5 @@
import { ResourceSuccess } from "./SuccessResult";
export class Unfetched extends ResourceSuccess {
readonly type = "unfetched" as const;
}

@ -0,0 +1,5 @@
import { ResourceSuccess } from "./SuccessResult";
export class UpdateSuccess extends ResourceSuccess {
readonly type = "updateSuccess";
}

@ -1,84 +1,131 @@
import { namedNode } from "@rdfjs/data-model";
import { ContainerRequester } from "../requester/ContainerRequester";
import {
AggregateError,
UnexpectedError,
} from "../requester/requestResults/ErrorResult";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type {
CreateResultErrors,
CreateResultWithoutOverwriteErrors,
CheckRootResult,
CheckRootResultError,
} from "../requester/requests/checkRootContainer";
import type {
ContainerCreateAndOverwriteResult,
ContainerCreateIfAbsentResult,
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "../requester/requests/createDataResource";
import type { DeleteResultError } from "../requester/requests/deleteResource";
import type { ReadResultError } from "../requester/requests/readResource";
import type {
UploadResultError,
UploadResultWithoutOverwriteError,
} from "../requester/requests/uploadResource";
DeleteResult,
DeleteResultError,
} from "../requester/requests/deleteResource";
import type {
ReadContainerResult,
ReadResultError,
} from "../requester/requests/readResource";
import { AggregateError } from "../requester/results/error/ErrorResult";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import {
AbsentReadSuccess,
ContainerReadSuccess,
} from "../requester/results/success/ReadSuccess";
import { AggregateSuccess } from "../requester/results/success/SuccessResult";
import { Unfetched } from "../requester/results/success/Unfetched";
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import { getParentUri, ldpContains } from "../util/rdfUtils";
import type { ContainerUri, LeafUri } from "../util/uriTypes";
import type { Leaf } from "./Leaf";
import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import { GetRootContainerSuccess } from "./resourceResults/GetRootContainerSuccess";
export class Container extends Resource {
readonly uri: ContainerUri;
protected requester: ContainerRequester;
protected rootContainer: boolean | undefined;
readonly type = "container" as const;
readonly isError = false as const;
status:
| SharedStatuses
| ReadContainerResult
| ContainerCreateAndOverwriteResult
| ContainerCreateIfAbsentResult
| CheckRootResult;
constructor(uri: ContainerUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
this.requester = new ContainerRequester(uri, context);
this.status = new Unfetched(this.uri);
}
isRootContainer(): boolean | undefined {
return this.rootContainer;
}
private async checkIfIsRootContainer(): Promise<
CheckRootResultError | undefined
> {
if (this.rootContainer === undefined) {
const rootContainerResult = await this.requester.isRootContainer();
if (typeof rootContainerResult !== "boolean") {
return rootContainerResult;
}
this.rootContainer = rootContainerResult;
// Read Methods
protected updateWithReadSuccess(
result: ContainerReadSuccess | AbsentReadSuccess,
): void {
if (result.type === "containerReadSuccess") {
this.rootContainer = result.isRootContainer;
}
}
async getParentContainer(): Promise<
Container | CheckRootResultError | undefined
async read(): Promise<ReadContainerResult> {
return (await super.read()) as ReadContainerResult;
}
protected toReadResult(): ReadContainerResult {
if (this.isAbsent()) {
return new AbsentReadSuccess(this.uri, true);
} else {
return new ContainerReadSuccess(this.uri, true, this.isRootContainer()!);
}
}
async readIfUnfetched(): Promise<ReadContainerResult> {
return super.readIfUnfetched() as Promise<ReadContainerResult>;
}
// Parent Container Methods
private async checkIfIsRootContainer(): Promise<CheckRootResult> {
const rootContainerResult = await this.requester.isRootContainer();
this.status = rootContainerResult;
if (rootContainerResult.isError) return rootContainerResult;
this.rootContainer = rootContainerResult.isRootContainer;
this.emit("update");
return rootContainerResult;
}
async getRootContainer(): Promise<
GetRootContainerSuccess | CheckRootResultError
> {
const checkResult = await this.checkIfIsRootContainer();
if (checkResult) return checkResult;
if (this.rootContainer) return undefined;
if (checkResult.isError) return checkResult;
if (this.rootContainer) {
return new GetRootContainerSuccess(this);
}
const parentUri = getParentUri(this.uri);
if (!parentUri) {
return new UnexpectedError(
return new NoncompliantPodError(
this.uri,
new Error("Resource does not have a root container"),
"Resource does not have a root container",
);
}
return this.context.resourceStore.get(parentUri);
return this.context.resourceStore.get(parentUri).getRootContainer();
}
async getRootContainer(): Promise<Container | CheckRootResultError> {
async getParentContainer(): Promise<
Container | CheckRootResultError | undefined
> {
const checkResult = await this.checkIfIsRootContainer();
if (checkResult) return checkResult;
if (this.rootContainer) {
return this;
}
if (checkResult.isError) return checkResult;
if (this.rootContainer) return undefined;
const parentUri = getParentUri(this.uri);
if (!parentUri) {
return new UnexpectedError(
return new NoncompliantPodError(
this.uri,
new Error("Resource does not have a root container"),
`${this.uri} is not root does not have a parent container`,
);
}
return this.context.resourceStore.get(parentUri).getRootContainer();
return this.context.resourceStore.get(parentUri);
}
children(): (Leaf | Container)[] {
@ -100,99 +147,78 @@ export class Container extends Resource {
return this.context.resourceStore.get(`${this.uri}${slug}`);
}
// Child Creators
createChildAndOverwrite(
slug: ContainerUri,
): Promise<Container | CreateResultErrors>;
createChildAndOverwrite(slug: LeafUri): Promise<Leaf | CreateResultErrors>;
createChildAndOverwrite(slug: string): Promise<Resource | CreateResultErrors>;
): Promise<ContainerCreateAndOverwriteResult>;
createChildAndOverwrite(slug: LeafUri): Promise<LeafCreateAndOverwriteResult>;
createChildAndOverwrite(
slug: string,
): Promise<Resource | CreateResultErrors> {
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult>;
createChildAndOverwrite(
slug: string,
): Promise<ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult> {
return this.child(slug).createAndOverwrite();
}
createChildIfAbsent(
slug: ContainerUri,
): Promise<Container | CreateResultWithoutOverwriteErrors>;
createChildIfAbsent(
slug: LeafUri,
): Promise<Leaf | CreateResultWithoutOverwriteErrors>;
createChildIfAbsent(slug: ContainerUri): Promise<LeafCreateIfAbsentResult>;
createChildIfAbsent(slug: LeafUri): Promise<LeafCreateIfAbsentResult>;
createChildIfAbsent(
slug: string,
): Promise<Container | Leaf | CreateResultWithoutOverwriteErrors>;
): Promise<ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult>;
createChildIfAbsent(
slug: string,
): Promise<Container | Leaf | CreateResultWithoutOverwriteErrors> {
): Promise<ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult> {
return this.child(slug).createIfAbsent();
}
async uploadChildAndOverwrite(
slug: string,
slug: LeafUri,
blob: Blob,
mimeType: string,
): Promise<Leaf | UploadResultError> {
const child = this.child(slug);
if (child.type === "leaf") {
return child.uploadAndOverwrite(blob, mimeType);
}
return new UnexpectedError(
child.uri,
new Error(`${slug} is not a leaf uri.`),
);
): Promise<LeafCreateAndOverwriteResult> {
return this.child(slug).uploadAndOverwrite(blob, mimeType);
}
async uploadIfAbsent(
slug: string,
async uploadChildIfAbsent(
slug: LeafUri,
blob: Blob,
mimeType: string,
): Promise<Leaf | UploadResultWithoutOverwriteError> {
const child = this.child(slug);
if (child.type === "leaf") {
return child.uploadIfAbsent(blob, mimeType);
}
return new UnexpectedError(
child.uri,
new Error(`${slug} is not a leaf uri.`),
);
): Promise<LeafCreateIfAbsentResult> {
return this.child(slug).uploadIfAbsent(blob, mimeType);
}
async clear(): Promise<
AggregateError<DeleteResultError | ReadResultError> | this
| AggregateSuccess<DeleteSuccess>
| AggregateError<DeleteResultError | ReadResultError>
> {
const readResult = await this.read();
if (readResult.type === "error")
return new AggregateError(this.uri, [readResult]);
const errors = (
if (readResult.isError) return new AggregateError([readResult]);
const results = (
await Promise.all(
this.children().map(async (child) => {
const deleteError = await child.delete();
if (deleteError.type === "error") return deleteError;
return child.delete();
}),
)
)
.flat()
.filter(
(
value,
): value is
| DeleteResultError
| AggregateError<DeleteResultError | ReadResultError> => !!value,
);
).flat();
const errors = results.filter(
(
value,
): value is
| DeleteResultError
| AggregateError<DeleteResultError | ReadResultError> => value.isError,
);
if (errors.length > 0) {
return new AggregateError(this.uri, errors);
return new AggregateError(errors);
}
return this;
return new AggregateSuccess<DeleteSuccess>(results as DeleteSuccess[]);
}
async delete(): Promise<
| this
| AggregateError<ReadResultError | DeleteResultError>
| DeleteResultError
DeleteResult | AggregateError<DeleteResultError | ReadResultError>
> {
const clearResult = await this.clear();
if (clearResult.type === "error") return clearResult;
return this.parseResult(await this.requester.delete()) as
| this
| DeleteResultError;
if (clearResult.isError) return clearResult;
return this.handleDelete();
}
}

@ -1,63 +1,58 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
import type { Quad } from "@rdfjs/types";
import { LeafRequester } from "../requester/LeafRequester";
import type { AbsentResult } from "../requester/requestResults/AbsentResult";
import type { BinaryResult } from "../requester/requestResults/BinaryResult";
import type { DataResult } from "../requester/requestResults/DataResult";
import type { ErrorResult } from "../requester/requestResults/ErrorResult";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type { DeleteResultError } from "../requester/requests/deleteResource";
import type { UpdateResultError } from "../requester/requests/updateDataResource";
import type {
UploadResultError,
UploadResultWithoutOverwriteError,
} from "../requester/requests/uploadResource";
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "../requester/requests/createDataResource";
import type { DeleteResult } from "../requester/requests/deleteResource";
import type { ReadLeafResult } from "../requester/requests/readResource";
import type { UpdateResult } from "../requester/requests/updateDataResource";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import {
BinaryReadSuccess,
DataReadSuccess,
AbsentReadSuccess,
} from "../requester/results/success/ReadSuccess";
import type { ResourceSuccess } from "../requester/results/success/SuccessResult";
import { Unfetched } from "../requester/results/success/Unfetched";
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import { getParentUri } from "../util/rdfUtils";
import type { LeafUri } from "../util/uriTypes";
import type { Container } from "./Container";
import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import type { GetRootContainerSuccess } from "./resourceResults/GetRootContainerSuccess";
export class Leaf extends Resource {
readonly uri: LeafUri;
protected requester: LeafRequester;
readonly type = "leaf" as const;
readonly isError = false as const;
status:
| SharedStatuses
| ReadLeafResult
| LeafCreateAndOverwriteResult
| LeafCreateIfAbsentResult
| UpdateResult;
protected binaryData: { data: Blob; mimeType: string } | undefined;
protected binaryData: { blob: Blob; mimeType: string } | undefined;
constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
this.requester = new LeafRequester(uri, context);
this.status = new Unfetched(this.uri);
}
// Getters
isUploading(): boolean {
return this.requester.isUploading();
}
isUpdating(): boolean {
return this.requester.isUpdating();
}
protected parseResult<PossibleErrors extends ErrorResult>(
result: AbsentResult | BinaryResult | DataResult | PossibleErrors,
): this | PossibleErrors {
if (result.type === "binary") {
this.binaryData = {
data: result.blob,
mimeType: result.mimeType,
};
} else {
delete this.binaryData;
}
return super.parseResult(result);
}
getParentContainer(): Container {
const parentUri = getParentUri(this.uri)!;
return this.context.resourceStore.get(parentUri);
}
getRootContainer(): Promise<Container | CheckRootResultError> {
const parentUri = getParentUri(this.uri)!;
const parent = this.context.resourceStore.get(parentUri);
return parent.getRootContainer();
}
getMimeType(): string | undefined {
return this.binaryData?.mimeType;
}
@ -74,28 +69,100 @@ export class Leaf extends Resource {
return !this.binaryData;
}
// Read Methods
protected updateWithReadSuccess(
result: BinaryReadSuccess | DataReadSuccess | AbsentReadSuccess,
): void {
super.updateWithReadSuccess(result);
if (result.type === "binaryReadSuccess") {
this.binaryData = { blob: result.blob, mimeType: result.mimeType };
} else {
this.binaryData = undefined;
}
}
async read(): Promise<ReadLeafResult> {
return (await super.read()) as ReadLeafResult;
}
protected toReadResult(): ReadLeafResult {
if (this.isAbsent()) {
return new AbsentReadSuccess(this.uri, true);
} else if (this.isBinary()) {
return new BinaryReadSuccess(
this.uri,
true,
this.binaryData!.blob,
this.binaryData!.mimeType,
);
} else {
return new DataReadSuccess(this.uri, true);
}
}
async readIfUnfetched(): Promise<ReadLeafResult> {
return super.readIfUnfetched() as Promise<ReadLeafResult>;
}
// Parent Container Methods
getParentContainer(): Container {
const parentUri = getParentUri(this.uri)!;
return this.context.resourceStore.get(parentUri);
}
getRootContainer(): Promise<GetRootContainerSuccess | CheckRootResultError> {
const parentUri = getParentUri(this.uri)!;
const parent = this.context.resourceStore.get(parentUri);
return parent.getRootContainer();
}
// Delete Methods
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
this.binaryData = undefined;
}
// Create Methods
protected updateWithCreateSuccess(_result: ResourceSuccess): void {
this.binaryData = undefined;
}
// Upload Methods
async uploadAndOverwrite(
blob: Blob,
mimeType: string,
): Promise<this | UploadResultError> {
return this.parseResult(await this.requester.upload(blob, mimeType, true));
): Promise<LeafCreateAndOverwriteResult> {
const result = await this.requester.upload(blob, mimeType, true);
this.status = result;
if (result.isError) return result;
super.updateWithCreateSuccess(result);
this.binaryData = { blob, mimeType };
this.emitThisAndParent();
return result;
}
async uploadIfAbsent(
blob: Blob,
mimeType: string,
): Promise<this | UploadResultWithoutOverwriteError> {
return this.parseResult(await this.requester.upload(blob, mimeType));
): Promise<LeafCreateIfAbsentResult> {
const result = await this.requester.upload(blob, mimeType);
this.status = result;
if (result.isError) return result;
super.updateWithCreateSuccess(result);
this.binaryData = { blob, mimeType };
this.emitThisAndParent();
return result;
}
async update(
changes: DatasetChanges<Quad>,
): Promise<this | UpdateResultError> {
return this.parseResult(await this.requester.updateDataResource(changes));
async update(changes: DatasetChanges<Quad>): Promise<UpdateResult> {
const result = await this.requester.updateDataResource(changes);
this.status = result;
if (result.isError) return result;
this.binaryData = undefined;
this.absent = false;
this.emitThisAndParent();
return result;
}
// Delete Method
async delete(): Promise<this | DeleteResultError> {
return this.parseResult(await this.requester.delete());
async delete(): Promise<DeleteResult> {
return this.handleDelete();
}
}

@ -1,27 +1,36 @@
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type { AbsentResult } from "../requester/requestResults/AbsentResult";
import type { BinaryResult } from "../requester/requestResults/BinaryResult";
import type { DataResult } from "../requester/requestResults/DataResult";
import { type ErrorResult } from "../requester/requestResults/ErrorResult";
import type {
CreateResultErrors,
CreateResultWithoutOverwriteErrors,
ContainerCreateAndOverwriteResult,
ContainerCreateIfAbsentResult,
LeafCreateAndOverwriteResult,
LeafCreateIfAbsentResult,
} from "../requester/requests/createDataResource";
import type { ReadResultError } from "../requester/requests/readResource";
import type { Container } from "./Container";
import type {
ReadContainerResult,
ReadLeafResult,
} from "../requester/requests/readResource";
import type { Requester } from "../requester/Requester";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type {
AccessRule,
AccessRuleChangeResult,
AccessRuleFetchError,
AccessRuleResult,
} from "../requester/requestResults/AccessRule";
import { getAccessRules } from "../requester/requests/getAccessRules";
import type { AccessRule } from "../requester/results/success/AccessRule";
import type { SetAccessRulesResult } from "../requester/requests/setAccessRules";
import { setAccessRules } from "../requester/requests/setAccessRules";
import type TypedEmitter from "typed-emitter";
import EventEmitter from "events";
import { getParentUri } from "../util/rdfUtils";
import type { RequesterResult } from "../requester/results/RequesterResult";
import type { DeleteResult } from "../requester/requests/deleteResource";
import { ReadSuccess } from "../requester/results/success/ReadSuccess";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import type { ResourceSuccess } from "../requester/results/success/SuccessResult";
import type { Unfetched } from "../requester/results/success/Unfetched";
import type { CreateSuccess } from "../requester/results/success/CreateSuccess";
import type { GetRootContainerSuccess } from "./resourceResults/GetRootContainerSuccess";
import type {
ReadResourceSuccessContainerTypes,
ReadResourceSuccessLeafTypes,
} from "./resourceResults/ReadResourceSuccess";
export type SharedStatuses = Unfetched | DeleteResult | CreateSuccess;
export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
update: () => void;
@ -30,6 +39,7 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
protected readonly context: SolidLdoDatasetContext;
abstract readonly uri: string;
abstract readonly type: string;
abstract status: RequesterResult;
protected abstract readonly requester: Requester;
protected didInitialFetch: boolean = false;
protected absent: boolean | undefined;
@ -46,9 +56,6 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
isCreating(): boolean {
return this.requester.isCreating();
}
isUploading(): boolean {
return this.requester.isUploading();
}
isReading(): boolean {
return this.requester.isReading();
}
@ -73,62 +80,105 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
return this.absent === undefined ? undefined : !this.absent;
}
protected parseResult<PossibleErrors extends ErrorResult>(
result: AbsentResult | BinaryResult | DataResult | PossibleErrors,
): this | PossibleErrors {
if (result.type === "error") {
return result;
}
if (result.type === "absent") {
this.didInitialFetch = true;
this.absent = true;
} else {
this.didInitialFetch = true;
this.absent = false;
}
// Helper Methods
protected emitThisAndParent() {
this.emit("update");
const parentUri = getParentUri(this.uri);
if (parentUri) {
const parentContainer = this.context.resourceStore.get(parentUri);
parentContainer.emit("update");
}
return this;
}
// Read Methods
async read(): Promise<this | ReadResultError> {
return this.parseResult(await this.requester.read());
protected updateWithReadSuccess(result: ReadSuccess) {
this.absent = result.type === "absentReadSuccess";
this.didInitialFetch = true;
}
async read(): Promise<
ReadResourceSuccessContainerTypes | ReadResourceSuccessLeafTypes
> {
const result = await this.requester.read();
this.status = result;
if (result.isError) return result;
this.updateWithReadSuccess(result);
this.emitThisAndParent();
return result;
}
async readIfUnfetched(): Promise<this | ReadResultError> {
protected abstract toReadResult(): ReadContainerResult | ReadLeafResult;
async readIfUnfetched(): Promise<ReadContainerResult | ReadLeafResult> {
if (this.didInitialFetch) {
return this;
const readResult = this.toReadResult();
this.status = readResult;
return readResult;
}
return this.read();
}
// Delete Methods
protected updateWithDeleteSuccess(_result: DeleteSuccess) {
this.absent = true;
this.didInitialFetch = true;
}
protected async handleDelete(): Promise<DeleteResult> {
const result = await this.requester.delete();
this.status = result;
if (result.isError) return result;
this.updateWithDeleteSuccess(result);
this.emitThisAndParent();
return result;
}
// Create Methods
async createAndOverwrite(): Promise<this | CreateResultErrors> {
return this.parseResult(await this.requester.createDataResource(true));
protected updateWithCreateSuccess(result: ResourceSuccess) {
this.absent = false;
this.didInitialFetch = true;
if (result instanceof ReadSuccess) {
this.updateWithReadSuccess(result);
}
}
async createIfAbsent(): Promise<this | CreateResultWithoutOverwriteErrors> {
return this.parseResult(await this.requester.createDataResource());
async createAndOverwrite(): Promise<
ContainerCreateAndOverwriteResult | LeafCreateAndOverwriteResult
> {
const result = await this.requester.createDataResource(true);
this.status = result;
if (result.isError) return result;
this.updateWithCreateSuccess(result);
this.emitThisAndParent();
return result;
}
async createIfAbsent(): Promise<
ContainerCreateIfAbsentResult | LeafCreateIfAbsentResult
> {
const result = await this.requester.createDataResource(true);
this.status = result;
if (result.isError) return result;
this.updateWithCreateSuccess(result);
this.emitThisAndParent();
return result;
}
// Parent Container Methods -- Remember to change for Container
abstract getRootContainer(): Promise<Container | CheckRootResultError>;
abstract getRootContainer(): Promise<
GetRootContainerSuccess | CheckRootResultError
>;
async getAccessRules(): Promise<AccessRuleResult | AccessRuleFetchError> {
return getAccessRules({ uri: this.uri, fetch: this.context.fetch });
}
// Access Rules Methods
// async getAccessRules(): Promise<AccessRuleResult | AccessRuleFetchError> {
// return getAccessRules({ uri: this.uri, fetch: this.context.fetch });
// }
async setAccessRules(
newAccessRules: AccessRule,
): Promise<AccessRuleChangeResult | AccessRuleFetchError> {
return setAccessRules(
{ uri: this.uri, fetch: this.context.fetch },
newAccessRules,
);
): Promise<SetAccessRulesResult> {
return setAccessRules(this.uri, newAccessRules, {
fetch: this.context.fetch,
});
}
}

@ -0,0 +1,10 @@
import type { LeafCreateAndOverwriteResult } from "../requester/requests/createDataResource";
import { createDataResource } from "../requester/requests/createDataResource";
import type { RequesterResult } from "../requester/results/RequesterResult";
import type { Container } from "./Container";
import type { Leaf } from "./Leaf";
export type ResourceResult<
Result extends RequesterResult,
ResourceType extends Leaf | Container,
> = Result & { resource: ResourceType };

@ -0,0 +1,14 @@
import { CreateSuccess } from "../../requester/results/success/CreateSuccess";
import type { Container } from "../Container";
import type { Leaf } from "../Leaf";
export class CreateResourceSuccess<
ResourceType extends Leaf | Container,
> extends CreateSuccess {
readonly createdResource: ResourceType;
constructor(uri: string, didOverwrite: boolean, resource: ResourceType) {
super(uri, didOverwrite);
this.createdResource = resource;
}
}

@ -0,0 +1,14 @@
import { DeleteSuccess } from "../../requester/results/success/DeleteSuccess";
import type { Container } from "../Container";
import type { Leaf } from "../Leaf";
export class DeleteResourceSuccess<
ResourceType extends Leaf | Container,
> extends DeleteSuccess {
readonly deletedResource: ResourceType;
constructor(uri: string, resourceExisted: boolean, resource: ResourceType) {
super(uri, resourceExisted);
this.deletedResource = resource;
}
}

@ -0,0 +1,12 @@
import { SuccessResult } from "../../requester/results/success/SuccessResult";
import type { Container } from "../Container";
export class GetRootContainerSuccess extends SuccessResult {
readonly type = "getRootContainerSuccess" as const;
readonly rootContainer: Container;
constructor(rootContainer: Container) {
super();
this.rootContainer = rootContainer;
}
}

@ -0,0 +1,72 @@
import type { ReadResultError } from "../../requester/requests/readResource";
import {
AbsentReadSuccess,
BinaryReadSuccess,
ContainerReadSuccess,
DataReadSuccess,
} from "../../requester/results/success/ReadSuccess";
import type { Container } from "../Container";
import type { Leaf } from "../Leaf";
export type ReadResourceSuccessContainerTypes =
| ContainerResourceReadSuccess
| AbsentResourceReadSuccess<Container>
| ReadResultError;
export type ReadResourceSuccessLeafTypes =
| DataResourceReadSuccess
| BinaryResourceReadSuccess
| AbsentResourceReadSuccess<Leaf>
| ReadResultError;
export class DataResourceReadSuccess extends DataReadSuccess {
readonly readResource: Leaf;
constructor(uri: string, recalledFromMemory: boolean, resource: Leaf) {
super(uri, recalledFromMemory);
this.readResource = resource;
}
}
export class BinaryResourceReadSuccess extends BinaryReadSuccess {
readonly readResource: Leaf;
constructor(
uri: string,
recalledFromMemory: boolean,
blob: Blob,
mimeType: string,
resource: Leaf,
) {
super(uri, recalledFromMemory, blob, mimeType);
this.readResource = resource;
}
}
export class ContainerResourceReadSuccess extends ContainerReadSuccess {
readonly readResource: Container;
constructor(
uri: string,
recalledFromMemory: boolean,
isRootContainer: boolean,
resource: Container,
) {
super(uri, recalledFromMemory, isRootContainer);
this.readResource = resource;
}
}
export class AbsentResourceReadSuccess<
ResourceType extends Leaf | Container,
> extends AbsentReadSuccess {
readonly readResource: ResourceType;
constructor(
uri: string,
recalledFromMemory: boolean,
resource: ResourceType,
) {
super(uri, recalledFromMemory);
this.readResource = resource;
}
}

@ -0,0 +1,11 @@
import { UpdateSuccess } from "../../requester/results/success/UpdateSuccess";
import type { Leaf } from "../Leaf";
export class UpdateResourceSuccess extends UpdateSuccess {
readonly updatedResource: Leaf;
constructor(uri: string, resource: Leaf) {
super(uri);
this.updatedResource = resource;
}
}

@ -0,0 +1,5 @@
import crossFetch from "cross-fetch";
export function guaranteeFetch(fetchInput?: typeof fetch): typeof fetch {
return fetchInput || crossFetch;
}

@ -1,10 +1,10 @@
import { parseRdf } from "@ldo/ldo";
import { namedNode, quad as createQuad } from "@rdfjs/data-model";
import { DataResult } from "../requester/requestResults/DataResult";
import { TurtleFormattingError } from "../requester/requestResults/DataResult";
import type { Dataset } from "@rdfjs/types";
import type { ContainerUri } from "./uriTypes";
import { isContainerUri } from "./uriTypes";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import { UnexpectedResourceError } from "../requester/results/error/ErrorResult";
export const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains");
export const rdfType = namedNode(
@ -78,16 +78,17 @@ export async function addRawTurtleToDataset(
rawTurtle: string,
dataset: Dataset,
baseUri: string,
): Promise<DataResult | TurtleFormattingError> {
): Promise<undefined | NoncompliantPodError> {
let loadedDataset: Dataset;
try {
loadedDataset = await parseRdf(rawTurtle, {
baseIRI: baseUri,
});
} catch (err) {
return new TurtleFormattingError(
const error = UnexpectedResourceError.fromThrown(baseUri, err);
return new NoncompliantPodError(
baseUri,
err instanceof Error ? err.message : "Failed to parse rdf",
`Request at ${baseUri} returned noncompliant turtle: ${error.message}`,
);
}
@ -100,5 +101,4 @@ export async function addRawTurtleToDataset(
createQuad(quad.subject, quad.predicate, quad.object, graphNode),
),
);
return new DataResult(baseUri);
}

Loading…
Cancel
Save