@abloatai/ablo 0.26.0 → 0.28.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 (418) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +102 -86
  3. package/dist/BaseSyncedStore.d.ts +85 -88
  4. package/dist/BaseSyncedStore.js +134 -151
  5. package/dist/Database.d.ts +68 -69
  6. package/dist/Database.js +316 -135
  7. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  8. package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
  9. package/dist/LazyReferenceCollection.d.ts +11 -15
  10. package/dist/LazyReferenceCollection.js +12 -16
  11. package/dist/Model.d.ts +54 -52
  12. package/dist/Model.js +78 -62
  13. package/dist/ModelRegistry.d.ts +21 -19
  14. package/dist/ModelRegistry.js +23 -27
  15. package/dist/NetworkMonitor.d.ts +5 -6
  16. package/dist/NetworkMonitor.js +5 -6
  17. package/dist/SyncClient.d.ts +122 -118
  18. package/dist/SyncClient.js +541 -245
  19. package/dist/adapters/alwaysOnline.d.ts +6 -8
  20. package/dist/adapters/alwaysOnline.js +6 -8
  21. package/dist/adapters/inMemoryStorage.d.ts +10 -9
  22. package/dist/adapters/inMemoryStorage.js +21 -9
  23. package/dist/agent/Agent.d.ts +27 -32
  24. package/dist/agent/Agent.js +18 -19
  25. package/dist/agent/index.d.ts +4 -4
  26. package/dist/agent/index.js +5 -5
  27. package/dist/agent/session.d.ts +47 -44
  28. package/dist/agent/session.js +37 -48
  29. package/dist/agent/types.d.ts +26 -31
  30. package/dist/agent/types.js +6 -7
  31. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  32. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  33. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  34. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
  35. package/dist/ai-sdk/index.d.ts +25 -22
  36. package/dist/ai-sdk/index.js +25 -22
  37. package/dist/ai-sdk/wrap.d.ts +6 -7
  38. package/dist/ai-sdk/wrap.js +1 -1
  39. package/dist/auth/credentialPolicy.d.ts +69 -74
  40. package/dist/auth/credentialPolicy.js +51 -56
  41. package/dist/auth/credentialSource.d.ts +6 -5
  42. package/dist/auth/credentialSource.js +9 -10
  43. package/dist/auth/index.d.ts +59 -58
  44. package/dist/auth/index.js +31 -37
  45. package/dist/auth/schemas.d.ts +5 -4
  46. package/dist/auth/schemas.js +5 -4
  47. package/dist/batching/index.d.ts +19 -21
  48. package/dist/batching/index.js +14 -17
  49. package/dist/cli.cjs +173 -121
  50. package/dist/client/Ablo.d.ts +97 -74
  51. package/dist/client/Ablo.js +129 -163
  52. package/dist/client/ApiClient.d.ts +30 -19
  53. package/dist/client/ApiClient.js +442 -81
  54. package/dist/client/auth.d.ts +47 -47
  55. package/dist/client/auth.js +108 -117
  56. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  57. package/dist/client/claimHeartbeatLoop.js +88 -0
  58. package/dist/client/consoleLogger.d.ts +5 -6
  59. package/dist/client/consoleLogger.js +5 -6
  60. package/dist/client/createInternalComponents.d.ts +16 -17
  61. package/dist/client/createInternalComponents.js +26 -31
  62. package/dist/client/createModelProxy.d.ts +130 -120
  63. package/dist/client/createModelProxy.js +152 -122
  64. package/dist/client/credentialEndpoint.d.ts +40 -42
  65. package/dist/client/credentialEndpoint.js +35 -36
  66. package/dist/client/functionalUpdate.d.ts +29 -27
  67. package/dist/client/functionalUpdate.js +21 -21
  68. package/dist/client/hostedEndpoints.d.ts +9 -12
  69. package/dist/client/hostedEndpoints.js +9 -12
  70. package/dist/client/httpClient.d.ts +59 -53
  71. package/dist/client/httpClient.js +29 -31
  72. package/dist/client/identity.d.ts +15 -20
  73. package/dist/client/identity.js +47 -58
  74. package/dist/client/modelRegistration.d.ts +5 -9
  75. package/dist/client/modelRegistration.js +78 -87
  76. package/dist/client/options.d.ts +157 -157
  77. package/dist/client/options.js +3 -7
  78. package/dist/client/registerDataSource.d.ts +9 -9
  79. package/dist/client/registerDataSource.js +15 -16
  80. package/dist/client/resourceTypes.d.ts +64 -75
  81. package/dist/client/resourceTypes.js +4 -10
  82. package/dist/client/schemaConfig.d.ts +31 -43
  83. package/dist/client/schemaConfig.js +38 -50
  84. package/dist/client/sessionMint.d.ts +16 -12
  85. package/dist/client/sessionMint.js +26 -31
  86. package/dist/client/validateAbloOptions.d.ts +12 -14
  87. package/dist/client/validateAbloOptions.js +8 -9
  88. package/dist/client/writeOptionsSchema.d.ts +18 -16
  89. package/dist/client/writeOptionsSchema.js +23 -20
  90. package/dist/client/wsMutationExecutor.d.ts +16 -20
  91. package/dist/client/wsMutationExecutor.js +18 -23
  92. package/dist/commit/contract.d.ts +493 -0
  93. package/dist/commit/contract.js +187 -0
  94. package/dist/commit/index.d.ts +6 -0
  95. package/dist/commit/index.js +5 -0
  96. package/dist/context.d.ts +6 -4
  97. package/dist/context.js +6 -4
  98. package/dist/coordination/index.d.ts +10 -8
  99. package/dist/coordination/index.js +14 -12
  100. package/dist/coordination/schema.d.ts +176 -128
  101. package/dist/coordination/schema.js +197 -133
  102. package/dist/coordination/trace.d.ts +9 -10
  103. package/dist/coordination/trace.js +13 -14
  104. package/dist/core/DatabaseManager.d.ts +5 -7
  105. package/dist/core/DatabaseManager.js +15 -19
  106. package/dist/core/QueryProcessor.d.ts +7 -9
  107. package/dist/core/QueryProcessor.js +22 -28
  108. package/dist/core/QueryView.d.ts +8 -8
  109. package/dist/core/QueryView.js +2 -2
  110. package/dist/core/StoreManager.d.ts +14 -14
  111. package/dist/core/StoreManager.js +33 -24
  112. package/dist/core/ViewRegistry.d.ts +5 -5
  113. package/dist/core/ViewRegistry.js +4 -4
  114. package/dist/core/index.d.ts +17 -12
  115. package/dist/core/index.js +32 -26
  116. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  117. package/dist/core/openIDBWithTimeout.js +42 -43
  118. package/dist/core/queryUtils.d.ts +45 -0
  119. package/dist/core/queryUtils.js +69 -0
  120. package/dist/core/storeContract.d.ts +63 -61
  121. package/dist/core/storeContract.js +8 -12
  122. package/dist/environment.d.ts +28 -0
  123. package/dist/environment.js +21 -0
  124. package/dist/errorCodes.d.ts +107 -99
  125. package/dist/errorCodes.js +137 -134
  126. package/dist/errors.d.ts +160 -166
  127. package/dist/errors.js +155 -158
  128. package/dist/index.d.ts +36 -27
  129. package/dist/index.js +91 -86
  130. package/dist/interfaces/index.d.ts +102 -113
  131. package/dist/interfaces/index.js +5 -4
  132. package/dist/keys/index.d.ts +27 -29
  133. package/dist/keys/index.js +41 -40
  134. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  135. package/dist/mutators/RecordingTransaction.js +31 -37
  136. package/dist/mutators/Transaction.d.ts +18 -26
  137. package/dist/mutators/Transaction.js +14 -20
  138. package/dist/mutators/UndoManager.d.ts +124 -131
  139. package/dist/mutators/UndoManager.js +177 -156
  140. package/dist/mutators/defineMutators.d.ts +23 -34
  141. package/dist/mutators/defineMutators.js +14 -20
  142. package/dist/mutators/inverseOp.d.ts +12 -15
  143. package/dist/mutators/inverseOp.js +12 -15
  144. package/dist/mutators/mutateActions.d.ts +10 -9
  145. package/dist/mutators/mutateActions.js +1 -1
  146. package/dist/mutators/readerActions.d.ts +9 -8
  147. package/dist/mutators/readerActions.js +2 -2
  148. package/dist/mutators/undoApply.d.ts +31 -27
  149. package/dist/mutators/undoApply.js +26 -24
  150. package/dist/policy/index.d.ts +5 -3
  151. package/dist/policy/index.js +5 -3
  152. package/dist/policy/types.d.ts +104 -100
  153. package/dist/policy/types.js +67 -66
  154. package/dist/query/client.d.ts +28 -23
  155. package/dist/query/client.js +45 -43
  156. package/dist/query/types.d.ts +37 -60
  157. package/dist/query/types.js +13 -33
  158. package/dist/react/AbloProvider.d.ts +1 -1
  159. package/dist/react/AbloProvider.js +2 -2
  160. package/dist/react/context.d.ts +25 -28
  161. package/dist/react/context.js +9 -10
  162. package/dist/react/index.d.ts +41 -42
  163. package/dist/react/index.js +37 -38
  164. package/dist/react/internalContext.d.ts +17 -19
  165. package/dist/react/useAblo.d.ts +28 -25
  166. package/dist/react/useAblo.js +41 -17
  167. package/dist/react/useCurrentUserId.d.ts +8 -7
  168. package/dist/react/useCurrentUserId.js +8 -7
  169. package/dist/react/useErrorListener.d.ts +7 -7
  170. package/dist/react/useErrorListener.js +10 -11
  171. package/dist/react/useMutationFailureListener.d.ts +8 -8
  172. package/dist/react/useMutationFailureListener.js +8 -8
  173. package/dist/react/useMutators.d.ts +11 -11
  174. package/dist/react/useMutators.js +3 -3
  175. package/dist/react/useReactive.js +2 -2
  176. package/dist/react/useSyncStatus.d.ts +4 -6
  177. package/dist/react/useUndoScope.d.ts +7 -9
  178. package/dist/react/useUndoScope.js +1 -1
  179. package/dist/schema/coordination.d.ts +21 -25
  180. package/dist/schema/coordination.js +21 -25
  181. package/dist/schema/ddl.d.ts +43 -39
  182. package/dist/schema/ddl.js +75 -68
  183. package/dist/schema/ddlLock.d.ts +20 -24
  184. package/dist/schema/ddlLock.js +18 -23
  185. package/dist/schema/diff.d.ts +99 -61
  186. package/dist/schema/diff.js +43 -34
  187. package/dist/schema/field.d.ts +37 -42
  188. package/dist/schema/field.js +35 -48
  189. package/dist/schema/generate.d.ts +12 -12
  190. package/dist/schema/generate.js +12 -12
  191. package/dist/schema/index.d.ts +3 -3
  192. package/dist/schema/index.js +21 -23
  193. package/dist/schema/model.d.ts +118 -143
  194. package/dist/schema/model.js +22 -33
  195. package/dist/schema/openapi.d.ts +10 -9
  196. package/dist/schema/openapi.js +5 -3
  197. package/dist/schema/queries.d.ts +29 -31
  198. package/dist/schema/queries.js +23 -25
  199. package/dist/schema/relation.d.ts +89 -99
  200. package/dist/schema/relation.js +13 -13
  201. package/dist/schema/residency.d.ts +16 -13
  202. package/dist/schema/residency.js +16 -13
  203. package/dist/schema/roles.d.ts +36 -43
  204. package/dist/schema/roles.js +31 -37
  205. package/dist/schema/schema.d.ts +64 -43
  206. package/dist/schema/schema.js +31 -32
  207. package/dist/schema/select.d.ts +13 -13
  208. package/dist/schema/select.js +13 -13
  209. package/dist/schema/serialize.d.ts +28 -31
  210. package/dist/schema/serialize.js +27 -31
  211. package/dist/schema/sugar.d.ts +17 -32
  212. package/dist/schema/sugar.js +14 -29
  213. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
  214. package/dist/schema/syncDeltaRow.js +89 -0
  215. package/dist/schema/tenancy.d.ts +44 -46
  216. package/dist/schema/tenancy.js +46 -48
  217. package/dist/server/adapter.d.ts +58 -58
  218. package/dist/server/adapter.js +13 -14
  219. package/dist/server/commit.d.ts +60 -64
  220. package/dist/server/index.d.ts +9 -10
  221. package/dist/server/index.js +1 -1
  222. package/dist/server/readConfig.d.ts +70 -0
  223. package/dist/server/readConfig.js +8 -0
  224. package/dist/server/storageMode.d.ts +23 -0
  225. package/dist/server/storageMode.js +17 -0
  226. package/dist/source/adapter.d.ts +30 -25
  227. package/dist/source/adapter.js +10 -10
  228. package/dist/source/adapters/drizzle.d.ts +28 -23
  229. package/dist/source/adapters/drizzle.js +30 -25
  230. package/dist/source/adapters/kysely.d.ts +27 -25
  231. package/dist/source/adapters/kysely.js +24 -23
  232. package/dist/source/adapters/memory.d.ts +8 -7
  233. package/dist/source/adapters/memory.js +9 -8
  234. package/dist/source/adapters/prisma.d.ts +13 -12
  235. package/dist/source/adapters/prisma.js +22 -25
  236. package/dist/source/conformance.d.ts +18 -11
  237. package/dist/source/conformance.js +17 -11
  238. package/dist/source/connector.d.ts +31 -32
  239. package/dist/source/connector.js +28 -28
  240. package/dist/source/connectorProtocol.d.ts +160 -0
  241. package/dist/source/connectorProtocol.js +162 -0
  242. package/dist/source/contract.d.ts +26 -27
  243. package/dist/source/contract.js +28 -29
  244. package/dist/source/factory.d.ts +46 -58
  245. package/dist/source/factory.js +22 -27
  246. package/dist/source/index.d.ts +7 -9
  247. package/dist/source/index.js +12 -14
  248. package/dist/source/migrations.d.ts +9 -9
  249. package/dist/source/migrations.js +9 -9
  250. package/dist/source/next.d.ts +9 -10
  251. package/dist/source/next.js +6 -7
  252. package/dist/source/pushQueue.d.ts +69 -47
  253. package/dist/source/pushQueue.js +32 -28
  254. package/dist/source/signing.d.ts +46 -17
  255. package/dist/source/signing.js +28 -11
  256. package/dist/source/types.d.ts +121 -104
  257. package/dist/source/types.js +13 -14
  258. package/dist/stores/ObjectStore.d.ts +24 -12
  259. package/dist/stores/ObjectStore.js +38 -16
  260. package/dist/stores/ObjectStoreContract.d.ts +14 -15
  261. package/dist/stores/SyncActionStore.d.ts +7 -11
  262. package/dist/stores/SyncActionStore.js +13 -17
  263. package/dist/surface.d.ts +28 -21
  264. package/dist/surface.js +29 -20
  265. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
  266. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
  267. package/dist/sync/ConnectionManager.d.ts +39 -50
  268. package/dist/sync/ConnectionManager.js +55 -66
  269. package/dist/sync/NetworkProbe.d.ts +24 -29
  270. package/dist/sync/NetworkProbe.js +63 -69
  271. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
  272. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
  273. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
  274. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
  275. package/dist/sync/SyncWebSocket.d.ts +141 -166
  276. package/dist/sync/SyncWebSocket.js +191 -223
  277. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  278. package/dist/sync/awaitClaimGrant.js +11 -11
  279. package/dist/sync/bootstrapApply.d.ts +34 -24
  280. package/dist/sync/bootstrapApply.js +27 -19
  281. package/dist/sync/commitFrames.d.ts +21 -20
  282. package/dist/sync/commitFrames.js +18 -18
  283. package/dist/sync/createClaimStream.d.ts +23 -22
  284. package/dist/sync/createClaimStream.js +105 -23
  285. package/dist/sync/createPresenceStream.d.ts +19 -18
  286. package/dist/sync/createPresenceStream.js +25 -26
  287. package/dist/sync/createSnapshot.d.ts +12 -14
  288. package/dist/sync/createSnapshot.js +20 -26
  289. package/dist/sync/credentialLifecycle.d.ts +104 -104
  290. package/dist/sync/credentialLifecycle.js +140 -147
  291. package/dist/sync/deltaPipeline.d.ts +36 -34
  292. package/dist/sync/deltaPipeline.js +64 -65
  293. package/dist/sync/groupChange.d.ts +63 -61
  294. package/dist/sync/groupChange.js +74 -78
  295. package/dist/sync/heartbeat.d.ts +34 -33
  296. package/dist/sync/heartbeat.js +31 -31
  297. package/dist/sync/participants.d.ts +19 -19
  298. package/dist/sync/persistedPrefix.d.ts +12 -0
  299. package/dist/sync/persistedPrefix.js +22 -0
  300. package/dist/sync/schemas.d.ts +3 -2
  301. package/dist/sync/schemas.js +14 -10
  302. package/dist/sync/syncCursor.d.ts +17 -21
  303. package/dist/sync/syncCursor.js +17 -21
  304. package/dist/sync/syncPlan.d.ts +28 -36
  305. package/dist/sync/syncPlan.js +18 -19
  306. package/dist/sync/syncPosition.d.ts +54 -49
  307. package/dist/sync/syncPosition.js +57 -52
  308. package/dist/sync/wsFrameHandlers.d.ts +35 -36
  309. package/dist/sync/wsFrameHandlers.js +63 -67
  310. package/dist/testing/fixtures/bootstrap.d.ts +12 -6
  311. package/dist/testing/fixtures/bootstrap.js +12 -6
  312. package/dist/testing/fixtures/deltas.d.ts +30 -33
  313. package/dist/testing/fixtures/deltas.js +30 -33
  314. package/dist/testing/fixtures/models.d.ts +11 -10
  315. package/dist/testing/fixtures/models.js +11 -10
  316. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  317. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  318. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
  319. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
  320. package/dist/testing/helpers/wait.d.ts +13 -8
  321. package/dist/testing/helpers/wait.js +13 -8
  322. package/dist/testing/index.d.ts +5 -3
  323. package/dist/testing/index.js +3 -2
  324. package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
  325. package/dist/testing/mocks/FakeDatabase.js +10 -0
  326. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  327. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  328. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  329. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  330. package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
  331. package/dist/testing/mocks/MockSyncContext.js +15 -13
  332. package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
  333. package/dist/testing/mocks/MockSyncStore.js +11 -11
  334. package/dist/testing/mocks/MockWebSocket.d.ts +28 -23
  335. package/dist/testing/mocks/MockWebSocket.js +22 -21
  336. package/dist/transactions/TransactionQueue.d.ts +244 -181
  337. package/dist/transactions/TransactionQueue.js +929 -423
  338. package/dist/transactions/TransactionStore.d.ts +6 -4
  339. package/dist/transactions/TransactionStore.js +6 -4
  340. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  341. package/dist/transactions/UnconfirmedWrites.js +104 -0
  342. package/dist/transactions/coalesceRules.d.ts +41 -17
  343. package/dist/transactions/coalesceRules.js +40 -17
  344. package/dist/transactions/commitEnvelope.d.ts +132 -0
  345. package/dist/transactions/commitEnvelope.js +139 -0
  346. package/dist/transactions/commitOutboxStore.d.ts +32 -0
  347. package/dist/transactions/commitOutboxStore.js +26 -0
  348. package/dist/transactions/commitPayload.d.ts +63 -52
  349. package/dist/transactions/commitPayload.js +54 -57
  350. package/dist/transactions/deltaConfirmation.d.ts +20 -22
  351. package/dist/transactions/deltaConfirmation.js +37 -45
  352. package/dist/transactions/httpCommitEnvelope.d.ts +43 -0
  353. package/dist/transactions/httpCommitEnvelope.js +179 -0
  354. package/dist/transactions/optimisticApply.d.ts +49 -0
  355. package/dist/transactions/optimisticApply.js +65 -0
  356. package/dist/transactions/replayValidation.d.ts +182 -0
  357. package/dist/transactions/replayValidation.js +156 -0
  358. package/dist/types/global.d.ts +46 -41
  359. package/dist/types/global.js +20 -19
  360. package/dist/types/index.d.ts +71 -77
  361. package/dist/types/index.js +22 -22
  362. package/dist/types/modelData.d.ts +6 -8
  363. package/dist/types/modelData.js +5 -7
  364. package/dist/types/participant.d.ts +10 -11
  365. package/dist/types/participant.js +6 -8
  366. package/dist/types/streams.d.ts +208 -195
  367. package/dist/types/streams.js +7 -7
  368. package/dist/utils/asyncIterator.d.ts +25 -32
  369. package/dist/utils/asyncIterator.js +25 -32
  370. package/dist/utils/duration.d.ts +12 -15
  371. package/dist/utils/duration.js +12 -15
  372. package/dist/utils/mobxSetup.d.ts +53 -0
  373. package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
  374. package/dist/webhooks/events.d.ts +21 -16
  375. package/dist/webhooks/events.js +10 -8
  376. package/dist/webhooks/index.d.ts +5 -7
  377. package/dist/webhooks/index.js +5 -7
  378. package/dist/wire/bootstrapReason.d.ts +9 -0
  379. package/dist/wire/bootstrapReason.js +8 -0
  380. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  381. package/dist/wire/delta.js +114 -0
  382. package/dist/wire/errorEnvelope.d.ts +30 -31
  383. package/dist/wire/errorEnvelope.js +34 -40
  384. package/dist/wire/frames.d.ts +315 -86
  385. package/dist/wire/frames.js +47 -33
  386. package/dist/wire/index.d.ts +18 -14
  387. package/dist/wire/index.js +32 -27
  388. package/dist/wire/listEnvelope.d.ts +16 -23
  389. package/dist/wire/listEnvelope.js +7 -6
  390. package/dist/wire/protocol.d.ts +25 -32
  391. package/dist/wire/protocol.js +25 -32
  392. package/dist/wire/protocolVersion.d.ts +44 -40
  393. package/dist/wire/protocolVersion.js +44 -40
  394. package/docs/api.md +10 -10
  395. package/docs/coordination.md +59 -0
  396. package/docs/mcp.md +1 -1
  397. package/package.json +17 -11
  398. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  399. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  400. package/dist/core/query-utils.d.ts +0 -34
  401. package/dist/core/query-utils.js +0 -59
  402. package/dist/schema/sync-delta-row.js +0 -103
  403. package/dist/schema/sync-delta-wire.js +0 -102
  404. package/dist/server/read-config.d.ts +0 -67
  405. package/dist/server/read-config.js +0 -8
  406. package/dist/server/storage-mode.d.ts +0 -8
  407. package/dist/server/storage-mode.js +0 -28
  408. package/dist/source/connector-protocol.d.ts +0 -159
  409. package/dist/source/connector-protocol.js +0 -161
  410. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  411. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  412. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  413. package/dist/transactions/mutation-error-handler.js +0 -39
  414. package/dist/transactions/optimistic.d.ts +0 -24
  415. package/dist/transactions/optimistic.js +0 -45
  416. package/dist/transactions/persistedReplay.d.ts +0 -93
  417. package/dist/transactions/persistedReplay.js +0 -105
  418. package/dist/utils/mobx-setup.d.ts +0 -42
