From d89eb0209a1720f664c9f59bcc4642b2c081a121 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 8 Sep 2026 14:05:43 +0200 Subject: [PATCH 01/32] Implement first methods useTrackingProxy useSubject useResource --- dev/solid-vue-vite-app/.gitignore | 39 + .../.vscode/extensions.json | 3 + dev/solid-vue-vite-app/README.md | 42 + dev/solid-vue-vite-app/env.d.ts | 1 + dev/solid-vue-vite-app/index.html | 13 + dev/solid-vue-vite-app/package.json | 37 + dev/solid-vue-vite-app/public/favicon.ico | Bin 0 -> 4286 bytes dev/solid-vue-vite-app/src/App.vue | 52 + .../src/_ldo/foafProfile.context.ts | 48 + .../src/_ldo/foafProfile.schema.ts | 97 + .../src/_ldo/foafProfile.shapeTypes.ts | 19 + .../src/_ldo/foafProfile.typings.ts | 33 + .../src/_shapes/foafProfile.shex | 19 + dev/solid-vue-vite-app/src/ldoVue.ts | 6 + dev/solid-vue-vite-app/src/main.ts | 4 + dev/solid-vue-vite-app/tsconfig.app.json | 18 + dev/solid-vue-vite-app/tsconfig.json | 11 + dev/solid-vue-vite-app/tsconfig.node.json | 27 + dev/solid-vue-vite-app/vite.config.ts | 15 + package-lock.json | 2402 ++++++++++++++++- packages/svelte/src/methods/useResource.ts | 2 +- packages/vue/README.md | 1 + packages/vue/package.json | 43 + packages/vue/src/createLdoVueMethods.tsx | 90 + packages/vue/src/index.ts | 17 + packages/vue/src/methods/useDataset.ts | 17 + packages/vue/src/methods/useResource.ts | 112 + packages/vue/src/methods/useSubject.ts | 61 + packages/vue/src/util/useTrackingProxy.ts | 35 + packages/vue/tsconfig.esm.json | 9 + packages/vue/tsdown.config.ts | 13 + packages/vue/vitest.config.js | 30 + 32 files changed, 3232 insertions(+), 84 deletions(-) create mode 100644 dev/solid-vue-vite-app/.gitignore create mode 100644 dev/solid-vue-vite-app/.vscode/extensions.json create mode 100644 dev/solid-vue-vite-app/README.md create mode 100644 dev/solid-vue-vite-app/env.d.ts create mode 100644 dev/solid-vue-vite-app/index.html create mode 100644 dev/solid-vue-vite-app/package.json create mode 100644 dev/solid-vue-vite-app/public/favicon.ico create mode 100644 dev/solid-vue-vite-app/src/App.vue create mode 100644 dev/solid-vue-vite-app/src/_ldo/foafProfile.context.ts create mode 100644 dev/solid-vue-vite-app/src/_ldo/foafProfile.schema.ts create mode 100644 dev/solid-vue-vite-app/src/_ldo/foafProfile.shapeTypes.ts create mode 100644 dev/solid-vue-vite-app/src/_ldo/foafProfile.typings.ts create mode 100644 dev/solid-vue-vite-app/src/_shapes/foafProfile.shex create mode 100644 dev/solid-vue-vite-app/src/ldoVue.ts create mode 100644 dev/solid-vue-vite-app/src/main.ts create mode 100644 dev/solid-vue-vite-app/tsconfig.app.json create mode 100644 dev/solid-vue-vite-app/tsconfig.json create mode 100644 dev/solid-vue-vite-app/tsconfig.node.json create mode 100644 dev/solid-vue-vite-app/vite.config.ts create mode 100644 packages/vue/README.md create mode 100644 packages/vue/package.json create mode 100644 packages/vue/src/createLdoVueMethods.tsx create mode 100644 packages/vue/src/index.ts create mode 100644 packages/vue/src/methods/useDataset.ts create mode 100644 packages/vue/src/methods/useResource.ts create mode 100644 packages/vue/src/methods/useSubject.ts create mode 100644 packages/vue/src/util/useTrackingProxy.ts create mode 100644 packages/vue/tsconfig.esm.json create mode 100644 packages/vue/tsdown.config.ts create mode 100644 packages/vue/vitest.config.js diff --git a/dev/solid-vue-vite-app/.gitignore b/dev/solid-vue-vite-app/.gitignore new file mode 100644 index 00000000..cd68f140 --- /dev/null +++ b/dev/solid-vue-vite-app/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/dev/solid-vue-vite-app/.vscode/extensions.json b/dev/solid-vue-vite-app/.vscode/extensions.json new file mode 100644 index 00000000..a7cea0b0 --- /dev/null +++ b/dev/solid-vue-vite-app/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/dev/solid-vue-vite-app/README.md b/dev/solid-vue-vite-app/README.md new file mode 100644 index 00000000..493f8a6f --- /dev/null +++ b/dev/solid-vue-vite-app/README.md @@ -0,0 +1,42 @@ +# solid-vue-vite-app + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` diff --git a/dev/solid-vue-vite-app/env.d.ts b/dev/solid-vue-vite-app/env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/dev/solid-vue-vite-app/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dev/solid-vue-vite-app/index.html b/dev/solid-vue-vite-app/index.html new file mode 100644 index 00000000..9d30802c --- /dev/null +++ b/dev/solid-vue-vite-app/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/dev/solid-vue-vite-app/package.json b/dev/solid-vue-vite-app/package.json new file mode 100644 index 00000000..61d255fe --- /dev/null +++ b/dev/solid-vue-vite-app/package.json @@ -0,0 +1,37 @@ +{ + "name": "solid-vue-vite-app", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "build:ldo": "ldo build --input src/_shapes --output src/_ldo" + }, + "dependencies": { + "@ldo/connected-solid": "^1.0.0-alpha.50", + "@ldo/ldo": "^1.0.0-alpha.50", + "@ldo/vue": "^1.0.0-alpha.50", + "vue": "^3.5.40" + }, + "devDependencies": { + "@ldo/cli": "^1.0.0-alpha.50", + "@tsconfig/node24": "^24.0.4", + "@types/jsonld": "^1.5.15", + "@types/node": "^24.13.3", + "@types/shexj": "^2.1.7", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-vue-devtools": "^8.1.5", + "vue-tsc": "^3.3.7" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } +} diff --git a/dev/solid-vue-vite-app/public/favicon.ico b/dev/solid-vue-vite-app/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/dev/solid-vue-vite-app/src/App.vue b/dev/solid-vue-vite-app/src/App.vue new file mode 100644 index 00000000..cd23ff44 --- /dev/null +++ b/dev/solid-vue-vite-app/src/App.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/dev/solid-vue-vite-app/src/_ldo/foafProfile.context.ts b/dev/solid-vue-vite-app/src/_ldo/foafProfile.context.ts new file mode 100644 index 00000000..e7a57dfc --- /dev/null +++ b/dev/solid-vue-vite-app/src/_ldo/foafProfile.context.ts @@ -0,0 +1,48 @@ +import type { LdoJsonldContext } from "@ldo/ldo"; + +/** + * ============================================================================= + * foafProfileContext: JSONLD Context for foafProfile + * ============================================================================= + */ +export const foafProfileContext: LdoJsonldContext = { + type: { + "@id": "@type", + "@isCollection": true, + }, + Person: { + "@id": "http://xmlns.com/foaf/0.1/Person", + "@context": { + type: { + "@id": "@type", + "@isCollection": true, + }, + name: { + "@id": "http://xmlns.com/foaf/0.1/name", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + img: { + "@id": "http://xmlns.com/foaf/0.1/img", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + knows: { + "@id": "http://xmlns.com/foaf/0.1/knows", + "@type": "@id", + "@isCollection": true, + }, + }, + }, + name: { + "@id": "http://xmlns.com/foaf/0.1/name", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + img: { + "@id": "http://xmlns.com/foaf/0.1/img", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + knows: { + "@id": "http://xmlns.com/foaf/0.1/knows", + "@type": "@id", + "@isCollection": true, + }, +}; diff --git a/dev/solid-vue-vite-app/src/_ldo/foafProfile.schema.ts b/dev/solid-vue-vite-app/src/_ldo/foafProfile.schema.ts new file mode 100644 index 00000000..25243cde --- /dev/null +++ b/dev/solid-vue-vite-app/src/_ldo/foafProfile.schema.ts @@ -0,0 +1,97 @@ +import type { Schema } from "shexj"; + +/** + * ============================================================================= + * foafProfileSchema: ShexJ Schema for foafProfile + * ============================================================================= + */ +export const foafProfileSchema: Schema = { + type: "Schema", + shapes: [ + { + id: "https://example.com/FoafProfile", + type: "ShapeDecl", + shapeExpr: { + type: "Shape", + expression: { + type: "EachOf", + expressions: [ + { + type: "TripleConstraint", + predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + valueExpr: { + type: "NodeConstraint", + values: ["http://xmlns.com/foaf/0.1/Person"], + }, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Defines the node as a Person (from foaf)", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/name", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#string", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Define a person's name.", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/img", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#string", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Photo link but in string form", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/knows", + valueExpr: "https://example.com/FoafProfile", + min: 0, + max: -1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: + "A list of WebIds for all the people this user knows.", + }, + }, + ], + }, + ], + }, + extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], + }, + }, + ], +}; diff --git a/dev/solid-vue-vite-app/src/_ldo/foafProfile.shapeTypes.ts b/dev/solid-vue-vite-app/src/_ldo/foafProfile.shapeTypes.ts new file mode 100644 index 00000000..2c52ee86 --- /dev/null +++ b/dev/solid-vue-vite-app/src/_ldo/foafProfile.shapeTypes.ts @@ -0,0 +1,19 @@ +import type { ShapeType } from "@ldo/ldo"; +import { foafProfileSchema } from "./foafProfile.schema"; +import { foafProfileContext } from "./foafProfile.context"; +import type { FoafProfile } from "./foafProfile.typings"; + +/** + * ============================================================================= + * LDO ShapeTypes foafProfile + * ============================================================================= + */ + +/** + * FoafProfile ShapeType + */ +export const FoafProfileShapeType: ShapeType = { + schema: foafProfileSchema, + shape: "https://example.com/FoafProfile", + context: foafProfileContext, +}; diff --git a/dev/solid-vue-vite-app/src/_ldo/foafProfile.typings.ts b/dev/solid-vue-vite-app/src/_ldo/foafProfile.typings.ts new file mode 100644 index 00000000..16843881 --- /dev/null +++ b/dev/solid-vue-vite-app/src/_ldo/foafProfile.typings.ts @@ -0,0 +1,33 @@ +import type { LdoJsonldContext, LdSet } from "@ldo/ldo"; + +/** + * ============================================================================= + * Typescript Typings for foafProfile + * ============================================================================= + */ + +/** + * FoafProfile Type + */ +export interface FoafProfile { + "@id"?: string; + "@context"?: LdoJsonldContext; + /** + * Defines the node as a Person (from foaf) + */ + type: LdSet<{ + "@id": "Person"; + }>; + /** + * Define a person's name. + */ + name?: string; + /** + * Photo link but in string form + */ + img?: string; + /** + * A list of WebIds for all the people this user knows. + */ + knows?: LdSet; +} diff --git a/dev/solid-vue-vite-app/src/_shapes/foafProfile.shex b/dev/solid-vue-vite-app/src/_shapes/foafProfile.shex new file mode 100644 index 00000000..dff09d16 --- /dev/null +++ b/dev/solid-vue-vite-app/src/_shapes/foafProfile.shex @@ -0,0 +1,19 @@ +# This shape is provided by default as an example +# You can create your own shape to fit your needs using ShEx (https://shex.io) +# Also check out https://shaperepo.com for examples of more shapes. + +PREFIX ex: +PREFIX foaf: +PREFIX rdfs: +PREFIX xsd: + +ex:FoafProfile EXTRA a { + a [ foaf:Person ] + // rdfs:comment "Defines the node as a Person (from foaf)" ; + foaf:name xsd:string ? + // rdfs:comment "Define a person's name." ; + foaf:img xsd:string ? + // rdfs:comment "Photo link but in string form" ; + foaf:knows @ex:FoafProfile * + // rdfs:comment "A list of WebIds for all the people this user knows." ; +} diff --git a/dev/solid-vue-vite-app/src/ldoVue.ts b/dev/solid-vue-vite-app/src/ldoVue.ts new file mode 100644 index 00000000..48e8bb9e --- /dev/null +++ b/dev/solid-vue-vite-app/src/ldoVue.ts @@ -0,0 +1,6 @@ +import { createLdoVueMethods } from "@ldo/vue"; +import { solidConnectedPlugin } from "@ldo/connected-solid"; + +export const { dataset, useSubject, useResource } = createLdoVueMethods([ + solidConnectedPlugin, +]); diff --git a/dev/solid-vue-vite-app/src/main.ts b/dev/solid-vue-vite-app/src/main.ts new file mode 100644 index 00000000..b670de8b --- /dev/null +++ b/dev/solid-vue-vite-app/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from "vue"; +import App from "./App.vue"; + +createApp(App).mount("#app"); diff --git a/dev/solid-vue-vite-app/tsconfig.app.json b/dev/solid-vue-vite-app/tsconfig.app.json new file mode 100644 index 00000000..c0f2d867 --- /dev/null +++ b/dev/solid-vue-vite-app/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/dev/solid-vue-vite-app/tsconfig.json b/dev/solid-vue-vite-app/tsconfig.json new file mode 100644 index 00000000..66b5e570 --- /dev/null +++ b/dev/solid-vue-vite-app/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/dev/solid-vue-vite-app/tsconfig.node.json b/dev/solid-vue-vite-app/tsconfig.node.json new file mode 100644 index 00000000..c9b2badc --- /dev/null +++ b/dev/solid-vue-vite-app/tsconfig.node.json @@ -0,0 +1,27 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/dev/solid-vue-vite-app/vite.config.ts b/dev/solid-vue-vite-app/vite.config.ts new file mode 100644 index 00000000..74831822 --- /dev/null +++ b/dev/solid-vue-vite-app/vite.config.ts @@ -0,0 +1,15 @@ +import { fileURLToPath, URL } from "node:url"; + +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import vueDevTools from "vite-plugin-vue-devtools"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue(), vueDevTools()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index 379b43a4..f603f770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -448,6 +448,159 @@ "url": "https://github.com/sponsors/Boshen" } }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "dev/solid-react-vite-app/node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.2", "cpu": [ @@ -478,6 +631,57 @@ "node": "^20.19.0 || >=22.12.0" } }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-react-vite-app/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "dev/solid-react-vite-app/node_modules/@rolldown/plugin-babel": { "version": "0.2.3", "dev": true, @@ -888,6 +1092,329 @@ } } }, + "dev/solid-vue-app": { + "version": "0.0.0", + "extraneous": true, + "dependencies": { + "vue": "^3.5.40" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-vue-devtools": "^8.1.5", + "vue-tsc": "^3.3.7" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } + }, + "dev/solid-vue-vite-app": { + "version": "0.0.0", + "dependencies": { + "@ldo/connected-solid": "^1.0.0-alpha.50", + "@ldo/ldo": "^1.0.0-alpha.50", + "@ldo/vue": "^1.0.0-alpha.50", + "vue": "^3.5.40" + }, + "devDependencies": { + "@ldo/cli": "^1.0.0-alpha.50", + "@tsconfig/node24": "^24.0.4", + "@types/jsonld": "^1.5.15", + "@types/node": "^24.13.3", + "@types/shexj": "^2.1.7", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-vue-devtools": "^8.1.5", + "vue-tsc": "^3.3.7" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } + }, + "dev/solid-vue-vite-app/node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "dev/solid-vue-vite-app/node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-vue-vite-app/node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "dev/solid-vue-vite-app/node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "dev/solid-vue-vite-app/node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "dev/solid-vue-vite-app/node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "dev/solid-vue-vite-app/node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "dev/solid-vue-vite-app/node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "dev/solid-vue-vite-app/node_modules/@vue/language-core": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.11.tgz", + "integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "dev/solid-vue-vite-app/node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "dev/solid-vue-vite-app/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "dev/solid-vue-vite-app/node_modules/rolldown": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.148.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" + } + }, + "dev/solid-vue-vite-app/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "dev/solid-vue-vite-app/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "dev/solid-vue-vite-app/node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "dev/solid-vue-vite-app/node_modules/vue-tsc": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.11.tgz", + "integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.11" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "dev": true, @@ -973,11 +1500,13 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -1031,12 +1560,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -1046,11 +1577,13 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1093,16 +1626,18 @@ "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.3", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.29.0", + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "engines": { @@ -1160,7 +1695,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -1168,12 +1705,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1208,18 +1747,22 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -1243,13 +1786,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1259,28 +1804,32 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "devOptional": true, + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "devOptional": true, + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1320,11 +1869,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "dev": true, + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -1422,6 +1972,24 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "dev": true, @@ -1433,6 +2001,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.28.6", "dev": true, @@ -1461,6 +2045,51 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "dev": true, @@ -2199,6 +2828,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", "dev": true, @@ -2372,29 +3021,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -2402,12 +3055,13 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "devOptional": true, + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -16519,6 +17173,40 @@ "node": ">=10.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.27.7", "cpu": [ @@ -17180,7 +17868,6 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -17679,6 +18366,10 @@ "resolved": "packages/type-traverser", "link": true }, + "node_modules/@ldo/vue": { + "resolved": "packages/vue", + "link": true + }, "node_modules/@lerna/child-process": { "version": "7.4.2", "dev": true, @@ -18174,6 +18865,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, "node_modules/@next/env": { "version": "16.2.6", "license": "MIT" @@ -18749,6 +19462,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@publint/pack": { "version": "0.1.4", "dev": true, @@ -18980,6 +19700,176 @@ "@types/node": "*" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.0-beta.9-commit.d91dfb5", "cpu": [ @@ -19006,6 +19896,76 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "dev": true, @@ -21242,6 +22202,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@tsconfig/node24": { + "version": "24.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/node24/-/node24-24.0.5.tgz", + "integrity": "sha512-DWC54fGPuhZgg3Ighg4Y/A/bA3ZdB5hnzAuv/MkQ/X85sVsBsMQxu8t7khutdYeiNksBbFdDyeqv1HSW2Zl8aA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/svelte": { "version": "5.0.8", "dev": true, @@ -21267,6 +22234,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/accepts": { "version": "1.3.7", "license": "MIT", @@ -21506,6 +22484,8 @@ }, "node_modules/@types/jsonld": { "version": "1.5.15", + "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.15.tgz", + "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==", "license": "MIT" }, "node_modules/@types/keygrip": { @@ -21808,6 +22788,8 @@ }, "node_modules/@types/shexj": { "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/shexj/-/shexj-2.1.7.tgz", + "integrity": "sha512-pu/0vIZxFTMPVjTlo5MJKFkBL/EbAuFhtCXpmBB7ZdUiyNpc6pt8GxfyRPqdf6q2SsWu71a/vbhvGK2IZN2Eug==", "license": "MIT" }, "node_modules/@types/spark-md5": { @@ -22138,6 +23120,30 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue/node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/coverage-istanbul": { "version": "3.2.4", "dev": true, @@ -22287,13 +23293,67 @@ "path-browserify": "^1.0.1" } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.33", + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.2", - "@vue/shared": "3.5.33", + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" @@ -22301,7 +23361,8 @@ }, "node_modules/@vue/compiler-core/node_modules/entities": { "version": "7.0.1", - "dev": true, + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -22312,18 +23373,104 @@ }, "node_modules/@vue/compiler-core/node_modules/estree-walker": { "version": "2.0.2", - "dev": true, + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.33", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz", + "integrity": "sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.2.1", + "@vue/devtools-shared": "^8.2.1" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.33", - "@vue/shared": "3.5.33" + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" } }, + "node_modules/@vue/devtools-kit/node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vue/devtools-kit/node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "dev": true, + "license": "MIT" + }, "node_modules/@vue/language-core": { "version": "1.8.27", "dev": true, @@ -22348,11 +23495,73 @@ } } }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, "node_modules/@vue/shared": { - "version": "3.5.33", - "dev": true, + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", "license": "MIT" }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "dev": true, @@ -22546,6 +23755,13 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "dev": true, @@ -23252,6 +24468,22 @@ "semver": "^7.0.0" } }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/byte-size": { "version": "8.1.1", "dev": true, @@ -24472,7 +25704,6 @@ }, "node_modules/csstype": { "version": "3.2.3", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -24675,6 +25906,36 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "dev": true, @@ -25108,6 +26369,16 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "dev": true, @@ -26402,6 +27673,20 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -27826,6 +29111,54 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "dev": true, @@ -29300,7 +30633,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -29314,21 +30649,177 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -29348,7 +30839,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -29367,6 +30860,50 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "2.0.4", "dev": true, @@ -29535,7 +31072,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -29753,6 +31289,15 @@ "tslib": "2" } }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/meow": { "version": "8.1.2", "dev": true, @@ -30159,6 +31704,16 @@ "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "license": "MIT" @@ -30241,7 +31796,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -31115,6 +32672,132 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/npm-run-all2": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-9.0.3.tgz", + "integrity": "sha512-BQAEdU1PtYc48qYRdghW2BVTQT3VqWCoFQmO87NlM1h1PYwMCKQpUWaNyB20V26caNzFsXQDfyOdznLlHCih6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^7.0.0", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^1.0.0", + "read-package-json-fast": "^6.0.0", + "shell-quote": "^1.8.4", + "which": "^7.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-7.0.0.tgz", + "integrity": "sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-all2/node_modules/json-parse-even-better-errors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", + "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-all2/node_modules/npm-normalize-package-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", + "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/read-package-json-fast": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", + "integrity": "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-all2/node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", + "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "dev": true, @@ -31428,6 +33111,13 @@ "node": ">=12.20.0" } }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, "node_modules/oidc-provider": { "version": "9.8.3", "license": "MIT", @@ -32151,6 +33841,13 @@ "through": "~2.3" } }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -32166,6 +33863,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-1.0.0.tgz", + "integrity": "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pify": { "version": "5.0.0", "dev": true, @@ -32245,8 +33955,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "dev": true, + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -32263,7 +33974,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -32271,6 +33982,19 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "license": "MIT", @@ -34049,6 +35773,159 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/rolldown/node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/rolldown/node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.0-rc.17", "cpu": [ @@ -34064,6 +35941,76 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/rolldown/node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/rolldown/node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.17", "dev": true, @@ -34117,6 +36064,19 @@ "dev": true, "license": "MIT" }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-async": { "version": "2.4.1", "dev": true, @@ -34852,6 +36812,21 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "license": "MIT" @@ -34929,6 +36904,10 @@ "resolved": "dev/solid-react-vite-app", "link": true }, + "node_modules/solid-vue-vite-app": { + "resolved": "dev/solid-vue-vite-app", + "link": true + }, "node_modules/sorcery": { "version": "0.11.1", "dev": true, @@ -36280,6 +38259,16 @@ "version": "2.0.2", "license": "MIT" }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "5.1.2", "dev": true, @@ -36981,7 +38970,7 @@ }, "node_modules/typescript": { "version": "5.9.3", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -37377,6 +39366,36 @@ "node": ">= 0.8" } }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/unrs-resolver": { "version": "1.12.2", "dev": true, @@ -37632,6 +39651,36 @@ } } }, + "node_modules/vite-dev-rpc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" + } + }, "node_modules/vite-node": { "version": "3.2.4", "dev": true, @@ -37687,6 +39736,114 @@ } } }, + "node_modules/vite-plugin-inspect": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.0", + "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", + "ohash": "^2.0.11", + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-inspect/node_modules/open": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.2.tgz", + "integrity": "sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.5.1", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.2.1", + "wsl-utils": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.2.1.tgz", + "integrity": "sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.2.1", + "@vue/devtools-kit": "^8.2.1", + "@vue/devtools-shared": "^8.2.1", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^6.0.0" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz", + "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/vite/node_modules/@esbuild/linux-x64": { "version": "0.25.12", "cpu": [ @@ -37877,6 +40034,34 @@ "dev": true, "license": "MIT" }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vue-template-compiler": { "version": "2.7.16", "dev": true, @@ -38344,6 +40529,52 @@ } } }, + "node_modules/wsl-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", + "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xlsx": { "version": "0.8.8", "dev": true, @@ -39813,6 +42044,11 @@ "uuid": "dist/bin/uuid" } }, + "packages/vue": { + "name": "@ldo/vue", + "version": "1.0.0-alpha.50", + "license": "MIT" + }, "packages/wac": { "extraneous": true } diff --git a/packages/svelte/src/methods/useResource.ts b/packages/svelte/src/methods/useResource.ts index c50b4714..96344c7b 100644 --- a/packages/svelte/src/methods/useResource.ts +++ b/packages/svelte/src/methods/useResource.ts @@ -55,7 +55,7 @@ export function createUseResource( if (uri) { // eslint-disable-next-line @typescript-eslint/no-explicit-any resource = dataset.getResource(uri) as any; - // Run read operations if necissary + // Run read operations if necessary if (!options?.suppressInitialRead) { if (options?.reloadOnMount) { resource!.read(); diff --git a/packages/vue/README.md b/packages/vue/README.md new file mode 100644 index 00000000..4c7ed235 --- /dev/null +++ b/packages/vue/README.md @@ -0,0 +1 @@ +# @ldo/vue diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 00000000..91beafdc --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,43 @@ +{ + "name": "@ldo/vue", + "version": "1.0.0-alpha.50", + "description": "A Vue library for LDO.", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "scripts": { + "build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 tsdown", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "ldo", + "linked-data-objects", + "rdf", + "solid-project", + "vuejs" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/o-development/ldo.git" + }, + "author": "Jackson Morgan", + "license": "MIT", + "bugs": { + "url": "https://github.com/o-development/ldo/issues" + }, + "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme" +} diff --git a/packages/vue/src/createLdoVueMethods.tsx b/packages/vue/src/createLdoVueMethods.tsx new file mode 100644 index 00000000..63dd58df --- /dev/null +++ b/packages/vue/src/createLdoVueMethods.tsx @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + createConnectedLdoDataset, + type ConnectedPlugin, +} from "@ldo/connected"; +// import { createUseLdo } from "./methods/useLdo"; +// import { createUseMatchObject } from "./methods/useMatchObject"; +// import { createUseMatchSubject } from "./methods/useMatchSubject"; +import { createUseResource } from "./methods/useResource"; +import { createUseSubject } from "./methods/useSubject"; +// import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; +// import { createUseLinkQuery } from "./methods/useLinkQuery"; +// import { createUseDataset } from "./methods/useDataset"; +// import { createUseChangeDataset } from "./methods/change/useChangeDataset"; +// import { createUseChangeSubject } from "./methods/change/useChangeSubject"; +// import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject"; +// import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject"; + +/** + * A function that creates all common vue functions given specific plugin. + * + * @example + * `methods.ts` + * ```tsx + * import { solidConnectedPlugin } from "@ldo/connected-solid"; + * import { nextGraphConnectedPlugin } from "@ldo/connected-nextgraph"; + * import { createLdoVueMethods } from "@ldo/vue"; + * + * // Export the results to be used in the reset of the application + * export const { + * dataset, + * useLdo, + * useMatchObject, + * useMatchSubject, + * useResource, + * useSubject, + * useSubscribeToResource, + * useLinkQuery, + * useChangeDataset, + * useChangeSubject, + * useChangeMatchObject, + * useChangeMatchSubject, + * } = createLdoVueMethods([ + * solidConnectedPlugin, + * nextGraphConnectedPlugin + * ]); + * ``` + * + * `App.tsx` + * ```typescript + * import { PostShShapeType } from "./_ldo/posts.shapeType.ts"; + * import { useResource, useSubject } from "./methods.ts"; + * + * const UseSubjectTest: FunctionComponent = () => { + * const resource = useResource(SAMPLE_DATA_URI); + * const post = useSubject(PostShShapeType, `${SAMPLE_DATA_URI}#Post1`); + * if (resource.isLoading() || !post) return

