@abloatai/ablo 0.34.1 → 0.36.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 (536) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +758 -5
  3. package/README.md +56 -502
  4. package/bin/ablo.cjs +39 -0
  5. package/dist/BaseSyncedStore.d.ts +176 -48
  6. package/dist/BaseSyncedStore.js +346 -214
  7. package/dist/Database.d.ts +17 -44
  8. package/dist/Database.js +96 -79
  9. package/dist/InstanceCache.d.ts +31 -6
  10. package/dist/InstanceCache.js +65 -30
  11. package/dist/LazyReferenceCollection.d.ts +3 -3
  12. package/dist/LazyReferenceCollection.js +4 -4
  13. package/dist/Model.d.ts +23 -13
  14. package/dist/Model.js +27 -17
  15. package/dist/ModelRegistry.d.ts +8 -4
  16. package/dist/ModelRegistry.js +20 -18
  17. package/dist/NetworkMonitor.d.ts +3 -1
  18. package/dist/NetworkMonitor.js +7 -5
  19. package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
  20. package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
  21. package/dist/SyncClient.d.ts +47 -47
  22. package/dist/SyncClient.js +215 -156
  23. package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
  24. package/dist/ai-sdk/coordinatedTool.js +1 -1
  25. package/dist/ai-sdk/coordinationContext.d.ts +2 -2
  26. package/dist/ai-sdk/coordinationContext.js +1 -1
  27. package/dist/ai-sdk/wrap.d.ts +3 -3
  28. package/dist/ai-sdk/wrap.js +2 -2
  29. package/dist/auth/index.d.ts +1 -156
  30. package/dist/auth/index.js +8 -301
  31. package/dist/client/Ablo.d.ts +42 -287
  32. package/dist/client/Ablo.js +129 -963
  33. package/dist/client/abloClient.d.ts +309 -0
  34. package/dist/client/abloClient.js +13 -0
  35. package/dist/client/clientPrelude.d.ts +52 -0
  36. package/dist/client/clientPrelude.js +60 -0
  37. package/dist/client/consoleLogger.d.ts +2 -2
  38. package/dist/client/coreClient.d.ts +60 -0
  39. package/dist/client/coreClient.js +118 -0
  40. package/dist/client/createInternalComponents.d.ts +8 -4
  41. package/dist/client/createInternalComponents.js +17 -10
  42. package/dist/client/createModelProxy.d.ts +98 -373
  43. package/dist/client/createModelProxy.js +233 -139
  44. package/dist/client/humans.d.ts +69 -0
  45. package/dist/client/humans.js +78 -0
  46. package/dist/client/modelRegistration.d.ts +1 -1
  47. package/dist/client/modelRegistration.js +9 -9
  48. package/dist/client/options.d.ts +73 -17
  49. package/dist/client/reactiveEngine.d.ts +53 -0
  50. package/dist/client/reactiveEngine.js +688 -0
  51. package/dist/client/resourceTypes.d.ts +9 -250
  52. package/dist/client/resourceTypes.js +8 -5
  53. package/dist/client/schemaConfig.d.ts +4 -4
  54. package/dist/client/schemaConfig.js +6 -2
  55. package/dist/client/storeCluster.d.ts +47 -0
  56. package/dist/client/storeCluster.js +118 -0
  57. package/dist/client/storeLifecycle.d.ts +61 -0
  58. package/dist/client/storeLifecycle.js +231 -0
  59. package/dist/client/validateAbloOptions.d.ts +3 -2
  60. package/dist/client/validateAbloOptions.js +1 -1
  61. package/dist/client/wsMutationExecutor.d.ts +3 -3
  62. package/dist/client/wsMutationExecutor.js +3 -3
  63. package/dist/context.d.ts +22 -9
  64. package/dist/context.js +33 -9
  65. package/dist/coordination/ClaimLog.d.ts +26 -0
  66. package/dist/coordination/ClaimLog.js +32 -0
  67. package/dist/coordination/index.d.ts +1 -15
  68. package/dist/coordination/index.js +8 -31
  69. package/dist/core/index.d.ts +3 -3
  70. package/dist/core/index.js +2 -2
  71. package/dist/docs/catalog.d.ts +72 -0
  72. package/dist/docs/catalog.js +230 -0
  73. package/dist/docs/index.d.ts +10 -0
  74. package/dist/docs/index.js +10 -0
  75. package/dist/environment.d.ts +1 -40
  76. package/dist/environment.js +8 -37
  77. package/dist/index.d.ts +44 -36
  78. package/dist/index.js +30 -22
  79. package/dist/interfaces/index.d.ts +44 -134
  80. package/dist/keys/index.d.ts +1 -77
  81. package/dist/keys/index.js +8 -190
  82. package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
  83. package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
  84. package/dist/mutators/Transaction.d.ts +1 -1
  85. package/dist/mutators/Transaction.js +1 -1
  86. package/dist/mutators/UndoManager.d.ts +6 -6
  87. package/dist/mutators/UndoManager.js +5 -5
  88. package/dist/mutators/defineMutators.d.ts +3 -3
  89. package/dist/mutators/defineMutators.js +1 -1
  90. package/dist/mutators/inverseOp.js +2 -2
  91. package/dist/mutators/mutateActions.d.ts +3 -3
  92. package/dist/mutators/mutateActions.js +1 -1
  93. package/dist/mutators/readerActions.d.ts +1 -1
  94. package/dist/mutators/undoApply.d.ts +1 -1
  95. package/dist/mutators/undoApply.js +1 -1
  96. package/dist/policy/index.d.ts +2 -2
  97. package/dist/policy/index.js +1 -1
  98. package/dist/query/client.d.ts +5 -2
  99. package/dist/query/client.js +10 -9
  100. package/dist/query/types.d.ts +6 -41
  101. package/dist/query/types.js +2 -2
  102. package/dist/react/AbloProvider.d.ts +18 -8
  103. package/dist/react/AbloProvider.js +10 -9
  104. package/dist/react/context.d.ts +3 -3
  105. package/dist/react/context.js +1 -1
  106. package/dist/react/createAbloReact.d.ts +56 -0
  107. package/dist/react/createAbloReact.js +51 -0
  108. package/dist/react/index.d.ts +6 -5
  109. package/dist/react/index.js +6 -3
  110. package/dist/react/internalContext.d.ts +1 -1
  111. package/dist/react/useAblo.d.ts +12 -5
  112. package/dist/react/useAblo.js +26 -8
  113. package/dist/react/useCurrentUserId.js +1 -1
  114. package/dist/react/useErrorListener.js +1 -1
  115. package/dist/react/useMutationFailureListener.d.ts +2 -2
  116. package/dist/react/useMutationFailureListener.js +1 -1
  117. package/dist/react/useMutators.d.ts +3 -3
  118. package/dist/react/useMutators.js +3 -3
  119. package/dist/react/useUndoScope.d.ts +5 -5
  120. package/dist/react/useUndoScope.js +1 -1
  121. package/dist/schema/coordination.d.ts +69 -10
  122. package/dist/schema/coordination.js +90 -9
  123. package/dist/schema/ddl.js +2 -2
  124. package/dist/schema/diff.d.ts +1 -1
  125. package/dist/schema/generate.js +1 -1
  126. package/dist/schema/index.d.ts +11 -10
  127. package/dist/schema/index.js +22 -18
  128. package/dist/schema/queries.d.ts +27 -27
  129. package/dist/schema/queries.js +23 -23
  130. package/dist/schema/select.d.ts +3 -3
  131. package/dist/schema/select.js +6 -3
  132. package/dist/schema/serialize.d.ts +15 -6
  133. package/dist/schema/serialize.js +20 -3
  134. package/dist/schema/sugar.d.ts +6 -7
  135. package/dist/schema/sugar.js +9 -12
  136. package/dist/schema/syncDeltaRow.d.ts +4 -152
  137. package/dist/schema/syncDeltaRow.js +4 -105
  138. package/dist/server/adapter.d.ts +18 -1
  139. package/dist/server/commit.d.ts +10 -16
  140. package/dist/server/index.d.ts +1 -1
  141. package/dist/server/index.js +1 -1
  142. package/dist/server/readConfig.d.ts +1 -1
  143. package/dist/source/adapter.d.ts +7 -5
  144. package/dist/source/adapter.js +7 -5
  145. package/dist/source/adapters/drizzle.d.ts +1 -1
  146. package/dist/source/adapters/drizzle.js +2 -2
  147. package/dist/source/adapters/kysely.d.ts +1 -1
  148. package/dist/source/adapters/kysely.js +1 -1
  149. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  150. package/dist/source/adapters/kyselyMutationCore.js +2 -2
  151. package/dist/source/adapters/memory.js +1 -1
  152. package/dist/source/adapters/prisma.d.ts +8 -3
  153. package/dist/source/adapters/prisma.js +1 -1
  154. package/dist/source/connector.js +1 -1
  155. package/dist/source/connectorProtocol.d.ts +2 -8
  156. package/dist/source/connectorProtocol.js +3 -2
  157. package/dist/source/contract.d.ts +29 -17
  158. package/dist/source/contract.js +27 -22
  159. package/dist/source/factory.d.ts +1 -1
  160. package/dist/source/idempotency.js +2 -2
  161. package/dist/source/index.d.ts +1 -0
  162. package/dist/source/index.js +3 -0
  163. package/dist/source/next.d.ts +1 -1
  164. package/dist/source/signing.d.ts +9 -2
  165. package/dist/source/signing.js +4 -1
  166. package/dist/source/types.d.ts +6 -4
  167. package/dist/source/types.js +1 -1
  168. package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
  169. package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
  170. package/dist/{core → stores}/DatabaseManager.js +14 -13
  171. package/dist/stores/ObjectStore.d.ts +1 -1
  172. package/dist/{core → stores}/StoreManager.d.ts +9 -26
  173. package/dist/{core → stores}/StoreManager.js +29 -77
  174. package/dist/stores/SyncActionStore.d.ts +4 -2
  175. package/dist/stores/SyncActionStore.js +11 -17
  176. package/dist/stores/syncAction.d.ts +26 -0
  177. package/dist/stores/syncAction.js +16 -0
  178. package/dist/surface.d.ts +3 -3
  179. package/dist/surface.js +6 -4
  180. package/dist/sync/BootstrapFetcher.d.ts +127 -6
  181. package/dist/sync/BootstrapFetcher.js +511 -83
  182. package/dist/sync/ConnectionManager.d.ts +6 -198
  183. package/dist/sync/ConnectionManager.js +6 -677
  184. package/dist/sync/OnDemandLoader.d.ts +5 -2
  185. package/dist/sync/OnDemandLoader.js +61 -21
  186. package/dist/sync/SubscriptionManager.d.ts +13 -2
  187. package/dist/sync/SubscriptionManager.js +23 -5
  188. package/dist/sync/SyncWebSocket.d.ts +27 -510
  189. package/dist/sync/SyncWebSocket.js +76 -954
  190. package/dist/sync/awaitClaimGrant.d.ts +4 -44
  191. package/dist/sync/awaitClaimGrant.js +4 -109
  192. package/dist/sync/bootstrapApply.d.ts +3 -0
  193. package/dist/sync/bootstrapApply.js +2 -2
  194. package/dist/sync/commitFrames.d.ts +6 -40
  195. package/dist/sync/commitFrames.js +6 -97
  196. package/dist/sync/contextPorts.d.ts +18 -0
  197. package/dist/sync/contextPorts.js +31 -0
  198. package/dist/sync/createClaimStream.d.ts +5 -49
  199. package/dist/sync/createClaimStream.js +5 -469
  200. package/dist/sync/createPresenceStream.d.ts +26 -4
  201. package/dist/sync/createPresenceStream.js +28 -20
  202. package/dist/sync/createSnapshot.d.ts +2 -2
  203. package/dist/sync/createSnapshot.js +1 -1
  204. package/dist/sync/credentialLifecycle.d.ts +5 -173
  205. package/dist/sync/credentialLifecycle.js +5 -320
  206. package/dist/sync/deltaPipeline.d.ts +13 -12
  207. package/dist/sync/deltaPipeline.js +21 -4
  208. package/dist/sync/groupChange.d.ts +3 -0
  209. package/dist/sync/groupChange.js +16 -14
  210. package/dist/sync/participants.d.ts +24 -6
  211. package/dist/sync/participants.js +32 -23
  212. package/dist/sync/schemaDrift.d.ts +55 -0
  213. package/dist/sync/schemaDrift.js +53 -0
  214. package/dist/sync/schemas.d.ts +23 -33
  215. package/dist/sync/schemas.js +29 -20
  216. package/dist/sync/syncPlan.d.ts +3 -3
  217. package/dist/sync/wsFrameHandlers.d.ts +6 -114
  218. package/dist/sync/wsFrameHandlers.js +6 -392
  219. package/dist/syncLog/contract.d.ts +20 -0
  220. package/dist/syncLog/contract.js +19 -0
  221. package/dist/syncLog/index.d.ts +1 -0
  222. package/dist/syncLog/index.js +1 -0
  223. package/dist/transaction/ablo.d.ts +88 -0
  224. package/dist/transaction/ablo.js +33 -0
  225. package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
  226. package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
  227. package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
  228. package/dist/transaction/auth/bootstrapScope.js +1 -0
  229. package/dist/transaction/auth/capability.d.ts +212 -0
  230. package/dist/transaction/auth/capability.js +224 -0
  231. package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
  232. package/dist/{client → transaction/auth}/identity.d.ts +8 -7
  233. package/dist/{client → transaction/auth}/identity.js +1 -1
  234. package/dist/transaction/auth/index.d.ts +162 -0
  235. package/dist/transaction/auth/index.js +304 -0
  236. package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
  237. package/dist/{auth → transaction/auth}/schemas.js +13 -13
  238. package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
  239. package/dist/{client → transaction/auth}/sessionMint.js +4 -7
  240. package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
  241. package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
  242. package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
  243. package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
  244. package/dist/transaction/coordination/claimMeta.d.ts +49 -0
  245. package/dist/transaction/coordination/claimMeta.js +52 -0
  246. package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
  247. package/dist/transaction/coordination/createClaimStream.js +475 -0
  248. package/dist/transaction/coordination/events.d.ts +74 -0
  249. package/dist/transaction/coordination/events.js +7 -0
  250. package/dist/transaction/coordination/index.d.ts +19 -0
  251. package/dist/transaction/coordination/index.js +45 -0
  252. package/dist/transaction/coordination/locator.d.ts +104 -0
  253. package/dist/transaction/coordination/locator.js +102 -0
  254. package/dist/transaction/coordination/schema.d.ts +1536 -0
  255. package/dist/transaction/coordination/schema.js +1177 -0
  256. package/dist/transaction/coordination/targetConflict.d.ts +2 -0
  257. package/dist/transaction/coordination/targetConflict.js +107 -0
  258. package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
  259. package/dist/{coordination → transaction/coordination}/trace.js +18 -25
  260. package/dist/transaction/durableWrites.d.ts +62 -0
  261. package/dist/{client → transaction}/durableWrites.js +28 -3
  262. package/dist/transaction/environment.d.ts +105 -0
  263. package/dist/transaction/environment.js +108 -0
  264. package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
  265. package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
  266. package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
  267. package/dist/{errors.js → transaction/errors.js} +85 -16
  268. package/dist/transaction/footprint.d.ts +111 -0
  269. package/dist/transaction/footprint.js +0 -0
  270. package/dist/transaction/index.d.ts +20 -0
  271. package/dist/transaction/index.js +20 -0
  272. package/dist/transaction/keys/index.d.ts +87 -0
  273. package/dist/transaction/keys/index.js +207 -0
  274. package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
  275. package/dist/transaction/log/syncDeltaRow.js +95 -0
  276. package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
  277. package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
  278. package/dist/transaction/logger.d.ts +16 -0
  279. package/dist/transaction/logger.js +7 -0
  280. package/dist/transaction/observability.d.ts +53 -0
  281. package/dist/transaction/observability.js +19 -0
  282. package/dist/transaction/plugin.d.ts +285 -0
  283. package/dist/transaction/plugin.js +106 -0
  284. package/dist/{policy → transaction/policy}/types.d.ts +3 -3
  285. package/dist/{policy → transaction/policy}/types.js +2 -0
  286. package/dist/transaction/resources/httpResources.d.ts +321 -0
  287. package/dist/transaction/resources/httpResources.js +7 -0
  288. package/dist/transaction/resources/modelOperations.d.ts +427 -0
  289. package/dist/transaction/resources/modelOperations.js +12 -0
  290. package/dist/transaction/resources/mutationOptions.d.ts +66 -0
  291. package/dist/transaction/resources/mutationOptions.js +9 -0
  292. package/dist/transaction/resources/where.d.ts +101 -0
  293. package/dist/transaction/resources/where.js +115 -0
  294. package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
  295. package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
  296. package/dist/{schema → transaction/schema}/field.d.ts +17 -23
  297. package/dist/{schema → transaction/schema}/field.js +5 -5
  298. package/dist/transaction/schema/fieldRef.d.ts +38 -0
  299. package/dist/transaction/schema/fieldRef.js +11 -0
  300. package/dist/transaction/schema/loadStrategy.d.ts +45 -0
  301. package/dist/transaction/schema/loadStrategy.js +46 -0
  302. package/dist/{schema → transaction/schema}/model.d.ts +50 -35
  303. package/dist/{schema → transaction/schema}/model.js +30 -20
  304. package/dist/transaction/schema/openapi.d.ts +58 -0
  305. package/dist/transaction/schema/openapi.js +501 -0
  306. package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
  307. package/dist/{schema → transaction/schema}/relation.js +7 -7
  308. package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
  309. package/dist/{schema → transaction/schema}/residency.js +0 -5
  310. package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
  311. package/dist/{schema → transaction/schema}/roles.js +5 -5
  312. package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
  313. package/dist/{schema → transaction/schema}/schema.js +24 -3
  314. package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
  315. package/dist/{schema → transaction/schema}/tenancy.js +7 -4
  316. package/dist/transaction/transactionLayer.d.ts +82 -0
  317. package/dist/transaction/transactionLayer.js +24 -0
  318. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
  319. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
  320. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
  321. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
  322. package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
  323. package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
  324. package/dist/transaction/transport/commitFrames.d.ts +90 -0
  325. package/dist/transaction/transport/commitFrames.js +134 -0
  326. package/dist/transaction/transport/connectionManager.d.ts +215 -0
  327. package/dist/transaction/transport/connectionManager.js +673 -0
  328. package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
  329. package/dist/transaction/transport/credentialLifecycle.js +324 -0
  330. package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
  331. package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
  332. package/dist/transaction/transport/httpClient.d.ts +131 -0
  333. package/dist/{client → transaction/transport}/httpClient.js +6 -5
  334. package/dist/transaction/transport/httpOptions.d.ts +33 -0
  335. package/dist/transaction/transport/httpOptions.js +12 -0
  336. package/dist/{client → transaction/transport}/httpTransport.js +295 -97
  337. package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
  338. package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
  339. package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
  340. package/dist/transaction/transport/wsFrameHandlers.js +429 -0
  341. package/dist/transaction/transport/wsTransport.d.ts +574 -0
  342. package/dist/transaction/transport/wsTransport.js +1023 -0
  343. package/dist/transaction/types/assertExact.d.ts +17 -0
  344. package/dist/transaction/types/assertExact.js +1 -0
  345. package/dist/{types → transaction/types}/global.d.ts +17 -2
  346. package/dist/{types → transaction/types}/global.js +2 -1
  347. package/dist/{types → transaction/types}/index.d.ts +14 -46
  348. package/dist/{types → transaction/types}/index.js +7 -16
  349. package/dist/{types → transaction/types}/streams.d.ts +73 -45
  350. package/dist/transaction/utils/duration.d.ts +50 -0
  351. package/dist/{utils → transaction/utils}/duration.js +32 -0
  352. package/dist/{utils → transaction/utils}/json.d.ts +18 -0
  353. package/dist/transaction/utils/json.js +276 -0
  354. package/dist/transaction/wire/accountResponses.d.ts +420 -0
  355. package/dist/transaction/wire/accountResponses.js +290 -0
  356. package/dist/transaction/wire/auth.d.ts +56 -0
  357. package/dist/transaction/wire/auth.js +63 -0
  358. package/dist/transaction/wire/claimEvent.d.ts +76 -0
  359. package/dist/transaction/wire/claimEvent.js +73 -0
  360. package/dist/transaction/wire/claims.d.ts +530 -0
  361. package/dist/transaction/wire/claims.js +327 -0
  362. package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
  363. package/dist/{wire → transaction/wire}/commit.js +68 -47
  364. package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
  365. package/dist/{wire → transaction/wire}/delta.js +37 -13
  366. package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
  367. package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
  368. package/dist/transaction/wire/feedCursor.d.ts +60 -0
  369. package/dist/transaction/wire/feedCursor.js +82 -0
  370. package/dist/transaction/wire/feedEvent.d.ts +204 -0
  371. package/dist/transaction/wire/feedEvent.js +65 -0
  372. package/dist/transaction/wire/frames.d.ts +194 -0
  373. package/dist/transaction/wire/frames.js +50 -0
  374. package/dist/transaction/wire/inboundFrames.d.ts +562 -0
  375. package/dist/transaction/wire/inboundFrames.js +116 -0
  376. package/dist/transaction/wire/index.d.ts +54 -0
  377. package/dist/transaction/wire/index.js +83 -0
  378. package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
  379. package/dist/transaction/wire/listEnvelope.js +42 -0
  380. package/dist/transaction/wire/modelMutations.d.ts +31 -0
  381. package/dist/transaction/wire/modelMutations.js +52 -0
  382. package/dist/transaction/wire/modelResponses.d.ts +85 -0
  383. package/dist/transaction/wire/modelResponses.js +43 -0
  384. package/dist/transaction/wire/modelShape.d.ts +78 -0
  385. package/dist/transaction/wire/modelShape.js +74 -0
  386. package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
  387. package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
  388. package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
  389. package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
  390. package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
  391. package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
  392. package/dist/transactions/mutations/commitLatency.d.ts +52 -0
  393. package/dist/transactions/mutations/commitLatency.js +130 -0
  394. package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
  395. package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
  396. package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
  397. package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
  398. package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
  399. package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
  400. package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
  401. package/dist/transactions/mutations/durableWriteStore.js +12 -0
  402. package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
  403. package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
  404. package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
  405. package/dist/utils/mobxSetup.d.ts +1 -1
  406. package/dist/utils/mobxSetup.js +5 -2
  407. package/dist/{core → views}/QueryView.d.ts +2 -2
  408. package/dist/{core → views}/QueryView.js +2 -2
  409. package/dist/{core → views}/ViewRegistry.d.ts +1 -1
  410. package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
  411. package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
  412. package/dist/webhooks/events.d.ts +2 -2
  413. package/dist/wire/index.d.ts +1 -34
  414. package/dist/wire/index.js +8 -49
  415. package/docs/agent-messaging.md +3 -3
  416. package/docs/agents.md +20 -13
  417. package/docs/api-keys.md +14 -10
  418. package/docs/api.md +27 -61
  419. package/docs/audit.md +6 -3
  420. package/docs/cli.md +41 -13
  421. package/docs/client-behavior.md +11 -9
  422. package/docs/concurrency-convention.md +49 -57
  423. package/docs/coordination.md +283 -121
  424. package/docs/data-sources.md +7 -5
  425. package/docs/debugging.md +39 -15
  426. package/docs/deployment.md +267 -0
  427. package/docs/examples/agent-human.md +49 -42
  428. package/docs/examples/ai-sdk-tool.md +69 -44
  429. package/docs/examples/existing-python-backend.md +8 -6
  430. package/docs/examples/nextjs.md +129 -47
  431. package/docs/examples/scoped-agent.md +46 -45
  432. package/docs/examples/server-agent.md +46 -26
  433. package/docs/groups.md +87 -30
  434. package/docs/guarantees.md +41 -12
  435. package/docs/how-it-works.md +38 -12
  436. package/docs/idempotency.md +126 -0
  437. package/docs/identity.md +77 -74
  438. package/docs/index.md +172 -86
  439. package/docs/integration-guide.md +31 -19
  440. package/docs/mcp.md +46 -21
  441. package/docs/migration.md +95 -18
  442. package/docs/operating-on-your-database.md +3 -1
  443. package/docs/projects.md +3 -1
  444. package/docs/quickstart.md +22 -5
  445. package/docs/react.md +31 -18
  446. package/docs/schema-contract.md +5 -3
  447. package/docs/session-settings.md +108 -0
  448. package/docs/sessions.md +4 -2
  449. package/docs/webhooks.md +12 -10
  450. package/llms.txt +48 -18
  451. package/package.json +21 -26
  452. package/dist/agent/Agent.d.ts +0 -366
  453. package/dist/agent/Agent.js +0 -514
  454. package/dist/agent/index.d.ts +0 -115
  455. package/dist/agent/index.js +0 -128
  456. package/dist/agent/session.d.ts +0 -93
  457. package/dist/agent/session.js +0 -149
  458. package/dist/agent/types.d.ts +0 -68
  459. package/dist/agent/types.js +0 -9
  460. package/dist/cli.cjs +0 -286329
  461. package/dist/client/durableWrites.d.ts +0 -21
  462. package/dist/client/httpClient.d.ts +0 -80
  463. package/dist/coordination/schema.d.ts +0 -722
  464. package/dist/coordination/schema.js +0 -578
  465. package/dist/schema/openapi.d.ts +0 -29
  466. package/dist/schema/openapi.js +0 -124
  467. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  468. package/dist/testing/fixtures/bootstrap.js +0 -59
  469. package/dist/testing/fixtures/deltas.d.ts +0 -83
  470. package/dist/testing/fixtures/deltas.js +0 -136
  471. package/dist/testing/fixtures/models.d.ts +0 -83
  472. package/dist/testing/fixtures/models.js +0 -272
  473. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  474. package/dist/testing/helpers/reactWrapper.js +0 -67
  475. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  476. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  477. package/dist/testing/helpers/wait.d.ts +0 -30
  478. package/dist/testing/helpers/wait.js +0 -49
  479. package/dist/testing/index.d.ts +0 -23
  480. package/dist/testing/index.js +0 -33
  481. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  482. package/dist/testing/mocks/FakeDatabase.js +0 -10
  483. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  484. package/dist/testing/mocks/MockMutationExecutor.js +0 -192
  485. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  486. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  487. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  488. package/dist/testing/mocks/MockSyncContext.js +0 -71
  489. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  490. package/dist/testing/mocks/MockSyncStore.js +0 -171
  491. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  492. package/dist/testing/mocks/MockWebSocket.js +0 -118
  493. package/dist/transactions/durableWriteStore.js +0 -30
  494. package/dist/utils/duration.d.ts +0 -25
  495. package/dist/utils/json.js +0 -88
  496. package/dist/wire/errorEnvelope.d.ts +0 -55
  497. package/dist/wire/frames.d.ts +0 -197
  498. package/dist/wire/frames.js +0 -49
  499. package/dist/wire/listEnvelope.js +0 -18
  500. package/docs/interaction-model.md +0 -97
  501. /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
  502. /package/dist/{core → query}/QueryProcessor.js +0 -0
  503. /package/dist/{core/storeContract.js → storeContract.js} +0 -0
  504. /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
  505. /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
  506. /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
  507. /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
  508. /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
  509. /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
  510. /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
  511. /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
  512. /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
  513. /package/dist/{client → transaction}/persistence.d.ts +0 -0
  514. /package/dist/{client → transaction}/persistence.js +0 -0
  515. /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
  516. /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
  517. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
  518. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
  519. /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
  520. /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
  521. /package/dist/{types → transaction/types}/modelData.js +0 -0
  522. /package/dist/{types → transaction/types}/participant.d.ts +0 -0
  523. /package/dist/{types → transaction/types}/participant.js +0 -0
  524. /package/dist/{types → transaction/types}/streams.js +0 -0
  525. /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
  526. /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
  527. /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
  528. /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
  529. /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
  530. /package/dist/{wire → transaction/wire}/protocol.js +0 -0
  531. /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
  532. /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
  533. /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
  534. /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
  535. /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
  536. /package/dist/{core → views}/ViewRegistry.js +0 -0
