From c943ef36594358376619746733d95ab33f0a079e Mon Sep 17 00:00:00 2001 From: callumalpass Date: Tue, 10 Nov 2026 18:08:10 +1100 Subject: [PATCH] Rework the SDK guide: a full example app, localhost development, and a generated API reference Add a Worklog example app under examples/worklog and rewrite the tutorial around it. New pages cover developing on localhost, sign-in and collections, work before connecting, React, the UI components and the developer kit. The overview now says why to build on mdbase, how an app is published, and where to ask for help. The API reference is generated from the SDK's type declarations, and CI checks the example builds and the reference is current. --- .github/workflows/ci.yml | 7 + README.md | 19 + examples/worklog/.gitignore | 3 + examples/worklog/index.html | 12 + .../worklog/mdbase/types/worklog-entry.md | 21 + examples/worklog/package-lock.json | 2812 +++++++++++ examples/worklog/package.json | 30 + examples/worklog/scripts/write-manifest.mjs | 77 + examples/worklog/src/App.tsx | 25 + examples/worklog/src/Worklog.tsx | 92 + examples/worklog/src/main.tsx | 13 + examples/worklog/src/mdbase.ts | 18 + examples/worklog/src/worklog.css | 73 + examples/worklog/tsconfig.json | 15 + examples/worklog/vite.config.ts | 10 + package.json | 2 + scripts/build-sdk-api.mjs | 250 + src/components/DocText.astro | 21 + src/data/docs-sections.ts | 29 +- src/data/sdk-api.json | 4210 +++++++++++++++++ src/pages/connect/index.astro | 4 +- src/pages/sdk/api/index.astro | 341 +- src/pages/sdk/connect-dev/index.astro | 185 + src/pages/sdk/connect-quickstart/index.astro | 369 +- src/pages/sdk/index.astro | 199 +- src/pages/sdk/lifecycle/index.astro | 438 ++ src/pages/sdk/local-development/index.astro | 256 + src/pages/sdk/manifest/index.astro | 30 +- src/pages/sdk/notifications/index.astro | 2 +- src/pages/sdk/react/index.astro | 204 + src/pages/sdk/security/index.astro | 8 +- src/pages/sdk/ui/index.astro | 262 + src/pages/sdk/unsynced/index.astro | 206 + src/styles/global.css | 13 +- tsconfig.json | 4 +- 35 files changed, 9916 insertions(+), 344 deletions(-) create mode 100644 examples/worklog/.gitignore create mode 100644 examples/worklog/index.html create mode 100644 examples/worklog/mdbase/types/worklog-entry.md create mode 100644 examples/worklog/package-lock.json create mode 100644 examples/worklog/package.json create mode 100644 examples/worklog/scripts/write-manifest.mjs create mode 100644 examples/worklog/src/App.tsx create mode 100644 examples/worklog/src/Worklog.tsx create mode 100644 examples/worklog/src/main.tsx create mode 100644 examples/worklog/src/mdbase.ts create mode 100644 examples/worklog/src/worklog.css create mode 100644 examples/worklog/tsconfig.json create mode 100644 examples/worklog/vite.config.ts create mode 100644 scripts/build-sdk-api.mjs create mode 100644 src/components/DocText.astro create mode 100644 src/data/sdk-api.json create mode 100644 src/pages/sdk/connect-dev/index.astro create mode 100644 src/pages/sdk/lifecycle/index.astro create mode 100644 src/pages/sdk/local-development/index.astro create mode 100644 src/pages/sdk/react/index.astro create mode 100644 src/pages/sdk/ui/index.astro create mode 100644 src/pages/sdk/unsynced/index.astro diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9cfa41..f4a32ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,13 @@ jobs: - name: Install website dependencies run: pnpm install --frozen-lockfile + - name: Check the Worklog example and the generated API reference + run: | + npm ci --prefix examples/worklog + npm run check --prefix examples/worklog + npm run build --prefix examples/worklog + pnpm check:sdk-api + - name: Build contract catalog working-directory: .sources/mdbase-contracts run: npm ci && npm run build diff --git a/README.md b/README.md index 27f5c30..48e0191 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,25 @@ remain traceable to the release artifacts they document. The first-party contract catalog is built from the commit pinned in `site-sources.json`, then published as static files under `/contracts/`. +## SDK guide example and API reference + +`examples/worklog` is the app the SDK tutorial builds. The tutorial and the +other SDK pages import their listings from its files, so a listing cannot drift +from code that compiles. CI installs, type-checks and builds it. + +```sh +cd examples/worklog +npm ci +npm run dev # http://localhost:5173, against production mdbase +npm run build # WORKLOG_ORIGIN=https://… sets the deployed origin +``` + +`/sdk/api/` is rendered from `src/data/sdk-api.json`, which +`pnpm build:sdk-api` reads out of the type declarations of the +`@mdbase-dev/sdk` version the example installs. After changing that version, +run `npm install` in `examples/worklog`, then `pnpm build:sdk-api`, and commit +both. CI fails (`pnpm check:sdk-api`) when the committed file is stale. + ## Connect release updates A published Connect desktop release dispatches its immutable tag to diff --git a/examples/worklog/.gitignore b/examples/worklog/.gitignore new file mode 100644 index 0000000..1cfc60a --- /dev/null +++ b/examples/worklog/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +src/mdbase-app.json diff --git a/examples/worklog/index.html b/examples/worklog/index.html new file mode 100644 index 0000000..088938a --- /dev/null +++ b/examples/worklog/index.html @@ -0,0 +1,12 @@ + + + + + + Worklog + + +
+ + + diff --git a/examples/worklog/mdbase/types/worklog-entry.md b/examples/worklog/mdbase/types/worklog-entry.md new file mode 100644 index 0000000..24bcc81 --- /dev/null +++ b/examples/worklog/mdbase/types/worklog-entry.md @@ -0,0 +1,21 @@ +--- +kind: mdbase.type +name: worklog-entry +version: 1 +description: One entry in a work log. +schema: + dialect: json-schema-2020-12 + value: + $schema: https://json-schema.org/draft/2020-12/schema + type: object + additionalProperties: true + required: [date, summary] + properties: + date: { type: string, format: date } + summary: { type: string, minLength: 1 } + minutes: { type: integer, minimum: 0 } +--- + +# Worklog entry + +What was done on one day, and optionally how long it took. diff --git a/examples/worklog/package-lock.json b/examples/worklog/package-lock.json new file mode 100644 index 0000000..06aa3e5 --- /dev/null +++ b/examples/worklog/package-lock.json @@ -0,0 +1,2812 @@ +{ + "name": "mdbase-worklog-example", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mdbase-worklog-example", + "version": "0.0.0", + "dependencies": { + "@lingui/core": "6.9.0", + "@lingui/react": "6.9.0", + "@mdbase-dev/i18n": "0.1.0-beta.166", + "@mdbase-dev/sdk": "0.1.0-beta.168", + "@mdbase-dev/ui": "0.1.0-beta.166", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@mdbase-dev/connect-dev": "0.1.0-beta.166", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^6.3.0", + "typescript": "^6.0.3", + "vite": "^8.3.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.13.tgz", + "integrity": "sha512-j1nvOQCZYSgp7HLmisnCXFTpVvFODiijQ9QHkORfGKWH6YDtskTEeCJS7eV545GZ9UBzrq0e2xsOd5QQ5PjIqw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-compilation-targets": "^7.29.13", + "@babel/helper-module-transforms": "^7.29.13", + "@babel/helpers": "^7.29.13", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.13", + "@babel/types": "^7.29.11", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.13.tgz", + "integrity": "sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.13", + "@babel/types": "^7.29.11", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.13.tgz", + "integrity": "sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.13.tgz", + "integrity": "sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.13" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz", + "integrity": "sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "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" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.13.tgz", + "integrity": "sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.13.tgz", + "integrity": "sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.11" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@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.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.13.tgz", + "integrity": "sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@fontsource/azeret-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/azeret-mono/-/azeret-mono-5.3.0.tgz", + "integrity": "sha512-Ag/+gN67fUry7i1yLlTnk16Rr1nnTQI2YefA/G7tYcCSr/+TC/ayMrE8Km7O0OKRgI/B5Mfel46Gq9IGY50Uaw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/ibm-plex-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans/-/ibm-plex-sans-5.3.0.tgz", + "integrity": "sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lingui/babel-plugin-extract-messages": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-6.9.0.tgz", + "integrity": "sha512-udKlLtGtJ9TjVjqtogIage/Z3merz5Ko5u72uIaHMZ9aPFjSNDnrsglWEfMLWTeSxmkdOrASDpn8YuBMLbhvPA==", + "license": "MIT", + "dependencies": { + "@lingui/conf": "6.9.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/babel-plugin-lingui-macro": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-6.9.0.tgz", + "integrity": "sha512-AwbD2JKdy3L0Vppv5X8xPZJzgY4W9Y+DcH5Tzb7oZA6o96N+VVeJU9xsLFbh5UGIQAwLeY7BZKDH88qmWj1gUA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7", + "@lingui/conf": "6.9.0", + "@lingui/message-utils": "6.9.0" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "@babel/core": "^7.20.12 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@lingui/cli": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-6.9.0.tgz", + "integrity": "sha512-/fO982w4GsMsxs5kTf/MnvkpD5/hm3c5uFaPmTWilvf5lnaJbjA3aBaBYliRocYDhvD71xMvLqcsfITB4wC4NA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.0", + "@babel/generator": "^7.28.5", + "@babel/parser": "^7.22.0", + "@babel/types": "^7.21.2", + "@lingui/babel-plugin-extract-messages": "6.9.0", + "@lingui/babel-plugin-lingui-macro": "6.9.0", + "@lingui/conf": "6.9.0", + "@lingui/core": "6.9.0", + "@lingui/format-po": "6.9.0", + "@lingui/message-utils": "6.9.0", + "@lingui/native-tools": "^0.1.5", + "chokidar": "5.0.0", + "cli-table3": "^0.6.5", + "commander": "^14.0.2", + "jiti": "^2.6.1", + "micromatch": "^4.0.7", + "ms": "^2.1.3", + "normalize-path": "^3.0.0", + "ora": "^9.1.0", + "pseudolocale": "^3.1.0", + "source-map": "^0.7.6", + "tinypool": "^2.1.0" + }, + "bin": { + "lingui": "dist/lingui.js" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "esbuild": "^0.28.1", + "rolldown": "^1.0.0" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + } + } + }, + "node_modules/@lingui/conf": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-6.9.0.tgz", + "integrity": "sha512-1fwYejoGLeoy5P1/mop0WP35fDRXkB7fUFxyPV5E+vBbgOXtw+QDRt1y9WtyqjI+dz9zQtNYmND3+tBSeAADfw==", + "license": "MIT", + "dependencies": { + "jest-validate": "^29.4.3", + "jiti": "^2.5.1", + "lilconfig": "^3.1.3", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/core": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-6.9.0.tgz", + "integrity": "sha512-woQCOsmbG3LxhqTyECcpxn6t3V5tQOgYOG7LL424a/dmpKttyqtJ1/qq3/U7H+ZG+ewFHh56UvQrXSJVnjCf2g==", + "license": "MIT", + "dependencies": { + "@lingui/babel-plugin-lingui-macro": "6.9.0", + "@lingui/message-utils": "6.9.0" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "babel-plugin-macros": "2 || 3" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/@lingui/format-po": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-6.9.0.tgz", + "integrity": "sha512-nfeAxAj3An1zq0F8k01fvw+cMgqU4KAIDemylwNnTI+Mamo1x6WUo+vtpaJQjtDDTPBMVKkMoGx9G9fpy5xrtQ==", + "license": "MIT", + "dependencies": { + "@lingui/conf": "6.9.0", + "@lingui/message-utils": "6.9.0", + "pofile-ts": "^4.0.3" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/message-utils": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-6.9.0.tgz", + "integrity": "sha512-LaFvx9Vc/lx4EPbbDaiDtyumDM4aAglRA3yvFQsJkhs1UWrCgDaaUBWhkXKYcmVe9gb7qeGYCqmGWOL84N9xDw==", + "license": "MIT", + "dependencies": { + "@messageformat/date-skeleton": "^1.1.0", + "@messageformat/parser": "^5.0.0", + "js-sha256": "^0.10.1" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools/-/native-tools-0.1.6.tgz", + "integrity": "sha512-xsn2R9YvWL9WuemM0ivSlXy14ORlfvPYLxFP3kbcFU3Pg5T9aRnmYOTfwszwGnjt4QsORO1mHHZT6k/Wr7V9pA==", + "license": "MIT", + "dependencies": { + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@lingui/native-tools-android-arm64": "0.1.6", + "@lingui/native-tools-darwin-arm64": "0.1.6", + "@lingui/native-tools-darwin-x64": "0.1.6", + "@lingui/native-tools-linux-arm64-gnu": "0.1.6", + "@lingui/native-tools-linux-arm64-musl": "0.1.6", + "@lingui/native-tools-linux-x64-gnu": "0.1.6", + "@lingui/native-tools-linux-x64-musl": "0.1.6", + "@lingui/native-tools-win32-arm64-msvc": "0.1.6", + "@lingui/native-tools-win32-x64-msvc": "0.1.6" + }, + "peerDependencies": { + "@lingui/conf": "^6.0.1" + } + }, + "node_modules/@lingui/native-tools-android-arm64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-android-arm64/-/native-tools-android-arm64-0.1.6.tgz", + "integrity": "sha512-kAwVqJlKv868dGJOhqXXqHwhI3bOlvdHSZz8rozlEEidvBuw6Srr0Es5hwisLlwybzcTYstjOeVGxmvu3QAV4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-darwin-arm64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-darwin-arm64/-/native-tools-darwin-arm64-0.1.6.tgz", + "integrity": "sha512-0KEoSob+pYS1jyRB+2bJp78L2L2i/VODFIk5Gs8LmKV58xZ+uN8MOhf1dYBBsi6+0tiqPartSCk3WA4i+lKPUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-darwin-x64": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-darwin-x64/-/native-tools-darwin-x64-0.1.6.tgz", + "integrity": "sha512-e7liIjkzbJ8w3F9PHzZPmpYpeiv7XO4Np9J1tEpUamKYEdYBNt4AKr/UmWAQ7AjxbZSV9N8lj1ulUl3pdFUuaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-linux-arm64-gnu": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-linux-arm64-gnu/-/native-tools-linux-arm64-gnu-0.1.6.tgz", + "integrity": "sha512-JKu/8CvLsYUiRbRSnuzmrQk24tfuizgnDYlMxE5/awUUrRE+GlpaMP76PhJpkl+tV0v7t6zVe3+I5lbq7LaqVw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-linux-arm64-musl": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-linux-arm64-musl/-/native-tools-linux-arm64-musl-0.1.6.tgz", + "integrity": "sha512-Aeexf/Ta0nZDZz3cDMAZ95GmFZ2lOxMwf1FK/ShK+U4WcNVSn3aI2KeuOeaLUfot9ZT8AiIIELPV4gO6b/GJgg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-linux-x64-gnu": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-linux-x64-gnu/-/native-tools-linux-x64-gnu-0.1.6.tgz", + "integrity": "sha512-SwX1dLcQE9pVcXwIxSGTE2+zBmaO6NbjD9XPqCiyoO8RFTkUTLjdnRDGHxFlFpIGMlVArQz5mMQ6coyUFl+AiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-linux-x64-musl": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-linux-x64-musl/-/native-tools-linux-x64-musl-0.1.6.tgz", + "integrity": "sha512-aWI8e10I6i7K9BG77LJDHREozx/VT8j8onf0wxd43ACor2S1VwS0ZNlIfpNWQTWovmU/CUliGO/VWQq2jGa+Sg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-win32-arm64-msvc": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-win32-arm64-msvc/-/native-tools-win32-arm64-msvc-0.1.6.tgz", + "integrity": "sha512-M2TZP4gKgLaLbiboXIrY5+8GsaQLHvZ8E6cUFgAuSaBa8AMa73+pZO1eay/St9t8yDh6EL6hOlqN0HedUHMwyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/native-tools-win32-x64-msvc": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@lingui/native-tools-win32-x64-msvc/-/native-tools-win32-x64-msvc-0.1.6.tgz", + "integrity": "sha512-5KvuSTFtDB8UGHS3Rxd+juYZgc51isQSx0hPukDObre6UxZGuUVCBmJ9YwoGVzXedViiNGmsQvrnCTBbVqI75g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@lingui/react": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-6.9.0.tgz", + "integrity": "sha512-0oSy/GpQUzMrylviy/IiDg6b0DH4HwGusSD8ht2dsDCdunH+wX8bfJ/njo7qtjyaawTjEbdyLCUzlZc2Gd2kVQ==", + "license": "MIT", + "dependencies": { + "@lingui/babel-plugin-lingui-macro": "6.9.0", + "@lingui/core": "6.9.0", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "babel-plugin-macros": "2 || 3", + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/@mdbase-dev/connect": { + "version": "0.1.0-beta.166", + "resolved": "https://registry.npmjs.org/@mdbase-dev/connect/-/connect-0.1.0-beta.166.tgz", + "integrity": "sha512-LApM4CInwayn9ZJ/nUbOEKIPpbDutwYDerW3cpfECP8qDAJEGtbPeluDL1voHu7QZqInKjpKyWC4AJoJHvcGJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdbase-dev/connect-protocol": "0.1.0-beta.166" + } + }, + "node_modules/@mdbase-dev/connect-dev": { + "version": "0.1.0-beta.166", + "resolved": "https://registry.npmjs.org/@mdbase-dev/connect-dev/-/connect-dev-0.1.0-beta.166.tgz", + "integrity": "sha512-olylXeMyvkZJkc2pYg4Gu3u5D0VdwLSG3Cet6OkVboJVyQ2r8hWueL2oU5HAKS/C13hRPW8/nxKwJsqu4hLtmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdbase-dev/connect": "0.1.0-beta.166", + "@mdbase-dev/connect-protocol": "0.1.0-beta.166", + "ajv": "^8.20.0", + "yaml": "^2.9.1" + }, + "bin": { + "mdbase-connect-dev": "dist/cli.js" + } + }, + "node_modules/@mdbase-dev/connect-protocol": { + "version": "0.1.0-beta.166", + "resolved": "https://registry.npmjs.org/@mdbase-dev/connect-protocol/-/connect-protocol-0.1.0-beta.166.tgz", + "integrity": "sha512-owBhZOSO6DbDEgdMS2Jl3Nut0wPs4DjbHKh6gLgfDGooxrnQz9ZqTzDvwMiI2aDW3VGelFfIRUZY6XHrLzhCjQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0", + "yaml": "^2.9.1" + } + }, + "node_modules/@mdbase-dev/i18n": { + "version": "0.1.0-beta.166", + "resolved": "https://registry.npmjs.org/@mdbase-dev/i18n/-/i18n-0.1.0-beta.166.tgz", + "integrity": "sha512-Ny4M+yQFf1D9CiSjoDLRB9xrVTKBbbxw6FhbHuGV4PAsXvZPWH5Tlq+vb+MsSXwXeXFhPYJBl6wffvUN/SZQNw==", + "license": "MIT", + "dependencies": { + "@lingui/cli": "6.9.0", + "@lingui/conf": "6.9.0", + "@lingui/format-po": "6.9.0", + "@lingui/message-utils": "6.9.0", + "typescript": "6.0.3" + }, + "bin": { + "mdbase-i18n": "scripts/bin.mjs" + }, + "peerDependencies": { + "@lingui/core": "6.9.0", + "@lingui/react": "6.9.0", + "react": "^19.2.0" + } + }, + "node_modules/@mdbase-dev/sdk": { + "version": "0.1.0-beta.168", + "resolved": "https://registry.npmjs.org/@mdbase-dev/sdk/-/sdk-0.1.0-beta.168.tgz", + "integrity": "sha512-hwQhUAdknThDhGU3QxKVMBZlXZb/HnEl7dhuc9qPFUX75diTFPjyzAwPhJbbgnIh1c1PtT9aUyuUlaVtafZ/6Q==", + "bundleDependencies": [ + "@mdbase-dev/connect-protocol" + ], + "license": "MIT", + "dependencies": { + "@mdbase-dev/connect-protocol": "file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.145-e6e66bcd172f.tgz", + "@noble/ciphers": "2.4.0", + "@noble/curves": "2.4.0", + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@mdbase-dev/sdk/node_modules/@mdbase-dev/connect-protocol": { + "version": "0.1.0-beta.145", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0", + "yaml": "^2.9.1" + } + }, + "node_modules/@mdbase-dev/sdk/node_modules/@noble/hashes": { + "version": "2.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@mdbase-dev/sdk/node_modules/yaml": { + "version": "2.9.1", + "inBundle": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@mdbase-dev/ui": { + "version": "0.1.0-beta.166", + "resolved": "https://registry.npmjs.org/@mdbase-dev/ui/-/ui-0.1.0-beta.166.tgz", + "integrity": "sha512-Bv6WfY/tT7f8wlZBu+Ll7KyjmL/pSjGQiKcQidlW82OWUmtX+fxAXbOPu8/OINNxyYS+8mWLaB0lCjBRZK0Exg==", + "license": "MIT", + "dependencies": { + "@fontsource/azeret-mono": "^5.3.0", + "@fontsource/ibm-plex-sans": "^5.3.0" + }, + "peerDependencies": { + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.1", + "@lezer/common": "^1.0.0", + "@lingui/core": "6.9.0", + "@lingui/react": "6.9.0", + "@mdbase-dev/i18n": "*", + "@mdbase-dev/sdk": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "peerDependenciesMeta": { + "@codemirror/commands": { + "optional": true + }, + "@codemirror/language": { + "optional": true + }, + "@codemirror/state": { + "optional": true + }, + "@codemirror/view": { + "optional": true + }, + "@lezer/common": { + "optional": true + }, + "@mdbase-dev/sdk": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@messageformat/date-skeleton": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz", + "integrity": "sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==", + "license": "MIT" + }, + "node_modules/@messageformat/parser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.2.0.tgz", + "integrity": "sha512-sc3hNZ0JtC32GhL5nUgPbh2ilcwji4G+Vmj/vhMd8hZaH//NDOvkfwKK1mFnRWG5h2DpksN8xr/HB5/+vMg3iw==", + "license": "MIT", + "dependencies": { + "moo": "^0.5.1" + } + }, + "node_modules/@noble/ciphers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz", + "integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz", + "integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz", + "integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz", + "integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz", + "integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz", + "integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz", + "integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz", + "integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz", + "integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz", + "integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==", + "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.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz", + "integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz", + "integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz", + "integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz", + "integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==", + "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.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz", + "integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.0.tgz", + "integrity": "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "26.6.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz", + "integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.3.0.tgz", + "integrity": "sha512-H9E955aVvD2F4zNHCw4eIZbIfF6xKldlh7MNu+hSU7xwQzh65lI/N+CJWqmfhi0PEjDXIt/H7T7J/4wU63KJiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0 || ^0.3.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": ">=0.152.0 <0.157.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz", + "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.33", + "caniuse-lite": "^1.0.30001819", + "electron-to-chromium": "^1.5.464", + "node-releases": "^2.0.60", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001823", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001823.tgz", + "integrity": "sha512-TQ5s/djDjNk1axreUW+4rULH+BmdCMLiJkhaXnO5wqivG7tITkSUj4ItIdzqdZHKaXcHAV8dhH3JxOM7/7bwqw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.479", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.479.tgz", + "integrity": "sha512-uAJdJQ7vHa4TvPfzt1TOqoRgHmav5gwmqHMPLxBH6m8Cbj5R9/z/rygTOQiZ8pe5g0ZAEogga1bP6HoKcOzx/Q==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-sha256": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", + "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "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": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "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" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "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" + ], + "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" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.5.tgz", + "integrity": "sha512-aRAOI9Ia43ygL4o1EOmC/sc/0OFxrm7qcQngiIyWaqVnSnz932ybJ07zyCcgMNGSDP8Yxaf88Xj1zHEXmvQ4SA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "license": "BSD-3-Clause" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.63", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz", + "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", + "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.2", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.6.0.tgz", + "integrity": "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pofile-ts": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/pofile-ts/-/pofile-ts-4.0.3.tgz", + "integrity": "sha512-sz1pnjgEfPyZ+QvaeX3NtCmbYnEvG01LZRLoN/uXoLtPZtxCIH5IctL7yXXc0fFyk/fqV6K8g3hlNfr6IJwupA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pseudolocale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-3.1.0.tgz", + "integrity": "sha512-jBidehQu5WO8zbFUod0ETJYoVPxZ0ixsdI9B8ilkE0eA+YTydxHA7ujNdU4YhTexD5yH5Griv9r5CuXx+nwFUA==", + "license": "MIT", + "dependencies": { + "commander": "^13.1.0" + }, + "bin": { + "pseudolocale": "dist/cli.mjs" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/pseudolocale/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rolldown": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz", + "integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.156.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.18", + "@rolldown/binding-android-arm64": "1.2.18", + "@rolldown/binding-darwin-arm64": "1.2.18", + "@rolldown/binding-darwin-x64": "1.2.18", + "@rolldown/binding-freebsd-x64": "1.2.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.18", + "@rolldown/binding-linux-arm64-gnu": "1.2.18", + "@rolldown/binding-linux-arm64-musl": "1.2.18", + "@rolldown/binding-linux-ppc64-gnu": "1.2.18", + "@rolldown/binding-linux-s390x-gnu": "1.2.18", + "@rolldown/binding-linux-x64-gnu": "1.2.18", + "@rolldown/binding-linux-x64-musl": "1.2.18", + "@rolldown/binding-openharmony-arm64": "1.2.18", + "@rolldown/binding-win32-arm64-msvc": "1.2.18", + "@rolldown/binding-win32-x64-msvc": "1.2.18" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/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/tinypool": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.2.0.tgz", + "integrity": "sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==", + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "8.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz", + "integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.32", + "rolldown": "~1.2.18", + "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.7.1 || ^0.8.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 + } + } + }, + "node_modules/vite/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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/examples/worklog/package.json b/examples/worklog/package.json new file mode 100644 index 0000000..9292631 --- /dev/null +++ b/examples/worklog/package.json @@ -0,0 +1,30 @@ +{ + "name": "mdbase-worklog-example", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "manifest": "node scripts/write-manifest.mjs", + "dev": "npm run manifest -- --dev && vite", + "check": "npm run manifest && tsc --noEmit", + "build": "npm run manifest && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@lingui/core": "6.9.0", + "@lingui/react": "6.9.0", + "@mdbase-dev/i18n": "0.1.0-beta.166", + "@mdbase-dev/sdk": "0.1.0-beta.168", + "@mdbase-dev/ui": "0.1.0-beta.166", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@mdbase-dev/connect-dev": "0.1.0-beta.166", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^6.3.0", + "typescript": "^6.0.3", + "vite": "^8.3.7" + } +} diff --git a/examples/worklog/scripts/write-manifest.mjs b/examples/worklog/scripts/write-manifest.mjs new file mode 100644 index 0000000..583a3cf --- /dev/null +++ b/examples/worklog/scripts/write-manifest.mjs @@ -0,0 +1,77 @@ +// Writes src/mdbase-app.json, the manifest the app passes to createMdbase(). +// +// node scripts/write-manifest.mjs the deployed app (WORKLOG_ORIGIN) +// node scripts/write-manifest.mjs --dev the app on http://localhost:5173 +import { readFile, writeFile } from "node:fs/promises"; + +import { + defineTypePack, + formatValidationIssues, + validateAppManifest, +} from "@mdbase-dev/connect-dev"; + +const development = process.argv.includes("--dev"); +const origin = development + ? "http://localhost:5173" + : (process.env.WORKLOG_ORIGIN ?? "https://worklog.example"); + +const read = (path) => readFile(new URL(`../mdbase/${path}`, import.meta.url), "utf8"); + +// The type the app's records use. A collection that lacks it is offered this +// copy during setup; after that the file belongs to the collection. +const types = defineTypePack({ + id: "dev.example.worklog", + version: "1.0.0", + name: "Worklog", + description: "The worklog-entry type.", + resources: [ + { + kind: "type", + mode: "seed", + source: "types/worklog-entry.md", + target: "_types/worklog-entry.md", + document: await read("types/worklog-entry.md"), + }, + ], +}); + +const manifest = { + manifest_version: 1, + id: "dev.example.worklog", + // A development build is a separate application to mdbase, so name it as one. + name: development ? "Worklog (development)" : "Worklog", + homepage: `${origin}/`, + redirect_uris: [`${origin}/`], + requirements: { + access: "full_collection", + contracts: [], + capabilities: { + contract_version: 2, + // definitions.manage lets setup add the worklog-entry type to a collection. + required: [ + "collection.read", + "records.create", + "records.edit", + "records.delete", + "definitions.manage", + ], + }, + files: { + required: ["list", "read", "add", "replace", "move", "delete"], + scope: { kind: "collection" }, + }, + }, + provisions: { type_packs: [types] }, +}; + +const result = validateAppManifest(manifest, { allowLocal: development }); +if (!result.valid) { + console.error(formatValidationIssues(result.issues)); + process.exit(1); +} + +await writeFile( + new URL("../src/mdbase-app.json", import.meta.url), + `${JSON.stringify(manifest, null, 2)}\n`, +); +console.log(`Wrote src/mdbase-app.json for ${origin}`); diff --git a/examples/worklog/src/App.tsx b/examples/worklog/src/App.tsx new file mode 100644 index 0000000..3fce300 --- /dev/null +++ b/examples/worklog/src/App.tsx @@ -0,0 +1,25 @@ +import { CollectionGate, CollectionSwitcher } from "@mdbase-dev/ui/collections"; + +import { mdbase } from "./mdbase"; +import { Worklog } from "./Worklog"; + +const app = { name: "Worklog" }; + +export function App() { + return ( + // The gate shows sign-in, the collection list, approval, setup and + // problems. It renders its children once a collection is open. + + {(connection) => ( +
+
+