loading

; + * + * return ( + *
    + * {post.publisher.map((publisher) => { + * return
  • {publisher["@id"]}
  • ; + * })} + *
+ * ); + * }; + * ``` + */ +export function createLdoVueMethods< + Plugins extends ConnectedPlugin[], +>(plugins: Plugins) { + const dataset = createConnectedLdoDataset(plugins); + dataset.setMaxListeners(1000); + + return { + dataset, + // useDataset: createUseDataset(dataset), + // useLdo: createUseLdo(dataset), + // useMatchObject: createUseMatchObject(dataset), + // useMatchSubject: createUseMatchSubject(dataset), + useResource: createUseResource(dataset), + useSubject: createUseSubject(dataset), + // useSubscribeToResource: createUseSubscribeToResource(dataset), + // useLinkQuery: createUseLinkQuery(dataset), + // useChangeDataset: createUseChangeDataset(dataset), + // useChangeSubject: createUseChangeSubject(dataset), + // useChangeMatchObject: createUseChangeMatchObject(dataset), + // useChangeMatchSubject: createUseChangeMatchSubject(dataset), + }; +} diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts new file mode 100644 index 00000000..7ab0fd86 --- /dev/null +++ b/packages/vue/src/index.ts @@ -0,0 +1,17 @@ +export * from "./createLdoVueMethods"; + +// export * from "./methods/useLdo"; +// export * from "./methods/useMatchObject"; +// export * from "./methods/useMatchSubject"; +export * from "./methods/useResource"; +export * from "./methods/useSubject"; +// export * from "./methods/useSubscribeToResource"; +// export * from "./methods/useLinkQuery"; + +// export * from "./methods/change/types"; +// export * from "./methods/change/useChangeDataset"; +// export * from "./methods/change/useChangeMatchObject"; +// export * from "./methods/change/useChangeMatchSubject"; +// export * from "./methods/change/useChangeSubject"; + +export * from "./util/useTrackingProxy"; diff --git a/packages/vue/src/methods/useDataset.ts b/packages/vue/src/methods/useDataset.ts new file mode 100644 index 00000000..b0bf1cde --- /dev/null +++ b/packages/vue/src/methods/useDataset.ts @@ -0,0 +1,17 @@ +import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; + +/** + * @internal + * + * Creates a useDataset function + */ +export function createUseDataset( + dataset: ConnectedLdoDataset, +) { + /** + * Returns the global dataset for the application + */ + return function useDataset() { + return dataset; + }; +} diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts new file mode 100644 index 00000000..eb1ce940 --- /dev/null +++ b/packages/vue/src/methods/useResource.ts @@ -0,0 +1,112 @@ +import type { + ConnectedLdoDataset, + ConnectedPlugin, + GetResourceReturnType, +} from "@ldo/connected"; +import { + shallowRef, + watch, + onBeforeUnmount, + type Ref, + triggerRef, + type MaybeRef, + toValue, +} from "vue"; + +export interface UseResourceOptions { + pluginName?: Name; + suppressInitialRead?: boolean; + reloadOnMount?: boolean; + subscribe?: boolean; +} + +export type useResourceType = { + < + Name extends Plugins[number]["name"], + Plugin extends Extract, + UriType extends string, + >( + uri: MaybeRef, + options?: UseResourceOptions, + ): Ref>; + < + Name extends Plugins[number]["name"], + Plugin extends Extract, + UriType extends string, + >( + uri?: MaybeRef, + options?: UseResourceOptions, + ): Ref | undefined>; +}; + +/** + * @internal + * + * Creates a useResource composable. + */ +export function createUseResource( + dataset: ConnectedLdoDataset, +): useResourceType { + /** + * Returns a resource and triggers a rerender if that resource is updated. + */ + return function useResource< + Name extends Plugins[number]["name"], + Plugin extends Extract, + UriType extends string, + >( + uri?: MaybeRef, + options?: UseResourceOptions, + ): Ref | undefined> { + const resourceRef: Ref | undefined> = + shallowRef | undefined>(); + + let currentResource: GetResourceReturnType | undefined; + + const onResourceUpdate = () => { + triggerRef(resourceRef); + }; + + watch( + () => toValue(uri), + (newUri) => { + if (currentResource) { + currentResource.off("update", onResourceUpdate); + currentResource.unsubscribeFromNotifications(); + } + if (newUri) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + currentResource = dataset.getResource(newUri) as any; + if (!options?.suppressInitialRead) { + if (options?.reloadOnMount) { + currentResource!.read(); + } else { + currentResource!.readIfUnfetched(); + } + } + + if (currentResource) { + currentResource.on("update", onResourceUpdate); + // Subscribe to notifications if it's needed + if (options?.subscribe) { + currentResource.subscribeToNotifications(); + } + } + } else { + currentResource = undefined; + } + + resourceRef.value = currentResource; + }, + { immediate: true }, + ); + + // cleanup + onBeforeUnmount(() => { + currentResource?.off("update", onResourceUpdate); + currentResource?.unsubscribeFromNotifications(); + }); + + return resourceRef; + }; +} diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts new file mode 100644 index 00000000..07a596ae --- /dev/null +++ b/packages/vue/src/methods/useSubject.ts @@ -0,0 +1,61 @@ +import type { SubjectNode } from "@ldo/rdf-utils"; +import type { ShapeType, LdoBase } from "@ldo/ldo"; +import { useTrackingProxy } from "../util/useTrackingProxy"; +import type { + ConnectedLdoDataset, + ConnectedPlugin, + IConnectedLdoDataset, +} from "@ldo/connected"; +import { computed, toValue, type MaybeRef, type Ref } from "vue"; + +export interface UseSubjectOptions { + dataset?: IConnectedLdoDataset; +} + +export type useSubjectType = { + ( + shapeType: ShapeType, + subject: MaybeRef, + options?: UseSubjectOptions, + ): Type; + ( + shapeType: ShapeType, + subject?: MaybeRef, + options?: UseSubjectOptions, + ): Type | undefined; + ( + shapeType: ShapeType, + subject?: MaybeRef, + options?: UseSubjectOptions, + ): Type | undefined; +}; + +/** + * @internal + * + * Creates a useSubject function. + */ +export function createUseSubject( + dataset: ConnectedLdoDataset, +): useSubjectType { + /** + * Returns a Linked Data Object based on the provided subject. + * Triggers a rerender if the data are updated. + */ + return function useSubject( + shapeType: ShapeType, + subject?: MaybeRef, + options?: UseSubjectOptions, + ): Ref { + return computed(() => { + const subjectValue = toValue(subject); + if (!subjectValue) return undefined; + + return useTrackingProxy( + shapeType, + (builder) => builder.fromSubject(subjectValue), + options?.dataset ?? dataset, + ).value; + }); + }; +} diff --git a/packages/vue/src/util/useTrackingProxy.ts b/packages/vue/src/util/useTrackingProxy.ts new file mode 100644 index 00000000..70d4a23b --- /dev/null +++ b/packages/vue/src/util/useTrackingProxy.ts @@ -0,0 +1,35 @@ +import type { LdoBuilder } from "@ldo/ldo"; +import type { LdoBase, LdoDataset, ShapeType } from "@ldo/ldo"; +import { createTrackingProxyBuilder } from "@ldo/connected"; +import { onBeforeUnmount, type Ref, shallowRef, triggerRef } from "vue"; + +/** + * @internal + * + * A composable for tracking proxies. + * + * TODO it doesn't update when createLdo, shapeType or dataset reference change + */ +export function useTrackingProxy( + shapeType: ShapeType, + createLdo: (builder: LdoBuilder) => ReturnType, + dataset: LdoDataset, +): Ref { + console.log("running useTrackingProxy"); + + const linkedDataObject = shallowRef() as Ref; + + const forceUpdate = () => { + triggerRef(linkedDataObject); + }; + + const builder = createTrackingProxyBuilder(dataset, shapeType, forceUpdate); + linkedDataObject.value = createLdo(builder); + + // cleanup + onBeforeUnmount(() => { + dataset.removeListenerFromAllEvents(forceUpdate); + }); + + return linkedDataObject; +} diff --git a/packages/vue/tsconfig.esm.json b/packages/vue/tsconfig.esm.json new file mode 100644 index 00000000..77952ca6 --- /dev/null +++ b/packages/vue/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.esm.json", + "compilerOptions": { + "outDir": "./dist/esm", + "rootDir": "./src", + "declarationDir": "./dist/types" + }, + "include": ["src"] +} diff --git a/packages/vue/tsdown.config.ts b/packages/vue/tsdown.config.ts new file mode 100644 index 00000000..7f0dfbd9 --- /dev/null +++ b/packages/vue/tsdown.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + target: "es2021", + unbundle: true, + dts: true, + clean: true, + outDir: "dist", + tsconfig: "tsconfig.esm.json", + skipNodeModulesBundle: true, +}); diff --git a/packages/vue/vitest.config.js b/packages/vue/vitest.config.js new file mode 100644 index 00000000..6ed8adf1 --- /dev/null +++ b/packages/vue/vitest.config.js @@ -0,0 +1,30 @@ +import { defineConfig } from "vitest/config"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import react from "@vitejs/plugin-react"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: [ + { + find: /^@ldo\/([^/]+)$/, + replacement: path.resolve(__dirname, "../$1/src/index.ts"), + }, + { + find: /^@ldo\/([^/]+)\/(.*)$/, + replacement: path.resolve(__dirname, "../$1/$2"), + }, + ], + }, + test: { + globals: true, + setupFiles: ["../../vitest.jest-compat.ts"], + coverage: { + provider: "istanbul", + }, + environment: "jsdom", + }, +}); From b513bdc96faf0a6b852a36b321fd0d1d319f3146 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 10 Sep 2026 12:05:00 +0200 Subject: [PATCH 02/32] Set up vitest tests for vue composables - Use happy-dom instead of jsdom because of UInt8Array mismatch when using @ldo/test-solid-server - Add DOM.Iterable into tsconfig.base.json to allow Headers.prototype.entries() and such widely available methods in TS types --- package-lock.json | 71 ++++++++++++++++++++++- packages/react/src/methods/useResource.ts | 4 +- packages/vue/package.json | 9 ++- packages/vue/test/Composables.test.ts | 55 ++++++++++++++++++ packages/vue/test/test-utils.ts | 18 ++++++ packages/vue/vitest.config.js | 5 +- tsconfig.base.json | 2 +- 7 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 packages/vue/test/Composables.test.ts create mode 100644 packages/vue/test/test-utils.ts diff --git a/package-lock.json b/package-lock.json index f603f770..f5306056 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22819,6 +22819,13 @@ "version": "10.0.0", "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "license": "MIT", @@ -24460,6 +24467,19 @@ "version": "1.1.2", "license": "MIT" }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/builtins": { "version": "5.1.0", "dev": true, @@ -28215,6 +28235,48 @@ "uglify-js": "^3.1.4" } }, + "node_modules/happy-dom": { + "version": "20.14.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.14.3.tgz", + "integrity": "sha512-0KMb/Eh8rsd+aMNkOk5h8LkAjo9Na1aksnEGmsuZf+GXl6UkAdIi0VbUHU7d59lXhNmawODgwwuCMiZpCxwwCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/happy-dom/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/happy-dom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/hard-rejection": { "version": "2.1.0", "dev": true, @@ -40511,7 +40573,9 @@ } }, "node_modules/ws": { - "version": "8.20.0", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -42047,7 +42111,10 @@ "packages/vue": { "name": "@ldo/vue", "version": "1.0.0-alpha.50", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "happy-dom": "^20.14.3" + } }, "packages/wac": { "extraneous": true diff --git a/packages/react/src/methods/useResource.ts b/packages/react/src/methods/useResource.ts index d17420d4..ad74d9c4 100644 --- a/packages/react/src/methods/useResource.ts +++ b/packages/react/src/methods/useResource.ts @@ -57,7 +57,7 @@ export function createUseResource( const resource = useMemo(() => { if (uri) { const resource = dataset.getResource(uri); - // Run read operations if necissary + // Run read operations if necessary if (!options?.suppressInitialRead) { if (options?.reloadOnMount) { resource.read(); @@ -93,7 +93,7 @@ export function createUseResource( // Callback function to force the react dom to reload. const forceReload = useCallback( - // Wrap the resource in a proxy so it's techically a different object + // Wrap the resource in a proxy so it's technically a different object () => { if (resource) setResourceRepresentation(new Proxy(resource, {})); }, diff --git a/packages/vue/package.json b/packages/vue/package.json index 91beafdc..03316787 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,7 +1,7 @@ { "name": "@ldo/vue", "version": "1.0.0-alpha.50", - "description": "A Vue library for LDO.", + "description": "A Vue library for LDO", "type": "module", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -21,7 +21,7 @@ }, "scripts": { "build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 tsdown", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run --coverage" }, "keywords": [ "ldo", @@ -39,5 +39,8 @@ "bugs": { "url": "https://github.com/o-development/ldo/issues" }, - "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme" + "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme", + "devDependencies": { + "happy-dom": "^20.14.3" + } } diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts new file mode 100644 index 00000000..3c1cdd14 --- /dev/null +++ b/packages/vue/test/Composables.test.ts @@ -0,0 +1,55 @@ +import { describe, it, beforeEach, vi, expect } from "vitest"; +import { createLdoVueMethods } from "../src/index"; +import { + type SolidConnectedPlugin, + solidConnectedPlugin, +} from "@ldo/connected-solid"; +import { withSetup } from "./test-utils.js"; +import assert from "node:assert"; +import { setupServer } from "@ldo/test-solid-server"; + +describe("LDO Vue Composables", () => { + let methods: ReturnType>; + const s = setupServer( + 3006, + { + slug: "directory/", + isContainer: true, + contains: [], + }, + // undefined, + // true, + ); + + beforeEach(() => { + methods = createLdoVueMethods([solidConnectedPlugin]); + methods.dataset.setContext("solid", { fetch: s.authFetch }); + }); + + describe("useResource", () => { + it("should fetch a resource by uri", async () => { + const [result, app] = withSetup(() => + methods.useResource("http://localhost:3006/directory/"), + ); + + expect(result.value.isFetched()).toBe(false); + + await vi.waitFor(() => { + if (!result.value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result.value.isFetched()).toBe(true); + + expect(result.value.type).toEqual("SolidContainer"); + + const headersResult = await result.value.getHeaders(); + assert(!headersResult.isError); + + console.log(Object.fromEntries(headersResult.headers.entries())); + + app.unmount(); + }); + }); +}); diff --git a/packages/vue/test/test-utils.ts b/packages/vue/test/test-utils.ts new file mode 100644 index 00000000..047636bb --- /dev/null +++ b/packages/vue/test/test-utils.ts @@ -0,0 +1,18 @@ +// https://vuejs.org/guide/scaling-up/testing.html#testing-composables + +import { createApp, type App } from "vue"; + +export function withSetup(composable: () => T): [T, App] { + let result!: T; + const app = createApp({ + setup() { + result = composable(); + // suppress missing template warning + return () => {}; + }, + }); + app.mount(document.createElement("div")); + // return the result and the app instance + // for testing provide/unmount + return [result, app]; +} diff --git a/packages/vue/vitest.config.js b/packages/vue/vitest.config.js index 6ed8adf1..bf5c605a 100644 --- a/packages/vue/vitest.config.js +++ b/packages/vue/vitest.config.js @@ -1,12 +1,11 @@ import { defineConfig } from "vitest/config"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import react from "@vitejs/plugin-react"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export default defineConfig({ - plugins: [react()], + plugins: [], resolve: { alias: [ { @@ -25,6 +24,6 @@ export default defineConfig({ coverage: { provider: "istanbul", }, - environment: "jsdom", + environment: "happy-dom", }, }); diff --git a/tsconfig.base.json b/tsconfig.base.json index ed5af721..abc54da4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2021", "module": "commonjs", - "lib": ["ES2021", "DOM"], + "lib": ["ES2021", "DOM", "DOM.Iterable"], "strict": true, "esModuleInterop": true, "moduleResolution": "node", From 5e6c9f43f55a6479039e11d025c8c1bd62e683b3 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 11 Sep 2026 05:50:31 +0200 Subject: [PATCH 03/32] Add test for reactive change of useResource URI --- packages/vue/test/Composables.test.ts | 65 ++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 3c1cdd14..4447d749 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -7,25 +7,32 @@ import { import { withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; +import { nextTick, ref } from "vue"; describe("LDO Vue Composables", () => { let methods: ReturnType>; - const s = setupServer( - 3006, - { - slug: "directory/", - isContainer: true, - contains: [], - }, - // undefined, - // true, - ); + const s = setupServer(3006, { + slug: "directory/", + isContainer: true, + contains: [ + { + slug: "leaf", + isContainer: false, + data: " .", + mimeType: "text/turtle", + }, + ], + }); beforeEach(() => { methods = createLdoVueMethods([solidConnectedPlugin]); methods.dataset.setContext("solid", { fetch: s.authFetch }); }); + describe("useTrackingProxy", () => { + it("should return a LinkedDataObject"); + }); + describe("useResource", () => { it("should fetch a resource by uri", async () => { const [result, app] = withSetup(() => @@ -49,6 +56,44 @@ describe("LDO Vue Composables", () => { console.log(Object.fromEntries(headersResult.headers.entries())); + const rootContainerResult = await result.value.getRootContainer(); + + assert(!rootContainerResult.isError); + console.log(rootContainerResult.uri); + + app.unmount(); + }); + + it("should fetch the new resource when uri changes", async () => { + const uriRef = ref("http://localhost:3006/directory/"); + const [result, app] = withSetup(() => methods.useResource(uriRef)); + + expect(result.value.isFetched()).toBe(false); + + await vi.waitFor(() => { + if (!result.value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result.value.isFetched()).toBe(true); + expect(result.value.type).toEqual("SolidContainer"); + + uriRef.value = "http://localhost:3006/directory/leaf"; + await nextTick(); + + expect(result.value.isFetched()).toBe(false); + expect(result.value.uri).toEqual("http://localhost:3006/directory/leaf"); + + await vi.waitFor(() => { + if (!result.value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result.value.isFetched()).toBe(true); + expect(result.value.type).toEqual("SolidLeaf"); + app.unmount(); }); }); From 8d940a819f50628ed34fca742689c03cfca22d7f Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 17 Sep 2026 03:35:55 +0200 Subject: [PATCH 04/32] Add reactive options to useResource, improve types and subscription --- packages/vue/src/methods/useResource.ts | 87 +++++++++++++++---------- packages/vue/test/Composables.test.ts | 37 ++++++++++- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index eb1ce940..70a042cf 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -2,6 +2,7 @@ import type { ConnectedLdoDataset, ConnectedPlugin, GetResourceReturnType, + Resource, } from "@ldo/connected"; import { shallowRef, @@ -9,7 +10,7 @@ import { onBeforeUnmount, type Ref, triggerRef, - type MaybeRef, + type MaybeRefOrGetter, toValue, } from "vue"; @@ -26,16 +27,16 @@ export type useResourceType = { Plugin extends Extract, UriType extends string, >( - uri: MaybeRef, - options?: UseResourceOptions, + uri: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, ): Ref>; < Name extends Plugins[number]["name"], Plugin extends Extract, UriType extends string, >( - uri?: MaybeRef, - options?: UseResourceOptions, + uri?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, ): Ref | undefined>; }; @@ -55,58 +56,78 @@ export function createUseResource( Plugin extends Extract, UriType extends string, >( - uri?: MaybeRef, - options?: UseResourceOptions, + uri?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, ): Ref | undefined> { - const resourceRef: Ref | undefined> = - shallowRef | undefined>(); - - let currentResource: GetResourceReturnType | undefined; + const resourceRef: Ref = shallowRef< + Resource | undefined + >(); + let subscriptionId: string | undefined; const onResourceUpdate = () => { triggerRef(resourceRef); }; watch( - () => toValue(uri), - (newUri) => { - if (currentResource) { - currentResource.off("update", onResourceUpdate); - currentResource.unsubscribeFromNotifications(); - } + [() => toValue(uri), () => toValue(options)], + async ([newUri, newOptions], [oldUri, oldOptions], onCleanup) => { + // TODO handle change in uri vs options more carefully + + let cancelled = false; + let localResource: Resource | undefined; + + onCleanup(async () => { + cancelled = true; + localResource?.off("update", onResourceUpdate); + if (subscriptionId) { + localResource?.unsubscribeFromNotifications(subscriptionId); + subscriptionId = undefined; + } + }); + if (newUri) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - currentResource = dataset.getResource(newUri) as any; - if (!options?.suppressInitialRead) { - if (options?.reloadOnMount) { - currentResource!.read(); + localResource = dataset.getResource(newUri) as Resource; + resourceRef.value = localResource; + if (!newOptions?.suppressInitialRead) { + if (newOptions?.reloadOnMount) { + resourceRef.value!.read(); } else { - currentResource!.readIfUnfetched(); + resourceRef.value!.readIfUnfetched(); } } - if (currentResource) { - currentResource.on("update", onResourceUpdate); + if (resourceRef.value) { + resourceRef.value.on("update", onResourceUpdate); // Subscribe to notifications if it's needed - if (options?.subscribe) { - currentResource.subscribeToNotifications(); + if (newOptions?.subscribe) { + const id = await resourceRef.value.subscribeToNotifications(); + + if (cancelled) { + await resourceRef.value.unsubscribeFromNotifications(id); + } else { + subscriptionId = id; + } } } } else { - currentResource = undefined; + resourceRef.value = undefined; } - - resourceRef.value = currentResource; }, { immediate: true }, ); // cleanup onBeforeUnmount(() => { - currentResource?.off("update", onResourceUpdate); - currentResource?.unsubscribeFromNotifications(); + resourceRef.value?.off("update", onResourceUpdate); + if (subscriptionId) { + resourceRef.value?.unsubscribeFromNotifications(subscriptionId); + subscriptionId = undefined; + } }); - return resourceRef; + return resourceRef as Ref< + GetResourceReturnType | undefined, + GetResourceReturnType + >; }; } diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 4447d749..2ac3c05a 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -1,13 +1,14 @@ import { describe, it, beforeEach, vi, expect } from "vitest"; -import { createLdoVueMethods } from "../src/index"; +import { createLdoVueMethods, type UseResourceOptions } from "../src/index"; import { type SolidConnectedPlugin, solidConnectedPlugin, + type SolidContainerUri, } from "@ldo/connected-solid"; import { withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; -import { nextTick, ref } from "vue"; +import { nextTick, type Ref, ref } from "vue"; describe("LDO Vue Composables", () => { let methods: ReturnType>; @@ -96,5 +97,37 @@ describe("LDO Vue Composables", () => { app.unmount(); }); + + it("should handle change in options and not create a new the resource", async () => { + s.fetchMock.mockClear(); + + const uriRef: Ref = ref( + "http://localhost:3006/directory/", + ); + const optionsRef: Ref> = ref({ + suppressInitialRead: true, + }); + const [result, app] = withSetup(() => + methods.useResource(uriRef, optionsRef), + ); + + expect(result.value.isFetched()).toBe(false); + + await new Promise((resolve) => setTimeout(resolve, 500)); + + expect(result.value.isFetched()).toBe(false); + + optionsRef.value = { suppressInitialRead: false }; + + await vi.waitFor(() => { + if (!result.value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result.value.isFetched()).toBe(true); + + app.unmount(); + }); }); }); From 5d70af60b6cf9bb521f9147060c82f3c21bd8cf5 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 17 Sep 2026 13:04:57 +0200 Subject: [PATCH 05/32] Add first test for vue's useSubject and fix implementation types --- packages/vue/src/methods/useSubject.ts | 21 ++-- packages/vue/test/Composables.test.ts | 66 ++++++++++++- packages/vue/test/_ldo/foafProfile.context.ts | 48 +++++++++ packages/vue/test/_ldo/foafProfile.schema.ts | 97 +++++++++++++++++++ .../vue/test/_ldo/foafProfile.shapeTypes.ts | 19 ++++ packages/vue/test/_ldo/foafProfile.typings.ts | 33 +++++++ 6 files changed, 271 insertions(+), 13 deletions(-) create mode 100644 packages/vue/test/_ldo/foafProfile.context.ts create mode 100644 packages/vue/test/_ldo/foafProfile.schema.ts create mode 100644 packages/vue/test/_ldo/foafProfile.shapeTypes.ts create mode 100644 packages/vue/test/_ldo/foafProfile.typings.ts diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts index 07a596ae..10e1ab67 100644 --- a/packages/vue/src/methods/useSubject.ts +++ b/packages/vue/src/methods/useSubject.ts @@ -6,7 +6,7 @@ import type { ConnectedPlugin, IConnectedLdoDataset, } from "@ldo/connected"; -import { computed, toValue, type MaybeRef, type Ref } from "vue"; +import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue"; export interface UseSubjectOptions { dataset?: IConnectedLdoDataset; @@ -15,19 +15,19 @@ export interface UseSubjectOptions { export type useSubjectType = { ( shapeType: ShapeType, - subject: MaybeRef, + subject: MaybeRefOrGetter, options?: UseSubjectOptions, - ): Type; + ): Ref; ( shapeType: ShapeType, - subject?: MaybeRef, + subject?: MaybeRefOrGetter, options?: UseSubjectOptions, - ): Type | undefined; + ): Ref; ( shapeType: ShapeType, - subject?: MaybeRef, + subject?: MaybeRefOrGetter, options?: UseSubjectOptions, - ): Type | undefined; + ): Ref; }; /** @@ -44,17 +44,18 @@ export function createUseSubject( */ return function useSubject( shapeType: ShapeType, - subject?: MaybeRef, - options?: UseSubjectOptions, + subject?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, ): Ref { return computed(() => { const subjectValue = toValue(subject); + const observedOptions = toValue(options); if (!subjectValue) return undefined; return useTrackingProxy( shapeType, (builder) => builder.fromSubject(subjectValue), - options?.dataset ?? dataset, + observedOptions?.dataset ?? dataset, ).value; }); }; diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 2ac3c05a..55ac27f5 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -8,7 +8,8 @@ import { import { withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; -import { nextTick, type Ref, ref } from "vue"; +import { nextTick, type Ref, ref, watch } from "vue"; +import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes"; describe("LDO Vue Composables", () => { let methods: ReturnType>; @@ -22,6 +23,18 @@ describe("LDO Vue Composables", () => { data: " .", mimeType: "text/turtle", }, + { + slug: "person", + isContainer: false, + data: ` + @prefix foaf: . + <#me> + a foaf:Person; + foaf:name "Name"; + foaf:knows , . + `, + mimeType: "text/turtle", + }, ], }); @@ -31,7 +44,7 @@ describe("LDO Vue Composables", () => { }); describe("useTrackingProxy", () => { - it("should return a LinkedDataObject"); + it.todo("should return a LinkedDataObject"); }); describe("useResource", () => { @@ -98,7 +111,7 @@ describe("LDO Vue Composables", () => { app.unmount(); }); - it("should handle change in options and not create a new the resource", async () => { + it("should handle change in options", async () => { s.fetchMock.mockClear(); const uriRef: Ref = ref( @@ -130,4 +143,51 @@ describe("LDO Vue Composables", () => { app.unmount(); }); }); + + describe("useSubject", () => { + it("should return a linked data object with given subject uri", async () => { + let rerenderCount = 0; + + const [result, app] = withSetup(() => { + const useSubjectResult = methods.useSubject( + FoafProfileShapeType, + "http://localhost:3006/directory/person#me", + ); + const useResourceResult = methods.useResource( + "http://localhost:3006/directory/person", + ); + + return [useSubjectResult, useResourceResult] as const; + }); + + watch(result, () => { + rerenderCount++; + }); + + expect(rerenderCount).toEqual(0); + + await vi.waitFor(() => { + if (!result[1].value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(rerenderCount).toEqual(1); + + assert(!result[1].value.isError); + expect(result[1].value.isAbsent()).toBe(false); + + expect(result[0].value.name).toEqual("Name"); + const knows = result[0].value.knows?.map((k) => k["@id"]); + expect(knows).toHaveLength(2); + expect(knows).toContain("https://example.com/profile/card#me"); + expect(knows).toContain("https://example.org/profile/card#i"); + + app.unmount(); + }); + + it.todo("should change the LDO when the subject changes"); + it.todo("should change the LDO when the shape type changes"); + it.todo("should change the LDO when the options.dataset changes"); + }); }); diff --git a/packages/vue/test/_ldo/foafProfile.context.ts b/packages/vue/test/_ldo/foafProfile.context.ts new file mode 100644 index 00000000..e7a57dfc --- /dev/null +++ b/packages/vue/test/_ldo/foafProfile.context.ts @@ -0,0 +1,48 @@ +import type { LdoJsonldContext } from "@ldo/ldo"; + +/** + * ============================================================================= + * foafProfileContext: JSONLD Context for foafProfile + * ============================================================================= + */ +export const foafProfileContext: LdoJsonldContext = { + type: { + "@id": "@type", + "@isCollection": true, + }, + Person: { + "@id": "http://xmlns.com/foaf/0.1/Person", + "@context": { + type: { + "@id": "@type", + "@isCollection": true, + }, + name: { + "@id": "http://xmlns.com/foaf/0.1/name", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + img: { + "@id": "http://xmlns.com/foaf/0.1/img", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + knows: { + "@id": "http://xmlns.com/foaf/0.1/knows", + "@type": "@id", + "@isCollection": true, + }, + }, + }, + name: { + "@id": "http://xmlns.com/foaf/0.1/name", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + img: { + "@id": "http://xmlns.com/foaf/0.1/img", + "@type": "http://www.w3.org/2001/XMLSchema#string", + }, + knows: { + "@id": "http://xmlns.com/foaf/0.1/knows", + "@type": "@id", + "@isCollection": true, + }, +}; diff --git a/packages/vue/test/_ldo/foafProfile.schema.ts b/packages/vue/test/_ldo/foafProfile.schema.ts new file mode 100644 index 00000000..25243cde --- /dev/null +++ b/packages/vue/test/_ldo/foafProfile.schema.ts @@ -0,0 +1,97 @@ +import type { Schema } from "shexj"; + +/** + * ============================================================================= + * foafProfileSchema: ShexJ Schema for foafProfile + * ============================================================================= + */ +export const foafProfileSchema: Schema = { + type: "Schema", + shapes: [ + { + id: "https://example.com/FoafProfile", + type: "ShapeDecl", + shapeExpr: { + type: "Shape", + expression: { + type: "EachOf", + expressions: [ + { + type: "TripleConstraint", + predicate: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", + valueExpr: { + type: "NodeConstraint", + values: ["http://xmlns.com/foaf/0.1/Person"], + }, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Defines the node as a Person (from foaf)", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/name", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#string", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Define a person's name.", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/img", + valueExpr: { + type: "NodeConstraint", + datatype: "http://www.w3.org/2001/XMLSchema#string", + }, + min: 0, + max: 1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: "Photo link but in string form", + }, + }, + ], + }, + { + type: "TripleConstraint", + predicate: "http://xmlns.com/foaf/0.1/knows", + valueExpr: "https://example.com/FoafProfile", + min: 0, + max: -1, + annotations: [ + { + type: "Annotation", + predicate: "http://www.w3.org/2000/01/rdf-schema#comment", + object: { + value: + "A list of WebIds for all the people this user knows.", + }, + }, + ], + }, + ], + }, + extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"], + }, + }, + ], +}; diff --git a/packages/vue/test/_ldo/foafProfile.shapeTypes.ts b/packages/vue/test/_ldo/foafProfile.shapeTypes.ts new file mode 100644 index 00000000..2c52ee86 --- /dev/null +++ b/packages/vue/test/_ldo/foafProfile.shapeTypes.ts @@ -0,0 +1,19 @@ +import type { ShapeType } from "@ldo/ldo"; +import { foafProfileSchema } from "./foafProfile.schema"; +import { foafProfileContext } from "./foafProfile.context"; +import type { FoafProfile } from "./foafProfile.typings"; + +/** + * ============================================================================= + * LDO ShapeTypes foafProfile + * ============================================================================= + */ + +/** + * FoafProfile ShapeType + */ +export const FoafProfileShapeType: ShapeType = { + schema: foafProfileSchema, + shape: "https://example.com/FoafProfile", + context: foafProfileContext, +}; diff --git a/packages/vue/test/_ldo/foafProfile.typings.ts b/packages/vue/test/_ldo/foafProfile.typings.ts new file mode 100644 index 00000000..16843881 --- /dev/null +++ b/packages/vue/test/_ldo/foafProfile.typings.ts @@ -0,0 +1,33 @@ +import type { LdoJsonldContext, LdSet } from "@ldo/ldo"; + +/** + * ============================================================================= + * Typescript Typings for foafProfile + * ============================================================================= + */ + +/** + * FoafProfile Type + */ +export interface FoafProfile { + "@id"?: string; + "@context"?: LdoJsonldContext; + /** + * Defines the node as a Person (from foaf) + */ + type: LdSet<{ + "@id": "Person"; + }>; + /** + * Define a person's name. + */ + name?: string; + /** + * Photo link but in string form + */ + img?: string; + /** + * A list of WebIds for all the people this user knows. + */ + knows?: LdSet; +} From 5401213acad53b523843238b628d25754d5eee5d Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 18 Sep 2026 06:51:13 +0200 Subject: [PATCH 06/32] Improve tests of useSubject --- packages/vue/src/methods/useResource.ts | 4 + packages/vue/src/util/useTrackingProxy.ts | 1 + packages/vue/test/Composables.test.ts | 96 ++++++++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index 70a042cf..c6b14749 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -66,11 +66,15 @@ export function createUseResource( const onResourceUpdate = () => { triggerRef(resourceRef); + console.log("TRIGGERING RESOURCE UPDATE REF"); }; watch( [() => toValue(uri), () => toValue(options)], async ([newUri, newOptions], [oldUri, oldOptions], onCleanup) => { + // console.log(newUri, oldUri); + + // if (newUri === oldUri && newOptions === oldOptions) return; // TODO handle change in uri vs options more carefully let cancelled = false; diff --git a/packages/vue/src/util/useTrackingProxy.ts b/packages/vue/src/util/useTrackingProxy.ts index 70d4a23b..a0af0729 100644 --- a/packages/vue/src/util/useTrackingProxy.ts +++ b/packages/vue/src/util/useTrackingProxy.ts @@ -21,6 +21,7 @@ export function useTrackingProxy( const forceUpdate = () => { triggerRef(linkedDataObject); + console.log("TRIGGERING REF"); }; const builder = createTrackingProxyBuilder(dataset, shapeType, forceUpdate); diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 55ac27f5..a03090a7 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -4,6 +4,7 @@ import { type SolidConnectedPlugin, solidConnectedPlugin, type SolidContainerUri, + type SolidLeafUri, } from "@ldo/connected-solid"; import { withSetup } from "./test-utils.js"; import assert from "node:assert"; @@ -32,6 +33,17 @@ describe("LDO Vue Composables", () => { a foaf:Person; foaf:name "Name"; foaf:knows , . + + <#i> a foaf:Person; foaf:name "myname". + `, + mimeType: "text/turtle", + }, + { + slug: "person2", + isContainer: false, + data: ` + @prefix foaf: . + <#me> a foaf:Person; foaf:name "Other Name". `, mimeType: "text/turtle", }, @@ -40,7 +52,9 @@ describe("LDO Vue Composables", () => { beforeEach(() => { methods = createLdoVueMethods([solidConnectedPlugin]); - methods.dataset.setContext("solid", { fetch: s.authFetch }); + methods.dataset.setContext("solid", { + fetch: s.fetchMock, + }); }); describe("useTrackingProxy", () => { @@ -112,7 +126,7 @@ describe("LDO Vue Composables", () => { }); it("should handle change in options", async () => { - s.fetchMock.mockClear(); + // s.fetchMock.mockClear(); const uriRef: Ref = ref( "http://localhost:3006/directory/", @@ -140,6 +154,8 @@ describe("LDO Vue Composables", () => { expect(result.value.isFetched()).toBe(true); + expect(s.fetchMock).toHaveBeenCalledTimes(1); + app.unmount(); }); }); @@ -186,8 +202,80 @@ describe("LDO Vue Composables", () => { app.unmount(); }); - it.todo("should change the LDO when the subject changes"); - it.todo("should change the LDO when the shape type changes"); + it("should change the LDO when the subject changes", async () => { + // s.fetchMock.mockClear(); + let rerenderCount = 0; + const subject = ref("http://localhost:3006/directory/person#me"); + + expect(s.fetchMock).toHaveBeenCalledTimes(0); + + const [result, app] = withSetup(() => { + const useSubjectResult = methods.useSubject( + FoafProfileShapeType, + subject, + ); + + const useResourceResult = methods.useResource( + subject as Ref, + ); + + return [useSubjectResult, useResourceResult] as const; + }); + + // inform the tracking proxy we're interested in this and check the result + // removing access to the property will lead to less renders + expect(result[0].value.name).toEqual(undefined); + + watch(result[0], () => { + console.log("RERENDER", rerenderCount + 1); + rerenderCount++; + }); + + expect(rerenderCount).toEqual(0); + + await vi.waitFor(() => { + if (!result[1].value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result[0].value.name).toEqual("Name"); + expect(rerenderCount).toEqual(1); + + subject.value = "http://localhost:3006/directory/person#i"; + + // this should not trigger refetch + await nextTick(); + + expect(result[1].value.isFetched()).toBe(true); + expect(rerenderCount).toEqual(2); + expect(result[0].value.name).toEqual("myname"); + + subject.value = "http://localhost:3006/directory/person2#me"; + + // this triggers refetch + await nextTick(); + expect(result[1].value.isFetched()).toBe(false); + expect(result[0].value.name).toEqual(undefined); + expect(rerenderCount).toEqual(3); + + await vi.waitFor(() => { + if (!result[1].value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + expect(result[1].value.isFetched()).toBe(true); + expect(result[1].value.isAbsent()).toBe(false); + expect(result[0].value.name).toEqual("Other Name"); + expect(rerenderCount).toEqual(4); + + app.unmount(); + }); + + it.todo( + "should change the LDO when the shape type changes (this will probably ruin types though)", + ); it.todo("should change the LDO when the options.dataset changes"); }); }); From 8a8121d10ddac74f1f6d1b6ab00a5a2d09ad9d67 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 18 Sep 2026 21:04:35 +0200 Subject: [PATCH 07/32] Implement basic useMatchSubject for @ldo/vue --- dev/solid-vue-vite-app/src/App.vue | 2 + dev/solid-vue-vite-app/src/ldoVue.ts | 5 +- .../src/useMatchSubjectTest.vue | 60 ++++++++ packages/vue/src/createLdoVueMethods.tsx | 4 +- packages/vue/src/methods/useMatchSubject.ts | 49 +++++++ packages/vue/src/methods/useSubject.ts | 6 +- packages/vue/test/Composables.test.ts | 138 +++++++++++++++--- packages/vue/test/test-utils.ts | 42 +++++- 8 files changed, 272 insertions(+), 34 deletions(-) create mode 100644 dev/solid-vue-vite-app/src/useMatchSubjectTest.vue create mode 100644 packages/vue/src/methods/useMatchSubject.ts diff --git a/dev/solid-vue-vite-app/src/App.vue b/dev/solid-vue-vite-app/src/App.vue index cd23ff44..08a70c78 100644 --- a/dev/solid-vue-vite-app/src/App.vue +++ b/dev/solid-vue-vite-app/src/App.vue @@ -2,6 +2,7 @@ import { computed, ref } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes.ts"; import { useSubject, useResource } from "./ldoVue.ts"; +import UseMatchSubjectTest from "./useMatchSubjectTest.vue"; const isValidUrl = (str: string) => { try { @@ -47,6 +48,7 @@ useResource(validUri);

   
+  
 
 
 