@@ -3,29 +3,71 @@
3
3
  * `ablo.<model>`.
4
4
  *
5
5
  * Each schema model gets one {@link ModelOperations}: the async server reads
6
- * `retrieve` and `list`, the synchronous local-graph snapshots `get`, `getAll`,
7
- * and `getCount`, the writes `create`, `update`, and `delete`, the coordination
6
+ * `retrieve` and `list`, the same verbs restricted to the local graph under
7
+ * `local`, the writes `create`, `update`, and `delete`, the coordination
8
8
  * namespace `claim` (callable as `claim({ id })`, plus `claim.state`,
9
9
  * `claim.queue`, `claim.release`, and `claim.reorder`), `join`, and `onChange`.
10
10
  * The factory returns a plain object; the client assembles the `ablo.<model>`
11
11
  * lookup table from one of these per model.
12
12
  */
13
13
  import { autorun } from 'mobx';
14
- import { AbloClaimedError, AbloStaleContextError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../errors.js';
15
- import { reconcileFunctionalUpdate, } from './functionalUpdate.js';
14
+ import { AbloClaimedError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../transaction/errors.js';
15
+ import { reconcileFunctionalUpdate, } from '../transaction/resources/functionalUpdate.js';
16
+ import { claimDescription, } from '../transaction/coordination/schema.js';
16
17
  import { Model, modelAsRow } from '../Model.js';
17
- import { toMs } from '../utils/duration.js';
18
- import { LEASE_TTL_MS } from '../wire/protocol.js';
19
- import { heartbeatCadenceMs, resolveHeartbeatOptions, startClaimHeartbeatLoop, } from './claimHeartbeatLoop.js';
20
- import { assertWriteOptions } from './writeOptionsSchema.js';
21
- import { ModelScope } from '../types/index.js';
18
+ import { toMs } from '../transaction/utils/duration.js';
19
+ import { LEASE_TTL_MS } from '../transaction/wire/protocol.js';
20
+ import { heartbeatCadenceMs, resolveHeartbeatOptions, resolveHeartbeatPlan, startClaimHeartbeatLoop, } from '../transaction/coordination/claimHeartbeatLoop.js';
21
+ import { assertWriteOptions } from '../transaction/resources/writeOptionsSchema.js';
22
+ import { modelTarget, subTarget } from '../transaction/coordination/index.js';
23
+ // A named claim-meta crossing (see `claim-meta-crossings-are-enumerated` in
24
+ // .dependency-cruiser.cjs): the reactive proxy's self-claim targets are
25
+ // decodes that build a public claim, so their `meta` converts wire→declared
26
+ // here like the other enumerated crossings.
27
+ import { declaredMeta } from '../transaction/coordination/claimMeta.js';
28
+ import { ModelScope } from '../transaction/types/index.js';
22
29
  const modelClientMeta = new WeakMap();
23
30
  export function getModelClientMeta(modelClient) {
24
31
  if (typeof modelClient !== 'object' || modelClient === null)
25
32
  return undefined;
26
33
  return modelClientMeta.get(modelClient);
27
34
  }
28
- export function createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, registry, hydration, collaboration) {
35
+ export function createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, registry,
36
+ /**
37
+ * The one thing this factory asks of the loader: fetch rows for a model.
38
+ *
39
+ * Declared as the slice rather than the whole `OnDemandLoader` because the
40
+ * whole is a class, and a parameter typed as a class can only ever be
41
+ * satisfied by an instance of it — so every caller that has a narrower
42
+ * collaborator, a test most of all, is pushed into a cast through `unknown`
43
+ * to supply the one method that is actually read.
44
+ */
45
+ hydration, collaboration,
46
+ /** The client-wide `wait` default; a per-call `wait` still wins over it. */
47
+ defaultWait) {
48
+ /**
49
+ * Resolve a row **this** resource owns.
50
+ *
51
+ * The pool is one id space, so `objectPool.get(id)` happily returns another
52
+ * model's row. Every write path below addresses rows by bare id, so without
53
+ * this an id from a sibling model resolves and gets written — silently
54
+ * corrupting a row the caller never named.
55
+ *
56
+ * `undefined` means genuinely absent. A row belonging to another model throws:
57
+ * unlike a read, a cross-model *write* is never a legitimate outcome, and
58
+ * naming both models turns a silent corruption into a one-line diagnosis.
59
+ */
60
+ const ownRowOrThrow = (id) => {
61
+ const own = objectPool.getOfType(id, registeredModelName);
62
+ if (own)
63
+ return own;
64
+ const foreign = objectPool.get(id);
65
+ if (!foreign)
66
+ return undefined;
67
+ const owner = foreign.getModelName();
68
+ throw new AbloValidationError(`No ${registeredModelName} with id ${id} — that id belongs to a ${owner}. ` +
69
+ `Read or write it through ${owner}.`, { code: 'entity_not_found' });
70
+ };
29
71
  const ModelClass = registry.getModelByName(registeredModelName);
30
72
  if (!ModelClass) {
31
73
  throw new AbloValidationError(`Ablo: schema model "${schemaKey}" resolved to "${registeredModelName}", ` +
@@ -66,7 +108,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
66
108
  return rows;
67
109
  };
68
110
  const waitForMutation = async (model, options) => {
69
- if (options?.wait !== 'confirmed')
111
+ // A per-call `wait` wins; otherwise the client-wide default decides. This
112
+ // is the single point that turns "confirmed" into actually waiting, so a
113
+ // client configured that way rejects on a refused write everywhere rather
114
+ // than in the one place a caller remembered to ask.
115
+ if ((options?.wait ?? defaultWait) !== 'confirmed')
70
116
  return;
71
117
  await syncClient.syncNow();
72
118
  await syncClient.waitForConfirmation(model.getModelName(), model.id);
@@ -93,7 +139,6 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
93
139
  value.object === 'claim' &&
94
140
  typeof value.id === 'string' &&
95
141
  typeof value.release === 'function';
96
- const claimMeta = (options) => options?.meta;
97
142
  const claimContextFromClaim = (claim) => {
98
143
  return {
99
144
  id: claim.id,
@@ -104,12 +149,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
104
149
  status: claim.status,
105
150
  expiresAt: claim.expiresAt,
106
151
  target: {
107
- model: claim.target.type,
108
- id: claim.target.id,
109
- path: claim.target.path,
110
- range: claim.target.range,
111
- field: claim.target.field,
112
- meta: claim.target.meta,
152
+ ...modelTarget(claim.target),
153
+ ...subTarget(claim.target),
113
154
  },
114
155
  };
115
156
  };
@@ -139,26 +180,21 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
139
180
  const held = collaboration.state({ model: wireModel, id });
140
181
  const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
141
182
  const failFast = options.queue === false;
142
- // Fail-fast (`queue: false`): if another participant already holds it,
143
- // reject now instead of queuing. Best-effort at the client (a racing
144
- // claim not yet synced into our snapshot slips through here) — the
145
- // commit-time claim guard is the authoritative backstop that rejects
146
- // the loser's first write. For work-distribution dedup that's exactly
147
- // right: don't wait (that would double-process), skip.
183
+ // The try-claim (`queue: false`): a held target is an expected outcome,
184
+ // not an error, so it resolves `null` the caller reads `if (!claim)`
185
+ // and moves on; who holds it stays readable via `claim.state`. Best-effort
186
+ // at the client (a racing claim not yet synced into our snapshot slips
187
+ // through here) the commit-time claim guard is the authoritative
188
+ // backstop that rejects the loser's first write. For work-distribution
189
+ // dedup that's exactly right: don't wait (that would double-process), skip.
148
190
  if (failFast && contended) {
149
- const claim = claimContextFromClaim(held);
150
- throw new AbloClaimedError(formatClaimedErrorMessage({
151
- targetLabel: `${registeredModelName}/${id}`,
152
- heldBy: held.heldBy,
153
- claim,
154
- fallback: `${registeredModelName}/${id} is held by ${held.heldBy ?? 'another participant'}.`,
155
- }), { code: 'entity_claimed', claims: [claim] });
191
+ return null;
156
192
  }
157
193
  // Ensure the row exists locally before claiming.
158
- let model = objectPool.get(id);
194
+ let model = ownRowOrThrow(id);
159
195
  if (!model) {
160
196
  await load({ where: [['id', id]] });
161
- model = objectPool.get(id);
197
+ model = ownRowOrThrow(id);
162
198
  }
163
199
  if (!model) {
164
200
  throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
@@ -180,15 +216,19 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
180
216
  target: {
181
217
  model: wireModel,
182
218
  id,
183
- ...(options.field ? { field: options.field } : {}),
184
- ...(options.path ? { path: options.path } : {}),
185
- ...(options.range ? { range: options.range } : {}),
186
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
219
+ // The whole sub-entity locator in one move — listing its members here
220
+ // is what let `fields` die between the caller and the lease, so the
221
+ // claim covered the whole row while the caller believed it named parts.
222
+ ...subTarget(options),
187
223
  },
188
- description: options.description ?? 'editing',
224
+ description: claimDescription(options),
189
225
  ttl: options.ttl,
190
226
  queue: !failFast,
191
227
  maxQueueDepth: options.maxQueueDepth,
228
+ // The one wait cap, declared once on ClaimTargetOptions — the socket
229
+ // wait and the HTTP poll-wait both honor it as `grant_timeout`.
230
+ waitTimeoutMs: options.waitTimeoutMs,
231
+ signal: options.signal,
192
232
  });
193
233
  // Only when the claim actually waited behind another holder can the row have
194
234
  // changed underneath us — re-read so the claimed snapshot reflects what that
@@ -205,20 +245,22 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
205
245
  // holder's final write may not have fanned out yet — the exact
206
246
  // stale-snapshot race this re-read closes.
207
247
  await load({ where: [['id', id]], type: 'complete' });
208
- model = objectPool.get(id) ?? model;
248
+ model = ownRowOrThrow(id) ?? model;
209
249
  }
210
250
  const snapshot = collaboration.createSnapshot(schemaKey, id);
211
- const description = options.description ?? 'editing';
251
+ const description = claimDescription(options);
212
252
  // The self-claim's `ClaimTarget` mirrors what a peer's `claim.state` would
213
253
  // report (`state` maps `held.target.model` to `type`), so a holder and a
214
254
  // peer see the same `target.type` for one row — the wire model token.
255
+ // Its `meta` is the DECLARED shape (the handle is a public claim), so the
256
+ // wire-shaped projection converts back through `declaredMeta` — the same
257
+ // crossing the HTTP handle assembly makes.
258
+ const { meta: selfMeta, ...selfNarrowed } = subTarget(options);
215
259
  const selfTarget = {
216
260
  type: wireModel,
217
261
  id,
218
- ...(options.field ? { field: options.field } : {}),
219
- ...(options.path ? { path: options.path } : {}),
220
- ...(options.range ? { range: options.range } : {}),
221
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
262
+ ...selfNarrowed,
263
+ ...(selfMeta !== undefined ? { meta: declaredMeta(selfMeta) } : {}),
222
264
  };
223
265
  const ttlMs = options.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS;
224
266
  const expiresAt = Date.now() + ttlMs;
@@ -229,17 +271,20 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
229
271
  description,
230
272
  expiresAt,
231
273
  });