@@ -1,20 +1,66 @@
1
1
  /**
2
- * Stateless API client for `Ablo({ apiKey })`.
3
- *
4
- * This is the hosted-API product surface: no schema, no object pool, no
5
- * IndexedDB, no WebSocket. It maps the public Model / Claim / Commit
6
- * nouns directly to HTTP routes on sync-server.
2
+ * The stateless API client behind `Ablo({ apiKey })`. It carries no schema,
3
+ * object pool, local database, or WebSocket, and maps the public Model, Claim,
4
+ * and Commit nouns directly to HTTP routes on the server. This is the transport
5
+ * used for server-side agents, workers, and serverless code.
7
6
  */
8
- import { AbloClaimedError, AbloAuthenticationError, AbloConnectionError, AbloValidationError, AbloNotFoundError, claimedError, translateHttpError, } from '../errors.js';
7
+ import { AbloClaimedError, AbloAuthenticationError, AbloConnectionError, AbloIdempotencyError, AbloValidationError, AbloNotFoundError, claimedError, translateHttpError, } from '../errors.js';
8
+ import { z } from 'zod';
9
+ import { v5 as uuidv5 } from 'uuid';
9
10
  import { reconcileFunctionalUpdate, } from './functionalUpdate.js';
10
11
  import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl, resolveDatabaseUrl, warnIfCliKeyMismatch, warnIfDatabaseUrlEnvIgnored, warnIfDatabaseUrlDeprecated, } from './auth.js';
