yaml-flow 5.4.0 → 6.0.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.
Files changed (200) hide show
  1. package/board-live-cards-cli.js +2 -2
  2. package/board-livecards-server-runtime.js +488 -551
  3. package/browser/asset-integrity.json +10 -0
  4. package/browser/board-livecards-runtime-client.js +0 -6
  5. package/browser/board-livegraph-engine.js +2 -1676
  6. package/browser/board-livegraph-engine.js.map +1 -1
  7. package/browser/live-cards.js +347 -26
  8. package/browser/live-cards.schema.json +418 -132
  9. package/card-store.js +37 -0
  10. package/dist/batch/index.cjs +1 -108
  11. package/dist/batch/index.cjs.map +1 -1
  12. package/dist/batch/index.js +1 -106
  13. package/dist/batch/index.js.map +1 -1
  14. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  15. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  16. package/dist/board-live-cards-public-CltXYgaY.d.cts +314 -0
  17. package/dist/board-live-cards-public-f-E-FAyp.d.ts +314 -0
  18. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  19. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  20. package/dist/board-livegraph-runtime/index.d.cts +1 -2
  21. package/dist/board-livegraph-runtime/index.d.ts +1 -2
  22. package/dist/board-livegraph-runtime/index.js +2 -1662
  23. package/dist/board-livegraph-runtime/index.js.map +1 -1
  24. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7587 -0
  25. package/dist/card-compute/index.cjs +9 -7159
  26. package/dist/card-compute/index.cjs.map +1 -1
  27. package/dist/card-compute/index.d.cts +22 -0
  28. package/dist/card-compute/index.d.ts +22 -0
  29. package/dist/card-compute/index.js +9 -7145
  30. package/dist/card-compute/index.js.map +1 -1
  31. package/dist/card-compute/jsonata-sync.cjs +7587 -0
  32. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -0
  33. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  34. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +24 -0
  35. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +24 -0
  36. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  38. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  39. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  40. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  41. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  42. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  43. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  44. package/dist/cli/browser-api/jsonata-sync.cjs +7587 -0
  45. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  46. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  47. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  48. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  49. package/dist/cli/node/artifacts-store-cli.js +11 -0
  50. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  51. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  52. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  53. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  54. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  55. package/dist/cli/node/board-live-cards-cli.js +15 -0
  56. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  57. package/dist/cli/node/card-store-cli.cjs +8 -0
  58. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  59. package/dist/cli/node/card-store-cli.d.cts +15 -0
  60. package/dist/cli/node/card-store-cli.d.ts +15 -0
  61. package/dist/cli/node/card-store-cli.js +8 -0
  62. package/dist/cli/node/card-store-cli.js.map +1 -0
  63. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  64. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  65. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  66. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  67. package/dist/cli/node/fs-board-adapter.js +14 -0
  68. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  69. package/dist/cli/node/jsonata-sync.cjs +7587 -0
  70. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  71. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  72. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  73. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  74. package/dist/cli/node/source-cli-task-executor.js +11 -0
  75. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  76. package/dist/config/index.cjs +1 -79
  77. package/dist/config/index.cjs.map +1 -1
  78. package/dist/config/index.js +1 -76
  79. package/dist/config/index.js.map +1 -1
  80. package/dist/continuous-event-graph/index.cjs +2 -2129
  81. package/dist/continuous-event-graph/index.cjs.map +1 -1
  82. package/dist/continuous-event-graph/index.d.cts +81 -5
  83. package/dist/continuous-event-graph/index.d.ts +81 -5
  84. package/dist/continuous-event-graph/index.js +2 -2088
  85. package/dist/continuous-event-graph/index.js.map +1 -1
  86. package/dist/continuous-event-graph/jsonata-sync.cjs +7587 -0
  87. package/dist/event-graph/index.cjs +22 -8292
  88. package/dist/event-graph/index.cjs.map +1 -1
  89. package/dist/event-graph/index.js +22 -8237
  90. package/dist/event-graph/index.js.map +1 -1
  91. package/dist/execution-refs.cjs +2 -0
  92. package/dist/execution-refs.cjs.map +1 -0
  93. package/dist/execution-refs.d.cts +222 -0
  94. package/dist/execution-refs.d.ts +222 -0
  95. package/dist/execution-refs.js +2 -0
  96. package/dist/execution-refs.js.map +1 -0
  97. package/dist/index.cjs +29 -13221
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.d.cts +2 -4
  100. package/dist/index.d.ts +2 -4
  101. package/dist/index.js +29 -13112
  102. package/dist/index.js.map +1 -1
  103. package/dist/inference/index.cjs +5 -617
  104. package/dist/inference/index.cjs.map +1 -1
  105. package/dist/inference/index.js +5 -610
  106. package/dist/inference/index.js.map +1 -1
  107. package/dist/jsonata-sync.cjs +7587 -0
  108. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  109. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  110. package/dist/pycli/quickjs-board-runtime.global.js +9 -0
  111. package/dist/pycli/quickjs-board-runtime.global.js.map +1 -0
  112. package/dist/pycli/quickjs-step-machine-runtime.global.js +5 -0
  113. package/dist/pycli/quickjs-step-machine-runtime.global.js.map +1 -0
  114. package/dist/step-machine/index.cjs +11 -7129
  115. package/dist/step-machine/index.cjs.map +1 -1
  116. package/dist/step-machine/index.js +11 -7113
  117. package/dist/step-machine/index.js.map +1 -1
  118. package/dist/storage-refs.cjs +10 -0
  119. package/dist/storage-refs.cjs.map +1 -0
  120. package/dist/storage-refs.d.cts +92 -0
  121. package/dist/storage-refs.d.ts +92 -0
  122. package/dist/storage-refs.js +10 -0
  123. package/dist/storage-refs.js.map +1 -0
  124. package/dist/stores/file.cjs +1 -114
  125. package/dist/stores/file.cjs.map +1 -1
  126. package/dist/stores/file.js +1 -112
  127. package/dist/stores/file.js.map +1 -1
  128. package/dist/stores/index.cjs +1 -231
  129. package/dist/stores/index.cjs.map +1 -1
  130. package/dist/stores/index.js +1 -227
  131. package/dist/stores/index.js.map +1 -1
  132. package/dist/stores/localStorage.cjs +1 -76
  133. package/dist/stores/localStorage.cjs.map +1 -1
  134. package/dist/stores/localStorage.js +1 -74
  135. package/dist/stores/localStorage.js.map +1 -1
  136. package/dist/stores/memory.cjs +1 -47
  137. package/dist/stores/memory.cjs.map +1 -1
  138. package/dist/stores/memory.js +1 -45
  139. package/dist/stores/memory.js.map +1 -1
  140. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +292 -0
  141. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  142. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  143. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  144. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +553 -0
  145. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +365 -0
  146. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  147. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  148. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +53 -1
  149. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +15 -6
  150. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -1
  151. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +57 -0
  152. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +1 -1
  153. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +1 -1
  154. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +7 -2
  155. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +6 -2
  156. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +97 -0
  157. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +50 -0
  158. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +44 -0
  159. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +70 -0
  160. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  161. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  162. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  163. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  164. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +25 -172
  165. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  166. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +46 -0
  167. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  168. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  169. package/examples/example-board/agent-instructions.md +11 -5
  170. package/examples/example-board/demo-chat-handler.js +14 -4
  171. package/examples/example-board/demo-server-config.json +1 -0
  172. package/examples/example-board/demo-server.js +19 -34
  173. package/examples/example-board/demo-shell-browser.html +5 -4
  174. package/examples/example-board/demo-shell-with-server.html +10 -6
  175. package/examples/example-board/demo-task-executor.js +81 -35
  176. package/examples/index.html +0 -14
  177. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  178. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  179. package/package.json +39 -3
  180. package/schema/live-cards.schema.json +418 -132
  181. package/dist/cli/board-live-cards-cli.cjs +0 -10644
  182. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  183. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  184. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  185. package/dist/cli/board-live-cards-cli.js +0 -10592
  186. package/dist/cli/board-live-cards-cli.js.map +0 -1
  187. package/dist/journal-9HEgs7dU.d.ts +0 -28
  188. package/dist/journal-B-JCfQnh.d.cts +0 -28
  189. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  190. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  191. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  192. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  193. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  194. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  195. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  196. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  197. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  198. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  199. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  200. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