diff --git a/dev/solid-vue-vite-app/src/ldoVue.ts b/dev/solid-vue-vite-app/src/ldoVue.ts
index 48e8bb9e..54680072 100644
--- a/dev/solid-vue-vite-app/src/ldoVue.ts
+++ b/dev/solid-vue-vite-app/src/ldoVue.ts
@@ -1,6 +1,5 @@
 import { createLdoVueMethods } from "@ldo/vue";
 import { solidConnectedPlugin } from "@ldo/connected-solid";
 
-export const { dataset, useSubject, useResource } = createLdoVueMethods([
-  solidConnectedPlugin,
-]);
+export const { dataset, useSubject, useResource, useMatchSubject } =
+  createLdoVueMethods([solidConnectedPlugin]);
diff --git a/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue b/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue
new file mode 100644
index 00000000..15ab9203
--- /dev/null
+++ b/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue
@@ -0,0 +1,60 @@
+
+
+
diff --git a/packages/vue/src/createLdoVueMethods.tsx b/packages/vue/src/createLdoVueMethods.tsx
index 63dd58df..186d57c9 100644
--- a/packages/vue/src/createLdoVueMethods.tsx
+++ b/packages/vue/src/createLdoVueMethods.tsx
@@ -5,7 +5,7 @@ import {
 } from "@ldo/connected";
 // import { createUseLdo } from "./methods/useLdo";
 // import { createUseMatchObject } from "./methods/useMatchObject";
-// import { createUseMatchSubject } from "./methods/useMatchSubject";
+import { createUseMatchSubject } from "./methods/useMatchSubject";
 import { createUseResource } from "./methods/useResource";
 import { createUseSubject } from "./methods/useSubject";
 // import { createUseSubscribeToResource } from "./methods/useSubscribeToResource";
@@ -77,7 +77,7 @@ export function createLdoVueMethods<
     // useDataset: createUseDataset(dataset),
     // useLdo: createUseLdo(dataset),
     // useMatchObject: createUseMatchObject(dataset),
-    // useMatchSubject: createUseMatchSubject(dataset),
+    useMatchSubject: createUseMatchSubject(dataset),
     useResource: createUseResource(dataset),
     useSubject: createUseSubject(dataset),
     // useSubscribeToResource: createUseSubscribeToResource(dataset),
diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts
new file mode 100644
index 00000000..dbd5da3b
--- /dev/null
+++ b/packages/vue/src/methods/useMatchSubject.ts
@@ -0,0 +1,49 @@
+import type { QuadMatch, SubjectNode } from "@ldo/rdf-utils";
+import type { ShapeType, LdoBase, LdSet } from "@ldo/ldo";
+import { useTrackingProxy } from "../util/useTrackingProxy";
+import type {
+  ConnectedLdoDataset,
+  ConnectedPlugin,
+  IConnectedLdoDataset,
+} from "@ldo/connected";
+import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue";
+
+export interface UseMatchSubjectOptions {
+  dataset?: IConnectedLdoDataset;
+}
+
+/**
+ * @internal
+ *
+ * Creates a useMatchSubject function.
+ */
+export function createUseMatchSubject(
+  dataset: ConnectedLdoDataset,
+) {
+  /**
+   * Returns a Linked Data Object based on the provided subject.
+   * Triggers a rerender if the data are updated.
+   */
+  return function useMatchSubject(
+    shapeType: ShapeType,
+    predicate?: MaybeRefOrGetter,
+    object?: MaybeRefOrGetter,
+    graph?: MaybeRefOrGetter,
+    options?: MaybeRefOrGetter>,
+  ): Ref> {
+    return computed(() => {
+      const predicateValue = toValue(predicate);
+      const objectValue = toValue(object);
+      const graphValue = toValue(graph);
+      const observedOptions = toValue(options);
+      console.log(predicateValue, objectValue, graphValue);
+
+      return useTrackingProxy(
+        shapeType,
+        (builder) =>
+          builder.matchSubject(predicateValue, objectValue, graphValue),
+        observedOptions?.dataset ?? dataset,
+      ).value;
+    });
+  };
+}
diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts
index 10e1ab67..f37a0fdb 100644
--- a/packages/vue/src/methods/useSubject.ts
+++ b/packages/vue/src/methods/useSubject.ts
@@ -16,17 +16,17 @@ export type useSubjectType = {
   (
     shapeType: ShapeType,
     subject: MaybeRefOrGetter,
-    options?: UseSubjectOptions,
+    options?: MaybeRefOrGetter>,
   ): Ref;
   (
     shapeType: ShapeType,
     subject?: MaybeRefOrGetter,
-    options?: UseSubjectOptions,
+    options?: MaybeRefOrGetter>,
   ): Ref;
   (
     shapeType: ShapeType,
     subject?: MaybeRefOrGetter,
-    options?: UseSubjectOptions,
+    options?: MaybeRefOrGetter>,
   ): Ref;
 };
 
diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts
index a03090a7..482d3e22 100644
--- a/packages/vue/test/Composables.test.ts
+++ b/packages/vue/test/Composables.test.ts
@@ -6,11 +6,12 @@ import {
   type SolidContainerUri,
   type SolidLeafUri,
 } from "@ldo/connected-solid";
-import { withSetup } from "./test-utils.js";
+import { RerenderCount, withSetup } from "./test-utils.js";
 import assert from "node:assert";
 import { setupServer } from "@ldo/test-solid-server";
-import { nextTick, type Ref, ref, watch } from "vue";
+import { nextTick, type Ref, ref } from "vue";
 import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes";
