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 { 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>; |
||||
} & { |
||||
[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 { ErrorResult } from "./results/error/ErrorResult"; |
||||
|
||||
export interface ConnectedPlugin { |
||||
name: string; |
||||
getResource(uri: string): Resource; |
||||
createResource(): Promise<Resource>; |
||||
isUriValid(uri: string): boolean; |
||||
normalizeUri?: (uri: string) => string; |
||||
export interface ConnectedPlugin< |
||||
Name extends string, |
||||
UriType extends string, |
||||
ResourceType extends Resource<UriType>, |
||||
ContextType, |
||||
> { |
||||
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 { ResourceError } from "../../../requester/results/error/ErrorResult"; |
||||
import type { Resource } from "../../Resource"; |
||||
import type { UnexpectedResourceError } from "./ErrorResult"; |
||||
import { ResourceError } from "./ErrorResult"; |
||||
|
||||
export type NotificationCallbackError = |
||||
| DisconnectedAttemptingReconnectError |
||||
| DisconnectedNotAttemptingReconnectError |
||||
| UnsupportedNotificationError |
||||
| UnexpectedResourceError; |
||||
| UnexpectedResourceError<Resource>; |
||||
|
||||
/** |
||||
* Indicates that the requested method for receiving notifications is not |
||||
* supported by this Pod. |
||||
*/ |
||||
export class UnsupportedNotificationError extends ResourceError { |
||||
export class UnsupportedNotificationError extends ResourceError<Resource> { |
||||
readonly type = "unsupportedNotificationError" as const; |
||||
} |
||||
|
||||
/** |
||||
* 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; |
||||
} |
||||
|
||||
/** |
||||
* 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; |
||||
} |
@ -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