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

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { LdoBase, ShapeType } from "@ldo/ldo";
import { LdoTransactionDataset } from "@ldo/ldo";
import type { DatasetFactory, Quad } from "@rdfjs/types";
import {
@ -7,7 +8,7 @@ import {
} from "@ldo/subscribable-dataset";
import type { DatasetChanges, GraphNode } from "@ldo/rdf-utils";
import type { ConnectedPlugin } from "./types/ConnectedPlugin";
import type { ConnectedContext } from "./ConnectedContext";
import type { ConnectedContext } from "./types/ConnectedContext";
import type {
GetResourceReturnType,
IConnectedLdoDataset,
@ -21,6 +22,8 @@ import type {
AggregateSuccess,
SuccessResult,
} from "./results/success/SuccessResult";
import { ConnectedLdoBuilder } from "./ConnectedLdoBuilder";
import jsonldDatasetProxy from "@ldo/jsonld-dataset-proxy";
/**
* 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,
};
}
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 "./ConnectedLdoBuilder";
export * from "./ConnectedLdoDataset";
export * from "./ConnectedLdoTransactionDataset";
export * from "./types/ConnectedPlugin";
@ -21,3 +22,6 @@ export * from "./results/success/ReadSuccess";
export * from "./results/success/UpdateSuccess";
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 type { ConnectedPlugin } from "../types/ConnectedPlugin";
import type { ConnectedContext } from "../ConnectedContext";
import type { ConnectedContext } from "../types/ConnectedContext";
import type { SubscriptionCallbacks } from "./SubscriptionCallbacks";
import type { NotificationCallbackError } from "../results/error/NotificationErrors";

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { ConnectedLdoDataset } from "./ConnectedLdoDataset";
import type { ConnectedPlugin } from "./types/ConnectedPlugin";
import type { ConnectedPlugin } from "./ConnectedPlugin";
import type { IConnectedLdoDataset } from "./IConnectedLdoDataset";
/**
* 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<
Plugins extends ConnectedPlugin<any, any, any, any>[],
> = {
dataset: ConnectedLdoDataset<Plugins>;
dataset: IConnectedLdoDataset<Plugins>;
} & {
[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
// 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
*/
export type LQInput<Type> = LQInputObject<Type>;
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
? LQInputObject<Type>
: true;
export type LQInput<Type> = Type extends LdSet<infer SetSubType>
export type LQInputFlattenSet<Type> = Type extends LdSet<infer SetSubType>
? LQInputSubSet<SetSubType>
: LQInputSubSet<Type>;
/**
* Link Query Input Default
*/
export type LQInputDefaultType<Type> = {
[key in keyof Type]: Type[key] extends object ? undefined : true;
};
// TODO: I don't remember why I need this. Delete if unneeded
// export type LQInputDefaultType<Type> = {
// [key in keyof Type]: Type[key] extends object ? undefined : true;
// };
export type LQInputDefault<Type> =
LQInputDefaultType<Type> extends LQInput<Type>
? LQInputDefaultType<Type>
: never;
// export type LQInputDefault<Type> =
// LQInputDefaultType<Type> extends LQInput<Type>
// ? LQInputDefaultType<Type>
// : never;
/**
* Link Query Return
*/
export type LQReturn<Type, Input extends LQInput<Type>> = LQReturnObject<
Type,
Input
>;
export type LQReturnObject<Type, Input extends LQInputObject<Type>> = {
[key in keyof Required<Type> as undefined extends Input[key]
? never
: key]: Input[key] extends LQInput<Type[key]>
: key]: Input[key] extends LQInputFlattenSet<Type[key]>
? undefined extends Type[key]
? LQReturn<Type[key], Input[key]> | undefined
: LQReturn<Type[key], Input[key]>
? LQReturnExpandSet<Type[key], Input[key]> | undefined
: LQReturnExpandSet<Type[key], Input[key]>
: never;
};
@ -51,9 +60,9 @@ export type LQReturnSubSet<Type, Input> = Input extends LQInputSubSet<Type>
: Type
: never;
export type LQReturn<
export type LQReturnExpandSet<
Type,
Input extends LQInput<Type>,
Input extends LQInputFlattenSet<Type>,
> = NonNullable<Type> extends LdSet<infer SetSubType>
? LdSet<LQReturnSubSet<SetSubType, Input>>
: LQReturnSubSet<Type, Input>;
@ -67,9 +76,33 @@ export type ExpandDeep<T> = T extends LdSet<infer U>
/**
* ILinkQuery: Manages resources in a link query
*/
export interface LinkQueryRunOptions {
reload?: boolean;
}
export interface ILinkQuery<Type extends LdoBase, Input extends LQInput<Type>> {
run(): Promise<ExpandDeep<LQReturn<Type, Input>>>;
subscribe(): Promise<void>;
unsubscribe(): void;
run(
options?: LinkQueryRunOptions,
): Promise<ExpandDeep<LQReturn<Type, Input>>>;
subscribe(): Promise<string>;
unsubscribe(subscriptionId: string): void;
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