+import { BasicLdSet } from "@ldo/jsonld-dataset-proxy";
 
 describe("LDO Vue Composables", () => {
   let methods: ReturnType>;
@@ -29,6 +30,7 @@ describe("LDO Vue Composables", () => {
         isContainer: false,
         data: `
         @prefix foaf: .
+        <> a foaf:PersonalProfileDocument.
         <#me>
           a foaf:Person;
           foaf:name "Name";
@@ -52,9 +54,7 @@ describe("LDO Vue Composables", () => {
 
   beforeEach(() => {
     methods = createLdoVueMethods([solidConnectedPlugin]);
-    methods.dataset.setContext("solid", {
-      fetch: s.fetchMock,
-    });
+    methods.dataset.setContext("solid", { fetch: s.fetchMock });
   });
 
   describe("useTrackingProxy", () => {
@@ -162,8 +162,6 @@ describe("LDO Vue Composables", () => {
 
   describe("useSubject", () => {
     it("should return a linked data object with given subject uri", async () => {
-      let rerenderCount = 0;
-
       const [result, app] = withSetup(() => {
         const useSubjectResult = methods.useSubject(
           FoafProfileShapeType,
@@ -176,11 +174,9 @@ describe("LDO Vue Composables", () => {
         return [useSubjectResult, useResourceResult] as const;
       });
 
-      watch(result, () => {
-        rerenderCount++;
-      });
+      const rerenderCount = new RerenderCount(result);
 
-      expect(rerenderCount).toEqual(0);
+      expect(rerenderCount.count).toEqual(0);
 
       await vi.waitFor(() => {
         if (!result[1].value.isFetched()) {
@@ -188,7 +184,7 @@ describe("LDO Vue Composables", () => {
         }
       });
 
-      expect(rerenderCount).toEqual(1);
+      expect(rerenderCount.count).toEqual(1);
 
       assert(!result[1].value.isError);
       expect(result[1].value.isAbsent()).toBe(false);
@@ -204,7 +200,7 @@ describe("LDO Vue Composables", () => {
 
     it("should change the LDO when the subject changes", async () => {
       // s.fetchMock.mockClear();
-      let rerenderCount = 0;
+
       const subject = ref("http://localhost:3006/directory/person#me");
 
       expect(s.fetchMock).toHaveBeenCalledTimes(0);
@@ -222,16 +218,13 @@ describe("LDO Vue Composables", () => {
         return [useSubjectResult, useResourceResult] as const;
       });
 
+      const rerenderCount = new RerenderCount(result[0]);
+
       // inform the tracking proxy we're interested in this and check the result
       // removing access to the property will lead to less renders
       expect(result[0].value.name).toEqual(undefined);
 
-      watch(result[0], () => {
-        console.log("RERENDER", rerenderCount + 1);
-        rerenderCount++;
-      });
-
-      expect(rerenderCount).toEqual(0);
+      expect(rerenderCount.count).toEqual(0);
 
       await vi.waitFor(() => {
         if (!result[1].value.isFetched()) {
@@ -240,7 +233,7 @@ describe("LDO Vue Composables", () => {
       });
 
       expect(result[0].value.name).toEqual("Name");
-      expect(rerenderCount).toEqual(1);
+      expect(rerenderCount.count).toEqual(1);
 
       subject.value = "http://localhost:3006/directory/person#i";
 
@@ -248,7 +241,7 @@ describe("LDO Vue Composables", () => {
       await nextTick();
 
       expect(result[1].value.isFetched()).toBe(true);
-      expect(rerenderCount).toEqual(2);
+      expect(rerenderCount.count).toEqual(2);
       expect(result[0].value.name).toEqual("myname");
 
       subject.value = "http://localhost:3006/directory/person2#me";
@@ -257,7 +250,7 @@ describe("LDO Vue Composables", () => {
       await nextTick();
       expect(result[1].value.isFetched()).toBe(false);
       expect(result[0].value.name).toEqual(undefined);
-      expect(rerenderCount).toEqual(3);
+      expect(rerenderCount.count).toEqual(3);
 
       await vi.waitFor(() => {
         if (!result[1].value.isFetched()) {
@@ -268,7 +261,7 @@ describe("LDO Vue Composables", () => {
       expect(result[1].value.isFetched()).toBe(true);
       expect(result[1].value.isAbsent()).toBe(false);
       expect(result[0].value.name).toEqual("Other Name");
-      expect(rerenderCount).toEqual(4);
+      expect(rerenderCount.count).toEqual(4);
 
       app.unmount();
     });
@@ -278,4 +271,103 @@ describe("LDO Vue Composables", () => {
     );
     it.todo("should change the LDO when the options.dataset changes");
   });
+
+  describe("useMatchSubject", () => {
+    it("should return a LdSet of matched linked data objects", async () => {
+      const [result, app] = withSetup(() => {
+        const useMatchSubjectResult = methods.useMatchSubject(
+          FoafProfileShapeType,
+          "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
+          "http://xmlns.com/foaf/0.1/Person",
+        );
+        const useResourceResult = methods.useResource(
+          "http://localhost:3006/directory/person",
+        );
+
+        return [useMatchSubjectResult, useResourceResult] as const;
+      });
+
+      await vi.waitFor(() => {
+        if (!result[1].value.isFetched()) {
+          throw new Error("not fetched yet");
+        }
+      });
+
+      assert(!result[1].value.isError);
+      expect(result[1].value.isAbsent()).toBe(false);
+
+      expect(result[0].value).toBeInstanceOf(BasicLdSet);
+      expect(result[0].value.size).toBe(2);
+      expect(result[0].value.map((v) => v["@id"])).toContain(
+        "http://localhost:3006/directory/person#me",
+      );
+      expect(result[0].value.map((v) => v["@id"])).toContain(
+        "http://localhost:3006/directory/person#i",
+      );
+
+      app.unmount();
+    });
+    it.todo(
+      "should change the LDO set when the shape type changes (this will probably ruin types though)",
+    );
+    it.todo("should change the LDO set when the options.dataset changes");
+    it.todo("should change the LDO set when predicate changes");
+
+    it("should change the LDO set when object changes", async () => {
+      const object = ref("http://xmlns.com/foaf/0.1/Person");
+
+      const [result, app] = withSetup(() => {
+        const useMatchSubjectResult = methods.useMatchSubject(
+          FoafProfileShapeType,
+          "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
+          object,
+          undefined,
+        );
+        const useResourceResult = methods.useResource(
+          "http://localhost:3006/directory/person",
+        );
+
+        return [useMatchSubjectResult, useResourceResult] as const;
+      });
+
+      const rerenderCount = new RerenderCount(result);
+
+      expect(result[0].value.size).toBe(0);
+
+      await vi.waitFor(() => {
+        if (!result[1].value.isFetched()) {
+          throw new Error("not fetched yet");
+        }
+      });
+
+      expect(rerenderCount.count).toBe(2);
+
+      assert(!result[1].value.isError);
+      expect(result[1].value.isAbsent()).toBe(false);
+
+      expect(result[0].value).toBeInstanceOf(BasicLdSet);
+      expect(result[0].value.size).toBe(2);
+      expect(result[0].value.map((v) => v["@id"])).toContain(
+        "http://localhost:3006/directory/person#me",
+      );
+      expect(result[0].value.map((v) => v["@id"])).toContain(
+        "http://localhost:3006/directory/person#i",
+      );
+
+      object.value = "http://xmlns.com/foaf/0.1/PersonalProfileDocument";
+      await nextTick();
+      expect(result[0].value.size).toBe(1);
+
+      result[0].value.forEach((v) => {
+        expect(v["@id"]).toBe("http://localhost:3006/directory/person");
+      });
+
+      // TODO fix, it does update in production though
+      expect(rerenderCount.count).toBe(3);
+
+      app.unmount();
+    });
+
+    it.todo("should change the LDO set when graph changes");
+  });
 });
diff --git a/packages/vue/test/test-utils.ts b/packages/vue/test/test-utils.ts
index 047636bb..75862a4a 100644
--- a/packages/vue/test/test-utils.ts
+++ b/packages/vue/test/test-utils.ts
@@ -1,7 +1,14 @@
-// https://vuejs.org/guide/scaling-up/testing.html#testing-composables
-
-import { createApp, type App } from "vue";
+import {
+  createApp,
+  type MultiWatchSources,
+  watch,
+  type App,
+  type WatchSource,
+} from "vue";
 
+/**
+ * https://vuejs.org/guide/scaling-up/testing.html#testing-composables
+ */
 export function withSetup(composable: () => T): [T, App] {
   let result!: T;
   const app = createApp({
@@ -16,3 +23,32 @@ export function withSetup(composable: () => T): [T, App] {
   // for testing provide/unmount
   return [result, app];
 }
+
+/**
+ * This counts how many times the reference or references updated.
+ * Constructor accepts any parameter that fits vue's watch source/sources parameter.
+ *
+ * https://vuejs.org/api/reactivity-core.html#watch
+ */
+type AllowedSources =
+  | WatchSource
+  | MultiWatchSources
+  | Readonly;
+export class RerenderCount {
+  private _count = 0;
+
+  constructor(sources: AllowedSources) {
+    watch(sources, () => {
+      this._count++;
+      console.log("RERENDER", this._count);
+    });
+  }
+
+  public get count() {
+    return this._count;
+  }
+
+  public clearCount() {
+    this._count = 0;
+  }
+}

From c89be6c4119a71697a333a77fc50af48214625ad Mon Sep 17 00:00:00 2001
From: mrkvon 
Date: Fri, 18 Sep 2026 22:19:39 +0200
Subject: [PATCH 08/32] Implement useMatchObject for @ldo/vue

---
 dev/solid-vue-vite-app/src/App.vue            |  9 +-
 .../src/useMatchSubjectTest.vue               |  8 +-
 package-lock.json                             | 90 +++++++++++++++++++
 packages/vue/src/createLdoVueMethods.tsx      | 36 ++++----
 packages/vue/src/index.ts                     |  4 +-
 packages/vue/src/methods/useMatchObject.ts    | 48 ++++++++++
 packages/vue/src/methods/useMatchSubject.ts   |  2 +-
 packages/vue/test/Composables.test.ts         | 44 +++++++++
 8 files changed, 214 insertions(+), 27 deletions(-)
 create mode 100644 packages/vue/src/methods/useMatchObject.ts

diff --git a/dev/solid-vue-vite-app/src/App.vue b/dev/solid-vue-vite-app/src/App.vue
index 08a70c78..f623c8e5 100644
--- a/dev/solid-vue-vite-app/src/App.vue
+++ b/dev/solid-vue-vite-app/src/App.vue
@@ -1,7 +1,7 @@
 
@@ -52,7 +52,7 @@ useResource(validResource);
     
    -
  • +
  • {{ item.knows?.map((p) => p["@id"]).join(", ") }}
diff --git a/package-lock.json b/package-lock.json index f5306056..c71b11c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42118,6 +42118,96 @@ }, "packages/wac": { "extraneous": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", + "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", + "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", + "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", + "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", + "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", + "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/packages/vue/src/createLdoVueMethods.tsx b/packages/vue/src/createLdoVueMethods.tsx index 186d57c9..1a7cf44b 100644 --- a/packages/vue/src/createLdoVueMethods.tsx +++ b/packages/vue/src/createLdoVueMethods.tsx @@ -4,7 +4,7 @@ import { type ConnectedPlugin, } from "@ldo/connected"; // import { createUseLdo } from "./methods/useLdo"; -// import { createUseMatchObject } from "./methods/useMatchObject"; +import { createUseMatchObject } from "./methods/useMatchObject"; import { createUseMatchSubject } from "./methods/useMatchSubject"; import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; @@ -46,24 +46,24 @@ import { createUseSubject } from "./methods/useSubject"; * ]); * ``` * - * `App.tsx` - * ```typescript - * import { PostShShapeType } from "./_ldo/posts.shapeType.ts"; - * import { useResource, useSubject } from "./methods.ts"; + * `App.vue` + * ```vue + * * - * return ( - *
    - * {post.publisher.map((publisher) => { - * return
  • {publisher["@id"]}
  • ; - * })} - *
- * ); - * }; + * * ``` */ export function createLdoVueMethods< @@ -76,7 +76,7 @@ export function createLdoVueMethods< dataset, // useDataset: createUseDataset(dataset), // useLdo: createUseLdo(dataset), - // useMatchObject: createUseMatchObject(dataset), + useMatchObject: createUseMatchObject(dataset), useMatchSubject: createUseMatchSubject(dataset), useResource: createUseResource(dataset), useSubject: createUseSubject(dataset), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 7ab0fd86..e9de1f9a 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,8 +1,8 @@ export * from "./createLdoVueMethods"; // export * from "./methods/useLdo"; -// export * from "./methods/useMatchObject"; -// export * from "./methods/useMatchSubject"; +export * from "./methods/useMatchObject"; +export * from "./methods/useMatchSubject"; export * from "./methods/useResource"; export * from "./methods/useSubject"; // export * from "./methods/useSubscribeToResource"; diff --git a/packages/vue/src/methods/useMatchObject.ts b/packages/vue/src/methods/useMatchObject.ts new file mode 100644 index 00000000..c3ab5434 --- /dev/null +++ b/packages/vue/src/methods/useMatchObject.ts @@ -0,0 +1,48 @@ +import type { QuadMatch } from "@ldo/rdf-utils"; +import type { ShapeType, LdoBase, LdSet } from "@ldo/ldo"; +import { useTrackingProxy } from "../util/useTrackingProxy"; +import type { + ConnectedLdoDataset, + ConnectedPlugin, + IConnectedLdoDataset, +} from "@ldo/connected"; +import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue"; + +export interface UseMatchObjectOptions { + dataset?: IConnectedLdoDataset; +} + +/** + * @internal + * + * Creates a useMatchObject function. + */ +export function createUseMatchObject( + dataset: ConnectedLdoDataset, +) { + /** + * Returns a LDO set of matching items. + * Triggers a rerender if the data are updated. + */ + return function useMatchObject( + shapeType: ShapeType, + subject?: MaybeRefOrGetter, + predicate?: MaybeRefOrGetter, + graph?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, + ): Ref> { + return computed(() => { + const subjectValue = toValue(subject); + const predicateValue = toValue(predicate); + const graphValue = toValue(graph); + const observedOptions = toValue(options); + + return useTrackingProxy( + shapeType, + (builder) => + builder.matchObject(subjectValue, predicateValue, graphValue), + observedOptions?.dataset ?? dataset, + ).value; + }); + }; +} diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts index dbd5da3b..ce28062c 100644 --- a/packages/vue/src/methods/useMatchSubject.ts +++ b/packages/vue/src/methods/useMatchSubject.ts @@ -21,7 +21,7 @@ export function createUseMatchSubject( dataset: ConnectedLdoDataset, ) { /** - * Returns a Linked Data Object based on the provided subject. + * Returns a LDO set of matching items. * Triggers a rerender if the data are updated. */ return function useMatchSubject( diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 482d3e22..3feb1fc7 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -370,4 +370,48 @@ describe("LDO Vue Composables", () => { it.todo("should change the LDO set when graph changes"); }); + + describe("useMatchObject", () => { + it("should return a LdSet of matched linked data objects", async () => { + const [result, app] = withSetup(() => { + const useMatchObjectResult = methods.useMatchObject( + FoafProfileShapeType, + "http://localhost:3006/directory/person#me", + "http://xmlns.com/foaf/0.1/knows", + "http://localhost:3006/directory/person", + ); + const useResourceResult = methods.useResource( + "http://localhost:3006/directory/person", + ); + + return [useMatchObjectResult, useResourceResult] as const; + }); + + await vi.waitFor(() => { + if (!result[1].value.isFetched()) { + throw new Error("not fetched yet"); + } + }); + + assert(!result[1].value.isError); + expect(result[1].value.isAbsent()).toBe(false); + + const friends = result[0].value; + expect(friends.size).toBe(2); + expect(friends.map((v) => v["@id"])).toContain( + "https://example.com/profile/card#me", + ); + expect(friends.map((v) => v["@id"])).toContain( + "https://example.org/profile/card#i", + ); + + app.unmount(); + }); + + it.todo("should change the LDO set when the shape type changes"); + it.todo("should change the LDO set when the options.dataset changes"); + it.todo("should change the LDO set when subject changes"); + it.todo("should change the LDO set when predicate changes"); + it.todo("should change the LDO set when graph changes"); + }); }); From 065c2e1c9f7e4d6bb7bb4545e1f3d2a658600f1f Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 18 Sep 2026 22:30:52 +0200 Subject: [PATCH 09/32] Refactor wait for resource in tests by extracting it to utils --- packages/vue/test/Composables.test.ts | 64 +++++---------------------- packages/vue/test/test-utils.ts | 10 +++++ 2 files changed, 22 insertions(+), 52 deletions(-) diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 3feb1fc7..af022779 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach, vi, expect } from "vitest"; +import { describe, it, beforeEach, expect } from "vitest"; import { createLdoVueMethods, type UseResourceOptions } from "../src/index"; import { type SolidConnectedPlugin, @@ -6,7 +6,7 @@ import { type SolidContainerUri, type SolidLeafUri, } from "@ldo/connected-solid"; -import { RerenderCount, withSetup } from "./test-utils.js"; +import { RerenderCount, waitForResource, withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; import { nextTick, type Ref, ref } from "vue"; @@ -69,11 +69,7 @@ describe("LDO Vue Composables", () => { expect(result.value.isFetched()).toBe(false); - await vi.waitFor(() => { - if (!result.value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result.value); expect(result.value.isFetched()).toBe(true); @@ -98,11 +94,7 @@ describe("LDO Vue Composables", () => { expect(result.value.isFetched()).toBe(false); - await vi.waitFor(() => { - if (!result.value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result.value); expect(result.value.isFetched()).toBe(true); expect(result.value.type).toEqual("SolidContainer"); @@ -113,11 +105,7 @@ describe("LDO Vue Composables", () => { expect(result.value.isFetched()).toBe(false); expect(result.value.uri).toEqual("http://localhost:3006/directory/leaf"); - await vi.waitFor(() => { - if (!result.value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result.value); expect(result.value.isFetched()).toBe(true); expect(result.value.type).toEqual("SolidLeaf"); @@ -146,11 +134,7 @@ describe("LDO Vue Composables", () => { optionsRef.value = { suppressInitialRead: false }; - await vi.waitFor(() => { - if (!result.value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result.value); expect(result.value.isFetched()).toBe(true); @@ -178,11 +162,7 @@ describe("LDO Vue Composables", () => { expect(rerenderCount.count).toEqual(0); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); expect(rerenderCount.count).toEqual(1); @@ -226,11 +206,7 @@ describe("LDO Vue Composables", () => { expect(rerenderCount.count).toEqual(0); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); expect(result[0].value.name).toEqual("Name"); expect(rerenderCount.count).toEqual(1); @@ -252,11 +228,7 @@ describe("LDO Vue Composables", () => { expect(result[0].value.name).toEqual(undefined); expect(rerenderCount.count).toEqual(3); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); expect(result[1].value.isFetched()).toBe(true); expect(result[1].value.isAbsent()).toBe(false); @@ -287,11 +259,7 @@ describe("LDO Vue Composables", () => { return [useMatchSubjectResult, useResourceResult] as const; }); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); assert(!result[1].value.isError); expect(result[1].value.isAbsent()).toBe(false); @@ -334,11 +302,7 @@ describe("LDO Vue Composables", () => { expect(result[0].value.size).toBe(0); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); expect(rerenderCount.count).toBe(2); @@ -387,11 +351,7 @@ describe("LDO Vue Composables", () => { return [useMatchObjectResult, useResourceResult] as const; }); - await vi.waitFor(() => { - if (!result[1].value.isFetched()) { - throw new Error("not fetched yet"); - } - }); + await waitForResource(result[1].value); assert(!result[1].value.isError); expect(result[1].value.isAbsent()).toBe(false); diff --git a/packages/vue/test/test-utils.ts b/packages/vue/test/test-utils.ts index 75862a4a..132570d8 100644 --- a/packages/vue/test/test-utils.ts +++ b/packages/vue/test/test-utils.ts @@ -1,3 +1,5 @@ +import type { Resource } from "@ldo/connected"; +import { vi } from "vitest"; import { createApp, type MultiWatchSources, @@ -52,3 +54,11 @@ export class RerenderCount { this._count = 0; } } + +export async function waitForResource(resource: Resource) { + await vi.waitFor(() => { + if (!resource.isFetched()) { + throw new Error("not fetched yet"); + } + }); +} From b47e714fbbdb4abb35a43222c62ba8251dfd56a5 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sun, 20 Sep 2026 23:01:22 +0200 Subject: [PATCH 10/32] Fix file extension tsx => ts --- .../vue/src/{createLdoVueMethods.tsx => createLdoVueMethods.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/vue/src/{createLdoVueMethods.tsx => createLdoVueMethods.ts} (100%) diff --git a/packages/vue/src/createLdoVueMethods.tsx b/packages/vue/src/createLdoVueMethods.ts similarity index 100% rename from packages/vue/src/createLdoVueMethods.tsx rename to packages/vue/src/createLdoVueMethods.ts From 05b1924b97f447ba408424671271f3348d2cf212 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Mon, 21 Sep 2026 05:18:09 +0200 Subject: [PATCH 11/32] Add useLdo method reexported from react --- packages/svelte/src/methods/useLdo.ts | 6 +++--- packages/vue/src/createLdoVueMethods.ts | 4 ++-- packages/vue/src/index.ts | 2 +- packages/vue/src/methods/useLdo.ts | 2 ++ packages/vue/src/methods/useMatchSubject.ts | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 packages/vue/src/methods/useLdo.ts diff --git a/packages/svelte/src/methods/useLdo.ts b/packages/svelte/src/methods/useLdo.ts index ea9ca689..5080e1f7 100644 --- a/packages/svelte/src/methods/useLdo.ts +++ b/packages/svelte/src/methods/useLdo.ts @@ -17,7 +17,7 @@ export interface UseLdoMethods { */ dataset: ConnectedLdoDataset; /** - * Retireves a representation of a Resource at the given URI. This resource + * Retrieves a representation of a Resource at the given URI. This resource * represents the current state of the resource: whether it is currently * fetched or in the process of fetching as well as some information about it. * @@ -29,7 +29,7 @@ export interface UseLdoMethods { */ getResource: ConnectedLdoDataset["getResource"]; /** - * Sets conetext for a specific plugin + * Sets context for a specific plugin * * @param pluginName - the name of the plugin * @param context - the context for this specific plugin @@ -66,7 +66,7 @@ export interface UseLdoMethods { ...additionalResources: Plugins[number]["types"]["resource"][] ): Type; /** - * Commits the data of a writable Linke Data Object back to the remote. + * Commits the data of a writable Linked Data Object back to the remote. */ commitData( input: LdoBase, diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 1a7cf44b..12824182 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -3,7 +3,7 @@ import { createConnectedLdoDataset, type ConnectedPlugin, } from "@ldo/connected"; -// import { createUseLdo } from "./methods/useLdo"; +import { createUseLdo } from "./methods/useLdo"; import { createUseMatchObject } from "./methods/useMatchObject"; import { createUseMatchSubject } from "./methods/useMatchSubject"; import { createUseResource } from "./methods/useResource"; @@ -75,7 +75,7 @@ export function createLdoVueMethods< return { dataset, // useDataset: createUseDataset(dataset), - // useLdo: createUseLdo(dataset), + useLdo: createUseLdo(dataset), useMatchObject: createUseMatchObject(dataset), useMatchSubject: createUseMatchSubject(dataset), useResource: createUseResource(dataset), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index e9de1f9a..ffc5f0c1 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,6 +1,6 @@ export * from "./createLdoVueMethods"; -// export * from "./methods/useLdo"; +export * from "./methods/useLdo"; export * from "./methods/useMatchObject"; export * from "./methods/useMatchSubject"; export * from "./methods/useResource"; diff --git a/packages/vue/src/methods/useLdo.ts b/packages/vue/src/methods/useLdo.ts new file mode 100644 index 00000000..11dc1ce4 --- /dev/null +++ b/packages/vue/src/methods/useLdo.ts @@ -0,0 +1,2 @@ +export type { UseLdoMethods } from "@ldo/react"; +export { createUseLdo } from "@ldo/react"; diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts index ce28062c..b8c3e352 100644 --- a/packages/vue/src/methods/useMatchSubject.ts +++ b/packages/vue/src/methods/useMatchSubject.ts @@ -1,4 +1,4 @@ -import type { QuadMatch, SubjectNode } from "@ldo/rdf-utils"; +import type { QuadMatch } from "@ldo/rdf-utils"; import type { ShapeType, LdoBase, LdSet } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; import type { From 6874d959c47d0e9abcc8e7b65f069e62ad27bdb1 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 22 Sep 2026 13:32:25 +0200 Subject: [PATCH 12/32] Ignore ts error that causes the example app build to fail TODO it should be fixed, otherwise people will have to ts-ignore always. --- dev/solid-vue-vite-app/src/useMatchSubjectTest.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue b/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue index c0264f11..18d69f7a 100644 --- a/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue +++ b/dev/solid-vue-vite-app/src/useMatchSubjectTest.vue @@ -2,6 +2,7 @@ import { computed, ref } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes.js"; import { useResource, useMatchSubject } from "./ldoVue.js"; +import type { FoafProfile } from "./_ldo/foafProfile.typings.js"; const isValidUrl = (str: string) => { try { @@ -53,7 +54,7 @@ useResource(validResource);
  • - {{ item.knows?.map((p) => p["@id"]).join(", ") }} + {{ item.knows?.map((p: FoafProfile) => p["@id"]).join(", ") }}
From 683c195c89b54e2b7d0365e188456b7ba9fea01b Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 25 Sep 2026 13:14:24 +0200 Subject: [PATCH 13/32] Implement useSubscribeToResource vue composable and part of tests. Also replace happy-dom with jsdom and fix Uint8Array issue because happy-dom was aggressively caching which caused testing issues. --- package-lock.json | 197 +++++++++--------- packages/vue/package.json | 5 +- packages/vue/src/createLdoVueMethods.ts | 4 +- .../vue/src/methods/useSubscribeToResource.ts | 67 ++++++ packages/vue/test/Composables.test.ts | 89 +++++++- packages/vue/vitest.config.js | 2 +- vitest.jest-compat.ts | 21 ++ 7 files changed, 282 insertions(+), 103 deletions(-) create mode 100644 packages/vue/src/methods/useSubscribeToResource.ts diff --git a/package-lock.json b/package-lock.json index c71b11c7..83485f32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18925,6 +18925,66 @@ "node": ">= 6" } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", + "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", + "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", + "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", + "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@next/swc-linux-x64-gnu": { "version": "16.2.6", "cpu": [ @@ -18953,6 +19013,36 @@ "node": ">= 10" } }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", + "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", + "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -22824,7 +22914,9 @@ "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", @@ -24473,6 +24565,8 @@ "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": "*" }, @@ -28241,6 +28335,8 @@ "integrity": "sha512-0KMb/Eh8rsd+aMNkOk5h8LkAjo9Na1aksnEGmsuZf+GXl6UkAdIi0VbUHU7d59lXhNmawODgwwuCMiZpCxwwCw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", @@ -28260,6 +28356,8 @@ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -28273,6 +28371,8 @@ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" } @@ -42111,103 +42211,10 @@ "packages/vue": { "name": "@ldo/vue", "version": "1.0.0-alpha.50", - "license": "MIT", - "devDependencies": { - "happy-dom": "^20.14.3" - } + "license": "MIT" }, "packages/wac": { "extraneous": true - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", - "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", - "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", - "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", - "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", - "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", - "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/packages/vue/package.json b/packages/vue/package.json index 03316787..4525ceb4 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -39,8 +39,5 @@ "bugs": { "url": "https://github.com/o-development/ldo/issues" }, - "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme", - "devDependencies": { - "happy-dom": "^20.14.3" - } + "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme" } diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 12824182..375069f9 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -8,7 +8,7 @@ import { createUseMatchObject } from "./methods/useMatchObject"; import { createUseMatchSubject } from "./methods/useMatchSubject"; import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; -// import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; +import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; // import { createUseLinkQuery } from "./methods/useLinkQuery"; // import { createUseDataset } from "./methods/useDataset"; // import { createUseChangeDataset } from "./methods/change/useChangeDataset"; @@ -80,7 +80,7 @@ export function createLdoVueMethods< useMatchSubject: createUseMatchSubject(dataset), useResource: createUseResource(dataset), useSubject: createUseSubject(dataset), - // useSubscribeToResource: createUseSubscribeToResource(dataset), + useSubscribeToResource: createUseSubscribeToResource(dataset), // useLinkQuery: createUseLinkQuery(dataset), // useChangeDataset: createUseChangeDataset(dataset), // useChangeSubject: createUseChangeSubject(dataset), diff --git a/packages/vue/src/methods/useSubscribeToResource.ts b/packages/vue/src/methods/useSubscribeToResource.ts new file mode 100644 index 00000000..c3df66eb --- /dev/null +++ b/packages/vue/src/methods/useSubscribeToResource.ts @@ -0,0 +1,67 @@ +import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import { onBeforeUnmount, toValue, watch, type MaybeRefOrGetter } from "vue"; + +/** + * @internal + * + * Creates a useSubscribeToResource composable + */ +export function createUseSubscribeToResource( + dataset: ConnectedLdoDataset, +) { + /** + * Starts and updates subscriptions to a list of resources + */ + return function useSubscribeToResource( + uris: MaybeRefOrGetter, + ): void { + const currentlySubscribed: Record = {}; + + watch( + () => toValue(uris), + async (newUris, oldUris) => { + const resources = newUris.map((uri) => dataset.getResource(uri)); + const previousSubscriptions = { ...currentlySubscribed }; + + await Promise.all( + resources.map(async (resource) => { + if (!previousSubscriptions[resource.uri]) { + // Prevent multiple triggers from created subscriptions while waiting + // for connection + currentlySubscribed[resource.uri] = "AWAITING"; + const result = await resource.readIfUnfetched(); + console.log(result, "****"); + currentlySubscribed[resource.uri] = + await resource.subscribeToNotifications(); + } else { + delete previousSubscriptions[resource.uri]; + } + }), + ); + + await Promise.all( + Object.entries(previousSubscriptions).map( + async ([resourceUri, subscriptionId]) => { + // Unsubscribe + delete currentlySubscribed[resourceUri]; + const resource = dataset.getResource(resourceUri); + await resource.unsubscribeFromNotifications(subscriptionId); + }, + ), + ); + }, + { deep: true, immediate: true }, // also detect edits of the array in-place + ); + + onBeforeUnmount(async () => { + await Promise.all( + Object.entries(currentlySubscribed).map( + async ([resourceUri, subscriptionId]) => { + const resource = dataset.getResource(resourceUri); + await resource.unsubscribeFromNotifications(subscriptionId); + }, + ), + ); + }); + }; +} diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index af022779..924efa06 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeEach, expect } from "vitest"; +import { describe, it, beforeEach, expect, vi } from "vitest"; import { createLdoVueMethods, type UseResourceOptions } from "../src/index"; import { type SolidConnectedPlugin, @@ -13,6 +13,34 @@ import { nextTick, type Ref, ref } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes"; import { BasicLdSet } from "@ldo/jsonld-dataset-proxy"; +// Only overwrite this if we are in test mode. This is an env var we happen to have in our repo +if (process.env.VITE_IS_TEST === "true") { + class ESBuildAndJSDOMCompatibleTextEncoder extends TextEncoder { + constructor() { + super(); + } + + encode(input: string) { + if (typeof input !== "string") { + throw new TypeError("`input` must be a string"); + } + + const decodedURI = decodeURIComponent(encodeURIComponent(input)); + const arr = new Uint8Array(decodedURI.length); + const chars = decodedURI.split(""); + for (let i = 0; i < chars.length; i++) { + arr[i] = decodedURI[i].charCodeAt(0); + } + return arr; + } + } + + Object.defineProperty(global, "TextEncoder", { + value: ESBuildAndJSDOMCompatibleTextEncoder, + writable: true, + }); +} + describe("LDO Vue Composables", () => { let methods: ReturnType>; const s = setupServer(3006, { @@ -374,4 +402,63 @@ describe("LDO Vue Composables", () => { it.todo("should change the LDO set when predicate changes"); it.todo("should change the LDO set when graph changes"); }); + + describe("useSubscribeToResource", () => { + const url = "http://localhost:3006/directory/person"; + const subject = url + "#me"; + it("should subscribe to resource changes (detect resource changes)", async () => { + // subscribe + const [result, app] = withSetup( + () => + [ + methods.useSubscribeToResource([url]), + methods.useSubject(FoafProfileShapeType, subject), + ] as const, + ); + + void result[1].value.name; + const rerenderCount = new RerenderCount(result[1]); + + // change the resource + const resource = methods.dataset.getResource(url); + + await vi.waitFor(() => { + expect(resource.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.isSubscribedToNotifications()).toBe(true); + }); + // check that the change has been detected + rerenderCount.clearCount(); + const changeResult = await s.authFetch(url, { + method: "PATCH", + headers: { "content-type": "text/n3" }, + body: ` + @prefix solid: . + @prefix foaf: . + + _:patch a solid:InsertDeletePatch; + solid:inserts { <#me> foaf:name "NEW NAME". }; + solid:deletes { <#me> foaf:name ?name. }; + solid:where { <#me> foaf:name ?name. }.`, + }); + expect(changeResult.ok).toBe(true); + await nextTick(); + // refetch should be going on + await vi.waitFor(() => { + expect(resource.isLoading()).toBe(true); + }); + // and then done + await vi.waitFor(() => expect(resource.isLoading()).toBe(false)); + expect(resource.isError).toBe(false); + // and expect refreshed data + expect(result[1].value.name).toEqual("NEW NAME"); + await nextTick(); + // expect(rerenderCount.count).toEqual(1); + + app.unmount(); + }); + it.todo("should unsubscribe from resources removed", async () => {}); + it.todo("should unsubscribe from resources when finished", async () => {}); + }); }); diff --git a/packages/vue/vitest.config.js b/packages/vue/vitest.config.js index bf5c605a..cc93abb3 100644 --- a/packages/vue/vitest.config.js +++ b/packages/vue/vitest.config.js @@ -24,6 +24,6 @@ export default defineConfig({ coverage: { provider: "istanbul", }, - environment: "happy-dom", + environment: "jsdom", }, }); diff --git a/vitest.jest-compat.ts b/vitest.jest-compat.ts index 130a1690..4c9922e0 100644 --- a/vitest.jest-compat.ts +++ b/vitest.jest-compat.ts @@ -2,3 +2,24 @@ import { vi } from "vitest"; // Keep legacy Jest test helpers working during migration. globalThis.jest = vi as unknown as Record; + +// vitest and jsdom don't play well together +// browser and Node have different Uint8Array instances, leading to error in Jose library. +// here, we loosen the instanceof Uint8Array check. +if (typeof globalThis !== "undefined") { + // Keep Node's Uint8Array for reference + const NodeUint8Array = globalThis.__node_uint8array__ ?? Uint8Array; + globalThis.__node_uint8array__ ??= NodeUint8Array; + + // overwrite the instanceof check with a more loose check so both instances pass the comparison + Object.defineProperty(NodeUint8Array, Symbol.hasInstance, { + value(instance: unknown) { + if (!instance) return false; + return ( + instance.constructor?.name === "Uint8Array" || + Object.prototype.toString.call(instance) === "[object Uint8Array]" + ); + }, + configurable: true, + }); +} From 3bc5c663e4e678efdecc10618475eb00ba14dca9 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Fri, 25 Sep 2026 20:50:20 +0200 Subject: [PATCH 14/32] Test that useSubscribeToResource unsubscribes resources properly --- .../vue/src/methods/useSubscribeToResource.ts | 2 +- packages/vue/test/Composables.test.ts | 59 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/vue/src/methods/useSubscribeToResource.ts b/packages/vue/src/methods/useSubscribeToResource.ts index c3df66eb..6f51f2ae 100644 --- a/packages/vue/src/methods/useSubscribeToResource.ts +++ b/packages/vue/src/methods/useSubscribeToResource.ts @@ -19,7 +19,7 @@ export function createUseSubscribeToResource( watch( () => toValue(uris), - async (newUris, oldUris) => { + async (newUris) => { const resources = newUris.map((uri) => dataset.getResource(uri)); const previousSubscriptions = { ...currentlySubscribed }; diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 924efa06..fd71cc2e 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -458,7 +458,62 @@ describe("LDO Vue Composables", () => { app.unmount(); }); - it.todo("should unsubscribe from resources removed", async () => {}); - it.todo("should unsubscribe from resources when finished", async () => {}); + + it("should unsubscribe from resources removed", async () => { + const urlRef = ref([url]); + // subscribe + const [, app] = withSetup( + () => + [ + methods.useSubscribeToResource(urlRef), + methods.useSubject(FoafProfileShapeType, subject), + ] as const, + ); + + const resource = methods.dataset.getResource(url); + await vi.waitFor(() => { + expect(resource.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.isSubscribedToNotifications()).toBe(true); + }); + + // remove resource from subscription + urlRef.value = []; + + // and check that it's not subscribed, eventually + await vi.waitFor(() => { + expect(resource.isSubscribedToNotifications()).toBe(false); + }); + + app.unmount(); + }); + + it("should unsubscribe from resources when finished", async () => { + // subscribe + const [, app] = withSetup( + () => + [ + methods.useSubscribeToResource([url]), + methods.useSubject(FoafProfileShapeType, subject), + ] as const, + ); + + const resource = methods.dataset.getResource(url); + await vi.waitFor(() => { + expect(resource.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.isSubscribedToNotifications()).toBe(true); + }); + + // unmount + app.unmount(); + + // and check that the resource is not subscribed, eventually + await vi.waitFor(() => { + expect(resource.isSubscribedToNotifications()).toBe(false); + }); + }); }); }); From 9fd15ff06bba60b048cb9f29656eaee553d4ce6e Mon Sep 17 00:00:00 2001 From: mrkvon Date: Mon, 28 Sep 2026 11:15:41 +0200 Subject: [PATCH 15/32] Implement useLinkQuery and fix useTrackingProxy and its dependents --- .../Websocket2023NotificationSubscription.ts | 7 +- .../src/linkTraversal/ResourceLinkQuery.ts | 8 +-- packages/vue/src/createLdoVueMethods.ts | 4 +- packages/vue/src/index.ts | 4 +- packages/vue/src/methods/useLinkQuery.ts | 57 +++++++++++++++ packages/vue/src/methods/useMatchObject.ts | 21 +++--- packages/vue/src/methods/useMatchSubject.ts | 23 +++---- packages/vue/src/methods/useResource.ts | 4 +- packages/vue/src/methods/useSubject.ts | 26 +++---- packages/vue/src/util/useTrackingProxy.ts | 69 ++++++++++++++----- packages/vue/test/Composables.test.ts | 69 +++++++++++++++---- 11 files changed, 215 insertions(+), 77 deletions(-) create mode 100644 packages/vue/src/methods/useLinkQuery.ts diff --git a/packages/connected-solid/src/notifications/Websocket2023NotificationSubscription.ts b/packages/connected-solid/src/notifications/Websocket2023NotificationSubscription.ts index 22710130..b7ffc5fa 100644 --- a/packages/connected-solid/src/notifications/Websocket2023NotificationSubscription.ts +++ b/packages/connected-solid/src/notifications/Websocket2023NotificationSubscription.ts @@ -22,7 +22,7 @@ const CHANNEL_TYPE = /** * @internal - * An implementation of the Websocket2023 notifiction spec + * An implementation of the Websocket2023 notification spec */ export class Websocket2023NotificationSubscription extends NotificationSubscription< SolidConnectedPlugin, @@ -106,7 +106,10 @@ export class Websocket2023NotificationSubscription extends NotificationSubscript this.socket.onerror = (err) => { this.onNotificationError( - new UnexpectedResourceError(this.resource, err.error), + new UnexpectedResourceError( + this.resource, + err.error ?? new Error("WebSocket error"), + ), ); }; return; diff --git a/packages/connected/src/linkTraversal/ResourceLinkQuery.ts b/packages/connected/src/linkTraversal/ResourceLinkQuery.ts index 5567b610..bbdf96b6 100644 --- a/packages/connected/src/linkTraversal/ResourceLinkQuery.ts +++ b/packages/connected/src/linkTraversal/ResourceLinkQuery.ts @@ -15,7 +15,7 @@ import type { nodeEventListener } from "@ldo/subscribable-dataset"; import type { Quad } from "@rdfjs/types"; /** - * Represents a query over multiple datasources and constituting muliple + * Represents a query over multiple datasources and constituting multiple * resources. * * @example @@ -36,7 +36,7 @@ import type { Quad } from "@rdfjs/types"; * }, * } * ); - * // Susbscribe to this link query, automaticically updating the dataset when + * // Subscribe to this link query, automatically updating the dataset when * // something from the link query is changed. * await linkQuery.subscribe(); * ``` @@ -102,7 +102,7 @@ export class ResourceLinkQuery< * }, * } * ); - * // Susbscribe to this link query, automaticically updating the dataset when + * // Subscribe to this link query, automatically updating the dataset when * // something from the link query is changed. * const result = await linkQuery.read(); * console.log(result.name); @@ -149,7 +149,7 @@ export class ResourceLinkQuery< * }, * } * ); - * // Susbscribe to this link query, automaticically updating the dataset when + * // Subscribe to this link query, automatically updating the dataset when * // something from the link query is changed. * const unsubscribeId = await linkQuery.subscribe(); * diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 375069f9..5ace1a31 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -9,7 +9,7 @@ import { createUseMatchSubject } from "./methods/useMatchSubject"; import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; -// import { createUseLinkQuery } from "./methods/useLinkQuery"; +import { createUseLinkQuery } from "./methods/useLinkQuery"; // import { createUseDataset } from "./methods/useDataset"; // import { createUseChangeDataset } from "./methods/change/useChangeDataset"; // import { createUseChangeSubject } from "./methods/change/useChangeSubject"; @@ -81,7 +81,7 @@ export function createLdoVueMethods< useResource: createUseResource(dataset), useSubject: createUseSubject(dataset), useSubscribeToResource: createUseSubscribeToResource(dataset), - // useLinkQuery: createUseLinkQuery(dataset), + useLinkQuery: createUseLinkQuery(dataset), // useChangeDataset: createUseChangeDataset(dataset), // useChangeSubject: createUseChangeSubject(dataset), // useChangeMatchObject: createUseChangeMatchObject(dataset), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index ffc5f0c1..4a2e4615 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -5,8 +5,8 @@ export * from "./methods/useMatchObject"; export * from "./methods/useMatchSubject"; export * from "./methods/useResource"; export * from "./methods/useSubject"; -// export * from "./methods/useSubscribeToResource"; -// export * from "./methods/useLinkQuery"; +export * from "./methods/useSubscribeToResource"; +export * from "./methods/useLinkQuery"; // export * from "./methods/change/types"; // export * from "./methods/change/useChangeDataset"; diff --git a/packages/vue/src/methods/useLinkQuery.ts b/packages/vue/src/methods/useLinkQuery.ts new file mode 100644 index 00000000..4893d26f --- /dev/null +++ b/packages/vue/src/methods/useLinkQuery.ts @@ -0,0 +1,57 @@ +import type { + ConnectedLdoDataset, + ConnectedPlugin, + LQInput, +} from "@ldo/connected"; +import type { LdoBase, ShapeType } from "@ldo/ldo"; +import type { SubjectNode } from "@ldo/rdf-utils"; +import { createUseSubject } from "@ldo/vue"; +import { type MaybeRefOrGetter, toValue, watch } from "vue"; + +export function createUseLinkQuery( + dataset: ConnectedLdoDataset, +) { + return function useLinkQuery< + Type extends LdoBase, + QueryInput extends LQInput, + >( + shapeType: MaybeRefOrGetter>, + startingResource: MaybeRefOrGetter, + startingSubject: MaybeRefOrGetter, + linkQuery: MaybeRefOrGetter, + ) { + watch( + [ + () => toValue(shapeType), + () => toValue(startingResource), + () => toValue(startingSubject), + () => toValue(linkQuery), + ], + async ( + [shapeType, startingResourceUri, startingSubjectUri, linkQuery], + _, + onCleanup, + ) => { + let finished = false; + const resource = dataset.getResource(startingResourceUri); + const resourceLinkQuery = dataset + .usingType(shapeType) + .startLinkQuery(resource, startingSubjectUri, linkQuery); + + await resourceLinkQuery.subscribe(); + // if already cleaned up, unsubscribe immediately + if (finished) { + await resourceLinkQuery.unsubscribeAll(); + } + + onCleanup(async () => { + finished = true; + await resourceLinkQuery.unsubscribeAll(); + }); + }, + { immediate: true }, + ); + + return createUseSubject(dataset)(shapeType, startingSubject); + }; +} diff --git a/packages/vue/src/methods/useMatchObject.ts b/packages/vue/src/methods/useMatchObject.ts index c3ab5434..e6c3fb75 100644 --- a/packages/vue/src/methods/useMatchObject.ts +++ b/packages/vue/src/methods/useMatchObject.ts @@ -1,5 +1,5 @@ import type { QuadMatch } from "@ldo/rdf-utils"; -import type { ShapeType, LdoBase, LdSet } from "@ldo/ldo"; +import type { ShapeType, LdoBase, LdSet, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; import type { ConnectedLdoDataset, @@ -25,24 +25,25 @@ export function createUseMatchObject( * Triggers a rerender if the data are updated. */ return function useMatchObject( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, subject?: MaybeRefOrGetter, predicate?: MaybeRefOrGetter, graph?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref> { - return computed(() => { + const createLdo = computed(() => { const subjectValue = toValue(subject); const predicateValue = toValue(predicate); const graphValue = toValue(graph); - const observedOptions = toValue(options); - return useTrackingProxy( - shapeType, - (builder) => - builder.matchObject(subjectValue, predicateValue, graphValue), - observedOptions?.dataset ?? dataset, - ).value; + return (builder: LdoBuilder) => + builder.matchObject(subjectValue, predicateValue, graphValue); }); + + return useTrackingProxy( + shapeType, + createLdo, + () => toValue(options)?.dataset ?? dataset, + ); }; } diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts index b8c3e352..ad57ddc0 100644 --- a/packages/vue/src/methods/useMatchSubject.ts +++ b/packages/vue/src/methods/useMatchSubject.ts @@ -1,5 +1,5 @@ import type { QuadMatch } from "@ldo/rdf-utils"; -import type { ShapeType, LdoBase, LdSet } from "@ldo/ldo"; +import type { ShapeType, LdoBase, LdSet, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; import type { ConnectedLdoDataset, @@ -25,25 +25,24 @@ export function createUseMatchSubject( * Triggers a rerender if the data are updated. */ return function useMatchSubject( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, predicate?: MaybeRefOrGetter, object?: MaybeRefOrGetter, graph?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref> { - return computed(() => { + const createLdo = computed(() => { const predicateValue = toValue(predicate); const objectValue = toValue(object); const graphValue = toValue(graph); - const observedOptions = toValue(options); - console.log(predicateValue, objectValue, graphValue); - - return useTrackingProxy( - shapeType, - (builder) => - builder.matchSubject(predicateValue, objectValue, graphValue), - observedOptions?.dataset ?? dataset, - ).value; + return (builder: LdoBuilder) => + builder.matchSubject(predicateValue, objectValue, graphValue); }); + + return useTrackingProxy( + shapeType, + createLdo, + () => toValue(options)?.dataset ?? dataset, + ); }; } diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index c6b14749..9d9dd356 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -71,9 +71,7 @@ export function createUseResource( watch( [() => toValue(uri), () => toValue(options)], - async ([newUri, newOptions], [oldUri, oldOptions], onCleanup) => { - // console.log(newUri, oldUri); - + async ([newUri, newOptions], _, onCleanup) => { // if (newUri === oldUri && newOptions === oldOptions) return; // TODO handle change in uri vs options more carefully diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts index f37a0fdb..837bf2c5 100644 --- a/packages/vue/src/methods/useSubject.ts +++ b/packages/vue/src/methods/useSubject.ts @@ -1,5 +1,5 @@ import type { SubjectNode } from "@ldo/rdf-utils"; -import type { ShapeType, LdoBase } from "@ldo/ldo"; +import type { ShapeType, LdoBase, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; import type { ConnectedLdoDataset, @@ -14,17 +14,17 @@ export interface UseSubjectOptions { export type useSubjectType = { ( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, subject: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref; ( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref; ( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref; @@ -43,20 +43,20 @@ export function createUseSubject( * Triggers a rerender if the data are updated. */ return function useSubject( - shapeType: ShapeType, + shapeType: MaybeRefOrGetter>, subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref { - return computed(() => { + const createLdo = computed(() => { const subjectValue = toValue(subject); - const observedOptions = toValue(options); if (!subjectValue) return undefined; - - return useTrackingProxy( - shapeType, - (builder) => builder.fromSubject(subjectValue), - observedOptions?.dataset ?? dataset, - ).value; + return (builder: LdoBuilder) => builder.fromSubject(subjectValue); }); + + return useTrackingProxy( + shapeType, + createLdo, + () => toValue(options)?.dataset ?? dataset, + ); }; } diff --git a/packages/vue/src/util/useTrackingProxy.ts b/packages/vue/src/util/useTrackingProxy.ts index a0af0729..d8bb7c48 100644 --- a/packages/vue/src/util/useTrackingProxy.ts +++ b/packages/vue/src/util/useTrackingProxy.ts @@ -1,36 +1,73 @@ import type { LdoBuilder } from "@ldo/ldo"; import type { LdoBase, LdoDataset, ShapeType } from "@ldo/ldo"; import { createTrackingProxyBuilder } from "@ldo/connected"; -import { onBeforeUnmount, type Ref, shallowRef, triggerRef } from "vue"; +import { + type MaybeRefOrGetter, + type Ref, + shallowRef, + toValue, + triggerRef, + watch, +} from "vue"; /** * @internal * * A composable for tracking proxies. - * - * TODO it doesn't update when createLdo, shapeType or dataset reference change */ export function useTrackingProxy( - shapeType: ShapeType, - createLdo: (builder: LdoBuilder) => ReturnType, - dataset: LdoDataset, -): Ref { - console.log("running useTrackingProxy"); - - const linkedDataObject = shallowRef() as Ref; + shapeType: MaybeRefOrGetter>, + createLdo: MaybeRefOrGetter<(builder: LdoBuilder) => ReturnType>, + dataset: MaybeRefOrGetter, +): Ref; +export function useTrackingProxy( + shapeType: MaybeRefOrGetter>, + createLdo: MaybeRefOrGetter, + dataset: MaybeRefOrGetter, +): Ref; +export function useTrackingProxy( + shapeType: MaybeRefOrGetter>, + createLdo: MaybeRefOrGetter< + ((builder: LdoBuilder) => ReturnType) | undefined + >, + dataset: MaybeRefOrGetter, +): Ref; +export function useTrackingProxy( + shapeType: MaybeRefOrGetter>, + createLdo: MaybeRefOrGetter< + ((builder: LdoBuilder) => ReturnType) | undefined + >, + dataset: MaybeRefOrGetter, +): Ref { + const linkedDataObject = shallowRef() as Ref< + ReturnType | undefined + >; const forceUpdate = () => { triggerRef(linkedDataObject); console.log("TRIGGERING REF"); }; - const builder = createTrackingProxyBuilder(dataset, shapeType, forceUpdate); - linkedDataObject.value = createLdo(builder); + watch( + [ + () => toValue(shapeType), + () => toValue(createLdo), + () => toValue(dataset), + ], + ([currentShapeType, currentCreateLdo, currentDataset], _, onCleanup) => { + onCleanup(() => { + currentDataset.removeListenerFromAllEvents(forceUpdate); + }); - // cleanup - onBeforeUnmount(() => { - dataset.removeListenerFromAllEvents(forceUpdate); - }); + const builder = createTrackingProxyBuilder( + currentDataset, + currentShapeType, + forceUpdate, + ); + linkedDataObject.value = currentCreateLdo?.(builder); + }, + { immediate: true }, + ); return linkedDataObject; } diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index fd71cc2e..c1344ee0 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -43,6 +43,10 @@ if (process.env.VITE_IS_TEST === "true") { describe("LDO Vue Composables", () => { let methods: ReturnType>; + const personResourceUri = "http://localhost:3006/directory/person"; + const personUri = personResourceUri + "#me"; + const person2ResourceUri = "http://localhost:3006/directory/person2"; + const person2Uri = person2ResourceUri + "#me"; const s = setupServer(3006, { slug: "directory/", isContainer: true, @@ -73,7 +77,7 @@ describe("LDO Vue Composables", () => { isContainer: false, data: ` @prefix foaf: . - <#me> a foaf:Person; foaf:name "Other Name". + <#me> a foaf:Person; foaf:name "Other Name"; foaf:knows <./person#me>. `, mimeType: "text/turtle", }, @@ -404,15 +408,13 @@ describe("LDO Vue Composables", () => { }); describe("useSubscribeToResource", () => { - const url = "http://localhost:3006/directory/person"; - const subject = url + "#me"; it("should subscribe to resource changes (detect resource changes)", async () => { // subscribe const [result, app] = withSetup( () => [ - methods.useSubscribeToResource([url]), - methods.useSubject(FoafProfileShapeType, subject), + methods.useSubscribeToResource([personResourceUri]), + methods.useSubject(FoafProfileShapeType, personUri), ] as const, ); @@ -420,7 +422,7 @@ describe("LDO Vue Composables", () => { const rerenderCount = new RerenderCount(result[1]); // change the resource - const resource = methods.dataset.getResource(url); + const resource = methods.dataset.getResource(personResourceUri); await vi.waitFor(() => { expect(resource.isFetched()).toBe(true); @@ -430,7 +432,7 @@ describe("LDO Vue Composables", () => { }); // check that the change has been detected rerenderCount.clearCount(); - const changeResult = await s.authFetch(url, { + const changeResult = await s.authFetch(personResourceUri, { method: "PATCH", headers: { "content-type": "text/n3" }, body: ` @@ -460,17 +462,17 @@ describe("LDO Vue Composables", () => { }); it("should unsubscribe from resources removed", async () => { - const urlRef = ref([url]); + const urlRef = ref([personResourceUri]); // subscribe const [, app] = withSetup( () => [ methods.useSubscribeToResource(urlRef), - methods.useSubject(FoafProfileShapeType, subject), + methods.useSubject(FoafProfileShapeType, personUri), ] as const, ); - const resource = methods.dataset.getResource(url); + const resource = methods.dataset.getResource(personResourceUri); await vi.waitFor(() => { expect(resource.isFetched()).toBe(true); }); @@ -494,12 +496,12 @@ describe("LDO Vue Composables", () => { const [, app] = withSetup( () => [ - methods.useSubscribeToResource([url]), - methods.useSubject(FoafProfileShapeType, subject), + methods.useSubscribeToResource([personResourceUri]), + methods.useSubject(FoafProfileShapeType, personUri), ] as const, ); - const resource = methods.dataset.getResource(url); + const resource = methods.dataset.getResource(personResourceUri); await vi.waitFor(() => { expect(resource.isFetched()).toBe(true); }); @@ -516,4 +518,45 @@ describe("LDO Vue Composables", () => { }); }); }); + + describe("useLinkQuery", () => { + it("should resolve the query", async () => { + const [result, app] = withSetup(() => { + const result = methods.useLinkQuery( + FoafProfileShapeType, + person2ResourceUri, + person2Uri, + { name: true, knows: { name: true, "@id": true } }, + ); + + return result; + }); + + const person2Resource = methods.dataset.getResource(person2ResourceUri); + const personResource = methods.dataset.getResource(personResourceUri); + + await vi.waitFor(() => { + expect(person2Resource.isFetched()).toBe(true); + expect(personResource.isFetched()).toBe(true); + }); + + // test result + expect(result.value.knows?.map((k) => k.name)).toEqual(["Name"]); + + // wait for resources to be subscribed to notifications + await vi.waitFor(() => { + expect(person2Resource.isSubscribedToNotifications()).toBe(true); + expect(personResource.isSubscribedToNotifications()).toBe(true); + }); + + app.unmount(); + + // wait for the resources to be unsubscribed from notifications + await vi.waitFor(() => { + expect(person2Resource.isSubscribedToNotifications()).toBe(false); + expect(personResource.isSubscribedToNotifications()).toBe(false); + }); + }); + it.todo("should update the query when a resource changes"); + }); }); From be26d676eac4e6910c39f31caba83e40a99cceb6 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 29 Sep 2026 09:33:52 +0200 Subject: [PATCH 16/32] Implement useChangeDataset, and test --- packages/vue/src/createLdoVueMethods.ts | 3 +- packages/vue/src/index.ts | 2 +- .../src/methods/change/useChangeDataset.ts | 53 +++++++++++ .../vue/src/methods/useSubscribeToResource.ts | 3 +- packages/vue/test/Composables.test.ts | 91 +++++++++++++++++-- 5 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 packages/vue/src/methods/change/useChangeDataset.ts diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 5ace1a31..17286c3e 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -10,6 +10,7 @@ import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; import { createUseLinkQuery } from "./methods/useLinkQuery"; +import { createUseChangeDataset } from "./methods/change/useChangeDataset.js"; // import { createUseDataset } from "./methods/useDataset"; // import { createUseChangeDataset } from "./methods/change/useChangeDataset"; // import { createUseChangeSubject } from "./methods/change/useChangeSubject"; @@ -82,7 +83,7 @@ export function createLdoVueMethods< useSubject: createUseSubject(dataset), useSubscribeToResource: createUseSubscribeToResource(dataset), useLinkQuery: createUseLinkQuery(dataset), - // useChangeDataset: createUseChangeDataset(dataset), + useChangeDataset: createUseChangeDataset(dataset), // useChangeSubject: createUseChangeSubject(dataset), // useChangeMatchObject: createUseChangeMatchObject(dataset), // useChangeMatchSubject: createUseChangeMatchSubject(dataset), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 4a2e4615..73c0d834 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -9,7 +9,7 @@ export * from "./methods/useSubscribeToResource"; export * from "./methods/useLinkQuery"; // export * from "./methods/change/types"; -// export * from "./methods/change/useChangeDataset"; +export * from "./methods/change/useChangeDataset"; // export * from "./methods/change/useChangeMatchObject"; // export * from "./methods/change/useChangeMatchSubject"; // export * from "./methods/change/useChangeSubject"; diff --git a/packages/vue/src/methods/change/useChangeDataset.ts b/packages/vue/src/methods/change/useChangeDataset.ts new file mode 100644 index 00000000..20af5c6b --- /dev/null +++ b/packages/vue/src/methods/change/useChangeDataset.ts @@ -0,0 +1,53 @@ +import type { + ConnectedLdoDataset, + ConnectedLdoTransactionDataset, + ConnectedPlugin, +} from "@ldo/connected"; +import { + type MaybeRefOrGetter, + toValue, + shallowRef, + watch, + type Ref, +} from "vue"; + +export function createUseChangeDataset( + dataset: ConnectedLdoDataset, +) { + return function useChangeDataset( + specificDataset?: MaybeRefOrGetter>, + ) { + const transactionDataset = shallowRef< + ConnectedLdoTransactionDataset + >() as Ref>; + + watch( + () => toValue(specificDataset), + (newDataset) => { + transactionDataset.value = (newDataset ?? dataset).startTransaction(); + }, + { deep: false, immediate: true }, + ); + + const setData = ( + changer: (toChange: ConnectedLdoTransactionDataset) => void, + ) => { + const subTransaction = transactionDataset.value.startTransaction(); + changer(subTransaction); + subTransaction.commit(); + }; + + const commitData = async () => { + const result = await transactionDataset.value.commitToRemote(); + if (!result.isError) { + // Replace with a new transaction from the dataset or specificDataset + transactionDataset.value = ( + toValue(specificDataset) ?? dataset + ).startTransaction(); + } + return result; + }; + + return { transactionDataset, setData, commitData }; + }; +} diff --git a/packages/vue/src/methods/useSubscribeToResource.ts b/packages/vue/src/methods/useSubscribeToResource.ts index 6f51f2ae..1a449ffb 100644 --- a/packages/vue/src/methods/useSubscribeToResource.ts +++ b/packages/vue/src/methods/useSubscribeToResource.ts @@ -29,8 +29,7 @@ export function createUseSubscribeToResource( // Prevent multiple triggers from created subscriptions while waiting // for connection currentlySubscribed[resource.uri] = "AWAITING"; - const result = await resource.readIfUnfetched(); - console.log(result, "****"); + const _result = await resource.readIfUnfetched(); currentlySubscribed[resource.uri] = await resource.subscribeToNotifications(); } else { diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index c1344ee0..19f78fbd 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -12,6 +12,8 @@ import { setupServer } from "@ldo/test-solid-server"; import { nextTick, type Ref, ref } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes"; import { BasicLdSet } from "@ldo/jsonld-dataset-proxy"; +import { literal, namedNode, quad } from "@ldo/rdf-utils"; +import { ConnectedLdoTransactionDataset } from "@ldo/connected"; // Only overwrite this if we are in test mode. This is an env var we happen to have in our repo if (process.env.VITE_IS_TEST === "true") { @@ -521,16 +523,14 @@ describe("LDO Vue Composables", () => { describe("useLinkQuery", () => { it("should resolve the query", async () => { - const [result, app] = withSetup(() => { - const result = methods.useLinkQuery( + const [result, app] = withSetup(() => + methods.useLinkQuery( FoafProfileShapeType, person2ResourceUri, person2Uri, { name: true, knows: { name: true, "@id": true } }, - ); - - return result; - }); + ), + ); const person2Resource = methods.dataset.getResource(person2ResourceUri); const personResource = methods.dataset.getResource(personResourceUri); @@ -551,7 +551,7 @@ describe("LDO Vue Composables", () => { app.unmount(); - // wait for the resources to be unsubscribed from notifications + // wait for the resources to be unsubscribed from notifications after unmount await vi.waitFor(() => { expect(person2Resource.isSubscribedToNotifications()).toBe(false); expect(personResource.isSubscribedToNotifications()).toBe(false); @@ -559,4 +559,81 @@ describe("LDO Vue Composables", () => { }); it.todo("should update the query when a resource changes"); }); + + describe("useChangeDataset", () => { + it("should accept and commit changes to a default dataset", async () => { + const [results, app] = withSetup(() => { + const changeDataset = methods.useChangeDataset(); + const resource = methods.useResource(personResourceUri); + methods.useSubscribeToResource([personResourceUri]); + + return { changeDataset, resource }; + }); + + expect(results.changeDataset.transactionDataset.value).toBeInstanceOf( + ConnectedLdoTransactionDataset, + ); + + // make sure the resource is fetched before proceeding + await vi.waitFor(() => { + expect(results.resource.value.isFetched()).toBe(true); + }); + // make sure resource will update + await vi.waitFor(() => { + expect(results.resource.value.isSubscribedToNotifications()).toBe(true); + }); + + const foafName = namedNode("http://xmlns.com/foaf/0.1/name"); + + // change person's name + results.changeDataset.setData((dataset) => { + dataset.deleteMatches(namedNode(personUri), foafName); + dataset.add( + quad( + namedNode(personUri), + foafName, + literal("New Saved Name"), + namedNode(personResourceUri), + ), + ); + }); + + expect( + results.changeDataset.transactionDataset.value.getChanges().added?.size, + ).toEqual(1); + expect( + results.changeDataset.transactionDataset.value.getChanges().removed + ?.size, + ).toEqual(1); + const result = await results.changeDataset.commitData(); + expect(result.isError).toBe(false); + // transaction dataset should be reset at this point + expect( + results.changeDataset.transactionDataset.value.getChanges(), + ).toEqual({}); + + // the resource should reload (thanks to subscription) + await vi.waitFor(() => { + expect(results.resource.value.isLoading()).toBe(true); + }); + await vi.waitFor(() => { + expect(results.resource.value.isLoading()).toBe(false); + }); + + const names = methods.dataset.match(namedNode(personUri), foafName); + + // and the name should be updated + expect(names.size).toBe(1); + expect(names.toArray()[0].object.value).toBe("New Saved Name"); + + app.unmount(); + + // let's just wait for unsubscribing at the end + await vi.waitFor(() => { + expect(results.resource.value.isLoading()).toBe(false); + }); + }); + + it.todo("should handle changes to custom dataset"); + }); }); From 96f12f8e14e1d1ff3be2acf6fe542a4f3c07c6b9 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Wed, 30 Sep 2026 04:26:56 +0200 Subject: [PATCH 17/32] Implement useChangeSubject based on react version --- .../src/types/IConnectedLdoDataset.ts | 11 +-- packages/vue/src/createLdoVueMethods.ts | 5 +- packages/vue/src/index.ts | 2 +- .../src/methods/change/useChangeDataset.ts | 8 ++- .../src/methods/change/useChangeSubject.ts | 71 +++++++++++++++++++ packages/vue/src/methods/useDataset.ts | 4 +- packages/vue/src/methods/useLinkQuery.ts | 4 +- packages/vue/src/methods/useMatchObject.ts | 8 +-- packages/vue/src/methods/useMatchSubject.ts | 8 +-- packages/vue/src/methods/useResource.ts | 4 +- packages/vue/src/methods/useSubject.ts | 14 ++-- .../vue/src/methods/useSubscribeToResource.ts | 4 +- packages/vue/test/Composables.test.ts | 34 ++++++++- 13 files changed, 137 insertions(+), 40 deletions(-) create mode 100644 packages/vue/src/methods/change/useChangeSubject.ts diff --git a/packages/connected/src/types/IConnectedLdoDataset.ts b/packages/connected/src/types/IConnectedLdoDataset.ts index b4e545c5..d904084a 100644 --- a/packages/connected/src/types/IConnectedLdoDataset.ts +++ b/packages/connected/src/types/IConnectedLdoDataset.ts @@ -3,6 +3,7 @@ import type { LdoBase, LdoDataset, ShapeType } from "@ldo/ldo"; import type { ConnectedPlugin } from "./ConnectedPlugin"; import type { InvalidIdentifierResource } from "../InvalidIdentifierResource"; import type { IConnectedLdoBuilder } from "./IConnectedLdoBuilder"; +import type { ConnectedLdoTransactionDataset } from "../ConnectedLdoTransactionDataset"; export type ReturnTypeFromArgs = Func extends ( arg: Arg, @@ -54,7 +55,7 @@ export type GetResourceReturnType< export interface IConnectedLdoDataset extends LdoDataset { /** - * Retireves a representation of a Resource at the given URI. This resource + * Retrieves a representation of a Resource at the given URI. This resource * represents the current state of the resource: whether it is currently * fetched or in the process of fetching as well as some information about it. * @@ -80,7 +81,7 @@ export interface IConnectedLdoDataset ): GetResourceReturnType; /** - * Retireves a representation of all Resources referenced by this dataset + * Retrieves a representation of all Resources referenced by this dataset * This does not necessarily mean that it's been fetched (use the * `getFetchedResources` method for that). It simply means that at one point * it was referenced. @@ -95,7 +96,7 @@ export interface IConnectedLdoDataset getResources(): Plugins[number]["types"]["resource"][]; /** - * Retireves a representation of all Resources that have been fetched. + * Retrieves a representation of all Resources that have been fetched. * * @returns a Resource array * @@ -152,7 +153,7 @@ export interface IConnectedLdoDataset forgetAllResources(): void; /** - * Sets conetext for a specific plugin + * Sets context for a specific plugin * * @param pluginName - the name of the plugin * @param context - the context for this specific plugin @@ -168,4 +169,6 @@ export interface IConnectedLdoDataset usingType( shapeType: ShapeType, ): IConnectedLdoBuilder; + + startTransaction(): ConnectedLdoTransactionDataset; } diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 17286c3e..f48bd78c 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -10,7 +10,8 @@ import { createUseResource } from "./methods/useResource"; import { createUseSubject } from "./methods/useSubject"; import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; import { createUseLinkQuery } from "./methods/useLinkQuery"; -import { createUseChangeDataset } from "./methods/change/useChangeDataset.js"; +import { createUseChangeDataset } from "./methods/change/useChangeDataset"; +import { createUseChangeSubject } from "./methods/change/useChangeSubject"; // import { createUseDataset } from "./methods/useDataset"; // import { createUseChangeDataset } from "./methods/change/useChangeDataset"; // import { createUseChangeSubject } from "./methods/change/useChangeSubject"; @@ -84,7 +85,7 @@ export function createLdoVueMethods< useSubscribeToResource: createUseSubscribeToResource(dataset), useLinkQuery: createUseLinkQuery(dataset), useChangeDataset: createUseChangeDataset(dataset), - // useChangeSubject: createUseChangeSubject(dataset), + useChangeSubject: createUseChangeSubject(dataset), // useChangeMatchObject: createUseChangeMatchObject(dataset), // useChangeMatchSubject: createUseChangeMatchSubject(dataset), }; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 73c0d834..60d30073 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -10,8 +10,8 @@ export * from "./methods/useLinkQuery"; // export * from "./methods/change/types"; export * from "./methods/change/useChangeDataset"; +export * from "./methods/change/useChangeSubject"; // export * from "./methods/change/useChangeMatchObject"; // export * from "./methods/change/useChangeMatchSubject"; -// export * from "./methods/change/useChangeSubject"; export * from "./util/useTrackingProxy"; diff --git a/packages/vue/src/methods/change/useChangeDataset.ts b/packages/vue/src/methods/change/useChangeDataset.ts index 20af5c6b..e27aa6ab 100644 --- a/packages/vue/src/methods/change/useChangeDataset.ts +++ b/packages/vue/src/methods/change/useChangeDataset.ts @@ -1,7 +1,7 @@ import type { - ConnectedLdoDataset, ConnectedLdoTransactionDataset, ConnectedPlugin, + IConnectedLdoDataset, } from "@ldo/connected"; import { type MaybeRefOrGetter, @@ -12,10 +12,12 @@ import { } from "vue"; export function createUseChangeDataset( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { return function useChangeDataset( - specificDataset?: MaybeRefOrGetter>, + specificDataset?: MaybeRefOrGetter< + IConnectedLdoDataset | undefined + >, ) { const transactionDataset = shallowRef< ConnectedLdoTransactionDataset diff --git a/packages/vue/src/methods/change/useChangeSubject.ts b/packages/vue/src/methods/change/useChangeSubject.ts new file mode 100644 index 00000000..ab44ddcf --- /dev/null +++ b/packages/vue/src/methods/change/useChangeSubject.ts @@ -0,0 +1,71 @@ +import type { IConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import { type LdSet, write, type LdoBase, type ShapeType } from "@ldo/ldo"; +import { computed, toValue, type MaybeRefOrGetter } from "vue"; +import { createUseChangeDataset } from "./useChangeDataset"; +import type { SubjectNode } from "@ldo/rdf-utils"; +import { createUseSubject, type UseSubjectOptions } from "../useSubject"; +import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy"; + +export function createUseChangeSubject( + dataset: IConnectedLdoDataset, +) { + const useChangeDataset = createUseChangeDataset(dataset); + const useSubject = createUseSubject(dataset); + + /** + * Returns a reactive subject that can be modified and committed + */ + return function useChangeSubject( + shapeType: MaybeRefOrGetter>, + subject: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, + ) { + const resolvedDataset = computed(() => toValue(options)?.dataset); + + const { + transactionDataset, + setData: setDataset, + commitData, + } = useChangeDataset(resolvedDataset); + + const subjectTransactionDataset = computed(() => ({ + dataset: transactionDataset.value, + })); + + const ldObject = useSubject(shapeType, subject, subjectTransactionDataset); + + const setData = < + OtherType extends LdoBase | LdSet | undefined = undefined, + >( + writeResource: Plugins[number]["types"]["resource"], + changer: ( + toChange: OtherType extends undefined ? Type : OtherType, + ) => void, + otherType?: OtherType, + ) => { + const subjectValue = toValue(subject); + if (!subjectValue) return; + setDataset((dataset) => { + const ldObject = otherType + ? write(writeResource.uri).usingCopy( + createProxyInteractOptions("dataset", dataset).usingCopy( + otherType, + )[0], + )[0] + : dataset + .usingType(toValue(shapeType)) + .write(writeResource.uri) + .fromSubject(subjectValue); + + changer(ldObject as OtherType extends undefined ? Type : OtherType); + }); + }; + + return { + ldo: ldObject, + setData, + commitData, + transactionDataset, + }; + }; +} diff --git a/packages/vue/src/methods/useDataset.ts b/packages/vue/src/methods/useDataset.ts index b0bf1cde..b643dfad 100644 --- a/packages/vue/src/methods/useDataset.ts +++ b/packages/vue/src/methods/useDataset.ts @@ -1,4 +1,4 @@ -import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import type { IConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; /** * @internal @@ -6,7 +6,7 @@ import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; * Creates a useDataset function */ export function createUseDataset( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { /** * Returns the global dataset for the application diff --git a/packages/vue/src/methods/useLinkQuery.ts b/packages/vue/src/methods/useLinkQuery.ts index 4893d26f..8027a588 100644 --- a/packages/vue/src/methods/useLinkQuery.ts +++ b/packages/vue/src/methods/useLinkQuery.ts @@ -1,5 +1,5 @@ import type { - ConnectedLdoDataset, + IConnectedLdoDataset, ConnectedPlugin, LQInput, } from "@ldo/connected"; @@ -9,7 +9,7 @@ import { createUseSubject } from "@ldo/vue"; import { type MaybeRefOrGetter, toValue, watch } from "vue"; export function createUseLinkQuery( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { return function useLinkQuery< Type extends LdoBase, diff --git a/packages/vue/src/methods/useMatchObject.ts b/packages/vue/src/methods/useMatchObject.ts index e6c3fb75..999e7eaa 100644 --- a/packages/vue/src/methods/useMatchObject.ts +++ b/packages/vue/src/methods/useMatchObject.ts @@ -1,11 +1,7 @@ import type { QuadMatch } from "@ldo/rdf-utils"; import type { ShapeType, LdoBase, LdSet, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; -import type { - ConnectedLdoDataset, - ConnectedPlugin, - IConnectedLdoDataset, -} from "@ldo/connected"; +import type { ConnectedPlugin, IConnectedLdoDataset } from "@ldo/connected"; import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue"; export interface UseMatchObjectOptions { @@ -18,7 +14,7 @@ export interface UseMatchObjectOptions { * Creates a useMatchObject function. */ export function createUseMatchObject( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { /** * Returns a LDO set of matching items. diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts index ad57ddc0..aa844cac 100644 --- a/packages/vue/src/methods/useMatchSubject.ts +++ b/packages/vue/src/methods/useMatchSubject.ts @@ -1,11 +1,7 @@ import type { QuadMatch } from "@ldo/rdf-utils"; import type { ShapeType, LdoBase, LdSet, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; -import type { - ConnectedLdoDataset, - ConnectedPlugin, - IConnectedLdoDataset, -} from "@ldo/connected"; +import type { ConnectedPlugin, IConnectedLdoDataset } from "@ldo/connected"; import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue"; export interface UseMatchSubjectOptions { @@ -18,7 +14,7 @@ export interface UseMatchSubjectOptions { * Creates a useMatchSubject function. */ export function createUseMatchSubject( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { /** * Returns a LDO set of matching items. diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index 9d9dd356..f290070c 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -1,7 +1,7 @@ import type { - ConnectedLdoDataset, ConnectedPlugin, GetResourceReturnType, + IConnectedLdoDataset, Resource, } from "@ldo/connected"; import { @@ -46,7 +46,7 @@ export type useResourceType = { * Creates a useResource composable. */ export function createUseResource( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ): useResourceType { /** * Returns a resource and triggers a rerender if that resource is updated. diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts index 837bf2c5..fc588306 100644 --- a/packages/vue/src/methods/useSubject.ts +++ b/packages/vue/src/methods/useSubject.ts @@ -1,11 +1,7 @@ import type { SubjectNode } from "@ldo/rdf-utils"; import type { ShapeType, LdoBase, LdoBuilder } from "@ldo/ldo"; import { useTrackingProxy } from "../util/useTrackingProxy"; -import type { - ConnectedLdoDataset, - ConnectedPlugin, - IConnectedLdoDataset, -} from "@ldo/connected"; +import type { ConnectedPlugin, IConnectedLdoDataset } from "@ldo/connected"; import { computed, toValue, type MaybeRefOrGetter, type Ref } from "vue"; export interface UseSubjectOptions { @@ -20,12 +16,12 @@ export type useSubjectType = { ): Ref; ( shapeType: MaybeRefOrGetter>, - subject?: MaybeRefOrGetter, + subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref; ( shapeType: MaybeRefOrGetter>, - subject?: MaybeRefOrGetter, + subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref; }; @@ -36,7 +32,7 @@ export type useSubjectType = { * Creates a useSubject function. */ export function createUseSubject( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ): useSubjectType { /** * Returns a Linked Data Object based on the provided subject. @@ -44,7 +40,7 @@ export function createUseSubject( */ return function useSubject( shapeType: MaybeRefOrGetter>, - subject?: MaybeRefOrGetter, + subject?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref { const createLdo = computed(() => { diff --git a/packages/vue/src/methods/useSubscribeToResource.ts b/packages/vue/src/methods/useSubscribeToResource.ts index 1a449ffb..e0a2a995 100644 --- a/packages/vue/src/methods/useSubscribeToResource.ts +++ b/packages/vue/src/methods/useSubscribeToResource.ts @@ -1,4 +1,4 @@ -import type { ConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import type { ConnectedPlugin, IConnectedLdoDataset } from "@ldo/connected"; import { onBeforeUnmount, toValue, watch, type MaybeRefOrGetter } from "vue"; /** @@ -7,7 +7,7 @@ import { onBeforeUnmount, toValue, watch, type MaybeRefOrGetter } from "vue"; * Creates a useSubscribeToResource composable */ export function createUseSubscribeToResource( - dataset: ConnectedLdoDataset, + dataset: IConnectedLdoDataset, ) { /** * Starts and updates subscriptions to a list of resources diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 19f78fbd..2ddb48aa 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -9,7 +9,7 @@ import { import { RerenderCount, waitForResource, withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; -import { nextTick, type Ref, ref } from "vue"; +import { nextTick, type Ref, ref, toValue } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes"; import { BasicLdSet } from "@ldo/jsonld-dataset-proxy"; import { literal, namedNode, quad } from "@ldo/rdf-utils"; @@ -635,5 +635,37 @@ describe("LDO Vue Composables", () => { }); it.todo("should handle changes to custom dataset"); + it.todo("should handle error in saving"); + }); + + describe("useChangeSubject", () => { + it("should accept and commit changes to a default dataset", async () => { + const [{ changeDataset, resource, subject }, app] = withSetup(() => { + const changeDataset = methods.useChangeSubject( + FoafProfileShapeType, + personUri, + ); + const resource = methods.useResource(personResourceUri); + const subject = methods.useSubject(FoafProfileShapeType, personUri); + return { changeDataset, resource, subject }; + }); + + await vi.waitFor(() => { + expect(resource.value.isFetched()).toBe(true); + }); + + changeDataset.setData(resource.value, (profile) => { + profile.name = "ANOTHER NAME"; + }); + + const result = await changeDataset.commitData(); + expect(result.isError).toBe(false); + expect(subject.value.name).toEqual("ANOTHER NAME"); + + app.unmount(); + }); + + it.todo("should accept and commit changes to a custom dataset"); + it.todo("should handle error in saving"); }); }); From 9465eec9ffd31827cc456c0219390a35cfe01c38 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Wed, 30 Sep 2026 14:12:48 +0200 Subject: [PATCH 18/32] Implement useChangeMatchSubject and useChangeMatchObject with some tests --- packages/vue/src/createLdoVueMethods.ts | 10 +- packages/vue/src/index.ts | 4 +- .../methods/change/useChangeMatchObject.ts | 86 ++++++ .../methods/change/useChangeMatchSubject.ts | 86 ++++++ packages/vue/test/Composables.test.ts | 286 +++++++++++++++++- 5 files changed, 450 insertions(+), 22 deletions(-) create mode 100644 packages/vue/src/methods/change/useChangeMatchObject.ts create mode 100644 packages/vue/src/methods/change/useChangeMatchSubject.ts diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index f48bd78c..21abdad7 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -12,11 +12,9 @@ import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; import { createUseLinkQuery } from "./methods/useLinkQuery"; import { createUseChangeDataset } from "./methods/change/useChangeDataset"; import { createUseChangeSubject } from "./methods/change/useChangeSubject"; +import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject"; +import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject"; // import { createUseDataset } from "./methods/useDataset"; -// import { createUseChangeDataset } from "./methods/change/useChangeDataset"; -// import { createUseChangeSubject } from "./methods/change/useChangeSubject"; -// import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject"; -// import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject"; /** * A function that creates all common vue functions given specific plugin. @@ -86,7 +84,7 @@ export function createLdoVueMethods< useLinkQuery: createUseLinkQuery(dataset), useChangeDataset: createUseChangeDataset(dataset), useChangeSubject: createUseChangeSubject(dataset), - // useChangeMatchObject: createUseChangeMatchObject(dataset), - // useChangeMatchSubject: createUseChangeMatchSubject(dataset), + useChangeMatchObject: createUseChangeMatchObject(dataset), + useChangeMatchSubject: createUseChangeMatchSubject(dataset), }; } diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 60d30073..bd63cb5e 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -11,7 +11,7 @@ export * from "./methods/useLinkQuery"; // export * from "./methods/change/types"; export * from "./methods/change/useChangeDataset"; export * from "./methods/change/useChangeSubject"; -// export * from "./methods/change/useChangeMatchObject"; -// export * from "./methods/change/useChangeMatchSubject"; +export * from "./methods/change/useChangeMatchObject"; +export * from "./methods/change/useChangeMatchSubject"; export * from "./util/useTrackingProxy"; diff --git a/packages/vue/src/methods/change/useChangeMatchObject.ts b/packages/vue/src/methods/change/useChangeMatchObject.ts new file mode 100644 index 00000000..e0a06570 --- /dev/null +++ b/packages/vue/src/methods/change/useChangeMatchObject.ts @@ -0,0 +1,86 @@ +import type { IConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import { type LdSet, write, type LdoBase, type ShapeType } from "@ldo/ldo"; +import { computed, toValue, type MaybeRefOrGetter } from "vue"; +import { createUseChangeDataset } from "./useChangeDataset"; +import type { QuadMatch } from "@ldo/rdf-utils"; +import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy"; +import { + createUseMatchObject, + type UseMatchObjectOptions, +} from "../useMatchObject"; + +export function createUseChangeMatchObject( + dataset: IConnectedLdoDataset, +) { + const useChangeDataset = createUseChangeDataset(dataset); + const useMatchObject = createUseMatchObject(dataset); + + /** + * Returns a reactive subject that can be modified and committed + */ + return function useChangeMatchObject( + shapeType: MaybeRefOrGetter>, + subject?: MaybeRefOrGetter, + predicate?: MaybeRefOrGetter, + graph?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, + ) { + const resolvedDataset = computed(() => toValue(options)?.dataset); + + const { + transactionDataset, + setData: setDataset, + commitData, + } = useChangeDataset(resolvedDataset); + + const transactionDatasetOption = computed(() => ({ + dataset: transactionDataset.value, + })); + + const ldObject = useMatchObject( + shapeType, + subject, + predicate, + graph, + transactionDatasetOption, + ); + + const setData = < + OtherType extends LdoBase | LdSet | undefined = undefined, + >( + writeResource: Plugins[number]["types"]["resource"], + changer: ( + toChange: OtherType extends undefined ? LdSet : OtherType, + ) => void, + otherType?: OtherType, + ) => { + setDataset((dataset) => { + const ldObject = otherType + ? write(writeResource.uri).usingCopy( + createProxyInteractOptions("dataset", dataset).usingCopy( + otherType, + )[0], + )[0] + : dataset + .usingType(toValue(shapeType)) + .write(writeResource.uri) + .matchObject( + toValue(subject), + toValue(predicate), + toValue(graph), + ); + + changer( + ldObject as OtherType extends undefined ? LdSet : OtherType, + ); + }); + }; + + return { + ldo: ldObject, + setData, + commitData, + transactionDataset, + }; + }; +} diff --git a/packages/vue/src/methods/change/useChangeMatchSubject.ts b/packages/vue/src/methods/change/useChangeMatchSubject.ts new file mode 100644 index 00000000..33e764a7 --- /dev/null +++ b/packages/vue/src/methods/change/useChangeMatchSubject.ts @@ -0,0 +1,86 @@ +import type { IConnectedLdoDataset, ConnectedPlugin } from "@ldo/connected"; +import { type LdSet, write, type LdoBase, type ShapeType } from "@ldo/ldo"; +import { computed, toValue, type MaybeRefOrGetter } from "vue"; +import { createUseChangeDataset } from "./useChangeDataset"; +import type { QuadMatch } from "@ldo/rdf-utils"; +import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy"; +import { + createUseMatchSubject, + type UseMatchSubjectOptions, +} from "../useMatchSubject"; + +export function createUseChangeMatchSubject( + dataset: IConnectedLdoDataset, +) { + const useChangeDataset = createUseChangeDataset(dataset); + const useMatchSubject = createUseMatchSubject(dataset); + + /** + * Returns a reactive subject that can be modified and committed + */ + return function useChangeMatchSubject( + shapeType: MaybeRefOrGetter>, + predicate?: MaybeRefOrGetter, + object?: MaybeRefOrGetter, + graph?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, + ) { + const resolvedDataset = computed(() => toValue(options)?.dataset); + + const { + transactionDataset, + setData: setDataset, + commitData, + } = useChangeDataset(resolvedDataset); + + const transactionDatasetOption = computed(() => ({ + dataset: transactionDataset.value, + })); + + const ldObject = useMatchSubject( + shapeType, + predicate, + object, + graph, + transactionDatasetOption, + ); + + const setData = < + OtherType extends LdoBase | LdSet | undefined = undefined, + >( + writeResource: Plugins[number]["types"]["resource"], + changer: ( + toChange: OtherType extends undefined ? LdSet : OtherType, + ) => void, + otherType?: OtherType, + ) => { + setDataset((dataset) => { + const ldObject = otherType + ? write(writeResource.uri).usingCopy( + createProxyInteractOptions("dataset", dataset).usingCopy( + otherType, + )[0], + )[0] + : dataset + .usingType(toValue(shapeType)) + .write(writeResource.uri) + .matchSubject( + toValue(predicate), + toValue(object), + toValue(graph), + ); + + changer( + ldObject as OtherType extends undefined ? LdSet : OtherType, + ); + }); + }; + + return { + ldo: ldObject, + setData, + commitData, + transactionDataset, + }; + }; +} diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index 2ddb48aa..edb88de2 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -1,5 +1,11 @@ import { describe, it, beforeEach, expect, vi } from "vitest"; -import { createLdoVueMethods, type UseResourceOptions } from "../src/index"; +import { + createLdoVueMethods, + createUseMatchObject, + createUseResource, + createUseSubscribeToResource, + type UseResourceOptions, +} from "../src/index"; import { type SolidConnectedPlugin, solidConnectedPlugin, @@ -9,11 +15,15 @@ import { import { RerenderCount, waitForResource, withSetup } from "./test-utils.js"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; -import { nextTick, type Ref, ref, toValue } from "vue"; +import { nextTick, type Ref, ref } from "vue"; import { FoafProfileShapeType } from "./_ldo/foafProfile.shapeTypes"; import { BasicLdSet } from "@ldo/jsonld-dataset-proxy"; import { literal, namedNode, quad } from "@ldo/rdf-utils"; -import { ConnectedLdoTransactionDataset } from "@ldo/connected"; +import { + ConnectedLdoTransactionDataset, + createConnectedLdoDataset, + type IConnectedLdoDataset, +} from "@ldo/connected"; // Only overwrite this if we are in test mode. This is an env var we happen to have in our repo if (process.env.VITE_IS_TEST === "true") { @@ -47,8 +57,12 @@ describe("LDO Vue Composables", () => { let methods: ReturnType>; const personResourceUri = "http://localhost:3006/directory/person"; const personUri = personResourceUri + "#me"; + const otherPersonUri = personResourceUri + "#i"; const person2ResourceUri = "http://localhost:3006/directory/person2"; const person2Uri = person2ResourceUri + "#me"; + const foafName = namedNode("http://xmlns.com/foaf/0.1/name"); + const foafKnows = namedNode("http://xmlns.com/foaf/0.1/knows"); + const s = setupServer(3006, { slug: "directory/", isContainer: true, @@ -583,8 +597,6 @@ describe("LDO Vue Composables", () => { expect(results.resource.value.isSubscribedToNotifications()).toBe(true); }); - const foafName = namedNode("http://xmlns.com/foaf/0.1/name"); - // change person's name results.changeDataset.setData((dataset) => { dataset.deleteMatches(namedNode(personUri), foafName); @@ -640,15 +652,20 @@ describe("LDO Vue Composables", () => { describe("useChangeSubject", () => { it("should accept and commit changes to a default dataset", async () => { - const [{ changeDataset, resource, subject }, app] = withSetup(() => { - const changeDataset = methods.useChangeSubject( - FoafProfileShapeType, - personUri, - ); - const resource = methods.useResource(personResourceUri); - const subject = methods.useSubject(FoafProfileShapeType, personUri); - return { changeDataset, resource, subject }; - }); + const [{ changeDataset, resource, subject, otherSubject }, app] = + withSetup(() => { + const changeDataset = methods.useChangeSubject( + FoafProfileShapeType, + personUri, + ); + const resource = methods.useResource(personResourceUri); + const subject = methods.useSubject(FoafProfileShapeType, personUri); + const otherSubject = methods.useSubject( + FoafProfileShapeType, + otherPersonUri, + ); + return { changeDataset, resource, subject, otherSubject }; + }); await vi.waitFor(() => { expect(resource.value.isFetched()).toBe(true); @@ -657,12 +674,253 @@ describe("LDO Vue Composables", () => { changeDataset.setData(resource.value, (profile) => { profile.name = "ANOTHER NAME"; }); + changeDataset.setData( + resource.value, + (profile) => { + profile.name = "OTHER PERSON NAME"; + }, + otherSubject.value, + ); const result = await changeDataset.commitData(); expect(result.isError).toBe(false); expect(subject.value.name).toEqual("ANOTHER NAME"); + expect(otherSubject.value.name).toEqual("OTHER PERSON NAME"); + + app.unmount(); + }); + + it.todo("should accept and commit changes to a custom dataset"); + it.todo("should handle error in saving"); + }); + + describe("useChangeMatchObject", () => { + it("should accept and commit changes to a default dataset", async () => { + const [{ changeDataset, resource, resource2, objects, subject }, app] = + withSetup(() => { + const changeDataset = methods.useChangeMatchObject( + FoafProfileShapeType, + person2Uri, + foafKnows, + ); + const resource = methods.useResource(personResourceUri); + const resource2 = methods.useResource(person2ResourceUri); + const subject = methods.useSubject(FoafProfileShapeType, person2Uri); + const objects = methods.useMatchObject( + FoafProfileShapeType, + person2Uri, + foafKnows, + ); + methods.useSubscribeToResource([ + personResourceUri, + person2ResourceUri, + ]); + return { changeDataset, resource, resource2, objects, subject }; + }); + + await vi.waitFor(() => { + expect(resource.value.isFetched()).toBe(true); + expect(resource2.value.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(true); + expect(resource2.value.isSubscribedToNotifications()).toBe(true); + }); + + changeDataset.setData(resource.value, (friends) => { + expect(friends.size).toEqual(1); + friends.forEach((person) => { + person.name = "ANOTHER NAME"; + }); + }); + + // we can also edit other subjects and graphs (resources) within the same transaction + changeDataset.setData( + resource2.value, + (person) => { + person.name = "PERSON 2 NAME"; + }, + subject.value, + ); + + const result = await changeDataset.commitData(); + expect(result.isError).toBe(false); + + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(true); + expect(resource2.value.isLoading()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(false); + expect(resource2.value.isLoading()).toBe(false); + }); + + expect(objects.value.size).toBe(1); + expect(objects.value.toArray()[0].name).toEqual("ANOTHER NAME"); + expect(subject.value.name).toEqual("PERSON 2 NAME"); + + app.unmount(); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(false); + expect(resource2.value.isSubscribedToNotifications()).toBe(false); + }); + }); + + it("should accept and commit changes to a custom dataset", async () => { + const otherDataset = createConnectedLdoDataset([ + solidConnectedPlugin, + ]) as IConnectedLdoDataset<[SolidConnectedPlugin]>; + otherDataset.setContext("solid", { fetch: s.fetchMock }); + const useResource = createUseResource(otherDataset); + const useMatchObject = createUseMatchObject(otherDataset); + const useSubscribeToResource = createUseSubscribeToResource(otherDataset); + const [{ changeDataset, resource, resource2, objects }, app] = withSetup( + () => { + const changeDataset = methods.useChangeMatchObject( + FoafProfileShapeType, + person2Uri, + foafKnows, + undefined, + { + dataset: otherDataset as IConnectedLdoDataset< + [SolidConnectedPlugin] + >, + }, + ); + const resource = useResource(personResourceUri); + const resource2 = useResource(person2ResourceUri); + const objects = useMatchObject( + FoafProfileShapeType, + person2Uri, + foafKnows, + ); + useSubscribeToResource([personResourceUri, person2ResourceUri]); + return { changeDataset, resource, resource2, objects }; + }, + ); + + await vi.waitFor(() => { + expect(resource.value.isFetched()).toBe(true); + expect(resource2.value.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(true); + expect(resource2.value.isSubscribedToNotifications()).toBe(true); + }); + + changeDataset.setData(resource.value, (friends) => { + friends.forEach((person) => { + person.name = "ANOTHER NAME"; + }); + }); + + const result = await changeDataset.commitData(); + expect(result.isError).toBe(false); + console.log(result); + + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(false); + }); + + expect(objects.value.size).toBe(1); + expect(objects.value.toArray()[0].name).toEqual("ANOTHER NAME"); app.unmount(); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(false); + expect(resource2.value.isSubscribedToNotifications()).toBe(false); + }); + }); + + it.todo("should handle error in saving"); + }); + + describe("useChangeMatchSubject", () => { + it("should accept and commit changes to a default dataset", async () => { + const [{ changeDataset, resource, resource2, subjects, subject2 }, app] = + withSetup(() => { + // people in first resource who have friends + const changeDataset = methods.useChangeMatchSubject( + FoafProfileShapeType, + foafKnows, + undefined, + namedNode(personResourceUri), + ); + const resource = methods.useResource(personResourceUri); + const resource2 = methods.useResource(person2ResourceUri); + const subject2 = methods.useSubject(FoafProfileShapeType, person2Uri); + const subjects = methods.useMatchSubject( + FoafProfileShapeType, + foafKnows, + undefined, + namedNode(personResourceUri), + ); + methods.useSubscribeToResource([ + personResourceUri, + person2ResourceUri, + ]); + return { + changeDataset, + resource, + resource2, + subjects, + subject2, + }; + }); + + await vi.waitFor(() => { + expect(resource.value.isFetched()).toBe(true); + expect(resource2.value.isFetched()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(true); + expect(resource2.value.isSubscribedToNotifications()).toBe(true); + }); + + changeDataset.setData(resource.value, (people) => { + // expect(people.size).toEqual(1); // weirdly, this fails! TODO + let count = 0; + people.forEach((person) => { + count++; + person.name = "ANOTHER NAME"; + }); + expect(count).toBe(1); + }); + + // we can also edit other subjects and graphs (resources) within the same transaction + changeDataset.setData( + resource2.value, + (person) => { + person.name = "PERSON 2 NAME"; + }, + subject2.value, + ); + + const result = await changeDataset.commitData(); + expect(result.isError).toBe(false); + + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(true); + expect(resource2.value.isLoading()).toBe(true); + }); + await vi.waitFor(() => { + expect(resource.value.isLoading()).toBe(false); + expect(resource2.value.isLoading()).toBe(false); + }); + + // expect(subjects.value.size).toBe(1); // TODO weirdly, this fails + expect(subjects.value.toArray()).toHaveLength(1); + expect(subjects.value.toArray()[0].name).toEqual("ANOTHER NAME"); + expect(subject2.value.name).toEqual("PERSON 2 NAME"); + + app.unmount(); + await vi.waitFor(() => { + expect(resource.value.isSubscribedToNotifications()).toBe(false); + expect(resource2.value.isSubscribedToNotifications()).toBe(false); + }); }); it.todo("should accept and commit changes to a custom dataset"); From e3dc6250b163b0635384e18fd98e93f460f589d9 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 1 Oct 2026 10:38:00 +0200 Subject: [PATCH 19/32] Fix missing dependencies in @ldo/vue Vue itself is added as peer dependency --- package-lock.json | 133 ++++++++++++++----------- packages/vue/package.json | 17 +++- packages/vue/src/methods/useLdo.ts | 151 ++++++++++++++++++++++++++++- 3 files changed, 239 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83485f32..ae1ebce1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23446,13 +23446,13 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", - "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz", + "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", - "@vue/shared": "3.5.42", + "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" @@ -23477,29 +23477,29 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", - "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz", + "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.42", - "@vue/shared": "3.5.42" + "@vue/compiler-core": "3.5.43", + "@vue/shared": "3.5.43" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", - "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz", + "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", - "@vue/compiler-core": "3.5.42", - "@vue/compiler-dom": "3.5.42", - "@vue/compiler-ssr": "3.5.42", - "@vue/shared": "3.5.42", + "@vue/compiler-core": "3.5.43", + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-ssr": "3.5.43", + "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", - "postcss": "^8.5.19", + "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, @@ -23510,13 +23510,13 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", - "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz", + "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.42", - "@vue/shared": "3.5.42" + "@vue/compiler-dom": "3.5.43", + "@vue/shared": "3.5.43" } }, "node_modules/@vue/devtools-core": { @@ -23595,51 +23595,51 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", - "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz", + "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.42" + "@vue/shared": "3.5.43" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", - "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz", + "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.42", - "@vue/shared": "3.5.42" + "@vue/reactivity": "3.5.43", + "@vue/shared": "3.5.43" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", - "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz", + "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.42", - "@vue/runtime-core": "3.5.42", - "@vue/shared": "3.5.42", + "@vue/reactivity": "3.5.43", + "@vue/runtime-core": "3.5.43", + "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", - "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz", + "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.42", - "@vue/runtime-dom": "3.5.42", - "@vue/shared": "3.5.42" + "@vue/compiler-ssr": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/shared": "3.5.43" } }, "node_modules/@vue/shared": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", - "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz", + "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==", "license": "MIT" }, "node_modules/@vue/tsconfig": { @@ -34117,9 +34117,9 @@ } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "funding": [ { "type": "opencollective", @@ -34136,7 +34136,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -40204,16 +40204,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.42", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", - "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz", + "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.42", - "@vue/compiler-sfc": "3.5.42", - "@vue/runtime-dom": "3.5.42", - "@vue/server-renderer": "3.5.42", - "@vue/shared": "3.5.42" + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-sfc": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/server-renderer": "3.5.43", + "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" @@ -42211,7 +42211,22 @@ "packages/vue": { "name": "@ldo/vue", "version": "1.0.0-alpha.50", - "license": "MIT" + "license": "MIT", + "dependencies": { + "@ldo/connected": "^1.0.0-alpha.50", + "@ldo/jsonld-dataset-proxy": "^1.0.0-alpha.50", + "@ldo/ldo": "^1.0.0-alpha.50", + "@ldo/rdf-utils": "^1.0.0-alpha.50" + }, + "devDependencies": { + "@ldo/connected-solid": "^1.0.0-alpha.50", + "@ldo/test-solid-server": "^1.0.0-alpha.50", + "@types/shexj": "^2.1.7", + "vue": "^3.5.43" + }, + "peerDependencies": { + "vue": "^3.3.0" + } }, "packages/wac": { "extraneous": true diff --git a/packages/vue/package.json b/packages/vue/package.json index 4525ceb4..c1f691a8 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -39,5 +39,20 @@ "bugs": { "url": "https://github.com/o-development/ldo/issues" }, - "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme" + "homepage": "https://github.com/o-development/ldo/tree/main/packages/vue#readme", + "dependencies": { + "@ldo/connected": "^1.0.0-alpha.50", + "@ldo/jsonld-dataset-proxy": "^1.0.0-alpha.50", + "@ldo/ldo": "^1.0.0-alpha.50", + "@ldo/rdf-utils": "^1.0.0-alpha.50" + }, + "peerDependencies": { + "vue": "^3.3.0" + }, + "devDependencies": { + "@ldo/connected-solid": "^1.0.0-alpha.50", + "@ldo/test-solid-server": "^1.0.0-alpha.50", + "@types/shexj": "^2.1.7", + "vue": "^3.5.43" + } } diff --git a/packages/vue/src/methods/useLdo.ts b/packages/vue/src/methods/useLdo.ts index 11dc1ce4..a2df3a6e 100644 --- a/packages/vue/src/methods/useLdo.ts +++ b/packages/vue/src/methods/useLdo.ts @@ -1,2 +1,149 @@ -export type { UseLdoMethods } from "@ldo/react"; -export { createUseLdo } from "@ldo/react"; +// This is a copy-paste from @ldo/react library. +// It has nothing framework-specific in it. It's just a wrapper of ldo methods. + +import { + changeData, + type ConnectedLdoDataset, + type ConnectedLdoTransactionDataset, + type ConnectedPlugin, +} from "@ldo/connected"; +import { getDataset, type LdoBase, type ShapeType } from "@ldo/ldo"; +import type { SubjectNode } from "@ldo/rdf-utils"; + +/** + * The methods returned by useLdo + */ +export interface UseLdoMethods { + /** + * A ConnectedLdoDataset + */ + dataset: ConnectedLdoDataset; + /** + * Retrieves a representation of a Resource at the given URI. This resource + * represents the current state of the resource: whether it is currently + * fetched or in the process of fetching as well as some information about it. + * + * @param uri - the URI of the resource + * @param pluginName - optionally, force this function to choose a specific + * plugin to use rather than perform content negotiation. + * + * @returns a Resource + */ + getResource: ConnectedLdoDataset["getResource"]; + /** + * Sets context for a specific plugin + * + * @param pluginName - the name of the plugin + * @param context - the context for this specific plugin + */ + setContext: ConnectedLdoDataset["setContext"]; + /** + * Gets a linked data object based on the subject + */ + getSubject( + shapeType: ShapeType, + subject: string | SubjectNode, + ): Type; + /** + * Shorthand for connectedLdoDataset + * .usingType(shapeType) + * .write(...resources.map((r) => r.uri)) + * .fromSubject(subject); + * @param shapeType - The shapeType to represent the data + * @param subject - A subject URI + * @param resources - The resources changes to should written to + */ + createData( + shapeType: ShapeType, + subject: string | SubjectNode, + resource: Plugins[number]["types"]["resource"], + ...additionalResources: Plugins[number]["types"]["resource"][] + ): Type; + /** + * Returns a writable LinkedDataObject given a linked data object + */ + changeData( + input: Type, + resource: Plugins[number]["types"]["resource"], + ...additionalResources: Plugins[number]["types"]["resource"][] + ): Type; + /** + * Commits the data of a writable Linked Data Object back to the remote. + */ + commitData( + input: LdoBase, + ): ReturnType["commitToRemote"]>; +} + +/** + * @internal + * Creates the useLdoHook + */ +export function createUseLdo( + dataset: ConnectedLdoDataset, +) { + const toReturn = { + dataset, + /** + * Gets a resource + */ + getResource: dataset.getResource.bind(dataset), + /** + * Set the context + */ + setContext: dataset.setContext.bind(dataset), + /** + * Returns a Linked Data Object for a subject + * @param shapeType The shape type for the data + * @param subject Subject Node + * @returns A Linked Data Object + */ + getSubject( + shapeType: ShapeType, + subject: string | SubjectNode, + ): Type { + return dataset.usingType(shapeType).fromSubject(subject); + }, + /** + * Begins tracking changes to eventually commit for a new subject + * @param shapeType The shape type that defines the created data + * @param subject The RDF subject for a Linked Data Object + * @param resources Any number of resources to which this data should be written + * @returns A Linked Data Object to modify and commit + */ + createData( + shapeType: ShapeType, + subject: string | SubjectNode, + resource: Plugins[number]["types"]["resource"], + ...additionalResources: Plugins[number]["types"]["resource"][] + ): Type { + return dataset.createData( + shapeType, + subject, + resource, + ...additionalResources, + ); + }, + /** + * Begins tracking changes to eventually commit + * @param input A linked data object to track changes on + * @param resources + */ + changeData: changeData, + /** + * Commits the transaction to the global dataset, syncing all subscribing + * components and Solid Pods + */ + commitData( + input: LdoBase, + ): ReturnType["commitToRemote"]> { + const inputDataset = getDataset( + input, + ) as ConnectedLdoTransactionDataset; + return inputDataset.commitToRemote(); + }, + }; + return function useLdo(): UseLdoMethods { + return toReturn; + }; +} From a684e712df1403ac8dd1df4546a8dec03419ad9a Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 1 Oct 2026 12:00:46 +0200 Subject: [PATCH 20/32] Write README for @ldo/vue --- packages/vue/README.md | 121 ++++++++++++++++++++++++ packages/vue/src/createLdoVueMethods.ts | 4 +- 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/packages/vue/README.md b/packages/vue/README.md index 4c7ed235..77a028aa 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -1 +1,122 @@ # @ldo/vue + +`@ldo/vue` provides composables for easily building Linked Data applications using [Vue](https://vuejs.org). + +**Note:** If you're building a [Solid](https://solidproject.org) application with Vue, we recommend that you use [@ldo/solid-vue](https://www.npmjs.com/package/@ldo/solid-vue) instead of this package. + +## Installation + +If you haven't already, [create your Vue project](https://vuejs.org/guide/quick-start.html#creating-a-vue-application). + +Navigate into your project's root folder and run the following command: + +```sh +cd my_project +npx @ldo/cli init +``` + +Now install the `@ldo/vue` library: + +```sh +npm i @ldo/vue +``` + +Also install a connected plugin of your choice: + +```sh +npm i @ldo/connected-solid @ldo/connected-nextgraph +``` + +
+ +Manual Installation + + +If you already have generated ShapeTypes, you may install the `@ldo/ldo` and `@ldo/vue` libraries independently. + +```sh +npm i @ldo/ldo @ldo/vue +``` + +Also install the connected plugin of your choice: + +```sh +npm i @ldo/connected-solid @ldo/connected-nextgraph +``` + +
+ +## Simple Example + +Below is a simple example of @ldo/vue in a real use-case. Assume that a ShapeType was previously generated and placed at `./_ldo/foafProfile.shapeTypes.ts`. + +```ts +// src/ldo.ts + +import { createLdoVueMethods } from "@ldo/vue"; +// replace the plugin with the connected plugin(s) of your choice +import { solidConnectedPlugin } from "@ldo/connected-solid"; + +// create LDO Vue methods that will be used in the whole app +export const { + dataset, + useSubject, + useResource, + useMatchSubject, + /* export all you need */ +} = createLdoVueMethods([solidConnectedPlugin]); + +// At some point, you may want to implement some form of authentication +// suitable for your connected plugin. +// In case of Solid plugin, you need to provide authenticated fetch from the current session +dataset.setContext("solid", { fetch: authFetch }); +// We recommend you just use @ldo/solid-vue instead +``` + +```vue + + + +``` + +## API Details + +Vue [composables](https://vuejs.org/guide/reusability/composables.html): + +- [useLdo](https://ldo.js.org/latest/api/vue/useLdo/) +- [useResource](https://ldo.js.org/latest/api/vue/useResource/) +- [useSubject](https://ldo.js.org/latest/api/vue/useSubject/) +- [useMatchSubject](https://ldo.js.org/latest/api/vue/useMatchSubject/) +- [useMatchObject](https://ldo.js.org/latest/api/vue/useMatchObject/) +- [useSubscribeToResource](https://ldo.js.org/latest/api/vue/useSubscribeToResource/) +- [useLinkQuery](https://ldo.js.org/latest/api/vue/useLinkQuery/) +- [useChangeDataset](https://ldo.js.org/latest/api/vue/useChangeDataset/) +- [useChangeSubject](https://ldo.js.org/latest/api/vue/useChangeSubject/) +- [useChangeMatchSubject](https://ldo.js.org/latest/api/vue/useChangeMatchSubject/) +- [useChangeMatchObject](https://ldo.js.org/latest/api/vue/useChangeMatchObject/) + +## Sponsorship + +This project was made possible by a grant from [NGI0 Commons Fund](https://nlnet.nl/commonsfund/) via nlnet. Learn more on the [NLnet project page](https://nlnet.nl/project/LDO-up/). + +[nlnet foundation logo](https://nlnet.nl/) +[NGI Zero Commons Logo](https://nlnet.nl/commonsfund/) + +## License + +MIT diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 21abdad7..8ba47c95 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -76,10 +76,10 @@ export function createLdoVueMethods< dataset, // useDataset: createUseDataset(dataset), useLdo: createUseLdo(dataset), - useMatchObject: createUseMatchObject(dataset), - useMatchSubject: createUseMatchSubject(dataset), useResource: createUseResource(dataset), useSubject: createUseSubject(dataset), + useMatchObject: createUseMatchObject(dataset), + useMatchSubject: createUseMatchSubject(dataset), useSubscribeToResource: createUseSubscribeToResource(dataset), useLinkQuery: createUseLinkQuery(dataset), useChangeDataset: createUseChangeDataset(dataset), From 9af3ac79d68118c92aa240df3c8933d8dca5ba45 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 1 Oct 2026 12:32:07 +0200 Subject: [PATCH 21/32] Test the README example in the solid-vue-vite-app --- dev/solid-vue-vite-app/src/App.vue | 4 +- .../src/{ldoVue.ts => ldo.ts} | 0 .../src/useMatchSubjectTest.vue | 2 +- dev/solid-vue-vite-app/src/vueReadmeTest.vue | 37 +++++++++++++++++++ packages/vue/README.md | 24 +++++++----- 5 files changed, 56 insertions(+), 11 deletions(-) rename dev/solid-vue-vite-app/src/{ldoVue.ts => ldo.ts} (100%) create mode 100644 dev/solid-vue-vite-app/src/vueReadmeTest.vue diff --git a/dev/solid-vue-vite-app/src/App.vue b/dev/solid-vue-vite-app/src/App.vue index f623c8e5..6a95f755 100644 --- a/dev/solid-vue-vite-app/src/App.vue +++ b/dev/solid-vue-vite-app/src/App.vue @@ -1,8 +1,9 @@ + + diff --git a/packages/vue/README.md b/packages/vue/README.md index 77a028aa..d8038cdd 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -63,6 +63,7 @@ export const { useSubject, useResource, useMatchSubject, + useSubscribeToResource, /* export all you need */ } = createLdoVueMethods([solidConnectedPlugin]); @@ -75,22 +76,27 @@ dataset.setContext("solid", { fetch: authFetch }); ```vue ``` From 065aa2e0a371217291162178d92175f78e3613b9 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 1 Oct 2026 13:27:02 +0200 Subject: [PATCH 22/32] Add useDataset for completeness even though it doesn't bring any real value compared to plain dataset --- packages/vue/src/createLdoVueMethods.ts | 4 ++-- packages/vue/src/index.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index 8ba47c95..aae67d3c 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -14,7 +14,7 @@ import { createUseChangeDataset } from "./methods/change/useChangeDataset"; import { createUseChangeSubject } from "./methods/change/useChangeSubject"; import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject"; import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject"; -// import { createUseDataset } from "./methods/useDataset"; +import { createUseDataset } from "./methods/useDataset"; /** * A function that creates all common vue functions given specific plugin. @@ -74,7 +74,7 @@ export function createLdoVueMethods< return { dataset, - // useDataset: createUseDataset(dataset), + useDataset: createUseDataset(dataset), useLdo: createUseLdo(dataset), useResource: createUseResource(dataset), useSubject: createUseSubject(dataset), diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index bd63cb5e..7318c705 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,5 +1,6 @@ export * from "./createLdoVueMethods"; +export * from "./methods/useDataset"; export * from "./methods/useLdo"; export * from "./methods/useMatchObject"; export * from "./methods/useMatchSubject"; From d2b6501437c763a8def089f3ceaa64b767d5a16c Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 8 Oct 2026 02:33:47 +0200 Subject: [PATCH 23/32] gitignore .vscode/extensions.json in example app --- dev/solid-vue-vite-app/.gitignore | 2 +- dev/solid-vue-vite-app/.vscode/extensions.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 dev/solid-vue-vite-app/.vscode/extensions.json diff --git a/dev/solid-vue-vite-app/.gitignore b/dev/solid-vue-vite-app/.gitignore index cd68f140..f22509c8 100644 --- a/dev/solid-vue-vite-app/.gitignore +++ b/dev/solid-vue-vite-app/.gitignore @@ -16,7 +16,7 @@ coverage # Editor directories and files .vscode/* -!.vscode/extensions.json +#!.vscode/extensions.json .idea *.suo *.ntvs* diff --git a/dev/solid-vue-vite-app/.vscode/extensions.json b/dev/solid-vue-vite-app/.vscode/extensions.json deleted file mode 100644 index a7cea0b0..00000000 --- a/dev/solid-vue-vite-app/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["Vue.volar"] -} From 98718fdc149d4cba1111c3461ec1c73fe4157c4f Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 8 Oct 2026 10:22:13 +0200 Subject: [PATCH 24/32] Add and improve jsdoc comments on the new methods --- packages/react/src/methods/useLinkQuery.ts | 2 +- .../src/methods/change/useChangeDataset.ts | 28 +++++++++++++++++++ .../methods/change/useChangeMatchObject.ts | 7 ++++- .../methods/change/useChangeMatchSubject.ts | 7 ++++- .../src/methods/change/useChangeSubject.ts | 5 ++++ packages/vue/src/methods/useLdo.ts | 9 ++++-- packages/vue/src/methods/useLinkQuery.ts | 10 +++++++ packages/vue/src/methods/useMatchObject.ts | 6 ++-- packages/vue/src/methods/useMatchSubject.ts | 4 +-- packages/vue/src/methods/useResource.ts | 2 +- packages/vue/src/methods/useSubject.ts | 4 +-- .../vue/src/methods/useSubscribeToResource.ts | 4 +-- 12 files changed, 72 insertions(+), 16 deletions(-) diff --git a/packages/react/src/methods/useLinkQuery.ts b/packages/react/src/methods/useLinkQuery.ts index 1accb785..75811c5b 100644 --- a/packages/react/src/methods/useLinkQuery.ts +++ b/packages/react/src/methods/useLinkQuery.ts @@ -14,7 +14,7 @@ import { useTrackingProxy } from "../util/useTrackingProxy"; /** * @internal * - * Creates a useMatchSubject function. + * Creates a useLinkQuery function. */ export function createUseLinkQuery( dataset: ConnectedLdoDataset, diff --git a/packages/vue/src/methods/change/useChangeDataset.ts b/packages/vue/src/methods/change/useChangeDataset.ts index e27aa6ab..b08590b5 100644 --- a/packages/vue/src/methods/change/useChangeDataset.ts +++ b/packages/vue/src/methods/change/useChangeDataset.ts @@ -11,9 +11,37 @@ import { type Ref, } from "vue"; +/** + * @internal + * + * Creates a useChangeDataset composable + */ export function createUseChangeDataset( dataset: IConnectedLdoDataset, ) { + /** + * Returns a transaction on the dataset that can be modified and committed. + * + * @example + * ```ts + * const { transactionDataset, setData, commitData } = useChangeDataset(); + * // you must populate the dataset with data in order to change it + * const resource = useResource(resourceUri); + * + * // you can do this in a callback, e. g. form update + * setData((dataset) => { + * // perform operations on the dataset + * dataset.deleteMatches(some, match); + * dataset.add(quad(subject, predicate, object, graph)) + * // etc... + * }); + * + * // save the data + * const result = await results.changeDataset.commitData() + * + * // check the result for errors + * ``` + */ return function useChangeDataset( specificDataset?: MaybeRefOrGetter< IConnectedLdoDataset | undefined diff --git a/packages/vue/src/methods/change/useChangeMatchObject.ts b/packages/vue/src/methods/change/useChangeMatchObject.ts index e0a06570..0eec2a15 100644 --- a/packages/vue/src/methods/change/useChangeMatchObject.ts +++ b/packages/vue/src/methods/change/useChangeMatchObject.ts @@ -9,6 +9,11 @@ import { type UseMatchObjectOptions, } from "../useMatchObject"; +/** + * @internal + * + * Creates a useChangeMatchObject composable + */ export function createUseChangeMatchObject( dataset: IConnectedLdoDataset, ) { @@ -16,7 +21,7 @@ export function createUseChangeMatchObject( const useMatchObject = createUseMatchObject(dataset); /** - * Returns a reactive subject that can be modified and committed + * Returns a reactive list of matched objects that can be modified and committed */ return function useChangeMatchObject( shapeType: MaybeRefOrGetter>, diff --git a/packages/vue/src/methods/change/useChangeMatchSubject.ts b/packages/vue/src/methods/change/useChangeMatchSubject.ts index 33e764a7..89267ca9 100644 --- a/packages/vue/src/methods/change/useChangeMatchSubject.ts +++ b/packages/vue/src/methods/change/useChangeMatchSubject.ts @@ -9,6 +9,11 @@ import { type UseMatchSubjectOptions, } from "../useMatchSubject"; +/** + * @internal + * + * Creates a useChangeMatchSubject composable + */ export function createUseChangeMatchSubject( dataset: IConnectedLdoDataset, ) { @@ -16,7 +21,7 @@ export function createUseChangeMatchSubject( const useMatchSubject = createUseMatchSubject(dataset); /** - * Returns a reactive subject that can be modified and committed + * Returns a reactive list of matched subjects that can be modified and committed */ return function useChangeMatchSubject( shapeType: MaybeRefOrGetter>, diff --git a/packages/vue/src/methods/change/useChangeSubject.ts b/packages/vue/src/methods/change/useChangeSubject.ts index ab44ddcf..d42b9f7a 100644 --- a/packages/vue/src/methods/change/useChangeSubject.ts +++ b/packages/vue/src/methods/change/useChangeSubject.ts @@ -6,6 +6,11 @@ import type { SubjectNode } from "@ldo/rdf-utils"; import { createUseSubject, type UseSubjectOptions } from "../useSubject"; import { createProxyInteractOptions } from "@ldo/jsonld-dataset-proxy"; +/** + * @internal + * + * Creates a useChangeSubject composable. + */ export function createUseChangeSubject( dataset: IConnectedLdoDataset, ) { diff --git a/packages/vue/src/methods/useLdo.ts b/packages/vue/src/methods/useLdo.ts index a2df3a6e..81821ee8 100644 --- a/packages/vue/src/methods/useLdo.ts +++ b/packages/vue/src/methods/useLdo.ts @@ -1,5 +1,8 @@ -// This is a copy-paste from @ldo/react library. -// It has nothing framework-specific in it. It's just a wrapper of ldo methods. +/* +This is a copy from @ldo/react library. +It has nothing framework-specific in it. +It's just a wrapper of ldo methods that looks like a composable. +*/ import { changeData, @@ -77,7 +80,7 @@ export interface UseLdoMethods { /** * @internal - * Creates the useLdoHook + * Creates a useLdo composable */ export function createUseLdo( dataset: ConnectedLdoDataset, diff --git a/packages/vue/src/methods/useLinkQuery.ts b/packages/vue/src/methods/useLinkQuery.ts index 8027a588..eff50cea 100644 --- a/packages/vue/src/methods/useLinkQuery.ts +++ b/packages/vue/src/methods/useLinkQuery.ts @@ -8,9 +8,19 @@ import type { SubjectNode } from "@ldo/rdf-utils"; import { createUseSubject } from "@ldo/vue"; import { type MaybeRefOrGetter, toValue, watch } from "vue"; +/** + * @internal + * + * Creates a useLinkQuery composable. + */ export function createUseLinkQuery( dataset: IConnectedLdoDataset, ) { + /** + * Runs a LDO link query. + * Subscribes to related changes. + * Returns the starting point as a reactive Linked Data Object. + */ return function useLinkQuery< Type extends LdoBase, QueryInput extends LQInput, diff --git a/packages/vue/src/methods/useMatchObject.ts b/packages/vue/src/methods/useMatchObject.ts index 999e7eaa..75e490b8 100644 --- a/packages/vue/src/methods/useMatchObject.ts +++ b/packages/vue/src/methods/useMatchObject.ts @@ -11,14 +11,14 @@ export interface UseMatchObjectOptions { /** * @internal * - * Creates a useMatchObject function. + * Creates a useMatchObject composable. */ export function createUseMatchObject( dataset: IConnectedLdoDataset, ) { /** - * Returns a LDO set of matching items. - * Triggers a rerender if the data are updated. + * Returns a reactive LDO set of matching items. + * Triggers a rerender if that data are updated. */ return function useMatchObject( shapeType: MaybeRefOrGetter>, diff --git a/packages/vue/src/methods/useMatchSubject.ts b/packages/vue/src/methods/useMatchSubject.ts index aa844cac..556eb295 100644 --- a/packages/vue/src/methods/useMatchSubject.ts +++ b/packages/vue/src/methods/useMatchSubject.ts @@ -11,13 +11,13 @@ export interface UseMatchSubjectOptions { /** * @internal * - * Creates a useMatchSubject function. + * Creates a useMatchSubject composable. */ export function createUseMatchSubject( dataset: IConnectedLdoDataset, ) { /** - * Returns a LDO set of matching items. + * Returns a reactive LDO set of matching linked data objects. * Triggers a rerender if the data are updated. */ return function useMatchSubject( diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index f290070c..3ec1adb6 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -49,7 +49,7 @@ export function createUseResource( dataset: IConnectedLdoDataset, ): useResourceType { /** - * Returns a resource and triggers a rerender if that resource is updated. + * Returns a reactive resource and triggers a rerender if that resource is updated. */ return function useResource< Name extends Plugins[number]["name"], diff --git a/packages/vue/src/methods/useSubject.ts b/packages/vue/src/methods/useSubject.ts index fc588306..2bbe727c 100644 --- a/packages/vue/src/methods/useSubject.ts +++ b/packages/vue/src/methods/useSubject.ts @@ -29,13 +29,13 @@ export type useSubjectType = { /** * @internal * - * Creates a useSubject function. + * Creates a useSubject composable. */ export function createUseSubject( dataset: IConnectedLdoDataset, ): useSubjectType { /** - * Returns a Linked Data Object based on the provided subject. + * Returns a reactive Linked Data Object based on the provided subject. * Triggers a rerender if the data are updated. */ return function useSubject( diff --git a/packages/vue/src/methods/useSubscribeToResource.ts b/packages/vue/src/methods/useSubscribeToResource.ts index e0a2a995..5f314cf8 100644 --- a/packages/vue/src/methods/useSubscribeToResource.ts +++ b/packages/vue/src/methods/useSubscribeToResource.ts @@ -4,13 +4,13 @@ import { onBeforeUnmount, toValue, watch, type MaybeRefOrGetter } from "vue"; /** * @internal * - * Creates a useSubscribeToResource composable + * Creates a useSubscribeToResource composable. */ export function createUseSubscribeToResource( dataset: IConnectedLdoDataset, ) { /** - * Starts and updates subscriptions to a list of resources + * Starts and updates subscriptions to a list of resources. */ return function useSubscribeToResource( uris: MaybeRefOrGetter, From 1ee80535b81df786243071b9e967d158d270d14c Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 8 Oct 2026 10:31:27 +0200 Subject: [PATCH 25/32] More specific eslint disable --- packages/vue/src/createLdoVueMethods.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index aae67d3c..a3ae1280 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { createConnectedLdoDataset, type ConnectedPlugin, @@ -67,6 +66,7 @@ import { createUseDataset } from "./methods/useDataset"; * ``` */ export function createLdoVueMethods< + // eslint-disable-next-line @typescript-eslint/no-explicit-any Plugins extends ConnectedPlugin[], >(plugins: Plugins) { const dataset = createConnectedLdoDataset(plugins); From 2e33a20db414df264ec2bce6df772faeb595b414 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sat, 10 Oct 2026 01:36:06 +0200 Subject: [PATCH 26/32] Add changelog entry and remove js extension from import --- CHANGELOG.md | 1 + packages/vue/test/Composables.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5260b91d..55d72719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **connected-solid:** Read links from Link header of a Solid resource. (#165) +- **vue:** Add [Vue](https://vuejs.org) support with `@ldo/vue` package. ([#171](https://github.com/o-development/ldo/pull/171)) ### Changed diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index edb88de2..bd36035f 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -12,7 +12,7 @@ import { type SolidContainerUri, type SolidLeafUri, } from "@ldo/connected-solid"; -import { RerenderCount, waitForResource, withSetup } from "./test-utils.js"; +import { RerenderCount, waitForResource, withSetup } from "./test-utils"; import assert from "node:assert"; import { setupServer } from "@ldo/test-solid-server"; import { nextTick, type Ref, ref } from "vue"; From 0ab6c4c0e47d60269d8c7eac9d3de98766eee6f0 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Mon, 12 Oct 2026 00:43:50 +0200 Subject: [PATCH 27/32] Add dataset provider and consumer composables for vue --- packages/vue/src/createLdoVueComposables.ts | 110 ++++++++++++++++++++ packages/vue/src/index.ts | 1 + packages/vue/src/methods/useMatchObject.ts | 19 +++- 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 packages/vue/src/createLdoVueComposables.ts diff --git a/packages/vue/src/createLdoVueComposables.ts b/packages/vue/src/createLdoVueComposables.ts new file mode 100644 index 00000000..57bdfa1c --- /dev/null +++ b/packages/vue/src/createLdoVueComposables.ts @@ -0,0 +1,110 @@ +import { + type ConnectedLdoDataset, + createConnectedLdoDataset, + type ConnectedPlugin, +} from "@ldo/connected"; +import { inject, provide, type InjectionKey } from "vue"; +import { createUseSubject } from "./methods/useSubject"; +import { createUseMatchObject } from "./methods/useMatchObject"; +import { createUseResource } from "./methods/useResource"; +import { createUseMatchSubject } from "./methods/useMatchSubject"; +import { createUseDataset } from "./methods/useDataset"; +import { createUseLdo } from "./methods/useLdo"; +import { createUseSubscribeToResource } from "./methods/useSubscribeToResource"; +import { createUseLinkQuery } from "./methods/useLinkQuery"; +import { createUseChangeDataset } from "./methods/change/useChangeDataset"; +import { createUseChangeMatchObject } from "./methods/change/useChangeMatchObject"; +import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubject"; + +/** + * Create ldo composables for Vue, where the dataset is isolated and provided/injected. + * + * You have to run `provideDataset()` in the ancestor component to set up the dataset. + * + * @example + * ```ts + * // in ldoComposables.ts do + * + * export const { provideDataset, useDataset, useSubject, ...etc } = createLdoVueComposables(connectedPlugins); + * export { provideDataset, useDataset } + * + * // provide dataset in parent component + * provideDataset() + * + * // use composables in child component + * const subjectLdo = useSubject(...subjectProps) + * ``` + */ +export function createLdoVueComposables( + plugins: Plugins, +) { + const DatasetKey: InjectionKey> = + Symbol("Dataset Key"); + + function provideDataset() { + const dataset = createConnectedLdoDataset(plugins); + provide(DatasetKey, dataset); + return dataset; + } + + return { + provideDataset, + useDataset: createComposable(DatasetKey, createUseDataset), + useLdo: createComposable(DatasetKey, createUseLdo), + useResource: createComposable(DatasetKey, createUseResource), + useSubject: createComposable(DatasetKey, createUseSubject), + useMatchObject: createComposable(DatasetKey, createUseMatchObject), + useMatchSubject: createComposable( + DatasetKey, + createUseMatchSubject, + ), + useSubscribeToResource: createComposable( + DatasetKey, + createUseSubscribeToResource, + ), + useLinkQuery: createComposable(DatasetKey, createUseLinkQuery), + useChangeDataset: createComposable( + DatasetKey, + createUseChangeDataset, + ), + useChangeSubject: createComposable(DatasetKey, createUseSubject), + useChangeMatchObject: createComposable( + DatasetKey, + createUseChangeMatchObject, + ), + useChangeMatchSubject: createComposable( + DatasetKey, + createUseChangeMatchSubject, + ), + }; +} + +/** + * @internal + * + * A method to create the composable with injected dataset + */ +const createComposable = < + Plugins extends ConnectedPlugin[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + CreateComposable extends (dataset: ConnectedLdoDataset) => any, +>( + key: InjectionKey>, + creator: CreateComposable, +): ReturnType => { + // We return a proxy function matching the exact signature layout of the underlying contract + return function wrappedMethod( + ...props: Parameters> + ): ReturnType> { + // inject the dataset + const dataset = inject(key); + // check that it's actually provided + if (!dataset) + throw new Error( + "Composable must have a dataset provider. Please call provideDataset() in a parent component.", + ); + + // Create and run the composable with the injected dataset + return creator(dataset)(...props); + } as ReturnType; +}; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 7318c705..9ed3d07e 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,4 +1,5 @@ export * from "./createLdoVueMethods"; +export * from "./createLdoVueComposables"; export * from "./methods/useDataset"; export * from "./methods/useLdo"; diff --git a/packages/vue/src/methods/useMatchObject.ts b/packages/vue/src/methods/useMatchObject.ts index 75e490b8..aecd7e79 100644 --- a/packages/vue/src/methods/useMatchObject.ts +++ b/packages/vue/src/methods/useMatchObject.ts @@ -8,6 +8,16 @@ export interface UseMatchObjectOptions { dataset?: IConnectedLdoDataset; } +export interface UseMatchObjectComposable { + ( + shapeType: MaybeRefOrGetter>, + subject?: MaybeRefOrGetter, + predicate?: MaybeRefOrGetter, + graph?: MaybeRefOrGetter, + options?: MaybeRefOrGetter>, + ): Ref>; +} + /** * @internal * @@ -15,17 +25,20 @@ export interface UseMatchObjectOptions { */ export function createUseMatchObject( dataset: IConnectedLdoDataset, -) { +): UseMatchObjectComposable { /** * Returns a reactive LDO set of matching items. * Triggers a rerender if that data are updated. */ - return function useMatchObject( + return function useMatchObject< + Type extends LdoBase, + CustomPlugins extends ConnectedPlugin[] = Plugins, + >( shapeType: MaybeRefOrGetter>, subject?: MaybeRefOrGetter, predicate?: MaybeRefOrGetter, graph?: MaybeRefOrGetter, - options?: MaybeRefOrGetter>, + options?: MaybeRefOrGetter>, ): Ref> { const createLdo = computed(() => { const subjectValue = toValue(subject); From 61c07f6496c29a67f3ce1236e656044380de322e Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 13 Oct 2026 01:41:27 +0200 Subject: [PATCH 28/32] Prevent unnecessary un- and resubscribing to notifications in useResource and fix import issue --- packages/vue/src/methods/useLinkQuery.ts | 2 +- packages/vue/src/methods/useResource.ts | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/vue/src/methods/useLinkQuery.ts b/packages/vue/src/methods/useLinkQuery.ts index eff50cea..f7ce02d7 100644 --- a/packages/vue/src/methods/useLinkQuery.ts +++ b/packages/vue/src/methods/useLinkQuery.ts @@ -5,7 +5,7 @@ import type { } from "@ldo/connected"; import type { LdoBase, ShapeType } from "@ldo/ldo"; import type { SubjectNode } from "@ldo/rdf-utils"; -import { createUseSubject } from "@ldo/vue"; +import { createUseSubject } from "./useSubject.js"; import { type MaybeRefOrGetter, toValue, watch } from "vue"; /** diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index 3ec1adb6..e65228fa 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -66,12 +66,11 @@ export function createUseResource( const onResourceUpdate = () => { triggerRef(resourceRef); - console.log("TRIGGERING RESOURCE UPDATE REF"); }; watch( [() => toValue(uri), () => toValue(options)], - async ([newUri, newOptions], _, onCleanup) => { + async ([newUri, newOptions], [oldUri, oldOptions], onCleanup) => { // if (newUri === oldUri && newOptions === oldOptions) return; // TODO handle change in uri vs options more carefully @@ -81,11 +80,20 @@ export function createUseResource( onCleanup(async () => { cancelled = true; localResource?.off("update", onResourceUpdate); + }); + + const uriChanged = oldUri !== newUri; + const subscriptionTurnedOff = !newOptions?.subscribe; + + if (uriChanged || subscriptionTurnedOff) { if (subscriptionId) { - localResource?.unsubscribeFromNotifications(subscriptionId); - subscriptionId = undefined; + const sid = subscriptionId; + await resourceRef.value?.unsubscribeFromNotifications(sid); + // clear old subscription id + // but make sure we don't unintentionally clear a new subscription id + if (sid === subscriptionId) subscriptionId = undefined; } - }); + } if (newUri) { localResource = dataset.getResource(newUri) as Resource; @@ -101,7 +109,10 @@ export function createUseResource( if (resourceRef.value) { resourceRef.value.on("update", onResourceUpdate); // Subscribe to notifications if it's needed - if (newOptions?.subscribe) { + if ( + newOptions?.subscribe && + (uriChanged || !oldOptions?.subscribe) + ) { const id = await resourceRef.value.subscribeToNotifications(); if (cancelled) { From 13b499b3a4aaa4bfd196a4283dbaf27136e5f666 Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 13 Oct 2026 02:54:38 +0200 Subject: [PATCH 29/32] update ref after subscription change --- packages/vue/src/methods/useResource.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index e65228fa..ef3ad89b 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -92,6 +92,7 @@ export function createUseResource( // clear old subscription id // but make sure we don't unintentionally clear a new subscription id if (sid === subscriptionId) subscriptionId = undefined; + onResourceUpdate(); } } @@ -114,9 +115,11 @@ export function createUseResource( (uriChanged || !oldOptions?.subscribe) ) { const id = await resourceRef.value.subscribeToNotifications(); + onResourceUpdate(); if (cancelled) { await resourceRef.value.unsubscribeFromNotifications(id); + onResourceUpdate(); } else { subscriptionId = id; } From 002f1f86670b9b54170a638029ca7dd67588dfdb Mon Sep 17 00:00:00 2001 From: mrkvon Date: Tue, 13 Oct 2026 08:18:10 +0200 Subject: [PATCH 30/32] Refactor @ldo/vue types slightly --- packages/vue/src/createLdoVueComposables.ts | 39 +++++++++++---------- packages/vue/src/createLdoVueMethods.ts | 7 ++-- packages/vue/src/methods/useResource.ts | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/vue/src/createLdoVueComposables.ts b/packages/vue/src/createLdoVueComposables.ts index 57bdfa1c..91404540 100644 --- a/packages/vue/src/createLdoVueComposables.ts +++ b/packages/vue/src/createLdoVueComposables.ts @@ -35,10 +35,10 @@ import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubj * const subjectLdo = useSubject(...subjectProps) * ``` */ -export function createLdoVueComposables( - plugins: Plugins, +export function createLdoVueComposables( + plugins: Plugin[], ) { - const DatasetKey: InjectionKey> = + const DatasetKey: InjectionKey> = Symbol("Dataset Key"); function provideDataset() { @@ -49,32 +49,35 @@ export function createLdoVueComposables( return { provideDataset, - useDataset: createComposable(DatasetKey, createUseDataset), - useLdo: createComposable(DatasetKey, createUseLdo), - useResource: createComposable(DatasetKey, createUseResource), - useSubject: createComposable(DatasetKey, createUseSubject), - useMatchObject: createComposable(DatasetKey, createUseMatchObject), + useDataset: createComposable(DatasetKey, createUseDataset), + useLdo: createComposable(DatasetKey, createUseLdo), + useResource: createComposable(DatasetKey, createUseResource), + useSubject: createComposable(DatasetKey, createUseSubject), + useMatchObject: createComposable( + DatasetKey, + createUseMatchObject, + ), useMatchSubject: createComposable( DatasetKey, - createUseMatchSubject, + createUseMatchSubject, ), useSubscribeToResource: createComposable( DatasetKey, - createUseSubscribeToResource, + createUseSubscribeToResource, ), - useLinkQuery: createComposable(DatasetKey, createUseLinkQuery), + useLinkQuery: createComposable(DatasetKey, createUseLinkQuery), useChangeDataset: createComposable( DatasetKey, - createUseChangeDataset, + createUseChangeDataset, ), - useChangeSubject: createComposable(DatasetKey, createUseSubject), + useChangeSubject: createComposable(DatasetKey, createUseSubject), useChangeMatchObject: createComposable( DatasetKey, - createUseChangeMatchObject, + createUseChangeMatchObject, ), useChangeMatchSubject: createComposable( DatasetKey, - createUseChangeMatchSubject, + createUseChangeMatchSubject, ), }; } @@ -85,11 +88,11 @@ export function createLdoVueComposables( * A method to create the composable with injected dataset */ const createComposable = < - Plugins extends ConnectedPlugin[], + Plugin extends ConnectedPlugin, // eslint-disable-next-line @typescript-eslint/no-explicit-any - CreateComposable extends (dataset: ConnectedLdoDataset) => any, + CreateComposable extends (dataset: ConnectedLdoDataset) => any, >( - key: InjectionKey>, + key: InjectionKey>, creator: CreateComposable, ): ReturnType => { // We return a proxy function matching the exact signature layout of the underlying contract diff --git a/packages/vue/src/createLdoVueMethods.ts b/packages/vue/src/createLdoVueMethods.ts index a3ae1280..2cdba671 100644 --- a/packages/vue/src/createLdoVueMethods.ts +++ b/packages/vue/src/createLdoVueMethods.ts @@ -65,10 +65,9 @@ import { createUseDataset } from "./methods/useDataset"; * * ``` */ -export function createLdoVueMethods< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Plugins extends ConnectedPlugin[], ->(plugins: Plugins) { +export function createLdoVueMethods( + plugins: Plugin[], +) { const dataset = createConnectedLdoDataset(plugins); dataset.setMaxListeners(1000); diff --git a/packages/vue/src/methods/useResource.ts b/packages/vue/src/methods/useResource.ts index ef3ad89b..3e9d894e 100644 --- a/packages/vue/src/methods/useResource.ts +++ b/packages/vue/src/methods/useResource.ts @@ -35,7 +35,7 @@ export type useResourceType = { Plugin extends Extract, UriType extends string, >( - uri?: MaybeRefOrGetter, + uri?: MaybeRefOrGetter, options?: MaybeRefOrGetter>, ): Ref | undefined>; }; From 6c87d2ae5fe0d7c23a4cf770308068a39e9dc2ac Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 15 Oct 2026 14:20:57 +0200 Subject: [PATCH 31/32] Small name change and type fix --- packages/vue/src/createLdoVueComposables.ts | 44 ++++++++++++++------- packages/vue/test/Composables.test.ts | 8 ++-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/vue/src/createLdoVueComposables.ts b/packages/vue/src/createLdoVueComposables.ts index 91404540..7ebeba2b 100644 --- a/packages/vue/src/createLdoVueComposables.ts +++ b/packages/vue/src/createLdoVueComposables.ts @@ -38,45 +38,61 @@ import { createUseChangeMatchSubject } from "./methods/change/useChangeMatchSubj export function createLdoVueComposables( plugins: Plugin[], ) { - const DatasetKey: InjectionKey> = + const datasetInjectionKey: InjectionKey> = Symbol("Dataset Key"); function provideDataset() { const dataset = createConnectedLdoDataset(plugins); - provide(DatasetKey, dataset); + provide(datasetInjectionKey, dataset); return dataset; } return { + datasetInjectionKey, provideDataset, - useDataset: createComposable(DatasetKey, createUseDataset), - useLdo: createComposable(DatasetKey, createUseLdo), - useResource: createComposable(DatasetKey, createUseResource), - useSubject: createComposable(DatasetKey, createUseSubject), + useDataset: createComposable( + datasetInjectionKey, + createUseDataset, + ), + useLdo: createComposable(datasetInjectionKey, createUseLdo), + useResource: createComposable( + datasetInjectionKey, + createUseResource, + ), + useSubject: createComposable( + datasetInjectionKey, + createUseSubject, + ), useMatchObject: createComposable( - DatasetKey, + datasetInjectionKey, createUseMatchObject, ), useMatchSubject: createComposable( - DatasetKey, + datasetInjectionKey, createUseMatchSubject, ), useSubscribeToResource: createComposable( - DatasetKey, + datasetInjectionKey, createUseSubscribeToResource, ), - useLinkQuery: createComposable(DatasetKey, createUseLinkQuery), + useLinkQuery: createComposable( + datasetInjectionKey, + createUseLinkQuery, + ), useChangeDataset: createComposable( - DatasetKey, + datasetInjectionKey, createUseChangeDataset, ), - useChangeSubject: createComposable(DatasetKey, createUseSubject), + useChangeSubject: createComposable( + datasetInjectionKey, + createUseSubject, + ), useChangeMatchObject: createComposable( - DatasetKey, + datasetInjectionKey, createUseChangeMatchObject, ), useChangeMatchSubject: createComposable( - DatasetKey, + datasetInjectionKey, createUseChangeMatchSubject, ), }; diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index bd36035f..dbd31be5 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -54,12 +54,12 @@ if (process.env.VITE_IS_TEST === "true") { } describe("LDO Vue Composables", () => { - let methods: ReturnType>; + let methods: ReturnType>; const personResourceUri = "http://localhost:3006/directory/person"; - const personUri = personResourceUri + "#me"; - const otherPersonUri = personResourceUri + "#i"; + const personUri = `${personResourceUri}#me`; + const otherPersonUri = `${personResourceUri}#i`; const person2ResourceUri = "http://localhost:3006/directory/person2"; - const person2Uri = person2ResourceUri + "#me"; + const person2Uri = `${person2ResourceUri}#me`; const foafName = namedNode("http://xmlns.com/foaf/0.1/name"); const foafKnows = namedNode("http://xmlns.com/foaf/0.1/knows"); From 155fefdee88c6f956897d17e8272c66428988afd Mon Sep 17 00:00:00 2001 From: mrkvon Date: Thu, 15 Oct 2026 14:51:36 +0200 Subject: [PATCH 32/32] vue package cleanup --- packages/vue/src/methods/useLinkQuery.ts | 2 +- packages/vue/src/util/useTrackingProxy.ts | 1 - packages/vue/test/Composables.test.ts | 9 ++------- packages/vue/test/test-utils.ts | 1 - 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/vue/src/methods/useLinkQuery.ts b/packages/vue/src/methods/useLinkQuery.ts index f7ce02d7..d024adb3 100644 --- a/packages/vue/src/methods/useLinkQuery.ts +++ b/packages/vue/src/methods/useLinkQuery.ts @@ -5,7 +5,7 @@ import type { } from "@ldo/connected"; import type { LdoBase, ShapeType } from "@ldo/ldo"; import type { SubjectNode } from "@ldo/rdf-utils"; -import { createUseSubject } from "./useSubject.js"; +import { createUseSubject } from "./useSubject"; import { type MaybeRefOrGetter, toValue, watch } from "vue"; /** diff --git a/packages/vue/src/util/useTrackingProxy.ts b/packages/vue/src/util/useTrackingProxy.ts index d8bb7c48..36f65abd 100644 --- a/packages/vue/src/util/useTrackingProxy.ts +++ b/packages/vue/src/util/useTrackingProxy.ts @@ -45,7 +45,6 @@ export function useTrackingProxy( const forceUpdate = () => { triggerRef(linkedDataObject); - console.log("TRIGGERING REF"); }; watch( diff --git a/packages/vue/test/Composables.test.ts b/packages/vue/test/Composables.test.ts index dbd31be5..8b2fdc7d 100644 --- a/packages/vue/test/Composables.test.ts +++ b/packages/vue/test/Composables.test.ts @@ -124,14 +124,12 @@ describe("LDO Vue Composables", () => { expect(result.value.type).toEqual("SolidContainer"); const headersResult = await result.value.getHeaders(); - assert(!headersResult.isError); - - console.log(Object.fromEntries(headersResult.headers.entries())); + expect(headersResult.isError).toBe(false); const rootContainerResult = await result.value.getRootContainer(); assert(!rootContainerResult.isError); - console.log(rootContainerResult.uri); + expect(rootContainerResult.uri).toEqual("http://localhost:3006/"); app.unmount(); }); @@ -162,8 +160,6 @@ describe("LDO Vue Composables", () => { }); it("should handle change in options", async () => { - // s.fetchMock.mockClear(); - const uriRef: Ref = ref( "http://localhost:3006/directory/", ); @@ -816,7 +812,6 @@ describe("LDO Vue Composables", () => { const result = await changeDataset.commitData(); expect(result.isError).toBe(false); - console.log(result); await vi.waitFor(() => { expect(resource.value.isLoading()).toBe(true); diff --git a/packages/vue/test/test-utils.ts b/packages/vue/test/test-utils.ts index 132570d8..0ec07079 100644 --- a/packages/vue/test/test-utils.ts +++ b/packages/vue/test/test-utils.ts @@ -42,7 +42,6 @@ export class RerenderCount { constructor(sources: AllowedSources) { watch(sources, () => { this._count++; - console.log("RERENDER", this._count); }); }