11
12
  import { registerDataSource } from './registerDataSource.js';
12
13
  import { PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER } from '../wire/protocolVersion.js';
13
- import { toSeconds } from '../utils/duration.js';
14
+ import { toMs, toSeconds } from '../utils/duration.js';
15
+ import { heartbeatCadenceMs, resolveHeartbeatOptions, startClaimHeartbeatLoop, } from './claimHeartbeatLoop.js';
14
16
  import { mintSession } from './sessionMint.js';
17
+ import { parseIdentityResolveResponse } from '../auth/schemas.js';
18
+ /**
19
+ * Interpret a heartbeat reply for a lease this handle HOLDS: anything other
20
+ * than `held` means the lease is no longer ours (a holder cannot be `queued`;
21
+ * `lost` rides a 409 that the wire error mapping already surfaces as
22
+ * AbloClaimedError before reaching here). The thrown loss is the definitive
23
+ * signal that stops the auto-heartbeat loop.
24
+ */
25
+ function heldHeartbeatReply(reply, label) {
26
+ if (reply.status === 'held' && typeof reply.expiresAt === 'number') {
27
+ return {
28
+ expiresAt: reply.expiresAt,
29
+ ...(reply.queueDepth !== undefined ? { queueDepth: reply.queueDepth } : {}),
30
+ };
31
+ }
32
+ throw new AbloClaimedError(`The lease behind ${label} is no longer held — it expired or was granted onward. Re-acquire the claim and retry; a write attempted under the old lease is rejected by its \`readAt\` guard.`, { code: 'claim_lost' });
33
+ }
15
34
  import { assertWriteOptions } from './writeOptionsSchema.js';
