parent
778d49ef85
commit
356ecf8c67
@ -1,3 +1,126 @@ |
|||||||
import type { Resource } from "@ldo/connected"; |
import type { |
||||||
|
ConnectedResult, |
||||||
|
Resource, |
||||||
|
ResourceResult, |
||||||
|
SubscriptionCallbacks, |
||||||
|
} from "@ldo/connected"; |
||||||
|
import type { SolidContainerUri, SolidLeafUri } from "../types"; |
||||||
|
|
||||||
export class SolidResource implements Resource {} |
export class SolidResource |
||||||
|
implements Resource<SolidLeafUri | SolidContainerUri> |
||||||
|
{ |
||||||
|
uri: SolidLeafUri | SolidContainerUri; |
||||||
|
type: string; |
||||||
|
status: ConnectedResult; |
||||||
|
isLoading(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isFetched(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isUnfetched(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isDoingInitialFetch(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isPresent(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isAbsent(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
isSubscribedToNotifications(): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
read(): Promise<ResourceResult<this>> { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
readIfAbsent(): Promise<ResourceResult<this>> { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
subscribeToNotifications(callbacks?: SubscriptionCallbacks): Promise<string> { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
unsubscribeFromNotifications(subscriptionId: string): Promise<void> { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
unsubscribeFromAllNotifications(): Promise<void> { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
addListener<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
on<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
once<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
prependListener<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
prependOnceListener<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
off<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
removeAllListeners<E extends "update" | "notification">( |
||||||
|
event?: E | undefined, |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
removeListener<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
listener: { update: () => void; notification: () => void }[E], |
||||||
|
): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
emit<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
...args: Parameters<{ update: () => void; notification: () => void }[E]> |
||||||
|
): boolean { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
eventNames(): (string | symbol)[] { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
rawListeners<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
): { update: () => void; notification: () => void }[E][] { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
listeners<E extends "update" | "notification">( |
||||||
|
event: E, |
||||||
|
): { update: () => void; notification: () => void }[E][] { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
listenerCount<E extends "update" | "notification">(event: E): number { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
getMaxListeners(): number { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
setMaxListeners(maxListeners: number): this { |
||||||
|
throw new Error("Method not implemented."); |
||||||
|
} |
||||||
|
} |
||||||
|
@ -1,8 +1,11 @@ |
|||||||
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset"; |
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset"; |
||||||
import type { ConnectedPlugin } from "./ConnectedPlugin"; |
import type { ConnectedPlugin } from "./ConnectedPlugin"; |
||||||
|
|
||||||
export type ConnectedContext<Plugins extends ConnectedPlugin[]> = { |
export type ConnectedContext< |
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
Plugins extends ConnectedPlugin<any, any, any, any>[], |
||||||
|
> = { |
||||||
dataset: ConnectedLdoDataset<Plugins>; |
dataset: ConnectedLdoDataset<Plugins>; |
||||||
} & { |
} & { |
||||||
[P in Plugins[number] as P["name"]]: P["context"]; |
[P in Plugins[number] as P["name"]]: P["types"]["context"]; |
||||||
}; |
}; |
||||||
|
@ -1,9 +1,23 @@ |
|||||||
import type { Resource } from "./Resource"; |
import type { Resource } from "./Resource"; |
||||||
|
import type { ErrorResult } from "./results/error/ErrorResult"; |
||||||
|
|
||||||
export interface ConnectedPlugin { |
export interface ConnectedPlugin< |
||||||
name: string; |
Name extends string, |
||||||
getResource(uri: string): Resource; |
UriType extends string, |
||||||
createResource(): Promise<Resource>; |
ResourceType extends Resource<UriType>, |
||||||
isUriValid(uri: string): boolean; |
ContextType, |
||||||
normalizeUri?: (uri: string) => string; |
> { |
||||||
|
name: Name; |
||||||
|
getResource(uri: UriType, context: ContextType): ResourceType | ErrorResult; |
||||||
|
createResource(context: ContextType): Promise<ResourceType | ErrorResult>; |
||||||
|
isUriValid(uri: UriType): boolean; |
||||||
|
normalizeUri?: (uri: UriType) => UriType; |
||||||
|
initialContext: ContextType; |
||||||
|
// This object exists to transfer typescript types. It does not need to be
|
||||||
|
// filled out in an actual instance.
|
||||||
|
types: { |
||||||
|
uri: UriType; |
||||||
|
context: ContextType; |
||||||
|
resource: ResourceType; |
||||||
|
}; |
||||||
} |
} |
||||||
|
@ -1,10 +0,0 @@ |
|||||||
/** |
|
||||||
* A message sent from the Pod as a notification |
|
||||||
*/ |
|
||||||
export interface NotificationMessage { |
|
||||||
"@context": string | string[]; |
|
||||||
id: string; |
|
||||||
type: "Update" | "Delete" | "Remove" | "Add"; |
|
||||||
object: string; |
|
||||||
published: string; |
|
||||||
} |
|
@ -1,144 +0,0 @@ |
|||||||
import type { SolidLdoDatasetContext } from "../../SolidLdoDatasetContext"; |
|
||||||
import type { Resource } from "../Resource"; |
|
||||||
import type { NotificationMessage } from "./NotificationMessage"; |
|
||||||
import type { NotificationCallbackError } from "./results/NotificationErrors"; |
|
||||||
import { v4 } from "uuid"; |
|
||||||
|
|
||||||
export interface SubscriptionCallbacks { |
|
||||||
onNotification?: (message: NotificationMessage) => void; |
|
||||||
// TODO: make notification errors more specific
|
|
||||||
onNotificationError?: (error: Error) => void; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* Abstract class for notification subscription methods. |
|
||||||
*/ |
|
||||||
export abstract class NotificationSubscription { |
|
||||||
protected resource: Resource; |
|
||||||
protected parentSubscription: (message: NotificationMessage) => void; |
|
||||||
protected context: SolidLdoDatasetContext; |
|
||||||
protected subscriptions: Record<string, SubscriptionCallbacks> = {}; |
|
||||||
private isOpen: boolean = false; |
|
||||||
|
|
||||||
constructor( |
|
||||||
resource: Resource, |
|
||||||
parentSubscription: (message: NotificationMessage) => void, |
|
||||||
context: SolidLdoDatasetContext, |
|
||||||
) { |
|
||||||
this.resource = resource; |
|
||||||
this.parentSubscription = parentSubscription; |
|
||||||
this.context = context; |
|
||||||
} |
|
||||||
|
|
||||||
public isSubscribedToNotifications(): boolean { |
|
||||||
return this.isOpen; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* =========================================================================== |
|
||||||
* PUBLIC |
|
||||||
* =========================================================================== |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* subscribeToNotifications |
|
||||||
*/ |
|
||||||
async subscribeToNotifications( |
|
||||||
subscriptionCallbacks?: SubscriptionCallbacks, |
|
||||||
): Promise<string> { |
|
||||||
const subscriptionId = v4(); |
|
||||||
this.subscriptions[subscriptionId] = subscriptionCallbacks ?? {}; |
|
||||||
if (!this.isOpen) { |
|
||||||
await this.open(); |
|
||||||
this.setIsOpen(true); |
|
||||||
} |
|
||||||
return subscriptionId; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* unsubscribeFromNotification |
|
||||||
*/ |
|
||||||
async unsubscribeFromNotification(subscriptionId: string): Promise<void> { |
|
||||||
if ( |
|
||||||
!!this.subscriptions[subscriptionId] && |
|
||||||
Object.keys(this.subscriptions).length === 1 |
|
||||||
) { |
|
||||||
await this.close(); |
|
||||||
this.setIsOpen(false); |
|
||||||
} |
|
||||||
delete this.subscriptions[subscriptionId]; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* unsubscribeFromAllNotifications |
|
||||||
*/ |
|
||||||
async unsubscribeFromAllNotifications(): Promise<void> { |
|
||||||
await Promise.all( |
|
||||||
Object.keys(this.subscriptions).map((id) => |
|
||||||
this.unsubscribeFromNotification(id), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* =========================================================================== |
|
||||||
* HELPERS |
|
||||||
* =========================================================================== |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* Opens the subscription |
|
||||||
*/ |
|
||||||
protected abstract open(): Promise<void>; |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* Closes the subscription |
|
||||||
*/ |
|
||||||
protected abstract close(): Promise<void>; |
|
||||||
|
|
||||||
/** |
|
||||||
* =========================================================================== |
|
||||||
* CALLBACKS |
|
||||||
* =========================================================================== |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* onNotification |
|
||||||
*/ |
|
||||||
protected onNotification(message: NotificationMessage): void { |
|
||||||
this.parentSubscription(message); |
|
||||||
Object.values(this.subscriptions).forEach(({ onNotification }) => { |
|
||||||
onNotification?.(message); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* onNotificationError |
|
||||||
*/ |
|
||||||
protected onNotificationError(message: NotificationCallbackError): void { |
|
||||||
Object.values(this.subscriptions).forEach(({ onNotificationError }) => { |
|
||||||
onNotificationError?.(message); |
|
||||||
}); |
|
||||||
if (message.type === "disconnectedNotAttemptingReconnectError") { |
|
||||||
this.setIsOpen(false); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @internal |
|
||||||
* setIsOpen |
|
||||||
*/ |
|
||||||
protected setIsOpen(status: boolean) { |
|
||||||
const shouldUpdate = status !== this.isOpen; |
|
||||||
this.isOpen = status; |
|
||||||
if (shouldUpdate) this.resource.emit("update"); |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +1,31 @@ |
|||||||
import type { UnexpectedResourceError } from "../../../requester/results/error/ErrorResult"; |
import type { Resource } from "../../Resource"; |
||||||
import { ResourceError } from "../../../requester/results/error/ErrorResult"; |
import type { UnexpectedResourceError } from "./ErrorResult"; |
||||||
|
import { ResourceError } from "./ErrorResult"; |
||||||
|
|
||||||
export type NotificationCallbackError = |
export type NotificationCallbackError = |
||||||
| DisconnectedAttemptingReconnectError |
| DisconnectedAttemptingReconnectError |
||||||
| DisconnectedNotAttemptingReconnectError |
| DisconnectedNotAttemptingReconnectError |
||||||
| UnsupportedNotificationError |
| UnsupportedNotificationError |
||||||
| UnexpectedResourceError; |
| UnexpectedResourceError<Resource>; |
||||||
|
|
||||||
/** |
/** |
||||||
* Indicates that the requested method for receiving notifications is not |
* Indicates that the requested method for receiving notifications is not |
||||||
* supported by this Pod. |
* supported by this Pod. |
||||||
*/ |
*/ |
||||||
export class UnsupportedNotificationError extends ResourceError { |
export class UnsupportedNotificationError extends ResourceError<Resource> { |
||||||
readonly type = "unsupportedNotificationError" as const; |
readonly type = "unsupportedNotificationError" as const; |
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Indicates that the socket has disconnected and is attempting to reconnect. |
* Indicates that the socket has disconnected and is attempting to reconnect. |
||||||
*/ |
*/ |
||||||
export class DisconnectedAttemptingReconnectError extends ResourceError { |
export class DisconnectedAttemptingReconnectError extends ResourceError<Resource> { |
||||||
readonly type = "disconnectedAttemptingReconnectError" as const; |
readonly type = "disconnectedAttemptingReconnectError" as const; |
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Indicates that the socket has disconnected and is attempting to reconnect. |
* Indicates that the socket has disconnected and is attempting to reconnect. |
||||||
*/ |
*/ |
||||||
export class DisconnectedNotAttemptingReconnectError extends ResourceError { |
export class DisconnectedNotAttemptingReconnectError extends ResourceError<Resource> { |
||||||
readonly type = "disconnectedNotAttemptingReconnectError" as const; |
readonly type = "disconnectedNotAttemptingReconnectError" as const; |
||||||
} |
} |
@ -1,19 +0,0 @@ |
|||||||
import type { Container } from "../../../resource/Container"; |
|
||||||
import type { ResourceSuccess, SuccessResult } from "./SuccessResult"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the request to check if a resource is the root container was |
|
||||||
* a success. |
|
||||||
*/ |
|
||||||
export interface CheckRootContainerSuccess extends ResourceSuccess { |
|
||||||
type: "checkRootContainerSuccess"; |
|
||||||
/** |
|
||||||
* True if this resoure is the root container |
|
||||||
*/ |
|
||||||
isRootContainer: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
export interface GetStorageContainerFromWebIdSuccess extends SuccessResult { |
|
||||||
type: "getStorageContainerFromWebIdSuccess"; |
|
||||||
storageContainers: Container[]; |
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the request to create the resource was a success. |
|
||||||
*/ |
|
||||||
export interface CreateSuccess extends ResourceSuccess { |
|
||||||
type: "createSuccess"; |
|
||||||
/** |
|
||||||
* True if there was a resource that existed before at the given URI that was |
|
||||||
* overwritten |
|
||||||
*/ |
|
||||||
didOverwrite: boolean; |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the request to delete a resource was a success. |
|
||||||
*/ |
|
||||||
export interface DeleteSuccess extends ResourceSuccess { |
|
||||||
type: "deleteSuccess"; |
|
||||||
|
|
||||||
/** |
|
||||||
* True if there was a resource at the provided URI that was deleted. False if |
|
||||||
* a resource didn't exist. |
|
||||||
*/ |
|
||||||
resourceExisted: boolean; |
|
||||||
} |
|
@ -1,71 +0,0 @@ |
|||||||
import type { ResourceSuccess, SuccessResult } from "./SuccessResult"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the request to read a resource was a success |
|
||||||
*/ |
|
||||||
export interface ReadSuccess extends ResourceSuccess { |
|
||||||
/** |
|
||||||
* True if the resource was recalled from local memory rather than a recent |
|
||||||
* request |
|
||||||
*/ |
|
||||||
recalledFromMemory: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the read request was successful and that the resource |
|
||||||
* retrieved was a binary resource. |
|
||||||
*/ |
|
||||||
export interface BinaryReadSuccess extends ReadSuccess { |
|
||||||
type: "binaryReadSuccess"; |
|
||||||
/** |
|
||||||
* The raw data for the binary resource |
|
||||||
*/ |
|
||||||
blob: Blob; |
|
||||||
/** |
|
||||||
* The mime type of the binary resource |
|
||||||
*/ |
|
||||||
mimeType: string; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the read request was successful and that the resource |
|
||||||
* retrieved was a data (RDF) resource. |
|
||||||
*/ |
|
||||||
export interface DataReadSuccess extends ReadSuccess { |
|
||||||
type: "dataReadSuccess"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the read request was successful and that the resource |
|
||||||
* retrieved was a container resource. |
|
||||||
*/ |
|
||||||
export interface ContainerReadSuccess extends ReadSuccess { |
|
||||||
type: "containerReadSuccess"; |
|
||||||
/** |
|
||||||
* True if this container is a root container |
|
||||||
*/ |
|
||||||
isRootContainer: boolean; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that the read request was successful, but no resource exists at |
|
||||||
* the provided URI. |
|
||||||
*/ |
|
||||||
export interface AbsentReadSuccess extends ReadSuccess { |
|
||||||
type: "absentReadSuccess"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* A helper function that checks to see if a result is a ReadSuccess result |
|
||||||
* |
|
||||||
* @param result - the result to check |
|
||||||
* @returns true if the result is a ReadSuccessResult result |
|
||||||
*/ |
|
||||||
export function isReadSuccess(result: SuccessResult): result is ReadSuccess { |
|
||||||
return ( |
|
||||||
result.type === "binaryReadSuccess" || |
|
||||||
result.type === "dataReadSuccess" || |
|
||||||
result.type === "absentReadSuccess" || |
|
||||||
result.type === "containerReadSuccess" |
|
||||||
); |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
import type { ResourceSuccess } from "./SuccessResult"; |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that an update request to a resource was successful |
|
||||||
*/ |
|
||||||
export interface UpdateSuccess extends ResourceSuccess { |
|
||||||
type: "updateSuccess"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that an update request to the default graph was successful. This |
|
||||||
* data was not written to a Pod. It was only written locally. |
|
||||||
*/ |
|
||||||
export interface UpdateDefaultGraphSuccess extends ResourceSuccess { |
|
||||||
type: "updateDefaultGraphSuccess"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates that LDO ignored an invalid update (usually because a container |
|
||||||
* attempted an update) |
|
||||||
*/ |
|
||||||
export interface IgnoredInvalidUpdateSuccess extends ResourceSuccess { |
|
||||||
type: "ignoredInvalidUpdateSuccess"; |
|
||||||
} |
|
Loading…
Reference in new issue