274
+ const { meta: targetMeta, ...targetNarrowed } = subTarget(options);
232
275
  const target = {
233
276
  type: schemaKey,
234
277
  id,
235
- ...(options.field ? { field: options.field } : {}),
236
- ...(options.path ? { path: options.path } : {}),
237
- ...(options.range ? { range: options.range } : {}),
238
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
278
+ ...targetNarrowed,
279
+ ...(targetMeta !== undefined ? { meta: declaredMeta(targetMeta) } : {}),
239
280
  };
281
+ // One reading of the heartbeat options — cadence and callbacks from
282
+ // whichever spelling the caller used (plan object, shorthand, or the
283
+ // deprecated flat callbacks).
284
+ const plan = resolveHeartbeatPlan(options);
240
285
  // A beat resolves with the server's extended expiry; keep the local
241
286
  // self-claim estimate in step so `claim.state` renders the real window,
242
- // and surface every answer through `onHeartbeat` (pressure signal).
287
+ // and surface every answer through the plan's `onBeat` (pressure signal).
243
288
  const heartbeat = async (beatOptions) => {
244
289
  if (!lease.heartbeat) {
245
290
  throw new AbloValidationError('This claim handle has no heartbeat wiring, which the standard Ablo({ schema, apiKey }) client provides on every claim. This appears only when a claim is minted through an internal path that predates heartbeats.', { code: 'claim_not_wired' });
@@ -252,18 +297,16 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
252
297
  const held = activeClaims.get(id);
253
298
  if (held)
254
299
  held.expiresAt = beat.expiresAt;
255
- options.onHeartbeat?.(beat);
300
+ plan.onBeat?.(beat);
256
301
  return beat;
257
302
  };
258
303
  // Opt-in auto-heartbeat: the loop beats until release, and a definitive
259
- // loss stops it and surfaces through `onHeartbeatLost`.
260
- const stopHeartbeatLoop = options.heartbeat
304
+ // loss stops it and surfaces through the plan's `onLost`.
305
+ const stopHeartbeatLoop = plan.loop
261
306
  ? startClaimHeartbeatLoop({
262
307
  beat: () => heartbeat(),
263
- intervalMs: heartbeatCadenceMs(ttlMs, options.heartbeat),
264
- ...(options.onHeartbeatLost
265
- ? { onLost: options.onHeartbeatLost }
266
- : {}),
308
+ intervalMs: heartbeatCadenceMs(ttlMs, plan.cadence),
309
+ ...(plan.onLost ? { onLost: plan.onLost } : {}),
267
310
  })
268
311
  : undefined;
269
312
  const release = () => {
@@ -305,19 +348,14 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
305
348
  const held = collaboration.state({ model: wireModel, id });
306
349
  const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
307
350
  const failFast = options.queue === false;
308
- // Fail-fast (`queue: false`): reject now if a holder is already visible.
309
- // Best-effort at the client — a row this participant never synced usually
310
- // carries no local claim state either, so a peer gets the deterministic
311
- // rejection only once it has observed the holder (entered the row's entity
312
- // scope). The server's queue is the backstop for the queuing path.
351
+ // The try-claim (`queue: false`): resolve `null` if a holder is already
352
+ // visible — an expected outcome, not an error. Best-effort at the client —
353
+ // a row this participant never synced usually carries no local claim state
354
+ // either, so a peer gets the deterministic `null` only once it has
355
+ // observed the holder (entered the row's entity scope). The server's
356
+ // queue is the backstop for the queuing path.
313
357
  if (failFast && contended) {
314
- const claim = claimContextFromClaim(held);
315
- throw new AbloClaimedError(formatClaimedErrorMessage({
316
- targetLabel: `${registeredModelName}/${id}`,
317
- heldBy: held.heldBy,
318
- claim,
319
- fallback: `${registeredModelName}/${id} is held by ${held.heldBy ?? 'another participant'}.`,
320
- }), { code: 'entity_claimed', claims: [claim] });
358
+ return null;
321
359
  }
322
360
  // Enter the entity scope before acquiring the lease so the holder's claim
323
361
  // presence broadcasts to everyone in this entity group — the same ordering
@@ -329,29 +367,30 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
329
367
  target: {
330
368
  model: wireModel,
331
369
  id,
332
- ...(options.field ? { field: options.field } : {}),
333
- ...(options.path ? { path: options.path } : {}),
334
- ...(options.range ? { range: options.range } : {}),
335
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
370
+ // The whole sub-entity locator in one move — listing its members here
371
+ // is what let `fields` die between the caller and the lease, so the
372
+ // claim covered the whole row while the caller believed it named parts.
373
+ ...subTarget(options),
336
374
  },
337
- description: options.description ?? 'editing',
375
+ description: claimDescription(options),
338
376
  ttl: options.ttl,
339
377
  queue: !failFast,
340
378
  maxQueueDepth: options.maxQueueDepth,
379
+ // The one wait cap, declared once on ClaimTargetOptions — the socket
380
+ // wait and the HTTP poll-wait both honor it as `grant_timeout`.
381
+ waitTimeoutMs: options.waitTimeoutMs,
382
+ signal: options.signal,
341
383
  });