Worklog

+ +
+ {/* A new connection means a different collection: start again. */} + +
+ )} +
+ ); +} diff --git a/examples/worklog/src/Worklog.tsx b/examples/worklog/src/Worklog.tsx new file mode 100644 index 0000000..a0326b3 --- /dev/null +++ b/examples/worklog/src/Worklog.tsx @@ -0,0 +1,92 @@ +import { describeError } from "@mdbase-dev/sdk/describe"; +import type { MdbaseClient, RecordView } from "@mdbase-dev/sdk"; +import { type SyntheticEvent, useEffect, useState } from "react"; + +const today = () => new Date().toLocaleDateString("en-CA"); // YYYY-MM-DD + +// The SDK's plain-language account of a failed call, or null when there is +// nothing to show (the person cancelled, say). +const explain = (error: unknown) => describeError(error)?.message ?? null; + +export function Worklog({ db }: { db: MdbaseClient }) { + const [entries, setEntries] = useState([]); + const [problem, setProblem] = useState(null); + + // A live query: the current entries first, then every change, including + // ones made in another app or on another device. + useEffect(() => { + const query = db.live({ + types: ["worklog-entry"], + order_by: [{ field: "date", direction: "desc" }], + limit: 100, + }); + const stop = query.subscribe((state) => setEntries(state.records)); + return () => { + stop(); + query.close(); + }; + }, [db]); + + async function add(event: SyntheticEvent) { + event.preventDefault(); + const form = event.currentTarget; + const fields = new FormData(form); + const date = String(fields.get("date")); + const minutes = Number(fields.get("minutes")); + try { + // A record is a Markdown file, so it needs a path. The write returns as + // soon as it is saved on this device; the live query above already shows + // it, and syncing continues in the background. + const write = await db.create({ + type: "worklog-entry", + path: `worklog/${date}-${crypto.randomUUID().slice(0, 8)}.md`, + frontmatter: { + date, + summary: String(fields.get("summary")), + ...(minutes > 0 ? { minutes } : {}), + }, + }); + // An entry the type does not allow is refused here, before it is saved. + if (write.state === "rejected") { + setProblem(explain(write.receipt.problem)); + return; + } + form.reset(); + setProblem(null); + } catch (error) { + setProblem(explain(error)); + } + } + + async function remove(entry: RecordView) { + try { + const write = await db.delete(entry); + setProblem(write.state === "rejected" ? explain(write.receipt.problem) : null); + } catch (error) { + setProblem(explain(error)); + } + } + + return ( + <> +
+ + + + +
+ {problem ?

{problem}

: null} +
    + {entries.map((entry) => ( +
  • + + {String(entry.frontmatter.get("summary"))} + +
  • + ))} +
+ + ); +} diff --git a/examples/worklog/src/main.tsx b/examples/worklog/src/main.tsx new file mode 100644 index 0000000..396caf3 --- /dev/null +++ b/examples/worklog/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import "@mdbase-dev/ui/collections.css"; +import "./worklog.css"; + +import { App } from "./App"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/examples/worklog/src/mdbase.ts b/examples/worklog/src/mdbase.ts new file mode 100644 index 0000000..1b4b932 --- /dev/null +++ b/examples/worklog/src/mdbase.ts @@ -0,0 +1,18 @@ +import { createMdbase } from "@mdbase-dev/sdk"; + +import manifest from "./mdbase-app.json"; + +// One front door for the whole app. `origin` is the origin the app is served +// from, which the manifest's homepage already names. +export const mdbase = createMdbase( + { + id: manifest.id, + name: manifest.name, + origin: new URL(manifest.homepage).origin, + environment: "production", + manifest, + }, + // Let people try the app before they sign in. Their entries stay in this + // browser until they bring them into a collection. + { unsynced: true }, +); diff --git a/examples/worklog/src/worklog.css b/examples/worklog/src/worklog.css new file mode 100644 index 0000000..ab38fc4 --- /dev/null +++ b/examples/worklog/src/worklog.css @@ -0,0 +1,73 @@ +:root { + font-family: system-ui, sans-serif; + color-scheme: light; +} + +body { + margin: 0; +} + +.worklog { + max-width: 40rem; + margin: 0 auto; + padding: 1.5rem 1rem; +} + +.worklog header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.worklog h1 { + font-size: 1.25rem; + margin: 0; +} + +.worklog form { + display: grid; + grid-template-columns: auto 1fr 6rem auto; + gap: 0.5rem; + margin: 1.5rem 0; +} + +.worklog input, +.worklog form button { + font: inherit; + padding: 0.4rem 0.6rem; +} + +.worklog ul { + list-style: none; + margin: 0; + padding: 0; +} + +.worklog li { + display: grid; + grid-template-columns: 7rem 1fr auto; + gap: 0.75rem; + align-items: baseline; + padding: 0.5rem 0; + border-top: 1px solid #ddd; +} + +.worklog time { + font-variant-numeric: tabular-nums; + color: #555; +} + +.worklog li button { + font: inherit; + border: 0; + background: none; + color: #555; + cursor: pointer; +} + +@media (max-width: 30rem) { + .worklog form { + grid-template-columns: 1fr 1fr; + } +} diff --git a/examples/worklog/tsconfig.json b/examples/worklog/tsconfig.json new file mode 100644 index 0000000..84529e8 --- /dev/null +++ b/examples/worklog/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/worklog/vite.config.ts b/examples/worklog/vite.config.ts new file mode 100644 index 0000000..0dcd415 --- /dev/null +++ b/examples/worklog/vite.config.ts @@ -0,0 +1,10 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// The SDK needs no bundler configuration: Vite serves its worker and +// WebAssembly files in development and emits them as assets in a build. +export default defineConfig({ + plugins: [react()], + // The development manifest names this exact origin. + server: { port: 5173, strictPort: true }, +}); diff --git a/package.json b/package.json index 1cb494c..775cc77 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "import:spec": "node scripts/import-spec.mjs", "index:site": "pagefind --site dist && node scripts/build-llms.mjs", "check:links": "node scripts/check-links.mjs", + "build:sdk-api": "node scripts/build-sdk-api.mjs", + "check:sdk-api": "node scripts/build-sdk-api.mjs --check", "check:release": "node scripts/check-connect-release.mjs", "check:release-order": "node scripts/check-connect-release-order.mjs", "test:deploy:dev": "node --test scripts/deploy-pages-dev.test.mjs", diff --git a/scripts/build-sdk-api.mjs b/scripts/build-sdk-api.mjs new file mode 100644 index 0000000..1ab40ae --- /dev/null +++ b/scripts/build-sdk-api.mjs @@ -0,0 +1,250 @@ +// Builds src/data/sdk-api.json, the data behind /sdk/api/, from the type +// declarations of the published @mdbase-dev/sdk package that the Worklog +// example installs. Signatures, members and descriptions are read from the +// declarations; this file chooses only which entry points and names are shown +// in full. +// +// node scripts/build-sdk-api.mjs write src/data/sdk-api.json +// node scripts/build-sdk-api.mjs --check fail if the committed file is stale +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const output = join(root, "src", "data", "sdk-api.json"); +const packageDir = join(root, "examples", "worklog", "node_modules", "@mdbase-dev", "sdk"); + +// Entry points an app imports from, in reading order, with the names shown in +// full on the page. Every other export is listed by name in the index. +const ENTRIES = [ + { + specifier: "@mdbase-dev/sdk", + title: "Core", + featured: [ + "createMdbase", + "Mdbase", + "MdbaseApplicationDeclaration", + "MdbaseConnectOptions", + "MdbaseSnapshot", + "MdbasePhase", + "MdbaseApplicationConnection", + "MdbaseSetupPlan", + "MdbaseSetupResult", + "MdbaseAccessRequest", + "MdbaseAccessResult", + "MdbaseTransferPlan", + "connect", + "ClientOptions", + "MdbaseClient", + "Query", + "CreateInput", + "UpdateInput", + "WriteOptions", + "Write", + "SaveResult", + "LiveQuery", + "RecordView", + "Receipt", + "MdbaseError", + "isMdbaseError", + "summarizeStatus", + ], + }, + { + specifier: "@mdbase-dev/sdk/describe", + title: "Problem descriptions", + featured: ["describeError", "describeDiagnostic", "MdbaseErrorDescription", "MdbaseErrorAction"], + }, + { + specifier: "@mdbase-dev/sdk/testing", + title: "Testing", + featured: ["MemoryReplica", "createApplicationFixture"], + }, + { specifier: "@mdbase-dev/sdk/node", title: "Node connectors", featured: [] }, + { specifier: "@mdbase-dev/sdk/manifest", title: "Manifest", featured: [] }, + { specifier: "@mdbase-dev/sdk/notifications", title: "Notifications", featured: [] }, + { specifier: "@mdbase-dev/sdk/account", title: "Account keys", featured: [] }, + { specifier: "@mdbase-dev/sdk/app-grant", title: "App grants", featured: [] }, +]; + +if (!existsSync(join(packageDir, "package.json"))) { + throw new Error( + `Missing ${packageDir}. Run npm ci in examples/worklog first: the API reference is read from the SDK it installs.`, + ); +} + +const pkg = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8")); +const entryFile = (specifier) => { + const key = specifier === pkg.name ? "." : `.${specifier.slice(pkg.name.length)}`; + const types = pkg.exports?.[key]?.types; + if (!types) throw new Error(`${pkg.name}@${pkg.version} has no entry point ${key}`); + return join(packageDir, types); +}; + +const program = ts.createProgram( + ENTRIES.map((entry) => entryFile(entry.specifier)), + { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + lib: ["lib.es2022.d.ts", "lib.dom.d.ts"], + skipLibCheck: true, + noEmit: true, + types: [], + }, +); +const checker = program.getTypeChecker(); + +const KINDS = new Map([ + [ts.SyntaxKind.FunctionDeclaration, "function"], + [ts.SyntaxKind.ClassDeclaration, "class"], + [ts.SyntaxKind.InterfaceDeclaration, "interface"], + [ts.SyntaxKind.TypeAliasDeclaration, "type"], + [ts.SyntaxKind.EnumDeclaration, "enum"], + [ts.SyntaxKind.VariableDeclaration, "constant"], + [ts.SyntaxKind.ModuleDeclaration, "namespace"], +]); + +const oneLine = (text) => text.replace(/\s+/g, " ").trim(); +const docs = (symbol) => + ts + .displayPartsToString(symbol.getDocumentationComment(checker)) + .replace(/\{@link\s+([^}\s]+)[^}]*\}/g, "`$1`") + .replace(/\r/g, "") + .trim(); +const deprecated = (symbol) => { + const tag = symbol.getJsDocTags(checker).find((item) => item.name === "deprecated"); + return tag ? oneLine(ts.displayPartsToString(tag.text)) || true : undefined; +}; + +/** The declaration as written, without the `export declare` prefix or a body's worth of comments. */ +function declarationText(node) { + const statement = ts.isVariableDeclaration(node) ? node.parent.parent : node; + return statement + .getText() + .replace(/^export\s+/, "") + .replace(/^declare\s+/, ""); +} + +function hidden(member) { + const flags = ts.getCombinedModifierFlags(member); + if (flags & (ts.ModifierFlags.Private | ts.ModifierFlags.Protected)) return true; + const name = member.name?.getText() ?? ""; + return name.startsWith("_") || name.startsWith("#"); +} + +function memberList(node) { + const members = []; + for (const member of node.members ?? []) { + if (hidden(member)) continue; + const symbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined; + const name = ts.isConstructorDeclaration(member) ? "constructor" : (member.name?.getText() ?? ""); + if (!name) continue; + const item = { + name, + signature: oneLine(member.getText().replace(/;$/, "")), + description: symbol ? docs(symbol) : "", + }; + const marked = symbol ? deprecated(symbol) : undefined; + if (marked) item.deprecated = marked; + members.push(item); + } + return members; +} + +function describeExport(exported, featured) { + const symbol = exported.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported; + const declarations = symbol.declarations ?? []; + // A name that is both a value and a type (a class, or a const beside an + // interface) is described by its richest declaration. + const node = + declarations.find((item) => ts.isClassDeclaration(item)) ?? + declarations.find((item) => ts.isFunctionDeclaration(item)) ?? + declarations.find((item) => ts.isInterfaceDeclaration(item)) ?? + declarations[0]; + if (!node) return null; + const item = { + name: exported.name, + kind: KINDS.get(node.kind) ?? "value", + description: docs(symbol), + }; + const marked = deprecated(symbol); + if (marked) item.deprecated = marked; + if (!featured) return item; + + if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) { + const heritage = (node.heritageClauses ?? []).map((clause) => oneLine(clause.getText())).join(" "); + const parameters = node.typeParameters?.length + ? `<${node.typeParameters.map((parameter) => parameter.getText()).join(", ")}>` + : ""; + item.signature = oneLine(`${item.kind} ${exported.name}${parameters} ${heritage}`); + item.members = memberList(node); + } else if (ts.isFunctionDeclaration(node)) { + // Overloads are separate declarations of the same symbol. + item.signature = declarations + .filter((declaration) => ts.isFunctionDeclaration(declaration)) + .map(declarationText) + .join("\n"); + } else { + item.signature = declarationText(node); + } + return item; +} + +const entries = ENTRIES.map((entry) => { + const source = program.getSourceFile(entryFile(entry.specifier)); + const module = source && checker.getSymbolAtLocation(source); + if (!module) throw new Error(`Cannot read the exports of ${entry.specifier}`); + const exported = new Map(checker.getExportsOfModule(module).map((symbol) => [symbol.name, symbol])); + const missing = entry.featured.filter((name) => !exported.has(name)); + if (missing.length) { + throw new Error(`${entry.specifier} no longer exports ${missing.join(", ")}; update ENTRIES in ${import.meta.url}`); + } + const featured = entry.featured.map((name) => describeExport(exported.get(name), true)); + const featuredNames = new Set(entry.featured); + const index = [...exported.values()] + .filter((symbol) => !featuredNames.has(symbol.name)) + .map((symbol) => describeExport(symbol, false)) + .filter(Boolean) + .map((item) => ({ ...item, description: firstSentence(item.description) })) + .sort((left, right) => left.name.localeCompare(right.name, "en")); + return { specifier: entry.specifier, title: entry.title, featured, index }; +}); + +// Error codes with the recovery each one names, read from the type of the +// exported ERROR_CODES constant. +function errorCodes() { + const source = program.getSourceFile(entryFile(pkg.name)); + const symbol = checker.getExportsOfModule(checker.getSymbolAtLocation(source)).find((item) => item.name === "ERROR_CODES"); + if (!symbol) throw new Error(`${pkg.name} no longer exports ERROR_CODES`); + const target = symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; + const type = checker.getTypeOfSymbolAtLocation(target, target.valueDeclaration); + return type.getProperties().map((property) => { + const recovery = checker.getTypeOfSymbolAtLocation(property, target.valueDeclaration); + if (!recovery.isStringLiteral()) throw new Error(`ERROR_CODES.${property.name} is not a literal recovery`); + return { code: property.name, recovery: recovery.value }; + }); +} + +function firstSentence(text) { + const paragraph = oneLine(text.split(/\n\s*\n/)[0] ?? ""); + const end = paragraph.search(/[.:;](\s|$)/); + return end === -1 ? paragraph : paragraph.slice(0, end + 1).replace(/[:;]$/, "."); +} + +const data = `${JSON.stringify({ package: pkg.name, version: pkg.version, entries, errors: errorCodes() }, null, 2)}\n`; + +if (process.argv.includes("--check")) { + if (!existsSync(output) || readFileSync(output, "utf8") !== data) { + console.error( + "src/data/sdk-api.json does not match the SDK in examples/worklog. Run node scripts/build-sdk-api.mjs and commit the result.", + ); + process.exit(1); + } + console.log(`sdk-api.json matches ${pkg.name}@${pkg.version}`); +} else { + writeFileSync(output, data); + const count = entries.reduce((total, entry) => total + entry.featured.length + entry.index.length, 0); + console.log(`Wrote ${count} exports of ${pkg.name}@${pkg.version} to src/data/sdk-api.json`); +} diff --git a/src/components/DocText.astro b/src/components/DocText.astro new file mode 100644 index 0000000..1e39772 --- /dev/null +++ b/src/components/DocText.astro @@ -0,0 +1,21 @@ +--- +// Renders a declaration comment: paragraphs, with `backticked` names as code. +// `inline` renders the text as one run, for use inside another paragraph. +interface Props { + text: string; + inline?: boolean; +} + +const { text, inline = false } = Astro.props; +const paragraphs = (inline ? [text] : text.split(/\n\s*\n/)) + .map((paragraph) => paragraph.replace(/\s+/g, " ").trim()) + .filter(Boolean) + .map((paragraph) => paragraph.split("`")); +--- + +{ + paragraphs.map((parts) => { + const run = parts.map((part, index) => (index % 2 === 1 ? {part} : part)); + return inline ? run :

{run}

