parent
9d8e3a7235
commit
e4f434eeb3
@ -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."); |
|
||||||
} |
|
||||||
} |
|
@ -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; |
||||||
|
}); |
||||||
|
} |
Loading…
Reference in new issue