+ >
+ );
+}
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)`, `` —\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
-
-
-
Method
Returns
-
-
-
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.
-
- 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.
-
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.
- 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().
-
Code
Recovery
When
+
+
Code
+
Recovery
+
When
+
-
invalid_request
fix_request
Malformed parameters, unsafe paths, an expired query cursor
-
invalid_record
fix_request
The write fails validation; see issues
-
not_found
refresh
No such record, file, hold or transfer
-
conflict
resolve_conflict
The current state doesn’t allow this write: revision, path_taken, duplicate_value, …
-
unauthenticated
reauthorize
No session, an expired one, or a revoked grant
-
forbidden
reauthorize
The grant lacks the capability, or the role doesn’t allow it
-
collection_invalid
repair_collection
The collection’s config or type files can’t be loaded
-
unavailable
retry
The replica can’t be reached, for example no device online
-
rate_limited
retry
Too many requests; see retryAfterMs
-
quota_exceeded
free_space
The collection is over its storage quota
-
too_large
fix_request
A request or file over its limit
-
upgrade_required
upgrade
This SDK version is too old for the replica or collection
-
outcome_unknown
resolve_outcome
A write’s outcome can’t be determined; read again and decide
-
cancelled
none
You cancelled the request
-
internal
contact_support
A bug; the request may be retried once
+ {
+ errors.map((error) => (
+
+
{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 field
+
Meaning
+
+
+
+
kind
"type", "contract" or "schema"
+
mode
"seed" or "managed"; see below
+
source
The file’s name inside the pack
+
target
Where it goes in the collection, such as _types/worklog-entry.md. Defaults to source.
+
document
The file’s exact text
+
upgradeFrom
Seed 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
+
+
+
+
Mode
+
Who owns the file after setup
+
Use it for
+
+
+
+
+
seed
+
The user. Your app writes it once; later edits by the user stay.
+
A type people may want to extend with fields of their own
+
+
+
managed
+
Your 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 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.
+
- 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.
- 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.
- 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.
- 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.
- @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.
- 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
+
+
+ Report a bug or ask a question in
+ {" "}GitHub issues.
+
+ Report a security problem as the
+ {" "}security page describes.
+
+
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.
+
+
+
+
+
Field
+
Meaning
+
+
+
+
phase
Where the user is. Render from this.
+
collections
The collections this app may open, for a picker.
+
selectedCollectionId
The open collection, or null.
+
sync
Sync state of the open collection, as summarizeStatus() returns it.
account and manageAccess: mdbase pages to link to for the account and for who can reach the collection.
+
unsyncedWork
Set 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.
+
+
+
+
+
+
Phase
+
The user is
+
What moves them on
+
+
+
+
+
starting
+
Waiting for the SDK to restore a signed-in device
+
Nothing; it settles by itself
+
+
+
signed_out
+
Not signed in. notice says why when sign-in just ended.
+
signIn()
+
+
+
authorizing
+
In the mdbase window. purpose is sign_in, add or approve.
+
The user finishes or closes the window
+
+
+
choosing
+
Signed in with nothing open
+
select(id), addCollection()
+
+
+
opening
+
Waiting for a collection to open. slow turns true after 15 seconds.
+
Nothing; chooseAnother() to leave
+
+
+
needs_approval
+
Looking at a collection this app is not approved for, usually from a shared link
+
approve()
+
+
+
needs_setup
+
In a collection that lacks something the manifest provides, such as your types
+
setup.plan() then setup.apply(plan)
+
+
+
ready
+
In an open collection. Use phase.connection.
+
+
+
+
unsynced
+
Working before connecting. Use phase.connection as when ready.
+
signIn() to connect
+
+
+
problem
+
Stopped. phase.problem has the wording and one action.
+
The action in problem.action
+
+
+
unavailable
+
Somewhere the SDK cannot run: server rendering, or an unsupported browser
+
+
+
+
closed
+
After 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.
+
+
+
+
+
+
Call
+
What 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
+ 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.
+
The 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.
+
+ 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.
+
+
+
What the user sees
+
+ 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 see
+
Cause
+
+
+
+
+
“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 server
+
The 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.
+
- 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)) {
+ 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.
+
+
+
+
+
+
Prop
+
Meaning
+
+
+
+
mdbase
The entry from createMdbase().
+
app
name, and optionally mark: an element shown as your app’s icon.
+
children
A 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.
+ 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
+
+
+
+
Export
+
Use
+
+
+
+
CollectionPanel
The 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.
+
CollectionProblem
One problem with its fix, as a block or a banner.
+
CollectionSetupReview
The setup review on its own.
+
CollectionUnsyncedLine, CollectionBringIn
The “saved only in this browser” line and the offer to bring that work in.
+
CollectionSaveWarning
A 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:
+
+ 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 outcome
+
Meaning
+
+
+
+
create
The record will be created, with the same ID.
+
present
An earlier transfer already brought it in.
+
skipped
Your map left it out.
+
changed
It 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.
+