@@ -0,0 +1,2 @@
1
+ 'use strict';function h(r){return `::${r.kind}::${r.value}`}function g(r){if(!r.startsWith("::"))throw new Error(`Invalid ref format (expected ::kind::value): ${r}`);let e=r.slice(2),a=e.indexOf("::");if(a===-1)throw new Error(`Invalid ref format (expected ::kind::value): ${r}`);return {kind:e.slice(0,a),value:e.slice(a+2)}}function f(r){if(r==null||typeof r!="object")return JSON.stringify(r);if(Array.isArray(r))return `[${r.map(f).join(",")}]`;let e=r;return `{${Object.keys(e).sort().map(i=>`${JSON.stringify(i)}:${f(e[i])}`).join(",")}}`}function d(r,e){let a=e>>>0;for(let i=0;i<r.length;i++)a^=r.charCodeAt(i),a=Math.imul(a,16777619)>>>0;return a}function w(r){let e=f(r),a=d(e,2166136261),i=d(e,3735928559),o=d(e,19088743),n=d(e,4277009102);return [a,i,o,n].map(t=>t.toString(16).padStart(8,"0")).join("")}function y(r){function e(n){return `${r}:blob:${n}`}let a=new TextEncoder;function i(n){if(typeof btoa=="function"){let t="";for(let s=0;s<n.length;s++)t+=String.fromCharCode(n[s]);return btoa(t)}return ""}function o(n){if(typeof atob=="function"){let t=atob(n),s=new Uint8Array(t.length);for(let l=0;l<t.length;l++)s[l]=t.charCodeAt(l);return s}return new Uint8Array}return {read(n){return globalThis.localStorage.getItem(e(n))},write(n,t){globalThis.localStorage.setItem(e(n),t);},exists(n){return globalThis.localStorage.getItem(e(n))!==null},remove(n){globalThis.localStorage.removeItem(e(n));},readBytes(n){let t=globalThis.localStorage.getItem(e(n));if(t===null)return null;try{let s=JSON.parse(t);if(s&&s.__kind==="bytes-b64"&&typeof s.data=="string")return o(s.data)}catch{}return a.encode(t)},writeBytes(n,t){let s=JSON.stringify({__kind:"bytes-b64",data:i(t)});globalThis.localStorage.setItem(e(n),s);},listKeys(n){let t=e(n??""),s=[];for(let l=0;l<globalThis.localStorage.length;l++){let u=globalThis.localStorage.key(l);u&&u.startsWith(t)&&s.push(u.slice(e("").length));}return s.sort()},stat(n){let t=globalThis.localStorage.getItem(e(n));if(t===null)return null;let s=a.encode(t).byteLength;try{let l=JSON.parse(t);l&&l.__kind==="bytes-b64"&&typeof l.data=="string"&&(s=o(l.data).byteLength);}catch{}return {key:n,size:s}}}}function p(r){function e(a){return `${r}:kv:${a}`}return {read(a){let i=globalThis.localStorage.getItem(e(a));if(i===null)return null;try{return JSON.parse(i)}catch{return null}},write(a,i){globalThis.localStorage.setItem(e(a),JSON.stringify(i));},delete(a){globalThis.localStorage.removeItem(e(a));},listKeys(a){let i=e(a??""),o=[];for(let n=0;n<globalThis.localStorage.length;n++){let t=globalThis.localStorage.key(n);t!==null&&t.startsWith(i)&&o.push(t.slice(e("").length));}return o}}}function k(r){function e(){let i=globalThis.localStorage.getItem(r);if(!i)return [];try{return JSON.parse(i)}catch{return []}}function a(i){globalThis.localStorage.setItem(r,JSON.stringify(i));}return {readAllEntries(){return e()},appendEntry(i){let o=e();o.push(i),a(o);},generateId(){return globalThis.crypto.randomUUID()}}}function S(){let r=false;return {tryAcquire(){return r?null:(r=true,()=>{r=false;})}}}function A(r,e){let a=e?.callbackBaseUrl?{meta:"board-live-cards",howToRun:"http:post",whatToRun:e.callbackBaseUrl}:{meta:"board-live-cards",howToRun:"built-in",whatToRun:"::built-in::board-live-cards-browser"},i=S();return {kvStorage:o=>p(`${r}:${o}`),blobStorage:o=>y(o?`${r}:${o}`:r),journalAdapter:()=>k(`${r}:journal`),lock:i,selfRef:a,async dispatchExecution(o,n){if(o.howToRun==="http:post")try{let t=o.whatToRun.startsWith("::")?g(o.whatToRun).value:o.whatToRun,s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return s.ok?{dispatched:!0}:{dispatched:!1,error:`HTTP ${s.status}: ${s.statusText}`}}catch(t){return {dispatched:false,error:t instanceof Error?t.message:String(t)}}if(o.howToRun==="http:get")try{let t=o.whatToRun.startsWith("::")?g(o.whatToRun).value:o.whatToRun,s=new URLSearchParams(Object.entries(n).filter(([,c])=>c!=null).map(([c,b])=>[c,String(b)])),l=`${t}?${s.toString()}`,u=await fetch(l);return u.ok?{dispatched:!0}:{dispatched:!1,error:`HTTP ${u.status}: ${u.statusText}`}}catch(t){return {dispatched:false,error:t instanceof Error?t.message:String(t)}}return {dispatched:false,error:`Browser adapter: only http:post and http:get dispatch are supported (got: ${o.howToRun})`}},resolveBlob(o){let t=y(r).read(o.value);if(t===null)throw new Error(`resolveBlob: blob not found: ${h(o)}`);return t},hashFn:w,genId:()=>globalThis.crypto.randomUUID().replace(/-/g,""),kvStorageForRef:o=>p(g(o).value),onWarn:e?.onWarn}}exports.createBrowserBoardPlatformAdapter=A;//# sourceMappingURL=board-live-cards-browser-adapter.cjs.map
2
+ //# sourceMappingURL=board-live-cards-browser-adapter.cjs.map
@@ -0,0 +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":["serializeRef","ref","parseRef","s","inner","idx","stableJson","value","obj","k","fnv32a","str","seed","h","computeStableJsonHashBrowser","b","c","d","n","createLocalStorageBlobStorage","prefix","key","textEncoder","encodeBytes","bytes","bin","i","decodeBytes","encoded","out","content","raw","parsed","envelope","prefix2","marker","size","createLocalStorageKvStorage","fullPrefix","result","lsKey","createLocalStorageJournalStorageAdapter","storageKey","load","save","entries","entry","createInMemoryRelayLock","held","createBrowserBoardPlatformAdapter","namespace","opts","selfRef","lock","ns","args","url","resp","e","baseUrl","params","v"],"mappings":"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","file":"board-live-cards-browser-adapter.cjs","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: ::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\n/** Serialize a KindValueRef to the wire format: ::kind::value */\nexport function serializeRef(ref: KindValueRef): string {\n return `::${ref.kind}::${ref.value}`;\n}\n\n/** Parse a wire-format ref string (::kind::value) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith('::')) throw new Error(`Invalid ref format (expected ::kind::value): ${s}`);\n const inner = s.slice(2);\n const idx = inner.indexOf('::');\n if (idx === -1) throw new Error(`Invalid ref format (expected ::kind::value): ${s}`);\n return { kind: inner.slice(0, idx), value: inner.slice(idx + 2) };\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 'http:post' and 'http:get' only\n * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)\n * - selfRef: 'built-in' kind — no spawnable CLI available\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// 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// ============================================================================\n\nexport function createBrowserBoardPlatformAdapter(\n namespace: string,\n opts?: {\n callbackBaseUrl?: string;\n onWarn?: (msg: string) => void;\n },\n): BoardPlatformAdapter {\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: 'built-in' as const,\n whatToRun: '::built-in::board-live-cards-browser',\n };\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('::')\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('::')\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 return {\n dispatched: false,\n error: `Browser adapter: only http:post and http:get dispatch are supported (got: ${ref.howToRun})`,\n };\n },\n\n resolveBlob(ref: KindValueRef): string {\n // In the browser, blobs are stored in localStorage under the board namespace.\n // The ref value is treated as a logical localStorage key.\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 // requestProcessAccumulated is intentionally absent — the browser caller\n // drives drain cycles via polling or setInterval.\n\n onWarn: opts?.onWarn,\n };\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { B as BoardPlatformAdapter } from '../../board-live-cards-public-CltXYgaY.cjs';
2
+ import '../../execution-refs.cjs';
3
+ import '../../board-live-cards-lib-Bg6EvCo5.cjs';
4
+ import '../../types-BBhqYGhE.cjs';
5
+
6
+ /**
7
+ * board-live-cards-browser-adapter.ts
8
+ *
9
+ * Browser implementation of BoardPlatformAdapter.
10
+ * Uses localStorage for all persistence.
11
+ *
12
+ * Constraints vs Node/FS adapter:
13
+ * - lock: in-memory no-op (browser is single-threaded; no cross-tab locking)
14
+ * - dispatchExecution: supports 'http:post' and 'http:get' only
15
+ * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)
16
+ * - selfRef: 'built-in' kind — no spawnable CLI available
17
+ */
18
+
19
+ declare function createBrowserBoardPlatformAdapter(namespace: string, opts?: {
20
+ callbackBaseUrl?: string;
21
+ onWarn?: (msg: string) => void;
22
+ }): BoardPlatformAdapter;
23
+
24
+ export { createBrowserBoardPlatformAdapter };
@@ -0,0 +1,24 @@
1
+ import { B as BoardPlatformAdapter } from '../../board-live-cards-public-f-E-FAyp.js';
2
+ import '../../execution-refs.js';
3
+ import '../../board-live-cards-lib-jM2uYG1v.js';
4
+ import '../../types-BBhqYGhE.js';
5
+
6
+ /**
7
+ * board-live-cards-browser-adapter.ts
8
+ *
9
+ * Browser implementation of BoardPlatformAdapter.
10
+ * Uses localStorage for all persistence.
11
+ *
12
+ * Constraints vs Node/FS adapter:
13
+ * - lock: in-memory no-op (browser is single-threaded; no cross-tab locking)
14
+ * - dispatchExecution: supports 'http:post' and 'http:get' only
15
+ * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)
16
+ * - selfRef: 'built-in' kind — no spawnable CLI available
17
+ */
18
+
19
+ declare function createBrowserBoardPlatformAdapter(namespace: string, opts?: {
20
+ callbackBaseUrl?: string;
21
+ onWarn?: (msg: string) => void;
22
+ }): BoardPlatformAdapter;
23
+
24
+ export { createBrowserBoardPlatformAdapter };
@@ -0,0 +1,2 @@
1
+ function h(r){return `::${r.kind}::${r.value}`}function g(r){if(!r.startsWith("::"))throw new Error(`Invalid ref format (expected ::kind::value): ${r}`);let e=r.slice(2),a=e.indexOf("::");if(a===-1)throw new Error(`Invalid ref format (expected ::kind::value): ${r}`);return {kind:e.slice(0,a),value:e.slice(a+2)}}function f(r){if(r==null||typeof r!="object")return JSON.stringify(r);if(Array.isArray(r))return `[${r.map(f).join(",")}]`;let e=r;return `{${Object.keys(e).sort().map(i=>`${JSON.stringify(i)}:${f(e[i])}`).join(",")}}`}function d(r,e){let a=e>>>0;for(let i=0;i<r.length;i++)a^=r.charCodeAt(i),a=Math.imul(a,16777619)>>>0;return a}function w(r){let e=f(r),a=d(e,2166136261),i=d(e,3735928559),o=d(e,19088743),n=d(e,4277009102);return [a,i,o,n].map(t=>t.toString(16).padStart(8,"0")).join("")}function y(r){function e(n){return `${r}:blob:${n}`}let a=new TextEncoder;function i(n){if(typeof btoa=="function"){let t="";for(let s=0;s<n.length;s++)t+=String.fromCharCode(n[s]);return btoa(t)}return ""}function o(n){if(typeof atob=="function"){let t=atob(n),s=new Uint8Array(t.length);for(let l=0;l<t.length;l++)s[l]=t.charCodeAt(l);return s}return new Uint8Array}return {read(n){return globalThis.localStorage.getItem(e(n))},write(n,t){globalThis.localStorage.setItem(e(n),t);},exists(n){return globalThis.localStorage.getItem(e(n))!==null},remove(n){globalThis.localStorage.removeItem(e(n));},readBytes(n){let t=globalThis.localStorage.getItem(e(n));if(t===null)return null;try{let s=JSON.parse(t);if(s&&s.__kind==="bytes-b64"&&typeof s.data=="string")return o(s.data)}catch{}return a.encode(t)},writeBytes(n,t){let s=JSON.stringify({__kind:"bytes-b64",data:i(t)});globalThis.localStorage.setItem(e(n),s);},listKeys(n){let t=e(n??""),s=[];for(let l=0;l<globalThis.localStorage.length;l++){let u=globalThis.localStorage.key(l);u&&u.startsWith(t)&&s.push(u.slice(e("").length));}return s.sort()},stat(n){let t=globalThis.localStorage.getItem(e(n));if(t===null)return null;let s=a.encode(t).byteLength;try{let l=JSON.parse(t);l&&l.__kind==="bytes-b64"&&typeof l.data=="string"&&(s=o(l.data).byteLength);}catch{}return {key:n,size:s}}}}function p(r){function e(a){return `${r}:kv:${a}`}return {read(a){let i=globalThis.localStorage.getItem(e(a));if(i===null)return null;try{return JSON.parse(i)}catch{return null}},write(a,i){globalThis.localStorage.setItem(e(a),JSON.stringify(i));},delete(a){globalThis.localStorage.removeItem(e(a));},listKeys(a){let i=e(a??""),o=[];for(let n=0;n<globalThis.localStorage.length;n++){let t=globalThis.localStorage.key(n);t!==null&&t.startsWith(i)&&o.push(t.slice(e("").length));}return o}}}function k(r){function e(){let i=globalThis.localStorage.getItem(r);if(!i)return [];try{return JSON.parse(i)}catch{return []}}function a(i){globalThis.localStorage.setItem(r,JSON.stringify(i));}return {readAllEntries(){return e()},appendEntry(i){let o=e();o.push(i),a(o);},generateId(){return globalThis.crypto.randomUUID()}}}function S(){let r=false;return {tryAcquire(){return r?null:(r=true,()=>{r=false;})}}}function A(r,e){let a=e?.callbackBaseUrl?{meta:"board-live-cards",howToRun:"http:post",whatToRun:e.callbackBaseUrl}:{meta:"board-live-cards",howToRun:"built-in",whatToRun:"::built-in::board-live-cards-browser"},i=S();return {kvStorage:o=>p(`${r}:${o}`),blobStorage:o=>y(o?`${r}:${o}`:r),journalAdapter:()=>k(`${r}:journal`),lock:i,selfRef:a,async dispatchExecution(o,n){if(o.howToRun==="http:post")try{let t=o.whatToRun.startsWith("::")?g(o.whatToRun).value:o.whatToRun,s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return s.ok?{dispatched:!0}:{dispatched:!1,error:`HTTP ${s.status}: ${s.statusText}`}}catch(t){return {dispatched:false,error:t instanceof Error?t.message:String(t)}}if(o.howToRun==="http:get")try{let t=o.whatToRun.startsWith("::")?g(o.whatToRun).value:o.whatToRun,s=new URLSearchParams(Object.entries(n).filter(([,c])=>c!=null).map(([c,b])=>[c,String(b)])),l=`${t}?${s.toString()}`,u=await fetch(l);return u.ok?{dispatched:!0}:{dispatched:!1,error:`HTTP ${u.status}: ${u.statusText}`}}catch(t){return {dispatched:false,error:t instanceof Error?t.message:String(t)}}return {dispatched:false,error:`Browser adapter: only http:post and http:get dispatch are supported (got: ${o.howToRun})`}},resolveBlob(o){let t=y(r).read(o.value);if(t===null)throw new Error(`resolveBlob: blob not found: ${h(o)}`);return t},hashFn:w,genId:()=>globalThis.crypto.randomUUID().replace(/-/g,""),kvStorageForRef:o=>p(g(o).value),onWarn:e?.onWarn}}export{A as createBrowserBoardPlatformAdapter};//# sourceMappingURL=board-live-cards-browser-adapter.js.map
2
+ //# sourceMappingURL=board-live-cards-browser-adapter.js.map
@@ -0,0 +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":["serializeRef","ref","parseRef","s","inner","idx","stableJson","value","obj","k","fnv32a","str","seed","h","computeStableJsonHashBrowser","b","c","d","n","createLocalStorageBlobStorage","prefix","key","textEncoder","encodeBytes","bytes","bin","i","decodeBytes","encoded","out","content","raw","parsed","envelope","prefix2","marker","size","createLocalStorageKvStorage","fullPrefix","result","lsKey","createLocalStorageJournalStorageAdapter","storageKey","load","save","entries","entry","createInMemoryRelayLock","held","createBrowserBoardPlatformAdapter","namespace","opts","selfRef","lock","ns","args","url","resp","e","baseUrl","params","v"],"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: ::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\n/** Serialize a KindValueRef to the wire format: ::kind::value */\nexport function serializeRef(ref: KindValueRef): string {\n return `::${ref.kind}::${ref.value}`;\n}\n\n/** Parse a wire-format ref string (::kind::value) into a KindValueRef. */\nexport function parseRef(s: string): KindValueRef {\n if (!s.startsWith('::')) throw new Error(`Invalid ref format (expected ::kind::value): ${s}`);\n const inner = s.slice(2);\n const idx = inner.indexOf('::');\n if (idx === -1) throw new Error(`Invalid ref format (expected ::kind::value): ${s}`);\n return { kind: inner.slice(0, idx), value: inner.slice(idx + 2) };\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 'http:post' and 'http:get' only\n * - requestProcessAccumulated: not applicable (caller drives via polling / setInterval)\n * - selfRef: 'built-in' kind — no spawnable CLI available\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// 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// ============================================================================\n\nexport function createBrowserBoardPlatformAdapter(\n namespace: string,\n opts?: {\n callbackBaseUrl?: string;\n onWarn?: (msg: string) => void;\n },\n): BoardPlatformAdapter {\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: 'built-in' as const,\n whatToRun: '::built-in::board-live-cards-browser',\n };\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('::')\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('::')\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 return {\n dispatched: false,\n error: `Browser adapter: only http:post and http:get dispatch are supported (got: ${ref.howToRun})`,\n };\n },\n\n resolveBlob(ref: KindValueRef): string {\n // In the browser, blobs are stored in localStorage under the board namespace.\n // The ref value is treated as a logical localStorage key.\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 // requestProcessAccumulated is intentionally absent — the browser caller\n // drives drain cycles via polling or setInterval.\n\n onWarn: opts?.onWarn,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ 'use strict';var x=require('jsonata'),module$1=require('module');require('ajv-formats');var _documentCurrentScript=typeof document!=='undefined'?document.currentScript:null;function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var x__default=/*#__PURE__*/_interopDefault(x);var w=module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('card-store-browser-api.cjs', document.baseURI).href)));w("./jsonata-sync.cjs")??x__default.default;function y(n,o){function e(){return n.readIndex()??{}}function r(t,s,i){let a=String(s||"").split(".").filter(Boolean);if(a.length===0)return i&&typeof i=="object"&&!Array.isArray(i)?i:{value:i};let u={...t},c=u;for(let d=0;d<a.length-1;d++){let l=a[d],f=c[l],m=f&&typeof f=="object"&&!Array.isArray(f)?{...f}:{};c[l]=m,c=m;}return c[a[a.length-1]]=i,u}return {readCard(t){let s=e()[t];return !s||!n.cardExists(s.key)?null:n.readCard(s.key)},readCardKey(t){return e()[t]?.key??null},readAllCards(){let t=[];for(let[s,i]of Object.entries(e())){if(!n.cardExists(i.key))continue;let a=n.readCard(i.key);a?t.push(a):o?.(`[card-store] could not read card "${s}" at key "${i.key}"`);}return t},readChecksumIndex(){let t={};for(let[s,i]of Object.entries(e()))t[s]=i.checksum;return t},changedSince(t){let s=e(),i=[];for(let[a,u]of Object.entries(s))t[a]!==u.checksum&&i.push(a);for(let a of Object.keys(t))s[a]||i.push(a);return i},validateUpsert(t,s){let i=e(),a=i[t],u=Object.entries(i).find(([,c])=>c.key===s);return a&&a.key!==s?{ok:false,error:`Card id "${t}" is already mapped to key "${a.key}", cannot remap to "${s}"`}:u&&u[0]!==t?{ok:false,error:`Key "${s}" is already mapped to card id "${u[0]}", cannot remap to "${t}"`}:{ok:true}},writeCard(t,s,i){let a=e(),u=i??a[t]?.key??n.defaultCardKey(t),c=n.writeCard(u,s);a[t]={key:u,checksum:c,updatedAt:new Date().toISOString()},n.writeIndex(a);},patchCard(t,s,i){let a=e(),u=a[t];if(!u||!n.cardExists(u.key))throw new Error(`card "${t}" not found`);let c=n.readCard(u.key);if(!c||typeof c!="object"||Array.isArray(c))throw new Error(`card "${t}" is not patchable`);let d=r(c,s,i),l=n.writeCard(u.key,d);a[t]={key:u.key,checksum:l,updatedAt:new Date().toISOString()},n.writeIndex(a);},removeCard(t){let s=e();s[t]&&(delete s[t],n.writeIndex(s));},readIndex(){return e()}}}function p(n){if(n==null||typeof n!="object")return JSON.stringify(n);if(Array.isArray(n))return `[${n.map(p).join(",")}]`;let o=n;return `{${Object.keys(o).sort().map(r=>`${JSON.stringify(r)}:${p(o[r])}`).join(",")}}`}function g(n,o){let e=o>>>0;for(let r=0;r<n.length;r++)e^=n.charCodeAt(r),e=Math.imul(e,16777619)>>>0;return e}function b(n){let o=p(n),e=g(o,2166136261),r=g(o,3735928559),t=g(o,19088743),s=g(o,4277009102);return [e,r,t,s].map(i=>i.toString(16).padStart(8,"0")).join("")}function v(n){function o(e){return `${n}:kv:${e}`}return {read(e){let r=globalThis.localStorage.getItem(o(e));if(r===null)return null;try{return JSON.parse(r)}catch{return null}},write(e,r){globalThis.localStorage.setItem(o(e),JSON.stringify(r));},delete(e){globalThis.localStorage.removeItem(o(e));},listKeys(e){let r=o(e??""),t=[];for(let s=0;s<globalThis.localStorage.length;s++){let i=globalThis.localStorage.key(s);i!==null&&i.startsWith(r)&&t.push(i.slice(o("").length));}return t}}}function h(n,o){let e={...n};for(let[r,t]of Object.entries(o))t!==null&&typeof t=="object"&&!Array.isArray(t)&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])?e[r]=h(e[r],t):e[r]=t;return e}function S(n,o,e){if(o.length===0)return n;let[r,...t]=o;if(t.length===0)return {...n,[r]:e};let s=n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])?n[r]:{};return {...n,[r]:S(s,t,e)}}function E(n){let o=v(n);return {read:e=>o.read(e),get(e,r){let t=o.read(e);if(t===null)return null;let s=t;for(let i of r.split(".").filter(Boolean)){if(s===null||typeof s!="object"||Array.isArray(s))return null;s=s[i]??null;}return s??null},write:(e,r)=>o.write(e,r),delete:e=>o.delete(e),listKeys:e=>o.listKeys(e),shallowMerge(e,r){let t=o.read(e)??{};o.write(e,{...t,...r});},deepMerge(e,r){let t=o.read(e)??{};o.write(e,h(t,r));},patch(e,r,t){let s=o.read(e)??{},i=r.split(".").filter(Boolean);o.write(e,S(s,i,t));}}}function k(n){let o=E(n);return {readIndex(){return o.read("_index")},writeIndex(e){o.write("_index",e);},readCard(e){return o.read(e)},writeCard(e,r){return o.write(e,r),b(r)},cardExists(e){return o.read(e)!==null},defaultCardKey(e){return e}}}function ye(n){let o=k(n),e=y(o);return {getCard(r){return e.readCard(r)},getAllCards(){return e.readAllCards()},upsertCard(r){let t=o.defaultCardKey(r.id);e.writeCard(r.id,r,t);},removeCard(r){e.removeCard(r);}}}exports.createBrowserCardStoreApi=ye;//# sourceMappingURL=card-store-browser-api.cjs.map
2
+ //# sourceMappingURL=card-store-browser-api.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/card-compute/index.ts","../../../src/cli/common/board-live-cards-lib.ts","../../../src/cli/browser-api/storage-localstorage-adapters.ts","../../../src/cli/browser-api/card-store-browser-api.ts"],"names":["_require","createRequire","jsonata","createCardStore","adapter","onWarn","loadIndex","applyJsonPath","obj","jsonPath","value","segments","out","target","i","key","cur","next","id","entry","cards","card","result","snapshotChecksumIndex","localIndex","changed","cardKey","index","existingById","existingByKey","e","resolvedKey","checksum","current","stableJson","k","fnv32a","str","seed","h","computeStableJsonHashBrowser","a","b","c","d","n","createLocalStorageKvStorage","prefix","raw","prefix2","fullPrefix","lsKey","deepMergeObjects","patch","v","head","tail","nested","createLocalStorageJsonStorage","kv","segment","existing","createLocalStorageCardStorageAdapter","json","cardId","createBrowserCardStoreApi","namespace","store"],"mappings":"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","file":"card-store-browser-api.cjs","sourcesContent":["/**\n * card-compute — JSONata-powered compute engine for LiveCards nodes.\n *\n * Isomorphic: works in browser, Node.js, and bundlers.\n * No DOM dependency. Compute expressions are JSONata strings.\n *\n * @example\n * ```typescript\n * import { CardCompute } from 'yaml-flow/card-compute';\n *\n * const node = {\n * id: 'sales',\n * card_data: { data: [{ revenue: 100 }, { revenue: 200 }] },\n * compute: [\n * { bindTo: 'total', expr: '$sum(card_data.data.revenue)' },\n * { bindTo: 'avg', expr: '$average(card_data.data.revenue)' },\n * ],\n * };\n * await CardCompute.run(node);\n * // node.computed_values.total === 300\n * // node.computed_values.avg === 150\n * ```\n *\n * Expressions are evaluated against { card_data, requires, fetched_sources, computed_values }.\n * computed_values is ephemeral — never persisted to disk.\n */\n\nimport jsonata from 'jsonata';\nimport { createRequire } from 'module';\nconst _require = createRequire(import.meta.url);\n// QuickJS bundles may initialize the shim binding after this module executes.\n// Fallback to the standard jsonata import so sync evaluation still works.\nconst jsonataSync: typeof jsonata = (_require('./jsonata-sync.cjs') ?? jsonata) as typeof jsonata;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A source definition: cli writes to outputFile; bindTo names the fetched_sources.* key in compute context. Both bindTo and outputFile must be unique across source_defs in a card. */\nexport interface ComputeSource {\n bindTo: string;\n outputFile: string;\n cli?: string;\n // Deprecated alias retained for compatibility with older cards.\n script?: string;\n optionalForCompletionGating?: boolean;\n /** Named data projections: each key maps to a JSONata expression rooted at card_data or requires.\n * The engine evaluates these before spawning the executor and passes results as _projections. */\n projections?: Record<string, string>;\n [key: string]: unknown;\n}\n\n/** Options for CardCompute.run() */\nexport interface RunOptions {\n /** Pre-loaded source results map (keyed by bindTo). Use in browser or when caller loads files. */\n sourcesData?: Record<string, unknown>;\n}\n\n/** A single compute step: bindTo names the computed_values key; expr is a JSONata expression. */\nexport interface ComputeStep {\n bindTo: string;\n expr: string;\n}\n\n/** Minimal node shape expected by CardCompute. */\nexport interface ComputeNode {\n id?: string;\n card_data?: Record<string, unknown>;\n requires?: Record<string, unknown>;\n source_defs?: ComputeSource[];\n compute?: ComputeStep[];\n computed_values?: Record<string, unknown>;\n /** Ephemeral: populated by run() from sourcesData option. Never persisted. */\n _sourcesData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Deep path utilities\n// ---------------------------------------------------------------------------\n\nfunction deepGet(obj: unknown, path: string): unknown {\n if (!path || !obj) return undefined;\n const parts = path.split('.');\n let cur: unknown = obj;\n for (let i = 0; i < parts.length; i++) {\n if (cur == null) return undefined;\n cur = (cur as Record<string, unknown>)[parts[i]];\n }\n return cur;\n}\n\nfunction deepSet(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (cur[parts[i]] == null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};\n cur = cur[parts[i]] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]] = value;\n}\n\n// ---------------------------------------------------------------------------\n// Engine — JSONata-based async evaluation\n// ---------------------------------------------------------------------------\n\n/**\n * Run all compute steps on a node.\n * Each step's expr is evaluated against { card_data, requires, fetched_sources, computed_values }.\n * Results are written to node.computed_values[bindTo].\n * computed_values and _sourcesData are reset on each call — ephemeral, never persisted.\n *\n * @param options.sourcesData Pre-loaded map of { [bindTo]: data } for fetched_sources namespace.\n * In Node/CLI: loaded from outputFiles by the caller (card-handler).\n * In browser: passed in by the caller (e.g. from fetch results).\n */\nasync function run(node: ComputeNode, options?: RunOptions): Promise<ComputeNode> {\n if (!node?.compute?.length) return node;\n if (!node.card_data) node.card_data = {};\n node.computed_values = {};\n node._sourcesData = options?.sourcesData ?? {};\n\n // Context passed to JSONata\n const ctx: Record<string, unknown> = {\n card_data: node.card_data,\n requires: node.requires ?? {},\n fetched_sources: node._sourcesData,\n computed_values: node.computed_values,\n };\n\n for (const step of node.compute) {\n try {\n const val = await jsonata(step.expr).evaluate(ctx);\n deepSet(node.computed_values, step.bindTo, val);\n ctx.computed_values = node.computed_values; // subsequent steps see earlier results\n } catch (err) {\n console.error(`CardCompute.run error on \"${node.id ?? '?'}.${step.bindTo}\":`, err);\n }\n }\n\n return node;\n}\n\n/**\n * Synchronous version of run() — uses a vendored sync JSONata build\n * (async/await stripped from jsonata.js since all built-in functions\n * are CPU-only).\n *\n * Same semantics as `run()`: evaluates all compute steps, populates\n * `node.computed_values`, returns the mutated node.\n *\n * @returns `{ ok: true, node }` when all steps evaluated successfully.\n * `{ ok: false, node }` is currently never returned but reserved\n * for future use if an expression requires true async evaluation.\n */\nfunction runSync(node: ComputeNode, options?: RunOptions): { ok: boolean; node: ComputeNode } {\n if (!node?.compute?.length) return { ok: true, node };\n if (!node.card_data) node.card_data = {};\n node.computed_values = {};\n node._sourcesData = options?.sourcesData ?? {};\n\n const ctx: Record<string, unknown> = {\n card_data: node.card_data,\n requires: node.requires ?? {},\n fetched_sources: node._sourcesData,\n computed_values: node.computed_values,\n };\n\n for (const step of node.compute) {\n try {\n const val = jsonataSync(step.expr).evaluate(ctx);\n deepSet(node.computed_values, step.bindTo, val);\n ctx.computed_values = node.computed_values;\n } catch (err) {\n console.error(`CardCompute.runSync error on \"${node.id ?? '?'}.${step.bindTo}\":`, err);\n }\n }\n\n return { ok: true, node };\n}\n\n/**\n * Evaluate a single JSONata expression against a node's context.\n * Context is { card_data, requires, fetched_sources, computed_values }.\n */\nasync function evalExpr(expr: string, node: ComputeNode): Promise<unknown> {\n const ctx: Record<string, unknown> = {\n card_data: node.card_data ?? {},\n requires: node.requires ?? {},\n fetched_sources: node._sourcesData ?? {},\n computed_values: node.computed_values ?? {},\n };\n return jsonata(expr).evaluate(ctx);\n}\n\n// ---------------------------------------------------------------------------\n// resolve — synchronous deep-get from node\n// ---------------------------------------------------------------------------\n\nfunction resolve(node: ComputeNode, path: string): unknown {\n if (path.startsWith('fetched_sources.')) {\n return deepGet(node._sourcesData ?? {}, path.slice('fetched_sources.'.length));\n }\n\n return deepGet(node, path);\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/** Result of validateNode — ok: true means valid, ok: false has errors[]. */\nexport interface ValidationResult {\n ok: boolean;\n errors: string[];\n}\n\nconst VALID_ELEMENT_KINDS = new Set([\n 'metric', 'table', 'editable-table', 'chart', 'form', 'filter', 'list',\n 'notes', 'todo', 'alert', 'narrative', 'badge', 'text',\n 'markdown', 'ref', 'custom', 'actions',\n]);\n\nconst ALLOWED_KEYS = new Set(['id', 'meta', 'requires', 'provides', 'view', 'card_data', 'compute', 'source_defs']);\n\nfunction validateNode(node: unknown): ValidationResult {\n const errors: string[] = [];\n\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n return { ok: false, errors: ['Node must be a non-null object'] };\n }\n\n const n = node as Record<string, unknown>;\n\n if (typeof n.id !== 'string' || !n.id) errors.push('id: required, must be a non-empty string');\n\n for (const key of Object.keys(n)) {\n if (!ALLOWED_KEYS.has(key)) errors.push(`Unknown top-level key: \"${key}\"`);\n }\n\n if (n.card_data == null || typeof n.card_data !== 'object' || Array.isArray(n.card_data)) {\n errors.push('card_data: required, must be an object');\n }\n\n if (n.meta != null) {\n if (typeof n.meta !== 'object' || Array.isArray(n.meta)) {\n errors.push('meta: must be an object');\n } else {\n const meta = n.meta as Record<string, unknown>;\n if (meta.title != null && typeof meta.title !== 'string') errors.push('meta.title: must be a string');\n if (meta.tags != null && !Array.isArray(meta.tags)) errors.push('meta.tags: must be an array');\n }\n }\n\n if (n.requires != null && !Array.isArray(n.requires)) errors.push('requires: must be an array of strings');\n\n if (n.provides != null) {\n if (!Array.isArray(n.provides)) {\n errors.push('provides: must be an array of { bindTo, ref } bindings');\n } else {\n (n.provides as unknown[]).forEach((p, i) => {\n if (!p || typeof p !== 'object' || Array.isArray(p)) {\n errors.push(`provides[${i}]: must be an object with bindTo and ref`);\n } else {\n const b = p as Record<string, unknown>;\n if (typeof b.bindTo !== 'string' || !b.bindTo) errors.push(`provides[${i}]: missing required \"bindTo\" string`);\n if (typeof b.ref !== 'string' || !b.ref) errors.push(`provides[${i}]: missing required \"ref\" string`);\n }\n });\n }\n }\n\n // compute — ordered array of { bindTo, expr } steps\n if (n.compute != null) {\n if (!Array.isArray(n.compute)) {\n errors.push('compute: must be an array of compute steps');\n } else {\n (n.compute as unknown[]).forEach((step, i) => {\n if (!step || typeof step !== 'object' || Array.isArray(step)) {\n errors.push(`compute[${i}]: must be a compute step object`);\n } else {\n const s = step as Record<string, unknown>;\n if (typeof s.bindTo !== 'string' || !s.bindTo) errors.push(`compute[${i}]: missing required \"bindTo\" property`);\n if (typeof s.expr !== 'string' || !s.expr) errors.push(`compute[${i}]: missing required \"expr\" string (JSONata expression)`);\n }\n });\n }\n }\n\n if (n.source_defs != null) {\n if (!Array.isArray(n.source_defs)) {\n errors.push('source_defs: must be an array');\n } else {\n const bindTos = new Set<string>();\n const outputFiles = new Set<string>();\n (n.source_defs as unknown[]).forEach((src, i) => {\n if (!src || typeof src !== 'object' || Array.isArray(src)) {\n errors.push(`source_defs[${i}]: must be an object`);\n } else {\n const s = src as Record<string, unknown>;\n if (typeof s.bindTo !== 'string' || !s.bindTo) {\n errors.push(`source_defs[${i}]: missing required \"bindTo\" property`);\n } else {\n if (bindTos.has(s.bindTo)) {\n errors.push(`source_defs[${i}]: bindTo \"${s.bindTo}\" is not unique across source_defs`);\n }\n bindTos.add(s.bindTo);\n }\n if (typeof s.outputFile !== 'string' || !s.outputFile) {\n errors.push(`source_defs[${i}]: missing required \"outputFile\" property`);\n } else {\n if (outputFiles.has(s.outputFile)) {\n errors.push(`source_defs[${i}]: outputFile \"${s.outputFile}\" is not unique across source_defs`);\n }\n outputFiles.add(s.outputFile);\n }\n if (s.optionalForCompletionGating != null && typeof s.optionalForCompletionGating !== 'boolean') {\n errors.push(`source_defs[${i}]: optionalForCompletionGating must be a boolean`);\n }\n }\n });\n }\n }\n\n if (n.view != null) {\n if (typeof n.view !== 'object' || Array.isArray(n.view)) {\n errors.push('view: must be an object');\n } else {\n const view = n.view as Record<string, unknown>;\n if (!Array.isArray(view.elements) || view.elements.length === 0) {\n errors.push('view.elements: required, must be a non-empty array');\n } else {\n (view.elements as Record<string, unknown>[]).forEach((elem, i) => {\n if (!elem || typeof elem !== 'object') { errors.push(`view.elements[${i}]: must be an object`); return; }\n if (!elem.kind || typeof elem.kind !== 'string') {\n errors.push(`view.elements[${i}].kind: required, must be a string`);\n } else if (!VALID_ELEMENT_KINDS.has(elem.kind as string)) {\n errors.push(`view.elements[${i}].kind: unknown kind \"${elem.kind}\". Valid: ${[...VALID_ELEMENT_KINDS].join(', ')}`);\n }\n if (elem.data != null && (typeof elem.data !== 'object' || Array.isArray(elem.data))) {\n errors.push(`view.elements[${i}].data: must be an object`);\n }\n });\n }\n if (view.layout != null && (typeof view.layout !== 'object' || Array.isArray(view.layout))) errors.push('view.layout: must be an object');\n if (view.features != null && (typeof view.features !== 'object' || Array.isArray(view.features))) errors.push('view.features: must be an object');\n }\n }\n\n return { ok: errors.length === 0, errors };\n}\n\n/**\n * Enrich source_defs with execution context for template interpolation and prompt rendering.\n * Pure function: no side effects, returns new enriched source_defs array.\n * \n * @param source_defs - Array of source definitions\n * @param context - Execution context containing requires, sourcesData, computed_values\n * @returns Promise resolving to a new array of source_defs with _projections attached.\n * Each _projections entry is the evaluated result of the corresponding projections expression.\n */\nasync function enrichSources(\n source_defs: any[] | undefined,\n context: {\n card_data?: Record<string, any>;\n requires?: Record<string, any>;\n sourcesData?: Record<string, any>; // unused post-projections, kept for call-site compat\n computed_values?: Record<string, any>; // unused post-projections, kept for call-site compat\n }\n): Promise<any[]> {\n if (!source_defs || source_defs.length === 0) return [];\n\n const evalCtx = {\n card_data: context.card_data ?? {},\n requires: context.requires ?? {},\n };\n\n return Promise.all(\n source_defs.map(async (src: any) => {\n const _projections: Record<string, unknown> = {};\n if (src.projections && typeof src.projections === 'object' && !Array.isArray(src.projections)) {\n for (const [key, expr] of Object.entries(src.projections as Record<string, string>)) {\n if (typeof expr === 'string' && expr.trim().length > 0) {\n try {\n _projections[key] = await jsonata(expr).evaluate(evalCtx);\n } catch {\n _projections[key] = undefined;\n }\n }\n }\n }\n return { ...src, _projections };\n })\n );\n}\n\nfunction enrichSourcesSync(\n source_defs: any[] | undefined,\n context: {\n card_data?: Record<string, any>;\n requires?: Record<string, any>;\n }\n): any[] {\n if (!source_defs || source_defs.length === 0) return [];\n\n const evalCtx = {\n card_data: context.card_data ?? {},\n requires: context.requires ?? {},\n };\n\n return source_defs.map((src: any) => {\n const _projections: Record<string, unknown> = {};\n if (src.projections && typeof src.projections === 'object' && !Array.isArray(src.projections)) {\n for (const [key, expr] of Object.entries(src.projections as Record<string, string>)) {\n if (typeof expr === 'string' && expr.trim().length > 0) {\n try {\n _projections[key] = jsonataSync(expr).evaluate(evalCtx);\n } catch {\n _projections[key] = undefined;\n }\n }\n }\n }\n return { ...src, _projections };\n });\n}\n\nexport const CardCompute = {\n run,\n runSync,\n eval: evalExpr,\n resolve,\n validate: validateNode,\n enrichSources,\n enrichSourcesSync,\n};\n\nexport {\n validateLiveCard,\n validateLiveCardSchema,\n validateLiveCardRuntimeExpressions,\n validateLiveCardDefinition,\n} from './schema-validator.js';\n\nexport default CardCompute;\n\n","/**\n * board-live-cards-lib — Pure logic library for the board-live-cards CLI.\n *\n * Merged from:\n * board-live-cards-all-stores.ts\n * board-live-cards-lib-types.ts\n * board-live-cards-lib-board-status.ts\n * board-live-cards-lib-card-handler.ts\n * board-live-cards-cli-board-commands.ts\n * board-live-cards-cli-card-commands.ts\n * board-live-cards-cli-callbacks.ts\n *\n * Zero platform imports. All storage is injected via adapter interfaces.\n * Safe for Node, browser, and neutral (V8/PyMiniRacer) bundles.\n */\n\nimport type { KVStorage, BlobStorage, KindValueRef } from './storage-interface.js';\nimport { serializeRef } from './storage-interface.js';\nimport { parseExecutionRef, serializeExecutionRef } from './execution-interface.js';\nimport type { ExecutionRef } from './execution-interface.js';\nimport type { GraphEvent, TaskConfig, GraphConfig } from '../../event-graph/types.js';\nimport type { LiveGraph, LiveGraphSnapshot } from '../../continuous-event-graph/types.js';\nimport { schedule } from '../../continuous-event-graph/schedule.js';\nimport type { TaskHandlerFn } from '../../continuous-event-graph/reactive.js';\nimport { CardCompute } from '../../card-compute/index.js';\nimport type { ComputeNode, ComputeStep, ComputeSource } from '../../card-compute/index.js';\nexport type { DispatchResult, InvocationAdapter } from './process-interface.js';\n\n// ============================================================================\n// ---- from board-live-cards-all-stores.ts ----\n// ============================================================================\n\n// ============================================================================\n// Card store — types\n// ============================================================================\n\nexport interface LiveCard {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface CardIndexEntry {\n /** Storage-specific address (file path, Cosmos doc id, localStorage key). */\n key: string;\n /** Checksum of card content — computed by the adapter at write time. */\n checksum: string;\n updatedAt: string;\n}\n\nexport type CardIndex = Record<string, CardIndexEntry>;\nexport type CardChecksumIndex = Record<string, string>;\n\n/**\n * Per-card entry stored in the card-upsert KV cache (one key per cardId).\n * Lives alongside the board journal — NOT inside the board snapshot.\n * Purpose: dedup gate to avoid redundant task-upsert journal entries.\n *\n * Write order: journal.append() THEN kv.write() — so a crash between the two\n * leaves the journal entry intact (board is correct) and the KV stale (next\n * upsert will see \"changed\" and re-append; addNode is idempotent in the board).\n */\nexport interface CardUpsertIndexEntry {\n /** Logical reference to the card blob — absolute path for fs, blob name for cloud. */\n blobRef: string;\n /** SHA-256 of stable-JSON-serialised taskConfig. Dedup key. */\n taskConfigHash: string;\n updatedAt: string;\n}\n\n// ============================================================================\n// CardStorageAdapter — injected by the caller\n// ============================================================================\n\nexport interface CardStorageAdapter {\n readIndex(): CardIndex | null;\n writeIndex(index: CardIndex): void;\n readCard(key: string): LiveCard | null;\n /** Write card content; returns checksum of what was written. */\n writeCard(key: string, card: LiveCard): string;\n cardExists(key: string): boolean;\n defaultCardKey(cardId: string): string;\n}\n\n// ============================================================================\n// CardStore — board one-cycle (read-only)\n// ============================================================================\n\nexport interface CardStore {\n readCard(id: string): LiveCard | null;\n readCardKey(id: string): string | null;\n readAllCards(): LiveCard[];\n readChecksumIndex(): CardChecksumIndex;\n changedSince(snapshotChecksumIndex: CardChecksumIndex): string[];\n}\n\n// ============================================================================\n// CardAdminStore — CLI write interface\n// ============================================================================\n\nexport interface CardUpsertValidation {\n ok: boolean;\n error?: string;\n}\n\nexport interface CardAdminStore extends CardStore {\n validateUpsert(id: string, cardKey: string): CardUpsertValidation;\n writeCard(id: string, card: LiveCard, cardKey?: string): void;\n patchCard(id: string, jsonPath: string, value: unknown): void;\n removeCard(id: string): void;\n readIndex(): CardIndex;\n}\n\n// ============================================================================\n// createCardStore — pure logic factory\n// ============================================================================\n\nexport function createCardStore(adapter: CardStorageAdapter, onWarn?: (msg: string) => void): CardAdminStore {\n function loadIndex(): CardIndex {\n return adapter.readIndex() ?? {};\n }\n\n function applyJsonPath(obj: Record<string, unknown>, jsonPath: string, value: unknown): Record<string, unknown> {\n const segments = String(jsonPath || '').split('.').filter(Boolean);\n if (segments.length === 0) {\n return (value && typeof value === 'object' && !Array.isArray(value))\n ? value as Record<string, unknown>\n : { value };\n }\n\n const out: Record<string, unknown> = { ...obj };\n let target: Record<string, unknown> = out;\n for (let i = 0; i < segments.length - 1; i++) {\n const key = segments[i];\n const cur = target[key];\n const next = (cur && typeof cur === 'object' && !Array.isArray(cur))\n ? { ...(cur as Record<string, unknown>) }\n : {};\n target[key] = next;\n target = next;\n }\n target[segments[segments.length - 1]] = value;\n return out;\n }\n\n return {\n readCard(id: string): LiveCard | null {\n const entry = loadIndex()[id];\n if (!entry || !adapter.cardExists(entry.key)) return null;\n return adapter.readCard(entry.key);\n },\n\n readCardKey(id: string): string | null {\n return loadIndex()[id]?.key ?? null;\n },\n\n readAllCards(): LiveCard[] {\n const cards: LiveCard[] = [];\n for (const [id, entry] of Object.entries(loadIndex())) {\n if (!adapter.cardExists(entry.key)) continue;\n const card = adapter.readCard(entry.key);\n if (card) cards.push(card);\n else onWarn?.(`[card-store] could not read card \"${id}\" at key \"${entry.key}\"`);\n }\n return cards;\n },\n\n readChecksumIndex(): CardChecksumIndex {\n const result: CardChecksumIndex = {};\n for (const [id, entry] of Object.entries(loadIndex())) result[id] = entry.checksum;\n return result;\n },\n\n changedSince(snapshotChecksumIndex: CardChecksumIndex): string[] {\n const localIndex = loadIndex();\n const changed: string[] = [];\n for (const [id, entry] of Object.entries(localIndex)) {\n if (snapshotChecksumIndex[id] !== entry.checksum) changed.push(id);\n }\n for (const id of Object.keys(snapshotChecksumIndex)) {\n if (!localIndex[id]) changed.push(id);\n }\n return changed;\n },\n\n validateUpsert(id: string, cardKey: string): CardUpsertValidation {\n const index = loadIndex();\n const existingById = index[id];\n const existingByKey = Object.entries(index).find(([, e]) => e.key === cardKey);\n if (existingById && existingById.key !== cardKey)\n return { ok: false, error: `Card id \"${id}\" is already mapped to key \"${existingById.key}\", cannot remap to \"${cardKey}\"` };\n if (existingByKey && existingByKey[0] !== id)\n return { ok: false, error: `Key \"${cardKey}\" is already mapped to card id \"${existingByKey[0]}\", cannot remap to \"${id}\"` };\n return { ok: true };\n },\n\n writeCard(id: string, card: LiveCard, cardKey?: string): void {\n const index = loadIndex();\n const resolvedKey = cardKey ?? index[id]?.key ?? adapter.defaultCardKey(id);\n const checksum = adapter.writeCard(resolvedKey, card);\n index[id] = { key: resolvedKey, checksum, updatedAt: new Date().toISOString() };\n adapter.writeIndex(index);\n },\n\n patchCard(id: string, jsonPath: string, value: unknown): void {\n const index = loadIndex();\n const entry = index[id];\n if (!entry || !adapter.cardExists(entry.key)) {\n throw new Error(`card \"${id}\" not found`);\n }\n const current = adapter.readCard(entry.key);\n if (!current || typeof current !== 'object' || Array.isArray(current)) {\n throw new Error(`card \"${id}\" is not patchable`);\n }\n const next = applyJsonPath(current as Record<string, unknown>, jsonPath, value) as LiveCard;\n const checksum = adapter.writeCard(entry.key, next);\n index[id] = { key: entry.key, checksum, updatedAt: new Date().toISOString() };\n adapter.writeIndex(index);\n },\n\n removeCard(id: string): void {\n const index = loadIndex();\n if (!index[id]) return;\n delete index[id];\n adapter.writeIndex(index);\n },\n\n readIndex(): CardIndex {\n return loadIndex();\n },\n };\n}\n\n// ============================================================================\n// FetchedSourcesStore\n// ============================================================================\n\nexport interface FetchedSourcesStore {\n /** Read committed source content. Returns parsed JSON or raw string; null if not yet committed. */\n readSourceData(cardId: string, outputFile: string): unknown;\n /** Stage incoming source data under deliveryToken. resolveRef converts the ref to content bytes. */\n ingestSourceDataStaged(cardId: string, outputFile: string, ref: KindValueRef, deliveryToken: string): void;\n /** Move staged data to live position. Returns false if staged entry is absent (stale delivery). */\n commitSourceData(cardId: string, outputFile: string, deliveryToken: string): boolean;\n /** True if live (committed) source data exists for this outputFile. */\n hasSource(cardId: string, outputFile: string): boolean;\n}\n\nexport function createFetchedSourcesStore(\n blob: BlobStorage,\n resolveRef: (ref: KindValueRef) => string,\n): FetchedSourcesStore {\n return {\n readSourceData(cardId, outputFile): unknown {\n const raw = blob.read(`${cardId}/${outputFile}`);\n if (raw == null) return null;\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try { return JSON.parse(trimmed); } catch { return trimmed; }\n },\n ingestSourceDataStaged(cardId, outputFile, ref, deliveryToken): void {\n const content = resolveRef(ref);\n blob.write(`${cardId}/.staged/${deliveryToken}/${outputFile}`, content);\n },\n commitSourceData(cardId, outputFile, deliveryToken): boolean {\n const stagedKey = `${cardId}/.staged/${deliveryToken}/${outputFile}`;\n const content = blob.read(stagedKey);\n if (content == null) return false;\n blob.write(`${cardId}/${outputFile}`, content);\n blob.remove(stagedKey);\n return true;\n },\n hasSource(cardId, outputFile): boolean {\n return blob.exists(`${cardId}/${outputFile}`);\n },\n };\n}\n\n// ============================================================================\n// Journal store — types\n// ============================================================================\n\nexport interface JournalEntry {\n id: string;\n event: GraphEvent;\n}\n\nexport interface JournalStorageAdapter {\n readAllEntries(): JournalEntry[];\n appendEntry(entry: JournalEntry): void;\n generateId(): string;\n}\n\nexport interface JournalStore {\n readEntriesAfterCursor(cursor: string): { events: GraphEvent[]; newCursor: string };\n pendingCount(cursor: string): number;\n}\n\nexport interface JournalAdminStore extends JournalStore {\n appendEvent(event: GraphEvent): void;\n}\n\nexport function createJournalStore(adapter: JournalStorageAdapter): JournalAdminStore {\n function entriesAfterCursor(cursor: string): JournalEntry[] {\n const all = adapter.readAllEntries();\n if (!cursor) return all;\n const idx = all.findIndex(e => e.id === cursor);\n return idx === -1 ? all : all.slice(idx + 1);\n }\n\n return {\n readEntriesAfterCursor(cursor: string): { events: GraphEvent[]; newCursor: string } {\n const entries = entriesAfterCursor(cursor);\n if (entries.length === 0) return { events: [], newCursor: cursor };\n return { events: entries.map(e => e.event), newCursor: entries[entries.length - 1].id };\n },\n\n pendingCount(cursor: string): number {\n return entriesAfterCursor(cursor).length;\n },\n\n appendEvent(event: GraphEvent): void {\n adapter.appendEntry({ id: adapter.generateId(), event });\n },\n };\n}\n\n// ============================================================================\n// ExecutionRequest store\n// ============================================================================\n\nexport interface ExecutionRequestEntry {\n taskKind: string;\n payload: unknown;\n}\n\nexport interface ExecutionRequestStore {\n appendEntries(journalId: string, entries: ExecutionRequestEntry[]): void;\n dispatchEntriesForJournalId(journalId: string, processorFn: (entry: ExecutionRequestEntry) => void): void;\n}\n\nexport function createExecutionRequestStore(\n kv: KVStorage,\n onDispatchFailed: (entry: ExecutionRequestEntry, error: string) => void,\n): ExecutionRequestStore {\n return {\n appendEntries(journalId: string, entries: ExecutionRequestEntry[]): void {\n if (!journalId || entries.length === 0) return;\n const existing = (kv.read(journalId) as ExecutionRequestEntry[] | null) ?? [];\n kv.write(journalId, [...existing, ...entries]);\n },\n\n dispatchEntriesForJournalId(journalId: string, processorFn: (entry: ExecutionRequestEntry) => void): void {\n if (!journalId) return;\n const entries = kv.read(journalId) as ExecutionRequestEntry[] | null;\n if (!entries || entries.length === 0) return;\n for (const entry of entries) {\n try { processorFn(entry); } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n try { onDispatchFailed(entry, msg); } catch { /* guard against failure in error handler */ }\n }\n }\n kv.delete(journalId);\n },\n };\n}\n\n// ============================================================================\n// StateSnapshot store\n// ============================================================================\n\nexport const SNAPSHOT_SCHEMA_VERSION_V1 = 'v1';\n\nexport const BOARD_GRAPH_KEY = 'board/graph';\nexport const BOARD_LAST_JOURNAL_PROCESSED_ID_KEY = 'board/lastJournalProcessedId';\n\nexport function cardRuntimeKey(cardId: string): string {\n return `cards/${cardId}/runtime`;\n}\n\nexport function cardFetchedSourcesManifestKey(cardId: string): string {\n return `cards/${cardId}/fetched-sources-manifest`;\n}\n\nexport interface CardRuntimeSnapshot {\n _sources: Record<string, { lastRequestedAt?: string; lastFetchedAt?: string; queueRequestedAt?: string }>;\n _lastExecutionCount?: number;\n}\n\nexport interface CardRuntimeStore {\n readRuntime(cardId: string): CardRuntimeSnapshot;\n writeRuntime(cardId: string, state: CardRuntimeSnapshot): void;\n}\n\nexport function createCardRuntimeStore(kv: KVStorage): CardRuntimeStore {\n return {\n readRuntime(cardId) {\n return (kv.read(cardRuntimeKey(cardId)) as CardRuntimeSnapshot | null) ?? { _sources: {} };\n },\n writeRuntime(cardId, state) {\n kv.write(cardRuntimeKey(cardId), state);\n },\n };\n}\n\nexport interface FetchedSourceManifestEntry {\n outputFile: string;\n blobRef: string;\n fetchedAt: string;\n sourceChecksum?: string;\n contentType?: string;\n sizeBytes?: number;\n}\n\nexport interface StateSnapshotReadView {\n version: string | null;\n values: Record<string, unknown>;\n}\n\nexport interface StateSnapshotCommitEnvelope {\n schemaVersion: typeof SNAPSHOT_SCHEMA_VERSION_V1;\n expectedVersion: string | null;\n commitId: string;\n committedAt: string;\n deleteKeys: string[];\n shallowMerge: Record<string, unknown>;\n}\n\nexport interface StateSnapshotCommitSuccess {\n ok: true;\n newVersion: string;\n}\n\nexport interface StateSnapshotCommitVersionMismatch {\n ok: false;\n reason: 'version-mismatch';\n currentVersion: string | null;\n}\n\nexport type StateSnapshotCommitResult =\n | StateSnapshotCommitSuccess\n | StateSnapshotCommitVersionMismatch;\n\nexport interface StateSnapshotStorageAdapter {\n readValues(scopeId: string): StateSnapshotReadView;\n writeValues(scopeId: string, nextValues: Record<string, unknown>, deletedKeys: string[]): string;\n}\n\nexport interface StateSnapshotStore {\n readSnapshot(scopeId: string): StateSnapshotReadView;\n commitSnapshot(scopeId: string, envelope: StateSnapshotCommitEnvelope): StateSnapshotCommitResult;\n}\n\nexport function applyStateSnapshotCommitEnvelope(\n current: Record<string, unknown>,\n envelope: Pick<StateSnapshotCommitEnvelope, 'deleteKeys' | 'shallowMerge'>,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...current };\n for (const key of envelope.deleteKeys) {\n delete next[key];\n }\n return { ...next, ...envelope.shallowMerge };\n}\n\nexport function createStateSnapshotStore(adapter: StateSnapshotStorageAdapter): StateSnapshotStore {\n return {\n readSnapshot(scopeId: string): StateSnapshotReadView {\n return adapter.readValues(scopeId);\n },\n\n commitSnapshot(scopeId: string, envelope: StateSnapshotCommitEnvelope): StateSnapshotCommitResult {\n if (envelope.schemaVersion !== SNAPSHOT_SCHEMA_VERSION_V1) {\n throw new Error(`Unsupported snapshot schema version: ${envelope.schemaVersion}`);\n }\n const current = adapter.readValues(scopeId);\n if (current.version !== envelope.expectedVersion) {\n return { ok: false, reason: 'version-mismatch', currentVersion: current.version };\n }\n const nextValues = applyStateSnapshotCommitEnvelope(current.values, envelope);\n const newVersion = adapter.writeValues(scopeId, nextValues, envelope.deleteKeys);\n return { ok: true, newVersion };\n },\n };\n}\n\n// ============================================================================\n// BoardConfigStore\n// ============================================================================\n\nexport interface BoardConfigStore {\n readTaskExecutorRef(): ExecutionRef | undefined;\n writeTaskExecutorRef(ref: ExecutionRef): void;\n readChatHandlerRef(): ExecutionRef | undefined;\n writeChatHandlerRef(ref: ExecutionRef): void;\n readCardStoreRef(): string | null;\n writeCardStoreRef(ref: string): void;\n readOutputsStoreRef(): string | null;\n writeOutputsStoreRef(ref: string): void;\n /** @deprecated use readChatHandlerRef */\n readChatHandler(): string | undefined;\n /** @deprecated use writeChatHandlerRef */\n writeChatHandler(value: string): void;\n}\n\nexport function createBoardConfigStore(kv: KVStorage): BoardConfigStore {\n function readKey(key: string): string | null {\n const v = kv.read(key);\n if (v == null) return null;\n return typeof v === 'string' ? v : JSON.stringify(v);\n }\n\n return {\n readTaskExecutorRef(): ExecutionRef | undefined {\n const raw = readKey('task-executor');\n if (!raw?.trim()) return undefined;\n return parseExecutionRef(raw.trim());\n },\n\n writeTaskExecutorRef(ref: ExecutionRef): void {\n kv.write('task-executor', serializeExecutionRef(ref));\n },\n\n readChatHandlerRef(): ExecutionRef | undefined {\n const raw = readKey('chat-handler');\n if (!raw?.trim()) return undefined;\n return parseExecutionRef(raw.trim());\n },\n\n writeChatHandlerRef(ref: ExecutionRef): void {\n kv.write('chat-handler', serializeExecutionRef(ref));\n },\n\n readCardStoreRef(): string | null {\n return readKey('card-store-ref');\n },\n\n writeCardStoreRef(ref: string): void {\n kv.write('card-store-ref', ref);\n },\n\n readOutputsStoreRef(): string | null {\n return readKey('outputs-store-ref');\n },\n\n writeOutputsStoreRef(ref: string): void {\n kv.write('outputs-store-ref', ref);\n },\n\n readChatHandler(): string | undefined {\n return readKey('chat-handler')?.trim() || undefined;\n },\n\n writeChatHandler(value: string): void {\n kv.write('chat-handler', value);\n },\n };\n}\n\n// ============================================================================\n// PublishedOutputsStore\n// ============================================================================\n\nexport type OutputStoreEvent =\n | { kind: 'computed_values'; cardId: string; values: Record<string, unknown> }\n | { kind: 'data_object'; key: string; payload: unknown }\n | { kind: 'status'; status: unknown };\n\nexport interface PublishedOutputsStore {\n writeComputedValues(cardId: string, values: Record<string, unknown>): void;\n readComputedValues(cardId: string): unknown | null;\n readAllComputedValues(): Record<string, unknown>;\n writeDataObjects(data: Record<string, unknown>): void;\n readDataObject(key: string): unknown | null;\n readAllDataObjects(): Record<string, unknown>;\n writeStatusSnapshot(status: unknown): void;\n readStatusSnapshot(): unknown | null;\n}\n\nexport function createPublishedOutputsStore(kv: KVStorage): PublishedOutputsStore {\n return {\n writeComputedValues(cardId, values) {\n kv.write(`cards/${cardId}/computed_values`, values);\n },\n readComputedValues(cardId) { return kv.read(`cards/${cardId}/computed_values`); },\n readAllComputedValues() {\n const out: Record<string, unknown> = {};\n for (const key of kv.listKeys('cards/')) {\n const m = key.match(/^cards\\/([^/]+)\\/computed_values$/);\n if (m) out[m[1]] = kv.read(key);\n }\n return out;\n },\n writeDataObjects(data) {\n for (const [token, payload] of Object.entries(data)) {\n if (!token) continue;\n kv.write(`data-objects/${token}`, payload);\n }\n },\n readDataObject(key) { return kv.read(`data-objects/${key}`); },\n readAllDataObjects() {\n const out: Record<string, unknown> = {};\n for (const key of kv.listKeys('data-objects/')) {\n out[key.slice('data-objects/'.length)] = kv.read(key);\n }\n return out;\n },\n writeStatusSnapshot(status) {\n kv.write('status', status);\n },\n readStatusSnapshot() { return kv.read('status'); },\n };\n}\n\n// ============================================================================\n// Future-facing blob and read-model cache interfaces\n// ============================================================================\n\nexport interface FetchedSourcesBlobStore {\n readBlob(blobRef: string): Promise<unknown | null>;\n}\n\nexport interface PublishedBoardStatusCache {\n writeStatusBestEffort(scopeId: string, statusPayload: unknown): Promise<void>;\n readStatus(scopeId: string): Promise<unknown | null>;\n}\n\n// ============================================================================\n// ---- from board-live-cards-lib-types.ts ----\n// ============================================================================\n\nexport interface SourceRuntimeEntry {\n lastRequestedAt?: string;\n lastFetchedAt?: string;\n lastError?: string;\n queueRequestedAt?: string;\n}\n\nexport type FetchRuntimeEntry = SourceRuntimeEntry;\n\nexport interface SourceTokenPayload {\n cbk: string;\n rg: string;\n br: string;\n cid: string;\n b: string;\n d: string;\n cs?: string;\n rqt: string;\n}\n\nexport function isSourceInFlight(entry: FetchRuntimeEntry | undefined): boolean {\n if (!entry?.lastRequestedAt) return false;\n return !entry.lastFetchedAt || entry.lastFetchedAt < entry.lastRequestedAt;\n}\n\nexport function decideSourceAction(\n entry: FetchRuntimeEntry | undefined,\n queueRequestedAt: string,\n): 'dispatch' | 'in-flight' | 'idle' {\n if (!entry?.lastRequestedAt) return 'dispatch';\n const inFlight = isSourceInFlight(entry);\n if (inFlight) return 'in-flight';\n if (!entry.lastFetchedAt) return 'dispatch';\n if (entry.lastFetchedAt < queueRequestedAt) return 'dispatch';\n return 'idle';\n}\n\nexport function nextEntryAfterFetchDelivery<T extends FetchRuntimeEntry>(\n entry: T,\n fetchedAt: string,\n): T {\n const next = { ...entry, lastFetchedAt: fetchedAt };\n delete (next as FetchRuntimeEntry).lastError;\n return next as T;\n}\n\nexport function nextEntryAfterFetchFailure<T extends FetchRuntimeEntry>(\n entry: T,\n reason: string,\n): T {\n const next = { ...entry, lastError: reason };\n delete (next as FetchRuntimeEntry).lastFetchedAt;\n return next as T;\n}\n\nexport interface CardHandlerAdapters {\n cardStore: CardStore;\n cardRuntimeStore: CardRuntimeStore;\n fetchedSourcesStore: FetchedSourcesStore;\n outputStore: PublishedOutputsStore;\n executionRequestStore: ExecutionRequestStore;\n}\n\nexport interface CommandResponse<T extends Record<string, unknown> = Record<string, unknown>> {\n status: 'success' | 'error';\n data: T;\n error?: string;\n}\n\nexport const Resp = {\n success<T extends Record<string, unknown>>(data: T): CommandResponse<T> {\n return { status: 'success', data };\n },\n\n error(error: string, data: Record<string, unknown> = {}): CommandResponse {\n return { status: 'error', data, error };\n },\n\n getStatus(r: CommandResponse): 'success' | 'error' {\n return r.status;\n },\n\n getData<T extends Record<string, unknown>>(r: CommandResponse<T>): T {\n return r.data;\n },\n\n isSuccess(r: CommandResponse): boolean {\n return r.status === 'success';\n },\n} as const;\n\n// ============================================================================\n// ---- from board-live-cards-lib-board-status.ts ----\n// ============================================================================\n\nexport interface BoardStatusCard {\n name: string;\n status: string;\n error?: {\n message: string;\n code?: string;\n at?: string;\n source?: 'task-runtime' | 'source-fetch' | 'timeout' | 'unknown';\n };\n requires: string[];\n requires_satisfied: string[];\n requires_missing: string[];\n provides_declared: string[];\n provides_runtime: string[];\n blocked_by: string[];\n unblocks: string[];\n runtime: {\n attempt_count: number;\n restart_count: number;\n in_progress_since: string | null;\n last_transition_at: string | null;\n last_completed_at: string | null;\n last_restarted_at: string | null;\n status_age_ms: number | null;\n };\n}\n\nexport interface BoardStatusObject {\n schema_version: 'v1';\n meta: {\n board: {\n path: string;\n };\n };\n summary: {\n card_count: number;\n completed: number;\n eligible: number;\n pending: number;\n blocked: number;\n unresolved: number;\n failed?: number;\n in_progress?: number;\n orphan_cards?: number;\n topology?: {\n edge_count: number;\n max_fan_out_card: string | null;\n max_fan_out: number;\n };\n };\n cards: BoardStatusCard[];\n}\n\nexport function buildBoardStatusObject(boardPath: string, live: LiveGraph): BoardStatusObject {\n const taskState = live.state.tasks;\n const taskConfig = live.config.tasks;\n const cardNames = Object.keys(taskState);\n const sched = schedule(live);\n\n const statusCounts = {\n completed: 0,\n failed: 0,\n in_progress: 0,\n pending: 0,\n blocked: 0,\n unresolved: 0,\n };\n\n const waitingByCard = new Map<string, string[]>();\n for (const p of sched.pending) waitingByCard.set(p.taskName, p.waitingOn);\n for (const u of sched.unresolved) waitingByCard.set(u.taskName, u.missingTokens);\n for (const b of sched.blocked) waitingByCard.set(b.taskName, b.failedTokens);\n\n const dependentsByToken = new Map<string, string[]>();\n for (const [name, cfg] of Object.entries(taskConfig)) {\n for (const token of cfg.requires ?? []) {\n const dependents = dependentsByToken.get(token) ?? [];\n dependents.push(name);\n dependentsByToken.set(token, dependents);\n }\n }\n\n const cards: BoardStatusCard[] = cardNames.sort().map((name) => {\n const state = taskState[name] as {\n status: string;\n data?: Record<string, unknown>;\n error?: string;\n startedAt?: string;\n completedAt?: string;\n failedAt?: string;\n lastUpdated?: string;\n executionCount?: number;\n retryCount?: number;\n };\n const cfg = taskConfig[name] ?? { requires: [], provides: [] };\n\n if (state.status === 'completed') statusCounts.completed += 1;\n else if (state.status === 'failed') statusCounts.failed += 1;\n else if (state.status === 'in-progress') statusCounts.in_progress += 1;\n\n const requires = cfg.requires ?? [];\n const provides = cfg.provides ?? [];\n const runtimeKeys = Object.keys(state.data ?? {}).sort();\n const requiresSatisfied = requires.filter(token => live.state.availableOutputs.includes(token));\n const requiresMissing = requires.filter(token => !live.state.availableOutputs.includes(token));\n const blockedBy = waitingByCard.get(name) ?? requiresMissing;\n\n const unblocks = new Set<string>();\n for (const token of provides) {\n for (const dependent of dependentsByToken.get(token) ?? []) {\n if (dependent !== name) unblocks.add(dependent);\n }\n }\n\n const lastFailureAt = state.failedAt;\n const error = state.error\n ? {\n message: state.error,\n code: 'TASK_FAILED',\n at: lastFailureAt,\n source: 'task-runtime' as const,\n }\n : undefined;\n\n return {\n name,\n status: state.status,\n error,\n requires,\n requires_satisfied: requiresSatisfied,\n requires_missing: requiresMissing,\n provides_declared: provides,\n provides_runtime: runtimeKeys,\n blocked_by: blockedBy,\n unblocks: Array.from(unblocks).sort(),\n runtime: {\n attempt_count: state.executionCount ?? 0,\n restart_count: state.retryCount ?? 0,\n in_progress_since: state.status === 'in-progress' ? (state.startedAt ?? null) : null,\n last_transition_at: state.lastUpdated ?? null,\n last_completed_at: state.completedAt ?? null,\n last_restarted_at: state.startedAt ?? null,\n // Keep status snapshots immutable across reads: this field must not depend on wall-clock pull time.\n status_age_ms: state.lastUpdated ? 0 : null,\n },\n };\n });\n\n statusCounts.pending = sched.pending.length;\n statusCounts.blocked = sched.blocked.length;\n statusCounts.unresolved = sched.unresolved.length;\n\n const fanOut = cards\n .map(c => ({ name: c.name, fanOut: c.unblocks.length }))\n .sort((a, b) => b.fanOut - a.fanOut || a.name.localeCompare(b.name));\n const maxFanOut = fanOut.length > 0 ? fanOut[0] : { name: null, fanOut: 0 };\n\n const allRequires = new Set<string>();\n for (const cfg of Object.values(taskConfig)) {\n for (const r of cfg.requires ?? []) allRequires.add(r);\n }\n let orphanCards = 0;\n for (const [name, cfg] of Object.entries(taskConfig)) {\n const requiresNone = (cfg.requires ?? []).length === 0;\n const providesList = cfg.provides ?? [];\n const feedsAny = providesList.some(p => (dependentsByToken.get(p) ?? []).some(d => d !== name));\n if (requiresNone && !feedsAny) orphanCards += 1;\n }\n\n return {\n schema_version: 'v1',\n meta: { board: { path: boardPath } },\n summary: {\n card_count: cardNames.length,\n completed: statusCounts.completed,\n eligible: sched.eligible.length,\n pending: statusCounts.pending,\n blocked: statusCounts.blocked,\n unresolved: statusCounts.unresolved,\n failed: statusCounts.failed,\n in_progress: statusCounts.in_progress,\n orphan_cards: orphanCards,\n topology: {\n edge_count: Array.from(allRequires).length,\n max_fan_out_card: maxFanOut.name,\n max_fan_out: maxFanOut.fanOut,\n },\n },\n cards,\n };\n}\n\n// ============================================================================\n// ---- from board-live-cards-lib-card-handler.ts ----\n// ============================================================================\n\nfunction nowHighRes(): string {\n return new Date().toISOString();\n}\n\nexport function createCardHandlerFn(\n baseRef: KindValueRef,\n journalId: string,\n adapters: CardHandlerAdapters,\n taskCompletedFn: (taskName: string, data: Record<string, unknown>) => void,\n _taskFailedFn: (taskName: string, error: string) => void,\n writeComputedValuesFn?: (cardId: string, values: Record<string, unknown>) => void,\n writeDataObjectsFn?: (data: Record<string, unknown>) => void,\n notifyCardFn?: (cardId: string, card: LiveCard) => void,\n): TaskHandlerFn {\n return async (input) => {\n const pendingRequests: ExecutionRequestEntry[] = [];\n const card = adapters.cardStore.readCard(input.nodeId);\n if (!card) return 'task-initiate-failure';\n\n const cardId = card.id as string;\n // Notify on fresh evaluations only (not source-fetch callbacks).\n if (!input.update) notifyCardFn?.(cardId, card);\n const cardState = (card.card_data ?? {}) as Record<string, unknown>;\n const allSources: ComputeSource[] = (card.source_defs ?? []) as ComputeSource[];\n const requiredSources = allSources.filter(s => s.optionalForCompletionGating !== true);\n\n let state: CardRuntimeSnapshot = adapters.cardRuntimeStore.readRuntime(cardId);\n let dirty = false;\n\n const flush = (): void => {\n if (!dirty) return;\n adapters.cardRuntimeStore.writeRuntime(cardId, state);\n dirty = false;\n };\n\n const getSourceEntry = (outputFile: string): SourceRuntimeEntry =>\n ({ ...(state._sources[outputFile] ?? {}) });\n const setSourceEntry = (outputFile: string, entry: SourceRuntimeEntry): void => {\n state._sources[outputFile] = entry; dirty = true;\n };\n\n const currentExecutionCount = input.taskState?.executionCount ?? 0;\n const lastExecCount = state._lastExecutionCount;\n if (typeof lastExecCount === 'number' && lastExecCount !== currentExecutionCount) {\n state._sources = {}; dirty = true;\n }\n if (lastExecCount !== currentExecutionCount) {\n state._lastExecutionCount = currentExecutionCount; dirty = true;\n }\n\n if (input.update) {\n const u = input.update;\n const outputFile = u.outputFile as string;\n if (outputFile) {\n const entry = getSourceEntry(outputFile);\n if (u.failure) {\n setSourceEntry(outputFile, nextEntryAfterFetchFailure(entry, (u.reason as string | undefined) ?? 'unknown'));\n } else {\n const incomingRqt = u.rqt as string;\n if (!entry.lastFetchedAt || incomingRqt > entry.lastFetchedAt) {\n const deliveryToken = typeof u.deliveryToken === 'string' ? u.deliveryToken : undefined;\n if (deliveryToken) {\n adapters.fetchedSourcesStore.commitSourceData(cardId, outputFile, deliveryToken);\n }\n setSourceEntry(outputFile, nextEntryAfterFetchDelivery(entry, incomingRqt));\n }\n }\n flush();\n }\n }\n\n const sourcesData: Record<string, unknown> = {};\n for (const src of allSources) {\n if (src.outputFile) {\n const content = adapters.fetchedSourcesStore.readSourceData(cardId, src.outputFile as string);\n if (content !== null) {\n sourcesData[src.bindTo] = content;\n }\n }\n }\n\n const requires: Record<string, unknown> = {};\n for (const [token, taskData] of Object.entries(input.state ?? {})) {\n if (taskData !== null && typeof taskData === 'object' && !Array.isArray(taskData)) {\n const unwrapped = (taskData as Record<string, unknown>)[token];\n requires[token] = unwrapped !== undefined ? unwrapped : taskData;\n } else {\n requires[token] = taskData;\n }\n }\n\n const computeNode: ComputeNode = {\n id: cardId,\n card_data: { ...cardState },\n requires,\n source_defs: allSources,\n compute: card.compute as ComputeStep[] | undefined,\n };\n computeNode._sourcesData = sourcesData;\n if (card.compute) {\n CardCompute.runSync(computeNode, { sourcesData });\n }\n\n (writeComputedValuesFn ?? adapters.outputStore.writeComputedValues.bind(adapters.outputStore))(cardId, computeNode.computed_values ?? {});\n\n const enrichedCard = { ...card };\n const enrichedSources = CardCompute.enrichSourcesSync(\n Array.isArray(card.source_defs) ? card.source_defs : undefined,\n {\n card_data: card.card_data as Record<string, unknown>,\n requires,\n },\n );\n\n const dir = baseRef.value;\n enrichedCard.source_defs = Array.isArray(enrichedSources)\n ? enrichedSources.map(src => ({\n ...src,\n boardDir: typeof src.boardDir === 'string' && src.boardDir ? src.boardDir : dir,\n }))\n : enrichedSources;\n\n const now = nowHighRes();\n const runQueuedAt = input.update ? undefined : now;\n\n const undeliveredRequired = requiredSources.filter(s => {\n const outputFile = s.outputFile;\n if (typeof outputFile !== 'string' || !outputFile) return true;\n let entry = getSourceEntry(outputFile);\n if (runQueuedAt) {\n entry = { ...entry, queueRequestedAt: runQueuedAt };\n setSourceEntry(outputFile, entry);\n }\n const qrt = entry.queueRequestedAt ?? entry.lastRequestedAt ?? now;\n const action = decideSourceAction(entry, qrt);\n if (action === 'in-flight') return false;\n return action === 'dispatch';\n });\n\n flush();\n\n if (undeliveredRequired.length > 0) {\n let stampedAny = false;\n let dispatchRqt = now;\n for (const src of undeliveredRequired) {\n const outputFile = src.outputFile;\n if (typeof outputFile !== 'string' || !outputFile) continue;\n const entry = getSourceEntry(outputFile);\n const queuedAt = entry.queueRequestedAt ?? now;\n setSourceEntry(outputFile, { ...entry, lastRequestedAt: queuedAt });\n dispatchRqt = queuedAt;\n stampedAny = true;\n }\n if (stampedAny) flush();\n if (!stampedAny) return 'task-initiated';\n\n pendingRequests.push({ taskKind: 'source-fetch', payload: { boardRef: serializeRef(baseRef), enrichedCard: enrichedCard as Record<string, unknown>, callbackToken: input.callbackToken, rqt: dispatchRqt } });\n adapters.executionRequestStore.appendEntries(journalId, pendingRequests);\n return 'task-initiated';\n }\n\n const providesBindings = (card.provides ?? []) as { bindTo: string; ref: string }[];\n const data: Record<string, unknown> = {};\n for (const { bindTo, ref } of providesBindings) {\n data[bindTo] = CardCompute.resolve(computeNode, ref);\n }\n\n (writeDataObjectsFn ?? adapters.outputStore.writeDataObjects.bind(adapters.outputStore))(data);\n\n const undeliveredOptional = allSources.filter(s => {\n if (s.optionalForCompletionGating !== true) return false;\n const entry = getSourceEntry(s.outputFile as string);\n if (!entry.lastRequestedAt) return true;\n if (!entry.lastFetchedAt) return true;\n return entry.lastFetchedAt <= entry.lastRequestedAt;\n });\n if (undeliveredOptional.length > 0) {\n pendingRequests.push({ taskKind: 'source-fetch', payload: { boardRef: serializeRef(baseRef), enrichedCard: enrichedCard as Record<string, unknown>, callbackToken: input.callbackToken, rqt: now } });\n }\n\n taskCompletedFn(input.nodeId, data);\n if (pendingRequests.length > 0) adapters.executionRequestStore.appendEntries(journalId, pendingRequests);\n return 'task-initiated';\n };\n}\n\n// ============================================================================\n// ---- pure constants / codecs lifted from board-live-cards-cli.ts ----\n// ============================================================================\n\nexport const EMPTY_CONFIG: GraphConfig = { settings: { completion: 'manual', refreshStrategy: 'data-changed' }, tasks: {} } as GraphConfig;\n\n/** Envelope stored in the snapshot store — wraps the LiveGraph snapshot with journal pointer. */\nexport interface BoardEnvelope {\n lastDrainedJournalId: string;\n graph: LiveGraphSnapshot;\n}\n\nexport function boardEnvelopeToSnapshotEntries(envelope: BoardEnvelope): Record<string, unknown> {\n return {\n [BOARD_GRAPH_KEY]: envelope.graph,\n [BOARD_LAST_JOURNAL_PROCESSED_ID_KEY]: envelope.lastDrainedJournalId,\n };\n}\n\nexport function snapshotEntriesToBoardEnvelope(entries: Record<string, unknown>): BoardEnvelope {\n const graph = entries[BOARD_GRAPH_KEY] as LiveGraphSnapshot | undefined;\n const lastDrainedJournalId = entries[BOARD_LAST_JOURNAL_PROCESSED_ID_KEY] as string | undefined;\n if (!graph || typeof graph !== 'object') {\n throw new Error(`State snapshot is missing required key: ${BOARD_GRAPH_KEY}`);\n }\n return {\n graph,\n lastDrainedJournalId: typeof lastDrainedJournalId === 'string' ? lastDrainedJournalId : '',\n };\n}\n\nexport interface CardInventoryEntry {\n cardId: string;\n cardFilePath: string;\n addedAt: string;\n}\n\nexport interface CardInventoryIndex {\n byCardId: Map<string, CardInventoryEntry>;\n byCardPath: Map<string, CardInventoryEntry>;\n}\n\n/**\n * Transform a LiveCard into a TaskConfig for the reactive graph.\n * Every card gets handler: 'card-handler'.\n */\nexport function liveCardToTaskConfig(card: LiveCard): TaskConfig {\n const requires = card.requires as string[] | undefined;\n const provides = (card.provides as Array<{ bindTo: string }> | undefined)?.map(p => p.bindTo) ?? [];\n\n return {\n requires: requires && requires.length > 0 ? requires : undefined,\n provides,\n taskHandlers: ['card-handler'],\n description: (card.meta as { title?: string } | undefined)?.title ?? card.id,\n };\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 * card-store-browser-api.ts\n *\n * Simple browser-facing card store API.\n * Wraps createCardStore() + createLocalStorageCardStorageAdapter()\n * into a minimal read/write interface suitable for browser consumption.\n */\n\nimport { createCardStore } from '../common/board-live-cards-lib.js';\nimport type { LiveCard } from '../common/board-live-cards-lib.js';\nimport { createLocalStorageCardStorageAdapter } from './storage-localstorage-adapters.js';\n\nexport type { LiveCard };\n\nexport interface BrowserCardStoreApi {\n getCard(id: string): LiveCard | null;\n getAllCards(): LiveCard[];\n upsertCard(card: LiveCard): void;\n removeCard(id: string): void;\n}\n\n/**\n * Create a browser card store backed by localStorage.\n *\n * @param namespace - localStorage key prefix (e.g. 'my-board:cards').\n * Multiple stores can coexist by using distinct namespaces.\n */\nexport function createBrowserCardStoreApi(namespace: string): BrowserCardStoreApi {\n const adapter = createLocalStorageCardStorageAdapter(namespace);\n const store = createCardStore(adapter);\n\n return {\n getCard(id: string): LiveCard | null {\n return store.readCard(id);\n },\n getAllCards(): LiveCard[] {\n return store.readAllCards();\n },\n upsertCard(card: LiveCard): void {\n const key = adapter.defaultCardKey(card.id);\n store.writeCard(card.id, card, key);\n },\n removeCard(id: string): void {\n store.removeCard(id);\n },\n };\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { L as LiveCard } from '../../board-live-cards-lib-Bg6EvCo5.cjs';
2
+ import '../../types-BBhqYGhE.cjs';
3
+
4
+ /**
5
+ * card-store-browser-api.ts
6
+ *
7
+ * Simple browser-facing card store API.
8
+ * Wraps createCardStore() + createLocalStorageCardStorageAdapter()
9
+ * into a minimal read/write interface suitable for browser consumption.
10
+ */
11
+
12
+ interface BrowserCardStoreApi {
13
+ getCard(id: string): LiveCard | null;
14
+ getAllCards(): LiveCard[];
15
+ upsertCard(card: LiveCard): void;
16
+ removeCard(id: string): void;
17
+ }
18
+ /**
19
+ * Create a browser card store backed by localStorage.
20
+ *
21
+ * @param namespace - localStorage key prefix (e.g. 'my-board:cards').
22
+ * Multiple stores can coexist by using distinct namespaces.
23
+ */
24
+ declare function createBrowserCardStoreApi(namespace: string): BrowserCardStoreApi;
25
+
26
+ export { type BrowserCardStoreApi, LiveCard, createBrowserCardStoreApi };
@@ -0,0 +1,26 @@
1
+ import { L as LiveCard } from '../../board-live-cards-lib-jM2uYG1v.js';
2
+ import '../../types-BBhqYGhE.js';
3
+
4
+ /**
5
+ * card-store-browser-api.ts
6
+ *
7
+ * Simple browser-facing card store API.
8
+ * Wraps createCardStore() + createLocalStorageCardStorageAdapter()
9
+ * into a minimal read/write interface suitable for browser consumption.
10
+ */
11
+
12
+ interface BrowserCardStoreApi {
13
+ getCard(id: string): LiveCard | null;
14
+ getAllCards(): LiveCard[];
15
+ upsertCard(card: LiveCard): void;
16
+ removeCard(id: string): void;
17
+ }
18
+ /**
19
+ * Create a browser card store backed by localStorage.
20
+ *
21
+ * @param namespace - localStorage key prefix (e.g. 'my-board:cards').
22
+ * Multiple stores can coexist by using distinct namespaces.
23
+ */
24
+ declare function createBrowserCardStoreApi(namespace: string): BrowserCardStoreApi;
25
+
26
+ export { type BrowserCardStoreApi, LiveCard, createBrowserCardStoreApi };
@@ -0,0 +1,2 @@
1
+ import x from'jsonata';import {createRequire}from'module';import'ajv-formats';var w=createRequire(import.meta.url);w("./jsonata-sync.cjs")??x;function y(n,o){function e(){return n.readIndex()??{}}function r(t,s,i){let a=String(s||"").split(".").filter(Boolean);if(a.length===0)return i&&typeof i=="object"&&!Array.isArray(i)?i:{value:i};let u={...t},c=u;for(let d=0;d<a.length-1;d++){let l=a[d],f=c[l],m=f&&typeof f=="object"&&!Array.isArray(f)?{...f}:{};c[l]=m,c=m;}return c[a[a.length-1]]=i,u}return {readCard(t){let s=e()[t];return !s||!n.cardExists(s.key)?null:n.readCard(s.key)},readCardKey(t){return e()[t]?.key??null},readAllCards(){let t=[];for(let[s,i]of Object.entries(e())){if(!n.cardExists(i.key))continue;let a=n.readCard(i.key);a?t.push(a):o?.(`[card-store] could not read card "${s}" at key "${i.key}"`);}return t},readChecksumIndex(){let t={};for(let[s,i]of Object.entries(e()))t[s]=i.checksum;return t},changedSince(t){let s=e(),i=[];for(let[a,u]of Object.entries(s))t[a]!==u.checksum&&i.push(a);for(let a of Object.keys(t))s[a]||i.push(a);return i},validateUpsert(t,s){let i=e(),a=i[t],u=Object.entries(i).find(([,c])=>c.key===s);return a&&a.key!==s?{ok:false,error:`Card id "${t}" is already mapped to key "${a.key}", cannot remap to "${s}"`}:u&&u[0]!==t?{ok:false,error:`Key "${s}" is already mapped to card id "${u[0]}", cannot remap to "${t}"`}:{ok:true}},writeCard(t,s,i){let a=e(),u=i??a[t]?.key??n.defaultCardKey(t),c=n.writeCard(u,s);a[t]={key:u,checksum:c,updatedAt:new Date().toISOString()},n.writeIndex(a);},patchCard(t,s,i){let a=e(),u=a[t];if(!u||!n.cardExists(u.key))throw new Error(`card "${t}" not found`);let c=n.readCard(u.key);if(!c||typeof c!="object"||Array.isArray(c))throw new Error(`card "${t}" is not patchable`);let d=r(c,s,i),l=n.writeCard(u.key,d);a[t]={key:u.key,checksum:l,updatedAt:new Date().toISOString()},n.writeIndex(a);},removeCard(t){let s=e();s[t]&&(delete s[t],n.writeIndex(s));},readIndex(){return e()}}}function p(n){if(n==null||typeof n!="object")return JSON.stringify(n);if(Array.isArray(n))return `[${n.map(p).join(",")}]`;let o=n;return `{${Object.keys(o).sort().map(r=>`${JSON.stringify(r)}:${p(o[r])}`).join(",")}}`}function g(n,o){let e=o>>>0;for(let r=0;r<n.length;r++)e^=n.charCodeAt(r),e=Math.imul(e,16777619)>>>0;return e}function b(n){let o=p(n),e=g(o,2166136261),r=g(o,3735928559),t=g(o,19088743),s=g(o,4277009102);return [e,r,t,s].map(i=>i.toString(16).padStart(8,"0")).join("")}function v(n){function o(e){return `${n}:kv:${e}`}return {read(e){let r=globalThis.localStorage.getItem(o(e));if(r===null)return null;try{return JSON.parse(r)}catch{return null}},write(e,r){globalThis.localStorage.setItem(o(e),JSON.stringify(r));},delete(e){globalThis.localStorage.removeItem(o(e));},listKeys(e){let r=o(e??""),t=[];for(let s=0;s<globalThis.localStorage.length;s++){let i=globalThis.localStorage.key(s);i!==null&&i.startsWith(r)&&t.push(i.slice(o("").length));}return t}}}function h(n,o){let e={...n};for(let[r,t]of Object.entries(o))t!==null&&typeof t=="object"&&!Array.isArray(t)&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])?e[r]=h(e[r],t):e[r]=t;return e}function S(n,o,e){if(o.length===0)return n;let[r,...t]=o;if(t.length===0)return {...n,[r]:e};let s=n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])?n[r]:{};return {...n,[r]:S(s,t,e)}}function E(n){let o=v(n);return {read:e=>o.read(e),get(e,r){let t=o.read(e);if(t===null)return null;let s=t;for(let i of r.split(".").filter(Boolean)){if(s===null||typeof s!="object"||Array.isArray(s))return null;s=s[i]??null;}return s??null},write:(e,r)=>o.write(e,r),delete:e=>o.delete(e),listKeys:e=>o.listKeys(e),shallowMerge(e,r){let t=o.read(e)??{};o.write(e,{...t,...r});},deepMerge(e,r){let t=o.read(e)??{};o.write(e,h(t,r));},patch(e,r,t){let s=o.read(e)??{},i=r.split(".").filter(Boolean);o.write(e,S(s,i,t));}}}function k(n){let o=E(n);return {readIndex(){return o.read("_index")},writeIndex(e){o.write("_index",e);},readCard(e){return o.read(e)},writeCard(e,r){return o.write(e,r),b(r)},cardExists(e){return o.read(e)!==null},defaultCardKey(e){return e}}}function ye(n){let o=k(n),e=y(o);return {getCard(r){return e.readCard(r)},getAllCards(){return e.readAllCards()},upsertCard(r){let t=o.defaultCardKey(r.id);e.writeCard(r.id,r,t);},removeCard(r){e.removeCard(r);}}}export{ye as createBrowserCardStoreApi};//# sourceMappingURL=card-store-browser-api.js.map
2
+ //# sourceMappingURL=card-store-browser-api.js.map