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