From bc9f10db0b66ecb3b8c521aad063f837e3235f26 Mon Sep 17 00:00:00 2001 From: Jackson Morgan Date: Tue, 14 Jan 2025 16:32:07 -0500 Subject: [PATCH] Untested implementation of init type index --- package-lock.json | 16 +- packages/solid-type-index/package.json | 3 +- .../src/.ldo/typeIndex.context.ts | 3 + packages/solid-type-index/src/constants.ts | 1 + packages/solid-type-index/src/getTypeIndex.ts | 39 ++-- packages/solid-type-index/src/index.ts | 4 + .../src/react/useAddToTypeIndex.ts | 0 .../src/react/useRemoveFromTypeIndex.ts | 0 packages/solid-type-index/src/setTypeIndex.ts | 200 ++++++++++++++++++ packages/solid-type-index/src/util/Options.ts | 13 ++ .../solid-type-index/test/General.test.tsx | 24 ++- packages/solid-type-index/test/React.tsx | 8 - packages/solid-type-index/test/setUpServer.ts | 110 ++++++---- .../solid/src/SolidLdoTransactionDataset.ts | 3 + 14 files changed, 354 insertions(+), 70 deletions(-) delete mode 100644 packages/solid-type-index/src/react/useAddToTypeIndex.ts delete mode 100644 packages/solid-type-index/src/react/useRemoveFromTypeIndex.ts create mode 100644 packages/solid-type-index/src/util/Options.ts delete mode 100644 packages/solid-type-index/test/React.tsx diff --git a/package-lock.json b/package-lock.json index 7339650..22f44a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29738,7 +29738,8 @@ "license": "MIT", "dependencies": { "@ldo/solid": "^0.0.1-alpha.28", - "@ldo/solid-react": "^0.0.1-alpha.28" + "@ldo/solid-react": "^0.0.1-alpha.28", + "uuid": "^11.0.5" }, "devDependencies": { "@ldo/rdf-utils": "^0.0.1-alpha.24", @@ -29810,6 +29811,19 @@ "node": ">=4.2.0" } }, + "packages/solid-type-index/node_modules/uuid": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "packages/solid/node_modules/ts-jest": { "version": "27.1.5", "dev": true, diff --git a/packages/solid-type-index/package.json b/packages/solid-type-index/package.json index 7970b8a..b53a786 100644 --- a/packages/solid-type-index/package.json +++ b/packages/solid-type-index/package.json @@ -37,7 +37,8 @@ }, "dependencies": { "@ldo/solid": "^0.0.1-alpha.28", - "@ldo/solid-react": "^0.0.1-alpha.28" + "@ldo/solid-react": "^0.0.1-alpha.28", + "uuid": "^11.0.5" }, "files": [ "dist", diff --git a/packages/solid-type-index/src/.ldo/typeIndex.context.ts b/packages/solid-type-index/src/.ldo/typeIndex.context.ts index 71ee727..e469629 100644 --- a/packages/solid-type-index/src/.ldo/typeIndex.context.ts +++ b/packages/solid-type-index/src/.ldo/typeIndex.context.ts @@ -6,6 +6,9 @@ import { LdoJsonldContext } from "@ldo/jsonld-dataset-proxy"; * ============================================================================= */ export const typeIndexContext: LdoJsonldContext = { + type: { + "@id": "@type" + }, TypeIndex: { "@id": "http://www.w3.org/ns/solid/terms#TypeIndex", "@context": { diff --git a/packages/solid-type-index/src/constants.ts b/packages/solid-type-index/src/constants.ts index 1d9e246..4fc412b 100644 --- a/packages/solid-type-index/src/constants.ts +++ b/packages/solid-type-index/src/constants.ts @@ -1,3 +1,4 @@ export const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; export const TYPE_REGISTRATION = "http://www.w3.org/ns/solid/terms#TypeRegistration"; +export const FOR_CLASS = "http://www.w3.org/2006/vcard/ns#forClass"; diff --git a/packages/solid-type-index/src/getTypeIndex.ts b/packages/solid-type-index/src/getTypeIndex.ts index 0753eb3..e34981e 100644 --- a/packages/solid-type-index/src/getTypeIndex.ts +++ b/packages/solid-type-index/src/getTypeIndex.ts @@ -1,31 +1,20 @@ -import type { ContainerUri, LeafUri, SolidLdoDataset } from "@ldo/solid"; -import { createSolidLdoDataset } from "@ldo/solid"; +import type { ContainerUri, LeafUri } from "@ldo/solid"; import type { TypeRegistration } from "./.ldo/typeIndex.typings"; -import { guaranteeFetch } from "@ldo/solid/dist/util/guaranteeFetch"; import type { TypeIndexProfile } from "./.ldo/profile.typings"; import { TypeIndexProfileShapeType } from "./.ldo/profile.shapeTypes"; import { TypeRegistrationShapeType } from "./.ldo/typeIndex.shapeTypes"; import { RDF_TYPE, TYPE_REGISTRATION } from "./constants"; - -interface GetInstanceUrisOptions { - solidLdoDataset?: SolidLdoDataset; - fetch?: typeof fetch; -} +import type { Options } from "./util/Options"; +import { guaranteeOptions } from "./util/Options"; export async function getTypeRegistrations( webId: string, - options?: GetInstanceUrisOptions, + options?: Options, ): Promise { - const fetch = guaranteeFetch(options?.fetch); - const dataset = options?.solidLdoDataset ?? createSolidLdoDataset({ fetch }); + const { dataset } = guaranteeOptions(options); // Get Profile - const profileResource = dataset.getResource(webId); - const readResult = await profileResource.readIfUnfetched(); - if (readResult.isError) throw readResult; - const profile = dataset - .usingType(TypeIndexProfileShapeType) - .fromSubject(webId); + const profile = await getProfile(webId, options); // Get Type Indexes const typeIndexUris = getTypeIndexesUrisFromProfile(profile); @@ -45,6 +34,17 @@ export async function getTypeRegistrations( .matchSubject(RDF_TYPE, TYPE_REGISTRATION); } +export async function getProfile( + webId: string, + options?: Options, +): Promise { + const { dataset } = guaranteeOptions(options); + const profileResource = dataset.getResource(webId); + const readResult = await profileResource.readIfUnfetched(); + if (readResult.isError) throw readResult; + return dataset.usingType(TypeIndexProfileShapeType).fromSubject(webId); +} + export function getTypeIndexesUrisFromProfile( profile: TypeIndexProfile, ): LeafUri[] { @@ -61,10 +61,9 @@ export function getTypeIndexesUrisFromProfile( export async function getInstanceUris( classUri: string, typeRegistrations: TypeRegistration[], - options?: GetInstanceUrisOptions, + options?: Options, ): Promise { - const fetch = guaranteeFetch(options?.fetch); - const dataset = options?.solidLdoDataset ?? createSolidLdoDataset({ fetch }); + const { dataset } = guaranteeOptions(options); const leafUris = new Set(); await Promise.all( diff --git a/packages/solid-type-index/src/index.ts b/packages/solid-type-index/src/index.ts index e69de29..9370593 100644 --- a/packages/solid-type-index/src/index.ts +++ b/packages/solid-type-index/src/index.ts @@ -0,0 +1,4 @@ +export * from "./getTypeIndex"; +export * from "./setTypeIndex"; +export * from "./react/useInstanceUris"; +export * from "./react/useTypeIndexProfile"; diff --git a/packages/solid-type-index/src/react/useAddToTypeIndex.ts b/packages/solid-type-index/src/react/useAddToTypeIndex.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/solid-type-index/src/react/useRemoveFromTypeIndex.ts b/packages/solid-type-index/src/react/useRemoveFromTypeIndex.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/solid-type-index/src/setTypeIndex.ts b/packages/solid-type-index/src/setTypeIndex.ts index e69de29..747ac68 100644 --- a/packages/solid-type-index/src/setTypeIndex.ts +++ b/packages/solid-type-index/src/setTypeIndex.ts @@ -0,0 +1,200 @@ +import { v4 } from "uuid"; +import { + TypeIndexDocumentShapeType, + TypeRegistrationShapeType, +} from "./.ldo/typeIndex.shapeTypes"; +import { FOR_CLASS, RDF_TYPE, TYPE_REGISTRATION } from "./constants"; +import { guaranteeOptions, type Options } from "./util/Options"; +import { namedNode } from "@rdfjs/data-model"; +import type { TypeRegistration } from "./.ldo/typeIndex.typings"; +import { getProfile } from "./getTypeIndex"; +import { TypeIndexProfileShapeType } from "./.ldo/profile.shapeTypes"; +import type { SolidLdoDataset } from "@ldo/solid"; +import type { Container } from "@ldo/solid"; + +/** + * ============================================================================= + * INITIALIZERS + * ============================================================================= + */ +export async function initTypeIndex( + webId: string, + options?: Options, +): Promise { + const { dataset } = guaranteeOptions(options); + const profile = await getProfile(webId, options); + if (!profile.privateTypeIndex?.length || !profile.publicTypeIndex?.length) { + const profileFolder = await dataset.getResource(webId).getParentContainer(); + if (profileFolder?.isError) throw profileFolder; + if (!profileFolder) + throw new Error("No folder to save the type indexes to."); + if (!profile.privateTypeIndex?.length) { + await createIndex(webId, profileFolder, dataset, true); + } + if (!profile.publicTypeIndex?.length) { + await createIndex(webId, profileFolder, dataset, true); + } + } +} + +/** + * @internal + * @param webId + * @param profileFolder + * @param dataset + */ +export async function createIndex( + webId, + profileFolder: Container, + dataset: SolidLdoDataset, + isPrivate: boolean, +) { + // Create a private type index + const createResult = await profileFolder.createChildAndOverwrite( + `${isPrivate ? "private" : "public"}_index_${v4()}`, + ); + if (createResult.isError) throw createResult; + const indexResource = createResult.resource; + const wacResult = await indexResource.setWac({ + agent: { + [webId]: { read: true, write: true, append: true, control: true }, + }, + public: { + read: isPrivate ? false : true, + write: true, + append: true, + control: true, + }, + authenticated: { + read: isPrivate ? false : true, + write: true, + append: true, + control: true, + }, + }); + if (wacResult.isError) throw wacResult; + const transaction = dataset.startTransaction(); + const cProfile = transaction + .usingType(TypeIndexProfileShapeType) + .write(dataset.getResource(webId).uri) + .fromSubject(webId); + if (isPrivate) { + cProfile.privateTypeIndex?.push({ "@id": indexResource.uri }); + } else { + cProfile.publicTypeIndex?.push({ "@id": indexResource.uri }); + } + const cTypeIndex = transaction + .usingType(TypeIndexDocumentShapeType) + .write(indexResource.uri) + .fromSubject(indexResource.uri); + + console.log(indexResource.uri, webId); + cTypeIndex.type = [{ "@id": "ListedDocument" }, { "@id": "TypeIndex" }]; + console.log("added", transaction.getChanges().added?.toString()); + console.log("removed", transaction.getChanges().added?.toString()); + const commitResult = await transaction.commitToPod(); + if (commitResult.isError) { + commitResult.errors.forEach((err) => { + if (err.type === "invalidUriError") { + console.log(err.uri); + } + }); + throw commitResult; + } +} + +/** + * ============================================================================= + * DATASET MODIFIERS + * ============================================================================= + */ +interface Instances { + instance?: string[]; + instanceContainer?: string[]; +} + +export function addRegistration( + indexUri: string, + classUri: string, + instances: Instances, + options?: Options, +): void { + // Check to see if its already in the index + const typeRegistration = findAppropriateTypeRegistration( + indexUri, + classUri, + options, + ); + + // Add instances to type registration + instances.instance?.forEach((instance) => { + typeRegistration.instance?.push({ "@id": instance }); + }); + instances.instanceContainer?.forEach((instanceContainer) => { + typeRegistration.instanceContainer?.push({ "@id": instanceContainer }); + }); +} + +export async function removeRegistration( + indexUri: string, + classUri: string, + instances: Instances, + options?: Options, +) { + // Check to see if its already in the index + const typeRegistration = findAppropriateTypeRegistration( + indexUri, + classUri, + options, + ); + + // Add instances to type registration + instances.instance?.forEach((instance) => { + typeRegistration.instance?.splice( + typeRegistration.instance.findIndex((val) => val["@id"] === instance), + 1, + ); + }); + instances.instanceContainer?.forEach((instanceContainer) => { + typeRegistration.instance?.splice( + typeRegistration.instance.findIndex( + (val) => val["@id"] === instanceContainer, + ), + 1, + ); + }); +} + +export function findAppropriateTypeRegistration( + indexUri: string, + classUri: string, + options?: Options, +) { + const { dataset } = guaranteeOptions(options); + // Check to see if its already in the index + const existingRegistrationUri: string | undefined = dataset + .match( + null, + namedNode(RDF_TYPE), + namedNode(TYPE_REGISTRATION), + namedNode(indexUri), + ) + .match(null, namedNode(FOR_CLASS), namedNode(classUri)) + .toArray()[0]?.subject.value; + let typeRegistration: TypeRegistration; + if (existingRegistrationUri) { + typeRegistration = dataset + .usingType(TypeRegistrationShapeType) + .write(indexUri) + .fromSubject(existingRegistrationUri); + } else { + typeRegistration = dataset.createData( + TypeRegistrationShapeType, + `${indexUri}#${v4()}`, + dataset.getResource(indexUri), + ); + typeRegistration.type = { "@id": "TypeRegistration" }; + typeRegistration.forClass = { "@id": classUri }; + } + return typeRegistration; +} diff --git a/packages/solid-type-index/src/util/Options.ts b/packages/solid-type-index/src/util/Options.ts new file mode 100644 index 0000000..bc85a52 --- /dev/null +++ b/packages/solid-type-index/src/util/Options.ts @@ -0,0 +1,13 @@ +import { createSolidLdoDataset, type SolidLdoDataset } from "@ldo/solid"; +import { guaranteeFetch } from "@ldo/solid/dist/util/guaranteeFetch"; + +export interface Options { + solidLdoDataset?: SolidLdoDataset; + fetch?: typeof fetch; +} + +export function guaranteeOptions(options?: Options) { + const fetch = guaranteeFetch(options?.fetch); + const dataset = options?.solidLdoDataset ?? createSolidLdoDataset({ fetch }); + return { fetch, dataset }; +} diff --git a/packages/solid-type-index/test/General.test.tsx b/packages/solid-type-index/test/General.test.tsx index 0b16062..346292d 100644 --- a/packages/solid-type-index/test/General.test.tsx +++ b/packages/solid-type-index/test/General.test.tsx @@ -2,18 +2,24 @@ import { createSolidLdoDataset } from "@ldo/solid"; import { MY_BOOKMARKS_1_URI, MY_BOOKMARKS_2_URI, + setupEmptyTypeIndex, + setupFullTypeIndex, setUpServer, WEB_ID, } from "./setUpServer"; import { getInstanceUris, getTypeRegistrations } from "../src/getTypeIndex"; +import { initTypeIndex } from "../src/setTypeIndex"; +import { TypeIndexProfileShapeType } from "../src/.ldo/profile.shapeTypes"; // Use an increased timeout, since the CSS server takes too much setup time. jest.setTimeout(40_000); describe("General Tests", () => { - setUpServer(); + const s = setUpServer(); it("gets the current typeindex", async () => { + await setupFullTypeIndex(s); + const solidLdoDataset = createSolidLdoDataset(); const typeRegistrations = await getTypeRegistrations(WEB_ID, { solidLdoDataset, @@ -38,4 +44,20 @@ describe("General Tests", () => { expect.arrayContaining([MY_BOOKMARKS_1_URI, MY_BOOKMARKS_2_URI]), ); }); + + it("initializes the type index", async () => { + await setupEmptyTypeIndex(s); + + const solidLdoDataset = createSolidLdoDataset(); + + await initTypeIndex(WEB_ID, { solidLdoDataset }); + + const profile = solidLdoDataset + .usingType(TypeIndexProfileShapeType) + .fromSubject(WEB_ID); + console.log(solidLdoDataset.toString()); + + expect(profile.privateTypeIndex?.["@id"]).toBeDefined(); + expect(profile.publicTypeIndex?.["@id"]).toBeDefined(); + }); }); diff --git a/packages/solid-type-index/test/React.tsx b/packages/solid-type-index/test/React.tsx deleted file mode 100644 index 42f6bd8..0000000 --- a/packages/solid-type-index/test/React.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { setUpServer } from "./setUpServer"; - -// Use an increased timeout, since the CSS server takes too much setup time. -jest.setTimeout(40_000); - -describe("React Tests", () => { - setUpServer(); -}); diff --git a/packages/solid-type-index/test/setUpServer.ts b/packages/solid-type-index/test/setUpServer.ts index b5fb559..04fc479 100644 --- a/packages/solid-type-index/test/setUpServer.ts +++ b/packages/solid-type-index/test/setUpServer.ts @@ -53,6 +53,77 @@ export interface SetUpServerReturn { >; } +export async function setupFullTypeIndex(s: SetUpServerReturn) { + // Create a new document called sample.ttl + await s.authFetch(WEB_ID, { method: "DELETE" }); + await s.authFetch(ROOT_CONTAINER, { + method: "POST", + headers: { + link: '; rel="type"', + slug: "myBookmarks/", + }, + }); + await Promise.all([ + s.authFetch(PROFILE_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "card.ttl" }, + body: PROFILE_TTL, + }), + s.authFetch(PROFILE_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "publicTypeIndex.ttl" }, + body: PUBLIC_TYPE_INDEX_TTL, + }), + s.authFetch(PROFILE_CONTAINER, { + method: "POST", + headers: { + "content-type": "text/turtle", + slug: "privateTypeIndex.ttl", + }, + body: PRIVATE_TYPE_INDEX_TTL, + }), + s.authFetch(MY_BOOKMARKS_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "bookmark1.ttl" }, + body: "", + }), + s.authFetch(MY_BOOKMARKS_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "bookmark2.ttl" }, + body: "", + }), + ]); +} + +export async function setupEmptyTypeIndex(s: SetUpServerReturn) { + // Create a new document called sample.ttl + await s.authFetch(WEB_ID, { method: "DELETE" }); + await s.authFetch(ROOT_CONTAINER, { + method: "POST", + headers: { + link: '; rel="type"', + slug: "myBookmarks/", + }, + }); + await Promise.all([ + s.authFetch(PROFILE_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "card.ttl" }, + body: "", + }), + s.authFetch(MY_BOOKMARKS_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "bookmark1.ttl" }, + body: "", + }), + s.authFetch(MY_BOOKMARKS_CONTAINER, { + method: "POST", + headers: { "content-type": "text/turtle", slug: "bookmark2.ttl" }, + body: "", + }), + ]); +} + export function setUpServer(): SetUpServerReturn { // Ignore to build s // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -66,45 +137,6 @@ export function setUpServer(): SetUpServerReturn { beforeEach(async () => { s.fetchMock = jest.fn(s.authFetch); - // Create a new document called sample.ttl - await s.authFetch(WEB_ID, { method: "DELETE" }); - await s.authFetch(ROOT_CONTAINER, { - method: "POST", - headers: { - link: '; rel="type"', - slug: "myBookmarks/", - }, - }); - await Promise.all([ - s.authFetch(PROFILE_CONTAINER, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "card.ttl" }, - body: PROFILE_TTL, - }), - s.authFetch(PROFILE_CONTAINER, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "publicTypeIndex.ttl" }, - body: PUBLIC_TYPE_INDEX_TTL, - }), - s.authFetch(PROFILE_CONTAINER, { - method: "POST", - headers: { - "content-type": "text/turtle", - slug: "privateTypeIndex.ttl", - }, - body: PRIVATE_TYPE_INDEX_TTL, - }), - s.authFetch(MY_BOOKMARKS_CONTAINER, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "bookmark1.ttl" }, - body: "", - }), - s.authFetch(MY_BOOKMARKS_CONTAINER, { - method: "POST", - headers: { "content-type": "text/turtle", slug: "bookmark2.ttl" }, - body: "", - }), - ]); }); afterEach(async () => { diff --git a/packages/solid/src/SolidLdoTransactionDataset.ts b/packages/solid/src/SolidLdoTransactionDataset.ts index cbacbb3..65bcaf9 100644 --- a/packages/solid/src/SolidLdoTransactionDataset.ts +++ b/packages/solid/src/SolidLdoTransactionDataset.ts @@ -117,6 +117,8 @@ export class SolidLdoTransactionDataset const changes = this.getChanges(); const changesByGraph = splitChangesByGraph(changes); + console.log(changesByGraph); + // Iterate through all changes by graph in const results: [ GraphNode, @@ -138,6 +140,7 @@ export class SolidLdoTransactionDataset ]; } if (isContainerUri(graph.value)) { + console.log(datasetChanges.removed?.toString()); return [ graph, datasetChanges,