Read theoretically works

main
Jackson Morgan 4 months ago
parent 9d8e3a7235
commit e4f434eeb3
  1. 10
      packages/connected/src/ConnectedLdoBuilder.ts
  2. 12
      packages/connected/src/ConnectedLdoTransactionDataset.ts
  3. 43
      packages/connected/src/ResourceLinkQuery.ts
  4. 4
      packages/connected/src/index.ts
  5. 75
      packages/connected/src/linkTraversal/ResourceLinkQuery.ts
  6. 135
      packages/connected/src/linkTraversal/exploreLinks.ts
  7. 2
      packages/connected/src/notifications/NotificationSubscription.ts
  8. 6
      packages/connected/src/types/ConnectedContext.ts
  9. 69
      packages/connected/src/types/ILinkQuery.ts

@ -4,9 +4,9 @@ import type { IConnectedLdoBuilder } from "./types/IConnectedLdoBuilder";
import type { JsonldDatasetProxyBuilder } from "@ldo/jsonld-dataset-proxy"; import type { JsonldDatasetProxyBuilder } from "@ldo/jsonld-dataset-proxy";
import type { SubjectNode } from "@ldo/rdf-utils"; import type { SubjectNode } from "@ldo/rdf-utils";
import type { LQInput, ILinkQuery } from "./types/ILinkQuery"; import type { LQInput, ILinkQuery } from "./types/ILinkQuery";
import { ResourceLinkQuery } from "./ResourceLinkQuery"; import { ResourceLinkQuery } from "./linkTraversal/ResourceLinkQuery";
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset";
import type { ConnectedPlugin } from "./types/ConnectedPlugin"; import type { ConnectedPlugin } from "./types/ConnectedPlugin";
import type { IConnectedLdoDataset } from "./types/IConnectedLdoDataset";
export class ConnectedLdoBuilder< export class ConnectedLdoBuilder<
Type extends LdoBase, Type extends LdoBase,
@ -15,10 +15,10 @@ export class ConnectedLdoBuilder<
extends LdoBuilder<Type> extends LdoBuilder<Type>
implements IConnectedLdoBuilder<Type, Plugins> implements IConnectedLdoBuilder<Type, Plugins>
{ {
protected parentDataset: ConnectedLdoDataset<Plugins>; protected parentDataset: IConnectedLdoDataset<Plugins>;
constructor( constructor(
parentDataset: ConnectedLdoDataset<Plugins>, parentDataset: IConnectedLdoDataset<Plugins>,
jsonldDatasetProxyBuilder: JsonldDatasetProxyBuilder, jsonldDatasetProxyBuilder: JsonldDatasetProxyBuilder,
shapeType: ShapeType<Type>, shapeType: ShapeType<Type>,
) { ) {
@ -34,7 +34,7 @@ export class ConnectedLdoBuilder<
return new ResourceLinkQuery( return new ResourceLinkQuery(
this.parentDataset, this.parentDataset,
this.shapeType, this.shapeType,
this.jsonldDatasetProxyBuilder, this,
startingResource, startingResource,
startingSubject, startingSubject,
linkQueryInput, linkQueryInput,

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { LdoBase, ShapeType } from "@ldo/ldo";
import { LdoTransactionDataset } from "@ldo/ldo"; import { LdoTransactionDataset } from "@ldo/ldo";
import type { DatasetFactory, Quad } from "@rdfjs/types"; import type { DatasetFactory, Quad } from "@rdfjs/types";
import { import {
@ -7,7 +8,7 @@ import {
} from "@ldo/subscribable-dataset"; } from "@ldo/subscribable-dataset";
import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils"; import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils";
import type { ConnectedPlugin } from "./types/ConnectedPlugin"; import type { ConnectedPlugin } from "./types/ConnectedPlugin";
import type { ConnectedContext } from "./ConnectedContext"; import type { ConnectedContext } from "./types/ConnectedContext";
import type { import type {
GetResourceReturnType, GetResourceReturnType,
IConnectedLdoDataset, IConnectedLdoDataset,
@ -21,6 +22,8 @@ import type {
AggregateSuccess, AggregateSuccess,
SuccessResult, SuccessResult,
} from "./results/success/SuccessResult"; } from "./results/success/SuccessResult";
import { ConnectedLdoBuilder } from "./ConnectedLdoBuilder";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
/** /**
* A ConnectedLdoTransactionDataset has all the functionality of a * A ConnectedLdoTransactionDataset has all the functionality of a
@ -225,4 +228,11 @@ export class ConnectedLdoTransactionDataset<Plugins extends ConnectedPlugin[]>
results: results.map((result) => result[2]) as any, results: results.map((result) => result[2]) as any,
}; };
} }
public usingType<Type extends LdoBase>(
shapeType: ShapeType<Type>,
): ConnectedLdoBuilder<Type, Plugins> {
const proxyBuilder = jsonldDatasetProxy(this, shapeType.context);
return new ConnectedLdoBuilder(this, proxyBuilder, shapeType);
}
} }

@ -1,43 +0,0 @@
import type { LdoBase, ShapeType } from "@ldo/ldo";
import type {
ExpandDeep,
ILinkQuery,
LQInput,
LQReturn,
} from "./types/ILinkQuery";
import type { ConnectedPlugin } from "./types/ConnectedPlugin";
import type { JsonldDatasetProxyBuilder } from "@ldo/jsonld-dataset-proxy";
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset";
import type { SubjectNode } from "@ldo/rdf-utils";
export class ResourceLinkQuery<
Type extends LdoBase,
QueryInput extends LQInput<Type>,
Plugins extends ConnectedPlugin[],
> implements ILinkQuery<Type, QueryInput>
{
constructor(
protected parentDataset: ConnectedLdoDataset<Plugins>,
protected shapeType: ShapeType<Type>,
protected jsonldDatasetProxyBuilder: JsonldDatasetProxyBuilder,
protected startingResource: Plugins[number]["types"]["resource"],
protected startingSubject: SubjectNode | string,
protected linkQueryInput: QueryInput,
) {}
run(): Promise<ExpandDeep<LQReturn<Type, QueryInput>>> {
throw new Error("Method not implemented.");
}
subscribe(): Promise<void> {
throw new Error("Method not implemented.");
}
unsubscribe(): void {
throw new Error("Method not implemented.");
}
fromSubject(): ExpandDeep<LQReturn<Type, QueryInput>> {
throw new Error("Method not implemented.");
}
}

@ -1,4 +1,5 @@
export * from "./types/IConnectedLdoDataset"; export * from "./types/IConnectedLdoDataset";
export * from "./ConnectedLdoBuilder";
export * from "./ConnectedLdoDataset"; export * from "./ConnectedLdoDataset";
export * from "./ConnectedLdoTransactionDataset"; export * from "./ConnectedLdoTransactionDataset";
export * from "./types/ConnectedPlugin"; export * from "./types/ConnectedPlugin";
@ -21,3 +22,6 @@ export * from "./results/success/ReadSuccess";
export * from "./results/success/UpdateSuccess"; export * from "./results/success/UpdateSuccess";
export * from "./notifications/NotificationSubscription"; export * from "./notifications/NotificationSubscription";
export * from "./linkTraversal/ResourceLinkQuery";
export * from "./linkTraversal/exploreLinks";

@ -0,0 +1,75 @@
import type { LdoBase, ShapeType } from "@ldo/ldo";
import type {
ExpandDeep,
ILinkQuery,
LQInput,
LQReturn,
} from "../types/ILinkQuery";
import type { ConnectedPlugin } from "../types/ConnectedPlugin";
import type { SubjectNode } from "@ldo/rdf-utils";
import { exploreLinks } from "./exploreLinks";
import type { IConnectedLdoDataset } from "../types/IConnectedLdoDataset";
import type { IConnectedLdoBuilder } from "../types/IConnectedLdoBuilder";
export class ResourceLinkQuery<
Type extends LdoBase,
QueryInput extends LQInput<Type>,
Plugins extends ConnectedPlugin[],
> implements ILinkQuery<Type, QueryInput>
{
protected trackedResources: Set<Plugins[number]["types"]["resource"]> =
new Set();
// uri -> unsubscribeId
protected resourceUnsubscribeIds: Record<string, string> = {};
protected thisUnsubscribeIds: Set<string> = new Set();
constructor(
protected parentDataset: IConnectedLdoDataset<Plugins>,
protected shapeType: ShapeType<Type>,
protected ldoBuilder: IConnectedLdoBuilder<Type, Plugins>,
protected startingResource: Plugins[number]["types"]["resource"],
protected startingSubject: SubjectNode | string,
protected linkQueryInput: QueryInput,
) {}
async run(options?: {
reload?: boolean;
}): Promise<ExpandDeep<LQReturn<Type, QueryInput>>> {
await exploreLinks(
this.parentDataset,
this.shapeType,
this.startingResource,
this.startingSubject,
this.linkQueryInput,
{ shouldRefreshResources: options?.reload },
);
return this.fromSubject();
}
subscribe(): Promise<string> {
throw new Error("Method not implemented.");
}
private async fullUnsubscribe(): Promise<void> {
// TODO
}
async unsubscribe(unsubscribeId: string): Promise<void> {
this.thisUnsubscribeIds.delete(unsubscribeId);
if (this.thisUnsubscribeIds.size === 0) {
await this.fullUnsubscribe();
}
}
fromSubject(): ExpandDeep<LQReturn<Type, QueryInput>> {
return this.ldoBuilder.fromSubject(
this.startingSubject,
) as unknown as ExpandDeep<LQReturn<Type, QueryInput>>;
}
getSubscribedResources(): Plugins[number]["types"]["resource"][] {
return Object.keys(this.resourceUnsubscribeIds).map((uri) =>
this.parentDataset.getResource(uri),
);
}
}

@ -0,0 +1,135 @@
import type { LdoBase, ShapeType } from "@ldo/ldo";
import type { ConnectedPlugin } from "../types/ConnectedPlugin";
import type { SubjectNode } from "@ldo/rdf-utils";
import type { LQInput } from "../types/ILinkQuery";
import { BasicLdSet } from "@ldo/jsonld-dataset-proxy";
import type { IConnectedLdoDataset } from "../types/IConnectedLdoDataset";
interface ExploreLinksOptions<Plugins extends ConnectedPlugin[]> {
onResourceEncountered?: (
resource: Plugins[number]["types"]["resource"],
) => void;
shouldRefreshResources?: boolean;
}
export async function exploreLinks<
Type extends LdoBase,
Plugins extends ConnectedPlugin[],
>(
dataset: IConnectedLdoDataset<Plugins>,
shapeType: ShapeType<Type>,
startingResource: Plugins[number]["types"]["resource"],
startingSubject: SubjectNode | string,
queryInput: LQInput<Type>,
options?: ExploreLinksOptions<Plugins>,
): Promise<void> {
// Do an initial check of the resources.
const readResult = options?.shouldRefreshResources
? await startingResource.read()
: await startingResource.readIfUnfetched();
if (readResult.isError) return;
const ldObject = dataset.usingType(shapeType).fromSubject(startingSubject);
const fetchedDuringThisExploration = new Set<string>([startingResource.uri]);
// Recursively explore the rest
await exploreLinksRecursive(
dataset,
ldObject,
queryInput,
fetchedDuringThisExploration,
options,
);
}
export async function exploreLinksRecursive<
Type extends LdoBase,
Plugins extends ConnectedPlugin[],
>(
dataset: IConnectedLdoDataset<Plugins>,
ldObject: Type,
queryInput: LQInput<Type>,
fetchedDuringThisExploration: Set<string>,
options?: ExploreLinksOptions<Plugins>,
): Promise<void> {
const shouldFetch = shouldFetchResource(
dataset,
ldObject,
queryInput,
fetchedDuringThisExploration,
);
if (shouldFetch) {
const resourceToFetch = dataset.getResource(ldObject["@id"]);
const readResult = options?.shouldRefreshResources
? await resourceToFetch.read()
: await resourceToFetch.readIfUnfetched();
// If there was an error with the read, the traversal is done.
if (readResult.isError) {
return;
}
fetchedDuringThisExploration.add(resourceToFetch.uri);
}
// Recurse through the other elemenets
await Promise.all(
Object.entries(queryInput).map(async ([queryKey, queryValue]) => {
if (
queryValue != undefined &&
queryValue !== true &&
ldObject[queryKey] != undefined
) {
if (ldObject[queryKey] instanceof BasicLdSet) {
await Promise.all(
ldObject[queryKey].map(async (item) => {
await exploreLinksRecursive(
dataset,
item,
queryValue,
fetchedDuringThisExploration,
options,
);
}),
);
}
await exploreLinksRecursive(
dataset,
ldObject[queryKey],
queryValue,
fetchedDuringThisExploration,
options,
);
}
}),
);
}
/**
* Determines if a resource needs to be fetched based on given data
*/
export function shouldFetchResource<
Type extends LdoBase,
Plugins extends ConnectedPlugin[],
>(
dataset: IConnectedLdoDataset<Plugins>,
ldObject: Type,
queryInput: LQInput<Type>,
fetchedDuringThisExploration: Set<string>,
): boolean {
const linkedResourceUri: string | undefined = ldObject["@id"];
// If it's a blank node, no need to fetch
if (!linkedResourceUri) return false;
const linkedResource = dataset.getResource(linkedResourceUri);
// If we've already explored the resource in this exporation, do not fetch
if (fetchedDuringThisExploration.has(linkedResource.uri)) return false;
return Object.entries(queryInput).some(([queryKey, queryValue]) => {
// If value is undefined then no need to fetch
if (!queryValue) return false;
// Always fetch if there's a set in the object
if (ldObject[queryKey] instanceof BasicLdSet) return true;
// Fetch if a singleton set is not present
if (ldObject[queryKey] == undefined) return true;
// Otherwise no need t to fetch
return false;
});
}

@ -1,6 +1,6 @@
import { v4 } from "uuid"; import { v4 } from "uuid";
import type { ConnectedPlugin } from "../types/ConnectedPlugin"; import type { ConnectedPlugin } from "../types/ConnectedPlugin";
import type { ConnectedContext } from "../ConnectedContext"; import type { ConnectedContext } from "../types/ConnectedContext";
import type { SubscriptionCallbacks } from "./SubscriptionCallbacks"; import type { SubscriptionCallbacks } from "./SubscriptionCallbacks";
import type { NotificationCallbackError } from "../results/error/NotificationErrors"; import type { NotificationCallbackError } from "../results/error/NotificationErrors";

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset"; import type { ConnectedPlugin } from "./ConnectedPlugin";
import type { ConnectedPlugin } from "./types/ConnectedPlugin"; import type { IConnectedLdoDataset } from "./IConnectedLdoDataset";
/** /**
* Each Plugin comes with a context. This is the aggregate of all those contexts * Each Plugin comes with a context. This is the aggregate of all those contexts
@ -9,7 +9,7 @@ import type { ConnectedPlugin } from "./types/ConnectedPlugin";
export type ConnectedContext< export type ConnectedContext<
Plugins extends ConnectedPlugin<any, any, any, any>[], Plugins extends ConnectedPlugin<any, any, any, any>[],
> = { > = {
dataset: ConnectedLdoDataset<Plugins>; dataset: IConnectedLdoDataset<Plugins>;
} & { } & {
[P in Plugins[number] as P["name"]]: P["types"]["context"]; [P in Plugins[number] as P["name"]]: P["types"]["context"];
}; };

@ -3,45 +3,54 @@
// If I ever want to implement a global query interface, this is a good place // If I ever want to implement a global query interface, this is a good place
// to start. // to start.
import type { LdoBase, LdSet } from "@ldo/ldo"; import type { LdoBase, LdSet, ShapeType } from "@ldo/ldo";
import { ProfileShapeType } from "packages/ldo/test/profileData";
/** /**
* Link Query Input * Link Query Input
*/ */
export type LQInput<Type> = LQInputObject<Type>;
export type LQInputObject<Type> = Partial<{ export type LQInputObject<Type> = Partial<{
[key in keyof Type]: LQInput<Type[key]>; [key in keyof Type]: LQInputFlattenSet<Type[key]>;
}>; }>;
export type LQInputSubSet<Type> = Type extends object export type LQInputSubSet<Type> = Type extends object
? LQInputObject<Type> ? LQInputObject<Type>
: true; : true;
export type LQInput<Type> = Type extends LdSet<infer SetSubType> export type LQInputFlattenSet<Type> = Type extends LdSet<infer SetSubType>
? LQInputSubSet<SetSubType> ? LQInputSubSet<SetSubType>
: LQInputSubSet<Type>; : LQInputSubSet<Type>;
/** /**
* Link Query Input Default * Link Query Input Default
*/ */
export type LQInputDefaultType<Type> = { // TODO: I don't remember why I need this. Delete if unneeded
[key in keyof Type]: Type[key] extends object ? undefined : true; // export type LQInputDefaultType<Type> = {
}; // [key in keyof Type]: Type[key] extends object ? undefined : true;
// };
export type LQInputDefault<Type> = // export type LQInputDefault<Type> =
LQInputDefaultType<Type> extends LQInput<Type> // LQInputDefaultType<Type> extends LQInput<Type>
? LQInputDefaultType<Type> // ? LQInputDefaultType<Type>
: never; // : never;
/** /**
* Link Query Return * Link Query Return
*/ */
export type LQReturn<Type, Input extends LQInput<Type>> = LQReturnObject<
Type,
Input
>;
export type LQReturnObject<Type, Input extends LQInputObject<Type>> = { export type LQReturnObject<Type, Input extends LQInputObject<Type>> = {
[key in keyof Required<Type> as undefined extends Input[key] [key in keyof Required<Type> as undefined extends Input[key]
? never ? never
: key]: Input[key] extends LQInput<Type[key]> : key]: Input[key] extends LQInputFlattenSet<Type[key]>
? undefined extends Type[key] ? undefined extends Type[key]
? LQReturn<Type[key], Input[key]> | undefined ? LQReturnExpandSet<Type[key], Input[key]> | undefined
: LQReturn<Type[key], Input[key]> : LQReturnExpandSet<Type[key], Input[key]>
: never; : never;
}; };
@ -51,9 +60,9 @@ export type LQReturnSubSet<Type, Input> = Input extends LQInputSubSet<Type>
: Type : Type
: never; : never;
export type LQReturn< export type LQReturnExpandSet<
Type, Type,
Input extends LQInput<Type>, Input extends LQInputFlattenSet<Type>,
> = NonNullable<Type> extends LdSet<infer SetSubType> > = NonNullable<Type> extends LdSet<infer SetSubType>
? LdSet<LQReturnSubSet<SetSubType, Input>> ? LdSet<LQReturnSubSet<SetSubType, Input>>
: LQReturnSubSet<Type, Input>; : LQReturnSubSet<Type, Input>;
@ -67,9 +76,33 @@ export type ExpandDeep<T> = T extends LdSet<infer U>
/** /**
* ILinkQuery: Manages resources in a link query * ILinkQuery: Manages resources in a link query
*/ */
export interface LinkQueryRunOptions {
reload?: boolean;
}
export interface ILinkQuery<Type extends LdoBase, Input extends LQInput<Type>> { export interface ILinkQuery<Type extends LdoBase, Input extends LQInput<Type>> {
run(): Promise<ExpandDeep<LQReturn<Type, Input>>>; run(
subscribe(): Promise<void>; options?: LinkQueryRunOptions,
unsubscribe(): void; ): Promise<ExpandDeep<LQReturn<Type, Input>>>;
subscribe(): Promise<string>;
unsubscribe(subscriptionId: string): void;
fromSubject(): ExpandDeep<LQReturn<Type, Input>>; fromSubject(): ExpandDeep<LQReturn<Type, Input>>;
} }
// TODO: Remove test functions
// function test<Type extends LdoBase, Input extends LQInput<Type>>(
// _shapeType: ShapeType<Type>,
// _input: Input,
// ): ExpandDeep<LQReturn<Type, Input>> {
// throw new Error("Not Implemeneted");
// }
// const result = test(ProfileShapeType, {
// fn: true,
// name: true,
// hasTelephone: {
// type: {
// "@id": true,
// },
// value: true,
// },
// });

Loading…
Cancel
Save