parent
9ae7dea357
commit
11bf103980
@ -1,8 +1,50 @@ |
|||||||
export * from "./types"; |
export * from "./types"; |
||||||
export * from "./SolidConnectedPlugin"; |
export * from "./SolidConnectedPlugin"; |
||||||
|
export * from "./createSolidLdoDataset"; |
||||||
|
|
||||||
export * from "./resources/SolidResource"; |
export * from "./resources/SolidResource"; |
||||||
export * from "./resources/SolidContainer"; |
export * from "./resources/SolidContainer"; |
||||||
export * from "./resources/SolidLeaf"; |
export * from "./resources/SolidLeaf"; |
||||||
|
|
||||||
|
export * from "./requester/BatchedRequester"; |
||||||
|
export * from "./requester/ContainerBatchedRequester"; |
||||||
|
export * from "./requester/LeafBatchedRequester"; |
||||||
|
|
||||||
|
export * from "./requester/requests/checkRootContainer"; |
||||||
|
export * from "./requester/requests/createDataResource"; |
||||||
|
export * from "./requester/requests/deleteResource"; |
||||||
|
export * from "./requester/requests/readResource"; |
||||||
|
export * from "./requester/requests/requestOptions"; |
||||||
|
export * from "./requester/requests/updateDataResource"; |
||||||
|
export * from "./requester/requests/uploadResource"; |
||||||
|
|
||||||
|
export * from "./requester/results/success/CheckRootContainerSuccess"; |
||||||
|
export * from "./requester/results/success/CreateSuccess"; |
||||||
|
export * from "./requester/results/success/DeleteSuccess"; |
||||||
|
export * from "./requester/results/success/SolidReadSuccess"; |
||||||
|
|
||||||
|
export * from "./requester/results/error/AccessControlError"; |
||||||
|
export * from "./requester/results/error/HttpErrorResult"; |
||||||
|
export * from "./requester/results/error/NoRootContainerError"; |
||||||
|
export * from "./requester/results/error/NoncompliantPodError"; |
||||||
|
|
||||||
|
export * from "./requester/util/modifyQueueFuntions"; |
||||||
|
|
||||||
export * from "./util/isSolidUri"; |
export * from "./util/isSolidUri"; |
||||||
|
export * from "./util/guaranteeFetch"; |
||||||
|
export * from "./util/rdfUtils"; |
||||||
|
export * from "./util/RequestBatcher"; |
||||||
|
|
||||||
|
export * from "./wac/getWacRule"; |
||||||
|
export * from "./wac/getWacUri"; |
||||||
|
export * from "./wac/setWacRule"; |
||||||
|
export * from "./wac/WacRule"; |
||||||
|
export * from "./wac/results/GetWacRuleSuccess"; |
||||||
|
export * from "./wac/results/GetWacUriSuccess"; |
||||||
|
export * from "./wac/results/SetWacRuleSuccess"; |
||||||
|
export * from "./wac/results/WacRuleAbsent"; |
||||||
|
|
||||||
|
export * from "./notifications/SolidNotificationMessage"; |
||||||
|
export * from "./notifications/SolidNotificationSubscription"; |
||||||
|
export * from "./notifications/Websocket2023NotificationSubscription"; |
||||||
|
export * from "./notifications/results/NotificationErrors"; |
||||||
|
@ -1,16 +0,0 @@ |
|||||||
import type { Resource } from "@ldo/connected"; |
|
||||||
import { ResourceError } from "@ldo/connected"; |
|
||||||
|
|
||||||
/** |
|
||||||
* An InvalidUriError is returned when a URI was provided that is not a valid |
|
||||||
* URI. |
|
||||||
*/ |
|
||||||
export class InvalidUriError< |
|
||||||
ResourceType extends Resource, |
|
||||||
> extends ResourceError<ResourceType> { |
|
||||||
readonly type = "invalidUriError" as const; |
|
||||||
|
|
||||||
constructor(resource: ResourceType, message?: string) { |
|
||||||
super(resource, message || `${resource.uri} is an invalid uri.`); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,64 @@ |
|||||||
|
import { |
||||||
|
AggregateError, |
||||||
|
ErrorResult, |
||||||
|
ResourceError, |
||||||
|
UnexpectedResourceError, |
||||||
|
} from "../src/requester/results/error/ErrorResult"; |
||||||
|
import { InvalidUriError } from "../src/requester/results/error/InvalidUriError"; |
||||||
|
|
||||||
|
describe("ErrorResult", () => { |
||||||
|
describe("fromThrown", () => { |
||||||
|
it("returns an UnexpecteResourceError if a string is provided", () => { |
||||||
|
expect( |
||||||
|
UnexpectedResourceError.fromThrown("https://example.com/", "hello") |
||||||
|
.message, |
||||||
|
).toBe("hello"); |
||||||
|
}); |
||||||
|
|
||||||
|
it("returns an UnexpecteResourceError if an odd valud is provided", () => { |
||||||
|
expect( |
||||||
|
UnexpectedResourceError.fromThrown("https://example.com/", 5).message, |
||||||
|
).toBe("Error of type number thrown: 5"); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("AggregateError", () => { |
||||||
|
it("flattens aggregate errors provided to the constructor", () => { |
||||||
|
const err1 = UnexpectedResourceError.fromThrown("https://abc.com", "1"); |
||||||
|
const err2 = UnexpectedResourceError.fromThrown("https://abc.com", "2"); |
||||||
|
const err3 = UnexpectedResourceError.fromThrown("https://abc.com", "3"); |
||||||
|
const err4 = UnexpectedResourceError.fromThrown("https://abc.com", "4"); |
||||||
|
const aggErr1 = new AggregateError([err1, err2]); |
||||||
|
const aggErr2 = new AggregateError([err3, err4]); |
||||||
|
const finalAgg = new AggregateError([aggErr1, aggErr2]); |
||||||
|
expect(finalAgg.errors.length).toBe(4); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("default messages", () => { |
||||||
|
class ConcreteResourceError extends ResourceError { |
||||||
|
readonly type = "concreteResourceError" as const; |
||||||
|
} |
||||||
|
class ConcreteErrorResult extends ErrorResult { |
||||||
|
readonly type = "concreteErrorResult" as const; |
||||||
|
} |
||||||
|
|
||||||
|
it("ResourceError fallsback to a default message if none is provided", () => { |
||||||
|
expect(new ConcreteResourceError("https://example.com/").message).toBe( |
||||||
|
"An unkown error for https://example.com/", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("ErrorResult fallsback to a default message if none is provided", () => { |
||||||
|
expect(new ConcreteErrorResult().message).toBe( |
||||||
|
"An unkown error was encountered.", |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it("InvalidUriError fallsback to a default message if none is provided", () => { |
||||||
|
expect(new InvalidUriError("https://example.com/").message).toBe( |
||||||
|
"https://example.com/ is an invalid uri.", |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -1,48 +1,54 @@ |
|||||||
import type { WebSocket, Event, ErrorEvent } from "ws"; |
describe("Websocket Trivial", () => { |
||||||
import { Websocket2023NotificationSubscription } from "../src/resource/notifications/Websocket2023NotificationSubscription"; |
it("is trivial", () => { |
||||||
import type { SolidLdoDatasetContext } from "../src"; |
expect(true).toBe(true); |
||||||
import { Leaf } from "../src"; |
|
||||||
import type { NotificationChannel } from "@solid-notifications/types"; |
|
||||||
|
|
||||||
describe("Websocket2023NotificationSubscription", () => { |
|
||||||
it("returns an error when websockets have an error", async () => { |
|
||||||
const WebSocketMock: WebSocket = {} as WebSocket; |
|
||||||
|
|
||||||
const subscription = new Websocket2023NotificationSubscription( |
|
||||||
new Leaf("https://example.com", { |
|
||||||
fetch, |
|
||||||
} as unknown as SolidLdoDatasetContext), |
|
||||||
() => {}, |
|
||||||
{} as unknown as SolidLdoDatasetContext, |
|
||||||
() => WebSocketMock, |
|
||||||
); |
|
||||||
|
|
||||||
const subPromise = subscription.subscribeToWebsocket({ |
|
||||||
receiveFrom: "http://example.com", |
|
||||||
} as unknown as NotificationChannel); |
|
||||||
WebSocketMock.onopen?.({} as Event); |
|
||||||
|
|
||||||
await subPromise; |
|
||||||
|
|
||||||
WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent); |
|
||||||
}); |
|
||||||
|
|
||||||
it("returns an error when websockets have an error at the beginning", async () => { |
|
||||||
const WebSocketMock: WebSocket = {} as WebSocket; |
|
||||||
|
|
||||||
const subscription = new Websocket2023NotificationSubscription( |
|
||||||
new Leaf("https://example.com", { |
|
||||||
fetch, |
|
||||||
} as unknown as SolidLdoDatasetContext), |
|
||||||
() => {}, |
|
||||||
{} as unknown as SolidLdoDatasetContext, |
|
||||||
() => WebSocketMock, |
|
||||||
); |
|
||||||
|
|
||||||
const subPromise = subscription.subscribeToWebsocket({ |
|
||||||
receiveFrom: "http://example.com", |
|
||||||
} as unknown as NotificationChannel); |
|
||||||
WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent); |
|
||||||
await subPromise; |
|
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|
||||||
|
// import type { WebSocket, Event, ErrorEvent } from "ws";
|
||||||
|
// import { Websocket2023NotificationSubscription } from "../src/notifications/Websocket2023NotificationSubscription";
|
||||||
|
// import type { SolidLdoDatasetContext } from "../src";
|
||||||
|
// import { Leaf } from "../src";
|
||||||
|
// import type { NotificationChannel } from "@solid-notifications/types";
|
||||||
|
|
||||||
|
// describe("Websocket2023NotificationSubscription", () => {
|
||||||
|
// it("returns an error when websockets have an error", async () => {
|
||||||
|
// const WebSocketMock: WebSocket = {} as WebSocket;
|
||||||
|
|
||||||
|
// const subscription = new Websocket2023NotificationSubscription(
|
||||||
|
// new Leaf("https://example.com", {
|
||||||
|
// fetch,
|
||||||
|
// } as unknown as SolidLdoDatasetContext),
|
||||||
|
// () => {},
|
||||||
|
// {} as unknown as SolidLdoDatasetContext,
|
||||||
|
// () => WebSocketMock,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const subPromise = subscription.subscribeToWebsocket({
|
||||||
|
// receiveFrom: "http://example.com",
|
||||||
|
// } as unknown as NotificationChannel);
|
||||||
|
// WebSocketMock.onopen?.({} as Event);
|
||||||
|
|
||||||
|
// await subPromise;
|
||||||
|
|
||||||
|
// WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it("returns an error when websockets have an error at the beginning", async () => {
|
||||||
|
// const WebSocketMock: WebSocket = {} as WebSocket;
|
||||||
|
|
||||||
|
// const subscription = new Websocket2023NotificationSubscription(
|
||||||
|
// new Leaf("https://example.com", {
|
||||||
|
// fetch,
|
||||||
|
// } as unknown as SolidLdoDatasetContext),
|
||||||
|
// () => {},
|
||||||
|
// {} as unknown as SolidLdoDatasetContext,
|
||||||
|
// () => WebSocketMock,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const subPromise = subscription.subscribeToWebsocket({
|
||||||
|
// receiveFrom: "http://example.com",
|
||||||
|
// } as unknown as NotificationChannel);
|
||||||
|
// WebSocketMock.onerror?.({ error: new Error("Test Error") } as ErrorEvent);
|
||||||
|
// await subPromise;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
@ -1,7 +1,7 @@ |
|||||||
import { isLeafUri } from "../src"; |
import { isSolidLeafUri } from "../src"; |
||||||
|
|
||||||
describe("isLeafUri", () => { |
describe("isLeafUri", () => { |
||||||
it("returns true if the given value is a leaf URI", () => { |
it("returns true if the given value is a leaf URI", () => { |
||||||
expect(isLeafUri("https://example.com/index.ttl")).toBe(true); |
expect(isSolidLeafUri("https://example.com/index.ttl")).toBe(true); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
@ -1,11 +0,0 @@ |
|||||||
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset"; |
|
||||||
import type { ConnectedPlugin } from "./ConnectedPlugin"; |
|
||||||
|
|
||||||
export type ConnectedContext< |
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
Plugins extends ConnectedPlugin<any, any, any, any>[], |
|
||||||
> = { |
|
||||||
dataset: ConnectedLdoDataset<Plugins>; |
|
||||||
} & { |
|
||||||
[P in Plugins[number] as P["name"]]: P["types"]["context"]; |
|
||||||
}; |
|
@ -0,0 +1,199 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||||
|
import { LdoTransactionDataset } from "@ldo/ldo"; |
||||||
|
import type { DatasetFactory, Quad } from "@rdfjs/types"; |
||||||
|
import { |
||||||
|
updateDatasetInBulk, |
||||||
|
type ITransactionDatasetFactory, |
||||||
|
} from "@ldo/subscribable-dataset"; |
||||||
|
import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils"; |
||||||
|
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset"; |
||||||
|
import type { ConnectedPlugin } from "./ConnectedPlugin"; |
||||||
|
import type { ConnectedContext } from "./ConnectedContext"; |
||||||
|
import type { InvalidIdentifierResource } from "./InvalidIdentifierResource"; |
||||||
|
import type { IConnectedLdoDataset } from "./IConnectedLdoDataset"; |
||||||
|
import { splitChangesByGraph } from "./util/splitChangesByGraph"; |
||||||
|
import type { |
||||||
|
IgnoredInvalidUpdateSuccess, |
||||||
|
UpdateSuccess, |
||||||
|
} from "./results/success/UpdateSuccess"; |
||||||
|
import { UpdateDefaultGraphSuccess } from "./results/success/UpdateSuccess"; |
||||||
|
import { AggregateError } from "./results/error/ErrorResult"; |
||||||
|
import type { AggregateSuccess } from "./results/success/SuccessResult"; |
||||||
|
|
||||||
|
/** |
||||||
|
* A SolidLdoTransactionDataset has all the functionality of a SolidLdoDataset |
||||||
|
* and represents a transaction to the parent SolidLdoDataset. |
||||||
|
* |
||||||
|
* It is recommended to use the `startTransaction` method on a SolidLdoDataset |
||||||
|
* 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 transaction = solidLdoDataset.startTransaction(); |
||||||
|
* |
||||||
|
* const profile = transaction |
||||||
|
* .using(ProfileShapeType) |
||||||
|
* .fromSubject("https://example.com/profile#me"); |
||||||
|
* profile.name = "Some Name"; |
||||||
|
* await transaction.commitToPod(); |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
export class ConnectedLdoTransactionDataset<Plugins extends ConnectedPlugin[]> |
||||||
|
extends LdoTransactionDataset |
||||||
|
implements IConnectedLdoDataset<Plugins> |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
public context: ConnectedContext<Plugins>; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* Serves no purpose |
||||||
|
*/ |
||||||
|
protected resourceMap: Map<string, Plugins[number]["types"]["resource"]> = |
||||||
|
new Map(); |
||||||
|
|
||||||
|
/** |
||||||
|
* @param context - SolidLdoDatasetContext |
||||||
|
* @param datasetFactory - An optional dataset factory |
||||||
|
* @param transactionDatasetFactory - A factory for creating transaction datasets |
||||||
|
* @param initialDataset - A set of triples to initialize this dataset |
||||||
|
*/ |
||||||
|
constructor( |
||||||
|
parentDataset: IConnectedLdoDataset<Plugins>, |
||||||
|
context: ConnectedContext<Plugins>, |
||||||
|
datasetFactory: DatasetFactory, |
||||||
|
transactionDatasetFactory: ITransactionDatasetFactory<Quad>, |
||||||
|
) { |
||||||
|
super(parentDataset, datasetFactory, transactionDatasetFactory); |
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
|
||||||
|
getResource< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
UriType extends string, |
||||||
|
>( |
||||||
|
uri: UriType, |
||||||
|
pluginName?: Name | undefined, |
||||||
|
): UriType extends Plugin["types"]["uri"] |
||||||
|
? Plugin["getResource"] extends (arg: UriType, context: any) => infer R |
||||||
|
? R |
||||||
|
: never |
||||||
|
: InvalidIdentifierResource | ReturnType<Plugin["getResource"]> { |
||||||
|
return this.context.dataset.getResource(uri, pluginName); |
||||||
|
} |
||||||
|
|
||||||
|
createResource< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
>(name: Name): Promise<ReturnType<Plugin["createResource"]>> { |
||||||
|
return this.context.dataset.createResource(name); |
||||||
|
} |
||||||
|
|
||||||
|
setContext< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
>(name: Name, context: Plugin["types"]["context"]): void { |
||||||
|
this.context.dataset.setContext(name, 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"); |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
public startTransaction(): ConnectedLdoTransactionDataset<Plugins> { |
||||||
|
return new ConnectedLdoTransactionDataset( |
||||||
|
this, |
||||||
|
this.context, |
||||||
|
this.datasetFactory, |
||||||
|
this.transactionDatasetFactory, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async commitChanges(): Promise< |
||||||
|
| AggregateSuccess< |
||||||
|
| UpdateSuccess<Plugins[number]["types"]["resource"]> |
||||||
|
| UpdateDefaultGraphSuccess |
||||||
|
> |
||||||
|
| AggregateError< |
||||||
|
Extract< |
||||||
|
ReturnType<Plugins[number]["types"]["resource"]["update"]>, |
||||||
|
{ isError: true } |
||||||
|
> |
||||||
|
> |
||||||
|
> { |
||||||
|
const changes = this.getChanges(); |
||||||
|
const changesByGraph = splitChangesByGraph(changes); |
||||||
|
|
||||||
|
// Iterate through all changes by graph in
|
||||||
|
const results: [ |
||||||
|
GraphNode, |
||||||
|
DatasetChanges<Quad>, |
||||||
|
( |
||||||
|
| ReturnType<Plugins[number]["types"]["resource"]["update"]> |
||||||
|
| IgnoredInvalidUpdateSuccess<any> |
||||||
|
| UpdateDefaultGraphSuccess |
||||||
|
), |
||||||
|
][] = await Promise.all( |
||||||
|
Array.from(changesByGraph.entries()).map( |
||||||
|
async ([graph, datasetChanges]) => { |
||||||
|
if (graph.termType === "DefaultGraph") { |
||||||
|
// Undefined means that this is the default graph
|
||||||
|
updateDatasetInBulk(this.parentDataset, datasetChanges); |
||||||
|
return [graph, datasetChanges, new UpdateDefaultGraphSuccess()]; |
||||||
|
} |
||||||
|
const resource = this.getResource(graph.value); |
||||||
|
const updateResult = await resource.update(datasetChanges); |
||||||
|
return [graph, datasetChanges, updateResult]; |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
// If one has errored, return error
|
||||||
|
const errors = results.filter((result) => result[2].isError); |
||||||
|
|
||||||
|
if (errors.length > 0) { |
||||||
|
return new AggregateError( |
||||||
|
errors.map((result) => result[2] as UpdateResultError), |
||||||
|
); |
||||||
|
} |
||||||
|
return { |
||||||
|
isError: false, |
||||||
|
type: "aggregateSuccess", |
||||||
|
results: results |
||||||
|
.map((result) => result[2]) |
||||||
|
.filter( |
||||||
|
(result): result is ResourceResult<UpdateSuccess, Leaf> => |
||||||
|
result.type === "updateSuccess" || |
||||||
|
result.type === "updateDefaultGraphSuccess" || |
||||||
|
result.type === "ignoredInvalidUpdateSuccess", |
||||||
|
), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
||||||
|
import type { LdoDataset } from "@ldo/ldo"; |
||||||
|
import type { ConnectedPlugin } from "./ConnectedPlugin"; |
||||||
|
import type { InvalidIdentifierResource } from "./InvalidIdentifierResource"; |
||||||
|
|
||||||
|
export type ReturnTypeFromArgs<Func, Arg> = Func extends ( |
||||||
|
arg: Arg, |
||||||
|
context: any, |
||||||
|
) => infer R |
||||||
|
? R |
||||||
|
: never; |
||||||
|
|
||||||
|
export interface IConnectedLdoDataset<Plugins extends ConnectedPlugin[]> |
||||||
|
extends LdoDataset { |
||||||
|
/** |
||||||
|
* Retireves a representation of a 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 pluginName - optionally, force this function to choose a specific |
||||||
|
* plugin to use rather than perform content negotiation. |
||||||
|
* |
||||||
|
* @returns a Resource |
||||||
|
* |
||||||
|
* @example |
||||||
|
* ```typescript
|
||||||
|
* const profileDocument = solidLdoDataset |
||||||
|
* .getResource("https://example.com/profile"); |
||||||
|
* ``` |
||||||
|
*/ |
||||||
|
getResource< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
UriType extends string, |
||||||
|
>( |
||||||
|
uri: UriType, |
||||||
|
pluginName?: Name, |
||||||
|
): UriType extends Plugin["types"]["uri"] |
||||||
|
? ReturnTypeFromArgs<Plugin["getResource"], UriType> |
||||||
|
: ReturnType<Plugin["getResource"]> | InvalidIdentifierResource; |
||||||
|
|
||||||
|
createResource< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
>( |
||||||
|
name: Name, |
||||||
|
): Promise<ReturnType<Plugin["createResource"]>>; |
||||||
|
|
||||||
|
setContext< |
||||||
|
Name extends Plugins[number]["name"], |
||||||
|
Plugin extends Extract<Plugins[number], { name: Name }>, |
||||||
|
>( |
||||||
|
name: Name, |
||||||
|
context: Plugin["types"]["context"], |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import { ResourceSuccess } from "@ldo/connected"; |
||||||
|
import type { Resource } from "@ldo/connected"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that the request to read a resource was a success |
||||||
|
*/ |
||||||
|
export abstract class ReadSuccess< |
||||||
|
ResourceType extends Resource, |
||||||
|
> extends ResourceSuccess<ResourceType> { |
||||||
|
/** |
||||||
|
* True if the resource was recalled from local memory rather than a recent |
||||||
|
* request |
||||||
|
*/ |
||||||
|
recalledFromMemory: boolean; |
||||||
|
|
||||||
|
constructor(resource: ResourceType, recalledFromMemory: boolean) { |
||||||
|
super(resource); |
||||||
|
this.recalledFromMemory = recalledFromMemory; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates that the read request was successful, but no resource exists at |
||||||
|
* the provided URI. |
||||||
|
*/ |
||||||
|
export class AbsentReadSuccess< |
||||||
|
ResourceType extends Resource, |
||||||
|
> extends ReadSuccess<ResourceType> { |
||||||
|
type = "absentReadSuccess" as const; |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
import { createDataset } from "@ldo/dataset"; |
||||||
|
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<Quad>, |
||||||
|
): Map<GraphNode, DatasetChanges<Quad>> { |
||||||
|
const changesMap: Record<string, DatasetChanges<Quad>> = {}; |
||||||
|
changes.added?.forEach((quad) => { |
||||||
|
const graphHash = graphNodeToString(quad.graph as GraphNode); |
||||||
|
if (!changesMap[graphHash]) { |
||||||
|
changesMap[graphHash] = {}; |
||||||
|
} |
||||||
|
if (!changesMap[graphHash].added) { |
||||||
|
changesMap[graphHash].added = createDataset(); |
||||||
|
} |
||||||
|
changesMap[graphHash].added?.add( |
||||||
|
createQuad(quad.subject, quad.predicate, quad.object, quad.graph), |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
changes.removed?.forEach((quad) => { |
||||||
|
const graphHash = graphNodeToString(quad.graph as GraphNode); |
||||||
|
if (!changesMap[graphHash]) { |
||||||
|
changesMap[graphHash] = {}; |
||||||
|
} |
||||||
|
if (!changesMap[graphHash].removed) { |
||||||
|
changesMap[graphHash].removed = createDataset(); |
||||||
|
} |
||||||
|
changesMap[graphHash].removed?.add( |
||||||
|
createQuad(quad.subject, quad.predicate, quad.object, quad.graph), |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
const finalMap = new Map<GraphNode, DatasetChanges<Quad>>(); |
||||||
|
Object.entries(changesMap).forEach(([key, value]) => { |
||||||
|
finalMap.set(stringToGraphNode(key), value); |
||||||
|
}); |
||||||
|
return finalMap; |
||||||
|
} |
Loading…
Reference in new issue