35
+ import { createDurableHttpCommitEnvelope, canonicalHttpCommitBody, durableHttpCommitEnvelopeSchema, httpCommitEnvelopeRecordId, isHttpCommitReplayExpired, } from '../transactions/httpCommitEnvelope.js';
16
36
  /** Default per-request deadline for the stateless HTTP transport. */
17
37
  export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
38
+ const successfulCommitResponseSchema = z
39
+ .object({
40
+ object: z.literal('commit_receipt'),
41
+ id: z.string().min(1).optional(),
42
+ clientTxId: z.string().min(1),
43
+ serverTxId: z.string().min(1),
44
+ status: z.enum(['queued', 'confirmed']),
45
+ success: z.literal(true),
46
+ lastSyncId: z.number().int().nonnegative().optional(),
47
+ ops: z.number().int().positive(),
48
+ /** Ids of UPDATE/DELETE targets that matched zero rows (loud 0-row writes). */
49
+ missingIds: z.array(z.string().min(1)).optional(),
50
+ })
51
+ .loose();
52
+ function parseSuccessfulCommitResponse(value, idempotencyKey) {
53
+ const parsed = successfulCommitResponseSchema.safeParse(value);
54
+ if (!parsed.success || parsed.data.clientTxId !== idempotencyKey) {
55
+ throw new AbloConnectionError('The commit endpoint returned an invalid success receipt; its outcome remains pending and is safe to retry.', {
56
+ code: 'commit_no_result',
57
+ cause: parsed.success
58
+ ? new Error('Commit receipt clientTxId did not match its idempotency key')
59
+ : parsed.error,
60
+ });
61
+ }
62
+ return parsed.data;
63
+ }
18
64
  const DEFAULT_AGENT_LEASE = '10m';
19
65
  export function createProtocolClient(options) {
20
66
  const env = readProcessEnv();
@@ -32,11 +78,12 @@ export function createProtocolClient(options) {
32
78
  databaseUrl: configuredDatabaseUrl,
33
79
  dangerouslyAllowBrowser: options.dangerouslyAllowBrowser,
34
80
  });
35
- // Observability seam for the STATELESS HTTP transport. The WS transport emits
36
- // claim/conflict events from SyncWebSocket; the HTTP path (server-side agents,
81
+ // Observability hook for the stateless HTTP transport. The WebSocket transport
82
+ // emits claim and conflict events; the HTTP path (server-side agents,
37
83
  // `transport: 'http'`) emitted nothing, so a `ClaimLog` handed to a headless
38
- // agent eval stayed empty. Mirror the two WS seams here: claim acquired +
39
- // coordination-conflict rejection. No-op when no provider is configured.
84
+ // agent evaluation stayed empty. This mirrors the two WebSocket events here:
85
+ // claim acquired and coordination-conflict rejection. A no-op when no provider
86
+ // is configured.
40
87
  const observability = options.observability;
41
88
  // Shared by the two HTTP write doors (`commits.create` + per-model
42
89
  // `mutateModel`): a rejected write whose code is a coordination conflict is