342
384
  // A watermark-only snapshot: `createSnapshot` still reads the engine's
343
385
  // current `lastSyncId` even though the pool holds no row (the bucket is
344
386
  // empty). It costs nothing extra and gives a write taken under this lease a
345
387
  // real `readAt` to guard against changes since the lease was acquired.
346
388
  const snapshot = collaboration.createSnapshot(schemaKey, id);
347
- const description = options.description ?? 'editing';
389
+ const description = claimDescription(options);
348
390
  const selfTarget = {
349
391
  type: wireModel,
350
392
  id,
351
- ...(options.field ? { field: options.field } : {}),
352
- ...(options.path ? { path: options.path } : {}),
353
- ...(options.range ? { range: options.range } : {}),
354
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
393
+ ...subTarget(options),
355
394
  };
356
395
  const ttlMs = options.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS;
357
396
  const expiresAt = Date.now() + ttlMs;
@@ -365,11 +404,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
365
404
  const target = {
366
405
  type: schemaKey,
367
406
  id,
368
- ...(options.field ? { field: options.field } : {}),
369
- ...(options.path ? { path: options.path } : {}),
370
- ...(options.range ? { range: options.range } : {}),
371
- ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
407
+ ...subTarget(options),
372
408
  };
409
+ // One reading of the heartbeat options — cadence and callbacks from
410
+ // whichever spelling the caller used.
411
+ const plan = resolveHeartbeatPlan(options);
373
412
  // A beat resolves with the server's extended expiry; keep the local
