@lamplitisles/dsh-nanocodex 0.1.0-beta.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 (276) hide show
  1. package/LICENSE +65 -0
  2. package/NOTICE +2 -0
  3. package/README.md +182 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/cordis.patch.yml +52 -0
  6. package/dist/index.d.ts +435 -0
  7. package/dist/index.js +3405 -0
  8. package/dist/standard-preset.d.ts +7 -0
  9. package/dist/standard-preset.js +25 -0
  10. package/engine-release.json +14 -0
  11. package/package.json +119 -0
  12. package/presets/standard/agent.cordis.yml +6 -0
  13. package/presets/standard/preset.yml +2 -0
  14. package/vendor/nanocodex/README.md +1344 -0
  15. package/vendor/nanocodex/actions/events.d.mts +10 -0
  16. package/vendor/nanocodex/actions/events.mjs +163 -0
  17. package/vendor/nanocodex/actions/index.d.mts +13 -0
  18. package/vendor/nanocodex/actions/index.mjs +35 -0
  19. package/vendor/nanocodex/actions/session.d.mts +66 -0
  20. package/vendor/nanocodex/actions/session.mjs +59 -0
  21. package/vendor/nanocodex/actions/turn.d.mts +59 -0
  22. package/vendor/nanocodex/actions/turn.mjs +37 -0
  23. package/vendor/nanocodex/actions/voice.d.mts +22 -0
  24. package/vendor/nanocodex/actions/voice.mjs +50 -0
  25. package/vendor/nanocodex/browser/Agent.d.mts +55 -0
  26. package/vendor/nanocodex/browser/Agent.mjs +11 -0
  27. package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
  28. package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
  29. package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
  30. package/vendor/nanocodex/browser/Transport.d.mts +74 -0
  31. package/vendor/nanocodex/browser/Transport.mjs +61 -0
  32. package/vendor/nanocodex/browser/Voice.d.mts +71 -0
  33. package/vendor/nanocodex/browser/Voice.mjs +291 -0
  34. package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
  35. package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
  36. package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
  37. package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
  38. package/vendor/nanocodex/browser/config.d.mts +36 -0
  39. package/vendor/nanocodex/browser/config.mjs +439 -0
  40. package/vendor/nanocodex/browser/engine.mjs +13 -0
  41. package/vendor/nanocodex/browser/harness.mjs +55 -0
  42. package/vendor/nanocodex/browser/host.d.mts +83 -0
  43. package/vendor/nanocodex/browser/host.mjs +555 -0
  44. package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
  45. package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
  46. package/vendor/nanocodex/browser/index.d.mts +92 -0
  47. package/vendor/nanocodex/browser/index.mjs +23 -0
  48. package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
  49. package/vendor/nanocodex/browser/workspace.d.mts +16 -0
  50. package/vendor/nanocodex/browser/workspace.mjs +107 -0
  51. package/vendor/nanocodex/cloud/Client.d.mts +83 -0
  52. package/vendor/nanocodex/cloud/Client.mjs +231 -0
  53. package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
  54. package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
  55. package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
  56. package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
  57. package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
  58. package/vendor/nanocodex/cloud/Errors.mjs +39 -0
  59. package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
  60. package/vendor/nanocodex/cloud/Principal.mjs +88 -0
  61. package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
  62. package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
  63. package/vendor/nanocodex/cloud/Transport.mjs +454 -0
  64. package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
  65. package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
  66. package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
  67. package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
  68. package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
  69. package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
  70. package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
  71. package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
  72. package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
  73. package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
  74. package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
  75. package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
  76. package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
  77. package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
  78. package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
  79. package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
  80. package/vendor/nanocodex/cloud/index.d.mts +30 -0
  81. package/vendor/nanocodex/cloud/index.mjs +9 -0
  82. package/vendor/nanocodex/cloud/internal.mjs +533 -0
  83. package/vendor/nanocodex/cloud/mercator.mjs +108 -0
  84. package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
  85. package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
  86. package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
  87. package/vendor/nanocodex/cloud/server/index.mjs +1 -0
  88. package/vendor/nanocodex/cloud/types.d.mts +211 -0
  89. package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
  90. package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
  91. package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
  92. package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
  93. package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
  94. package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
  95. package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
  96. package/vendor/nanocodex/cloudflare/index.mjs +6 -0
  97. package/vendor/nanocodex/host/Agent.d.mts +55 -0
  98. package/vendor/nanocodex/host/Agent.mjs +1 -0
  99. package/vendor/nanocodex/host/index.d.mts +82 -0
  100. package/vendor/nanocodex/host/index.mjs +10 -0
  101. package/vendor/nanocodex/index.d.mts +81 -0
  102. package/vendor/nanocodex/index.mjs +12 -0
  103. package/vendor/nanocodex/internal.mjs +1275 -0
  104. package/vendor/nanocodex/managed/Agent.d.mts +380 -0
  105. package/vendor/nanocodex/managed/Agent.mjs +1694 -0
  106. package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
  107. package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
  108. package/vendor/nanocodex/managed/README.md +241 -0
  109. package/vendor/nanocodex/managed/Voice.mjs +202 -0
  110. package/vendor/nanocodex/managed/index.d.mts +20 -0
  111. package/vendor/nanocodex/managed/index.mjs +2 -0
  112. package/vendor/nanocodex/managed/internal.mjs +119 -0
  113. package/vendor/nanocodex/node/Agent.d.mts +50 -0
  114. package/vendor/nanocodex/node/Agent.mjs +203 -0
  115. package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
  116. package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
  117. package/vendor/nanocodex/node/Transport.d.mts +44 -0
  118. package/vendor/nanocodex/node/Transport.mjs +46 -0
  119. package/vendor/nanocodex/node/host.mjs +731 -0
  120. package/vendor/nanocodex/node/index.d.mts +73 -0
  121. package/vendor/nanocodex/node/index.mjs +17 -0
  122. package/vendor/nanocodex/node/workspace.d.mts +10 -0
  123. package/vendor/nanocodex/node/workspace.mjs +198 -0
  124. package/vendor/nanocodex/package.json +200 -0
  125. package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
  126. package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
  127. package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
  128. package/vendor/nanocodex/pkg-node/package.json +1 -0
  129. package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
  130. package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
  131. package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
  132. package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
  133. package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
  134. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
  135. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
  136. package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
  137. package/vendor/nanocodex/pkg-web/package.json +1 -0
  138. package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
  139. package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
  140. package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
  141. package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
  142. package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
  143. package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
  144. package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
  145. package/vendor/nanocodex/runtime/durability.mjs +153 -0
  146. package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
  147. package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
  148. package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
  149. package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
  150. package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
  151. package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
  152. package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
  153. package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
  154. package/vendor/nanocodex/runtime/subagents.mjs +54 -0
  155. package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
  156. package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
  157. package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
  158. package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
  159. package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
  160. package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
  161. package/vendor/nanocodex/runtime/utf8.mjs +1 -0
  162. package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
  163. package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
  164. package/vendor/nanocodex/runtime/workspace.mjs +1 -0
  165. package/vendor/nanocodex/tools/Tools.d.mts +56 -0
  166. package/vendor/nanocodex/tools/Tools.mjs +136 -0
  167. package/vendor/nanocodex/tools/artifact.d.mts +1 -0
  168. package/vendor/nanocodex/tools/artifact.mjs +1 -0
  169. package/vendor/nanocodex/tools/attachment.mjs +1 -0
  170. package/vendor/nanocodex/tools/bash.d.mts +1 -0
  171. package/vendor/nanocodex/tools/bash.mjs +1 -0
  172. package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
  173. package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
  174. package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
  175. package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
  176. package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
  177. package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
  178. package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
  179. package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
  180. package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
  181. package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
  182. package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  183. package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
  184. package/vendor/nanocodex/tools/browser/index.mjs +174 -0
  185. package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
  186. package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
  187. package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
  188. package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
  189. package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
  190. package/vendor/nanocodex/tools/dataset.d.mts +1 -0
  191. package/vendor/nanocodex/tools/dataset.mjs +1 -0
  192. package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
  193. package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
  194. package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
  195. package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
  196. package/vendor/nanocodex/tools/index.d.mts +49 -0
  197. package/vendor/nanocodex/tools/index.mjs +19 -0
  198. package/vendor/nanocodex/tools/namedTool.mjs +1 -0
  199. package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
  200. package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
  201. package/vendor/nanocodex/tools/ssh.d.mts +1 -0
  202. package/vendor/nanocodex/tools/ssh.mjs +1 -0
  203. package/vendor/nanocodex/tools/standard.mjs +1 -0
  204. package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
  205. package/vendor/nanocodex/types.d.mts +663 -0
  206. package/vendor/nanocodex/wasm.d.mts +4 -0
  207. package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
  208. package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
  209. package/vendor/nanocodex/worker/index.d.mts +18 -0
  210. package/vendor/nanocodex/worker/index.mjs +5 -0
  211. package/vendor/nanocodex-tools/README.md +65 -0
  212. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
  213. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
  214. package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
  215. package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
  216. package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
  217. package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
  218. package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
  219. package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
  220. package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
  221. package/vendor/nanocodex-tools/dist/index.js +14 -0
  222. package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
  223. package/vendor/nanocodex-tools/dist/memory.js +411 -0
  224. package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
  225. package/vendor/nanocodex-tools/dist/namespace.js +338 -0
  226. package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
  227. package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
  228. package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
  229. package/vendor/nanocodex-tools/dist/runtime.js +37 -0
  230. package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
  231. package/vendor/nanocodex-tools/dist/session.js +286 -0
  232. package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
  233. package/vendor/nanocodex-tools/dist/shell.js +553 -0
  234. package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
  235. package/vendor/nanocodex-tools/dist/ssh.js +66 -0
  236. package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
  237. package/vendor/nanocodex-tools/dist/workspace.js +237 -0
  238. package/vendor/nanocodex-tools/package.json +122 -0
  239. package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
  240. package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
  241. package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
  242. package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
  243. package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
  244. package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
  245. package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
  246. package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
  247. package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
  248. package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
  249. package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
  250. package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
  251. package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
  252. package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
  253. package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
  254. package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
  255. package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
  256. package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
  257. package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
  258. package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
  259. package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
  260. package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
  261. package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
  262. package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
  263. package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
  264. package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
  265. package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
  266. package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
  267. package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
  268. package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
  269. package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
  270. package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
  271. package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
  272. package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
  273. package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
  274. package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
  275. package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
  276. package/vendor/nanocodex-tools/tools/x.mjs +91 -0