; + }) +} diff --git a/src/data/docs-sections.ts b/src/data/docs-sections.ts index c79f672..d4fdc9e 100644 --- a/src/data/docs-sections.ts +++ b/src/data/docs-sections.ts @@ -23,24 +23,35 @@ export const docsSections: Record = { { label: "Start", docs: [ + { href: "/sdk/", label: "SDK overview", key: "overview" }, { href: "/sdk/quickstart/", label: "Quickstart", key: "quickstart" }, - { href: "/sdk/connect-quickstart/", label: "Connect a browser app", key: "connect-quickstart" }, - { href: "/sdk/", label: "SDK overview", key: "overview" } + { href: "/sdk/connect-quickstart/", label: "Build an app", key: "connect-quickstart" }, + { href: "/sdk/local-development/", label: "Develop on localhost", key: "local-development" } ] }, { label: "Build", docs: [ - { href: "/sdk/portable-apps/", label: "Browser bundle", key: "portable-apps" }, - { href: "/sdk/manifest/", label: "Application manifest", key: "manifest" }, - { href: "/sdk/contracts/", label: "Contracts and adapters", key: "contracts" }, - { href: "/sdk/authorization/", label: "Authorization", key: "authorization" }, { href: "/sdk/operations/", label: "Records and operations", key: "operations" }, { href: "/sdk/editing/", label: "Editing records", key: "editing" }, - { href: "/sdk/routing/", label: "How apps reach a collection", key: "routing" }, - { href: "/sdk/testing/", label: "Testing", key: "testing" }, + { href: "/sdk/lifecycle/", label: "Sign-in and collections", key: "lifecycle" }, + { href: "/sdk/unsynced/", label: "Work before connecting", key: "unsynced" }, + { href: "/sdk/manifest/", label: "Application manifest", key: "manifest" }, + { href: "/sdk/authorization/", label: "Authorization", key: "authorization" }, + { href: "/sdk/contracts/", label: "Contracts and adapters", key: "contracts" }, + { href: "/sdk/offline-sync/", label: "Pending writes and sync status", key: "offline-sync" }, { href: "/sdk/notifications/", label: "Notifications and timers", key: "notifications" }, - { href: "/sdk/offline-sync/", label: "Pending writes and sync status", key: "offline-sync" } + { href: "/sdk/testing/", label: "Testing", key: "testing" }, + { href: "/sdk/routing/", label: "How apps reach a collection", key: "routing" }, + { href: "/sdk/portable-apps/", label: "Browser bundle", key: "portable-apps" } + ] + }, + { + label: "Packages", + docs: [ + { href: "/sdk/react/", label: "React", key: "react" }, + { href: "/sdk/ui/", label: "UI components", key: "ui" }, + { href: "/sdk/connect-dev/", label: "Developer kit", key: "connect-dev" } ] }, { diff --git a/src/data/sdk-api.json b/src/data/sdk-api.json new file mode 100644 index 0000000..f6f57bb --- /dev/null +++ b/src/data/sdk-api.json @@ -0,0 +1,4210 @@ +{ + "package": "@mdbase-dev/sdk", + "version": "0.1.0-beta.168", + "entries": [ + { + "specifier": "@mdbase-dev/sdk", + "title": "Core", + "featured": [ + { + "name": "createMdbase", + "kind": "function", + "description": "Create the app's mdbase entry. Construction does no IO; `start()` begins.", + "signature": "function createMdbase(declaration: MdbaseApplicationDeclaration, options?: MdbaseConnectOptions): Mdbase;" + }, + { + "name": "Mdbase", + "kind": "interface", + "description": "", + "signature": "interface Mdbase", + "members": [ + { + "name": "getSnapshot", + "signature": "getSnapshot(): MdbaseSnapshot", + "description": "" + }, + { + "name": "subscribe", + "signature": "subscribe(listener: () => void): () => void", + "description": "" + }, + { + "name": "start", + "signature": "start(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Start the session (restores a signed-in device). Safe to call again." + }, + { + "name": "signIn", + "signature": "signIn(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Call from the user gesture: sign in (opens Connect)." + }, + { + "name": "addCollection", + "signature": "addCollection(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Call from the user gesture: choose a Cloud copy or Private collection,\nor create a collection in Connect. No current collection is preselected." + }, + { + "name": "addPrivateCollection", + "signature": "addPrivateCollection(options?: MdbaseApplicationRequestOptions): Promise", + "description": "", + "deprecated": "Use `addCollection`. This is the same unified chooser, with the same options and no preselected collection." + }, + { + "name": "refreshCollectionList", + "signature": "refreshCollectionList(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Re-read the approved collection list now (for example when a switcher opens)." + }, + { + "name": "reauthorize", + "signature": "reauthorize(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Call from the user gesture: re-approve the open (or requested) collection,\nfor example after access was lost or the app needs a wider grant." + }, + { + "name": "configure", + "signature": "configure(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Configure the open collection from the declaration, with nothing to review\n(`setup.plan` + `setup.apply` is the reviewable form)." + }, + { + "name": "approve", + "signature": "approve(collectionId?: string | null, options?: MdbaseApplicationRequestOptions): Promise", + "description": "Call from the user gesture: approve one collection (default: the requested one)." + }, + { + "name": "select", + "signature": "select(collectionId: string, options?: MdbaseApplicationRequestOptions): Promise", + "description": "" + }, + { + "name": "chooseAnother", + "signature": "chooseAnother(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Close the open collection and show the list; Back restores it." + }, + { + "name": "forget", + "signature": "forget(collectionId: string, options?: MdbaseApplicationRequestOptions): Promise", + "description": "Hide a collection on this device (does not revoke access or delete anything)." + }, + { + "name": "deleteCollection", + "signature": "deleteCollection(collectionId: string, options: MdbaseCollectionDeleteOptions): Promise", + "description": "" + }, + { + "name": "requestAccess", + "signature": "requestAccess(request: MdbaseAccessRequest, options: MdbaseAccessRequestOptions): Promise", + "description": "" + }, + { + "name": "assess", + "signature": "assess(options?: MdbaseApplicationRequestOptions): Promise", + "description": "Re-check the open collection now." + }, + { + "name": "setup", + "signature": "readonly setup: { plan(options?: MdbaseSetupPlanOptions): Promise; apply(plan: MdbaseSetupPlan, options?: MdbaseApplicationRequestOptions): Promise; }", + "description": "" + }, + { + "name": "connection", + "signature": "connection(collectionId: string): MdbaseApplicationConnection | null", + "description": "The open collection's connection (also `phase.connection` when ready)." + }, + { + "name": "setForeground", + "signature": "setForeground(active: boolean): void", + "description": "" + }, + { + "name": "signOut", + "signature": "signOut(options?: MdbaseSignOutOptions): Promise", + "description": "Sign out of mdbase on this device (see `MdbaseApplication.signOut`).\nRejects with `sign_out_pending_writes` while changes are still saving,\nunless `{discard: true}`." + }, + { + "name": "close", + "signature": "close(): Promise", + "description": "" + }, + { + "name": "unsynced", + "signature": "readonly unsynced: { /** Open the app's unsynced collection in this tab, creating it on first * use: for \"Continue without connecting\". Signed out with nothing * requested, `start()` already does this. */ open(options?: MdbaseApplicationRequestOptions): Promise; /** Compare the unsynced collection with the open (ready) collection: what * would be created, what an earlier transfer already brought, what `map` * leaves out, renamed paths and types the destination lacks. Writes nothing. */ planTransfer(options?: MdbaseTransferPlanOptions): Promise; /** Create a plan's records in the open collection, keeping their IDs. * Interrupted? Plan and transfer again: only what is missing is created. */ transfer(plan: MdbaseTransferPlan, options?: MdbaseTransferOptions): Promise; /** Erase the unsynced collection from this browser. Ask the person to * confirm first: it cannot be undone. A tab showing it continues in a new, * empty one. */ discard(options?: MdbaseApplicationRequestOptions): Promise; }", + "description": "Work before connecting (the `unsynced` option)." + }, + { + "name": "application", + "signature": "readonly application: MdbaseApplication", + "description": "The underlying application, for code written against `MdbaseConnect`." + } + ] + }, + { + "name": "MdbaseApplicationDeclaration", + "kind": "interface", + "description": "App data only: never trust roots, runtime loader, Worker/SQL callbacks, keys,\nbearer, or authority ports. Existing registered application ID stays exact;\nlogical declaration identity is not an inferred CP registration receipt.", + "signature": "interface MdbaseApplicationDeclaration", + "members": [ + { + "name": "id", + "signature": "readonly id: string", + "description": "" + }, + { + "name": "name", + "signature": "readonly name: string", + "description": "" + }, + { + "name": "origin", + "signature": "readonly origin: string", + "description": "" + }, + { + "name": "environment", + "signature": "readonly environment: \"lab\" | \"staging\" | \"production\" | \"local\"", + "description": "" + }, + { + "name": "installationKind", + "signature": "readonly installationKind?: \"app-runtime\" | \"mobile\"", + "description": "Installation kind sent at pairing; the origin must be declared under the\nmanifest's `installation_origins..`. Default: \"mobile\"\nfor `capacitor://` origins, else \"app-runtime\". Android Capacitor apps\nserved from https set \"mobile\" explicitly." + }, + { + "name": "manifest", + "signature": "readonly manifest: unknown", + "description": "Complete canonical manifest data; runtime validation uses the public\nConnect parser. Accepts canonical typed builders without index-signature casts." + }, + { + "name": "storage", + "signature": "readonly storage?: ApplicationStorageDeclaration", + "description": "" + } + ] + }, + { + "name": "MdbaseConnectOptions", + "kind": "interface", + "description": "", + "signature": "interface MdbaseConnectOptions", + "members": [ + { + "name": "signIn", + "signature": "readonly signIn?: MdbaseSignInOpener", + "description": "" + }, + { + "name": "followVisibility", + "signature": "readonly followVisibility?: boolean", + "description": "Drive `setForeground` from `document.visibilityState`, and sync at once\nwhen the page regains focus or the network comes back. A hidden page only\nfinishes sending pending writes (for at most two minutes). Default true;\nfalse leaves `setForeground` to the app." + }, + { + "name": "readiness", + "signature": "readonly readiness?: { readonly typeIssues?: \"block\" | \"degrade\"; }", + "description": "Readiness policy. `typeIssues: \"degrade\"` keeps the collection ready when\none of the app's own types has an error-level definition issue, and lists\nit in `readiness.assessment.unavailableTypes`; the app must then treat\nthat type as unavailable. Default \"block\". Issues on definitions the app\ndoes not declare never affect readiness." + }, + { + "name": "privateCollections", + "signature": "readonly privateCollections?: boolean", + "description": "SDK-10: list this browser's Private collections (kept on the person's\ncomputers) in `collections` and open them by the same selection. Default\ntrue; false for an app that lists them itself through the app-grant entry." + }, + { + "name": "unsynced", + "signature": "readonly unsynced?: boolean", + "description": "Let people start working before they sign in or connect a collection.\nSigned out with no collection requested, the app opens its unsynced\ncollection (`phase.kind === \"unsynced\"`): saved only in this browser,\nnever listed in `collections`. Default false." + } + ] + }, + { + "name": "MdbaseSnapshot", + "kind": "interface", + "description": "The application snapshot with the derived fields always present.", + "signature": "interface MdbaseSnapshot extends MdbaseApplicationSnapshot", + "members": [ + { + "name": "phase", + "signature": "readonly phase: MdbasePhase", + "description": "R1: the one derived state for UI. Switch on `phase.kind`; never hand-roll\na ready check from `status`/`readiness`. Always set by the SDK entry." + }, + { + "name": "sync", + "signature": "readonly sync: StatusSummary | null", + "description": "Sync of the open collection (null until it is ready)." + }, + { + "name": "links", + "signature": "readonly links: MdbaseLinks | null", + "description": "Connect pages for this account (SDK-4). Null where the SDK cannot run." + } + ] + }, + { + "name": "MdbasePhase", + "kind": "type", + "description": "", + "signature": "type MdbasePhase = \n/** The SDK is starting: show the app's wordmark or the cached list. */\n{\n readonly kind: \"starting\";\n}\n/** Not signed in. `notice` explains why when sign-in just ended (window closed, signed out). */\n | {\n readonly kind: \"signed_out\";\n readonly notice: MdbaseProblem | null;\n}\n/** Waiting for the Connect window: sign in, add a collection or approve one. */\n | {\n readonly kind: \"authorizing\";\n readonly purpose: \"sign_in\" | \"add\" | \"approve\";\n readonly collectionId: string | null;\n}\n/** Signed in, nothing open: choose from `snapshot.collections` or add one. */\n | {\n readonly kind: \"choosing\";\n readonly lastApproval: MdbaseApprovalOutcome | null;\n}\n/** SDK-9: `slow` turns on when opening takes longer than usual. */\n | {\n readonly kind: \"opening\";\n readonly collectionId: string;\n readonly displayName: string | null;\n readonly slow: boolean;\n readonly progress?: import(\"../app-host/opening-progress.js\").AppOpeningProgress | null;\n}\n/** The requested collection is not approved for this app (yet). */\n | {\n readonly kind: \"needs_approval\";\n readonly collectionId: string;\n readonly displayName: string | null;\n readonly declined: boolean;\n}\n/** Open, but needs a one-time setup: `setup.plan()` then `setup.apply(plan)`. */\n | {\n readonly kind: \"needs_setup\";\n readonly collectionId: string;\n readonly collection: MdbaseCollectionSummary;\n}\n/** Open and usable. `rechecking` and `problem` are informational only. */\n | {\n readonly kind: \"ready\";\n readonly collectionId: string;\n readonly collection: MdbaseCollectionSummary;\n readonly connection: MdbaseApplicationConnection;\n readonly rechecking: boolean;\n readonly problem: MdbaseProblem | null;\n readonly unavailableTypes: readonly MdbaseUnavailableType[];\n}\n/** Working before connecting (`createMdbase(declaration, {unsynced: true})`):\n * the app's unsynced collection is open. Use `connection` as when ready.\n * Its work is saved only in this browser: say so, and offer \"Connect to sync\n * your work\". It is never in `snapshot.collections`. */\n | {\n readonly kind: \"unsynced\";\n readonly collectionId: string;\n readonly connection: MdbaseApplicationConnection;\n readonly rechecking: boolean;\n}\n/** Can't continue: show `problem` with its action. */\n | {\n readonly kind: \"problem\";\n readonly collectionId: string | null;\n readonly problem: MdbaseProblem;\n}\n/** The SDK can't run here (SSR, Node, an unsupported browser). */\n | {\n readonly kind: \"unavailable\";\n readonly problem: MdbaseProblem;\n} | {\n readonly kind: \"closed\";\n};" + }, + { + "name": "MdbaseApplicationConnection", + "kind": "interface", + "description": "", + "signature": "interface MdbaseApplicationConnection", + "members": [ + { + "name": "id", + "signature": "readonly id: string", + "description": "" + }, + { + "name": "displayName", + "signature": "readonly displayName: string", + "description": "" + }, + { + "name": "scope", + "signature": "readonly scope: Readonly", + "description": "" + }, + { + "name": "generation", + "signature": "readonly generation: string", + "description": "" + }, + { + "name": "records", + "signature": "readonly records: MdbaseClient", + "description": "Actual public borrowed UI client, not a legacy facade or second engine." + }, + { + "name": "files", + "signature": "readonly files: FilesApi", + "description": "SAME records.files. Available accessors do not widen original authority." + }, + { + "name": "people", + "signature": "readonly people: MdbasePeopleClient", + "description": "" + }, + { + "name": "timers", + "signature": "readonly timers?: TimersApi", + "description": "" + }, + { + "name": "isCurrent", + "signature": "isCurrent(): boolean", + "description": "" + } + ] + }, + { + "name": "MdbaseSetupPlan", + "kind": "interface", + "description": "", + "signature": "interface MdbaseSetupPlan", + "members": [ + { + "name": "status", + "signature": "readonly status: \"current\" | \"setup\" | \"conflict\"", + "description": "`current`: nothing to do. `setup`: changes to apply. `conflict`: blocked." + }, + { + "name": "canApply", + "signature": "readonly canApply: boolean", + "description": "" + }, + { + "name": "digest", + "signature": "readonly digest: string", + "description": "Binds the reviewed collection state and decisions." + }, + { + "name": "summary", + "signature": "readonly summary: string", + "description": "One plain sentence for the top of the review screen." + }, + { + "name": "changes", + "signature": "readonly changes: readonly MdbaseSetupChange[]", + "description": "" + }, + { + "name": "conflicts", + "signature": "readonly conflicts: readonly MdbaseSetupConflict[]", + "description": "" + }, + { + "name": "adoptable", + "signature": "readonly adoptable: Readonly>", + "description": "Conflicts that using the app's version would resolve: pass as `adopt` and plan again." + }, + { + "name": "packs", + "signature": "readonly packs: readonly MdbaseSetupPack[]", + "description": "" + }, + { + "name": "staged", + "signature": "readonly staged: readonly { readonly path: string; readonly document: string; }[]", + "description": "The definition documents setup would write, for a details view." + }, + { + "name": "records", + "signature": "readonly records: readonly { readonly path: string; readonly action: \"create\" | \"skip\"; }[]", + "description": "Starter records: `create` is written with the setup, `skip` already exists." + }, + { + "name": "refusal", + "signature": "readonly refusal?: { readonly code: MdbaseSetupRefusalCode; readonly message: string; }", + "description": "" + } + ] + }, + { + "name": "MdbaseSetupResult", + "kind": "interface", + "description": "SDK-8: how `setup.apply` ended. `changed`: the collection changed since the\nplan was reviewed (plan again). `unknown`: the result is not known yet; the\nsnapshot settles it. Refusals and other failures still reject.", + "signature": "interface MdbaseSetupResult", + "members": [ + { + "name": "outcome", + "signature": "readonly outcome: \"applied\" | \"changed\" | \"unknown\"", + "description": "" + }, + { + "name": "snapshot", + "signature": "readonly snapshot: MdbaseSnapshot", + "description": "" + } + ] + }, + { + "name": "MdbaseAccessRequest", + "kind": "interface", + "description": "What an app asks for at the moment of use. Only file actions today.", + "signature": "interface MdbaseAccessRequest", + "members": [ + { + "name": "files", + "signature": "readonly files: readonly FileAction[]", + "description": "" + } + ] + }, + { + "name": "MdbaseAccessResult", + "kind": "type", + "description": "`granted`: the open collection's connection may now use `actions` (every\nfile action it is allowed, reads included). `declined`: the person said no.\n`cancelled`: the window was closed, the request expired or was aborted.", + "signature": "type MdbaseAccessResult = {\n readonly status: \"granted\";\n readonly actions: readonly FileAction[];\n} | {\n readonly status: \"declined\";\n} | {\n readonly status: \"cancelled\";\n};" + }, + { + "name": "MdbaseTransferPlan", + "kind": "interface", + "description": "", + "signature": "interface MdbaseTransferPlan", + "members": [ + { + "name": "source", + "signature": "readonly source: string", + "description": "The unsynced collection." + }, + { + "name": "destination", + "signature": "readonly destination: string", + "description": "" + }, + { + "name": "items", + "signature": "readonly items: readonly MdbaseTransferItem[]", + "description": "" + }, + { + "name": "missingTypes", + "signature": "readonly missingTypes: readonly string[]", + "description": "Types the records to create name that the destination does not define\nand this transfer does not add. They are still created; the app may ask\nthe person to add the types, or plan again with `addTypes`." + }, + { + "name": "addTypes", + "signature": "readonly addTypes: readonly string[]", + "description": "Types the transfer adds to the destination, from the source's own\ndefinitions (`addTypes` only)." + }, + { + "name": "counts", + "signature": "readonly counts: Readonly<{ create: number; present: number; skipped: number; renamed: number; changed: number; }>", + "description": "" + } + ] + }, + { + "name": "connect", + "kind": "function", + "description": "Connect to a replica.", + "signature": "function connect(opts: ClientOptions): Promise;" + }, + { + "name": "ClientOptions", + "kind": "interface", + "description": "", + "signature": "interface ClientOptions", + "members": [ + { + "name": "connector", + "signature": "connector: Connector", + "description": "" + }, + { + "name": "app", + "signature": "app: { name: string; version: string; }", + "description": "App ID and version, sent in `hello`." + }, + { + "name": "timezone", + "signature": "timezone?: string", + "description": "IANA time zone for this session's writes and queries. Defaults to the host's." + }, + { + "name": "features", + "signature": "features?: string[]", + "description": "Extra features to request (\"fence\", \"presence\", ...)." + }, + { + "name": "reconnect", + "signature": "reconnect?: false | { minDelayMs?: number; maxDelayMs?: number; }", + "description": "Reconnect after the link drops (default on)." + }, + { + "name": "waitForDevice", + "signature": "waitForDevice?: boolean", + "description": "Private collections: wait for a device replica to come online instead of failing." + }, + { + "name": "onWaiting", + "signature": "onWaiting?: (why: MdbaseError) => void", + "description": "Called while waiting for a device or the original Native grant to become live." + }, + { + "name": "ledger", + "signature": "ledger?: WriteLedgerStore", + "description": "Where the write ledger persists (default: memory). Web apps get the replica's store." + }, + { + "name": "signal", + "signature": "signal?: AbortSignal", + "description": "" + } + ] + }, + { + "name": "MdbaseClient", + "kind": "class", + "description": "", + "signature": "class MdbaseClient", + "members": [ + { + "name": "files", + "signature": "readonly files: FilesApi", + "description": "Files: handles, uploads, downloads (§10)." + }, + { + "name": "presence", + "signature": "readonly presence: PresenceApi", + "description": "Presence on records (§11)." + }, + { + "name": "ledger", + "signature": "get ledger(): WriteLedgerView", + "description": "Unconfirmed and refused writes, and the overall save state. Survives a\nreload when the connector persists it (web apps do). Event-driven: it\nsettles from receipts and the replica's status push, never by polling." + }, + { + "name": "connect", + "signature": "static connect(opts: ClientOptions): Promise", + "description": "Connect to a replica and say hello. With `waitForDevice`, a private collection with\nno device online keeps retrying (calling `onWaiting`) instead of failing.\nA next-relay pre-live grant waits up to 30 seconds on this same connector.\nThis is initial hello only: no application request or unknown write is replayed." + }, + { + "name": "hello", + "signature": "get hello(): HelloResult", + "description": "The negotiated session, with its latest role-effective grant." + }, + { + "name": "collection", + "signature": "get collection(): Uuid", + "description": "" + }, + { + "name": "link", + "signature": "get link(): LinkState", + "description": "" + }, + { + "name": "linkProblem", + "signature": "get linkProblem(): MdbaseError | null", + "description": "Why the link is not open: the last connect error. `reason: \"no_device_online\"`\nmeans a private collection has no device replica reachable right now; show\n\"waiting for one of your devices\" rather than an error." + }, + { + "name": "onLink", + "signature": "onLink(fn: (s: LinkState, why: MdbaseError | null) => void): () => void", + "description": "" + }, + { + "name": "close", + "signature": "close(): void", + "description": "" + }, + { + "name": "readFenced", + "signature": "get readFenced(): boolean", + "description": "Next-relay consistency floor ONLY; not keyed/readable/Saved authority." + }, + { + "name": "readyForRead", + "signature": "readyForRead(signal?: AbortSignal, immediate?: boolean): Promise", + "description": "Recheck session identity after waiting for catch-up (SDK-internal use)." + }, + { + "name": "currentSession", + "signature": "currentSession(): Session", + "description": "The open session, or throw `unavailable` (SDK-internal use)." + }, + { + "name": "call", + "signature": "call(method: string, p: CborValue, o?: { signal?: AbortSignal; codec?: import(\"./codec.js\").Codec; retry?: boolean; }): Promise", + "description": "Advanced: run a request on the current session; on a dropped link, wait for the\nreconnect and retry when `retry` (idempotent requests only)." + }, + { + "name": "authenticatedDevice", + "signature": "get authenticatedDevice(): string | undefined", + "description": "Transport-authenticated replica identity (Noise target), NOT hello/witness\nself-claims. Absent when the transport has no authenticated device identity." + }, + { + "name": "headWitness", + "signature": "get headWitness(): Uint8Array | undefined", + "description": "The replica's latest signed head witness from `hello`, if it sent one." + }, + { + "name": "status", + "signature": "get status(): SyncStatus", + "description": "The latest known status." + }, + { + "name": "getStatus", + "signature": "getStatus(): Promise", + "description": "Fresh status from the replica." + }, + { + "name": "appliedPrefix", + "signature": "appliedPrefix(seq: number, signal?: AbortSignal): Promise", + "description": "Read the retained historical chain at a captured hosted fence. READ/session\nauthorization is unchanged. No automatic retry/reconnect: a handover must\nrevalidate the authenticated session and all readiness gates after any await.\nMissing chain or any error means remain hosted; never substitute asOf or the\ncurrent head/generations. Decoding does not authenticate a witness or switch." + }, + { + "name": "onStatus", + "signature": "onStatus(fn: (s: SyncStatus) => void): () => void", + "description": "Call `fn` with the status now and on every change (pushed, ≤ 4/s)." + }, + { + "name": "describe", + "signature": "describe(options?: AbortSignal | DescribeOptions): Promise", + "description": "Catalog summary: types with their version, source revision and contract\nimplementations, contracts, settings (§4.4). `definitions` adds each type's\ndefinition and schema and each contract's schemas, bounded by the replica\n(`too_large` when over budget; read them with `resources.get` instead).\nA producer that cannot describe definitions fails `upgrade_required`." + }, + { + "name": "createType", + "signature": "createType(document: string, o?: WriteOptions): Promise", + "description": "Create a type from its complete type-file `document`. The replica's Core\nderives the name and path (`/.md`) from the local\ncatalog, then the document is written create-only there, so a concurrent\ncreator loses with `conflict`. Refusals: `invalid_record` /\n`invalid_type_definition` (unusable name, or the definition is invalid\nwhen written), `conflict` / `type_conflict` (the name exists under any\ncasing) or `path_taken`, and `forbidden` without `definitions.manage`.\nUpdate a type with `resources.put(path, document, { baseRevision })`,\nusing the path and revision from `describe`." + }, + { + "name": "describeTyping", + "signature": "describeTyping(types: readonly string[], paths: readonly string[], signal?: AbortSignal): Promise", + "description": "Core's temporal typing for top-level fields under a query's exact `types` OR list\n(`date` / `date_time` only when every type agrees; otherwise `none`). The answer is\nbound to a catalog generation; nested paths are `none`. The SDK never infers typing." + }, + { + "name": "get", + "signature": "get(ref: RecordRef, include?: Include, signal?: AbortSignal): Promise", + "description": "One record by ID or path. Throws `not_found`." + }, + { + "name": "getContract", + "signature": "getContract(ref: RecordRef, contract: ContractSelector, signal?: AbortSignal): Promise", + "description": "One record's valid view through a record contract, mapped by the replica's\nCore. Refusals: `not_found` (no record, or `data_contract_not_implemented`\nwhen it does not implement the contract), `not_found`/`data_contract_not_found`,\n`invalid_request`/`data_contract_ambiguous` (select `type`; details: the types),\n`invalid_record`/`data_contract_record_invalid` (with issues), and\n`upgrade_required`/`contract_records_unsupported` from an older replica." + }, + { + "name": "readMany", + "signature": "readMany(refs: readonly RecordRef[], include?: Include, signal?: AbortSignal, options?: ReadManyOptions): Promise", + "description": "Bounded ordered ordinary gets. Missing/refused reads fail the batch;\nduplicate slots and the original signal are retained, not a snapshot." + }, + { + "name": "find", + "signature": "find(ref: RecordRef, include?: Include, signal?: AbortSignal): Promise", + "description": "One record, or `null` when there is none." + }, + { + "name": "query", + "signature": "query(query: Query, include?: Include, signal?: AbortSignal, opts?: { contract?: string; }): Promise", + "description": "One page of a query, evaluated by the replica (body predicates and links\nincluded, §4). Pass `result.cursor` back as `query.cursor` for the next.\n`opts.contract` is not supported by the live replica dispatch. Supplying it\nreturns `invalid_request` with reason `unknown_param`; no contract filter\nis applied. The option remains encoded unchanged for API compatibility." + }, + { + "name": "queryContract", + "signature": "queryContract(contract: ContractRef, options?: ContractQueryOptions): Promise", + "description": "Canonical contract projections with validation issues and ordinary cursor\nmetadata. Positive page sizes above the producer's 128-record maximum clamp\nto 128; use the returned cursor for the remaining records. Old producers\nrefuse; there is no SDK projection fallback." + }, + { + "name": "backlinks", + "signature": "backlinks(target: RecordRef, o?: { include?: Include; limit?: number; cursor?: string; signal?: AbortSignal; }): Promise", + "description": "One page of the records whose links resolve to `target`, from the\nreplica's link index with Core resolution over the local view (pending\nchanges included; the target's links to itself excluded). Referrers come\nin path order, each with its links to the target. Pass the returned\n`cursor` for the next page (`limit` 1–1000, default 128); a page may hold\nfewer than `limit`, and only an absent cursor ends the list. Any local\nchange between pages refuses the continuation with `cursor_stale`: start\nagain from the first page. A replica without the method refuses with\n`upgrade_required` / `backlinks_unsupported` (§4.2)." + }, + { + "name": "resolveLinks", + "signature": "resolveLinks(from: string, links: readonly string[], o?: { signal?: AbortSignal; }): Promise", + "description": "Resolve link occurrences written in the note at `from`, exactly as the\nreplica resolves them for backlinks and rename: Core parsing and spec 08\nresolution over the local view, pending changes included. `from` need not\nexist yet (an unsaved draft). Each entry is one occurrence as written —\n`[[target]]`, `![[target#anchor|alias]]`, `[text](path)`, `![alt](path)` —\nand the result holds one resolution per entry, in order: a record or file\nwith its ID and path, `ambiguous` with sorted candidate paths, or\n`not_found` / `invalid`. Titles and aliases are not link keys. 1–512\nentries of at most 4096 bytes each. A replica without the method refuses\nwith `upgrade_required` / `resolve_links_unsupported` (§4.3)." + }, + { + "name": "resources", + "signature": "readonly resources: { get: (path: string, signal?: AbortSignal) => Promise; /** One bounded resource inventory page. Text defaults to false; limit defaults * to 64 (maximum 128). Preserve folder/text/limit on continuation. Collect all * pages before assessing a complete snapshot; stale/expired cursors require * explicit reassessment, never an automatic restart. */ list: (o?: { folder?: string; text?: boolean; cursor?: string; limit?: number; signal?: AbortSignal; }) => Promise; /** * Write a resource. CAS: pass the revision you read as `baseRevision`, or * `mustNotExist` to create only (a taken path is `conflict` / `path_taken`). Not both. */ put: (path: string, doc: string, o?: WriteOptions & { baseRevision?: string; mustNotExist?: boolean; }) => Promise; delete: (path: string, o?: WriteOptions & { baseRevision?: string; }) => Promise; }", + "description": "Definitions: `mdbase.yaml`, type files, contract files (§4.1)." + }, + { + "name": "typePacks", + "signature": "readonly typePacks: Readonly<{ plan: (pack: TypePackProvision, options: TypePackOptions) => Promise; apply: (plan: TypePackPlan, o?: WriteOptions) => Promise; }>", + "description": "Type packs: `plan` (writes nothing), review, then `apply(plan)` as one\nguarded all-or-nothing mutation (needs `definitions.manage`)." + }, + { + "name": "records", + "signature": "readonly records: Records", + "description": "" + }, + { + "name": "views", + "signature": "readonly views: { createSource: (input: ViewSourceCreateInput, options: ViewSourceWriteOptions) => Promise; updateSource: (seen: RecordView, document: string, options: ViewSourceWriteOptions) => Promise; getSourceRecord: (source: RecordRef, options?: ViewSourceReadOptions) => Promise; /** `mdbase.view` sources (spec 12 `list_views` shape), resolved over served * contract queries: the native runtime has no `list_views` RPC. */ list: (folder?: string, signal?: AbortSignal) => Promise; /** Spec 11 named-view resolution, executed as one ordinary native query. */ execute: (source: RecordRef, view: string, o?: ExecuteViewOptions) => Promise; readSource: (source: RecordRef, signal?: AbortSignal) => Promise; }", + "description": "Saved views (§4.3). Source helpers use ordinary record ops/full CAS reads;\nweb app-host Bases reads use the separate native AppBases methods." + }, + { + "name": "pendingWrites", + "signature": "pendingWrites(signal?: AbortSignal): Promise", + "description": "Pending mutations of this session's owner (its grant, or the device), oldest\nfirst (§6): a new session after a reload sees writes made before it. Follow\neach with `awaitReceipt`, or keep the `mutationId` you chose and call\n`receipt(id)` / `awaitReceipt(id)`. Pages through everything." + }, + { + "name": "preflightRename", + "signature": "preflightRename(target: RecordView, to: string, o?: { updateRefs?: boolean; }): Promise", + "description": "What a rename would do to links (dry run, §5): the rewrites, and links left broken.\nThrows the problem the real rename would get." + }, + { + "name": "preflightDelete", + "signature": "preflightDelete(target: RecordRef): Promise", + "description": "Links a delete would break (dry run, §5)." + }, + { + "name": "pages", + "signature": "pages(query: Query, include?: Include, signal?: AbortSignal, options?: PagesOptions): AsyncGenerator", + "description": "Every page of a query. Cursor resets require explicit caller clearing;\notherwise cursor_expired/cursor_stale is thrown unchanged." + }, + { + "name": "queryPages", + "signature": "queryPages(query: Query, include?: Include, signal?: AbortSignal, options?: PagesOptions): AsyncGenerator", + "description": "Shared alias of pages(): whole native pages, no implicit cursor reset." + }, + { + "name": "live", + "signature": "live(query: Query, include?: Include): LiveQuery", + "description": "A live query. Subscribe for pushes; keep lists cheap with a `limit` window and no\nbodies, and widen the window with `setQuery`." + }, + { + "name": "changes", + "signature": "changes(cursor?: string, limit?: number, signal?: AbortSignal): Promise", + "description": "One page of the change feed. A `reset: true` result means \"re-read what you cache\"." + }, + { + "name": "watchChanges", + "signature": "watchChanges(cursor: string | undefined, onChanges: (batch: ChangesResult) => void): ChangesWatch", + "description": "Follow the change feed from `cursor`. `onChanges` gets every batch; `reset: true`\nmeans the app must re-read what it caches. Survives reconnects.\n`ready` confirms the first watch ACK, not a complete/current collection cache.\nAfter reconnect, observe `state`; an already-resolved promise is not freshness." + }, + { + "name": "validate", + "signature": "validate(refs?: readonly RecordRef[], signal?: AbortSignal): Promise", + "description": "Validate these records, or every record. Every record is validated a\npage at a time when the replica grants `validate-page-v1`; each page is an\nindependent READ, so records changed meanwhile may be missed or repeated.\nOlder replicas validate every record in one bounded request." + }, + { + "name": "executeAppBases", + "signature": "executeAppBases(request: AppBasesRequest, signal?: AbortSignal): Promise", + "description": "Independent native app-host Bases READ, exact original held session.\nUnsupported transports refuse: no generic execute_view/data fallback." + }, + { + "name": "executeAppBasesPages", + "signature": "executeAppBasesPages(request: AppBasesRequest, options?: AppBasesPagesOptions): AsyncGenerator<{ kind: \"refusal\"; problem: Problem; } | import(\"./app-host/bases-pages.js\").AppBasesPage, any, any>", + "description": "All rows of a saved view, page by page over native windows (prod\n`executeViewPages`). Each page is an independent READ; `changedSinceFirst`\nmarks pages taken after the collection changed." + }, + { + "name": "listAppBasesViews", + "signature": "listAppBasesViews(request: AppBasesDiscoveryRequest, signal?: AbortSignal): Promise", + "description": "One bounded native discovery page. Empty views do not mean EOF when a\ncontinuation is present. Supplied handles never silently restart/retry." + }, + { + "name": "readAppBasesViewSource", + "signature": "readAppBasesViewSource(request: AppBasesSourceRequest, signal?: AbortSignal): Promise", + "description": "Exact current source for one native UUID/SHA/original ordinal, never path\naliases, inferred catalog entries, generic RPC or cached document fallback." + }, + { + "name": "submit", + "signature": "submit(ops: Op[], o?: WriteOptions & { allowPartial?: boolean; }): Promise", + "description": "Submit operations as one mutation (or one per op with `allowPartial`)." + }, + { + "name": "create", + "signature": "create(input: CreateInput, o?: WriteOptions): Promise", + "description": "Create a record. The ID is minted here, so the app can use it before confirmation." + }, + { + "name": "update", + "signature": "update(target: RecordRef, changes: UpdateInput, o?: WriteOptions): Promise", + "description": "Update a record by field-level intent. Pass the `RecordView` you edited from, and\nthe SDK sends `base` for every key you set or unset, and the body as a 3-way merge,\nso concurrent edits merge instead of last-writer-wins." + }, + { + "name": "updateOp", + "signature": "updateOp(target: RecordRef, c: UpdateInput): Op & { kind: \"update\"; }", + "description": "Build an `update` op (exposed for batching with `submit`)." + }, + { + "name": "replaceDocument", + "signature": "replaceDocument(target: RecordRef, document: string, o?: WriteOptions & { ifRevision?: string; }): Promise", + "description": "Replace a record's whole document." + }, + { + "name": "delete", + "signature": "delete(target: RecordRef, o?: WriteOptions & { ifRevision?: string; contract?: ContractSelector; }): Promise", + "description": "Delete a record. With a RecordView, a concurrent change supersedes the delete.\nWith `contract`, the record must implement it; the delete is then bound to\nthe revision checked, so a concurrent change supersedes it." + }, + { + "name": "rename", + "signature": "rename(target: RecordView | ContractRecordView, to: string, o?: WriteOptions & { updateRefs?: boolean; ifRevision?: string; contract?: ContractSelector; }): Promise", + "description": "Rename (move) a record; `updateRefs` rewrites links to it (default true).\nWith `contract`, the record must implement it, and moves from its current path." + }, + { + "name": "save", + "signature": "save(write: Write | Promise, o?: { readBack?: boolean; timeoutMs?: number; signal?: AbortSignal; }): Promise", + "description": "Settle a write for the UI: waits up to `timeoutMs` (default 3 s) for the log\nto confirm it. Still pending then is `saved_on_device`, not a failure: the\nwrite is kept on this device and the ledger settles it later. With\n`readBack`, `record` is the record as it now reads here." + }, + { + "name": "receipt", + "signature": "receipt(mutation: Uuid, signal?: AbortSignal): Promise", + "description": "The current receipt of a mutation. Failures carry `mutationId`." + }, + { + "name": "awaitReceipt", + "signature": "awaitReceipt(mutation: Uuid, timeoutMs?: number, signal?: AbortSignal): Promise", + "description": "Resolves when the receipt leaves `pending`, or at `timeoutMs` with the pending receipt." + }, + { + "name": "listHolds", + "signature": "listHolds(signal?: AbortSignal): Promise", + "description": "" + }, + { + "name": "onHolds", + "signature": "onHolds(fn: (h: Hold[]) => void): () => void", + "description": "Call `fn` with the holds now and on every change." + }, + { + "name": "resolveHold", + "signature": "resolveHold(id: Uuid, how: HoldResolution, use?: string): Promise", + "description": "Resolve a hold. Submits an ordinary mutation and returns its write." + }, + { + "name": "listConflicts", + "signature": "listConflicts(record?: Uuid, signal?: AbortSignal): Promise", + "description": "" + }, + { + "name": "onConflicts", + "signature": "onConflicts(fn: (c: ConflictEntry[]) => void): () => void", + "description": "" + }, + { + "name": "dismissConflict", + "signature": "dismissConflict(entry: ConflictEntry, o?: WriteOptions): Promise", + "description": "Accept what the record kept." + }, + { + "name": "resolveConflict", + "signature": "resolveConflict(entry: ConflictEntry, field: string, chosen: PlainValue, o?: WriteOptions): Promise", + "description": "Resolve a field conflict with a chosen value, dismissing it in the same mutation." + }, + { + "name": "pendingDevices", + "signature": "pendingDevices(signal?: AbortSignal): Promise", + "description": "Devices waiting for approval in a private collection. Offered only to the app\nhosting the replica (no grant). `exchangeReady` is challenge/reveal readiness,\nNOT approval/key delivery. Once ready, `sas` is the host-only comparison code\nto display beside Allow; the user must check it matches on the other computer." + }, + { + "name": "approveDevice", + "signature": "approveDevice(device: Uuid, sas: string): Promise", + "description": "Confirm with the six digits displayed for the current host exchange, only\nafter the user visually checks they match on the requesting computer. Nothing\nis typed. Never substitute a newer code after the user confirms. The replica\nchecks the current context and awaits the actual approval disposition." + }, + { + "name": "rejectDevice", + "signature": "rejectDevice(device: Uuid): Promise", + "description": "Dismiss this request until a fresh commitment is logged, including automatic renewal. Not revocation." + }, + { + "name": "accountKey", + "signature": "readonly accountKey: { /** Setup step 2: check the enrolment and key the recovery device (an ordinary mutation). */ setup: (secret: Uint8Array) => Promise; /** Unlock step 3: start the recovery-signed self-grant; poll `status` until `keyed` or `refused`. */ unlock: (secret: Uint8Array) => Promise; status: (signal?: AbortSignal) => Promise; }", + "description": "The account key (AK1 §6; hosting app only, never over a cloud-copy thin session).\n`secret` is the 32-byte account secret `R`; the replica derives the collection's\nrecovery device from it and drops it. The SDK keeps no copy." + }, + { + "name": "recoveryKey", + "signature": "readonly recoveryKey: { /** Whether a recovery key is set up for this collection. */ status: () => Promise; /** * Create a recovery key. The returned phrase is shown to the user once and never * stored by the SDK; the replica keys a recovery device with it. */ create: () => Promise<{ phrase: string; device: Uuid; }>; /** Join this device using a recovery phrase (when no other device is available). */ import: (phrase: string) => Promise; }", + "description": "Recovery key for a private collection (hosting app only)." + }, + { + "name": "offerFence", + "signature": "offerFence(handler: FenceHandler): void", + "description": "Offer the editor fence: the replica will route changes to open files through\n`handler`. Requires the \"fence\" feature in `hello`." + }, + { + "name": "reportFence", + "signature": "reportFence(open: FenceReportEntry[]): Promise", + "description": "Report the files open in editors (on open, close, dirty change, and after saves)." + } + ] + }, + { + "name": "Query", + "kind": "type", + "description": "A spec 11 query object. Plain objects are converted to data maps.", + "signature": "type Query = {\n types?: string[];\n where?: string;\n order_by?: PlainValue;\n select?: PlainValue;\n limit?: number;\n cursor?: string;\n timezone?: string;\n context?: PlainValue;\n [key: string]: PlainValue | undefined;\n};" + }, + { + "name": "CreateInput", + "kind": "interface", + "description": "", + "signature": "interface CreateInput", + "members": [ + { + "name": "id", + "signature": "id?: Uuid", + "description": "A record ID minted by the caller; default a new UUIDv7." + }, + { + "name": "type", + "signature": "type?: string", + "description": "" + }, + { + "name": "path", + "signature": "path?: string", + "description": "" + }, + { + "name": "frontmatter", + "signature": "frontmatter?: Map | { [k: string]: PlainValue | undefined; }", + "description": "" + }, + { + "name": "body", + "signature": "body?: string", + "description": "" + }, + { + "name": "document", + "signature": "document?: string", + "description": "Complete source instead of frontmatter + body." + }, + { + "name": "contract", + "signature": "contract?: ContractSelector", + "description": "Create through a record contract: `frontmatter` holds contract fields, which\nthe replica maps to the implementing type's record fields (and sets `type`).\nUnmapped contract fields and an invalid resulting view are refused before\nanything is submitted. Not with `document`." + } + ] + }, + { + "name": "UpdateInput", + "kind": "interface", + "description": "", + "signature": "interface UpdateInput", + "members": [ + { + "name": "updateRefs", + "signature": "updateRefs?: boolean", + "description": "Rewrite inbound links on path-stored field moves (default true)." + }, + { + "name": "patch", + "signature": "patch?: Map | { [k: string]: PlainValue | undefined; }", + "description": "Set these top-level keys." + }, + { + "name": "unset", + "signature": "unset?: string[]", + "description": "" + }, + { + "name": "add", + "signature": "add?: { [field: string]: PlainValue[]; }", + "description": "" + }, + { + "name": "remove", + "signature": "remove?: { [field: string]: PlainValue[]; }", + "description": "" + }, + { + "name": "body", + "signature": "body?: string", + "description": "Replace the body. With a RecordView target, it is sent as a 3-way merge." + }, + { + "name": "bodyEdits", + "signature": "bodyEdits?: BodyEdit[]", + "description": "" + }, + { + "name": "bodyBase", + "signature": "bodyBase?: string", + "description": "" + }, + { + "name": "ifRevision", + "signature": "ifRevision?: string", + "description": "Opt-in whole-document CAS." + }, + { + "name": "contract", + "signature": "contract?: ContractSelector", + "description": "Update through a record contract: `patch` and `unset` name contract fields,\nmapped by the replica to record fields (nested record fields are merged with\nthe current record, sending their observed values as `base`). The record\nmust implement the contract. `patch`, `unset`, the body options and `ifRevision`\napply, in one atomic update." + }, + { + "name": "promote", + "signature": "promote?: boolean", + "description": "With `contract`: an untyped record (no type membership) first takes the\nselected implementing type through the collection's explicit type key, in\nthe same atomic update; its ID, body and other fields are kept. A record\nthat already implements the contract is updated as usual; one with other\ntypes is refused (`invalid_request`/`data_contract_promote_typed`). Older\nreplicas refuse the option with `invalid_request`." + } + ] + }, + { + "name": "WriteOptions", + "kind": "interface", + "description": "", + "signature": "interface WriteOptions", + "members": [ + { + "name": "mutationId", + "signature": "mutationId?: Uuid", + "description": "" + }, + { + "name": "intent", + "signature": "intent?: string", + "description": "A stable name for this one write, such as a draft ID. Retrying with the same\n`intent` in the same collection sends the same mutation (and, for `create`,\nthe same record ID), so a write retried after a lost reply lands once; the\nfirst attempt's content wins. Use a new name for each new write." + }, + { + "name": "conflictMode", + "signature": "conflictMode?: ConflictMode", + "description": "" + }, + { + "name": "timezone", + "signature": "timezone?: string", + "description": "" + }, + { + "name": "include", + "signature": "include?: Include", + "description": "" + }, + { + "name": "dryRun", + "signature": "dryRun?: boolean", + "description": "" + }, + { + "name": "wait", + "signature": "wait?: \"pending\" | \"confirmed\" | \"published\"", + "description": "Hold for log confirmation, or final local file publication (not confirmation)." + }, + { + "name": "validation", + "signature": "validation?: ValidationLevel", + "description": "Validation level for this write's single-record checks (spec 04); default\nthe collection's `settings.validation`. Only `error` refuses a write with\n`invalid_record`. A sync tool sends `warn`: files it carries are never\nrejected for validity. Older replicas ignore it and refuse at `error`." + }, + { + "name": "signal", + "signature": "signal?: AbortSignal", + "description": "" + } + ] + }, + { + "name": "Write", + "kind": "class", + "description": "A submitted write. `receipt` is what the replica answered (usually `pending` with\noptimistic `records`). `confirmed` settles when it leaves `pending`.", + "signature": "class Write", + "members": [ + { + "name": "receipt", + "signature": "receipt: Receipt", + "description": "" + }, + { + "name": "confirmed", + "signature": "readonly confirmed: Promise", + "description": "Resolves with the confirmed receipt; rejects with the problem if rejected or unknown." + }, + { + "name": "published", + "signature": "readonly published: Promise", + "description": "Final local file result, possibly BEFORE log confirmation. `not_published`\nis a final non-publication, not success. Without files, settles at confirmation.\nRejects rejected/unknown outcomes; not a native custody/durable ACK gate." + }, + { + "name": "constructor", + "signature": "constructor(receipt: Receipt)", + "description": "" + }, + { + "name": "mutationId", + "signature": "get mutationId(): Uuid", + "description": "" + }, + { + "name": "records", + "signature": "get records(): RecordView[]", + "description": "The records as they now read locally (optimistic until confirmed)." + }, + { + "name": "state", + "signature": "get state(): Receipt[\"state\"]", + "description": "" + }, + { + "name": "onReceipt", + "signature": "onReceipt(fn: ReceiptListener): () => void", + "description": "" + }, + { + "name": "update", + "signature": "update(r: Receipt): void", + "description": "SDK-internal: apply a newer receipt. Kept in the published types (apps' tests use it)." + } + ] + }, + { + "name": "SaveResult", + "kind": "interface", + "description": "What `MdbaseClient.save` settled to.", + "signature": "interface SaveResult", + "members": [ + { + "name": "outcome", + "signature": "readonly outcome: \"confirmed\" | \"saved_on_device\" | \"rejected\"", + "description": "" + }, + { + "name": "mutationId", + "signature": "readonly mutationId: Uuid", + "description": "" + }, + { + "name": "records", + "signature": "readonly records: readonly RecordView[]", + "description": "" + }, + { + "name": "record", + "signature": "readonly record?: RecordView", + "description": "The first record, read back when asked." + }, + { + "name": "problem", + "signature": "readonly problem?: Problem", + "description": "" + } + ] + }, + { + "name": "LiveQuery", + "kind": "class", + "description": "A live query handle.", + "signature": "class LiveQuery implements LiveQueryState", + "members": [ + { + "name": "ready", + "signature": "readonly ready: Promise", + "description": "Rejects if the first subscribe fails; resolves on the first snapshot." + }, + { + "name": "error", + "signature": "error: MdbaseError | null", + "description": "The last error from (re)subscribing, if the subscription is currently broken." + }, + { + "name": "records", + "signature": "get records(): readonly RecordView[]", + "description": "The current result, in result order." + }, + { + "name": "complete", + "signature": "get complete(): boolean", + "description": "False while the replica installs a snapshot (rows may be missing)." + }, + { + "name": "asOf", + "signature": "get asOf(): number", + "description": "Local view version of this result." + }, + { + "name": "stale", + "signature": "get stale(): boolean", + "description": "True until the first snapshot, and between a `reset` (or a reconnect) and the\nnext snapshot. The previous records stay visible meanwhile." + }, + { + "name": "metadata", + "signature": "get metadata(): QueryUpdate[\"metadata\"]", + "description": "Full native metadata bound to asOf; never reconstructed from the window." + }, + { + "name": "closed", + "signature": "get closed(): boolean", + "description": "" + }, + { + "name": "get", + "signature": "get(id: Uuid): RecordView | undefined", + "description": "" + }, + { + "name": "subscribe", + "signature": "subscribe(fn: LiveListener): () => void", + "description": "Call `fn` now and on every change. Returns an unsubscribe function." + }, + { + "name": "setQuery", + "signature": "setQuery(query: Query, include?: Include | undefined): Promise", + "description": "Change the query (for example a larger `limit` window). Re-subscribes." + }, + { + "name": "close", + "signature": "close(): void", + "description": "" + } + ] + }, + { + "name": "RecordView", + "kind": "interface", + "description": "", + "signature": "interface RecordView", + "members": [ + { + "name": "id", + "signature": "id: Uuid", + "description": "" + }, + { + "name": "path", + "signature": "path: string", + "description": "" + }, + { + "name": "revision", + "signature": "revision: Hash", + "description": "" + }, + { + "name": "frontmatter", + "signature": "frontmatter: FmMap", + "description": "" + }, + { + "name": "effective", + "signature": "effective?: FmMap", + "description": "" + }, + { + "name": "body", + "signature": "body?: string", + "description": "" + }, + { + "name": "document", + "signature": "document?: string", + "description": "" + }, + { + "name": "types", + "signature": "types: string[]", + "description": "" + }, + { + "name": "state", + "signature": "state: RecordState", + "description": "" + }, + { + "name": "diagnostics", + "signature": "diagnostics?: Issue[]", + "description": "" + }, + { + "name": "values", + "signature": "values?: FmMap", + "description": "`select` outputs by output name (queries with `select`)." + }, + { + "name": "mtime", + "signature": "mtime?: number", + "description": "Capture time of the last change to the bytes (§3.1); not a device file mtime." + }, + { + "name": "links", + "signature": "links?: LinkView[]", + "description": "Outgoing links and embeds (include `links`)." + }, + { + "name": "createdBy", + "signature": "createdBy?: AuthorRef", + "description": "Who created the record (absent in local-only collections)." + }, + { + "name": "modifiedBy", + "signature": "modifiedBy?: AuthorRef", + "description": "Who last changed it." + } + ] + }, + { + "name": "Receipt", + "kind": "interface", + "description": "", + "signature": "interface Receipt", + "members": [ + { + "name": "mutation", + "signature": "mutation: Uuid", + "description": "" + }, + { + "name": "state", + "signature": "state: ReceiptState", + "description": "" + }, + { + "name": "seq", + "signature": "seq?: number", + "description": "" + }, + { + "name": "status", + "signature": "status?: EntryStatus", + "description": "" + }, + { + "name": "conflicts", + "signature": "conflicts?: Conflict[]", + "description": "" + }, + { + "name": "records", + "signature": "records?: RecordView[]", + "description": "" + }, + { + "name": "problem", + "signature": "problem?: Problem", + "description": "" + }, + { + "name": "published", + "signature": "published?: PublishState", + "description": "Present on file-backed replicas; not a confirmation/custody authority." + }, + { + "name": "preflight", + "signature": "preflight?: Preflight", + "description": "Dry run only." + }, + { + "name": "relocatedFrom", + "signature": "relocatedFrom?: number", + "description": "Earlier confirmed position after acknowledged-intent resurrection." + } + ] + }, + { + "name": "MdbaseError", + "kind": "class", + "description": "An error from a replica or from the SDK itself. Apps branch on `code` (and\noptionally `reason`) and show their own text; `message` is for developers.", + "signature": "class MdbaseError extends Error", + "members": [ + { + "name": "code", + "signature": "readonly code: ErrorCode", + "description": "" + }, + { + "name": "recovery", + "signature": "readonly recovery: Recovery", + "description": "" + }, + { + "name": "reason", + "signature": "readonly reason?: string", + "description": "" + }, + { + "name": "details", + "signature": "readonly details?: unknown", + "description": "" + }, + { + "name": "retryAfterMs", + "signature": "readonly retryAfterMs?: number", + "description": "" + }, + { + "name": "issues", + "signature": "readonly issues?: Issue[]", + "description": "" + }, + { + "name": "traceId", + "signature": "readonly traceId?: string", + "description": "" + }, + { + "name": "mutationId", + "signature": "readonly mutationId?: Uuid", + "description": "The original mutation of a failed or unsettled write. Its outcome may still be\nunknown: resolve it with `receipt` / `awaitReceipt` / `pendingWrites` under this\nsame ID. The SDK never resubmits it under a new identity. Absent for reads and\ndry runs, which capture nothing. Not part of `toProblem`." + }, + { + "name": "mutationIds", + "signature": "readonly mutationIds?: readonly Uuid[]", + "description": "Every original mutation of a failed partial batch, in op order." + }, + { + "name": "constructor", + "signature": "constructor(problem: Problem, context?: MdbaseErrorContext)", + "description": "" + }, + { + "name": "toProblem", + "signature": "toProblem(): Problem", + "description": "" + } + ] + }, + { + "name": "isMdbaseError", + "kind": "function", + "description": "", + "signature": "function isMdbaseError(e: unknown, code?: ErrorCode): e is MdbaseError;" + }, + { + "name": "summarizeStatus", + "kind": "function", + "description": "Pass the application snapshot's `problem` as `problem` so a live server\nrefusal (collection_migrating, update_required, account_suspended) shows as\nthat, not as offline or catching up.", + "signature": "function summarizeStatus(s: SyncStatus, problem?: string | null, transport?: TransportHealth | null, live?: boolean | null): StatusSummary;" + } + ], + "index": [ + { + "name": "ACCOUNT_KEY_STATES", + "kind": "constant", + "description": "Where an account-key unlock stands on this replica." + }, + { + "name": "AccountIdentity", + "kind": "interface", + "description": "" + }, + { + "name": "AccountKeyState", + "kind": "type", + "description": "" + }, + { + "name": "AccountKeyStatus", + "kind": "interface", + "description": "" + }, + { + "name": "AccountProfile", + "kind": "interface", + "description": "" + }, + { + "name": "API_VERSIONS", + "kind": "constant", + "description": "API versions this SDK speaks, highest first." + }, + { + "name": "ApplicationJson", + "kind": "type", + "description": "" + }, + { + "name": "applicationPhase", + "kind": "function", + "description": "Derive the phase of a public application snapshot." + }, + { + "name": "ApplicationStorageDeclaration", + "kind": "interface", + "description": "" + }, + { + "name": "AppliedPrefix", + "kind": "interface", + "description": "Historical chain at the echoed seq, NOT the local current chain/generations." + }, + { + "name": "AppliedPrefixParams", + "kind": "interface", + "description": "Historical position to compare under the existing authenticated READ session." + }, + { + "name": "approvalReadiness", + "kind": "constant", + "description": "" + }, + { + "name": "ApprovalReadiness", + "kind": "interface", + "description": "Host-only `approval` push, not a joiner code or an approval result." + }, + { + "name": "AppTimersPort", + "kind": "interface", + "description": "A narrow original-app-consent port, supplied by the Connect client or owned native Worker." + }, + { + "name": "AttachmentContentV1", + "kind": "interface", + "description": "" + }, + { + "name": "AttachmentHoldContent", + "kind": "interface", + "description": "Binary Hold content retains the COMPLETE typed manifest/content identity." + }, + { + "name": "AttachmentRefV1", + "kind": "interface", + "description": "" + }, + { + "name": "AuthenticatedRelayByteDuplex", + "kind": "interface", + "description": "The authenticated relay stream `openPipe` returns." + }, + { + "name": "BlobRef", + "kind": "interface", + "description": "" + }, + { + "name": "ByteChannel", + "kind": "interface", + "description": "A byte channel carrying the plaintext record stream (after Noise, if any)." + }, + { + "name": "bytesEqual", + "kind": "function", + "description": "Byte-wise equality." + }, + { + "name": "ByteStream", + "kind": "interface", + "description": "A duplex byte stream (a Node socket, a named pipe)." + }, + { + "name": "CborError", + "kind": "class", + "description": "Bytes that are not valid `mdb-cbor/1`, or a value that cannot be encoded." + }, + { + "name": "CborErrorKind", + "kind": "type", + "description": "" + }, + { + "name": "CborMap", + "kind": "type", + "description": "" + }, + { + "name": "CborValue", + "kind": "type", + "description": "" + }, + { + "name": "Change", + "kind": "interface", + "description": "" + }, + { + "name": "ChangesWatch", + "kind": "interface", + "description": "Callable for compatibility with the former stop-only return value." + }, + { + "name": "ChangesWatchState", + "kind": "type", + "description": "Local subscription state, not cache completeness or fresh authority." + }, + { + "name": "ClientKey", + "kind": "interface", + "description": "" + }, + { + "name": "clientPrologue", + "kind": "function", + "description": "The client session prologue." + }, + { + "name": "Codec", + "kind": "interface", + "description": "" + }, + { + "name": "COLLECTION_UNAVAILABLE_REASONS", + "kind": "constant", + "description": "" + }, + { + "name": "CollectionAvailability", + "kind": "type", + "description": "" + }, + { + "name": "CollectionMemberProfile", + "kind": "interface", + "description": "" + }, + { + "name": "collectionUnavailableError", + "kind": "function", + "description": "The error that opening an unavailable collection fails with." + }, + { + "name": "CollectionUnavailableReason", + "kind": "type", + "description": "" + }, + { + "name": "compareVersions", + "kind": "function", + "description": "SemVer precedence." + }, + { + "name": "ConfirmedHead", + "kind": "interface", + "description": "Confirmed-only prefix facts." + }, + { + "name": "Conflict", + "kind": "interface", + "description": "" + }, + { + "name": "ConflictEntry", + "kind": "interface", + "description": "" + }, + { + "name": "ConflictValue", + "kind": "type", + "description": "" + }, + { + "name": "Connector", + "kind": "interface", + "description": "Opens a port to a replica and performs the hello exchange." + }, + { + "name": "ContractIdentity", + "kind": "interface", + "description": "The exact contract and implementation a view or write went through." + }, + { + "name": "ContractProjection", + "kind": "interface", + "description": "" + }, + { + "name": "ContractQueryOptions", + "kind": "interface", + "description": "" + }, + { + "name": "ContractQueryRecord", + "kind": "interface", + "description": "" + }, + { + "name": "ContractQueryResult", + "kind": "interface", + "description": "" + }, + { + "name": "ContractRecordView", + "kind": "interface", + "description": "One record's valid contract view." + }, + { + "name": "ContractRef", + "kind": "interface", + "description": "" + }, + { + "name": "ContractSelector", + "kind": "interface", + "description": "An exact registered record contract." + }, + { + "name": "ControlRouteOptions", + "kind": "interface", + "description": "" + }, + { + "name": "controlRouteResolver", + "kind": "function", + "description": "A `resolveRoute` for `relayConnector`, backed by the control plane." + }, + { + "name": "ControlRouteResponse", + "kind": "interface", + "description": "" + }, + { + "name": "ControlRouteTarget", + "kind": "interface", + "description": "" + }, + { + "name": "createViewSource", + "kind": "function", + "description": "Submit exactly the caller-captured document/UUID/MID through ordinary create." + }, + { + "name": "CurrentAccount", + "kind": "interface", + "description": "" + }, + { + "name": "decode", + "kind": "function", + "description": "Decode exactly one canonical `mdb-cbor/1` item." + }, + { + "name": "DESCRIBE_TYPING_MAX_PATHS", + "kind": "constant", + "description": "" + }, + { + "name": "DESCRIBE_TYPING_MAX_TYPES", + "kind": "constant", + "description": "Request bounds (`too_large` beyond them on the replica." + }, + { + "name": "describeDiagnostic", + "kind": "function", + "description": "Developer detail for logs and consoles, including notes on non-problems (for example a superseded registration ID)." + }, + { + "name": "describeError", + "kind": "function", + "description": "Describe a problem for people." + }, + { + "name": "DescribeErrorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "describeHold", + "kind": "function", + "description": "Present one hold." + }, + { + "name": "DescribeHoldOptions", + "kind": "interface", + "description": "" + }, + { + "name": "DescribeOptions", + "kind": "interface", + "description": "" + }, + { + "name": "DescribeTypingResult", + "kind": "interface", + "description": "" + }, + { + "name": "DesiredTimer", + "kind": "interface", + "description": "Backend-independent, content-free Connect control-plane timers." + }, + { + "name": "DEVICE_KINDS", + "kind": "constant", + "description": "`device-kind` (policy.md)." + }, + { + "name": "DeviceKind", + "kind": "type", + "description": "" + }, + { + "name": "diffEdits", + "kind": "function", + "description": "The smallest single edit turning `base` into `next`, in Unicode scalar offsets of `base` (the `body_edits` unit)." + }, + { + "name": "duplexCarrier", + "kind": "function", + "description": "Whole Noise messages over an authenticated relay duplex." + }, + { + "name": "effectiveCapabilities", + "kind": "function", + "description": "Prod `effectiveCapabilities`." + }, + { + "name": "encode", + "kind": "function", + "description": "Encode a value in canonical `mdb-cbor/1` form." + }, + { + "name": "encodeSecret", + "kind": "function", + "description": "Encode key-bearing values, wiping all encoder-owned scratch buffers (including superseded growth buffers) on return or throw." + }, + { + "name": "ERROR_CODES", + "kind": "constant", + "description": "" + }, + { + "name": "ErrorCode", + "kind": "type", + "description": "" + }, + { + "name": "ExecuteViewOptions", + "kind": "interface", + "description": "" + }, + { + "name": "FcmChannelOptions", + "kind": "interface", + "description": "" + }, + { + "name": "FenceHandler", + "kind": "type", + "description": "Fence handler offered by an editor host (§14)." + }, + { + "name": "FileAction", + "kind": "type", + "description": "What a session may do with files." + }, + { + "name": "FileContentProfile", + "kind": "type", + "description": "Actual persisted file kind and content representation (§10.1), never inferred from the path." + }, + { + "name": "fileGrant", + "kind": "function", + "description": "The file actions and folder scope of a session, mapped from its native grant." + }, + { + "name": "FileGrant", + "kind": "interface", + "description": "" + }, + { + "name": "FileRef", + "kind": "type", + "description": "" + }, + { + "name": "FilesApi", + "kind": "class", + "description": "" + }, + { + "name": "FileScope", + "kind": "type", + "description": "Where file actions apply." + }, + { + "name": "FileView", + "kind": "interface", + "description": "" + }, + { + "name": "FileWrite", + "kind": "interface", + "description": "A tracked upload receipt with its original, replica-owned file identity." + }, + { + "name": "Float64", + "kind": "class", + "description": "A float whose value is integral (or −0), kept distinct from an integer." + }, + { + "name": "formatSas", + "kind": "function", + "description": "Format either computer's local comparison code, preserving leading zeroes." + }, + { + "name": "framedPort", + "kind": "function", + "description": "Turn a byte channel of `u32be(length) ‖ frame` records into a frame port." + }, + { + "name": "FramePort", + "kind": "interface", + "description": "" + }, + { + "name": "fromHex", + "kind": "function", + "description": "Parse lowercase or uppercase hex." + }, + { + "name": "generateKeyPair", + "kind": "function", + "description": "" + }, + { + "name": "GrantInfo", + "kind": "interface", + "description": "" + }, + { + "name": "handleMdbaseNotificationClick", + "kind": "function", + "description": "A service-worker `notificationclick` handler." + }, + { + "name": "HandleMdbaseNotificationClickOptions", + "kind": "interface", + "description": "" + }, + { + "name": "handleMdbasePush", + "kind": "function", + "description": "A service-worker `push` handler." + }, + { + "name": "HandleMdbasePushOptions", + "kind": "interface", + "description": "" + }, + { + "name": "Hash", + "kind": "type", + "description": "`sha256:<64 lowercase hex>`." + }, + { + "name": "HelloResult", + "kind": "interface", + "description": "" + }, + { + "name": "Hold", + "kind": "interface", + "description": "" + }, + { + "name": "HOLD_COMPARE_SCHEME", + "kind": "constant", + "description": "" + }, + { + "name": "HOLD_TITLE", + "kind": "constant", + "description": "" + }, + { + "name": "HoldAction", + "kind": "type", + "description": "What the user can do about a hold." + }, + { + "name": "HoldActionOption", + "kind": "interface", + "description": "" + }, + { + "name": "holdCompareLink", + "kind": "function", + "description": "The compare hand-off the daemon tray opens and apps register." + }, + { + "name": "holdNotice", + "kind": "function", + "description": "Banner text for a list of holds, or `null` when there are none." + }, + { + "name": "HoldPresentation", + "kind": "interface", + "description": "" + }, + { + "name": "HoldReason", + "kind": "type", + "description": "" + }, + { + "name": "HoldResolution", + "kind": "type", + "description": "" + }, + { + "name": "IkInitiator", + "kind": "class", + "description": "The initiator (client) side of IK." + }, + { + "name": "IkResponder", + "kind": "class", + "description": "The responder (replica) side of IK." + }, + { + "name": "Incident", + "kind": "interface", + "description": "" + }, + { + "name": "Include", + "kind": "interface", + "description": "" + }, + { + "name": "indexedDbKeyStorage", + "kind": "function", + "description": "Keys in IndexedDB (browsers, extensions, Obsidian)." + }, + { + "name": "InProcessConnectOptions", + "kind": "interface", + "description": "" + }, + { + "name": "inProcessConnector", + "kind": "function", + "description": "A connector to a runtime in this JS process." + }, + { + "name": "InProcessRuntime", + "kind": "interface", + "description": "What a shared runtime offers in process." + }, + { + "name": "InvalidPersonRecord", + "kind": "interface", + "description": "" + }, + { + "name": "isCollectionUnavailableReason", + "kind": "function", + "description": "" + }, + { + "name": "isConfirmed", + "kind": "function", + "description": "True once a write is durable." + }, + { + "name": "isErrorCode", + "kind": "function", + "description": "" + }, + { + "name": "isReadyPhase", + "kind": "function", + "description": "True when the phase is ready for this collection with a current connection." + }, + { + "name": "Issue", + "kind": "interface", + "description": "" + }, + { + "name": "isValid", + "kind": "function", + "description": "True when `bytes` is valid `mdb-cbor/1`." + }, + { + "name": "JsonLike", + "kind": "type", + "description": "" + }, + { + "name": "KeyPair", + "kind": "interface", + "description": "" + }, + { + "name": "keyPairFromSecret", + "kind": "function", + "description": "" + }, + { + "name": "KeyStorage", + "kind": "interface", + "description": "" + }, + { + "name": "LinkState", + "kind": "type", + "description": "The link between this client and its replica (not the replica's sync state)." + }, + { + "name": "ListViewsResult", + "kind": "interface", + "description": "" + }, + { + "name": "LiveQueryState", + "kind": "interface", + "description": "" + }, + { + "name": "LoadKeyOptions", + "kind": "interface", + "description": "" + }, + { + "name": "loadOrCreateClientKey", + "kind": "function", + "description": "Load one committed identity." + }, + { + "name": "LocalLink", + "kind": "interface", + "description": "" + }, + { + "name": "localLinkConnector", + "kind": "function", + "description": "A connector to the desktop daemon over the authenticated localhost link." + }, + { + "name": "LocalLinkConnectorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "localLinkFirstPayload", + "kind": "function", + "description": "`{0." + }, + { + "name": "Materialization", + "kind": "interface", + "description": "" + }, + { + "name": "MAX_CONVENIENCE_BYTES", + "kind": "constant", + "description": "Convenience reads that return whole bytes are capped, as today (§10.2)." + }, + { + "name": "MAX_DEPTH", + "kind": "constant", + "description": "Maximum nesting depth, as in the Rust codec." + }, + { + "name": "MDBASE_NOTIFICATION_CLICK", + "kind": "constant", + "description": "The message `handleMdbaseNotificationClick` posts to the app window it focuses." + }, + { + "name": "MDBASE_NOTIFICATION_TYPE", + "kind": "constant", + "description": "" + }, + { + "name": "MdbaseAccessRequestOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplication", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationAssessment", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationCapabilities", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationCapability", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationEntry", + "kind": "interface", + "description": "Structural public consumer port." + }, + { + "name": "MdbaseApplicationFacet", + "kind": "type", + "description": "" + }, + { + "name": "MdbaseApplicationFacetSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationProblem", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationReadinessSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationRequestOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationSelectionSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationSelectionState", + "kind": "type", + "description": "- none." + }, + { + "name": "MdbaseApplicationSetup", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseApplicationUnavailableError", + "kind": "class", + "description": "" + }, + { + "name": "MdbaseApprovalOutcome", + "kind": "type", + "description": "SDK-6." + }, + { + "name": "MdbaseCollectionDeleteOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseCollectionSummary", + "kind": "type", + "description": "One collection this app may open, as listed in `snapshot.collections`." + }, + { + "name": "MdbaseConnect", + "kind": "class", + "description": "The application entry." + }, + { + "name": "mdbaseError", + "kind": "function", + "description": "Build an error locally (transport failures, cancellation, schema mismatches)." + }, + { + "name": "MdbaseErrorAction", + "kind": "type", + "description": "" + }, + { + "name": "MdbaseErrorContext", + "kind": "interface", + "description": "Local facts about a failure that are not part of the wire problem." + }, + { + "name": "MdbaseErrorDescription", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseLinks", + "kind": "interface", + "description": "SDK-4." + }, + { + "name": "MdbaseNotification", + "kind": "interface", + "description": "A validated envelope." + }, + { + "name": "MdbaseNotificationClickMessage", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseNotificationDisplayOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseNotificationPresentation", + "kind": "interface", + "description": "" + }, + { + "name": "MdbasePeopleClient", + "kind": "class", + "description": "Complete directories only." + }, + { + "name": "MdbasePhaseKind", + "kind": "type", + "description": "" + }, + { + "name": "MdbasePhaseOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseProblem", + "kind": "type", + "description": "A described problem." + }, + { + "name": "MdbaseProblemMessages", + "kind": "interface", + "description": "Translated copy for one problem key." + }, + { + "name": "MdbaseSetupChange", + "kind": "interface", + "description": "One line of the review screen." + }, + { + "name": "MdbaseSetupChangeKind", + "kind": "type", + "description": "" + }, + { + "name": "MdbaseSetupConflict", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseSetupPlanOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseSetupRecord", + "kind": "interface", + "description": "A starter record created once, in the same mutation as the first setup." + }, + { + "name": "MdbaseSetupRefusalCode", + "kind": "type", + "description": "Why setup can't run." + }, + { + "name": "MdbaseSignInOpener", + "kind": "interface", + "description": "Sign-in outside a popup." + }, + { + "name": "MdbaseSignOutOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseTransferItem", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseTransferMap", + "kind": "type", + "description": "Map one source record to what to create, or null to leave it out." + }, + { + "name": "MdbaseTransferOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseTransferPlanOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseTransferProgress", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseTransferResult", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseUnavailableType", + "kind": "interface", + "description": "A declared type the app cannot use." + }, + { + "name": "MdbaseVersion", + "kind": "interface", + "description": "The SDK's one semantic-version implementation (SemVer 2.0.0)." + }, + { + "name": "MdbaseVersionRelation", + "kind": "type", + "description": "How an installed version relates to the version an app ships." + }, + { + "name": "memoryKeyStorage", + "kind": "function", + "description": "Keys in memory only (tests, short-lived scripts)." + }, + { + "name": "memoryWriteLedgerStore", + "kind": "function", + "description": "" + }, + { + "name": "MessageCarrier", + "kind": "interface", + "description": "A message-oriented channel carrying whole Noise messages." + }, + { + "name": "NextBridge", + "kind": "interface", + "description": "What `mdbaseNext(connection)` from `@mdbase-dev/connect/next` provides." + }, + { + "name": "nextRelayConnector", + "kind": "function", + "description": "A connector for web apps reaching a device replica through the relay." + }, + { + "name": "NextRelayConnectorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "NextRouteResponse", + "kind": "interface", + "description": "" + }, + { + "name": "NextRouteTarget", + "kind": "interface", + "description": "" + }, + { + "name": "noiseChannel", + "kind": "function", + "description": "A byte channel over an established Noise transport." + }, + { + "name": "noiseConnector", + "kind": "function", + "description": "A `Connector` that opens Noise IK sessions to a replica." + }, + { + "name": "NoiseConnectorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "NoiseError", + "kind": "class", + "description": "" + }, + { + "name": "NoiseTarget", + "kind": "interface", + "description": "" + }, + { + "name": "NoiseTransport", + "kind": "interface", + "description": "The two transport ciphers after the handshake, and the handshake hash." + }, + { + "name": "Op", + "kind": "type", + "description": "" + }, + { + "name": "OpenConfig", + "kind": "interface", + "description": "" + }, + { + "name": "OpenedPort", + "kind": "interface", + "description": "" + }, + { + "name": "orderTargets", + "kind": "function", + "description": "Valid relay targets, sticky device first, then those hinted online, in route order." + }, + { + "name": "PagesOptions", + "kind": "interface", + "description": "" + }, + { + "name": "PagesReset", + "kind": "interface", + "description": "" + }, + { + "name": "parseCollectionAvailability", + "kind": "function", + "description": "Parse a server `availability` object." + }, + { + "name": "parseHoldCompareLink", + "kind": "function", + "description": "Parse a compare hand-off." + }, + { + "name": "parseMdbaseNativeNotification", + "kind": "function", + "description": "Parse the string-valued data of an FCM/APNs notification (`version` \"1\" or 1)." + }, + { + "name": "parseMdbasePush", + "kind": "function", + "description": "Parse a Web Push payload." + }, + { + "name": "parseVersion", + "kind": "function", + "description": "Parse `1.2.3`, `0.3.0-rc.12`, `1.0.0+build`." + }, + { + "name": "Peer", + "kind": "interface", + "description": "" + }, + { + "name": "PendingDevice", + "kind": "interface", + "description": "" + }, + { + "name": "PeopleAuthority", + "kind": "interface", + "description": "" + }, + { + "name": "PeopleDirectory", + "kind": "interface", + "description": "" + }, + { + "name": "PeopleDirectoryOptions", + "kind": "interface", + "description": "" + }, + { + "name": "PeopleRequestOptions", + "kind": "interface", + "description": "" + }, + { + "name": "PERSON_CONTRACT", + "kind": "constant", + "description": "" + }, + { + "name": "PersonRecord", + "kind": "interface", + "description": "" + }, + { + "name": "PersonResolution", + "kind": "type", + "description": "" + }, + { + "name": "PipeAuth", + "kind": "interface", + "description": "" + }, + { + "name": "pipeCloseError", + "kind": "function", + "description": "" + }, + { + "name": "PlainValue", + "kind": "type", + "description": "Plain JSON-ish data an app may pass where a `value` is expected." + }, + { + "name": "portPair", + "kind": "function", + "description": "Two connected in-memory ports (tests, and same-process hosts)." + }, + { + "name": "PresenceApi", + "kind": "class", + "description": "" + }, + { + "name": "Problem", + "kind": "interface", + "description": "" + }, + { + "name": "PROBLEM_CODES", + "kind": "constant", + "description": "Every problem code an app can meet." + }, + { + "name": "ProblemCode", + "kind": "type", + "description": "" + }, + { + "name": "problemCodeOf", + "kind": "function", + "description": "The ProblemCode a code means, or null when it has none." + }, + { + "name": "PublishState", + "kind": "type", + "description": "Local file publication, independent of log confirmation." + }, + { + "name": "QueryExecutionProfile", + "kind": "type", + "description": "Trusted host initialization policy, not a grant/session/query override." + }, + { + "name": "QueryGroup", + "kind": "interface", + "description": "Complete pre-window grouping tuple and native summary results." + }, + { + "name": "QueryMetadata", + "kind": "interface", + "description": "Full metadata replacement bound to the enclosing live update's asOf." + }, + { + "name": "queryPages", + "kind": "function", + "description": "Alias the canonical paginator." + }, + { + "name": "QueryResult", + "kind": "interface", + "description": "" + }, + { + "name": "QueryUpdate", + "kind": "interface", + "description": "" + }, + { + "name": "READ_MANY_MAX_CONCURRENCY", + "kind": "constant", + "description": "" + }, + { + "name": "READ_MANY_MAX_REFS", + "kind": "constant", + "description": "" + }, + { + "name": "readMany", + "kind": "function", + "description": "Ordered ordinary reads, not a transaction or a consistent native snapshot." + }, + { + "name": "ReadManyOptions", + "kind": "interface", + "description": "" + }, + { + "name": "readMdbaseNotificationData", + "kind": "function", + "description": "Read the envelope kept on a shown notification, or on a native tap." + }, + { + "name": "readViewSourceRecord", + "kind": "function", + "description": "Read a complete current local record, not a projection or a source-text DTO." + }, + { + "name": "RecordFlushOptions", + "kind": "interface", + "description": "" + }, + { + "name": "RecordFlushResult", + "kind": "type", + "description": "" + }, + { + "name": "RecordLease", + "kind": "interface", + "description": "" + }, + { + "name": "RecordOpenOptions", + "kind": "interface", + "description": "" + }, + { + "name": "RecordReader", + "kind": "class", + "description": "Splits a byte stream into `u32be(length) ‖ frame` records." + }, + { + "name": "RecordRecovery", + "kind": "interface", + "description": "" + }, + { + "name": "RecordRecoveryRef", + "kind": "interface", + "description": "" + }, + { + "name": "RecordRef", + "kind": "type", + "description": "" + }, + { + "name": "RecordResolution", + "kind": "type", + "description": "" + }, + { + "name": "Records", + "kind": "interface", + "description": "" + }, + { + "name": "RecordSession", + "kind": "interface", + "description": "" + }, + { + "name": "RecordSessionSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "RecordSessionState", + "kind": "type", + "description": "" + }, + { + "name": "RecordValidation", + "kind": "interface", + "description": "" + }, + { + "name": "RecordWrite", + "kind": "interface", + "description": "A create receipt with the record identity minted by the SDK, even when the replica omits optimistic records from its response." + }, + { + "name": "Recovery", + "kind": "type", + "description": "" + }, + { + "name": "recoveryFor", + "kind": "function", + "description": "The recovery action for a code, or `contact_support` for a code this SDK doesn't know." + }, + { + "name": "RecoveryKeyStatus", + "kind": "interface", + "description": "" + }, + { + "name": "relayConnector", + "kind": "function", + "description": "A connector for web apps and other remote clients." + }, + { + "name": "RelayConnectorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "relayPipeCarrier", + "kind": "function", + "description": "Open a relay pipe and return a carrier of whole Noise messages." + }, + { + "name": "RelayRoute", + "kind": "interface", + "description": "" + }, + { + "name": "resolveRecordLinks", + "kind": "function", + "description": "Resolve a persisted flat frontmatter key through ONE ordinary native query." + }, + { + "name": "Resyncing", + "kind": "interface", + "description": "" + }, + { + "name": "ResyncPhase", + "kind": "type", + "description": "Allocated lost-tail repair phases." + }, + { + "name": "revisionOf", + "kind": "function", + "description": "`sha256:` of a string's UTF-8 bytes." + }, + { + "name": "routeFromControl", + "kind": "function", + "description": "The first usable target, or `null` when there is none (the SDK then reports `unavailable` / `no_device_online`." + }, + { + "name": "RuntimeInfo", + "kind": "interface", + "description": "" + }, + { + "name": "sameIdentity", + "kind": "function", + "description": "" + }, + { + "name": "SaveState", + "kind": "type", + "description": "- `saved`." + }, + { + "name": "SchemaError", + "kind": "class", + "description": "" + }, + { + "name": "Session", + "kind": "class", + "description": "" + }, + { + "name": "SESSION_LIFETIME_MS", + "kind": "constant", + "description": "Session lifetime (§12.3)." + }, + { + "name": "showMdbaseNotification", + "kind": "function", + "description": "Validate a Web Push payload and show it." + }, + { + "name": "SLOW_OPENING_MS", + "kind": "constant", + "description": "Opening longer than this reads `slow` (SDK-9)." + }, + { + "name": "StaticKey", + "kind": "interface", + "description": "A static key the SDK can use without seeing the secret." + }, + { + "name": "staticKeyOf", + "kind": "function", + "description": "" + }, + { + "name": "StatusKind", + "kind": "type", + "description": "" + }, + { + "name": "StatusSummary", + "kind": "interface", + "description": "" + }, + { + "name": "StoredKey", + "kind": "type", + "description": "What a key store persists for one key." + }, + { + "name": "streamCarrier", + "kind": "function", + "description": "Carry Noise messages on a byte stream as `u16be(length) ‖ message`." + }, + { + "name": "structMap", + "kind": "function", + "description": "A struct map from `[key, value]` pairs, dropping `undefined` values." + }, + { + "name": "SubmitParams", + "kind": "interface", + "description": "" + }, + { + "name": "SyncStatus", + "kind": "interface", + "description": "\"Confirmed through N, plus pending\" (§7)." + }, + { + "name": "syncStatusText", + "kind": "function", + "description": "The sync status line every client shows." + }, + { + "name": "TEMPORAL_HINTS", + "kind": "constant", + "description": "Core's `temporal_hint`." + }, + { + "name": "TemporalHint", + "kind": "type", + "description": "" + }, + { + "name": "TextOrBlob", + "kind": "type", + "description": "Text/legacy BlobRef bytes are unchanged." + }, + { + "name": "Timer", + "kind": "interface", + "description": "" + }, + { + "name": "TimerCancellation", + "kind": "interface", + "description": "" + }, + { + "name": "TimerChannelRegistration", + "kind": "interface", + "description": "" + }, + { + "name": "TimerList", + "kind": "interface", + "description": "" + }, + { + "name": "TimerOperationIdentity", + "kind": "interface", + "description": "Caller persists this original identity and desired snapshot BEFORE dispatch." + }, + { + "name": "TimerOperationLookup", + "kind": "type", + "description": "" + }, + { + "name": "TimerOperationReceipt", + "kind": "interface", + "description": "" + }, + { + "name": "TimerReconcileInput", + "kind": "interface", + "description": "" + }, + { + "name": "TimerReconciliation", + "kind": "interface", + "description": "" + }, + { + "name": "TimerRecoverableReconcileInput", + "kind": "interface", + "description": "" + }, + { + "name": "TimerRequestOptions", + "kind": "interface", + "description": "" + }, + { + "name": "TimersApi", + "kind": "class", + "description": "Opaque timer HTTP API." + }, + { + "name": "toHex", + "kind": "function", + "description": "Lowercase hex." + }, + { + "name": "toPlain", + "kind": "function", + "description": "A plain-object view of a value, for display and for code written against JSON." + }, + { + "name": "toValue", + "kind": "function", + "description": "Convert plain data into a `value`." + }, + { + "name": "TransferProgress", + "kind": "interface", + "description": "Upload transfers are identified by UUID, download streams by number." + }, + { + "name": "TransportHealth", + "kind": "interface", + "description": "" + }, + { + "name": "TransportHealthKind", + "kind": "type", + "description": "Why the log cannot be reached right now, from the last failed request." + }, + { + "name": "TypeCreation", + "kind": "interface", + "description": "A type created by `MdbaseClient.createType`." + }, + { + "name": "TypePackAction", + "kind": "type", + "description": "" + }, + { + "name": "TypePackApplyResult", + "kind": "interface", + "description": "" + }, + { + "name": "TypePackConflict", + "kind": "interface", + "description": "" + }, + { + "name": "TypePackOptions", + "kind": "interface", + "description": "The caller's decisions." + }, + { + "name": "TypePackPlan", + "kind": "interface", + "description": "One reviewable plan." + }, + { + "name": "TypePackProvision", + "kind": "interface", + "description": "A pack." + }, + { + "name": "TypePackReceipt", + "kind": "interface", + "description": "" + }, + { + "name": "TypePackRefusalCode", + "kind": "type", + "description": "Stable refusal codes." + }, + { + "name": "TypePackResource", + "kind": "interface", + "description": "One resource of the plan." + }, + { + "name": "TypePackResourceKind", + "kind": "type", + "description": "" + }, + { + "name": "TypePackStatus", + "kind": "type", + "description": "" + }, + { + "name": "TypingEntry", + "kind": "interface", + "description": "" + }, + { + "name": "updateViewSource", + "kind": "function", + "description": "Replace a full source at its actual captured UUID/path/revision, including a pending local revision." + }, + { + "name": "UploadOptions", + "kind": "interface", + "description": "" + }, + { + "name": "UploadSource", + "kind": "type", + "description": "Anything the SDK can upload from." + }, + { + "name": "Uuid", + "kind": "type", + "description": "" + }, + { + "name": "uuidv7", + "kind": "function", + "description": "A new UUIDv7 (RFC 9562)." + }, + { + "name": "ValidationIssue", + "kind": "interface", + "description": "A real Core diagnostic of a current persisted/local-native record." + }, + { + "name": "ValidationLevel", + "kind": "type", + "description": "Validation level (spec 04)." + }, + { + "name": "Value", + "kind": "type", + "description": "A frontmatter or app value." + }, + { + "name": "valueEquals", + "kind": "function", + "description": "Spec 12A-style equality of two values (exact." + }, + { + "name": "versionRelation", + "kind": "function", + "description": "" + }, + { + "name": "ViewSourceCreateInput", + "kind": "interface", + "description": "Capture identities once, before the first submission." + }, + { + "name": "ViewSourceReadOptions", + "kind": "interface", + "description": "" + }, + { + "name": "ViewSourceWriteOptions", + "kind": "interface", + "description": "" + }, + { + "name": "WasmRuntime", + "kind": "class", + "description": "A `runtime.wasm` instance hosting one collection." + }, + { + "name": "WebPushChannelOptions", + "kind": "interface", + "description": "" + }, + { + "name": "webSocketCarrier", + "kind": "function", + "description": "Open a WebSocket carrying one Noise message per binary message." + }, + { + "name": "WebSocketFactory", + "kind": "type", + "description": "" + }, + { + "name": "WebSocketLike", + "kind": "interface", + "description": "The subset of the WebSocket API the SDK uses (browser `WebSocket`, Node 22's, `ws`)." + }, + { + "name": "wire", + "kind": "value", + "description": "" + }, + { + "name": "WriteLedgerEntry", + "kind": "interface", + "description": "" + }, + { + "name": "WriteLedgerSnapshot", + "kind": "interface", + "description": "" + }, + { + "name": "WriteLedgerStore", + "kind": "interface", + "description": "Where entries persist." + }, + { + "name": "WriteLedgerView", + "kind": "interface", + "description": "`client.ledger`." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/describe", + "title": "Problem descriptions", + "featured": [ + { + "name": "describeError", + "kind": "function", + "description": "Describe a problem for people. Accepts a code string, an `MdbaseError` or SDK\nerror, `{code, reason?}`, a public application snapshot, a phase, a readiness\nfacet, a collection availability or a collection list entry carrying one.\nReturns `null` when the input has no problem (a ready snapshot) or is not a\nproblem (a cancellation, a note the SDK handled).\nThe most specific known code wins (server reason, then reason, then code).", + "signature": "function describeError(input: unknown, options?: DescribeErrorOptions): MdbaseErrorDescription | null;" + }, + { + "name": "describeDiagnostic", + "kind": "function", + "description": "Developer detail for logs and consoles, including notes on non-problems\n(for example a superseded registration ID). Never show it to end users.", + "signature": "function describeDiagnostic(input: unknown, options?: Pick): string | null;" + }, + { + "name": "MdbaseErrorDescription", + "kind": "interface", + "description": "", + "signature": "interface MdbaseErrorDescription", + "members": [ + { + "name": "key", + "signature": "readonly key: string", + "description": "Stable id for translation and analytics, e.g. \"collection_migrating\"." + }, + { + "name": "title", + "signature": "readonly title: string", + "description": "" + }, + { + "name": "message", + "signature": "readonly message: string", + "description": "" + }, + { + "name": "action", + "signature": "readonly action: MdbaseErrorAction", + "description": "" + }, + { + "name": "actionLabel", + "signature": "readonly actionLabel: string | null", + "description": "" + }, + { + "name": "retryAfterMs", + "signature": "readonly retryAfterMs: number | null", + "description": "Server-directed wait, clamped to 5 s..10 min." + }, + { + "name": "updateUrl", + "signature": "readonly updateUrl: string | null", + "description": "Set with action `update_app`: the server's https link when it is on\nmdbase.dev or the declared CP origin, otherwise the mdbase downloads page." + }, + { + "name": "severity", + "signature": "readonly severity: \"info\" | \"warning\" | \"error\"", + "description": "" + }, + { + "name": "transient", + "signature": "readonly transient: boolean", + "description": "True when the SDK clears it by itself (busy, migrating); apps may show it quietly." + }, + { + "name": "code", + "signature": "readonly code: string", + "description": "The original code, for logs and support only. Do not show it as the message." + }, + { + "name": "developerMessage", + "signature": "readonly developerMessage: string | null", + "description": "Plain detail for the app's developer (logs, developer consoles), never for\nend users. Built only from typed codes and manifest paths; null when none.\nThe same text is available from `describeDiagnostic`." + } + ] + }, + { + "name": "MdbaseErrorAction", + "kind": "type", + "description": "", + "signature": "type MdbaseErrorAction = \"retry\" | \"wait\" | \"reload\" | \"sign_in\" | \"approve\" | \"choose_collection\" | \"set_up\" | \"update_app\" | \"free_space\" | \"check_connection\" | \"resolve_outcome\" | \"contact_support\" | \"none\";" + } + ], + "index": [ + { + "name": "DescribeErrorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "INTERNAL_PROBLEM_CODES", + "kind": "constant", + "description": "Published inventory of codec/protocol internals, test fakes and developer misuse." + }, + { + "name": "MdbaseProblemMessages", + "kind": "interface", + "description": "Translated copy for one problem key." + }, + { + "name": "NON_PROBLEM_CODES", + "kind": "constant", + "description": "Not problems." + }, + { + "name": "PROBLEM_CODES", + "kind": "constant", + "description": "Every problem code an app can meet." + }, + { + "name": "ProblemCode", + "kind": "type", + "description": "" + }, + { + "name": "problemCodeOf", + "kind": "function", + "description": "The ProblemCode a code means, or null when it has none." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/testing", + "title": "Testing", + "featured": [ + { + "name": "MemoryReplica", + "kind": "class", + "description": "", + "signature": "class MemoryReplica", + "members": [ + { + "name": "collection", + "signature": "readonly collection: w.Uuid", + "description": "" + }, + { + "name": "typingAnswer", + "signature": "typingAnswer?: (types: string[], paths: string[]) => w.DescribeTypingResult", + "description": "Test hook: answer `describe_typing` with this instead (for mismatch tests)." + }, + { + "name": "constructor", + "signature": "constructor(opts?: MemoryReplicaOptions)", + "description": "" + }, + { + "name": "connector", + "signature": "connector(): Connector", + "description": "A connector for `MdbaseClient.connect`. Each `open` is a new session." + }, + { + "name": "dropConnections", + "signature": "dropConnections(): void", + "description": "Drop every session (simulates a broken link; clients reconnect)." + }, + { + "name": "setOnline", + "signature": "setOnline(online: boolean): void", + "description": "Simulate the log service being unreachable: submits stay pending." + }, + { + "name": "seed", + "signature": "seed(r: { id?: w.Uuid; path: string; types?: string[]; frontmatter?: Record; body?: string; }): MemoryRecord", + "description": "Seed a confirmed record directly." + }, + { + "name": "confirmAll", + "signature": "confirmAll(): void", + "description": "Confirm every pending mutation now." + }, + { + "name": "editElsewhere", + "signature": "editElsewhere(ref: string, change: { body?: string; frontmatter?: Record; }): void", + "description": "Another device edits a record; the change arrives confirmed. A `null`\nfrontmatter value removes the field." + }, + { + "name": "renameElsewhere", + "signature": "renameElsewhere(ref: string, to: string): void", + "description": "Another device renames (moves) a record." + }, + { + "name": "deleteElsewhere", + "signature": "deleteElsewhere(ref: string): void", + "description": "Another device deletes a record." + }, + { + "name": "loseNextResponse", + "signature": "loseNextResponse(): void", + "description": "The next request runs but its response is lost and the session drops,\nas when a link breaks after a write was captured. Clients reconnect, and\na resubmitted write (same mutation ID) gets its original receipt." + }, + { + "name": "failNextWrite", + "signature": "failNextWrite(p?: w.Problem): void", + "description": "The next submit fails with `p` and captures nothing." + }, + { + "name": "stallConfirmations", + "signature": "stallConfirmations(stalled?: boolean): void", + "description": "Stuck pending: while stalled, writes are not confirmed on their own even\nthough the replica is online (`confirmAll` still confirms)." + }, + { + "name": "hold", + "signature": "hold(ref: string, o?: { reason?: w.HoldReason; mine?: string; theirs?: string; }): w.Uuid", + "description": "Put a hold on a record (as the replica does on a conflicting or suspect\nwrite). Resolve it with `client.resolveHold`." + }, + { + "name": "conflict", + "signature": "conflict(ref: string, o: { field: string; kept: PlainValue; lost: PlainValue; }): w.ConflictEntry", + "description": "Record a merged field conflict on a record (the kept value is applied)." + }, + { + "name": "seedType", + "signature": "seedType(name: string, definition?: Record): string", + "description": "Declare a type, so `describe` lists it with this definition and schema." + }, + { + "name": "seedBaseView", + "signature": "seedBaseView(view: MemoryBaseView): { record: w.Uuid; sourceRevision: string; ordinal: number; }", + "description": "Add a saved view for app Bases reads (another view in the same file when\nthe path exists). Returns its identity for `executeAppBases`." + }, + { + "name": "reject", + "signature": "reject(mutation: w.Uuid, p?: w.Problem): void", + "description": "Reject a pending mutation (as a failed check at head would)." + }, + { + "name": "allRecords", + "signature": "get allRecords(): MemoryRecord[]", + "description": "" + }, + { + "name": "sessionCount", + "signature": "get sessionCount(): number", + "description": "" + }, + { + "name": "seedResource", + "signature": "seedResource(path: string, text: string): void", + "description": "Seed a resource (a type file, mdbase.yaml)." + } + ] + }, + { + "name": "createApplicationFixture", + "kind": "function", + "description": "Frozen public test seam. No constructor override, alternate engine or alias.\nFixture lifetime fences are combined with supplied connection.isCurrent().\nOriginal scope/generation and clients stay exact; no production scope is minted.\nSelection/configuration never infer readiness or modify supplied native data.", + "signature": "function createApplicationFixture(options: ApplicationFixtureOptions): ApplicationFixture;" + } + ], + "index": [ + { + "name": "ApplicationFixture", + "kind": "interface", + "description": "" + }, + { + "name": "ApplicationFixtureAction", + "kind": "type", + "description": "" + }, + { + "name": "ApplicationFixtureCall", + "kind": "interface", + "description": "" + }, + { + "name": "ApplicationFixtureControls", + "kind": "interface", + "description": "" + }, + { + "name": "ApplicationFixtureHandler", + "kind": "type", + "description": "" + }, + { + "name": "ApplicationFixtureOptions", + "kind": "interface", + "description": "" + }, + { + "name": "createTestApp", + "kind": "function", + "description": "" + }, + { + "name": "createTestMdbase", + "kind": "function", + "description": "" + }, + { + "name": "MemoryContract", + "kind": "interface", + "description": "One type implementing one contract version, as Core's catalog would see it." + }, + { + "name": "MemoryRecord", + "kind": "interface", + "description": "" + }, + { + "name": "MemoryReplicaOptions", + "kind": "interface", + "description": "" + }, + { + "name": "serveNoise", + "kind": "function", + "description": "Accept one Noise session on `carrier` and serve it from `backend`." + }, + { + "name": "ServeNoiseOptions", + "kind": "interface", + "description": "" + }, + { + "name": "TestApp", + "kind": "interface", + "description": "" + }, + { + "name": "TestAppCollection", + "kind": "interface", + "description": "" + }, + { + "name": "TestAppFault", + "kind": "type", + "description": "One-shot faults, consumed by the next matching action." + }, + { + "name": "TestAppOptions", + "kind": "interface", + "description": "" + }, + { + "name": "TestAppPreset", + "kind": "type", + "description": "The screen to start on (or switch to with `set`)." + }, + { + "name": "TestMdbase", + "kind": "interface", + "description": "`createTestApp`, returning the `Mdbase` front door (as `createMdbase` does)." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/node", + "title": "Node connectors", + "featured": [], + "index": [ + { + "name": "connectDaemonLocalhost", + "kind": "function", + "description": "Connect to the desktop daemon over the localhost link as the hosting session of `collection` (§12.4)." + }, + { + "name": "DaemonIdentity", + "kind": "interface", + "description": "" + }, + { + "name": "daemonIdentityFile", + "kind": "function", + "description": "Where `daemon.json` lives." + }, + { + "name": "defaultDaemonStateDir", + "kind": "function", + "description": "The daemon's per-user state directory, as `crates/daemon` (`paths.rs`) defines it." + }, + { + "name": "defaultIpcPath", + "kind": "function", + "description": "The daemon's replica endpoint for the current user (§12.2)." + }, + { + "name": "ipcConnector", + "kind": "function", + "description": "A connector to the desktop daemon over local IPC." + }, + { + "name": "IpcConnectorOptions", + "kind": "interface", + "description": "" + }, + { + "name": "readDaemonIdentity", + "kind": "function", + "description": "Read the daemon's identity, `{\"device\"." + }, + { + "name": "readLocalLink", + "kind": "function", + "description": "The localhost link (§12.4)." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/manifest", + "title": "Manifest", + "featured": [], + "index": [ + { + "name": "AppManifestValidationError", + "kind": "class", + "description": "" + }, + { + "name": "formatManifestValidationIssues", + "kind": "function", + "description": "" + }, + { + "name": "ManifestValidationIssue", + "kind": "interface", + "description": "" + }, + { + "name": "ManifestValidationOptions", + "kind": "interface", + "description": "" + }, + { + "name": "ManifestValidationResult", + "kind": "type", + "description": "" + }, + { + "name": "MDBASE_TIMER_FIRED_CONTRACT", + "kind": "constant", + "description": "Exact identity of the standard timer-fired runtime event contract." + }, + { + "name": "parseAppManifest", + "kind": "function", + "description": "Validate, normalize optional sections, and return an isolated declaration." + }, + { + "name": "validateAppManifest", + "kind": "function", + "description": "Validate the complete bundled application declaration accepted by Connect." + }, + { + "name": "ValidatedAppManifest", + "kind": "type", + "description": "" + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/notifications", + "title": "Notifications", + "featured": [], + "index": [ + { + "name": "handleMdbaseNotificationClick", + "kind": "function", + "description": "A service-worker `notificationclick` handler." + }, + { + "name": "HandleMdbaseNotificationClickOptions", + "kind": "interface", + "description": "" + }, + { + "name": "handleMdbasePush", + "kind": "function", + "description": "A service-worker `push` handler." + }, + { + "name": "HandleMdbasePushOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MDBASE_NOTIFICATION_CLICK", + "kind": "constant", + "description": "The message `handleMdbaseNotificationClick` posts to the app window it focuses." + }, + { + "name": "MDBASE_NOTIFICATION_TYPE", + "kind": "constant", + "description": "" + }, + { + "name": "MdbaseNotification", + "kind": "interface", + "description": "A validated envelope." + }, + { + "name": "MdbaseNotificationClickEvent", + "kind": "interface", + "description": "The parts of a service-worker `NotificationEvent` the helpers use." + }, + { + "name": "MdbaseNotificationClickMessage", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseNotificationClients", + "kind": "interface", + "description": "The parts of a service worker's `Clients` the helpers use." + }, + { + "name": "MdbaseNotificationDisplayOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseNotificationPresentation", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseNotificationRegistration", + "kind": "interface", + "description": "The parts of a `ServiceWorkerRegistration` the helpers use." + }, + { + "name": "MdbasePushEvent", + "kind": "interface", + "description": "The parts of a service-worker `PushEvent` the helpers use." + }, + { + "name": "parseMdbaseNativeNotification", + "kind": "function", + "description": "Parse the string-valued data of an FCM/APNs notification (`version` \"1\" or 1)." + }, + { + "name": "parseMdbasePush", + "kind": "function", + "description": "Parse a Web Push payload." + }, + { + "name": "readMdbaseNotificationData", + "kind": "function", + "description": "Read the envelope kept on a shown notification, or on a native tap." + }, + { + "name": "showMdbaseNotification", + "kind": "function", + "description": "Validate a Web Push payload and show it." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/account", + "title": "Account keys", + "featured": [], + "index": [ + { + "name": "AccountKeyControlPort", + "kind": "interface", + "description": "The account-key control routes (AK1 §5.2–5.3), implemented by the Connect client for its signed-in account." + }, + { + "name": "AccountKeyError", + "kind": "class", + "description": "" + }, + { + "name": "AccountKeyErrorCode", + "kind": "type", + "description": "Why an account-key operation failed." + }, + { + "name": "AccountKeyMode", + "kind": "type", + "description": "" + }, + { + "name": "AccountKeyProof", + "kind": "interface", + "description": "The account key's proof signer." + }, + { + "name": "AccountKeyReplicaPort", + "kind": "interface", + "description": "The replica side (AK1 §6), per private collection this device has open." + }, + { + "name": "accountKeyRewrapDigest", + "kind": "function", + "description": "`H(\"mdbase/v1/account-key-rewrap\", cbor[account, sha256(bundle), expected_version])`, signed by the proof key." + }, + { + "name": "AccountSecretStore", + "kind": "interface", + "description": "Where `R` lives between calls." + }, + { + "name": "Argon2idRunner", + "kind": "type", + "description": "Runs Argon2id." + }, + { + "name": "Bundle", + "kind": "interface", + "description": "" + }, + { + "name": "BUNDLE_VERSION", + "kind": "constant", + "description": "" + }, + { + "name": "checkKdfParams", + "kind": "function", + "description": "" + }, + { + "name": "checkPassword", + "kind": "function", + "description": "Length policy only (the strength meter is `passwordStrength`)." + }, + { + "name": "checkRecoveryKey", + "kind": "function", + "description": "Accept a typed recovery key as the account key of `bundle` (forgotten password)." + }, + { + "name": "decodeBundle", + "kind": "function", + "description": "Strict decoding." + }, + { + "name": "DEFAULT_KDF", + "kind": "constant", + "description": "v1 defaults." + }, + { + "name": "deriveAccountKeyProof", + "kind": "function", + "description": "" + }, + { + "name": "deriveRecoveryDevice", + "kind": "function", + "description": "Derive this collection's recovery device from `R`." + }, + { + "name": "domainHash", + "kind": "function", + "description": "`H(tag, m) = SHA-256(u8(len(tag)) ‖ tag ‖ m)` (`00-overview.md` §4)." + }, + { + "name": "encodeBundle", + "kind": "function", + "description": "Canonical encoding." + }, + { + "name": "Entropy", + "kind": "type", + "description": "Random bytes from the platform CSPRNG." + }, + { + "name": "formatRecoveryKey", + "kind": "function", + "description": "The text to show once and have the user write down." + }, + { + "name": "generateRecoveryKey", + "kind": "function", + "description": "A fresh account secret `R`." + }, + { + "name": "inlineArgon2id", + "kind": "constant", + "description": "The default runner." + }, + { + "name": "KdfParams", + "kind": "interface", + "description": "" + }, + { + "name": "keyId", + "kind": "function", + "description": "The public id of an account key." + }, + { + "name": "MAX_BUNDLE_BYTES", + "kind": "constant", + "description": "" + }, + { + "name": "MAX_PASSWORD_BYTES", + "kind": "constant", + "description": "Maximum password length, in UTF-8 bytes before and after NFKC." + }, + { + "name": "memorySecretStore", + "kind": "function", + "description": "RAM only, for tests and short-lived tools." + }, + { + "name": "MIN_PASSWORD_CHARS", + "kind": "constant", + "description": "Minimum password length, in Unicode scalar values after NFKC." + }, + { + "name": "normalizePassword", + "kind": "function", + "description": "NFKC, bounded before and after normalisation." + }, + { + "name": "openBundle", + "kind": "function", + "description": "Open a bundle with `password` for `account`." + }, + { + "name": "OpenOptions", + "kind": "interface", + "description": "" + }, + { + "name": "parseRecoveryKey", + "kind": "function", + "description": "Parse what the user typed or pasted." + }, + { + "name": "passwordStrength", + "kind": "function", + "description": "The strength of `password`." + }, + { + "name": "PrivateAccount", + "kind": "class", + "description": "Setup, unlock and recovery for private collections (AK1 §7)." + }, + { + "name": "PrivateAccountPorts", + "kind": "interface", + "description": "" + }, + { + "name": "PrivateAccountRequestOptions", + "kind": "interface", + "description": "" + }, + { + "name": "PrivateAccountStatus", + "kind": "interface", + "description": "" + }, + { + "name": "RecoveryDevice", + "kind": "interface", + "description": "The recovery device a recovery key derives for one collection (`sealed-envelope.md` §5.4)." + }, + { + "name": "recoverySign", + "kind": "function", + "description": "Sign with the recovery device (the proof of possession for enrolment, AK1 §5.3)." + }, + { + "name": "sealBundle", + "kind": "function", + "description": "Seal `secret` under `password` for `account`." + }, + { + "name": "SealOptions", + "kind": "interface", + "description": "" + }, + { + "name": "SetupResult", + "kind": "interface", + "description": "" + }, + { + "name": "signAccountKeyRewrap", + "kind": "function", + "description": "" + }, + { + "name": "Strength", + "kind": "interface", + "description": "" + }, + { + "name": "StrictPendingTarget", + "kind": "interface", + "description": "" + }, + { + "name": "StrictResult", + "kind": "interface", + "description": "" + }, + { + "name": "UnlockResult", + "kind": "interface", + "description": "" + }, + { + "name": "wipe", + "kind": "function", + "description": "Wipe a secret buffer (best effort)." + }, + { + "name": "wipeRecoveryDevice", + "kind": "function", + "description": "Wipe a derived recovery device's secrets." + } + ] + }, + { + "specifier": "@mdbase-dev/sdk/app-grant", + "title": "App grants", + "featured": [], + "index": [ + { + "name": "AppGrantCallbackPort", + "kind": "interface", + "description": "How the redirect page's URL reaches the waiting flow." + }, + { + "name": "AppGrantPopup", + "kind": "interface", + "description": "A window the UI reserved synchronously in the user's gesture (`window.open`)." + }, + { + "name": "AppGrantStore", + "kind": "interface", + "description": "" + }, + { + "name": "COLLECTION_UNAVAILABLE_REASONS", + "kind": "constant", + "description": "" + }, + { + "name": "CollectionAvailability", + "kind": "type", + "description": "" + }, + { + "name": "CollectionUnavailableReason", + "kind": "type", + "description": "" + }, + { + "name": "deliverAppGrantCallback", + "kind": "function", + "description": "Call from the redirect page with `location.href`." + }, + { + "name": "indexedDbAppGrantStore", + "kind": "function", + "description": "IndexedDB, one database per control-plane origin, local owner and application." + }, + { + "name": "IndexedDbAppGrantStoreOptions", + "kind": "interface", + "description": "" + }, + { + "name": "isCollectionUnavailableReason", + "kind": "function", + "description": "" + }, + { + "name": "MdbaseAppGrants", + "kind": "class", + "description": "Application grants of one app installation (a browser profile) against its Connect environment." + }, + { + "name": "MdbaseAppGrantsOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseAuthorizeOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseCollectionEntry", + "kind": "interface", + "description": "One entry of the merged list." + }, + { + "name": "MdbaseCollectionRole", + "kind": "type", + "description": "The account's role in a collection." + }, + { + "name": "MdbasePrepareAuthorizationOptions", + "kind": "interface", + "description": "Preparation for the SAME popup's ordinary authorization callback." + }, + { + "name": "MdbasePreparedAuthorization", + "kind": "interface", + "description": "" + }, + { + "name": "MdbasePrivateCollection", + "kind": "interface", + "description": "A Private collection this app has a grant for." + }, + { + "name": "MdbasePrivateSetup", + "kind": "interface", + "description": "" + }, + { + "name": "MdbasePrivateSetupStatus", + "kind": "type", + "description": "" + }, + { + "name": "MdbaseSetupChange", + "kind": "interface", + "description": "One line of the review screen." + }, + { + "name": "MdbaseSetupConflict", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseSetupPlan", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseSetupPlanOptions", + "kind": "interface", + "description": "" + }, + { + "name": "MdbaseSetupRecord", + "kind": "interface", + "description": "A starter record created once, in the same mutation as the first setup." + }, + { + "name": "MdbaseWaitOptions", + "kind": "interface", + "description": "" + }, + { + "name": "memoryAppGrantStore", + "kind": "function", + "description": "In memory only (tests and short-lived scripts)." + }, + { + "name": "mergeCollectionEntries", + "kind": "function", + "description": "Cloud copies from the installation sign-in plus Private collections, by name." + }, + { + "name": "parseCollectionAvailability", + "kind": "function", + "description": "Parse a server `availability` object." + }, + { + "name": "StoredAppGrant", + "kind": "interface", + "description": "One grant." + }, + { + "name": "StoredAppIdentity", + "kind": "interface", + "description": "The installation's signing identity." + } + ] + } + ], + "errors": [ + { + "code": "invalid_request", + "recovery": "fix_request" + }, + { + "code": "invalid_record", + "recovery": "fix_request" + }, + { + "code": "not_found", + "recovery": "refresh" + }, + { + "code": "conflict", + "recovery": "resolve_conflict" + }, + { + "code": "unauthenticated", + "recovery": "reauthorize" + }, + { + "code": "forbidden", + "recovery": "reauthorize" + }, + { + "code": "collection_invalid", + "recovery": "repair_collection" + }, + { + "code": "unavailable", + "recovery": "retry" + }, + { + "code": "rate_limited", + "recovery": "retry" + }, + { + "code": "quota_exceeded", + "recovery": "free_space" + }, + { + "code": "too_large", + "recovery": "fix_request" + }, + { + "code": "upgrade_required", + "recovery": "upgrade" + }, + { + "code": "outcome_unknown", + "recovery": "resolve_outcome" + }, + { + "code": "cancelled", + "recovery": "none" + }, + { + "code": "internal", + "recovery": "contact_support" + } + ] +} diff --git a/src/pages/connect/index.astro b/src/pages/connect/index.astro index 4d43873..580f980 100644 --- a/src/pages/connect/index.astro +++ b/src/pages/connect/index.astro @@ -147,8 +147,8 @@ Book a photo appointment first. collections.

- Building an app? Connect a browser - app walks through sign-in, choosing a collection and working with + Building an app? Build an app + {" "}walks through sign-in, choosing a collection and working with records using the SDK.

diff --git a/src/pages/sdk/api/index.astro b/src/pages/sdk/api/index.astro index cb62e78..d154c46 100644 --- a/src/pages/sdk/api/index.astro +++ b/src/pages/sdk/api/index.astro @@ -1,222 +1,177 @@ --- import DocsLayout from "../../../components/DocsLayout.astro"; import CodeBlock from "../../../components/CodeBlock.astro"; +import DocText from "../../../components/DocText.astro"; +import api from "../../../data/sdk-api.json"; -const connectSignature = `connect(options: ClientOptions): Promise +type Member = { name: string; signature: string; description: string; deprecated?: string | boolean }; +type Export = { + name: string; + kind: string; + description: string; + signature?: string; + members?: Member[]; + deprecated?: string | boolean; +}; -interface ClientOptions { - connector: Connector; - app: { name: string; version: string }; - timezone?: string; // IANA zone for writes and queries; default: the host's - features?: string[]; // optional features to request, such as "presence" - reconnect?: false | { minDelayMs?: number; maxDelayMs?: number }; // on by default - waitForDevice?: boolean; // Private: wait for a device instead of failing - onWaiting?: (why: MdbaseError) => void; - signal?: AbortSignal; -}`; +const slug = (specifier: string) => + specifier === api.package ? "core" : specifier.slice(api.package.length + 1).replace(/[^a-z0-9]+/g, "-"); +const anchor = (specifier: string, name: string) => `${slug(specifier)}-${name}`; +const entries = api.entries as Array<{ specifier: string; title: string; featured: Export[]; index: Export[] }>; -const writeOptions = `interface WriteOptions { - mutationId?: string; // default: a new UUIDv7 - conflictMode?: "record" | "reject"; - timezone?: string; - include?: Include; - dryRun?: boolean; - wait?: "pending" | "confirmed" | "published"; - signal?: AbortSignal; -}`; +// The codes and recoveries come from the SDK's types. The sentence beside each +// is written here, and the build fails when the two lists differ. +const when: Record = { + invalid_request: "Malformed parameters, an unsafe path, or an expired query cursor", + invalid_record: "The write fails validation; see issues", + not_found: "No such record, file, hold or transfer", + conflict: "The current state does not allow this write; reason says why, such as revision or path_taken", + unauthenticated: "No session, an expired one, or a revoked grant", + forbidden: "The grant lacks the capability, or the person’s role does not allow it", + collection_invalid: "The collection’s configuration or type files cannot be loaded", + unavailable: "The replica cannot be reached, for example when no device is online", + rate_limited: "Too many requests; see retryAfterMs", + quota_exceeded: "The collection is over its storage quota", + too_large: "A request or file is over its limit", + upgrade_required: "This SDK version is too old for the replica or the collection", + outcome_unknown: "A write’s outcome cannot be determined; read again and decide", + cancelled: "Your code cancelled the request", + internal: "A bug; the request may be retried once", +}; +const errors = api.errors as Array<{ code: string; recovery: string }>; +const undescribed = errors.filter((error) => !(error.code in when)).map((error) => error.code); +const stale = Object.keys(when).filter((code) => !errors.some((error) => error.code === code)); +if (undescribed.length || stale.length) { + throw new Error( + `src/pages/sdk/api/index.astro: error codes without a description: [${undescribed}]; descriptions without a code: [${stale}]`, + ); +} --- ({ href: `#${slug(entry.specifier)}`, label: entry.title })), + { href: "#errors", label: "Errors" }, ]} >

- This page lists the main public API. The package’s TypeScript declarations - are the full reference. + This page is generated from the declarations published in + {" "}{api.package} {api.version}, so every + signature and description is the one your editor shows. The main types are + listed in full under each entry point, followed by an index of its other + exports. The guide pages explain how the parts fit together; start with + {" "}Build an app.

-

connect()

- -

- Connectors: relayConnector and - {" "}nextRelayConnector (remote, through the relay or to the - hosted replica), inProcessConnector, - {" "}localLinkConnector, and from - {" "}@mdbase-dev/sdk/node, ipcConnector and - {" "}connectDaemonLocalhost. In tests, - {" "}new MemoryReplica().connector(). -

- -

Reads

- - - - - - - - - - - - - - - - - - - -
MethodReturns
get(ref, include?, signal?)RecordView; throws not_found
find(ref, include?, signal?)RecordView | null
readMany(refs, …)Several records in one call
query(query, include?, signal?)One QueryResult page; cursor when there is more
pages(query, include?, signal?, { onReset })An async iterator over every page
live(query, include?)A LiveQuery: records, stale, complete, ready, subscribe(fn), setQuery(q), close()
changes(cursor?, limit?){ changes, cursor, reset }
watchChanges(cursor, onChanges)A stop function with ready and state
backlinks(…)Records that link to a record
describe()Types, contracts and settings of the collection
queryContract({ id, version }, options?)Records through a contract’s field names
validate(refs?)Validation issues per record
views.list(), views.execute(…)Saved views
-

- A RecordRef is a record ID, { path }, - or a RecordView. Include is - {" "}{ body, effective, document, links, diagnostics }, - each a boolean. -

- -

Writes

- - - - - - - - - - - - - - - -
MethodNotes
create({ id?, type?, path?, frontmatter?, body?, document? }, options?)The ID is minted locally and usable at once
update(ref, { patch, unset, add, remove, body, bodyEdits, ifRevision }, options?)Pass a RecordView to merge field by field
replaceDocument(ref, source, options?)Replace the whole Markdown source
rename(view, to, { updateRefs, ifRevision })updateRefs defaults to true
delete(ref, { ifRevision })
preflightRename(view, to), preflightDelete(ref)What the change would touch
receipt(mutationId), awaitReceipt(mutationId, timeoutMs?)The current receipt; or wait until it leaves pending
pendingWrites()Writes still waiting
records.open(ref, { autosave, timeoutMs, signal, recovery })A record session lease; see Editing records
- -

- Every write resolves to a Write: - {" "}state, receipt, records (the - optimistic view), mutationId, onReceipt(fn), - {" "}confirmed and published (promises). - {" "}confirmed rejects with an MdbaseError if the - write is rejected or its outcome is unknown. published - {" "}settles when a file-backed replica has written the change to disk, - which can be before confirmation. -

- -

Status and link

- - - - - - - - - - - -
MemberNotes
getStatus(), onStatus(fn), statusSyncStatus: mode, confirmedThrough, headKnown, pending, holds, unresolved, connection, installing
summarizeStatus(status){ kind, text, pending, holds, conflicts, confirmedThrough }
link, onLink(fn)"open", "reconnecting" or "closed"
helloWhat the replica reported at connect, including the grant’s role and capabilities
close()Close the client and its subscriptions
- -

Holds and conflicts

- - - - - - - - - - - -
MemberNotes
listHolds(), onHolds(fn)Hold[]: id, path, reason, mine, theirs
resolveHold(id, how, use?)how: keep_mine, take_theirs, use, delete, keep_both
describeHold(hold, { collectionId }), holdNotice(holds)Plain-language text and the actions that apply
listConflicts(recordId?), onConflicts(fn)ConflictEntry[]
resolveConflict(entry, field, value), dismissConflict(entry)Choose a value, or keep what the record has
- -

Files

- - - - - - - - - - -
Member of db.filesNotes
list({ folder, media }), listPage(…), get(ref)FileView: id, path, size, digest, media, state
upload(path, source, { onProgress, transferId, fileId, ifRevision, signal })source: Uint8Array, ArrayBuffer or Blob
download(ref), downloadStream(ref, { range })download is capped at 64 MiB
move(view, to), delete(ref)Both take ifRevision
- -

createMdbase

-

- createMdbase(declaration) is the entry point for web apps. Its - snapshot has a phase that says what to show; when - {" "}phase.kind is "ready", - {" "}phase.connection is the open connection. Its actions - include start, signIn, select, - {" "}forget, signOut and close. -

-

- new MdbaseConnect(declaration) is the same entry under its - previous name. Its application() returns an object with - {" "}getSnapshot, subscribe, start, - {" "}authorize, select, assess, - {" "}configure, clearSelection, - {" "}forget, setForeground and - {" "}close. connection(collectionId) returns a - connection whose records is an MdbaseClient. - See Connect a browser app for its - current status. -

- -

Other exports

-
    -
  • toPlain, toValue, valueEquals, uuidv7: values and IDs.
  • -
  • loadOrCreateClientKey, indexedDbKeyStorage, memoryKeyStorage: your app’s session key.
  • -
  • isConfirmed(receipt): whether a receipt is confirmed.
  • -
  • TimersApi: see Notifications and timers.
  • -
  • @mdbase-dev/sdk/testing: MemoryReplica, createApplicationFixture.
  • -
+ { + entries.map((entry) => ( + <> +

{entry.title}

+

+ import { … } from "{entry.specifier}" +

+ {entry.featured.map((item) => ( +
+

+ {item.name} {item.kind} +

+ {item.deprecated && ( +

+ Deprecated{typeof item.deprecated === "string" ? <>: : "."} +

+ )} + + {item.members ? ( +
+ {item.members.map((member) => ( + <> +
+ {member.signature} +
+
+ {member.deprecated && ( +

+ Deprecated{typeof member.deprecated === "string" ? <>: : "."} +

+ )} + +
+ + ))} +
+ ) : ( + + )} +
+ ))} + {entry.index.length > 0 && ( + <> +

+ {entry.featured.length > 0 ? "Other exports" : "Exports"} +

+ + + + + + + + + + {entry.index.map((item) => ( + + + + + + ))} + +
NameKindDescription
+ {item.name} + {item.kind} + {item.deprecated ? "Deprecated. " : ""} + +
+ + )} + + )) + }

Errors

- Failures throw MdbaseError with code, - {" "}recovery, and often reason, + Failures throw MdbaseError with a code, a + {" "}recovery, and often a reason, {" "}details, issues or - {" "}retryAfterMs. Use isMdbaseError(e, code?). - Branch on code and reason; message - {" "}is for developers. Each code has one recovery. + {" "}retryAfterMs. Check them with + {" "}isMdbaseError(error, code?). Branch on code + {" "}and reason; message is for developers. A + write the collection refuses does not throw: its Write has + {" "}state: "rejected" and the same fields under + {" "}receipt.problem. To show a failure to a person, pass it + to describeError().

- + + + + + - - - - - - - - - - - - - - - + { + errors.map((error) => ( + + + + + + )) + }
CodeRecoveryWhen
CodeRecoveryWhen
invalid_requestfix_requestMalformed parameters, unsafe paths, an expired query cursor
invalid_recordfix_requestThe write fails validation; see issues
not_foundrefreshNo such record, file, hold or transfer
conflictresolve_conflictThe current state doesn’t allow this write: revision, path_taken, duplicate_value, …
unauthenticatedreauthorizeNo session, an expired one, or a revoked grant
forbiddenreauthorizeThe grant lacks the capability, or the role doesn’t allow it
collection_invalidrepair_collectionThe collection’s config or type files can’t be loaded
unavailableretryThe replica can’t be reached, for example no device online
rate_limitedretryToo many requests; see retryAfterMs
quota_exceededfree_spaceThe collection is over its storage quota
too_largefix_requestA request or file over its limit
upgrade_requiredupgradeThis SDK version is too old for the replica or collection
outcome_unknownresolve_outcomeA write’s outcome can’t be determined; read again and decide
cancellednoneYou cancelled the request
internalcontact_supportA bug; the request may be retried once
{error.code}{error.recovery}{when[error.code]}
diff --git a/src/pages/sdk/connect-dev/index.astro b/src/pages/sdk/connect-dev/index.astro new file mode 100644 index 0000000..663b24a --- /dev/null +++ b/src/pages/sdk/connect-dev/index.astro @@ -0,0 +1,185 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +import manifestScript from "../../../../examples/worklog/scripts/write-manifest.mjs?raw"; + +const install = `npm install --save-dev --save-exact @mdbase-dev/connect-dev`; + +const cli = `npx mdbase-connect-dev validate-manifest mdbase-app.json +npx mdbase-connect-dev validate-manifest mdbase-app.dev.json --allow-local +npx mdbase-connect-dev validate-contract contracts/example.work-item.json`; + +const validate = `import { formatValidationIssues, validateAppManifest } from "@mdbase-dev/connect-dev"; + +const result = validateAppManifest(manifest, { allowLocal: false }); +if (!result.valid) { + // Each issue has a JSON pointer, such as /requirements/capabilities/required/2. + console.error(formatValidationIssues(result.issues)); + process.exit(1); +}`; + +const pack = `import { defineTypePack } from "@mdbase-dev/connect-dev"; + +const types = defineTypePack({ + id: "dev.example.worklog", + version: "1.0.0", + name: "Worklog", + resources: [ + { + kind: "type", + mode: "seed", + source: "types/worklog-entry.md", + target: "_types/worklog-entry.md", + document: await readFile("mdbase/types/worklog-entry.md", "utf8"), + }, + ], +}); + +// In the manifest: +// "provisions": { "type_packs": [types] }`; + +const contract = `import { dataContractDigest, defineDataContract } from "@mdbase-dev/connect-dev"; + +const workItem = defineDataContract({ + // the contract document; see Contracts and adapters +}); + +// The digest to put in requirements.contracts and in a type pack's provides. +const digest = dataContractDigest(workItem);`; +--- + + +

+ A build-time package. Nothing from it runs in your app; install it as a + development dependency. +

+ +

Install

+ + +

Check a manifest

+ +

+ validate-manifest runs the parser mdbase uses when your app + registers. It exits non-zero with a list of problems, each with the path + of the field at fault. +

+

+ An HTTP homepage is refused unless you pass --allow-local. + Use the flag for a development + manifest and leave it off for the one you publish, so a localhost + address cannot reach production by mistake. +

+ +

Check it in a build

+ +

+ The SDK also validates the manifest when the app starts. Checking in the + build moves the failure from your users’ browsers to your own terminal. +

+ +

Build a type pack

+

+ A type pack carries the type + and contract files your app adds to a collection. Inside a manifest every + file is pinned by a SHA-256 digest. defineTypePack() reads + the documents, computes the digests and returns the object to place under + {" "}provisions.type_packs: +

+ + + + + + + + + + + + + + + + +
Resource fieldMeaning
kind"type", "contract" or "schema"
mode"seed" or "managed"; see below
sourceThe file’s name inside the pack
targetWhere it goes in the collection, such as _types/worklog-entry.md. Defaults to source.
documentThe file’s exact text
upgradeFromSeed types only: earlier versions you shipped, oldest first, that a collection may be upgraded from
+

+ Keep the documents as real files in your repository and read them in the + script. They are then reviewable in a pull request, and the manifest is a + build output. +

+ +

Seed and managed

+ + + + + + + + + + + + + + + + + + + + +
ModeWho owns the file after setupUse it for
seedThe user. Your app writes it once; later edits by the user stay.A type people may want to extend with fields of their own
managedYour app. A new version of your pack updates it.A contract, or a type whose exact shape your app depends on
+

+ Adding files to a collection needs the + {" "}definitions.manage capability in your manifest, and the + user sees and accepts the change first. See + {" "}Setup. +

+ +

Contracts

+

+ defineDataContract() validates a + {" "}contract document as you write it, and + {" "}dataContractDigest() computes the digest a manifest + names it by. +

+ +

+ validate-contract on the command line checks a contract file + the same way. +

+ +

A whole manifest script

+

+ The Worklog example writes its development and deployed manifests from one + script, validating each: +

+ + +

The sandbox

+

+ The package also exports createSandbox(). It is for the + older @mdbase-dev/connect client. With + {" "}@mdbase-dev/sdk, test against + {" "}MemoryReplica and + {" "}createApplicationFixture(); see + {" "}Testing. +

+
diff --git a/src/pages/sdk/connect-quickstart/index.astro b/src/pages/sdk/connect-quickstart/index.astro index 357a164..e9308e8 100644 --- a/src/pages/sdk/connect-quickstart/index.astro +++ b/src/pages/sdk/connect-quickstart/index.astro @@ -2,36 +2,32 @@ import DocsLayout from "../../../components/DocsLayout.astro"; import CodeBlock from "../../../components/CodeBlock.astro"; -const manifest = `{ - "manifest_version": 1, - "id": "dev.example.worklog", - "name": "Worklog", - "homepage": "https://worklog.example", - "redirect_uris": ["https://worklog.example/auth/mdbase/callback"], - "requirements": { - "access": "full_collection", - "contracts": [], - "capabilities": { - "contract_version": 2, - "required": ["collection.read", "records.create", "records.edit"] - } - } -}`; +// Every listing on this page is the file from examples/worklog, which CI +// installs, type-checks and builds. +import typeDocument from "../../../../examples/worklog/mdbase/types/worklog-entry.md?raw"; +import manifestScript from "../../../../examples/worklog/scripts/write-manifest.mjs?raw"; +import packageJson from "../../../../examples/worklog/package.json?raw"; +import viteConfig from "../../../../examples/worklog/vite.config.ts?raw"; +import entry from "../../../../examples/worklog/src/mdbase.ts?raw"; +import main from "../../../../examples/worklog/src/main.tsx?raw"; +import app from "../../../../examples/worklog/src/App.tsx?raw"; +import worklog from "../../../../examples/worklog/src/Worklog.tsx?raw"; + +const scaffold = `npm create vite@latest worklog -- --template react-ts +cd worklog -const install = `npm install @mdbase-dev/sdk`; +npm install --save-exact @mdbase-dev/sdk @mdbase-dev/ui @mdbase-dev/i18n \\ + @lingui/core@6.9.0 @lingui/react@6.9.0 +npm install --save-dev --save-exact @mdbase-dev/connect-dev`; -const client = `import { createMdbase } from "@mdbase-dev/sdk"; -import manifest from "./mdbase-app.json"; +const run = `npm run dev`; -export const mdbase = createMdbase({ - id: "dev.example.worklog", - name: "Worklog", // must match the manifest's name - origin: "https://worklog.example", - environment: "production", - manifest, -});`; +const deploy = `WORKLOG_ORIGIN=https://worklog.example.com npm run build +# Upload dist/ to the static host that serves https://worklog.example.com`; -const authorize = `import { mdbase } from "./mdbase"; +const scripts = JSON.stringify({ scripts: JSON.parse(packageJson).scripts }, null, 2); + +const plain = `import { mdbase } from "./mdbase"; mdbase.subscribe(() => render(mdbase.getSnapshot())); await mdbase.start(); @@ -41,109 +37,286 @@ signInButton.onclick = () => mdbase.signIn(); function render(snapshot) { const { phase } = snapshot; - // "signed_out", "authorizing", "choosing", "needs_setup", "ready", … - showScreen(phase.kind); - if (phase.kind === "choosing") { + if (phase.kind === "ready" || phase.kind === "unsynced") { + showWorklog(phase.connection.records); + } else if (phase.kind === "choosing") { showPicker(snapshot.collections, (id) => mdbase.select(id)); + } else { + showScreen(phase.kind); // "signed_out", "authorizing", "needs_setup", … } - if (phase.kind === "ready") startApp(phase.connection.records); -}`; - -const operate = `async function startApp(db) { - const entries = db.live({ - types: ["worklog_entry"], - order_by: [{ field: "date", direction: "desc" }], - limit: 50, - }); - entries.subscribe((state) => renderEntries(state.records)); - - addButton.onclick = () => - db.create({ - type: "worklog_entry", - frontmatter: { date: today(), summary: summaryInput.value }, - }); }`; --- -
- Test without an account first. +

What you build

+

+ Worklog is a one-screen app: a form that adds an entry, and a list of + entries that stays current. Each entry is a Markdown file in the user’s + collection, so the user can also open it in the + {" "}mdbase apps, in Obsidian, or in any text editor. +

+

+ The finished app is about 150 lines. mdbase supplies sign-in, the + collection picker, storage, sync and conflict handling. The complete + project is in + {" "}examples/worklog; + every listing on this page is a file from it. +

+
+ What you need

- You can write and test the whole flow on this page against - {" "}createApplicationFixture() before - signing in to a real collection. + Node 20 or later. You need no account to start: the app first runs in + a collection kept in your browser. Signing in to an mdbase account + comes at step 7. Developing on http://localhost needs an + {" "}@mdbase-dev/sdk release after + {" "}0.1.0-beta.168.

-

1. Write a manifest

+

1. Create the project

+ +

+ @mdbase-dev/sdk is the client. + {" "}@mdbase-dev/ui has the sign-in and collection screens; + {" "}@mdbase-dev/i18n and the two Lingui packages are its + peer dependencies. @mdbase-dev/connect-dev checks the + manifest at build time. The packages are in beta, so the versions are + pinned exactly. +

+

+ Delete the template’s src/App.tsx, src/App.css + {" "}and src/index.css; the steps below replace them. Then fix + the development port, because the manifest names the exact origin the app + is served from: +

+ + +

2. Describe your records

+

+ Every record in a collection has a type, and a type is itself a Markdown + file in the collection’s _types/ folder. Worklog needs one. + Write the copy your app will offer to a collection that lacks it: +

+ +

+ The schema is JSON Schema. mdbase checks every write against it, so an + entry with no summary is refused before it is saved. + {" "}additionalProperties: true lets the user and other apps + keep fields of their own on the same records. +

+ +

3. Write the manifest

- The manifest says who your app is and what it needs. Ask only for the - capabilities you use; see Authorization. + The manifest tells mdbase who your app is and what it needs. It names + the origin the app is served from, so development and the deployed site + each need their own. A short script writes either one: +

+ +
    +
  • + id is a lowercase reverse-domain name with + at least three parts. Use a domain you control. +
  • +
  • + homepage and redirect_uris + {" "}are on the app’s origin. A browser app can list its homepage as + the one redirect URI. +
  • +
  • + capabilities and files are + what the user is asked to approve. Ask for what you use; + {" "}Authorization lists them. + {" "}definitions.manage is there so the app can add its + type to a collection the user already has. +
  • +
  • + provisions.type_packs carries the type + document from step 2. See + {" "}Provisioning. +
  • +
+

Run the script before the development server and before a build:

+ +

+ Add src/mdbase-app.json to .gitignore: it is + generated.

- -

2. Install

- +

4. Declare the app

+ +

+ Create one entry for the whole app. Creating it does no work; the SDK + starts on the first call. It registers the manifest with mdbase when the + app starts, so there is no application ID to request first. +

+
    +
  • + origin must be the exact origin the page is served from. + Reading it from the manifest keeps the two in step. +
  • +
  • + environment is "production" on localhost too. + Your development build talks to the same mdbase service your users do. +
  • +
  • + unsynced: true lets people use the app before they sign in. + Their work is kept in the browser until they connect a collection. See + {" "}Work before connecting. +
  • +
+ +

5. Sign-in and collections

+

+ Between opening the app and seeing their entries, a user may need to sign + in, pick or create a collection, approve your app’s access, and let it add + its type. CollectionGate from @mdbase-dev/ui + {" "}shows each of those screens and renders your app once a collection is + open. +

+

- The SDK is in beta; pin an exact version in an app you ship. See the - {" "}quickstart. + The gate hands your app a connection. Its + {" "}records property is an MdbaseClient for the + open collection. CollectionSwitcher is the control for + changing collection, adding one and signing out. Keying the screen on + {" "}connection.id resets it when the user switches. +

+

The entry point loads the gate’s stylesheet:

+ +

+ UI components covers restyling and the other + components. To draw these screens yourself, see + {" "}Sign-in and collections.

-

3. Declare the app

- +

6. Read and write records

+ +
    +
  • + The list is a live query. db.live() sends + the current entries and then every change, including ones made in + another app or on another device. Nothing in this file refetches. +
  • +
  • + Writes return at once. db.create() + {" "}resolves when the entry is saved on this device, and the live query + already shows it. Sync continues in the background, online or offline. +
  • +
  • + A record needs a path. Records are files. Pass + {" "}path, or give the type a path pattern. +
  • +
  • + A refused write does not throw. When the collection + refuses a write, such as an entry the type does not allow, the returned + {" "}Write has state: "rejected" and + {" "}receipt.problem says why. describeError() + {" "}turns that, or a thrown error, into a sentence you can show. +
  • +

- Create one createMdbase() entry for your app. - {" "}origin must be the exact origin your app is served from. - The SDK registers your manifest with mdbase when the app starts, so you - don’t need an ID from mdbase first. MdbaseConnect is the same - entry under its previous name. + Records and operations has the rest: + queries, updates, renames, attachments.

-

4. Sign in and pick a collection

- +

7. Run it on localhost

+

- Render from the snapshot’s phase. It tells you where the user - is: signed out, signing in, choosing a collection, or ready. Switch on - {" "}phase.kind rather than building your own ready check. - When a collection needs set-up first, the phase is - {" "}needs_setup; - {" "}assess() says why and configure() applies - what your manifest declares. + Open http://localhost:5173. The app opens straight away in + its unsynced collection. Add an entry: a line appears saying the work is + saved only in this browser. Reload, and the entry is still there.

- forget(id) removes a collection from your app’s list on this - device. It does not revoke access or delete anything. + Choose Connect to sync your work to sign in. mdbase opens + in a window and asks you to approve the app. Because the app is on + {" "}localhost, that screen calls it “an app running on this + computer” and says its identity is unverified. Then: +

+
    +
  • + A collection you create during sign-in gets the + {" "}worklog-entry type automatically. +
  • +
  • + A collection you already had shows a short review first: “Set up + (collection) for Worklog”, listing the type file to be added. Nothing + changes until you accept it. +
  • +
  • + The entries you made before signing in are offered to the collection + you opened. Bringing them in keeps their IDs, and running it twice + creates nothing twice. +
  • +
+

+ Open the same collection in the + {" "}Editor: your entries are + files under worklog/. Change one there and the list in your + app updates. +

+

+ Develop on localhost explains what + is different about a development build.

-

5. Use the collection

+

8. Deploy

+

- connection.records is an ordinary MdbaseClient. - Everything in Records and operations applies. + The build is a folder of static files. Host it anywhere that serves HTTPS + at the origin you built for. There is no server component, no API key and + no secret: the app holds nothing until a user signs in and approves it.

- +
    +
  • + The manifest’s homepage origin and the site’s origin must + match exactly, including www and any port. +
  • +
  • + The deployed app is a separate application from your localhost build. + Users approve it on its own, and its storage is its own. +
  • +
  • + Before you ship, change id from + {" "}dev.example.worklog to a name under your own domain. +
  • +
-

Origins

+

Other frameworks

+

+ Only App.tsx and the hooks in Worklog.tsx are + React. The entry is a plain object with a snapshot you subscribe to, and + a phase that says where the user is: +

+

- origin is normally your manifest’s HTTPS - {" "}homepage origin. A Capacitor shell - ({" "}capacitor://) or Chrome extension - ({" "}chrome-extension://) origin must also be listed under the - manifest’s installation_origins. Firefox extension origins are - not accepted. Browser redirect_uris must be HTTPS on the - homepage origin; a native app can use a private-use scheme based on its - manifest ID. + You can draw each phase yourself, or mount the shared screens into an + element with mountCollectionUi(); see + {" "}UI components.

+ +

Next steps

+ diff --git a/src/pages/sdk/index.astro b/src/pages/sdk/index.astro index 54398fd..67861df 100644 --- a/src/pages/sdk/index.astro +++ b/src/pages/sdk/index.astro @@ -19,56 +19,94 @@ export async function listOpenTasks(db: MdbaseClient) {

- Web apps, Node scripts and editor plugins use it to read, query, watch - and change records and files in one collection. + An mdbase collection is a folder of Markdown files with typed + frontmatter, synced across a person’s devices. An app built with the SDK + reads and writes the records in a collection its user chooses.

+

Why build on mdbase

+
    +
  • + You run no backend. The user’s collection is your + app’s database. A finished app is a folder of static files: no server, + no database to operate and no API key to guard. +
  • +
  • + Sign-in, sync and offline are done. The SDK signs the + user in, keeps a copy of the collection on the device, saves writes + there at once and syncs them when it can. Edits made on two devices + merge field by field, and a value that loses is kept for review. +
  • +
  • + Users bring their data, and keep it. Records are + Markdown files the user owns. They can open them in mdbase’s + {" "}Editor, Reader and Writer, in Obsidian, or in + a text editor, and they still have them if they stop using your app. +
  • +
  • + Apps can share records. A + {" "}contract names a shape of record, + such as a task. Two apps that use the same contract work on the same + records in one collection, with no integration between them. +
  • +
  • + Access is the user’s decision. Your app asks for the + capabilities in its manifest, the user + approves them for one collection, and can withdraw them later. See the + {" "}security model. +
  • +
+

Where to start

Quickstart

- Run a short script against an in-memory replica. It needs no account - or service. + Run a short script against an in-memory collection. It needs no + account or service, and shows reads, writes and live queries.

Run the quickstart
- Connect an app + Build an app

- Declare your app, sign the user in and use the collection they pick. + Build Worklog, a small React app, from an empty folder to a deployed + site: types, manifest, sign-in, live list and writes.

- Connect a browser app + Follow the tutorial +
+
+ Develop on localhost +

+ Run your app from a development server against real accounts and + collections. +

+ Set up local development
The format

- Build tools that read and write mdbase collections directly, without - the SDK. + Build tools that read and write mdbase collections as files, with no + SDK.

Read the specification
-
- Release status -

- @mdbase-dev/sdk is in beta on - {" "}npm. New - betas are published as Connect changes, and the API can change between - them, so pin an exact version in an app you ship. -

-

How the SDK works

@@ -119,7 +157,7 @@ export async function listOpenTasks(db: MdbaseClient) { createMdbase() signs the user in and hands you a connection. Its client is an ordinary MdbaseClient. - See Connect a browser app. + See Build an app. @@ -147,7 +185,41 @@ export async function listOpenTasks(db: MdbaseClient) {

-

Package map

+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageWhat it is for
@mdbase-dev/sdk + The client. createMdbase for apps, + {" "}connect and MdbaseClient for + everything that reads and writes records. + {" "}API reference. +
@mdbase-dev/ui + The sign-in, collection picker, approval and setup screens as React + components. UI components. +
@mdbase-dev/connect-dev + Build-time tools: the manifest checker and the type pack builder. + {" "}Developer kit. +
+

The SDK has several entry points:

@@ -159,18 +231,22 @@ export async function listOpenTasks(db: MdbaseClient) { - - + + + + + + - - - -
@mdbase-dev/sdk - connect, MdbaseClient, - {" "}createMdbase, errors, status helpers and connectors + createMdbase, connect, + {" "}MdbaseClient, errors and status helpers
@mdbase-dev/sdk/nodeConnectors to the desktop app over local IPC or localhost@mdbase-dev/sdk/describedescribeError: a title, message and action for any failure
@mdbase-dev/sdk/testing MemoryReplica and test fixtures
@mdbase-dev/sdk/nodeConnectors to the desktop app over local IPC or localhost
@mdbase-dev/sdk/browser @@ -182,17 +258,70 @@ export async function listOpenTasks(db: MdbaseClient) { @mdbase-dev/sdk/account Encryption password and recovery key set-up for Private collections
@mdbase-dev/sdk/app-host, @mdbase-dev/sdk/app-storage - The parts that let an app run its own replica in the browser. These - are for mdbase’s own apps at launch. -

- The package needs Node 20 or later outside the browser. It is what apps - use instead of the @mdbase-dev/connect beta. + The SDK needs Node 20 or later outside the browser. It is what apps use + instead of the @mdbase-dev/connect beta. +

+ +

Publishing an app

+

+ There is no review, registration form or API key. The SDK registers your + manifest with mdbase the first time your app starts, and each user + approves the app for their own collection. To publish, deploy the built + files to an HTTPS origin; the + {" "}tutorial ends with + that step. +

+

+ The approval screen shows your app’s name and the site it is served + from. mdbase does not verify who publishes an app, and an application ID + is a label, so serve your app from an origin your users recognise. +

+

+ The apps page lists the apps mdbase makes. There is + no directory of other apps yet. If you build one, write to + {" "}callum@mdbase.dev. +

+ +

Stability

+

+ @mdbase-dev/sdk, @mdbase-dev/ui and + {" "}@mdbase-dev/connect-dev are in beta on npm.

+
    +
  • + The API can change between betas. Pin exact versions in an app you + ship, and read the + {" "}release notes + {" "}before you upgrade. +
  • +
  • + Publishing a new beta does not change an app that is already + deployed; it keeps the SDK it was built with. If the service ever needs + a newer one, the SDK reports a problem whose action is + {" "}update_app. +
  • +
  • + The collection format is specified separately and + does not change with SDK releases. Records your app writes stay + readable. +
  • +
+ +

Getting help

+
diff --git a/src/pages/sdk/lifecycle/index.astro b/src/pages/sdk/lifecycle/index.astro new file mode 100644 index 0000000..35d5bb6 --- /dev/null +++ b/src/pages/sdk/lifecycle/index.astro @@ -0,0 +1,438 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +const render = `import { describeError } from "@mdbase-dev/sdk/describe"; +import { mdbase } from "./mdbase"; + +mdbase.subscribe(() => render(mdbase.getSnapshot())); +await mdbase.start(); + +function render(snapshot) { + const { phase } = snapshot; + switch (phase.kind) { + case "starting": + return showSplash(); + case "signed_out": + return showSignIn(phase.notice?.message); + case "authorizing": + return showWaiting("Continue in the mdbase window"); + case "choosing": + return showPicker(snapshot.collections); + case "opening": + return showOpening(phase.displayName, { slow: phase.slow }); + case "needs_approval": + return showApprove(phase.displayName); + case "needs_setup": + return showSetup(phase.collection); + case "ready": + case "unsynced": + return showApp(phase.connection); + case "problem": + case "unavailable": + return showProblem(phase.problem); + case "closed": + return; + } +}`; + +const gestures = `// Each of these opens the mdbase window, so call it directly in the +// click handler. After an await, the browser blocks the window. +signInButton.onclick = () => mdbase.signIn(); +addButton.onclick = () => mdbase.addCollection(); +approveButton.onclick = () => mdbase.approve(); +reconnectButton.onclick = () => mdbase.reauthorize();`; + +const collections = `const { collections, selectedCollectionId } = mdbase.getSnapshot(); + +for (const item of collections) { + // item.collectionId, item.displayName, item.role: "owner" | "editor" | "viewer" + addRow(item.displayName, () => mdbase.select(item.collectionId)); +} + +// Back to the list; the browser's Back button returns to the collection. +switchButton.onclick = () => mdbase.chooseAnother(); + +// Hide one on this device. It stays approved and nothing is deleted. +await mdbase.forget(collectionId);`; + +const setup = `const plan = await mdbase.setup.plan(); + +if (plan.status === "current") { + // Nothing to do. +} else if (plan.canApply) { + showReview(plan.summary, plan.changes.map((change) => change.message)); + // When the user accepts: + const result = await mdbase.setup.apply(plan); + if (result.outcome === "changed") { + // The collection changed while the user was reading. Plan again. + } +} else { + showBlocked(plan.refusal?.message, plan.conflicts.map((c) => c.message)); +}`; + +const access = `// The manifest lists "add" under files.optional. +attachButton.onclick = async () => { + const result = await mdbase.requestAccess( + { files: ["add"] }, + { reason: "Attach images to your entries" }, + ); + if (result.status === "granted") await attachImage(); +};`; + +const signOut = `import { isMdbaseError } from "@mdbase-dev/sdk"; + +try { + await mdbase.signOut(); +} catch (error) { + if (isMdbaseError(error) && error.reason === "sign_out_pending_writes") { + // Changes on this device have not reached mdbase yet. + if (await confirmDiscard()) await mdbase.signOut({ discard: true }); + } else { + throw error; + } +}`; + +const problem = `import { describeError } from "@mdbase-dev/sdk/describe"; + +function showProblem(problem) { + // problem.title, problem.message, and one action to offer. + title.textContent = problem.title; + body.textContent = problem.message; +} + +// The same for anything a call throws: +try { + await mdbase.select(id); +} catch (error) { + const described = describeError(error); + if (described) showProblem(described); +}`; + +const opener = `import { Browser } from "@capacitor/browser"; + +export const mdbase = createMdbase(declaration, { + signIn: { + open: (url) => Browser.open({ url }), + close: () => Browser.close(), + returnUrl: "dev.example.worklog://auth/mdbase/callback", + }, +});`; +--- + + +

+ createMdbase() returns one object for your whole app. It + holds a snapshot of where the user is, and the actions that move them on. + {" "}CollectionGate draws every screen + on this page for you; read on if you draw them yourself, or want to know + what the gate is doing. +

+ +

The snapshot

+

+ mdbase.getSnapshot() returns the current state, and + {" "}mdbase.subscribe(listener) calls you when it changes. + The snapshot object is replaced on every change, so it works with + {" "}useSyncExternalStore and with + plain equality checks. +

+ + + + + + + + + + + + + + + + +
FieldMeaning
phaseWhere the user is. Render from this.
collectionsThe collections this app may open, for a picker.
selectedCollectionIdThe open collection, or null.
syncSync state of the open collection, as summarizeStatus() returns it.
writesUnconfirmed and refused writes of the open collection. See Pending writes and sync status.
linksaccount and manageAccess: mdbase pages to link to for the account and for who can reach the collection.
unsyncedWorkSet when work saved before connecting has not been brought in. See Work before connecting.
+ +

Phases

+

+ snapshot.phase.kind is always exactly one of these. Switch on + it; do not build a ready check from other fields. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PhaseThe user isWhat moves them on
startingWaiting for the SDK to restore a signed-in deviceNothing; it settles by itself
signed_outNot signed in. notice says why when sign-in just ended.signIn()
authorizingIn the mdbase window. purpose is sign_in, add or approve.The user finishes or closes the window
choosingSigned in with nothing openselect(id), addCollection()
openingWaiting for a collection to open. slow turns true after 15 seconds.Nothing; chooseAnother() to leave
needs_approvalLooking at a collection this app is not approved for, usually from a shared linkapprove()
needs_setupIn a collection that lacks something the manifest provides, such as your typessetup.plan() then setup.apply(plan)
readyIn an open collection. Use phase.connection.
unsyncedWorking before connecting. Use phase.connection as when ready.signIn() to connect
problemStopped. phase.problem has the wording and one action.The action in problem.action
unavailableSomewhere the SDK cannot run: server rendering, or an unsupported browser
closedAfter close()
+

+ Once a collection is ready it stays ready through + re-checks and passing problems; phase.rechecking and + {" "}phase.problem report them for a status line. Your app + screen is not torn down while the network drops and returns. +

+

+ phase.connection has records (the + {" "}MdbaseClient), + {" "}files, people and, when the manifest asks + for them, timers. +

+ +

Calls that open a window

+

+ Sign-in and approval happen on mdbase’s own pages, in a window the SDK + opens. Browsers only allow that during a click or key press. +

+ + + + + + + + + + + + + + + +
CallWhat the user does in the window
signIn()Signs in, then picks or creates a collection and approves your app for it
addCollection()Picks another collection or creates one. Nothing is preselected.
approve(id?)Approves one named collection; by default, the one the URL asks for
reauthorize()Approves the open collection again, after access was lost or the app’s manifest asks for more
requestAccess()Allows an optional file action. See below.
+

+ Each call resolves with the new snapshot when the window closes. In the + {" "}choosing phase, phase.lastApproval.outcome + {" "}says how the last one ended: added, + {" "}unchanged, declined or closed. +

+ +

Collections

+ +
    +
  • + The list is re-read when the page regains focus. Call + {" "}refreshCollectionList() to re-read it at another + moment, such as when a menu opens. +
  • +
  • + deleteCollection(id, { confirmName }) deletes a + collection for everyone, on every device. Only its owner can, and + {" "}confirmName must be the collection’s name as the user + typed it. +
  • +
  • + A collection kept on the user’s own computers has + {" "}kind: "private". It opens the same way; see + {" "}Private collections. +
  • +
+ +

The URL

+

+ The SDK keeps the open collection in the page URL as + {" "}?collection=<id> and follows Back and Forward. A + link with that parameter opens that collection for anyone the app is + approved for, and takes others to needs_approval. The rest of + the URL, path and hash included, is yours. +

+ +

Setup

+

+ A collection is in needs_setup when it lacks something your + manifest’s provisions supply. + Setup shows the user what would be added and applies it only when they + accept. +

+ +
    +
  • + plan.summary and each change.message are + written to be shown as they are. plan.staged has the + documents themselves, for a details view. +
  • +
  • + Setup needs the definitions.manage capability. An app that + lacks it gets a plan with refusal.code + {" "}"setup_forbidden". +
  • +
  • + When a file already in the collection differs from yours, the plan has + status: "conflict". Conflicts listed in + {" "}plan.adoptable can be resolved by taking your version: + pass them back as setup.plan({ adopt }). +
  • +
  • + setup.plan({ records }) adds starter records, + such as a welcome note, written with the setup when their path is free. +
  • +
  • + configure() plans and applies in one call, for an app with + nothing to review. +
  • +
+

+ A collection the user creates while signing in to your app is set up + during sign-in, so new users do not see this screen. +

+ +

Asking for more access

+

+ File actions listed as optional in the manifest are asked for + at the moment the user first needs them. +

+ +

+ The result is granted, declined or + {" "}cancelled. An action that is already allowed resolves + {" "}granted at once and opens nothing. The reason is one + line of at most 140 characters, shown to the user. The collection stays + open throughout. +

+ +

Lost access

+

+ A user can remove your app’s access on their account page at any time. + The collection then stops with a problem whose action is + {" "}approve; offer a button that calls + {" "}reauthorize(). Link to + {" "}snapshot.links.manageAccess wherever your app says who + can reach the collection; that page is where access is managed. +

+ +

Signing out

+ +

+ Signing out ends the session and removes this browser’s copy of the + account’s collections and keys. It is refused while changes are still + waiting to sync, so that work is not thrown away by accident. +

+ +

Problems

+ +

+ describeError() accepts a thrown error, a refused write’s + {" "}receipt.problem, or a phase’s problem, and returns a + title, a message and one action. It returns null when there + is nothing to show, such as a request your own code aborted. +

+ + + + + + + + + + + + + + + + + + +
actionWhat to offer
sign_inA button that calls signIn()
approveA button that calls approve() or reauthorize()
choose_collectionchooseAnother()
set_upYour setup review
retrystart(), or the call that failed
waitNothing to press; retryAfterMs may say how long
reloadReload the page
update_appA link to updateUrl
free_space, check_connection, resolve_outcome, contact_support, noneThe message says what to do; there is nothing to call
+

+ actionLabel is the button’s wording, and + {" "}transient is true for problems the SDK clears by itself, + which can be shown quietly. +

+ +

Sign-in outside a popup

+

+ A native shell or a browser extension cannot open a popup. Pass a + {" "}signIn opener and the SDK gives it the URL to show: +

+ +

+ returnUrl must be one of the manifest’s + {" "}redirect_uris. mdbase sends the browser there when + sign-in ends. It carries no credential; the SDK learns the result itself. +

+
diff --git a/src/pages/sdk/local-development/index.astro b/src/pages/sdk/local-development/index.astro new file mode 100644 index 0000000..de38feb --- /dev/null +++ b/src/pages/sdk/local-development/index.astro @@ -0,0 +1,256 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +const manifest = `{ + "manifest_version": 1, + "id": "dev.example.worklog", + "name": "Worklog (development)", + "homepage": "http://localhost:5173/", + "redirect_uris": ["http://localhost:5173/"], + "requirements": { + "access": "full_collection", + "contracts": [], + "capabilities": { + "contract_version": 2, + "required": ["collection.read", "records.create", "records.edit"] + } + } +}`; + +const entry = `import { createMdbase } from "@mdbase-dev/sdk"; +import manifest from "./mdbase-app.json"; // the development or the deployed one + +export const mdbase = createMdbase({ + id: manifest.id, + name: manifest.name, + origin: new URL(manifest.homepage).origin, // "http://localhost:5173" + environment: "production", + manifest, +});`; + +const validate = `npx mdbase-connect-dev validate-manifest mdbase-app.dev.json --allow-local +npx mdbase-connect-dev validate-manifest mdbase-app.json`; + +const port = `import { defineConfig } from "vite"; + +export default defineConfig({ + server: { port: 5173, strictPort: true }, +});`; +--- + + +

+ You develop against the same mdbase service your users use. There is no + development mode, sandbox key or allowlist to apply for. +

+
+ SDK version +

+ Use an @mdbase-dev/sdk release after + {" "}0.1.0-beta.168. Earlier releases refuse an + {" "}http://localhost origin and fail to load under + {" "}vite dev. +

+
+ +

What is accepted

+

+ Your app’s origin may be plain HTTP when it is on this computer: +

+
    +
  • http://localhost:<port>
  • +
  • http://127.0.0.1:<port>
  • +
  • http://[::1]:<port>
  • +
+

+ Any port works. Every other app origin must be HTTPS, including a + development machine reached by its LAN address or a tunnel hostname. +

+ +

A development manifest

+

+ A manifest names the origin its app is served from, so keep two: the one + you publish, with your HTTPS homepage, and one for development whose + {" "}homepage and redirect_uris use the + development server’s origin. +

+ +

+ Everything else can be the same. You can keep two files, or write both + from one script as the Worklog tutorial + {" "}does. +

+

+ The manifest checker refuses an HTTP homepage by default, so that a + development manifest is caught before it ships. Pass + {" "}--allow-local for the development one: +

+ +

+ The published + {" "}JSON Schema + {" "}describes the deployed form, so an editor that validates against it + marks an HTTP homepage. The checker with --allow-local is the + authority for a development manifest. +

+ +

Declare the app

+ +

+ environment stays "production". It names the + mdbase service your app talks to, and that is the same one in development. + Only origin and the manifest change between development and + your deployed site. +

+ +

A separate application

+

+ mdbase identifies an app by its manifest and binds what a user approves to + the exact origin, port included. Your localhost build is therefore its own + application: +

+
    +
  • + Collections approved on localhost:5173 are approved for + that origin only. Your deployed app asks its users separately. +
  • +
  • + Sign-in state and the on-device copy of a collection belong to the + origin. Changing the port starts again from signed out. +
  • +
  • + Give the development manifest its own name, such as + “Worklog (development)”, so the two are easy to tell apart on your + account page. +
  • +
+

+ The records are real. A development build that opens your own collection + reads and writes your own files, so use a collection made for testing. +

+ + +

+ When a localhost app asks for access, the approval screen calls it “an + app running on this computer”, says its identity is unverified, and shows + no icon. A page on the internet cannot give itself a localhost origin, so + the only program that can be there is one already running on the person’s + machine. The screen says that plainly instead of presenting the manifest’s + name as a brand. +

+

+ Account sign-in, the collection choice and the list of capabilities are + the same as for a deployed app. +

+ +

Redirect URIs

+

+ redirect_uris must list at least one URL on the app’s origin. + A browser app needs no page there. Sign-in happens in a window that the + SDK opens, and the SDK learns the result itself; that window never + navigates to your redirect URI and no token is delivered to it. Listing + the homepage is enough. +

+

+ The redirect URI is used when you pass your own sign-in opener, as a + native shell or browser extension does. mdbase then sends the person’s + browser to returnUrl when sign-in ends, to bring them back to + your app. It carries no credential, so any page or deep link of yours can + receive it. See Sign-in outside a popup. +

+ +

Development servers

+

+ The SDK runs its engine in a Web Worker and loads WebAssembly files that + ship inside the package. With Vite, vite dev and + {" "}vite build both work with the default configuration. +

+

+ Fix the port, since the manifest names it: +

+ +

+ With another bundler, the requirement is that the files under the + package’s dist/ are served from your own origin, beside the + SDK’s modules. For a page with no bundler, use the + {" "}browser bundle. +

+ +

Working with no account

+

+ Much of an app can be built before you sign in at all: +

+
    +
  • + createMdbase(declaration, { unsynced: true }) + {" "}opens a real collection kept in the browser. See + {" "}Work before connecting. +
  • +
  • + MemoryReplica and createApplicationFixture() + {" "}run the same client in tests. See Testing. +
  • +
+ +

Common problems

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
What you seeCause
“The app couldn’t load” as soon as the page opens + The SDK release predates localhost support, or the SDK’s files are + not served from your origin. +
The entry refuses to start with an origin error + origin differs from the page’s origin, or from the + manifest’s homepage origin. localhost and + {" "}127.0.0.1 are different origins. +
Signed out after restarting the development serverThe server picked another port. Set strictPort.
The checker reports /homepage + An HTTP homepage needs --allow-local, or + {" "}allowLocal: true when you call + {" "}validateAppManifest(). +
The sign-in window does not open + signIn() was called after an await. Call + it directly in the click handler. +
+
diff --git a/src/pages/sdk/manifest/index.astro b/src/pages/sdk/manifest/index.astro index a265cdc..3623c05 100644 --- a/src/pages/sdk/manifest/index.astro +++ b/src/pages/sdk/manifest/index.astro @@ -111,11 +111,28 @@ const provision = `"provisions": {

Web apps

- A web app declares an HTTPS homepage and up to ten - {" "}redirect_uris on the homepage’s origin. + A web app declares an HTTPS homepage and between one and + ten redirect_uris on the homepage’s origin. {" "}distribution may be left out; it defaults to {" "}"web".

+

+ For development, the homepage and redirect URIs may instead be on + {" "}http://localhost, http://127.0.0.1 or + {" "}http://[::1], with any port. That manifest is a separate + application from your published one; see + {" "}Develop on localhost. +

+

+ A browser app that signs in through the SDK’s own window can list its + homepage as its one redirect URI. See + {" "}Redirect URIs. +

+

+ You do not have to write the manifest by hand. + {" "}@mdbase-dev/connect-dev + {" "}checks it and builds its type packs. +

Requirements

@@ -199,6 +216,15 @@ const provision = `"provisions": { {" "}redirect_uris. The scheme must match or extend the application ID, as in dev.example.worklog://auth/mdbase/callback.

+

+ The origin you give createMdbase() is normally + the manifest’s homepage origin. A Capacitor shell + ({" "}capacitor://) or Chrome extension + ({" "}chrome-extension://) origin must also be listed under + the manifest’s installation_origins. Firefox extension + origins are not accepted. These apps sign in through an opener; see + {" "}Sign-in outside a popup. +

Changing the manifest

diff --git a/src/pages/sdk/notifications/index.astro b/src/pages/sdk/notifications/index.astro index 0eafb46..cfd6ff5 100644 --- a/src/pages/sdk/notifications/index.astro +++ b/src/pages/sdk/notifications/index.astro @@ -77,7 +77,7 @@ if (await deliveries.claim(event.delivery_id)) {

connection.timers is on the connection you get from {" "}createMdbase() once a collection is ready. See - {" "}Connect a browser app. + {" "}Sign-in and collections.

diff --git a/src/pages/sdk/react/index.astro b/src/pages/sdk/react/index.astro new file mode 100644 index 0000000..f27a72c --- /dev/null +++ b/src/pages/sdk/react/index.astro @@ -0,0 +1,204 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +const snapshot = `import { useSyncExternalStore } from "react"; +import { mdbase } from "./mdbase"; + +export function useMdbase() { + return useSyncExternalStore(mdbase.subscribe, mdbase.getSnapshot, mdbase.getSnapshot); +}`; + +const phase = `import { useEffect } from "react"; +import { mdbase } from "./mdbase"; +import { useMdbase } from "./use-mdbase"; + +export function App() { + const { phase } = useMdbase(); + + useEffect(() => { + void mdbase.start(); + }, []); + + switch (phase.kind) { + case "ready": + case "unsynced": + return ; + case "signed_out": + return ; + default: + return ; + } +}`; + +const live = `import type { MdbaseClient, Query, RecordView } from "@mdbase-dev/sdk"; +import { useEffect, useState } from "react"; + +export function useLiveQuery(db: MdbaseClient, query: Query) { + const [state, setState] = useState<{ records: readonly RecordView[]; stale: boolean }>({ + records: [], + stale: true, + }); + // A query is plain data, so its JSON is a stable dependency. + const key = JSON.stringify(query); + + useEffect(() => { + const live = db.live(JSON.parse(key) as Query); + const stop = live.subscribe((next) => setState({ records: next.records, stale: next.stale })); + return () => { + stop(); + live.close(); + }; + }, [db, key]); + + return state; +}`; + +const use = `function Entries({ db }: { db: MdbaseClient }) { + const { records, stale } = useLiveQuery(db, { + types: ["worklog-entry"], + order_by: [{ field: "date", direction: "desc" }], + limit: 100, + }); + + return ( +
    + {records.map((entry) => ( +
  • {String(entry.frontmatter.get("summary"))}
  • + ))} +
+ ); +}`; + +const write = `async function complete(task: RecordView) { + const write = await db.update(task, { patch: { status: "done" } }); + if (write.state === "rejected") { + setProblem(describeError(write.receipt.problem)?.message ?? null); + } +}`; + +const status = `import { useMdbase } from "./use-mdbase"; + +export function SyncStatus() { + const { sync } = useMdbase(); + return sync ? {sync.text} : null; +}`; +--- + + +

What there is

+

+ The SDK has no React dependency and needs no provider. Two things make + it fit React directly: +

+
    +
  • + The entry from createMdbase() is an external store: + {" "}subscribe and getSnapshot have the shapes + {" "}useSyncExternalStore expects. +
  • +
  • + @mdbase-dev/ui has the sign-in and + collection screens as components. With CollectionGate you + may not need the first hook on this page at all. +
  • +
+

+ The Worklog tutorial is a complete + React app built this way. +

+ +

The snapshot as a store

+ +

+ The snapshot is replaced only when something changes, so components + re-render only then. The third argument is the server snapshot: where the + SDK cannot run, the phase is unavailable, so a + server-rendered page gets a defined state. +

+ +

+ Create the entry once, in a module, as the tutorial’s + {" "}mdbase.ts does. Do not create it inside a component. +

+ +

A live query hook

+ + +
    +
  • + The effect’s cleanup both unsubscribes and closes the query. Closing + tells the collection to stop sending changes. +
  • +
  • + stale is true until the first result arrives, and again + while a query is reset. The previous records stay in place meanwhile, so + the list does not flash empty. +
  • +
  • + Give lists a limit and leave bodies out. To page, raise + the limit; live.setQuery() changes a running query without + a new subscription. +
  • +
+ +

Writes

+

+ Call the client from event handlers. A write resolves when it is saved on + the device, and a live query already shows it, so there is no cache to + update and nothing to refetch. +

+ +

+ For a screen where someone types into one record, use a + {" "}record session. It keeps the text field + and the record in step while other devices edit the same record. +

+ +

Sync status

+ +

+ snapshot.sync is the open collection’s status as a short + line of text with counts, and snapshot.writes lists writes + that are still waiting or were refused. See + {" "}Pending writes and sync status. +

+ +

Things to get right

+
    +
  • + Key your screen on the connection. + {" "}key={connection.id} resets component state + when the user switches collection. +
  • +
  • + Call sign-in from the click. + {" "}onClick={() => mdbase.signIn()} works. + Awaiting something first loses the browser’s permission to open the + window. +
  • +
  • + Strict Mode is fine. start() is safe to + call twice, and the live query hook above cleans up after the first + mount. +
  • +
  • + Server rendering. The SDK runs in the browser. + Creating the entry and reading its snapshot on the server is safe; the + phase there is unavailable. Call start() only + in the browser, from an effect: on the server it rejects. +
  • +
+
diff --git a/src/pages/sdk/security/index.astro b/src/pages/sdk/security/index.astro index 6287e80..25ec0da 100644 --- a/src/pages/sdk/security/index.astro +++ b/src/pages/sdk/security/index.astro @@ -84,7 +84,13 @@ import DocsLayout from "../../../components/DocsLayout.astro";

Origins and callbacks

    -
  • Your homepage, icon and browser callbacks use HTTPS.
  • +
  • + Your homepage, icon and browser callbacks use HTTPS. The one exception + is an app on this computer: + {" "}a localhost origin may use + HTTP, and the approval screen then says the app’s identity is + unverified. +
  • Browser callbacks stay on the homepage’s origin.
  • Native callback schemes match or extend your application ID.
  • The origin you give createMdbase() is the exact origin you serve from.
  • diff --git a/src/pages/sdk/ui/index.astro b/src/pages/sdk/ui/index.astro new file mode 100644 index 0000000..72412dd --- /dev/null +++ b/src/pages/sdk/ui/index.astro @@ -0,0 +1,262 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +import app from "../../../../examples/worklog/src/App.tsx?raw"; + +const install = `npm install --save-exact @mdbase-dev/ui @mdbase-dev/i18n \\ + @lingui/core@6.9.0 @lingui/react@6.9.0`; + +const css = `import "@mdbase-dev/ui/collections.css";`; + +const gateProps = ` }} + layout="panel" + extraActions={What is Worklog?} + onBeforeLeave={(intent) => (hasUnsavedDraft() ? confirmLeave(intent) : true)} + unsynced +> + {(connection) => } +`; + +const switcher = `// In a header: the collection's name, opening the list. + + +// In a toolbar: a button with an icon only. + + +// Opened from an item in your own menu. +const [open, setOpen] = useState(false); +const menuButton = useRef(null); + +`; + +const problem = `import { CollectionProblem } from "@mdbase-dev/ui/collections"; + +// The current phase's problem, as a banner above your content: + + +// An error from one of your own calls: +`; + +const theme = `.worklog { + --mdbase-collections-accent: #0f766e; + --mdbase-collections-on-accent: #ffffff; + --mdbase-collections-font: "Inter", system-ui, sans-serif; + --mdbase-collections-radius: 6px; +}`; + +const mount = `import { mountCollectionUi } from "@mdbase-dev/ui/collections"; +import "@mdbase-dev/ui/collections.css"; +import { mdbase } from "./mdbase"; + +const app = { name: "Worklog" }; + +// Sign-in, choosing and the list, inside your own dialog or page. +const panel = mountCollectionUi(document.querySelector("#collections"), { + part: "panel", + mdbase, + app, +}); + +// The header control. +mountCollectionUi(document.querySelector("#switcher"), { + part: "switcher", + variant: "compact", + mdbase, + app, +}); + +mdbase.subscribe(() => { + const { phase } = mdbase.getSnapshot(); + const open = phase.kind === "ready" || phase.kind === "unsynced"; + document.querySelector("#collections").hidden = open; + if (open) showWorklog(phase.connection.records); +}); + +// Later: panel.update({ ... }) to change options, panel.unmount() to remove it.`; + +const view = `import { collectionView } from "@mdbase-dev/ui/collections"; + +const view = collectionView(mdbase.getSnapshot(), { appName: "Worklog" }); +// view.kind names the screen to draw, with that screen's wording and controls.`; + +const links = `import { mdbaseAppHref } from "@mdbase-dev/ui/apps"; + +// The collection open in this page, opened in the Editor. +editorLink.href = mdbaseAppHref("https://editor.mdbase.dev/", location.href);`; +--- + + +

    + Every app built on mdbase needs the same screens before its own: sign + in, pick a collection, approve access, review setup, and explain a + problem. This package has them, so users meet the same flow in every app + and you write none of it. +

    + +

    Install

    + +

    + The components need React 19.2 or later. @mdbase-dev/i18n + {" "}and the two Lingui packages are peer dependencies. Import the + stylesheet once, anywhere in your app: +

    + + +

    CollectionGate

    +

    + The gate takes your createMdbase() + {" "}entry and shows the right screen for each phase. It renders its + children once a collection is open, and starts the SDK itself. +

    + +
+ + + + + + + + + + + + + + + + + +
PropMeaning
mdbaseThe entry from createMdbase().
appname, and optionally mark: an element shown as your app’s icon.
childrenA function of the open connection. Called in the ready and unsynced phases.
layout"page" (default) fills the viewport. "panel" sits inside your own shell or dialog.
unsyncedShow the work before connecting screens. Pass true, or { storage, map }.
extraActionsYour own links or buttons on the gate’s screens, such as “Try a sample”.
onBeforeLeaveCalled before anything that leaves the open collection. Return false, or a promise of it, to stay.
autoStartCall mdbase.start() on mount. Default true.
copyReplacement wording. See below.
+ +

+ While a collection is open, the gate shows a banner above your app for a + live problem, such as access that was removed. +

+ +

CollectionSwitcher

+

+ The control inside your app for the open collection. It lists the + approved collections, and has “Add a collection”, “Manage access” and the + account with sign-out. It takes the same mdbase, + {" "}app, unsynced and + {" "}onBeforeLeave props as the gate. +

+ + +

Smaller parts

+ + + + + + + + + + + + + + + +
ExportUse
CollectionPanelThe gate’s screens and the switcher’s list in one panel, for a settings dialog or an app that opens on its own screen first.
CollectionProblemOne problem with its fix, as a block or a banner.
CollectionSetupReviewThe setup review on its own.
CollectionUnsyncedLine, CollectionBringInThe “saved only in this browser” line and the offer to bring that work in.
CollectionSaveWarningA warning when unsaved changes sit in storage the browser may clear.
useCollectionUi()The state and actions behind the components, for a layout of your own.
+ +

+ All of them are exported from @mdbase-dev/ui/collections. +

+ +

Restyle

+

+ The stylesheet is self-contained. It follows the system’s light or dark + setting; put data-theme="light" or + {" "}data-theme="dark" on the root element to fix one. Set + {" "}--mdbase-collections-* custom properties on any + ancestor to match your app: +

+ + + + + + + + + + + + + + + + +
PropertiesControl
-accent, -on-accentPrimary buttons and focus
-canvas, -surface, -border, -current-bgBackgrounds and lines
-text, -text-soft, -danger, -warningText and status colours
-font, -font-size, -font-size-caption, -font-size-section, -font-size-heading, -font-weight-strongType
-radius-sm, -radius, -radius-lg, -control-heightShape and size
-elevation-low, -elevation-high, -scrimShadows and the dialog backdrop
+ +

Wording and language

+

+ The components are in English by default and need no provider. Pass + {" "}copy to replace individual strings; + {" "}defaultCollectionCopy lists every key. +

+

+ The package also ships its strings in French, German, Spanish, Italian, + Portuguese, Russian, Japanese, Korean and Chinese. + {" "}@mdbase-dev/ui/i18n exports them as + {" "}uiCatalogues, for an app that sets up + {" "}@mdbase-dev/i18n with its own Lingui catalogue. +

+ +

Apps that do not use React

+

+ mountCollectionUi() renders a component into an element you + give it. Your app needs no React code of its own, though the + {" "}react and react-dom packages must be + installed. +

+ +

+ part is "panel", "switcher" or + {" "}"problem". To draw everything yourself and keep only the + logic, collectionView() turns a snapshot into the screen to + show: +

+ + +

Links to the mdbase apps

+

+ A collection your app uses can also be opened in mdbase’s + {" "}Editor, Reader and Writer. + {" "}mdbaseAppHref() builds a link to one of them that opens + the collection named in the current page’s URL: +

+ +

+ The AppSwitcher component in the same package is the menu + mdbase’s own apps show under their name. It lists only those three apps, + so it is not a place to add yours. +

+ diff --git a/src/pages/sdk/unsynced/index.astro b/src/pages/sdk/unsynced/index.astro new file mode 100644 index 0000000..24f3d2d --- /dev/null +++ b/src/pages/sdk/unsynced/index.astro @@ -0,0 +1,206 @@ +--- +import DocsLayout from "../../../components/DocsLayout.astro"; +import CodeBlock from "../../../components/CodeBlock.astro"; + +const enable = `import { createMdbase } from "@mdbase-dev/sdk"; +import manifest from "./mdbase-app.json"; + +export const mdbase = createMdbase( + { id: manifest.id, name: manifest.name, origin: location.origin, environment: "production", manifest }, + { unsynced: true }, +);`; + +const render = `function render(snapshot) { + const { phase } = snapshot; + if (phase.kind === "ready" || phase.kind === "unsynced") { + showApp(phase.connection.records); + savedOnlyHere.hidden = phase.kind !== "unsynced" || !snapshot.unsyncedWork; + } +} + +// "Connect to sync your work" +connectButton.onclick = () => mdbase.signIn(); + +// On a sign-in screen: "Continue without connecting" +continueButton.onclick = () => mdbase.unsynced.open();`; + +const transfer = `// A collection is open (phase "ready") and snapshot.unsyncedWork is set. +const plan = await mdbase.unsynced.planTransfer(); + +// plan.counts: { create, present, skipped, renamed, changed } +// plan.missingTypes: types the records use that this collection lacks +if (plan.missingTypes.length > 0 && (await userAgreesToAddTypes(plan.missingTypes))) { + return bringIn(await mdbase.unsynced.planTransfer({ addTypes: true })); +} +await bringIn(plan); + +async function bringIn(plan) { + const result = await mdbase.unsynced.transfer(plan, { + onProgress: ({ done, total }) => showProgress(done, total), + }); + if (result.failed.length > 0) { + // Plan and transfer again to retry. Nothing already done repeats. + } +}`; + +const map = `const plan = await mdbase.unsynced.planTransfer({ + map: (record) => { + if (!record.types.includes("worklog-entry")) return null; // leave it out + // Each record arrives with its body and complete source. + return { path: \`journal/\${record.path}\`, document: record.document }; + }, +});`; + +const discard = `if (await confirm("Delete the work saved in this browser? This cannot be undone.")) { + await mdbase.unsynced.discard(); +}`; +--- + + +

What it is

+

+ With the unsynced option, a signed-out visitor gets a + working app straight away. The SDK opens an unsynced + collection: a real collection, with your types installed, stored + in this browser and nowhere else. Your code reads and writes it through + the same MdbaseClient as a synced one. +

+

+ When the person later signs in and opens a collection, the work they did + can be brought into it. +

+

+ The shared UI handles all of this when you pass + {" "}unsynced to CollectionGate and + {" "}CollectionSwitcher. The rest of this page is for apps + that draw it themselves. +

+ +

Turn it on

+ +

+ With the option set, start() opens the unsynced collection + whenever the person is signed out and the URL names no collection. The + phase is unsynced. The types in your manifest’s + {" "}provisions are installed + in it automatically. +

+ +

Show it

+ +
    +
  • + Treat unsynced like ready: + {" "}phase.connection is the same kind of connection. +
  • +
  • + Tell the person where their work is. Once something has been saved, + {" "}snapshot.unsyncedWork is set; show a line such as + “Saved only in this browser” with a way to connect. +
  • +
  • + The unsynced collection is never listed in + {" "}snapshot.collections. +
  • +
  • + A signed-in person with no collection open is in the + {" "}choosing phase as usual. + {" "}mdbase.unsynced.open() opens the unsynced collection + from there, or from a sign-in screen that offers “Continue without + connecting”. +
  • +
+ +

Bring the work in

+

+ After the person connects, snapshot.unsyncedWork stays set + while the collection they opened does not have the work yet. Plan the + transfer, show what it would do when there is something to decide, then + run it. +

+ + + + + + + + + + + + + + +
Item outcomeMeaning
createThe record will be created, with the same ID.
presentAn earlier transfer already brought it in.
skippedYour map left it out.
changedIt was brought in earlier and has changed here since. The newer version is created beside the first as a copy.
+
    +
  • + Planning writes nothing. A plan is safe to throw away. +
  • +
  • + A transfer can be interrupted by a reload or a lost connection. Plan + and transfer again: only what is missing is created. +
  • +
  • + When a path is already taken in the destination, the record goes to a + free path beside it; the item’s renamedFrom has the path + you asked for. +
  • +
  • + The destination’s own types and settings are never replaced. With + {" "}addTypes, types it lacks are added from the unsynced + collection. +
  • +
  • + When everything is in the destination, result.retired is + true and the unsynced collection starts again empty. +
  • +
+ +

Reshape records

+

+ By default each record keeps its path and its whole Markdown source. Pass + {" "}map to change where records go, or to leave some out: +

+ +

+ The function returns a CreateInput, or null to + leave the record out. A record always keeps its ID; returning a different + {" "}id is refused. +

+ +

Discard

+ +

+ Discarding erases the unsynced collection from this browser. Ask first. +

+ +

Limits

+
    +
  • + The work is in browser storage. Clearing site data, or a browser that + evicts storage under pressure, removes it. Say so in your interface, and + offer connecting early. +
  • +
  • + It is per browser and per origin. It is not on the person’s other + devices, and a development build on another port has its own. +
  • +
  • + There is one unsynced collection per app in a browser. +
  • +
+
diff --git a/src/styles/global.css b/src/styles/global.css index 054ee9c..965e6af 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -225,6 +225,17 @@ main[tabindex="-1"]:focus { outline: none; } } .trust-table th, .profile-table th, .api-table th { color: var(--ink-muted); font: 400 0.68rem/1.4 var(--sans); } +/* Generated API reference: one item per exported name, its members as a list. */ +.api-item h3 code { font-size: 1em; } +.api-kind { margin-left: 0.4rem; color: var(--ink-muted); font: 400 0.68rem/1.4 var(--sans); } +.api-item > p, .api-members dd p, .api-index td p { margin-top: 0.4rem; color: var(--ink-soft); } +.api-index td p:first-child { margin-top: 0; } +.api-members { margin-top: 1rem; } +.api-members dt { margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--line); overflow-wrap: anywhere; } +.api-members dt code { font-size: 0.76rem; line-height: 1.58; } +.api-members dd { margin: 0; font-size: 0.82rem; } +.api-deprecated { color: var(--ink-muted); } + .status { color: var(--ink-soft); font-size: 0.82rem; } .status::before { display: none; } @@ -303,7 +314,7 @@ main[tabindex="-1"]:focus { outline: none; } .docs-article > p, .docs-article li { color: var(--ink-soft); } .docs-article ul, .docs-article ol { padding-left: 1.2rem; } .docs-article li + li { margin-top: 0.3rem; } -.docs-article :not(pre) > code { padding: 0; color: var(--ink); background: transparent; border: 0; } +.docs-article :not(pre) > code { padding: 0; color: var(--ink); background: transparent; border: 0; overflow-wrap: break-word; } .docs-article kbd { padding: 0.05rem 0.3rem; color: var(--ink); font: 400 0.78em/1.4 var(--mono); border: 1px solid var(--line); border-radius: 2px; white-space: nowrap; } .docs-toc { padding-left: 1rem; border-left: 1px solid var(--ink); } .docs-toc > strong { color: var(--ink); } diff --git a/tsconfig.json b/tsconfig.json index 1385b35..41676bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,6 @@ "extends": "astro/tsconfigs/strict", "compilerOptions": { "baseUrl": "." - } + }, + "exclude": ["dist", "examples"] } -