374
413
  // self-claim estimate in step so `claim.state` renders the real window.
375
414
  const heartbeat = async (beatOptions) => {
@@ -384,16 +423,14 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
384
423
  const held = activeClaims.get(id);
385
424
  if (held)
386
425
  held.expiresAt = beat.expiresAt;
387
- options.onHeartbeat?.(beat);
426
+ plan.onBeat?.(beat);
388
427
  return beat;
389
428
  };
390
- const stopHeartbeatLoop = options.heartbeat
429
+ const stopHeartbeatLoop = plan.loop
391
430
  ? startClaimHeartbeatLoop({
392
431
  beat: () => heartbeat(),
393
- intervalMs: heartbeatCadenceMs(ttlMs, options.heartbeat),
394
- ...(options.onHeartbeatLost
395
- ? { onLost: options.onHeartbeatLost }
396
- : {}),
432
+ intervalMs: heartbeatCadenceMs(ttlMs, plan.cadence),
433
+ ...(plan.onLost ? { onLost: plan.onLost } : {}),
397
434
  })
398
435
  : undefined;
399
436
  const release = () => {
@@ -434,7 +471,41 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
434
471
  // members to read/steer the coordination plane. Attach the readers to the
435
472
  // callable so `ablo.<model>.claim(...)` and `ablo.<model>.claim.state(...)`
436
473
  // are the same object.
437
- const claimApi = Object.assign(claim, {
474
+ // `state` and `queue` take a caller-named `meta` shape. The runtime cannot
475
+ // check it and is not meant to: `target.meta` is application data the
476
+ // protocol carries verbatim and never interprets, so naming its type is the
477
+ // caller asserting what it put there — the same bargain as parsing your own
478
+ // JSON into an interface. These read as `Claim` here, and the one assertion
479
+ // that applies the caller's parameter is on the assignment below, in one
480
+ // place rather than at every call site.
481
+ /**
482
+ * This client's own claim on a row, as a claim-state object.
483
+ *
484
+ * The server excludes a holder's own presence frames and the client skips
485
+ * them, so a row this client holds is absent from every peer-derived read.
486
+ * Both `state` and `list` therefore synthesize it from the stored lease, and
487
+ * they do it through here so the two answers cannot describe the same
488
+ * holding differently.
489
+ */
490
+ const ownClaimState = (id) => {
491
+ const own = activeClaims.get(id);
492
+ if (!own)
493
+ return null;
494
+ return {
495
+ object: 'claim',
496
+ id: own.lease.id,
497
+ status: 'active',
498
+ target: own.target,
499
+ description: own.description,
500
+ heldBy: collaboration?.selfParticipantId ?? '',
501
+ participantKind: collaboration?.selfParticipantKind ?? 'user',
502
+ expiresAt: own.expiresAt,
503
+ // Symmetric with the peer projection: a holder reading its own claim
504
+ // sees the same `meta` an observer does.
505
+ ...(own.target.meta !== undefined ? { meta: own.target.meta } : {}),
506
+ };
507
+ };
508
+ const claimReaders = {
438
509
  state(params) {
439
510
  // Read interest: a passive observer of a row's claim state must enter that
440
511
  // row's entity scope, or it sits only on broader `org:`/`user:` groups and
@@ -445,20 +516,27 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
445
516
  // the client skips them, so `state` would return null for a row this client
446
517
  // holds. Synthesize the active claim from the stored lease so the holder
447
518
  // sees its own claim, honoring the documented contract on `claim.state`.
448
- const own = activeClaims.get(params.id);
449
- if (own) {
450
- return {
451
- object: 'claim',
452
- id: own.lease.id,
453
- status: 'active',
454
- target: own.target,
455
- description: own.description,
456
- heldBy: collaboration?.selfParticipantId ?? '',
457
- participantKind: collaboration?.selfParticipantKind ?? 'user',
458
- expiresAt: own.expiresAt,
459
- };
460
- }
461
- return collaboration?.state({ model: wireModel, id: params.id }) ?? null;
519
+ return (ownClaimState(params.id) ??
520
+ collaboration?.state({ model: wireModel, id: params.id }) ??
521
+ null);
522
+ },
523
+ /**
524
+ * Every claim on the row, holders first. Sub-row claims on disjoint parts
525
+ * are all granted, so a row can have several holders at once and
526
+ * {@link state} answers with one of them — this is the read that renders
527
+ * all of them. Same list envelope as {@link queue}, reactive on the same
528
+ * snapshot, so a render reads it inline.
529
+ */
530
+ list(params) {
531
+ void collaboration?.enterScope?.({ [schemaKey]: params.id });
532
+ const own = ownClaimState(params.id);
533
+ const peers = collaboration?.holders({ model: wireModel, id: params.id }) ?? [];
534
+ return {
535
+ object: 'list',
536
+ // Own claim first: the server excludes a holder's own presence frames,
537
+ // so it is never among `peers` and the two never duplicate.
538
+ data: own ? [own, ...peers] : [...peers],
539
+ };
462
540
  },
463
541
  queue(params) {
464
542
  return {
@@ -470,28 +548,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
470
548
  collaboration?.reorder({ model: wireModel, id: params.id }, params.order);
471
549
  },
472
550
  release: guard((params) => releaseClaim(isClaimHandle(params) ? params.target.id : params.id)),
473
- });
474
- const operations = {
475
- retrieve: guard(async (params) => {
476
- // Read-interest enrolment: reading a row enters its entity scope, so a
477
- // client lands in the same group the holder's claim presence fans out
478
- // on and `claim.state`/`claim.queue` report peers. Best-effort and
479
- // fire-and-forget it never makes the read reject or run slower.
480
- void collaboration?.enterScope?.({ [schemaKey]: params.id });
481
- const rows = await load({
482
- ...params,
483
- where: [['id', params.id]],
484
- limit: 1,
485
- });
486
- return rows[0];
487
- }),
488
- // No automatic scope enrolment on bulk `list`/`getAll`: that would subscribe
489
- // to an unbounded set of rows' entity groups.
490
- list: guard(load),
491
- get(id) {
492
- return objectPool.get(id);
551
+ };
552
+ // The one place the caller's `meta` parameter is applied — see the note on
553
+ // `claimReaders`. Everything else about this object is checked structurally.
554
+ const claimApi = Object.assign(claim, claimReaders);
555
+ const local = {
556
+ retrieve(id) {
557
+ // Scoped to this model: an id belonging to a sibling model reads as
558
+ // absent rather than being handed back as if it were a `T`.
559
+ return objectPool.getOfType(id, registeredModelName);
493
560
  },
494
- getAll(options) {
561
+ list(options) {
495
562
  const all = objectPool.getByType(ModelClass, (options?.state ?? ModelScope.live));
496
563
  let result = all;
497
564
  if (options?.where) {
@@ -525,9 +592,28 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
525
592
  result = result.slice(0, options.limit);
526
593
  return result;
527
594
  },
528
- getCount(options) {
529
- return this.getAll(options).length;
595
+ count(options) {
596
+ return local.list(options).length;
530
597
  },
598
+ };
599
+ const operations = {
600
+ local,
601
+ retrieve: guard(async (params) => {
602
+ // Read-interest enrolment: reading a row enters its entity scope, so a
603
+ // client lands in the same group the holder's claim presence fans out
604
+ // on and `claim.state`/`claim.queue` report peers. Best-effort and
605
+ // fire-and-forget — it never makes the read reject or run slower.
606
+ void collaboration?.enterScope?.({ [schemaKey]: params.id });
607
+ const rows = await load({
608
+ ...params,
609
+ where: [['id', params.id]],
610
+ limit: 1,
611
+ });
612
+ return rows[0];
613
+ }),
614
+ // No automatic scope enrolment on bulk `list`: that would subscribe to an
615
+ // unbounded set of rows' entity groups.
616
+ list: guard(load),
531
617
  create: guard(async (params) => {
532
618
  const id = params.id ?? Model.generateId();
533
619
  const opts = mutationOptions(params);
@@ -547,12 +633,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
547
633
  target: {
548
634
  model: wireModel,
549
635
  id,
550
- ...(claim.field ? { field: claim.field } : {}),
551
- ...(claim.path ? { path: claim.path } : {}),
552
- ...(claim.range ? { range: claim.range } : {}),
553
- ...(claimMeta(claim) ? { meta: claimMeta(claim) } : {}),
636
+ ...subTarget(claim),
554
637
  },
555
- description: claim.description ?? 'creating',
638
+ description: claimDescription(claim, 'creating'),
556
639
  ttl: claim.ttl,
557
640
  queue: claim.queue !== false,
558
641
  maxQueueDepth: claim.maxQueueDepth,
@@ -615,7 +698,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
615
698
  // `type: 'complete'` forces the round-trip — the hydration ledger
616
699
  // would otherwise serve a possibly-stale local row for a hydrated id.
617
700
  await load({ where: [['id', id]], type: 'complete' });
618
- const fresh = objectPool.get(id);
701
+ const fresh = ownRowOrThrow(id);
619
702
  const snapshot = collaboration.createSnapshot(schemaKey, id);
620
703
  return {
621
704
  data: fresh ? modelAsRow(fresh) : undefined,
@@ -623,7 +706,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
623
706
  };
624
707
  },
625
708
  writeNext: async (patch, readAt) => {
626
- const model = objectPool.get(id);
709
+ const model = ownRowOrThrow(id);
627
710
  if (!model) {
628
711
  throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
629
712
  }
@@ -643,6 +726,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
643
726
  const autoClaim = params.claim && !isClaimHandle(params.claim) ? params.claim : null;
644
727
  if (autoClaim) {
645
728
  const handle = await takeClaim({ ...autoClaim, id: params.id });
729
+ // A declined try-claim is `null` only on the standalone verb; a
730
+ // write that could not take its claim is a failed write.
731
+ if (!handle) {
732
+ throw new AbloClaimedError(`${registeredModelName}/${params.id} is held by another participant, so this update's claim could not be taken.`, { code: 'entity_claimed' });
733
+ }
646
734
  try {
647
735
  return await operations.update({ ...params, claim: handle });
648
736
  }
@@ -651,7 +739,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
651
739
  }
652
740
  }
653
741
  const { id } = params;
654
- const model = objectPool.get(id);
742
+ const model = ownRowOrThrow(id);
655
743
  if (!model)
656
744
  throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
657
745
  // If we hold a claim on this row, guard the write with its snapshot
@@ -702,6 +790,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
702
790
  const autoClaim = params.claim && !isClaimHandle(params.claim) ? params.claim : null;
703
791
  if (autoClaim) {
704
792
  const handle = await takeClaim({ ...autoClaim, id: params.id });
793
+ // Same rule as update: a write that could not take its claim fails.
794
+ if (!handle) {
795
+ throw new AbloClaimedError(`${registeredModelName}/${params.id} is held by another participant, so this delete's claim could not be taken.`, { code: 'entity_claimed' });
796
+ }
705
797
  try {
706
798
  await operations.delete({ ...params, claim: handle });
707
799
  }
@@ -711,7 +803,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
711
803
  return;
712
804
  }
713
805
  const { id } = params;
714
- const model = objectPool.get(id);
806
+ // Scoped: "ensure absent" stays idempotent for an id this model simply
807
+ // doesn't hold, but an id owned by a sibling model throws rather than
808
+ // deleting a row the caller never addressed.
809
+ const model = ownRowOrThrow(id);
715
810
  // Idempotent delete: "ensure absent". A row that isn't in this client's
716
811
  // replicated view is already gone from its perspective, so a delete is a
717
812
  // no-op success rather than an `entity_not_found` error. This matches the
@@ -758,13 +853,13 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
758
853
  };
759
854
  // A track carries no write, so it rides the commit lane as a zero-operation
760
855
  // commit: the queue tolerates disconnects and de-dupes replays, and the
761
- // server's track-only path registers the dependency and reports anything
856
+ // server's track-only path registers the premise and reports anything
762
857
  // that already fired. Reuse the same lane the batch `commits.create` door
763
858
  // uses rather than opening a bespoke transport.
764
859
  const clientTxId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
765
860
  ? crypto.randomUUID()
766
861
  : `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
767
- const queue = syncClient.getTransactionQueue();
862
+ const queue = syncClient.getMutationQueue();
768
863
  await queue.enqueueCommit(clientTxId, [], { track: [dep] });
769
864
  const { notifications } = await queue.waitForCommitReceipt(clientTxId);
770
865
  return notifications && notifications.length > 0 ? { notifications } : {};
@@ -777,8 +872,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
777
872
  }),
778
873
  onChange(callback, options) {
779
874
  return autorun(() => {
780
- const entities = this.getAll(options);
781
- callback(entities);
875
+ callback(local.list(options));
782
876
  });
783
877
  },
784
878
  };