yaml-flow 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/asset-integrity.json +1 -1
- package/browser/board-livecards-client.js +1 -1
- package/browser/board-livecards-client.js.map +1 -1
- package/browser/board-livecards-localstorage.js +5 -5
- package/browser/board-livecards-localstorage.js.map +1 -1
- package/browser/live-cards.js +3 -1
- package/dist/{board-live-cards-public-CW5074xr.d.cts → board-live-cards-public-5n1-syA3.d.cts} +1 -2
- package/dist/{board-live-cards-public-hnZo0mAf.d.ts → board-live-cards-public-CK_J8uv0.d.ts} +1 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -1
- package/dist/cli/node/artifacts-store-cli.cjs +5 -5
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -1
- package/dist/cli/node/artifacts-store-cli.js +5 -5
- package/dist/cli/node/artifacts-store-cli.js.map +1 -1
- package/dist/cli/node/board-live-cards-cli.cjs +7 -7
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/node/board-live-cards-cli.js +7 -7
- package/dist/cli/node/board-live-cards-cli.js.map +1 -1
- package/dist/cli/node/card-store-cli.cjs +4 -4
- package/dist/cli/node/card-store-cli.cjs.map +1 -1
- package/dist/cli/node/card-store-cli.js +4 -4
- package/dist/cli/node/card-store-cli.js.map +1 -1
- package/dist/cli/node/execution-adapter.cjs +1 -1
- package/dist/cli/node/execution-adapter.cjs.map +1 -1
- package/dist/cli/node/execution-adapter.js +1 -1
- package/dist/cli/node/execution-adapter.js.map +1 -1
- package/dist/cli/node/fs-board-adapter.cjs +7 -7
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -1
- package/dist/cli/node/fs-board-adapter.d.cts +2 -2
- package/dist/cli/node/fs-board-adapter.d.ts +2 -2
- package/dist/cli/node/fs-board-adapter.js +7 -7
- package/dist/cli/node/fs-board-adapter.js.map +1 -1
- package/dist/cli/node/source-cli-task-executor.cjs +2 -2
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -1
- package/dist/cli/node/source-cli-task-executor.js +2 -2
- package/dist/cli/node/source-cli-task-executor.js.map +1 -1
- package/dist/execution-refs.cjs +2 -2
- package/dist/execution-refs.cjs.map +1 -1
- package/dist/execution-refs.d.cts +9 -4
- package/dist/execution-refs.d.ts +9 -4
- package/dist/execution-refs.js +2 -2
- package/dist/execution-refs.js.map +1 -1
- package/dist/server-runtime/index.cjs +4 -4
- package/dist/server-runtime/index.cjs.map +1 -1
- package/dist/server-runtime/index.d.cts +3 -3
- package/dist/server-runtime/index.d.ts +3 -3
- package/dist/server-runtime/index.js +4 -4
- package/dist/server-runtime/index.js.map +1 -1
- package/dist/step-machine-public/index.cjs +2 -1
- package/dist/step-machine-public/index.cjs.map +1 -1
- package/dist/step-machine-public/index.d.cts +7 -0
- package/dist/step-machine-public/index.d.ts +7 -0
- package/dist/step-machine-public/index.js +2 -1
- package/dist/step-machine-public/index.js.map +1 -1
- package/dist/storage-refs.cjs +2 -2
- package/dist/storage-refs.cjs.map +1 -1
- package/dist/storage-refs.d.cts +1 -2
- package/dist/storage-refs.d.ts +1 -2
- package/dist/storage-refs.js +2 -2
- package/dist/storage-refs.js.map +1 -1
- package/dist/{types-BxEFcVK9.d.cts → types-CU3DjTKL.d.cts} +1 -1
- package/dist/{types-B1ZRa4aI.d.ts → types-HGDTWIun.d.ts} +1 -1
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +13 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.py +398 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +4 -4
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +1 -1
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +1 -1
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +1 -1
- package/examples/example-board/agent-instructions.md +1 -1
- package/examples/example-board/cards/{card-market-prices.json → cardT-market-prices.json} +2 -2
- package/examples/example-board/cards/{card-portfolio.json → cardT-portfolio.json} +3 -13
- package/examples/example-board/demo-server-config.json +1 -1
- package/examples/example-board/demo-server.js +48 -25
- package/examples/example-board/demo-shell-localstorage.html +3 -3
- package/examples/example-board/demo-shell-with-server.html +2 -2
- package/examples/example-board/demo-task-executor.js +4 -8
- package/package.json +2 -2
- package/step-machine-cli.js +1 -1
- package/examples/example-board/cards/_index.json +0 -47
- /package/examples/example-board/cards/{card-portfolio-value.json → cardT-portfolio-value.json} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/cli/common/storage-interface.ts","../../../src/cli/browser-api/storage-localstorage-adapters.ts","../../../src/cli/browser-api/board-live-cards-browser-adapter.ts"],"names":["REF_PREFIX","toBase64Url","raw","utf8","buf","base64","binary","byte","fromBase64Url","input","bytes","serializeRef","ref","parseRef","s","parsed","candidate","stableJson","value","obj","k","fnv32a","str","seed","h","i","computeStableJsonHashBrowser","a","b","c","d","n","createLocalStorageBlobStorage","prefix","key","textEncoder","encodeBytes","bin","decodeBytes","encoded","out","content","envelope","prefix2","marker","size","createLocalStorageKvStorage","fullPrefix","result","lsKey","createLocalStorageJournalStorageAdapter","storageKey","load","save","entries","entry","createInMemoryRelayLock","held","_busRegistry","getInMemoryNotificationBus","channel","bus","listeners","event","fn","onEvent","createInMemoryNotificationTransport","e","createBrowserBoardPlatformAdapter","namespace","opts","selfRef","handlerRegistry","memoryBlobs","lock","ns","args","url","resp","baseUrl","params","v","handler","notifications","name","data"],"mappings":"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","file":"board-live-cards-browser-adapter.js","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef.\n * Also accepts the legacy ::fs-path::<path> format for backward compatibility. */\nexport function parseRef(s: string): KindValueRef {\n // Legacy format: ::fs-path::<path>\n if (s.startsWith('::fs-path::')) {\n return { kind: 'fs-path', value: s.slice('::fs-path::'.length) };\n }\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * storage-localstorage-adapters.ts\n *\n * Browser localStorage implementations of the board-live-cards storage primitives:\n * BlobStorage — localStorage keys prefixed with `${prefix}:blob:`\n * KVStorage — localStorage keys prefixed with `${prefix}:kv:`, values JSON-encoded\n * JournalStorageAdapter — single localStorage key holding a JSON array of entries\n * CardStorageAdapter — KV-backed, compatible with createCardStore()\n *\n * No Node imports. Requires globalThis.localStorage (browser / jsdom environment).\n */\n\nimport type { BlobStorage, KVStorage, JSONStorage } from '../common/storage-interface.js';\nimport type { JournalStorageAdapter, CardStorageAdapter, JournalEntry, LiveCard, CardIndex } from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// Stable JSON + sync hash\n// Used for card dedup and snapshot versioning. Not security-sensitive.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nfunction fnv32a(str: string, seed: number): number {\n let h = seed >>> 0;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h;\n}\n\n/**\n * Synchronous stable content hash for browser environments.\n * Uses four FNV-1a 32-bit passes to produce 32 hex chars.\n * Deterministic and cross-session stable; NOT cryptographically secure.\n */\nexport function computeStableJsonHashBrowser(value: unknown): string {\n const str = stableJson(value);\n const a = fnv32a(str, 0x811c9dc5);\n const b = fnv32a(str, 0xdeadbeef);\n const c = fnv32a(str, 0x01234567);\n const d = fnv32a(str, 0xfeedface);\n return [a, b, c, d].map(n => n.toString(16).padStart(8, '0')).join('');\n}\n\n// ============================================================================\n// createLocalStorageBlobStorage\n// ============================================================================\n\nexport function createLocalStorageBlobStorage(prefix: string): BlobStorage {\n function key(k: string): string { return `${prefix}:blob:${k}`; }\n const textEncoder = new TextEncoder();\n\n function encodeBytes(bytes: Uint8Array): string {\n if (typeof btoa === 'function') {\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n return btoa(bin);\n }\n return '';\n }\n\n function decodeBytes(encoded: string): Uint8Array {\n if (typeof atob === 'function') {\n const bin = atob(encoded);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array();\n }\n\n return {\n read(k: string): string | null {\n return globalThis.localStorage.getItem(key(k));\n },\n write(k: string, content: string): void {\n globalThis.localStorage.setItem(key(k), content);\n },\n exists(k: string): boolean {\n return globalThis.localStorage.getItem(key(k)) !== null;\n },\n remove(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n\n readBytes(k: string): Uint8Array | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n return decodeBytes(parsed.data);\n }\n } catch {\n // fall through to plain text path\n }\n return textEncoder.encode(raw);\n },\n\n writeBytes(k: string, content: Uint8Array): void {\n // Store binary payloads as base64 envelope to avoid lossy UTF-8 coercion.\n const envelope = JSON.stringify({ __kind: 'bytes-b64', data: encodeBytes(content) });\n globalThis.localStorage.setItem(key(k), envelope);\n },\n\n listKeys(prefix2?: string): string[] {\n const marker = key(prefix2 ?? '');\n const out: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const k = globalThis.localStorage.key(i);\n if (k && k.startsWith(marker)) out.push(k.slice(key('').length));\n }\n return out.sort();\n },\n\n stat(k: string) {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n let size = textEncoder.encode(raw).byteLength;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n size = decodeBytes(parsed.data).byteLength;\n }\n } catch {\n // plain text path\n }\n return { key: k, size };\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageKvStorage\n// ============================================================================\n\nexport function createLocalStorageKvStorage(prefix: string): KVStorage {\n function key(k: string): string { return `${prefix}:kv:${k}`; }\n\n return {\n read(k: string): unknown | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try { return JSON.parse(raw); } catch { return null; }\n },\n write(k: string, value: unknown): void {\n globalThis.localStorage.setItem(key(k), JSON.stringify(value));\n },\n delete(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n listKeys(prefix2?: string): string[] {\n const fullPrefix = key(prefix2 ?? '');\n const result: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const lsKey = globalThis.localStorage.key(i);\n if (lsKey !== null && lsKey.startsWith(fullPrefix)) {\n // Strip the outer prefix + ':kv:' to return the logical key\n result.push(lsKey.slice(key('').length));\n }\n }\n return result;\n },\n };\n}\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createLocalStorageJsonStorage(prefix: string): JSONStorage {\n const kv = createLocalStorageKvStorage(prefix);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix2?) => kv.listKeys(prefix2),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageJournalStorageAdapter\n// All entries stored as a JSON array under a single localStorage key.\n// ============================================================================\n\nexport function createLocalStorageJournalStorageAdapter(storageKey: string): JournalStorageAdapter {\n function load(): JournalEntry[] {\n const raw = globalThis.localStorage.getItem(storageKey);\n if (!raw) return [];\n try { return JSON.parse(raw) as JournalEntry[]; } catch { return []; }\n }\n\n function save(entries: JournalEntry[]): void {\n globalThis.localStorage.setItem(storageKey, JSON.stringify(entries));\n }\n\n return {\n readAllEntries(): JournalEntry[] {\n return load();\n },\n appendEntry(entry: JournalEntry): void {\n const entries = load();\n entries.push(entry);\n save(entries);\n },\n generateId(): string {\n return globalThis.crypto.randomUUID();\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageCardStorageAdapter\n// Mirrors createFsCardStorageAdapter — KV-backed, cards keyed by cardId.\n// ============================================================================\n\nexport function createLocalStorageCardStorageAdapter(prefix: string): CardStorageAdapter {\n const json = createLocalStorageJsonStorage(prefix);\n\n return {\n readIndex(): CardIndex | null {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index: CardIndex): void {\n json.write('_index', index);\n },\n readCard(id: string): LiveCard | null {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id: string, card: LiveCard): string {\n json.write(id, card);\n return computeStableJsonHashBrowser(card);\n },\n cardExists(id: string): boolean {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId: string): string {\n return cardId;\n },\n };\n}\n","/**\n * board-live-cards-browser-adapter.ts\n *\n * Browser implementation of BoardPlatformAdapter.\n * Uses localStorage for all persistence.\n *\n * Constraints vs Node/FS adapter:\n * - lock: in-memory no-op (browser is single-threaded; no cross-tab locking)\n * - dispatchExecution: supports 'in-browser', 'http:post' and 'http:get'\n * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)\n * - selfRef: 'in-browser' kind — routes to registered in-memory handlers\n */\n\nimport type { KindValueRef, AtomicRelayLock } from '../common/storage-interface.js';\nimport { serializeRef, parseRef } from '../common/storage-interface.js';\nimport type { BoardPlatformAdapter } from '../common/board-live-cards-public.js';\nimport {\n createLocalStorageBlobStorage,\n createLocalStorageKvStorage,\n createLocalStorageJournalStorageAdapter,\n computeStableJsonHashBrowser,\n} from './storage-localstorage-adapters.js';\n\n// ============================================================================\n// In-memory no-op AtomicRelayLock\n// Browser is single-threaded; no concurrent actors within one tab.\n// ============================================================================\n\nfunction createInMemoryRelayLock(): AtomicRelayLock {\n let held = false;\n return {\n tryAcquire(): (() => void) | null {\n if (held) return null;\n held = true;\n return () => { held = false; };\n },\n };\n}\n\n// ============================================================================\n// In-memory notification bus (keyed by channel name)\n//\n// Same role as named-pipe in Node.js: the adapter publishes to a channel,\n// and a NotificationTransport subscribes on a matching KindValueRef.\n// Kind: \"in-memory-bus\" — e.g. { kind: 'in-memory-bus', value: 'my-board:notify' }\n// ============================================================================\n\ninterface InMemoryBus {\n publish(event: unknown): void;\n subscribe(onEvent: (event: unknown) => void): () => void;\n}\n\nconst _busRegistry = new Map<string, InMemoryBus>();\n\nexport function getInMemoryNotificationBus(channel: string): InMemoryBus {\n let bus = _busRegistry.get(channel);\n if (!bus) {\n const listeners = new Set<(event: unknown) => void>();\n bus = {\n publish(event) { for (const fn of listeners) fn(event); },\n subscribe(onEvent) {\n listeners.add(onEvent);\n return () => { listeners.delete(onEvent); };\n },\n };\n _busRegistry.set(channel, bus);\n }\n return bus;\n}\n\n/**\n * In-memory NotificationTransport for the browser.\n * Subscribes to the same in-memory bus that the adapter publishes to.\n * Use with notifyRef: { kind: 'in-memory-bus', value: '<channel>' }\n */\nexport function createInMemoryNotificationTransport(): import('../../server-runtime/types.js').NotificationTransport {\n return {\n async subscribe(ref, onEvent) {\n if (ref.kind !== 'in-memory-bus') {\n console.warn(`[in-memory-transport] unsupported kind: ${ref.kind}`);\n return () => {};\n }\n const bus = getInMemoryNotificationBus(ref.value);\n return bus.subscribe((event) => {\n const e = event as { kind?: string; notifications?: unknown[] };\n if (e && e.kind === 'notification-batch' && Array.isArray(e.notifications)) {\n for (const n of e.notifications) onEvent(n);\n return;\n }\n onEvent(event);\n });\n },\n };\n}\n\n// ============================================================================\n// createBrowserBoardPlatformAdapter\n//\n// namespace — logical name for this board instance (e.g. 'my-board').\n// Used as the localStorage key prefix so multiple boards can coexist.\n// opts.callbackBaseUrl — if set, used as selfRef.whatToRun for http callbacks.\n// e.g. 'https://my-app.example.com/api/board'\n// opts.notifyChannel — in-memory notification channel name.\n// The adapter publishes to this channel; pair with notifyRef { kind: 'in-memory-bus', value: channel }.\n// ============================================================================\n\nimport type { ExecutionRef } from '../common/execution-interface.js';\n\n/**\n * Registry of in-browser execution handlers keyed by whatToRun value.\n * Consumers register handlers that will be invoked when the drain cycle\n * dispatches execution with howToRun === 'in-browser'.\n */\nexport type InBrowserHandler = (ref: ExecutionRef, args: Record<string, unknown>) => Promise<{ dispatched: boolean; error?: string }>;\n\nexport function createBrowserBoardPlatformAdapter(\n namespace: string,\n opts?: {\n callbackBaseUrl?: string;\n notifyChannel?: string;\n onWarn?: (msg: string) => void;\n },\n): BoardPlatformAdapter & {\n registerHandler(name: string, handler: InBrowserHandler): void;\n writeMemoryBlob(key: string, data: string): string;\n} {\n const selfRef = opts?.callbackBaseUrl\n ? {\n meta: 'board-live-cards',\n howToRun: 'http:post' as const,\n whatToRun: opts.callbackBaseUrl,\n }\n : {\n meta: 'board-live-cards',\n howToRun: 'in-browser' as const,\n whatToRun: serializeRef({ kind: 'in-browser', value: namespace }),\n };\n\n // In-browser handler registry: maps whatToRun → handler function\n const handlerRegistry = new Map<string, InBrowserHandler>();\n\n // In-memory blob store: ephemeral key→value map for blob refs (kind: 'in-memory')\n const memoryBlobs = new Map<string, string>();\n\n const lock = createInMemoryRelayLock();\n\n return {\n kvStorage: (ns: string) =>\n createLocalStorageKvStorage(`${namespace}:${ns}`),\n\n blobStorage: (ns: string) =>\n createLocalStorageBlobStorage(ns ? `${namespace}:${ns}` : namespace),\n\n journalAdapter: () =>\n createLocalStorageJournalStorageAdapter(`${namespace}:journal`),\n\n lock,\n\n selfRef,\n\n async dispatchExecution(ref, args): Promise<{ dispatched: boolean; error?: string }> {\n if (ref.howToRun === 'http:post') {\n try {\n const url = ref.whatToRun.startsWith('b64:')\n ? parseRef(ref.whatToRun).value\n : ref.whatToRun;\n const resp = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(args),\n });\n if (!resp.ok) {\n return { dispatched: false, error: `HTTP ${resp.status}: ${resp.statusText}` };\n }\n return { dispatched: true };\n } catch (e) {\n return { dispatched: false, error: e instanceof Error ? e.message : String(e) };\n }\n }\n\n if (ref.howToRun === 'http:get') {\n try {\n const baseUrl = ref.whatToRun.startsWith('b64:')\n ? parseRef(ref.whatToRun).value\n : ref.whatToRun;\n const params = new URLSearchParams(\n Object.entries(args as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .map(([k, v]) => [k, String(v)]),\n );\n const url = `${baseUrl}?${params.toString()}`;\n const resp = await fetch(url);\n if (!resp.ok) {\n return { dispatched: false, error: `HTTP ${resp.status}: ${resp.statusText}` };\n }\n return { dispatched: true };\n } catch (e) {\n return { dispatched: false, error: e instanceof Error ? e.message : String(e) };\n }\n }\n\n if (ref.howToRun === 'in-browser') {\n const handler = handlerRegistry.get(ref.whatToRun);\n if (handler) return handler(ref, args);\n return { dispatched: false, error: `No in-browser handler registered for: ${ref.whatToRun}` };\n }\n\n return {\n dispatched: false,\n error: `Browser adapter: unsupported dispatch kind (got: ${ref.howToRun})`,\n };\n },\n\n resolveBlob(ref: KindValueRef): string {\n // In-memory blobs: written by task executors, ephemeral (page-lifetime)\n if (ref.kind === 'in-memory') {\n const content = memoryBlobs.get(ref.value);\n if (content === null || content === undefined) {\n throw new Error(`resolveBlob: in-memory blob not found: ${serializeRef(ref)}`);\n }\n return content;\n }\n // localStorage blobs: persistent across page reloads\n const storage = createLocalStorageBlobStorage(namespace);\n const content = storage.read(ref.value);\n if (content === null) {\n throw new Error(`resolveBlob: blob not found: ${serializeRef(ref)}`);\n }\n return content;\n },\n\n hashFn: computeStableJsonHashBrowser,\n\n genId: (): string => globalThis.crypto.randomUUID().replace(/-/g, ''),\n\n kvStorageForRef: (ref: string) => createLocalStorageKvStorage(parseRef(ref).value),\n\n publishBoardChangeNotifications(notifications) {\n if (!opts?.notifyChannel || notifications.length === 0) return;\n const bus = getInMemoryNotificationBus(opts.notifyChannel);\n bus.publish({ kind: 'notification-batch', notifications });\n },\n\n // requestProcessAccumulated is intentionally absent — the browser caller\n // drives drain cycles via polling or setInterval.\n\n onWarn: opts?.onWarn,\n\n registerHandler(name: string, handler: InBrowserHandler) {\n handlerRegistry.set(name, handler);\n },\n\n writeMemoryBlob(key: string, data: string): string {\n memoryBlobs.set(key, data);\n return serializeRef({ kind: 'in-memory', value: key });\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/cli/common/storage-interface.ts","../../../src/cli/browser-api/storage-localstorage-adapters.ts","../../../src/cli/browser-api/board-live-cards-browser-adapter.ts"],"names":["REF_PREFIX","toBase64Url","raw","utf8","buf","base64","binary","byte","fromBase64Url","input","bytes","serializeRef","ref","parseRef","s","parsed","candidate","stableJson","value","obj","k","fnv32a","str","seed","h","i","computeStableJsonHashBrowser","a","b","c","d","n","createLocalStorageBlobStorage","prefix","key","textEncoder","encodeBytes","bin","decodeBytes","encoded","out","content","envelope","prefix2","marker","size","createLocalStorageKvStorage","fullPrefix","result","lsKey","createLocalStorageJournalStorageAdapter","storageKey","load","save","entries","entry","createInMemoryRelayLock","held","_busRegistry","getInMemoryNotificationBus","channel","bus","listeners","event","fn","onEvent","createInMemoryNotificationTransport","e","createBrowserBoardPlatformAdapter","namespace","opts","selfRef","handlerRegistry","memoryBlobs","lock","ns","args","url","resp","baseUrl","params","v","handlerKey","handler","notifications","name","data"],"mappings":"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","file":"board-live-cards-browser-adapter.js","sourcesContent":["/**\n * storage-interface.ts\n *\n * Three minimal storage primitives that together cover all persistence needs\n * of the board-live-cards system. Any backend (Node fs, CosmosDB, Azure Blob,\n * browser localStorage, in-memory test double) implements these three interfaces.\n *\n * The pure-logic stores in board-live-cards-all-stores.ts depend only on these\n * interfaces — never on Node built-ins.\n *\n * Blob — raw string content at a logical, backend-neutral key\n * Journal — append-only log with cursor-based reads\n * KV — key-value store with list/delete\n *\n * Mapping to existing storage adapters:\n *\n * CardStorageAdapter\n * inventory (cardId → { blobRef, checksum, fileMetadata? }) → KV\n * card JSON files → Blob\n * source output files → Blob\n *\n * JournalStorageAdapter → Journal (board-journal.jsonl)\n *\n * ExecutionRequestStore → KV (keyed by journalId, via createFsKvStorage)\n *\n * StateSnapshotStorageAdapter\n * board-graph.json (packed single JSON, written atomically) → Blob\n * per-card sidecars (cards/<id>/runtime, fetched-sources-manifest) → KV\n */\n\n// ============================================================================\n// Blob — raw content at an opaque key\n//\n// The key is backend-specific (file path, blob name, storage key).\n// Text helpers are always available. Binary helpers are optional so existing\n// backends can adopt incrementally.\n// ============================================================================\n\nexport interface BlobStat {\n key: string;\n size: number;\n updatedAt?: string;\n contentType?: string;\n}\n\nexport interface BlobStorage {\n /** Returns raw content string, or null if the blob does not exist. */\n read(key: string): string | null;\n\n /** Write content at key. Implementations should be atomic (write-rename). */\n write(key: string, content: string): void;\n\n /** Returns true if a blob exists at key. */\n exists(key: string): boolean;\n\n /** Delete the blob at key. No-op if it does not exist. */\n remove(key: string): void;\n\n /** Optional binary read for file-like artifacts. */\n readBytes?(key: string): Uint8Array | null;\n\n /** Optional binary write for file-like artifacts. */\n writeBytes?(key: string, content: Uint8Array): void;\n\n /** Optional key listing by prefix. */\n listKeys?(prefix?: string): string[];\n\n /** Optional metadata lookup. */\n stat?(key: string): BlobStat | null;\n}\n\n// ============================================================================\n// KindValueRef — backend-neutral typed reference\n//\n// A ref describes WHERE content lives without carrying the bytes.\n// Serialized on the CLI wire as: b64:<base64url({\"kind\":\"...\",\"value\":\"...\"})>\n// kind = 'fs-path': value is an absolute file path\n// Additional kinds (e.g. 'cosmos') are added in public-storage-adapter.ts as new backends are supported.\n// ============================================================================\n\nexport interface KindValueRef {\n readonly kind: string;\n readonly value: string;\n}\n\nconst REF_PREFIX = 'b64:';\n\nfunction toBase64Url(raw: string): string {\n const utf8 = new TextEncoder().encode(raw);\n const buf = (globalThis as { Buffer?: { from(data: Uint8Array): { toString(enc: string): string } } }).Buffer;\n let base64: string;\n if (buf) {\n base64 = buf.from(utf8).toString('base64');\n } else if (typeof btoa === 'function') {\n let binary = '';\n for (const byte of utf8) binary += String.fromCharCode(byte);\n base64 = btoa(binary);\n } else {\n throw new Error('No base64 encoder available in this runtime');\n }\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nfunction fromBase64Url(input: string): string {\n const base64 = input.replace(/-/g, '+').replace(/_/g, '/')\n + '='.repeat((4 - (input.length % 4)) % 4);\n const buf = (globalThis as { Buffer?: { from(data: string, enc: string): { toString(enc: string): string } } }).Buffer;\n if (buf) return buf.from(base64, 'base64').toString('utf8');\n if (typeof atob === 'function') {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder().decode(bytes);\n }\n throw new Error('No base64 decoder available in this runtime');\n}\n\n/** Serialize a KindValueRef to the wire format: b64:<base64url(json)> */\nexport function serializeRef(ref: KindValueRef): string {\n return `${REF_PREFIX}${toBase64Url(JSON.stringify(ref))}`;\n}\n\n/** Parse a wire-format ref string (b64:<base64url(json)>) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith(REF_PREFIX)) throw new Error(`Invalid ref format (expected ${REF_PREFIX}<base64url(json)>): ${s}`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(fromBase64Url(s.slice(REF_PREFIX.length)));\n } catch {\n throw new Error(`Invalid ref format (malformed base64url/json): ${s}`);\n }\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid ref format (expected object payload): ${s}`);\n }\n const candidate = parsed as { kind?: unknown; value?: unknown };\n if (typeof candidate.kind !== 'string' || typeof candidate.value !== 'string') {\n throw new Error(`Invalid ref format (payload must contain string kind/value): ${s}`);\n }\n return { kind: candidate.kind, value: candidate.value };\n}\n\n// ============================================================================\n// Journal — append-only log, cursor-based reads\n//\n// Each entry has a string id (UUID or monotonic token) and an opaque payload.\n// Cursors are entry ids — readAfter returns entries strictly after that id.\n// A null/empty cursor means \"read from the beginning\".\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n payload: unknown;\n}\n\nexport interface JournalReadResult {\n entries: JournalEntry[];\n /** The id of the last entry returned, suitable for use as the next cursor. */\n newCursor: string | null;\n}\n\nexport interface JournalStorage {\n /** Append an entry. The storage layer assigns the id. */\n append(payload: unknown): JournalEntry;\n\n /** Read ALL entries (for index rebuilds, full replay). */\n readAll(): JournalEntry[];\n\n /**\n * Read entries appended after the given cursor id.\n * If cursor is null/empty, returns all entries from the beginning.\n */\n readAfter(cursor: string | null): JournalReadResult;\n}\n\n// ============================================================================\n// KV — key-value store with list and delete\n//\n// Values are opaque unknown — callers own serialisation.\n// Keys are scoped by the adapter factory (e.g. a boardDir prefix is closed\n// over in the adapter, not passed per-call).\n// ============================================================================\n\nexport interface KVStorage {\n /** Returns the stored value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /**\n * List all keys, optionally filtered to those starting with prefix.\n * Order is implementation-defined.\n */\n listKeys(prefix?: string): string[];\n}\n\n// ============================================================================\n// JSONStorage — KV store with JSON-aware merge operations\n//\n// Backed by KVStorage under the hood. Adds deepMerge and shallowMerge so\n// callers never need to read-modify-write manually for partial updates.\n// ============================================================================\n\nexport interface JSONStorage {\n /** Returns the stored JSON value, or null if the key does not exist. */\n read(key: string): unknown | null;\n\n /**\n * Read a nested value inside the stored object using a dot-notation path.\n * e.g. get('myKey', 'a.b.c') returns the value at { a: { b: { c: ... } } }.\n * Returns null if the key does not exist or the path cannot be traversed.\n */\n get(key: string, jsonPath: string): unknown | null;\n\n /** Write value at key. Overwrites any existing value. */\n write(key: string, value: unknown): void;\n\n /** Delete the key. No-op if it does not exist. */\n delete(key: string): void;\n\n /** List all keys, optionally filtered by prefix. */\n listKeys(prefix?: string): string[];\n\n /**\n * Shallow-merge patch into the existing object at key.\n * Equivalent to: write(key, { ...read(key), ...patch })\n * Creates the key if it does not exist.\n */\n shallowMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Deep-merge patch into the existing object at key.\n * Recursively merges nested plain objects; arrays and primitives are replaced.\n * Creates the key if it does not exist.\n */\n deepMerge(key: string, patch: Record<string, unknown>): void;\n\n /**\n * Set a nested value inside the stored object using a dot-notation path.\n * e.g. patch('myKey', 'a.b.c', 42) sets { a: { b: { c: 42 } } } into the stored object.\n * Intermediate objects are created if absent. Arrays are not traversed — use integer\n * segments to index into them (e.g. 'items.0.name').\n * Creates the top-level key if it does not exist.\n */\n patch(key: string, jsonPath: string, value: unknown): void;\n}\n\n// ============================================================================\n// StorageProvider — aggregate of all three primitives\n//\n// Adapter factories receive a StorageProvider and close over any scope (e.g.\n// boardDir) themselves. This is the single injection point for swapping\n// backends (Node fs → CosmosDB, browser localStorage, test doubles, etc.).\n// ============================================================================\n\nexport interface StorageProvider {\n blob: BlobStorage;\n journal: JournalStorage;\n kv: KVStorage;\n}\n\n// ============================================================================\n// AtomicRelayLock — non-blocking try-acquire lock with relay-on-busy semantics\n//\n// This interface serves TWO tightly coupled purposes which are intentionally\n// unified into a single primitive:\n//\n// 1. ATOMICITY — ensures that a read-mutate-save cycle is executed by at\n// most one actor at a time, preventing concurrent actors from racing on\n// stale state and writing conflicting snapshots.\n//\n// 2. RELAY SIGNAL — when tryAcquire() returns null, the caller knows the\n// cycle is already in progress. Because the holder always reads fresh\n// state upon entry, it will pick up every change appended by the skipping\n// caller before the lock was attempted. The caller can therefore safely\n// exit — its work will be completed by the holder. This is the\n// \"relay baton\" pattern: the lock being held IS the in-progress signal.\n//\n// These two purposes are not an accidental overload — they are the same\n// invariant expressed at different scopes. Any backend implementation\n// (FS lockfile, Cosmos document lease, Azure entity lock, in-memory flag)\n// that satisfies \"at most one holder at a time\" automatically satisfies both.\n//\n// Contract:\n// - tryAcquire() is non-blocking. It never waits.\n// - Returns a release function on success, or null if already held.\n// - The release function must be called exactly once (use try/finally).\n// - Behaviour after calling release() more than once is undefined.\n// ============================================================================\n\nexport interface AtomicRelayLock {\n /**\n * Attempt to acquire the lock without blocking.\n * Returns a `release` function if successful, or `null` if the lock is\n * already held by another actor (relay: that actor will complete the work).\n */\n tryAcquire(): (() => void) | null;\n}\n\n/**\n * Execute `work` under an `AtomicRelayLock`.\n *\n * - If the lock is busy, returns false immediately (relay: the holder will\n * complete the work on behalf of this caller).\n * - If acquired, runs `work` exclusively, releases the lock, then calls\n * `continuation` if provided — allowing the caller to schedule the next\n * cycle (e.g. spawn a detached process) after the lock is free.\n * - Returns true if work ran.\n */\nexport async function withRelayLock(\n lock: AtomicRelayLock,\n work: () => Promise<void>,\n continuation?: () => void,\n): Promise<boolean> {\n const release = lock.tryAcquire();\n if (!release) return false; // relay: holder is already doing the work\n try {\n await work();\n } finally {\n release(); // release before continuation so it can immediately re-acquire\n }\n continuation?.();\n return true;\n}\n","/**\n * storage-localstorage-adapters.ts\n *\n * Browser localStorage implementations of the board-live-cards storage primitives:\n * BlobStorage — localStorage keys prefixed with `${prefix}:blob:`\n * KVStorage — localStorage keys prefixed with `${prefix}:kv:`, values JSON-encoded\n * JournalStorageAdapter — single localStorage key holding a JSON array of entries\n * CardStorageAdapter — KV-backed, compatible with createCardStore()\n *\n * No Node imports. Requires globalThis.localStorage (browser / jsdom environment).\n */\n\nimport type { BlobStorage, KVStorage, JSONStorage } from '../common/storage-interface.js';\nimport type { JournalStorageAdapter, CardStorageAdapter, JournalEntry, LiveCard, CardIndex } from '../common/board-live-cards-lib.js';\n\n// ============================================================================\n// Stable JSON + sync hash\n// Used for card dedup and snapshot versioning. Not security-sensitive.\n// ============================================================================\n\nfunction stableJson(value: unknown): string {\n if (value === null || value === undefined || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${(value as unknown[]).map(stableJson).join(',')}]`;\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map(k => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(',')}}`;\n}\n\nfunction fnv32a(str: string, seed: number): number {\n let h = seed >>> 0;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h;\n}\n\n/**\n * Synchronous stable content hash for browser environments.\n * Uses four FNV-1a 32-bit passes to produce 32 hex chars.\n * Deterministic and cross-session stable; NOT cryptographically secure.\n */\nexport function computeStableJsonHashBrowser(value: unknown): string {\n const str = stableJson(value);\n const a = fnv32a(str, 0x811c9dc5);\n const b = fnv32a(str, 0xdeadbeef);\n const c = fnv32a(str, 0x01234567);\n const d = fnv32a(str, 0xfeedface);\n return [a, b, c, d].map(n => n.toString(16).padStart(8, '0')).join('');\n}\n\n// ============================================================================\n// createLocalStorageBlobStorage\n// ============================================================================\n\nexport function createLocalStorageBlobStorage(prefix: string): BlobStorage {\n function key(k: string): string { return `${prefix}:blob:${k}`; }\n const textEncoder = new TextEncoder();\n\n function encodeBytes(bytes: Uint8Array): string {\n if (typeof btoa === 'function') {\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n return btoa(bin);\n }\n return '';\n }\n\n function decodeBytes(encoded: string): Uint8Array {\n if (typeof atob === 'function') {\n const bin = atob(encoded);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n return new Uint8Array();\n }\n\n return {\n read(k: string): string | null {\n return globalThis.localStorage.getItem(key(k));\n },\n write(k: string, content: string): void {\n globalThis.localStorage.setItem(key(k), content);\n },\n exists(k: string): boolean {\n return globalThis.localStorage.getItem(key(k)) !== null;\n },\n remove(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n\n readBytes(k: string): Uint8Array | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n return decodeBytes(parsed.data);\n }\n } catch {\n // fall through to plain text path\n }\n return textEncoder.encode(raw);\n },\n\n writeBytes(k: string, content: Uint8Array): void {\n // Store binary payloads as base64 envelope to avoid lossy UTF-8 coercion.\n const envelope = JSON.stringify({ __kind: 'bytes-b64', data: encodeBytes(content) });\n globalThis.localStorage.setItem(key(k), envelope);\n },\n\n listKeys(prefix2?: string): string[] {\n const marker = key(prefix2 ?? '');\n const out: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const k = globalThis.localStorage.key(i);\n if (k && k.startsWith(marker)) out.push(k.slice(key('').length));\n }\n return out.sort();\n },\n\n stat(k: string) {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n let size = textEncoder.encode(raw).byteLength;\n try {\n const parsed = JSON.parse(raw) as { __kind?: string; data?: string };\n if (parsed && parsed.__kind === 'bytes-b64' && typeof parsed.data === 'string') {\n size = decodeBytes(parsed.data).byteLength;\n }\n } catch {\n // plain text path\n }\n return { key: k, size };\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageKvStorage\n// ============================================================================\n\nexport function createLocalStorageKvStorage(prefix: string): KVStorage {\n function key(k: string): string { return `${prefix}:kv:${k}`; }\n\n return {\n read(k: string): unknown | null {\n const raw = globalThis.localStorage.getItem(key(k));\n if (raw === null) return null;\n try { return JSON.parse(raw); } catch { return null; }\n },\n write(k: string, value: unknown): void {\n globalThis.localStorage.setItem(key(k), JSON.stringify(value));\n },\n delete(k: string): void {\n globalThis.localStorage.removeItem(key(k));\n },\n listKeys(prefix2?: string): string[] {\n const fullPrefix = key(prefix2 ?? '');\n const result: string[] = [];\n for (let i = 0; i < globalThis.localStorage.length; i++) {\n const lsKey = globalThis.localStorage.key(i);\n if (lsKey !== null && lsKey.startsWith(fullPrefix)) {\n // Strip the outer prefix + ':kv:' to return the logical key\n result.push(lsKey.slice(key('').length));\n }\n }\n return result;\n },\n };\n}\n\nfunction deepMergeObjects(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(patch)) {\n if (v !== null && typeof v === 'object' && !Array.isArray(v) &&\n result[k] !== null && typeof result[k] === 'object' && !Array.isArray(result[k])) {\n result[k] = deepMergeObjects(result[k] as Record<string, unknown>, v as Record<string, unknown>);\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n\nfunction applyJsonPath(obj: Record<string, unknown>, segments: string[], value: unknown): Record<string, unknown> {\n if (segments.length === 0) return obj;\n const [head, ...tail] = segments;\n if (tail.length === 0) return { ...obj, [head]: value };\n const nested = (obj[head] !== null && typeof obj[head] === 'object' && !Array.isArray(obj[head]))\n ? (obj[head] as Record<string, unknown>)\n : {};\n return { ...obj, [head]: applyJsonPath(nested, tail, value) };\n}\n\nexport function createLocalStorageJsonStorage(prefix: string): JSONStorage {\n const kv = createLocalStorageKvStorage(prefix);\n return {\n read: (key) => kv.read(key),\n get(key, jsonPath) {\n const obj = kv.read(key);\n if (obj === null) return null;\n let current: unknown = obj;\n for (const segment of jsonPath.split('.').filter(Boolean)) {\n if (current === null || typeof current !== 'object' || Array.isArray(current)) return null;\n current = (current as Record<string, unknown>)[segment] ?? null;\n }\n return current ?? null;\n },\n write: (key, value) => kv.write(key, value),\n delete: (key) => kv.delete(key),\n listKeys: (prefix2?) => kv.listKeys(prefix2),\n shallowMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, { ...existing, ...patch });\n },\n deepMerge(key, patch) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n kv.write(key, deepMergeObjects(existing, patch));\n },\n patch(key, jsonPath, value) {\n const existing = (kv.read(key) as Record<string, unknown> | null) ?? {};\n const segments = jsonPath.split('.').filter(Boolean);\n kv.write(key, applyJsonPath(existing, segments, value));\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageJournalStorageAdapter\n// All entries stored as a JSON array under a single localStorage key.\n// ============================================================================\n\nexport function createLocalStorageJournalStorageAdapter(storageKey: string): JournalStorageAdapter {\n function load(): JournalEntry[] {\n const raw = globalThis.localStorage.getItem(storageKey);\n if (!raw) return [];\n try { return JSON.parse(raw) as JournalEntry[]; } catch { return []; }\n }\n\n function save(entries: JournalEntry[]): void {\n globalThis.localStorage.setItem(storageKey, JSON.stringify(entries));\n }\n\n return {\n readAllEntries(): JournalEntry[] {\n return load();\n },\n appendEntry(entry: JournalEntry): void {\n const entries = load();\n entries.push(entry);\n save(entries);\n },\n generateId(): string {\n return globalThis.crypto.randomUUID();\n },\n };\n}\n\n// ============================================================================\n// createLocalStorageCardStorageAdapter\n// Mirrors createFsCardStorageAdapter — KV-backed, cards keyed by cardId.\n// ============================================================================\n\nexport function createLocalStorageCardStorageAdapter(prefix: string): CardStorageAdapter {\n const json = createLocalStorageJsonStorage(prefix);\n\n return {\n readIndex(): CardIndex | null {\n return json.read('_index') as CardIndex | null;\n },\n writeIndex(index: CardIndex): void {\n json.write('_index', index);\n },\n readCard(id: string): LiveCard | null {\n return json.read(id) as LiveCard | null;\n },\n writeCard(id: string, card: LiveCard): string {\n json.write(id, card);\n return computeStableJsonHashBrowser(card);\n },\n cardExists(id: string): boolean {\n return json.read(id) !== null;\n },\n defaultCardKey(cardId: string): string {\n return cardId;\n },\n };\n}\n","/**\n * board-live-cards-browser-adapter.ts\n *\n * Browser implementation of BoardPlatformAdapter.\n * Uses localStorage for all persistence.\n *\n * Constraints vs Node/FS adapter:\n * - lock: in-memory no-op (browser is single-threaded; no cross-tab locking)\n * - dispatchExecution: supports 'in-browser', 'http:post' and 'http:get'\n * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)\n * - selfRef: 'in-browser' kind — routes to registered in-memory handlers\n */\n\nimport type { KindValueRef, AtomicRelayLock } from '../common/storage-interface.js';\nimport { serializeRef, parseRef } from '../common/storage-interface.js';\nimport type { BoardPlatformAdapter } from '../common/board-live-cards-public.js';\nimport {\n createLocalStorageBlobStorage,\n createLocalStorageKvStorage,\n createLocalStorageJournalStorageAdapter,\n computeStableJsonHashBrowser,\n} from './storage-localstorage-adapters.js';\n\n// ============================================================================\n// In-memory no-op AtomicRelayLock\n// Browser is single-threaded; no concurrent actors within one tab.\n// ============================================================================\n\nfunction createInMemoryRelayLock(): AtomicRelayLock {\n let held = false;\n return {\n tryAcquire(): (() => void) | null {\n if (held) return null;\n held = true;\n return () => { held = false; };\n },\n };\n}\n\n// ============================================================================\n// In-memory notification bus (keyed by channel name)\n//\n// Same role as named-pipe in Node.js: the adapter publishes to a channel,\n// and a NotificationTransport subscribes on a matching KindValueRef.\n// Kind: \"in-memory-bus\" — e.g. { kind: 'in-memory-bus', value: 'my-board:notify' }\n// ============================================================================\n\ninterface InMemoryBus {\n publish(event: unknown): void;\n subscribe(onEvent: (event: unknown) => void): () => void;\n}\n\nconst _busRegistry = new Map<string, InMemoryBus>();\n\nexport function getInMemoryNotificationBus(channel: string): InMemoryBus {\n let bus = _busRegistry.get(channel);\n if (!bus) {\n const listeners = new Set<(event: unknown) => void>();\n bus = {\n publish(event) { for (const fn of listeners) fn(event); },\n subscribe(onEvent) {\n listeners.add(onEvent);\n return () => { listeners.delete(onEvent); };\n },\n };\n _busRegistry.set(channel, bus);\n }\n return bus;\n}\n\n/**\n * In-memory NotificationTransport for the browser.\n * Subscribes to the same in-memory bus that the adapter publishes to.\n * Use with notifyRef: { kind: 'in-memory-bus', value: '<channel>' }\n */\nexport function createInMemoryNotificationTransport(): import('../../server-runtime/types.js').NotificationTransport {\n return {\n async subscribe(ref, onEvent) {\n if (ref.kind !== 'in-memory-bus') {\n console.warn(`[in-memory-transport] unsupported kind: ${ref.kind}`);\n return () => {};\n }\n const bus = getInMemoryNotificationBus(ref.value);\n return bus.subscribe((event) => {\n const e = event as { kind?: string; notifications?: unknown[] };\n if (e && e.kind === 'notification-batch' && Array.isArray(e.notifications)) {\n for (const n of e.notifications) onEvent(n);\n return;\n }\n onEvent(event);\n });\n },\n };\n}\n\n// ============================================================================\n// createBrowserBoardPlatformAdapter\n//\n// namespace — logical name for this board instance (e.g. 'my-board').\n// Used as the localStorage key prefix so multiple boards can coexist.\n// opts.callbackBaseUrl — if set, used as selfRef.whatToRun for http callbacks.\n// e.g. 'https://my-app.example.com/api/board'\n// opts.notifyChannel — in-memory notification channel name.\n// The adapter publishes to this channel; pair with notifyRef { kind: 'in-memory-bus', value: channel }.\n// ============================================================================\n\nimport type { ExecutionRef } from '../common/execution-interface.js';\n\n/**\n * Registry of in-browser execution handlers keyed by whatToRun value.\n * Consumers register handlers that will be invoked when the drain cycle\n * dispatches execution with howToRun === 'in-browser'.\n */\nexport type InBrowserHandler = (ref: ExecutionRef, args: Record<string, unknown>) => Promise<{ dispatched: boolean; error?: string }>;\n\nexport function createBrowserBoardPlatformAdapter(\n namespace: string,\n opts?: {\n callbackBaseUrl?: string;\n notifyChannel?: string;\n onWarn?: (msg: string) => void;\n },\n): BoardPlatformAdapter & {\n registerHandler(name: string, handler: InBrowserHandler): void;\n writeMemoryBlob(key: string, data: string): string;\n} {\n const selfRef = opts?.callbackBaseUrl\n ? {\n meta: 'board-live-cards',\n howToRun: 'http:post' as const,\n whatToRun: opts.callbackBaseUrl,\n }\n : {\n meta: 'board-live-cards',\n howToRun: 'in-browser' as const,\n whatToRun: serializeRef({ kind: 'in-browser', value: namespace }),\n };\n\n // In-browser handler registry: maps whatToRun → handler function\n const handlerRegistry = new Map<string, InBrowserHandler>();\n\n // In-memory blob store: ephemeral key→value map for blob refs (kind: 'in-memory')\n const memoryBlobs = new Map<string, string>();\n\n const lock = createInMemoryRelayLock();\n\n return {\n kvStorage: (ns: string) =>\n createLocalStorageKvStorage(`${namespace}:${ns}`),\n\n blobStorage: (ns: string) =>\n createLocalStorageBlobStorage(ns ? `${namespace}:${ns}` : namespace),\n\n journalAdapter: () =>\n createLocalStorageJournalStorageAdapter(`${namespace}:journal`),\n\n lock,\n\n selfRef,\n\n async dispatchExecution(ref, args): Promise<{ dispatched: boolean; error?: string }> {\n if (ref.howToRun === 'http:post') {\n try {\n const raw = ref.whatToRun;\n const url = typeof raw === 'object' ? raw.value : parseRef(raw).value;\n const resp = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(args),\n });\n if (!resp.ok) {\n return { dispatched: false, error: `HTTP ${resp.status}: ${resp.statusText}` };\n }\n return { dispatched: true };\n } catch (e) {\n return { dispatched: false, error: e instanceof Error ? e.message : String(e) };\n }\n }\n\n if (ref.howToRun === 'http:get') {\n try {\n const raw = ref.whatToRun;\n const baseUrl = typeof raw === 'object' ? raw.value : parseRef(raw).value;\n const params = new URLSearchParams(\n Object.entries(args as Record<string, unknown>)\n .filter(([, v]) => v !== undefined && v !== null)\n .map(([k, v]) => [k, String(v)]),\n );\n const url = `${baseUrl}?${params.toString()}`;\n const resp = await fetch(url);\n if (!resp.ok) {\n return { dispatched: false, error: `HTTP ${resp.status}: ${resp.statusText}` };\n }\n return { dispatched: true };\n } catch (e) {\n return { dispatched: false, error: e instanceof Error ? e.message : String(e) };\n }\n }\n\n if (ref.howToRun === 'in-browser') {\n const raw = ref.whatToRun;\n const handlerKey = typeof raw === 'object' ? raw.value : parseRef(raw).value;\n const handler = handlerRegistry.get(handlerKey);\n if (handler) return handler(ref, args);\n return { dispatched: false, error: `No in-browser handler registered for: ${handlerKey}` };\n }\n\n return {\n dispatched: false,\n error: `Browser adapter: unsupported dispatch kind (got: ${ref.howToRun})`,\n };\n },\n\n resolveBlob(ref: KindValueRef): string {\n // In-memory blobs: written by task executors, ephemeral (page-lifetime)\n if (ref.kind === 'in-memory') {\n const content = memoryBlobs.get(ref.value);\n if (content === null || content === undefined) {\n throw new Error(`resolveBlob: in-memory blob not found: ${serializeRef(ref)}`);\n }\n return content;\n }\n // localStorage blobs: persistent across page reloads\n const storage = createLocalStorageBlobStorage(namespace);\n const content = storage.read(ref.value);\n if (content === null) {\n throw new Error(`resolveBlob: blob not found: ${serializeRef(ref)}`);\n }\n return content;\n },\n\n hashFn: computeStableJsonHashBrowser,\n\n genId: (): string => globalThis.crypto.randomUUID().replace(/-/g, ''),\n\n kvStorageForRef: (ref: string) => createLocalStorageKvStorage(parseRef(ref).value),\n\n publishBoardChangeNotifications(notifications) {\n if (!opts?.notifyChannel || notifications.length === 0) return;\n const bus = getInMemoryNotificationBus(opts.notifyChannel);\n bus.publish({ kind: 'notification-batch', notifications });\n },\n\n // requestProcessAccumulated is intentionally absent — the browser caller\n // drives drain cycles via polling or setInterval.\n\n onWarn: opts?.onWarn,\n\n registerHandler(name: string, handler: InBrowserHandler) {\n handlerRegistry.set(name, handler);\n },\n\n writeMemoryBlob(key: string, data: string): string {\n memoryBlobs.set(key, data);\n return serializeRef({ kind: 'in-memory', value: key });\n },\n };\n}\n"]}
|