@@ -0,0 +1,631 @@
1
+ const MAX_REVISION = 18_446_744_073_709_551_615n;
2
+ const MAX_REVISION_TEXT = String(MAX_REVISION);
3
+ const PORTABLE_FORMAT = "nanocodex-durability-state-v1";
4
+ const PORTABLE_PAGE_FORMAT = "nanocodex-durability-state-page-v1";
5
+ const PORTABLE_EXPORT_OWNER = "nanocodex-portable-export";
6
+ const PORTABLE_IMPORT_OWNER = "nanocodex-portable-import";
7
+ const DEFAULT_EXPORT_PAGE_SIZE = 256 * 1024;
8
+ const MAX_EXPORT_PAGE_SIZE = 1024 * 1024;
9
+
10
+ export const sqliteDurabilitySchema = Object.freeze([
11
+ `CREATE TABLE IF NOT EXISTS nanocodex_durable_owners (
12
+ state_id TEXT PRIMARY KEY,
13
+ owner_id TEXT NOT NULL,
14
+ fence TEXT NOT NULL
15
+ )`,
16
+ `CREATE TABLE IF NOT EXISTS nanocodex_durable_states (
17
+ state_id TEXT PRIMARY KEY,
18
+ revision TEXT NOT NULL,
19
+ payload TEXT NOT NULL
20
+ )`,
21
+ ]);
22
+
23
+ export function durabilityRevision(value) {
24
+ return durabilityUint64(value, "revision");
25
+ }
26
+
27
+ /** Returns SHA-256 over the UTF-8 JSON tuple `[revision, payload]`. */
28
+ export async function durabilityStateDigest(state) {
29
+ const exact = copyState(state);
30
+ const encoded = new TextEncoder().encode(JSON.stringify([exact.revision, exact.payload]));
31
+ const digest = await globalThis.crypto?.subtle?.digest("SHA-256", encoded);
32
+ if (digest === undefined) {
33
+ throw new Error("durability range portability requires Web Crypto SHA-256 support");
34
+ }
35
+ return `sha256:${[...new Uint8Array(digest)]
36
+ .map((byte) => byte.toString(16).padStart(2, "0"))
37
+ .join("")}`;
38
+ }
39
+
40
+ export class DurabilityImportConflictError extends Error {
41
+ constructor(stateId, expectedRevision, actualRevision) {
42
+ const id = JSON.stringify(stateId);
43
+ super(expectedRevision === undefined
44
+ ? `durability state ${id} already exists at the import destination`
45
+ : expectedRevision === actualRevision
46
+ ? `durability import expected the exact destination state at revision ${expectedRevision} for ${id}, but found a different payload lineage`
47
+ : `durability import expected destination revision ${expectedRevision} for state ${id}, but found ${actualRevision}`);
48
+ this.name = "DurabilityImportConflictError";
49
+ this.expectedRevision = expectedRevision;
50
+ this.actualRevision = actualRevision;
51
+ }
52
+ }
53
+
54
+ /** Exports one deterministic page of the exact total state at `to`. */
55
+ export async function exportDurabilityStatePage(store, stateId, request) {
56
+ requireId(stateId, "state");
57
+ if (!store || typeof store.acquire !== "function") {
58
+ throw new TypeError("durability export requires a state store");
59
+ }
60
+ const from = durabilityRevision(request?.from);
61
+ let fromDigest;
62
+ if (request?.fromDigest === undefined) {
63
+ if (from !== "0") {
64
+ throw new TypeError("nonzero durability export ranges require a from-state digest");
65
+ }
66
+ fromDigest = await durabilityStateDigest({ revision: "0", payload: null });
67
+ }
68
+ else {
69
+ fromDigest = durabilityStateDigestText(request.fromDigest);
70
+ }
71
+ const requestedTo = request?.to === undefined ? undefined : durabilityRevision(request.to);
72
+ const offset = decodeExportCursor(request?.cursor);
73
+ const limit = exportPageSize(request?.limit);
74
+ const acquired = await store.acquire(stateId, { ownerId: PORTABLE_EXPORT_OWNER });
75
+ exactObject(acquired, ["ownerId", "fence", "revision", "payload"], "durability export acquisition");
76
+ if (acquired.ownerId !== PORTABLE_EXPORT_OWNER) {
77
+ throw new TypeError("durability export acquisition returned a different owner ID");
78
+ }
79
+ durabilityFence(acquired.fence);
80
+ const state = copyState({ revision: acquired.revision, payload: acquired.payload });
81
+ const to = state.revision;
82
+ if (requestedTo !== undefined && requestedTo !== to) {
83
+ throw new DurabilityImportConflictError(stateId, requestedTo, to);
84
+ }
85
+ if (BigInt(from) >= BigInt(to)) {
86
+ throw new RangeError("durability export from revision must be less than to revision");
87
+ }
88
+ const payload = state.payload ?? "";
89
+ if (offset > payload.length) throw new TypeError("durability export cursor is out of range");
90
+ let end = Math.min(offset + limit, payload.length);
91
+ if (end < payload.length && end > offset && isHighSurrogate(payload.charCodeAt(end - 1))) end -= 1;
92
+ const nextCursor = end < payload.length ? encodeExportCursor(end) : null;
93
+ return Object.freeze({
94
+ format: PORTABLE_PAGE_FORMAT,
95
+ stateId,
96
+ from,
97
+ fromDigest,
98
+ to,
99
+ cursor: encodeExportCursor(offset),
100
+ nextCursor,
101
+ payloadLength: payload.length,
102
+ payload: payload.slice(offset, end),
103
+ });
104
+ }
105
+
106
+ /** Imports contiguous cursor pages with a CAS guard on the destination's `from` revision. */
107
+ export async function importDurabilityStatePages(store, pages) {
108
+ if (!store || typeof store.importState !== "function") {
109
+ throw new TypeError("durability import requires a portable state store");
110
+ }
111
+ if (!pages || typeof pages[Symbol.iterator] !== "function") {
112
+ throw new TypeError("durability import pages must be iterable");
113
+ }
114
+ let first;
115
+ let cursor = encodeExportCursor(0);
116
+ let payload = "";
117
+ let complete = false;
118
+ for (const page of pages) {
119
+ exactObject(
120
+ page,
121
+ [
122
+ "format", "stateId", "from", "fromDigest", "to", "cursor", "nextCursor",
123
+ "payloadLength", "payload",
124
+ ],
125
+ "durability export page",
126
+ );
127
+ if (page.format !== PORTABLE_PAGE_FORMAT || typeof page.payload !== "string") {
128
+ throw new TypeError("unsupported durability export page");
129
+ }
130
+ if (!Number.isSafeInteger(page.payloadLength) || page.payloadLength < 0) {
131
+ throw new TypeError("durability export page payload length is invalid");
132
+ }
133
+ const identity = {
134
+ stateId: requireId(page.stateId, "exported state"),
135
+ from: durabilityRevision(page.from),
136
+ fromDigest: durabilityStateDigestText(page.fromDigest),
137
+ to: durabilityRevision(page.to),
138
+ payloadLength: page.payloadLength,
139
+ };
140
+ first ??= identity;
141
+ if (identity.stateId !== first.stateId || identity.from !== first.from
142
+ || identity.fromDigest !== first.fromDigest
143
+ || identity.to !== first.to || identity.payloadLength !== first.payloadLength) {
144
+ throw new TypeError("durability export pages describe different revision ranges");
145
+ }
146
+ const offset = decodeExportCursor(page.cursor);
147
+ if (complete || page.cursor !== cursor || offset !== payload.length) {
148
+ throw new TypeError("durability export pages are missing, duplicated, or out of order");
149
+ }
150
+ payload += page.payload;
151
+ if (payload.length > first.payloadLength) {
152
+ throw new TypeError("durability export pages exceed their declared payload length");
153
+ }
154
+ if (page.nextCursor === null) {
155
+ if (payload.length !== first.payloadLength) {
156
+ throw new TypeError("durability export pages are incomplete");
157
+ }
158
+ complete = true;
159
+ }
160
+ else {
161
+ if (page.nextCursor !== encodeExportCursor(payload.length)) {
162
+ throw new TypeError("durability export page cursor does not match its payload");
163
+ }
164
+ cursor = page.nextCursor;
165
+ }
166
+ }
167
+ if (!first || !complete) throw new TypeError("durability export pages are incomplete");
168
+ if (BigInt(first.from) >= BigInt(first.to)) {
169
+ throw new RangeError("durability import from revision must be less than to revision");
170
+ }
171
+ const state = copyState({ revision: first.to, payload: first.to === "0" ? null : payload });
172
+ const current = copyState(await store.load(first.stateId));
173
+ const currentDigest = await durabilityStateDigest(current);
174
+ if (current.revision !== first.from || currentDigest !== first.fromDigest) {
175
+ throw new DurabilityImportConflictError(first.stateId, first.from, current.revision);
176
+ }
177
+ const imported = await store.importState(first.stateId, state, {
178
+ expectedRevision: first.from,
179
+ expectedPayload: current.payload,
180
+ });
181
+ return copyState(imported);
182
+ }
183
+
184
+ /** Fences a source store and exports one coherent provider-neutral state archive. */
185
+ export async function exportDurabilityState(store, stateId) {
186
+ requireId(stateId, "state");
187
+ if (!store || typeof store.acquire !== "function") {
188
+ throw new TypeError("durability export requires a state store");
189
+ }
190
+ const ownerId = PORTABLE_EXPORT_OWNER;
191
+ const acquired = await store.acquire(stateId, { ownerId });
192
+ exactObject(
193
+ acquired,
194
+ ["ownerId", "fence", "revision", "payload"],
195
+ "durability export acquisition",
196
+ );
197
+ if (acquired.ownerId !== ownerId) {
198
+ throw new TypeError("durability export acquisition returned a different owner ID");
199
+ }
200
+ durabilityFence(acquired.fence);
201
+ const state = copyState({ revision: acquired.revision, payload: acquired.payload });
202
+ return Object.freeze({ format: PORTABLE_FORMAT, stateId, ...state });
203
+ }
204
+
205
+ /** Restores an exact provider-neutral archive into an empty portable store. */
206
+ export async function importDurabilityState(store, archive) {
207
+ if (!store || typeof store.importState !== "function") {
208
+ throw new TypeError("durability import requires a portable state store");
209
+ }
210
+ exactObject(
211
+ archive,
212
+ ["format", "stateId", "revision", "payload"],
213
+ "durability export archive",
214
+ );
215
+ if (archive.format !== PORTABLE_FORMAT) {
216
+ throw new TypeError(`unsupported durability export format: ${String(archive.format)}`);
217
+ }
218
+ requireId(archive.stateId, "exported state");
219
+ const state = copyState({ revision: archive.revision, payload: archive.payload });
220
+ const imported = await store.importState(archive.stateId, state);
221
+ return copyState(imported);
222
+ }
223
+
224
+ function durabilityFence(value) {
225
+ return durabilityUint64(value, "fence");
226
+ }
227
+
228
+ function durabilityUint64(value, field) {
229
+ if (typeof value === "number" && (!Number.isSafeInteger(value) || value < 0)) {
230
+ throw new TypeError(
231
+ `durability ${field} numbers must be nonnegative safe integers; `
232
+ + "use exact unsigned decimal text for larger values",
233
+ );
234
+ }
235
+ if (typeof value !== "string" && typeof value !== "bigint" && typeof value !== "number") {
236
+ throw new TypeError(`durability ${field} must be an unsigned 64-bit decimal string`);
237
+ }
238
+ const encoded = String(value);
239
+ if (!/^(0|[1-9][0-9]*)$/.test(encoded) || BigInt(encoded) > MAX_REVISION) {
240
+ throw new TypeError(`durability ${field} must be an unsigned 64-bit decimal string`);
241
+ }
242
+ return encoded;
243
+ }
244
+
245
+ export function createMemoryDurabilityStore(stateId, initial) {
246
+ requireId(stateId, "state");
247
+ const states = new Map([[stateId, {
248
+ state: copyState(initial ?? { revision: "0", payload: null }),
249
+ owner: undefined,
250
+ }]]);
251
+ const select = (selected) => {
252
+ requireId(selected, "state");
253
+ const entry = states.get(selected);
254
+ if (entry === undefined) throw new Error(`unknown durability state: ${selected}`);
255
+ return entry;
256
+ };
257
+ return Object.freeze({
258
+ stateId,
259
+ load(selected) {
260
+ return select(selected).state;
261
+ },
262
+ acquire(selected, request) {
263
+ requireId(selected, "state");
264
+ const ownerId = requireId(request?.ownerId, "owner");
265
+ let entry = states.get(selected);
266
+ if (entry === undefined) {
267
+ entry = {
268
+ state: copyState({ revision: "0", payload: null }),
269
+ owner: Object.freeze({ ownerId, fence: "1" }),
270
+ };
271
+ states.set(selected, entry);
272
+ return acquiredState(entry.owner, entry.state);
273
+ }
274
+ if (entry.owner?.ownerId === ownerId) return acquiredState(entry.owner, entry.state);
275
+ const previousFence = entry.owner?.fence ?? "0";
276
+ if (previousFence === MAX_REVISION_TEXT) {
277
+ throw new RangeError("in-memory durability fence overflow");
278
+ }
279
+ entry.owner = Object.freeze({
280
+ ownerId,
281
+ fence: durabilityFence(BigInt(previousFence) + 1n),
282
+ });
283
+ return acquiredState(entry.owner, entry.state);
284
+ },
285
+ replace(selected, request) {
286
+ const entry = select(selected);
287
+ const ownerId = requireId(request?.ownerId, "owner");
288
+ const fence = durabilityFence(request?.fence);
289
+ if (ownerId !== entry.owner?.ownerId || fence !== entry.owner.fence) {
290
+ return { status: "fenced" };
291
+ }
292
+ const expectedRevision = durabilityRevision(request?.expectedRevision);
293
+ if (expectedRevision !== entry.state.revision) {
294
+ if (entry.state.revision === nextRevision(expectedRevision)
295
+ && typeof request?.payload === "string"
296
+ && entry.state.payload === request.payload) {
297
+ return { status: "replaced", revision: entry.state.revision };
298
+ }
299
+ return { status: "conflict", actualRevision: entry.state.revision };
300
+ }
301
+ if (expectedRevision === MAX_REVISION_TEXT) {
302
+ return { status: "not_committed", message: "in-memory durability revision overflow" };
303
+ }
304
+ const payload = requirePayload(request?.payload);
305
+ const revision = durabilityRevision(BigInt(expectedRevision) + 1n);
306
+ entry.state = Object.freeze({ revision, payload });
307
+ return { status: "replaced", revision };
308
+ },
309
+ importState(selected, imported, options) {
310
+ requireId(selected, "state");
311
+ const next = copyState(imported);
312
+ const expectedRevision = options?.expectedRevision === undefined
313
+ ? undefined
314
+ : durabilityRevision(options.expectedRevision);
315
+ const expectedPayload = expectedImportPayload(options, expectedRevision);
316
+ const existing = states.get(selected);
317
+ const entry = existing ?? {
318
+ state: copyState({ revision: "0", payload: null }),
319
+ owner: undefined,
320
+ };
321
+ if (expectedRevision === undefined
322
+ ? entry.owner !== undefined || entry.state.revision !== "0"
323
+ : entry.state.revision !== expectedRevision
324
+ || expectedPayload.present && entry.state.payload !== expectedPayload.value) {
325
+ throw new DurabilityImportConflictError(selected, expectedRevision, entry.state.revision);
326
+ }
327
+ const owner = Object.freeze({
328
+ ownerId: PORTABLE_IMPORT_OWNER,
329
+ fence: durabilityFence(BigInt(entry.owner?.fence ?? "0") + 1n),
330
+ });
331
+ if (existing === undefined) states.set(selected, entry);
332
+ entry.owner = owner;
333
+ entry.state = next;
334
+ return entry.state;
335
+ },
336
+ snapshot() {
337
+ return states.get(stateId).state;
338
+ },
339
+ });
340
+ }
341
+
342
+ export function createSqliteDurabilityStore(options) {
343
+ if (!options || typeof options.transaction !== "function") {
344
+ throw new TypeError("SQLite durability requires a transaction function");
345
+ }
346
+ return Object.freeze({
347
+ load(stateId) {
348
+ requireId(stateId, "state");
349
+ return options.transaction((query) => loadSqliteState(query, stateId));
350
+ },
351
+ acquire(stateId, request) {
352
+ requireId(stateId, "state");
353
+ const ownerId = requireId(request?.ownerId, "owner");
354
+ return options.transaction((query) => mapMaybePromise(
355
+ query(
356
+ "SELECT owner_id, fence FROM nanocodex_durable_owners WHERE state_id = ?",
357
+ [stateId],
358
+ ),
359
+ (owners) => {
360
+ exactRows(owners, 1, "SQLite durability owner");
361
+ if (owners.length === 1) exactObject(owners[0], ["owner_id", "fence"], "SQLite durability owner");
362
+ if (owners[0]?.owner_id === ownerId) {
363
+ return mapMaybePromise(
364
+ loadSqliteState(query, stateId),
365
+ (state) => acquiredState({ ownerId, fence: durabilityFence(owners[0].fence) }, state),
366
+ );
367
+ }
368
+ const previousFence = durabilityFence(owners[0]?.fence ?? "0");
369
+ if (previousFence === MAX_REVISION_TEXT) {
370
+ throw new RangeError("SQLite durability fence overflow");
371
+ }
372
+ const fence = durabilityFence(BigInt(previousFence) + 1n);
373
+ return mapMaybePromise(
374
+ query(
375
+ `INSERT INTO nanocodex_durable_owners (state_id, owner_id, fence) VALUES (?, ?, ?)
376
+ ON CONFLICT (state_id) DO UPDATE
377
+ SET owner_id = excluded.owner_id, fence = excluded.fence`,
378
+ [stateId, ownerId, fence],
379
+ ),
380
+ () => mapMaybePromise(
381
+ loadSqliteState(query, stateId),
382
+ (state) => acquiredState({ ownerId, fence }, state),
383
+ ),
384
+ );
385
+ },
386
+ ));
387
+ },
388
+ replace(stateId, request) {
389
+ requireId(stateId, "state");
390
+ const ownerId = requireId(request?.ownerId, "owner");
391
+ const fence = durabilityFence(request?.fence);
392
+ return options.transaction((query) => mapMaybePromise(
393
+ query(
394
+ "SELECT owner_id, fence FROM nanocodex_durable_owners WHERE state_id = ?",
395
+ [stateId],
396
+ ),
397
+ (owners) => {
398
+ exactRows(owners, 1, "SQLite durability owner");
399
+ if (owners.length === 1) exactObject(owners[0], ["owner_id", "fence"], "SQLite durability owner");
400
+ const storedOwner = owners[0];
401
+ if (
402
+ storedOwner?.owner_id !== ownerId
403
+ || durabilityFence(storedOwner?.fence ?? "0") !== fence
404
+ ) {
405
+ return { status: "fenced" };
406
+ }
407
+ const expectedRevision = durabilityRevision(request?.expectedRevision);
408
+ // The normal write needs only the revision. Loading the previous
409
+ // payload also hydrates every Cloudflare chunk, doubling live state
410
+ // memory while the replacement is already held by Rust and JS.
411
+ return mapMaybePromise(query(
412
+ "SELECT revision FROM nanocodex_durable_states WHERE state_id = ?",
413
+ [stateId],
414
+ ), (rows) => {
415
+ exactRows(rows, 1, "SQLite durability revision");
416
+ if (rows.length === 1) exactObject(rows[0], ["revision"], "SQLite durability revision");
417
+ const actualRevision = durabilityRevision(rows[0]?.revision ?? "0");
418
+ if (actualRevision !== expectedRevision) {
419
+ if (actualRevision === nextRevision(expectedRevision)
420
+ && typeof request?.payload === "string") {
421
+ // A lost acknowledgement must still replay only the exact
422
+ // committed payload, under the same transaction and fence.
423
+ return mapMaybePromise(loadSqliteState(query, stateId), (state) =>
424
+ state.payload === request.payload
425
+ ? { status: "replaced", revision: actualRevision }
426
+ : { status: "conflict", actualRevision });
427
+ }
428
+ return { status: "conflict", actualRevision };
429
+ }
430
+ if (expectedRevision === MAX_REVISION_TEXT) {
431
+ return { status: "not_committed", message: "SQLite durability revision overflow" };
432
+ }
433
+ const payload = requirePayload(request?.payload);
434
+ const revision = durabilityRevision(BigInt(expectedRevision) + 1n);
435
+ return mapMaybePromise(
436
+ query(
437
+ `INSERT INTO nanocodex_durable_states (state_id, revision, payload) VALUES (?, ?, ?)
438
+ ON CONFLICT (state_id) DO UPDATE
439
+ SET revision = excluded.revision, payload = excluded.payload`,
440
+ [stateId, revision, payload],
441
+ ),
442
+ () => ({ status: "replaced", revision }),
443
+ );
444
+ });
445
+ },
446
+ ));
447
+ },
448
+ importState(stateId, imported, importOptions) {
449
+ requireId(stateId, "state");
450
+ const state = copyState(imported);
451
+ const expectedRevision = importOptions?.expectedRevision === undefined
452
+ ? undefined
453
+ : durabilityRevision(importOptions.expectedRevision);
454
+ const expectedPayload = expectedImportPayload(importOptions, expectedRevision);
455
+ return options.transaction((query) => mapMaybePromise(
456
+ query(
457
+ "SELECT owner_id, fence FROM nanocodex_durable_owners WHERE state_id = ?",
458
+ [stateId],
459
+ ),
460
+ (owners) => {
461
+ exactRows(owners, 1, "SQLite durability owner");
462
+ if (owners.length === 1) {
463
+ exactObject(owners[0], ["owner_id", "fence"], "SQLite durability owner");
464
+ }
465
+ return mapMaybePromise(
466
+ query(
467
+ "SELECT revision, payload FROM nanocodex_durable_states WHERE state_id = ?",
468
+ [stateId],
469
+ ),
470
+ (states) => {
471
+ exactRows(states, 1, "SQLite durability state");
472
+ const current = states.length === 0
473
+ ? Object.freeze({ revision: "0", payload: null })
474
+ : copyState({ revision: states[0].revision, payload: states[0].payload });
475
+ if (expectedRevision === undefined
476
+ ? owners.length !== 0 || states.length !== 0
477
+ : current.revision !== expectedRevision
478
+ || expectedPayload.present && current.payload !== expectedPayload.value) {
479
+ throw new DurabilityImportConflictError(stateId, expectedRevision, current.revision);
480
+ }
481
+ const previousFence = durabilityFence(owners[0]?.fence ?? "0");
482
+ if (previousFence === MAX_REVISION_TEXT) throw new RangeError("SQLite durability fence overflow");
483
+ const fence = durabilityFence(BigInt(previousFence) + 1n);
484
+ return mapMaybePromise(
485
+ query(
486
+ `INSERT INTO nanocodex_durable_owners (state_id, owner_id, fence) VALUES (?, ?, ?)
487
+ ON CONFLICT (state_id) DO UPDATE
488
+ SET owner_id = excluded.owner_id, fence = excluded.fence`,
489
+ [stateId, PORTABLE_IMPORT_OWNER, fence],
490
+ ),
491
+ () => state.revision === "0"
492
+ ? state
493
+ : mapMaybePromise(
494
+ query(
495
+ `INSERT INTO nanocodex_durable_states (state_id, revision, payload) VALUES (?, ?, ?)
496
+ ON CONFLICT (state_id) DO UPDATE
497
+ SET revision = excluded.revision, payload = excluded.payload`,
498
+ [stateId, state.revision, state.payload],
499
+ ),
500
+ () => state,
501
+ ),
502
+ );
503
+ },
504
+ );
505
+ },
506
+ ));
507
+ },
508
+ });
509
+ }
510
+
511
+ function nextRevision(revision) {
512
+ return revision === MAX_REVISION_TEXT ? undefined : durabilityRevision(BigInt(revision) + 1n);
513
+ }
514
+
515
+ function exportPageSize(value) {
516
+ if (value === undefined) return DEFAULT_EXPORT_PAGE_SIZE;
517
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_EXPORT_PAGE_SIZE) {
518
+ throw new TypeError(`durability export page limit must be an integer from 1 to ${MAX_EXPORT_PAGE_SIZE}`);
519
+ }
520
+ return value;
521
+ }
522
+
523
+ function encodeExportCursor(offset) {
524
+ return `v1:${offset}`;
525
+ }
526
+
527
+ function decodeExportCursor(value) {
528
+ if (value === undefined) return 0;
529
+ if (typeof value !== "string" || !/^v1:(0|[1-9][0-9]*)$/.test(value)) {
530
+ throw new TypeError("durability export cursor is invalid");
531
+ }
532
+ const offset = Number(value.slice(3));
533
+ if (!Number.isSafeInteger(offset)) throw new TypeError("durability export cursor is invalid");
534
+ return offset;
535
+ }
536
+
537
+ function isHighSurrogate(value) {
538
+ return value >= 0xd800 && value <= 0xdbff;
539
+ }
540
+
541
+ function durabilityStateDigestText(value) {
542
+ if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
543
+ throw new TypeError("durability export page from-state digest is invalid");
544
+ }
545
+ return value;
546
+ }
547
+
548
+ function expectedImportPayload(options, expectedRevision) {
549
+ const present = options?.expectedPayload !== undefined;
550
+ if (!present) return { present: false, value: undefined };
551
+ if (expectedRevision === undefined) {
552
+ throw new TypeError("durability import expected payload requires an expected revision");
553
+ }
554
+ const expected = copyState({ revision: expectedRevision, payload: options.expectedPayload });
555
+ return { present: true, value: expected.payload };
556
+ }
557
+
558
+ function loadSqliteState(query, stateId) {
559
+ return mapMaybePromise(
560
+ query(
561
+ "SELECT revision, payload FROM nanocodex_durable_states WHERE state_id = ?",
562
+ [stateId],
563
+ ),
564
+ (rows) => {
565
+ exactRows(rows, 1, "SQLite durability state");
566
+ if (rows.length === 0) return Object.freeze({ revision: "0", payload: null });
567
+ exactObject(rows[0], ["revision", "payload"], "SQLite durability state");
568
+ return copyState({ revision: rows[0].revision, payload: rows[0].payload });
569
+ },
570
+ );
571
+ }
572
+
573
+ function mapMaybePromise(value, mapper) {
574
+ return value && typeof value.then === "function" ? value.then(mapper) : mapper(value);
575
+ }
576
+
577
+ function copyState(state) {
578
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
579
+ throw new TypeError("durability state must be an object");
580
+ }
581
+ const keys = Object.keys(state).sort();
582
+ if (keys.length !== 2 || keys[0] !== "payload" || keys[1] !== "revision") {
583
+ throw new TypeError("durability state must contain exactly payload and revision");
584
+ }
585
+ const revision = durabilityRevision(state.revision);
586
+ const payload = state.payload === null ? null : requirePayload(state.payload);
587
+ if ((revision === "0") !== (payload === null)) {
588
+ throw new TypeError("durability state payload must be null exactly at revision zero");
589
+ }
590
+ return Object.freeze({ revision, payload });
591
+ }
592
+
593
+ function acquiredState(owner, state) {
594
+ return Object.freeze({
595
+ ownerId: owner.ownerId,
596
+ fence: owner.fence,
597
+ revision: state.revision,
598
+ payload: state.payload,
599
+ });
600
+ }
601
+
602
+ function requireId(value, kind) {
603
+ if (typeof value !== "string" || !value.trim()) {
604
+ throw new TypeError(`durability ${kind} ID must be a non-empty string`);
605
+ }
606
+ return value;
607
+ }
608
+
609
+ function requirePayload(value) {
610
+ if (typeof value !== "string") {
611
+ throw new TypeError("durability payload must be a string");
612
+ }
613
+ return value;
614
+ }
615
+
616
+ function exactRows(rows, maximum, label) {
617
+ if (!Array.isArray(rows) || rows.length > maximum) {
618
+ throw new TypeError(`${label} query returned an invalid row set`);
619
+ }
620
+ }
621
+
622
+ function exactObject(value, expected, label) {
623
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
624
+ throw new TypeError(`${label} row must be an object`);
625
+ }
626
+ const actual = Object.keys(value).sort();
627
+ const required = [...expected].sort();
628
+ if (actual.length !== required.length || actual.some((key, index) => key !== required[index])) {
629
+ throw new TypeError(`${label} row has an invalid shape`);
630
+ }
631
+ }