@@ -47,14 +94,17 @@ export function createProtocolClient(options) {
47
94
  const recordCoordinationConflict = (error, clientTxId, fallbackRows) => {
48
95
  if (!observability)
49
96
  return;
50
- const code = error?.code;
97
+ const errorRecord = typeof error === 'object' && error !== null
98
+ ? error
99
+ : undefined;
100
+ const code = errorRecord?.code;
51
101
  const isConflict = code === 'stale_context' ||
52
102
  code === 'claim_conflict' ||
53
103
  code === 'entity_claimed' ||
54
104
  (typeof code === 'string' && code.startsWith('policy:'));
55
105
  if (!isConflict)
56
106
  return;
57
- const rawConflicts = error?.conflicts;
107
+ const rawConflicts = errorRecord?.conflicts;
58
108
  const rows = Array.isArray(rawConflicts) && rawConflicts.length > 0
59
109
  ? rawConflicts.map((r) => ({
60
110
  model: typeof r.model === 'string' ? r.model : 'unknown',
@@ -73,19 +123,54 @@ export function createProtocolClient(options) {
73
123
  url,
74
124
  bootstrapBaseUrl: options.bootstrapBaseUrl,
75
125
  }).replace(/\/+$/, '');
126
+ const commitOutbox = options.commitOutbox;
127
+ const httpOutboxPlaneNamespace = canonicalHttpCommitBody({
128
+ apiBaseUrl,
129
+ defaultQuery: Object.entries(options.defaultQuery ?? {}).sort(([a], [b]) => a.localeCompare(b)),
130
+ });
131
+ let httpOutboxScopeNamespace = null;
76
132
  let readyPromise = null;
133
+ let httpCommitLane = Promise.resolve();
134
+ function runInHttpCommitLane(work) {
135
+ const result = httpCommitLane.then(work);
136
+ httpCommitLane = result.then(() => undefined, () => undefined);
137
+ return result;
138
+ }
139
+ async function resolveHttpOutboxScope() {
140
+ if (!commitOutbox)
141
+ return null;
142
+ if (httpOutboxScopeNamespace)
143
+ return httpOutboxScopeNamespace;
144
+ let scope = options.commitOutboxScope;
145
+ if (!scope) {
146
+ const rawIdentity = await requestJson('/auth/identity', { method: 'GET' }, true);
147
+ const identity = parseIdentityResolveResponse(rawIdentity);
148
+ scope = {
149
+ organizationId: identity.accountScope,
150
+ participantId: identity.participantId,
151
+ namespace: 'http',
152
+ };
153
+ }
154
+ httpOutboxScopeNamespace = canonicalHttpCommitBody({
155
+ ...scope,
156
+ plane: httpOutboxPlaneNamespace,
157
+ });
158
+ return httpOutboxScopeNamespace;
159
+ }
77
160
  async function ready() {
78
161
  if (readyPromise)
79
162
  return readyPromise;
80
163
  readyPromise = (async () => {
81
- if (!configuredDatabaseUrl)
82
- return;
83
- await registerDataSource({
84
- baseUrl: apiBaseUrl,
85
- apiKey: await resolveApiKeyValue(configuredApiKey),
86
- databaseUrl: configuredDatabaseUrl,
87
- ...(options.fetch ? { fetchImpl: options.fetch } : {}),
88
- });
164
+ if (configuredDatabaseUrl) {
165
+ await registerDataSource({
166
+ baseUrl: apiBaseUrl,
167
+ apiKey: await resolveApiKeyValue(configuredApiKey),
168
+ databaseUrl: configuredDatabaseUrl,
169
+ ...(options.fetch ? { fetchImpl: options.fetch } : {}),
170
+ });
171
+ }
172
+ await resolveHttpOutboxScope();
173
+ await replayHttpCommitOutbox();
89
174
  })();
90
175
  try {
91
176
  await readyPromise;
@@ -127,8 +212,9 @@ export function createProtocolClient(options) {
127
212
  return target.toString();
128
213
  }
129
214
  const requestTimeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
130
- async function requestJson(path, init) {
131
- await ready();
215
+ async function requestJson(path, init, skipReady = false) {
216
+ if (!skipReady)
217
+ await ready();
132
218
  const { idempotencyKey, ...requestInit } = init;
133
219
  const headers = await authHeaders();
134
220
  if (idempotencyKey)
@@ -139,7 +225,7 @@ export function createProtocolClient(options) {
139
225
  // is combined with the deadline via a shared controller — the portable
140
226
  // equivalent of `AbortSignal.any([caller, AbortSignal.timeout(t)])`,
141
227
  // which older runtimes (and the jsdom test env) don't implement. The
142
- // same pattern already guards `query/client.ts` and `BootstrapHelper`.
228
+ // same pattern already guards `query/client.ts` and `BootstrapFetcher`.
143
229
  const callerSignal = requestInit.signal ?? undefined;
144
230
  const controller = new AbortController();
145
231
  const onCallerAbort = () => { controller.abort(callerSignal?.reason); };
@@ -172,6 +258,7 @@ export function createProtocolClient(options) {
172
258
  bodyText = await res.text();
173
259
  }
174
260
  catch (error) {
261
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- assigned asynchronously by the deadline callback
175
262
  if (timedOut) {
176
263
  // Retryable by contract: `wait_for_timeout` is a registered transient
177
264
  // transport code, so `isRetryableCode` steers callers to retry.
@@ -192,6 +279,169 @@ export function createProtocolClient(options) {
192
279
  }
193
280
  return body;
194
281
  }
282
+ function isDefinitiveHttpRejection(error) {
283
+ if (typeof error !== 'object' || error === null)
284
+ return false;
285
+ const candidate = error;
286
+ const status = typeof candidate.httpStatus === 'number'
287
+ ? candidate.httpStatus
288
+ : typeof candidate.status === 'number'
289
+ ? candidate.status
290
+ : undefined;
291
+ return (status !== undefined &&
292
+ status >= 400 &&
293
+ status < 500 &&
294
+ status !== 408 &&
295
+ status !== 425 &&
296
+ status !== 429);
297
+ }
298
+ async function settleHttpEnvelope(recordId) {
299
+ if (!commitOutbox)
300
+ return;
301
+ try {
302
+ await commitOutbox.remove(recordId);
303
+ }
304
+ catch (cause) {
305
+ // Do not report the remote outcome until local settlement is durable.
306
+ // The retained record can still be replayed inside the safe window.
307
+ throw new AbloConnectionError('The server settled the commit, but its local outbox record could not be cleared.', { code: 'db_not_opened', cause });
308
+ }
309
+ }
310
+ async function replayHttpCommitOutbox() {
311
+ const replayed = new Map();
312
+ if (!commitOutbox)
313
+ return replayed;
314
+ const scopeNamespace = await resolveHttpOutboxScope();
315
+ if (!scopeNamespace)
316
+ return replayed;
317
+ const rows = await commitOutbox.list();
318
+ const envelopes = [];
319
+ for (const row of rows) {
320
+ if (typeof row !== 'object' ||
321
+ row === null ||
322
+ row.type !== 'http_commit_envelope')
323
+ continue;
324
+ const parsed = durableHttpCommitEnvelopeSchema.safeParse(row);
325
+ if (!parsed.success) {
326
+ throw new AbloValidationError('A saved HTTP write is unreadable; replay stopped before any newer write was sent.', { code: 'write_options_invalid', cause: parsed.error });
327
+ }
328
+ if (parsed.data.scopeNamespace !== scopeNamespace)
329
+ continue;
330
+ if (isHttpCommitReplayExpired(parsed.data)) {
331
+ throw new AbloIdempotencyError('A saved HTTP write is older than the server idempotency window and cannot be replayed safely.', { code: 'idempotency_conflict' });
332
+ }
333
+ envelopes.push(parsed.data);
334
+ }
335
+ envelopes.sort((a, b) => (a.sequence ?? a.sealedAt * 1_000) -
336
+ (b.sequence ?? b.sealedAt * 1_000) ||
337
+ a.id.localeCompare(b.id));
338
+ for (const envelope of envelopes) {
339
+ try {
340
+ const raw = await requestJson(envelope.request.path, {
341
+ method: envelope.request.method,
342
+ idempotencyKey: envelope.idempotencyKey,
343
+ body: envelope.request.body,
344
+ }, true);
345
+ const response = parseSuccessfulCommitResponse(raw, envelope.idempotencyKey);
346
+ await settleHttpEnvelope(envelope.id);
347
+ replayed.set(envelope.idempotencyKey, { envelope, response });
348
+ }
349
+ catch (error) {
350
+ if (isDefinitiveHttpRejection(error)) {
351
+ await settleHttpEnvelope(envelope.id);
352
+ }
353
+ throw error;
354
+ }
355
+ }
356
+ return replayed;
357
+ }
358
+ let lastHttpCommitSequence = 0;
359
+ function nextHttpCommitSequence() {
360
+ const wallSequence = Date.now() * 1_000;
361
+ lastHttpCommitSequence = Math.max(wallSequence, lastHttpCommitSequence + 1);
362
+ return lastHttpCommitSequence;
363
+ }
364
+ async function sealHttpCommit(input) {
365
+ if (!commitOutbox)
366
+ return null;
367
+ const scopeNamespace = await resolveHttpOutboxScope();
368
+ if (!scopeNamespace) {
369
+ throw new AbloValidationError('HTTP commit outbox scope was not resolved', {
370
+ code: 'write_options_invalid',
371
+ });
372
+ }
373
+ const recordId = httpCommitEnvelopeRecordId(input.idempotencyKey, scopeNamespace);
374
+ const legacyRecordId = httpCommitEnvelopeRecordId(input.idempotencyKey);
375
+ const existingRows = await commitOutbox.list();
376
+ const existingRaw = existingRows.find((row) => typeof row === 'object' &&
377
+ row !== null &&
378
+ (row.id === recordId ||
379
+ row.id === legacyRecordId));
380
+ const serializedBody = canonicalHttpCommitBody(input.body);
381
+ if (existingRaw !== undefined) {
382
+ const existing = durableHttpCommitEnvelopeSchema.parse(existingRaw);
383
+ if (isHttpCommitReplayExpired(existing)) {
384
+ throw new AbloIdempotencyError('This saved HTTP write is older than the server idempotency window and cannot be retried safely.', { code: 'idempotency_conflict' });
385
+ }
386
+ if (existing.scopeNamespace !== scopeNamespace ||
387
+ existing.request.method !== input.method ||
388
+ existing.request.path !== input.path ||
389
+ existing.request.body !== serializedBody) {
390
+ throw new AbloIdempotencyError('Idempotency key reused with a different HTTP commit request', { code: 'idempotency_conflict' });
391
+ }
392
+ return existing;
393
+ }
394
+ const envelope = createDurableHttpCommitEnvelope({
395
+ idempotencyKey: input.idempotencyKey,
396
+ request: { method: input.method, path: input.path, body: input.body },
397
+ scopeNamespace,
398
+ sequence: nextHttpCommitSequence(),
399
+ });
400
+ await commitOutbox.seal(envelope, []);
401
+ return envelope;
402
+ }
403
+ async function dispatchHttpCommit(input, beforeSettlement) {
404
+ return runInHttpCommitLane(async () => {
405
+ await ready();
406
+ // `ready()` covers startup. Re-draining here makes every later write wait
407
+ // behind an ambiguous predecessor from this same process.
408
+ const replayed = await replayHttpCommitOutbox();
409
+ const prior = replayed.get(input.idempotencyKey);
410
+ if (prior) {
411
+ const serializedBody = canonicalHttpCommitBody(input.body);
412
+ if (prior.envelope.request.method !== input.method ||
413
+ prior.envelope.request.path !== input.path ||
414
+ prior.envelope.request.body !== serializedBody) {
415
+ throw new AbloIdempotencyError('Idempotency key reused with a different HTTP commit request', { code: 'idempotency_conflict' });
416
+ }
417
+ await beforeSettlement?.(prior.response);
418
+ return prior.response;
419
+ }
420
+ const durableEnvelope = await sealHttpCommit(input);
421
+ const requestBody = durableEnvelope?.request.body ?? canonicalHttpCommitBody(input.body);
422
+ let response;
423
+ try {
424
+ const raw = await requestJson(input.path, {
425
+ method: input.method,
426
+ idempotencyKey: input.idempotencyKey,
427
+ body: requestBody,
428
+ }, true);
429
+ response = parseSuccessfulCommitResponse(raw, input.idempotencyKey);
430
+ }
431
+ catch (error) {
432
+ if (durableEnvelope && isDefinitiveHttpRejection(error)) {
433
+ await settleHttpEnvelope(durableEnvelope.id);
434
+ }
435
+ throw error;
436
+ }
437
+ // A model-create readback can participate in settlement: if it fails,
438
+ // retain the exact write so a same-key retry recovers the generated id.
439
+ await beforeSettlement?.(response);
440
+ if (durableEnvelope)
441
+ await settleHttpEnvelope(durableEnvelope.id);
442
+ return response;
443
+ });
444
+ }
195
445
  function createClientTxId(idempotencyKey) {
196
446
  if (idempotencyKey && idempotencyKey.length > 0)
197
447
  return idempotencyKey;
@@ -204,17 +454,28 @@ export function createProtocolClient(options) {
204
454
  ? `int_${crypto.randomUUID()}`
205
455
  : `int_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
206
456
  }
207
- function createModelId() {
457
+ function createModelId(modelName, idempotencyKey) {
458
+ if (idempotencyKey) {
459
+ return uuidv5(`${modelName}:${idempotencyKey}`, 'aa4ba6d4-bf0b-5b38-9c45-116f79a6e548');
460
+ }
208
461
  return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
209
462
  ? crypto.randomUUID()
210
463
  : `id_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
211
464
  }
212
- function childClient(authToken) {
465
+ function childClient(authToken, scope) {
213
466
  return createProtocolClient({
214
467
  ...options,
215
468
  apiKey: null,
216
469
  authToken,
217
470
  schema: null,
471
+ ...(scope
472
+ ? {
473
+ commitOutboxScope: {
474
+ ...scope,
475
+ namespace: options.commitOutboxScope?.namespace ?? 'http',
476
+ },
477
+ }
478
+ : { commitOutboxScope: undefined }),
218
479
  });
219
480
  }
220
481
  function normalizeCommitOperation(op, defaults) {
@@ -341,17 +602,20 @@ export function createProtocolClient(options) {
341
602
  readAt: commitOptions.readAt ?? claim?.readAt ?? null,
342
603
  onStale: commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
343
604
  });
605
+ const requestBody = {
606
+ clientTxId,
607
+ idempotencyKey: clientTxId,
608
+ claim: normalizeClaimId(commitOptions.claimRef) ?? claim?.id,
609
+ operations,
610
+ reads: commitOptions.reads,
611
+ };
344
612
  let body;
345
613
  try {
346
- body = await requestJson('/v1/commits', {
614
+ body = await dispatchHttpCommit({
615
+ path: '/v1/commits',
347
616
  method: 'POST',
348
617
  idempotencyKey: clientTxId,
349
- body: JSON.stringify({
350
- clientTxId,
351
- idempotencyKey: clientTxId,
352
- claim: normalizeClaimId(commitOptions.claimRef) ?? claim?.id,
353
- operations,
354
- }),
618
+ body: requestBody,
355
619
  });
356
620
  }
357
621
  catch (error) {
@@ -370,7 +634,7 @@ export function createProtocolClient(options) {
370
634
  // the rejection body (already thrown).
371
635
  const status = body.status === 'queued' ? 'queued' : 'confirmed';
372
636
  return {
373
- id: body.id ?? body.clientTxId ?? clientTxId,
637
+ id: body.id ?? body.clientTxId,
374
638
  status,
375
639
  lastSyncId: body.lastSyncId,
376
640
  ...(body.missingIds && body.missingIds.length > 0
@@ -381,9 +645,9 @@ export function createProtocolClient(options) {
381
645
  };
382
646
  const capabilities = {
383
647
  async create(capabilityOptions) {
384
- const ttlSeconds = capabilityOptions.ttlSeconds ??
648
+ const ttlSeconds = capabilityOptions.ttlSeconds ?? // eslint-disable-line @typescript-eslint/no-deprecated -- compatibility alias
385
649
  capabilityOptions.leaseSeconds ??
386
- toSeconds(capabilityOptions.ttl ?? capabilityOptions.lease ?? DEFAULT_AGENT_LEASE);
650
+ toSeconds(capabilityOptions.ttl ?? capabilityOptions.lease ?? DEFAULT_AGENT_LEASE); // eslint-disable-line @typescript-eslint/no-deprecated -- compatibility alias
387
651
  const body = await requestJson('/v1/capabilities', {
388
652
  method: 'POST',
389
653
  body: JSON.stringify({
@@ -408,7 +672,10 @@ export function createProtocolClient(options) {
408
672
  organizationId: body.organizationId,
409
673
  scope: body.scope,
410
674
  userMeta: body.userMeta,
411
- client: () => childClient(body.token),
675
+ client: () => childClient(body.token, {
676
+ organizationId: body.organizationId,
677
+ participantId: body.scope.participantId,
678
+ }),
412
679
  };
413
680
  },
414
681
  async retrieve(id) {
@@ -462,7 +729,10 @@ export function createProtocolClient(options) {
462
729
  id: body.rotatedFrom.capabilityId ?? body.rotatedFrom.id ?? id,
463
730
  expiresAt: body.rotatedFrom.expiresAt,
464
731
  },
465
- client: () => childClient(body.token),
732
+ client: () => childClient(body.token, {
733
+ organizationId: body.organizationId,
734
+ participantId: body.scope.participantId,
735
+ }),
466
736
  };
467
737
  },
468
738
  mint(options) {
@@ -482,11 +752,11 @@ export function createProtocolClient(options) {
482
752
  queue: claimOptions.queue,
483
753
  }),
484
754
  });
485
- // The fair-queue grant is PUSHED over a WebSocket (`claim_granted`),
486
- // which this stateless HTTP client doesn't hold. Returning a handle here
487
- // would be a phantom holder — a lease we can't confirm is ours. So a
488
- // queued response is surfaced as a typed claimed signal; callers that need
489
- // to *wait* in line use the realtime (WS-backed) `ablo.<model>.claim`.
755
+ // The fair-queue grant is pushed over a WebSocket (`claim_granted`), which
756
+ // this stateless HTTP client doesn't hold. Returning a handle here would be
757
+ // a phantom holder — a lease we can't confirm is ours. So a queued response
758
+ // is surfaced as a typed claimed signal; callers that need to wait in line
759
+ // use the realtime (WebSocket-backed) `ablo.<model>.claim`.
490
760
  if (body.status === 'queued') {
491
761
  throw new AbloClaimedError(`Target ${claimOptions.target.model}/${claimOptions.target.id} is held; ` +
492
762
  `queued at position ${body.position ?? 0}. The HTTP client can't await ` +
@@ -500,9 +770,24 @@ export function createProtocolClient(options) {
500
770
  released = true;
501
771
  await requestJson(`/v1/claims/${encodeURIComponent(id)}`, { method: 'DELETE' });
502
772
  };
773
+ // The by-id twin of the model-scoped heartbeat — same reply contract.
774
+ const heartbeat = async (beatOptions) => {
775
+ const resolved = resolveHeartbeatOptions(beatOptions);
776
+ const reply = await requestJson(`/v1/claims/${encodeURIComponent(id)}/heartbeat`, {
777
+ method: 'POST',
778
+ body: JSON.stringify({
779
+ ...(resolved.ttl !== undefined ? { ttl: resolved.ttl } : {}),
780
+ ...(resolved.details !== undefined
781
+ ? { details: resolved.details }
782
+ : {}),
783
+ }),
784
+ });
785
+ return heldHeartbeatReply(reply, `claim ${id}`);
786
+ };
503
787
  return {
504
788
  object: 'claim',
505
789
  id,
790
+ heartbeat,
506
791
  reason: claimOptions.reason,
507
792
  target: {
508
793
  type: claimOptions.target.model,
@@ -523,6 +808,23 @@ export function createProtocolClient(options) {
523
808
  waitFor(target, options) {
524
809
  return waitForNoClaims(target, options);
525
810
  },
811
+ async heartbeatAll(options) {
812
+ const reply = await requestJson('/v1/claims/heartbeat', {
813
+ method: 'POST',
814
+ body: JSON.stringify(options?.ttl !== undefined ? { ttl: options.ttl } : {}),
815
+ });
816
+ return (reply.results ?? []).flatMap((entry) => typeof entry.claimId === 'string' && typeof entry.expiresAt === 'number'
817
+ ? [
818
+ {
819
+ claimId: entry.claimId,
820
+ expiresAt: entry.expiresAt,
821
+ ...(entry.queueDepth !== undefined
822
+ ? { queueDepth: entry.queueDepth }
823
+ : {}),
824
+ },
825
+ ]
826
+ : []);
827
+ },
526
828
  };
527
829
  async function listModel(modelName, options) {
528
830
  const params = new URLSearchParams();
@@ -554,11 +856,11 @@ export function createProtocolClient(options) {
554
856
  const query = await requestJson(`/v1/models/${encodeURIComponent(modelName)}/${encodeURIComponent(params.id)}`, {
555
857
  method: 'GET',
556
858
  });
557
- // Miss = `data: undefined`, NOT a thrown error. The WebSocket client's
859
+ // A miss is `data: undefined`, not a thrown error. The WebSocket client's
558
860
  // `retrieve` returns `T | undefined` for a missing row; throwing only here
559
861
  // made the obvious read ("does this row exist?") a hard edge that an agent
560
- // had to wrap in try/catch. Both transports now agree: absent row absent
561
- // data. Callers branch on `.data` (already the documented `.data?.x` usage).
862
+ // had to wrap in try/catch. Both transports agree: an absent row means absent
863
+ // data. Callers branch on `.data` (the documented `.data?.x` usage).
562
864
  // Normalize a miss to `undefined` (the server may send `null` or omit it).
563
865
  const data = (query.data ?? undefined);
564
866
  return {
@@ -568,18 +870,18 @@ export function createProtocolClient(options) {
568
870
  };
569
871
  }
570
872
  /**
571
- * Single-op mutation over the model-scoped routes — the canonical surface
572
- * that mirrors `ablo.<model>.create/update/delete`:
873
+ * A single-operation mutation over the model-scoped routes — the canonical
874
+ * surface that mirrors `ablo.<model>.create/update/delete`:
573
875
  *
574
876
  * POST /v1/models/:model create
575
877
  * PATCH /v1/models/:model/:id update
576
878
  * DELETE /v1/models/:model/:id delete
577
879
  *
578
- * This replaces the previous indirection through `POST /v1/commits`. The raw
579
- * `commits.create(...)` resource is still the path for ATOMIC MULTI-OP
580
- * envelopes — this helper is the one-op, one-record path only.
880
+ * The `commits.create(...)` resource remains the path for atomic
881
+ * multi-operation envelopes; this helper handles the one-operation,
882
+ * one-record case.
581
883
  */
582
- async function mutateModel(action, modelName, id, data, options) {
884
+ async function mutateModel(action, modelName, id, data, options, beforeSettlement) {
583
885
  assertWriteOptions(options && {
584
886
  idempotencyKey: options.idempotencyKey,
585
887
  readAt: options.readAt,
@@ -595,11 +897,12 @@ export function createProtocolClient(options) {
595
897
  const method = action === 'create' ? 'POST' : action === 'update' ? 'PATCH' : 'DELETE';
596
898
  // A carried claim handle supplies the stale-guard defaults — one claim
597
899
  // vocabulary across the WS proxy, `commits.create`, and these routes.
598
- const claimHandle = typeof options?.claim === 'object' &&
599
- options?.claim !== null &&
600
- options.claim.object === 'claim' &&
601
- typeof options.claim.id === 'string'
602
- ? options.claim
900
+ const rawClaim = options?.claim;
901
+ const claimHandle = typeof rawClaim === 'object' &&
902
+ rawClaim !== null &&
903
+ rawClaim.object === 'claim' &&
904
+ typeof rawClaim.id === 'string'
905
+ ? rawClaim
603
906
  : undefined;
604
907
  const readAt = options?.readAt ?? claimHandle?.readAt;
605
908
  const requestBody = {
@@ -614,16 +917,16 @@ export function createProtocolClient(options) {
614
917
  requestBody.data = data;
615
918
  let body;
616
919
  try {
617
- body = await requestJson(path, {
920
+ body = await dispatchHttpCommit({
921
+ path,
618
922
  method,
619
923
  idempotencyKey: clientTxId,
620
- body: JSON.stringify(requestBody),
621
- });
924
+ body: requestBody,
925
+ }, beforeSettlement);
622
926
  }
623
927
  catch (error) {
624
- // Per-model write door (`ablo.<model>.update/create/delete`) — the path
625
- // the demo editor + server agents actually take. Capture coordination
626
- // collisions here too; this single row IS the fallback target.
928
+ // The per-model write door (`ablo.<model>.update/create/delete`). Capture
929
+ // coordination collisions here too; this single row is the fallback target.
627
930
  recordCoordinationConflict(error, clientTxId, [{ model: modelName, id }]);
628
931
  throw error;
629
932
  }
@@ -632,7 +935,7 @@ export function createProtocolClient(options) {
632
935
  // subset; `'rejected'` only appears on a thrown rejection body.
633
936
  const status = body.status === 'queued' ? 'queued' : 'confirmed';
634
937
  return {
635
- id: body.serverTxId ?? body.id ?? body.clientTxId ?? clientTxId,
938
+ id: body.serverTxId,
636
939
  status,
637
940
  lastSyncId: body.lastSyncId,
638
941
  };
@@ -669,11 +972,25 @@ export function createProtocolClient(options) {
669
972
  throw new AbloClaimedError(`Target ${name}/${params.id} is held; queued at position ${body.position ?? 0}. ` +
670
973
  `The HTTP client cannot await the grant without a WebSocket.`, { code: 'claim_queued' });
671
974
  }
672
- // `claimId` is the queued-response field name (routes/claims.ts) — a
673
- // pre-rename regression duplicated the `body.id` arm and dropped it.
975
+ // `claimId` is the field name the queued response uses; check it alongside
976
+ // the other id shapes the response may carry.
674
977
  return body.claim?.id ?? body.id ?? body.claimId ?? createClaimId();
675
978
  };
676
979
  const releaseClaim = (params) => requestJson(claimPath(isClaimHandle(params) ? params.target.id : params.id), { method: 'DELETE' }).then(() => undefined);
980
+ // One beat on the held lease. A lapsed lease answers `claim_lost`
981
+ // (409), which the wire error mapping surfaces as AbloClaimedError —
982
+ // the definitive signal that stops the auto-heartbeat loop.
983
+ const heartbeatClaim = async (id, claimId, options) => {
984
+ const reply = await requestJson(`${claimPath(id)}/heartbeat`, {
985
+ method: 'POST',
986
+ body: JSON.stringify({
987
+ claimId,
988
+ ...(options.ttl !== undefined ? { ttl: options.ttl } : {}),
989
+ ...(options.details !== undefined ? { details: options.details } : {}),
990
+ }),
991
+ });
992
+ return heldHeartbeatReply(reply, `claim ${claimId} on ${name}/${id}`);
993
+ };
677
994
  async function claimImpl(params) {
678
995
  const claimId = await acquireClaim(params);
679
996
  observability?.captureClaim({
@@ -691,7 +1008,33 @@ export function createProtocolClient(options) {
691
1008
  if (data === undefined) {
692
1009
  throw new AbloNotFoundError(`Cannot claim ${name}/${params.id}: it does not exist (or is outside this credential's scope).`, [params.id]);
693
1010
  }
694
- const release = () => releaseClaim(params);
1011
+ const heartbeat = async (beatOptions) => {
1012
+ const resolved = resolveHeartbeatOptions(beatOptions);
1013
+ const beat = await heartbeatClaim(params.id, claimId, {
1014
+ ttl: resolved.ttl ?? params.ttl,
1015
+ ...(resolved.details !== undefined
1016
+ ? { details: resolved.details }
1017
+ : {}),
1018
+ });
1019
+ params.onHeartbeat?.(beat);
1020
+ return beat;
1021
+ };
1022
+ // Opt-in auto-heartbeat — the background-worker cadence. The stateless
1023
+ // HTTP claim defaults to the server's 60s acquire window when no TTL
1024
+ // was requested, so the default cadence lands at 20s beats.
1025
+ const stopHeartbeatLoop = params.heartbeat
1026
+ ? startClaimHeartbeatLoop({
1027
+ beat: () => heartbeat(),
1028
+ intervalMs: heartbeatCadenceMs(params.ttl !== undefined ? toMs(params.ttl) : 60_000, params.heartbeat),
1029
+ ...(params.onHeartbeatLost
1030
+ ? { onLost: params.onHeartbeatLost }
1031
+ : {}),
1032
+ })
1033
+ : undefined;
1034
+ const release = () => {
1035
+ stopHeartbeatLoop?.();
1036
+ return releaseClaim(params);
1037
+ };
695
1038
  return {
696
1039
  object: 'claim',
697
1040
  id: claimId,
@@ -711,6 +1054,7 @@ export function createProtocolClient(options) {
711
1054
  revoke: () => {
712
1055
  void release().catch(() => { });
713
1056
  },
1057
+ heartbeat,
714
1058
  [Symbol.asyncDispose]: release,
715
1059
  };
716
1060
  }
@@ -729,8 +1073,8 @@ export function createProtocolClient(options) {
729
1073
  reorder: async (params) => {
730
1074
  await requestJson(`${claimPath(params.id)}/reorder`, {
731
1075
  method: 'POST',
732
- // The reorder route's payload is `{ heldBy, claimId }[]` — Claim's id
733
- // IS the claimId.
1076
+ // The reorder route's payload is `{ heldBy, claimId }[]` — a Claim's id
1077
+ // is the claimId.
734
1078
  body: JSON.stringify({ order: params.order.map((i) => ({ heldBy: i.heldBy, claimId: i.id })) }),
735
1079
  });
736
1080
  },
@@ -742,7 +1086,13 @@ export function createProtocolClient(options) {
742
1086
  if (isClaimHandle(claimInput)) {
743
1087
  return run({ ...input, claimRef: { id: claimInput.id }, claim: undefined });
744
1088
  }
745
- const claimId = await acquireClaim({ id, ...claimInput });
1089
+ // `isClaimHandle` ruled out the handle form above; the generic mismatch
1090
+ // (the union carries `Claim`, the guard narrows `Claim<T>`) keeps the
1091
+ // compiler from subtracting it, so narrow to the inline-options form.
1092
+ const claimId = await acquireClaim({
1093
+ id,
1094
+ ...claimInput,
1095
+ });
746
1096
  try {
747
1097
  return await run({ ...input, claimRef: { id: claimId }, claim: undefined });
748
1098
  }
@@ -790,23 +1140,31 @@ export function createProtocolClient(options) {
790
1140
  return listModel(name, options);
791
1141
  },
792
1142
  async create(params) {
793
- const id = params.id ?? createModelId();
1143
+ const id = params.id ?? createModelId(name, params.idempotencyKey);
794
1144
  return withMutationClaim(id, params, async (options) => {
795
1145
  await applyClaimedPolicy({ model: name, id }, options);
796
1146
  // Confirm the write, then return the row — the obvious expectation of
797
1147
  // "create" (the WebSocket client already returns the row). The read-
798
1148
  // back is the authoritative server row, so it carries the framework
799
- // defaults (createdAt/createdBy/…) AND, for an idempotent re-create of
800
- // an existing id, the EXISTING row rather than the caller's input.
1149
+ // defaults (createdAt, createdBy, …) and, for an idempotent re-create of
1150
+ // an existing id, the existing row rather than the caller's input.
1151
+ let created;
801
1152
  await mutateModel('create', name, id, params.data, {
802
1153
  ...options,
803
1154
  wait: options?.wait ?? 'confirmed',
1155
+ }, async () => {
1156
+ const read = await retrieveModel(name, { id });
1157
+ if (read.data === undefined) {
1158
+ throw new AbloNotFoundError(`create ${name}/${id} did not yield a readable row (the write did not confirm).`, [id]);
1159
+ }
1160
+ created = read.data;
804
1161
  });
805
- const { data } = await retrieveModel(name, { id });
806
- if (data === undefined) {
807
- throw new AbloNotFoundError(`create ${name}/${id} did not yield a readable row (the write did not confirm).`, [id]);
1162
+ if (created === undefined) {
1163
+ throw new AbloConnectionError('Create settlement did not return its row.', {
1164
+ code: 'commit_no_result',
1165
+ });
808
1166
  }
809
- return data;
1167
+ return created;
810
1168
  });
811
1169
  },
812
1170
  update: updateModel,
@@ -820,7 +1178,10 @@ export function createProtocolClient(options) {
820
1178
  }
821
1179
  return {
822
1180
  ready,
823
- async waitForFlush() { },
1181
+ waitForFlush: () => runInHttpCommitLane(async () => {
1182
+ await ready();
1183
+ await replayHttpCommitOutbox();
1184
+ }),
824
1185
  async dispose() { },
825
1186
  async purge() { },
826
1187
  capabilities,
@@ -829,9 +1190,9 @@ export function createProtocolClient(options) {
829
1190
  model,
830
1191
  sessions: {
831
1192
  async create(params) {
832
- // Stateless mint: the configured key IS the control-plane credential here
833
- // (no startup `rk_` exchange runs on this client). Reuse the resolved base
834
- // URL + fetch; the shared `mintSession` owns the two server doors.
1193
+ // Stateless mint: the configured key is the control-plane credential here
1194
+ // (no startup `rk_` exchange runs on this client). It reuses the resolved
1195
+ // base URL and fetch; the shared `mintSession` handles the two server routes.
835
1196
  const apiKey = await resolveApiKeyValue(configuredApiKey);
836
1197
  if (!apiKey) {
837
1198
  throw new AbloAuthenticationError('sessions.create requires a secret (sk_) API key — call it from your backend, not the browser.', { code: 'apikey_missing' });