@abloatai/ablo 0.34.0 → 0.35.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 (483) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +684 -5
  3. package/README.md +39 -22
  4. package/dist/BaseSyncedStore.d.ts +152 -44
  5. package/dist/BaseSyncedStore.js +300 -184
  6. package/dist/Database.d.ts +9 -24
  7. package/dist/Database.js +37 -22
  8. package/dist/InstanceCache.d.ts +25 -4
  9. package/dist/InstanceCache.js +48 -15
  10. package/dist/LazyReferenceCollection.d.ts +3 -3
  11. package/dist/LazyReferenceCollection.js +4 -4
  12. package/dist/Model.d.ts +6 -6
  13. package/dist/Model.js +10 -10
  14. package/dist/ModelRegistry.d.ts +4 -4
  15. package/dist/ModelRegistry.js +3 -3
  16. package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
  17. package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
  18. package/dist/SyncClient.d.ts +42 -32
  19. package/dist/SyncClient.js +166 -110
  20. package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
  21. package/dist/ai-sdk/coordinatedTool.js +1 -1
  22. package/dist/ai-sdk/coordinationContext.d.ts +2 -2
  23. package/dist/ai-sdk/coordinationContext.js +1 -1
  24. package/dist/ai-sdk/wrap.d.ts +3 -3
  25. package/dist/ai-sdk/wrap.js +2 -2
  26. package/dist/auth/index.d.ts +1 -156
  27. package/dist/auth/index.js +8 -301
  28. package/dist/cli.cjs +3459 -1126
  29. package/dist/client/Ablo.d.ts +42 -287
  30. package/dist/client/Ablo.js +118 -963
  31. package/dist/client/abloClient.d.ts +309 -0
  32. package/dist/client/abloClient.js +13 -0
  33. package/dist/client/clientPrelude.d.ts +52 -0
  34. package/dist/client/clientPrelude.js +60 -0
  35. package/dist/client/consoleLogger.d.ts +2 -2
  36. package/dist/client/coreClient.d.ts +60 -0
  37. package/dist/client/coreClient.js +118 -0
  38. package/dist/client/createInternalComponents.d.ts +4 -4
  39. package/dist/client/createInternalComponents.js +9 -8
  40. package/dist/client/createModelProxy.d.ts +78 -373
  41. package/dist/client/createModelProxy.js +114 -86
  42. package/dist/client/humans.d.ts +48 -0
  43. package/dist/client/humans.js +52 -0
  44. package/dist/client/modelRegistration.d.ts +1 -1
  45. package/dist/client/modelRegistration.js +9 -9
  46. package/dist/client/options.d.ts +73 -17
  47. package/dist/client/reactiveEngine.d.ts +48 -0
  48. package/dist/client/reactiveEngine.js +910 -0
  49. package/dist/client/resourceTypes.d.ts +9 -250
  50. package/dist/client/resourceTypes.js +8 -5
  51. package/dist/client/schemaConfig.d.ts +4 -4
  52. package/dist/client/schemaConfig.js +6 -2
  53. package/dist/client/validateAbloOptions.d.ts +3 -2
  54. package/dist/client/validateAbloOptions.js +1 -1
  55. package/dist/client/wsMutationExecutor.d.ts +3 -3
  56. package/dist/client/wsMutationExecutor.js +3 -3
  57. package/dist/context.d.ts +9 -9
  58. package/dist/context.js +10 -9
  59. package/dist/coordination/ClaimLog.d.ts +26 -0
  60. package/dist/coordination/ClaimLog.js +32 -0
  61. package/dist/coordination/index.d.ts +1 -15
  62. package/dist/coordination/index.js +8 -31
  63. package/dist/core/DatabaseManager.js +1 -1
  64. package/dist/core/QueryView.d.ts +1 -1
  65. package/dist/core/QueryView.js +1 -1
  66. package/dist/core/StoreManager.d.ts +4 -23
  67. package/dist/core/StoreManager.js +5 -55
  68. package/dist/core/index.d.ts +2 -2
  69. package/dist/core/index.js +2 -2
  70. package/dist/core/storeContract.d.ts +2 -2
  71. package/dist/docs/catalog.d.ts +72 -0
  72. package/dist/docs/catalog.js +227 -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 +40 -34
  78. package/dist/index.js +26 -20
  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 +2 -2
  99. package/dist/query/client.js +4 -4
  100. package/dist/query/types.d.ts +6 -41
  101. package/dist/query/types.js +2 -2
  102. package/dist/react/AbloProvider.d.ts +6 -8
  103. package/dist/react/AbloProvider.js +5 -7
  104. package/dist/react/context.d.ts +1 -1
  105. package/dist/react/context.js +1 -1
  106. package/dist/react/index.d.ts +5 -5
  107. package/dist/react/index.js +3 -3
  108. package/dist/react/internalContext.d.ts +1 -1
  109. package/dist/react/useAblo.d.ts +3 -3
  110. package/dist/react/useAblo.js +1 -1
  111. package/dist/react/useCurrentUserId.js +1 -1
  112. package/dist/react/useErrorListener.js +1 -1
  113. package/dist/react/useMutationFailureListener.d.ts +2 -2
  114. package/dist/react/useMutationFailureListener.js +1 -1
  115. package/dist/react/useMutators.d.ts +3 -3
  116. package/dist/react/useMutators.js +3 -3
  117. package/dist/react/useUndoScope.d.ts +5 -5
  118. package/dist/react/useUndoScope.js +1 -1
  119. package/dist/schema/coordination.d.ts +69 -10
  120. package/dist/schema/coordination.js +86 -9
  121. package/dist/schema/ddl.js +2 -2
  122. package/dist/schema/diff.d.ts +1 -1
  123. package/dist/schema/generate.js +1 -1
  124. package/dist/schema/index.d.ts +10 -10
  125. package/dist/schema/index.js +18 -18
  126. package/dist/schema/queries.d.ts +27 -27
  127. package/dist/schema/queries.js +23 -23
  128. package/dist/schema/select.d.ts +3 -3
  129. package/dist/schema/select.js +3 -3
  130. package/dist/schema/serialize.d.ts +15 -6
  131. package/dist/schema/serialize.js +17 -3
  132. package/dist/schema/sugar.d.ts +6 -7
  133. package/dist/schema/sugar.js +9 -12
  134. package/dist/schema/syncDeltaRow.d.ts +4 -152
  135. package/dist/schema/syncDeltaRow.js +4 -105
  136. package/dist/server/adapter.d.ts +18 -1
  137. package/dist/server/commit.d.ts +10 -16
  138. package/dist/server/index.d.ts +1 -1
  139. package/dist/server/index.js +1 -1
  140. package/dist/server/readConfig.d.ts +1 -1
  141. package/dist/source/adapters/drizzle.d.ts +1 -1
  142. package/dist/source/adapters/drizzle.js +2 -2
  143. package/dist/source/adapters/kysely.d.ts +1 -1
  144. package/dist/source/adapters/kysely.js +1 -1
  145. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  146. package/dist/source/adapters/kyselyMutationCore.js +2 -2
  147. package/dist/source/adapters/memory.js +1 -1
  148. package/dist/source/adapters/prisma.d.ts +8 -3
  149. package/dist/source/adapters/prisma.js +1 -1
  150. package/dist/source/connector.js +1 -1
  151. package/dist/source/connectorProtocol.d.ts +2 -8
  152. package/dist/source/connectorProtocol.js +3 -2
  153. package/dist/source/contract.d.ts +29 -17
  154. package/dist/source/contract.js +27 -22
  155. package/dist/source/factory.d.ts +1 -1
  156. package/dist/source/footprint.d.ts +111 -0
  157. package/dist/source/footprint.js +0 -0
  158. package/dist/source/idempotency.js +2 -2
  159. package/dist/source/index.d.ts +1 -0
  160. package/dist/source/index.js +3 -0
  161. package/dist/source/next.d.ts +1 -1
  162. package/dist/source/signing.d.ts +9 -2
  163. package/dist/source/signing.js +4 -1
  164. package/dist/source/types.d.ts +6 -4
  165. package/dist/source/types.js +1 -1
  166. package/dist/stores/ObjectStore.d.ts +1 -1
  167. package/dist/stores/SyncActionStore.d.ts +1 -1
  168. package/dist/stores/SyncActionStore.js +2 -10
  169. package/dist/stores/syncAction.d.ts +26 -0
  170. package/dist/stores/syncAction.js +16 -0
  171. package/dist/surface.d.ts +3 -3
  172. package/dist/surface.js +6 -4
  173. package/dist/sync/BootstrapFetcher.d.ts +123 -6
  174. package/dist/sync/BootstrapFetcher.js +492 -66
  175. package/dist/sync/ConnectionManager.d.ts +6 -198
  176. package/dist/sync/ConnectionManager.js +6 -677
  177. package/dist/sync/OnDemandLoader.d.ts +2 -2
  178. package/dist/sync/OnDemandLoader.js +60 -21
  179. package/dist/sync/SubscriptionManager.d.ts +13 -2
  180. package/dist/sync/SubscriptionManager.js +23 -5
  181. package/dist/sync/SyncWebSocket.d.ts +27 -510
  182. package/dist/sync/SyncWebSocket.js +76 -954
  183. package/dist/sync/awaitClaimGrant.d.ts +4 -44
  184. package/dist/sync/awaitClaimGrant.js +4 -109
  185. package/dist/sync/commitFrames.d.ts +6 -40
  186. package/dist/sync/commitFrames.js +6 -97
  187. package/dist/sync/contextPorts.d.ts +18 -0
  188. package/dist/sync/contextPorts.js +31 -0
  189. package/dist/sync/createClaimStream.d.ts +5 -49
  190. package/dist/sync/createClaimStream.js +5 -469
  191. package/dist/sync/createPresenceStream.d.ts +26 -4
  192. package/dist/sync/createPresenceStream.js +28 -20
  193. package/dist/sync/createSnapshot.d.ts +2 -2
  194. package/dist/sync/createSnapshot.js +1 -1
  195. package/dist/sync/credentialLifecycle.d.ts +5 -173
  196. package/dist/sync/credentialLifecycle.js +5 -320
  197. package/dist/sync/deltaPipeline.d.ts +1 -1
  198. package/dist/sync/participants.d.ts +5 -4
  199. package/dist/sync/participants.js +29 -22
  200. package/dist/sync/schemaDrift.d.ts +55 -0
  201. package/dist/sync/schemaDrift.js +53 -0
  202. package/dist/sync/schemas.d.ts +21 -32
  203. package/dist/sync/schemas.js +26 -17
  204. package/dist/sync/syncPlan.d.ts +3 -3
  205. package/dist/sync/wsFrameHandlers.d.ts +6 -114
  206. package/dist/sync/wsFrameHandlers.js +6 -392
  207. package/dist/testing/fixtures/bootstrap.d.ts +1 -1
  208. package/dist/testing/fixtures/deltas.d.ts +1 -1
  209. package/dist/testing/fixtures/httpResponses.d.ts +70 -0
  210. package/dist/testing/fixtures/httpResponses.js +90 -0
  211. package/dist/testing/fixtures/models.js +1 -1
  212. package/dist/testing/helpers/wait.js +1 -1
  213. package/dist/testing/mocks/MockMutationExecutor.d.ts +2 -2
  214. package/dist/testing/mocks/MockMutationExecutor.js +8 -14
  215. package/dist/testing/mocks/MockSyncContext.d.ts +11 -11
  216. package/dist/testing/mocks/MockSyncContext.js +10 -9
  217. package/dist/testing/mocks/MockSyncStore.js +1 -1
  218. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  219. package/dist/transaction/ablo.d.ts +88 -0
  220. package/dist/transaction/ablo.js +33 -0
  221. package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
  222. package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
  223. package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
  224. package/dist/transaction/auth/bootstrapScope.js +1 -0
  225. package/dist/transaction/auth/capability.d.ts +177 -0
  226. package/dist/transaction/auth/capability.js +199 -0
  227. package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
  228. package/dist/{client → transaction/auth}/identity.d.ts +8 -7
  229. package/dist/{client → transaction/auth}/identity.js +1 -1
  230. package/dist/transaction/auth/index.d.ts +162 -0
  231. package/dist/transaction/auth/index.js +304 -0
  232. package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
  233. package/dist/{auth → transaction/auth}/schemas.js +13 -13
  234. package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
  235. package/dist/{client → transaction/auth}/sessionMint.js +4 -7
  236. package/dist/transaction/coordination/awaitClaimGrant.d.ts +49 -0
  237. package/dist/transaction/coordination/awaitClaimGrant.js +112 -0
  238. package/dist/transaction/coordination/claimMeta.d.ts +49 -0
  239. package/dist/transaction/coordination/claimMeta.js +52 -0
  240. package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
  241. package/dist/transaction/coordination/createClaimStream.js +475 -0
  242. package/dist/transaction/coordination/events.d.ts +74 -0
  243. package/dist/transaction/coordination/events.js +7 -0
  244. package/dist/transaction/coordination/index.d.ts +19 -0
  245. package/dist/transaction/coordination/index.js +44 -0
  246. package/dist/transaction/coordination/locator.d.ts +83 -0
  247. package/dist/transaction/coordination/locator.js +82 -0
  248. package/dist/transaction/coordination/schema.d.ts +1473 -0
  249. package/dist/{coordination → transaction/coordination}/schema.js +490 -55
  250. package/dist/transaction/coordination/targetConflict.d.ts +2 -0
  251. package/dist/transaction/coordination/targetConflict.js +103 -0
  252. package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
  253. package/dist/{coordination → transaction/coordination}/trace.js +18 -25
  254. package/dist/transaction/durableWrites.d.ts +62 -0
  255. package/dist/{client → transaction}/durableWrites.js +28 -3
  256. package/dist/transaction/environment.d.ts +105 -0
  257. package/dist/transaction/environment.js +108 -0
  258. package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +11 -11
  259. package/dist/{errorCodes.js → transaction/errorCodes.js} +35 -12
  260. package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
  261. package/dist/{errors.js → transaction/errors.js} +85 -16
  262. package/dist/transaction/index.d.ts +20 -0
  263. package/dist/transaction/index.js +20 -0
  264. package/dist/transaction/keys/index.d.ts +87 -0
  265. package/dist/transaction/keys/index.js +207 -0
  266. package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
  267. package/dist/transaction/log/syncDeltaRow.js +95 -0
  268. package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
  269. package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
  270. package/dist/transaction/logger.d.ts +16 -0
  271. package/dist/transaction/logger.js +7 -0
  272. package/dist/transaction/observability.d.ts +53 -0
  273. package/dist/transaction/observability.js +19 -0
  274. package/dist/transaction/plugin.d.ts +192 -0
  275. package/dist/transaction/plugin.js +87 -0
  276. package/dist/{policy → transaction/policy}/types.d.ts +3 -3
  277. package/dist/{policy → transaction/policy}/types.js +2 -0
  278. package/dist/transaction/resources/httpResources.d.ts +266 -0
  279. package/dist/transaction/resources/httpResources.js +7 -0
  280. package/dist/transaction/resources/modelOperations.d.ts +319 -0
  281. package/dist/transaction/resources/modelOperations.js +12 -0
  282. package/dist/transaction/resources/mutationOptions.d.ts +66 -0
  283. package/dist/transaction/resources/mutationOptions.js +9 -0
  284. package/dist/transaction/resources/where.d.ts +85 -0
  285. package/dist/transaction/resources/where.js +70 -0
  286. package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
  287. package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
  288. package/dist/{schema → transaction/schema}/field.d.ts +5 -5
  289. package/dist/{schema → transaction/schema}/field.js +5 -5
  290. package/dist/transaction/schema/loadStrategy.d.ts +45 -0
  291. package/dist/transaction/schema/loadStrategy.js +46 -0
  292. package/dist/{schema → transaction/schema}/model.d.ts +50 -35
  293. package/dist/{schema → transaction/schema}/model.js +30 -20
  294. package/dist/transaction/schema/openapi.d.ts +57 -0
  295. package/dist/transaction/schema/openapi.js +340 -0
  296. package/dist/{schema → transaction/schema}/relation.d.ts +14 -14
  297. package/dist/{schema → transaction/schema}/relation.js +7 -7
  298. package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
  299. package/dist/{schema → transaction/schema}/residency.js +0 -5
  300. package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
  301. package/dist/{schema → transaction/schema}/roles.js +5 -5
  302. package/dist/{schema → transaction/schema}/schema.d.ts +12 -10
  303. package/dist/{schema → transaction/schema}/schema.js +4 -3
  304. package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
  305. package/dist/{schema → transaction/schema}/tenancy.js +7 -4
  306. package/dist/transaction/transactionLayer.d.ts +82 -0
  307. package/dist/transaction/transactionLayer.js +24 -0
  308. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +4 -5
  309. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
  310. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
  311. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
  312. package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +10 -36
  313. package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
  314. package/dist/transaction/transport/commitFrames.d.ts +90 -0
  315. package/dist/transaction/transport/commitFrames.js +134 -0
  316. package/dist/transaction/transport/connectionManager.d.ts +215 -0
  317. package/dist/transaction/transport/connectionManager.js +673 -0
  318. package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
  319. package/dist/transaction/transport/credentialLifecycle.js +324 -0
  320. package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
  321. package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
  322. package/dist/{client → transaction/transport}/httpClient.d.ts +59 -16
  323. package/dist/{client → transaction/transport}/httpClient.js +5 -5
  324. package/dist/transaction/transport/httpOptions.d.ts +33 -0
  325. package/dist/transaction/transport/httpOptions.js +12 -0
  326. package/dist/{client → transaction/transport}/httpTransport.js +171 -85
  327. package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
  328. package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
  329. package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
  330. package/dist/transaction/transport/wsFrameHandlers.js +429 -0
  331. package/dist/transaction/transport/wsTransport.d.ts +576 -0
  332. package/dist/transaction/transport/wsTransport.js +1017 -0
  333. package/dist/transaction/types/assertExact.d.ts +17 -0
  334. package/dist/transaction/types/assertExact.js +1 -0
  335. package/dist/{types → transaction/types}/global.d.ts +17 -2
  336. package/dist/{types → transaction/types}/global.js +2 -1
  337. package/dist/{types → transaction/types}/index.d.ts +14 -46
  338. package/dist/{types → transaction/types}/index.js +7 -16
  339. package/dist/{types → transaction/types}/streams.d.ts +63 -45
  340. package/dist/{utils → transaction/utils}/json.d.ts +18 -0
  341. package/dist/transaction/utils/json.js +276 -0
  342. package/dist/transaction/wire/accountResponses.d.ts +351 -0
  343. package/dist/transaction/wire/accountResponses.js +255 -0
  344. package/dist/transaction/wire/auth.d.ts +49 -0
  345. package/dist/transaction/wire/auth.js +57 -0
  346. package/dist/transaction/wire/claimEvent.d.ts +76 -0
  347. package/dist/transaction/wire/claimEvent.js +73 -0
  348. package/dist/transaction/wire/claims.d.ts +463 -0
  349. package/dist/transaction/wire/claims.js +229 -0
  350. package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
  351. package/dist/{wire → transaction/wire}/commit.js +68 -47
  352. package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
  353. package/dist/{wire → transaction/wire}/delta.js +37 -13
  354. package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
  355. package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
  356. package/dist/transaction/wire/feedCursor.d.ts +60 -0
  357. package/dist/transaction/wire/feedCursor.js +82 -0
  358. package/dist/transaction/wire/feedEvent.d.ts +177 -0
  359. package/dist/transaction/wire/feedEvent.js +39 -0
  360. package/dist/transaction/wire/frames.d.ts +194 -0
  361. package/dist/transaction/wire/frames.js +50 -0
  362. package/dist/transaction/wire/inboundFrames.d.ts +552 -0
  363. package/dist/transaction/wire/inboundFrames.js +116 -0
  364. package/dist/transaction/wire/index.d.ts +50 -0
  365. package/dist/transaction/wire/index.js +74 -0
  366. package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
  367. package/dist/transaction/wire/listEnvelope.js +42 -0
  368. package/dist/transaction/wire/modelResponses.d.ts +85 -0
  369. package/dist/transaction/wire/modelResponses.js +43 -0
  370. package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +79 -38
  371. package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +110 -59
  372. package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
  373. package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
  374. package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
  375. package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
  376. package/dist/transactions/mutations/commitLatency.d.ts +52 -0
  377. package/dist/transactions/mutations/commitLatency.js +130 -0
  378. package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
  379. package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
  380. package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +16 -15
  381. package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +12 -12
  382. package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +11 -11
  383. package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +7 -7
  384. package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
  385. package/dist/transactions/mutations/durableWriteStore.js +12 -0
  386. package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
  387. package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +3 -3
  388. package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +4 -3
  389. package/dist/utils/mobxSetup.d.ts +1 -1
  390. package/dist/utils/mobxSetup.js +5 -2
  391. package/dist/webhooks/events.d.ts +2 -2
  392. package/dist/wire/index.d.ts +1 -34
  393. package/dist/wire/index.js +8 -49
  394. package/docs/agent-messaging.md +3 -3
  395. package/docs/agents.md +19 -12
  396. package/docs/api-keys.md +8 -4
  397. package/docs/api.md +22 -18
  398. package/docs/audit.md +2 -0
  399. package/docs/cli.md +31 -3
  400. package/docs/client-behavior.md +8 -6
  401. package/docs/concurrency-convention.md +30 -24
  402. package/docs/coordination.md +48 -38
  403. package/docs/data-sources.md +3 -1
  404. package/docs/debugging.md +5 -3
  405. package/docs/deployment.md +267 -0
  406. package/docs/examples/agent-human.md +49 -42
  407. package/docs/examples/ai-sdk-tool.md +69 -44
  408. package/docs/examples/existing-python-backend.md +8 -6
  409. package/docs/examples/nextjs.md +129 -47
  410. package/docs/examples/scoped-agent.md +45 -44
  411. package/docs/examples/server-agent.md +46 -26
  412. package/docs/groups.md +32 -29
  413. package/docs/guarantees.md +4 -2
  414. package/docs/how-it-works.md +9 -7
  415. package/docs/idempotency.md +126 -0
  416. package/docs/identity.md +58 -54
  417. package/docs/index.md +172 -84
  418. package/docs/integration-guide.md +17 -16
  419. package/docs/interaction-model.md +6 -4
  420. package/docs/mcp.md +41 -16
  421. package/docs/migration.md +63 -5
  422. package/docs/operating-on-your-database.md +111 -0
  423. package/docs/projects.md +2 -0
  424. package/docs/quickstart.md +22 -5
  425. package/docs/react.md +12 -10
  426. package/docs/schema-contract.md +5 -3
  427. package/docs/session-settings.md +108 -0
  428. package/docs/sessions.md +3 -1
  429. package/docs/webhooks.md +3 -1
  430. package/llms.txt +47 -17
  431. package/package.json +10 -8
  432. package/dist/agent/Agent.d.ts +0 -366
  433. package/dist/agent/Agent.js +0 -514
  434. package/dist/agent/index.d.ts +0 -115
  435. package/dist/agent/index.js +0 -128
  436. package/dist/agent/session.d.ts +0 -93
  437. package/dist/agent/session.js +0 -149
  438. package/dist/agent/types.d.ts +0 -68
  439. package/dist/agent/types.js +0 -9
  440. package/dist/client/durableWrites.d.ts +0 -21
  441. package/dist/coordination/schema.d.ts +0 -722
  442. package/dist/schema/openapi.d.ts +0 -29
  443. package/dist/schema/openapi.js +0 -124
  444. package/dist/transactions/durableWriteStore.js +0 -30
  445. package/dist/utils/json.js +0 -88
  446. package/dist/wire/errorEnvelope.d.ts +0 -55
  447. package/dist/wire/frames.d.ts +0 -197
  448. package/dist/wire/frames.js +0 -49
  449. package/dist/wire/listEnvelope.js +0 -18
  450. /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
  451. /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
  452. /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
  453. /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
  454. /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
  455. /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
  456. /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
  457. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +0 -0
  458. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +0 -0
  459. /package/dist/{client → transaction}/persistence.d.ts +0 -0
  460. /package/dist/{client → transaction}/persistence.js +0 -0
  461. /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
  462. /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
  463. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
  464. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
  465. /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
  466. /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
  467. /package/dist/{types → transaction/types}/modelData.js +0 -0
  468. /package/dist/{types → transaction/types}/participant.d.ts +0 -0
  469. /package/dist/{types → transaction/types}/participant.js +0 -0
  470. /package/dist/{types → transaction/types}/streams.js +0 -0
  471. /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
  472. /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
  473. /package/dist/{utils → transaction/utils}/duration.d.ts +0 -0
  474. /package/dist/{utils → transaction/utils}/duration.js +0 -0
  475. /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
  476. /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
  477. /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
  478. /package/dist/{wire → transaction/wire}/protocol.js +0 -0
  479. /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
  480. /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
  481. /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
  482. /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
  483. /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
@@ -1,11 +1,15 @@
1
1
  # Server Agent
2
2
 
3
+ > A stateless schema-backed worker: wake, claim, commit, go idle.
4
+
3
5
  A server agent is backend code — a cron job, a queue worker, an AI task — that
4
6
  reads and writes your app's records outside the browser. The hard part is doing
5
- it without racing the live UI: if your worker and a user edit the same report at
6
- once, one write clobbers the other. This is what `claim()` is for. Below, a
7
- worker finishes a weather report by claiming it, writing the result, and
8
- releasing it automatically when the claim goes out of scope.
7
+ it without racing whatever else is working: if two workers pick up the same task
8
+ at once, one write clobbers the other. This is what `claim()` is for.
9
+
10
+ Agents hold no socket, so pass `transport: 'http'` and import the same schema the
11
+ rest of the app uses. Below, a worker finishes a task by claiming it, writing the
12
+ result, and releasing it automatically when the claim goes out of scope.
9
13
 
10
14
  `claim({ id })` takes the record for your worker and returns a disposable handle:
11
15
  the fresh post-lease row is on `claim.data`, and holding the handle with
@@ -18,53 +22,69 @@ import Ablo from '@abloatai/ablo';
18
22
  import { defineSchema, model, z } from '@abloatai/ablo/schema';
19
23
 
20
24
  const schema = defineSchema({
21
- weatherReports: model({
22
- location: z.string(),
23
- status: z.enum(['pending', 'ready']),
24
- forecast: z.string().optional(),
25
+ tasks: model({
26
+ title: z.string(),
27
+ status: z.enum(['todo', 'doing', 'done']),
28
+ summary: z.string().optional(),
25
29
  }),
26
30
  });
27
31
 
28
32
  const ablo = Ablo({
29
33
  schema,
30
34
  apiKey: process.env.ABLO_API_KEY,
35
+ transport: 'http',
31
36
  });
32
37
 
33
- export async function completeReport(reportId: string) {
38
+ export async function completeTask(taskId: string) {
34
39
  await ablo.ready();
35
40
 
36
- const report = await ablo.weatherReports.retrieve({ id: reportId });
37
- if (!report) return { status: 'not_found' };
41
+ const task = await ablo.tasks.retrieve({ id: taskId });
42
+ if (!task) return { status: 'not_found' };
38
43
 
39
- await using claim = await ablo.weatherReports.claim({
40
- id: reportId,
44
+ await using claim = await ablo.tasks.claim({
45
+ id: taskId,
41
46
  queue: false,
42
47
  description: 'completing',
43
48
  });
44
- const claimed = claim.data;
45
49
 
46
- const updated = await ablo.weatherReports.update({
47
- id: claimed.id,
48
- data: { status: 'ready' },
50
+ const updated = await ablo.tasks.update({
51
+ id: claim.data.id,
52
+ data: { status: 'done' },
49
53
  wait: 'confirmed',
50
54
  });
51
55
 
52
- return { status: 'ready', report: updated };
56
+ return { status: 'done', task: updated };
57
+ // claim auto-releases as the function returns
53
58
  }
54
59
  ```
55
60
 
56
61
  `retrieve({ id })` is an async server read — it hits the server and returns the
57
- row (or `null`, which the early `not_found` guard handles). The update runs while
58
- the claim is held, and `wait: 'confirmed'` makes that update resolve only once
59
- the server has accepted it.
62
+ row (or `undefined`, which the early `not_found` guard handles). The update runs
63
+ while the claim is held, and `wait: 'confirmed'` makes it resolve only once your
64
+ database has confirmed the row landed.
60
65
 
61
66
  The two options on the claim:
62
67
 
63
68
  - `queue: false` — skip this record if another claim is already in progress,
64
- rather than queueing behind it. (The default queues.)
65
- - `description: 'completing'` a human-readable label for what your worker is doing,
69
+ rather than queueing behind it. Fail-fast dedup: *if someone else has this job,
70
+ skip it.* (The default queues.)
71
+ - `description: 'completing'` — a readable label for what your worker is doing,
66
72
  visible to anyone reading `claim.state({ id })`.
67
73
 
68
- Because the worker uses the same schema and `claim()` as the UI, its writes sync
69
- to every connected client in real time and never collide with edits already in
70
- progress.
74
+ ## Atomic batches
75
+
76
+ When several rows must change together, submit one atomic commit through the same
77
+ schema-backed client:
78
+
79
+ ```ts
80
+ await ablo.commits.create({
81
+ operations: [
82
+ { action: 'update', model: 'tasks', id: 'task_123', data: { status: 'done' } },
83
+ ],
84
+ wait: 'confirmed',
85
+ });
86
+ ```
87
+
88
+ Because the worker uses the same schema and `claim()` as everything else, its
89
+ writes reach every connected client in real time and never collide with work
90
+ already in progress.
package/docs/groups.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Change Propagation
2
2
 
3
+ > How one row's change reaches the rows and actors that depend on it.
4
+
3
5
  > How a change to one row reaches the rows and actors that depend on it, and how
4
6
  > to keep a chain of dependent work fresh. This is the propagation half of sync
5
7
  > groups; [`identity.md`](./identity.md) is the access half (who may read a
@@ -10,8 +12,8 @@
10
12
 
11
13
  ## Start from the problem
12
14
 
13
- An agent reads deck `A` to write slide `B`. A moment later it reads `B` to write
14
- layer `C`. Between those steps someone else edits `A`. The agent is now building
15
+ An agent reads workspace `A` to write document `B`. A moment later it reads `B` to write
16
+ block `C`. Between those steps someone else edits `A`. The agent is now building
15
17
  `C` on a premise that has moved — and nothing about writing `C` looks wrong in
16
18
  isolation. That is stale context, and it is the thing sync groups let you catch.
17
19
 
@@ -19,19 +21,19 @@ The recipe is one field on the commit: declare the group you read as a premise,
19
21
  and say what should happen if it moved.
20
22
 
21
23
  ```ts
22
- // The agent read everything under deck:abc to compose this write.
23
- await ablo.layers.update({
24
- id: 'layer-C',
24
+ // The agent read everything under workspace:abc to compose this write.
25
+ await ablo.blocks.update({
26
+ id: 'block-C',
25
27
  data: { text: revised },
26
- reads: [{ group: 'deck:abc', readAt: watermark, onStale: 'notify' }],
28
+ reads: [{ group: 'workspace:abc', readAt: watermark, onStale: 'notify' }],
27
29
  });
28
30
  ```
29
31
 
30
32
  At commit, inside the write transaction, the engine asks a single question: *did
31
- any delta routed to `deck:abc` land after `watermark`?* If nothing moved, the
33
+ any delta routed to `workspace:abc` land after `watermark`?* If nothing moved, the
32
34
  write applies. If something moved, `onStale` decides — `notify` holds the write
33
35
  and hands the agent a `StaleNotification` naming the group, so it re-reads
34
- `deck:abc` and regenerates; `reject` aborts the batch with a `409`. The agent
36
+ `workspace:abc` and regenerates; `reject` aborts the batch with a `409`. The agent
35
37
  never persists work built on a premise it can no longer see.
36
38
 
37
39
  ---
@@ -43,13 +45,13 @@ for you and leaves the third to you — on purpose.
43
45
 
44
46
  **Routing — who hears about a change.** Every row belongs to one or more sync
45
47
  groups, and a write fans out to all of them. A row also inherits its ancestors'
46
- groups: editing a layer stamps the delta with `layer:…`, `slide:…`, *and*
47
- `deck:…`, so everyone watching the deck sees the layer move. This is delivery,
48
+ groups: editing a block stamps the delta with `block:…`, `document:…`, *and*
49
+ `workspace:…`, so everyone watching the workspace sees the block move. This is delivery,
48
50
  resolved by walking the ownership tree at commit time. It routes the change; it
49
51
  never recomputes a value.
50
52
 
51
- **Structural cascade — what disappears with a change.** Deleting a deck removes
52
- its slides and layers. The database does that through `ON DELETE CASCADE`, but a
53
+ **Structural cascade — what disappears with a change.** Deleting a workspace removes
54
+ its documents and blocks. The database does that through `ON DELETE CASCADE`, but a
53
55
  database-level cascade emits no delta, so open clients would quietly hold rows
54
56
  that no longer exist. The engine closes that gap: before the delete it snapshots
55
57
  the subtree and emits a tombstone for each descendant, routed to the right
@@ -85,8 +87,8 @@ reaches `C`.
85
87
 
86
88
  The direction matters. The signal flows forward, A to B to C, and each hop is a
87
89
  real write an actor chose to make. The engine supplies the edges (group
88
- membership) and a stale signal on each edge (the read-dependency check); the
89
- actors are the runtime that walks them. It is closer to a spreadsheet an analyst
90
+ membership) and a stale signal on each edge (the premise check); the actors are
91
+ the runtime that walks them. It is closer to a spreadsheet an analyst
90
92
  recalculates cell by cell than to a reactive engine that recomputes the whole
91
93
  column for you.
92
94
 
@@ -102,16 +104,17 @@ Two consequences worth designing around:
102
104
 
103
105
  ---
104
106
 
105
- ## Declaring a read premise
107
+ ## Declaring the batch premise
106
108
 
107
- A read-dependency is a premise for the *whole* batch: its disposition governs
108
- every write in the commit, not just one operation. You choose the granularity per
109
+ `reads[]` declares what the commit was based on. Each entry is a premise, and
110
+ each governs the *whole* commit: if one goes stale, its disposition applies to
111
+ every write in the batch, not just one operation. You choose the granularity per
109
112
  entry.
110
113
 
111
114
  ```ts
112
115
  reads: [
113
- { group: 'deck:abc', readAt: N, onStale: 'notify' }, // did anything in the deck move?
114
- { model: 'Slide', id: 's-1', readAt: N, fields: ['title'] }, // did this row (this field) move?
116
+ { group: 'workspace:abc', readAt: N, onStale: 'notify' }, // did anything in the workspace move?
117
+ { model: 'Document', id: 's-1', readAt: N, fields: ['title'] }, // did this row (this field) move?
115
118
  ]
116
119
  ```
117
120
 
@@ -137,7 +140,7 @@ the same row don't collide.
137
140
 
138
141
  ## Staying subscribed across commits: `track`
139
142
 
140
- A read premise guards a single commit: you state what you read, the engine
143
+ A batch premise guards a single commit: you state what you read, the engine
141
144
  checks it, the premise is gone. That fits an actor that reads and writes in one
142
145
  breath. It does not fit a long-running one — an agent that reads a row now,
143
146
  works for a few minutes, and writes much later. By the time it commits, the
@@ -152,10 +155,10 @@ you, arriving on the write you were going to make anyway.
152
155
 
153
156
  ```ts
154
157
  // Register interest and walk away — no write required.
155
- await ablo.slides.track({ id: 's-1' });
158
+ await ablo.documents.track({ id: 's-1' });
156
159
 
157
160
  // …minutes of other work later, on your next commit…
158
- const res = await ablo.layers.update({ id: 'layer-C', data: { text: revised } });
161
+ const res = await ablo.blocks.update({ id: 'block-C', data: { text: revised } });
159
162
  res.notifications; // populated if s-1 moved under you in the meantime
160
163
  ```
161
164
 
@@ -169,11 +172,11 @@ You can also register a track as part of a write you are already making, the
169
172
  persisted companion to `reads`:
170
173
 
171
174
  ```ts
172
- await ablo.slides.update({
175
+ await ablo.documents.update({
173
176
  id: 's-1',
174
177
  data: { title: revised },
175
- reads: [{ group: 'deck:abc', readAt: N, onStale: 'notify' }], // guards THIS commit
176
- track: [{ group: 'deck:abc' }], // and keeps watching after it
178
+ reads: [{ group: 'workspace:abc', readAt: N, onStale: 'notify' }], // guards THIS commit
179
+ track: [{ group: 'workspace:abc' }], // and keeps watching after it
177
180
  });
178
181
  ```
179
182
 
@@ -194,8 +197,8 @@ broad wakes actors for changes they don't care about, and one that is too narrow
194
197
  misses the dependency you meant to track.
195
198
 
196
199
  The rule of thumb: **make a group the smallest set of rows that must stay
197
- mutually consistent.** A deck and its slides belong together because editing one
198
- changes what the others mean; two unrelated decks do not. Reach for finer,
200
+ mutually consistent.** A workspace and its documents belong together because editing one
201
+ changes what the others mean; two unrelated workspaces do not. Reach for finer,
199
202
  overlapping groups when you genuinely have a dependency chain to track, and keep
200
203
  them coarse everywhere else.
201
204
 
@@ -204,7 +207,7 @@ them coarse everywhere else.
204
207
  ## Where this is defined
205
208
 
206
209
  - **Access** — who may read or write a group — is [`identity.md`](./identity.md).
207
- - **The convention** — non-coercion, the read-set, and the notification — is
210
+ - **The convention** — non-coercion, the premise, and the notification — is
208
211
  [`concurrency-convention.md`](./concurrency-convention.md) (§4 and §5).
209
- - **The mechanics** — the three coordination layers underneath — are
212
+ - **The mechanics** — the three coordination blocks underneath — are
210
213
  [`coordination.md`](./coordination.md).
@@ -1,7 +1,9 @@
1
1
  # Guarantees
2
2
 
3
- When an Ablo write succeeds, the server has accepted it and when two people or
4
- agents touch the same row, Ablo coordinates them instead of letting one silently
3
+ > Exactly what a confirmed write, a rejected stale write, and a held claim each promise.
4
+
5
+ When an Ablo write succeeds, the server has accepted it — and when two agents
6
+ touch the same row, Ablo coordinates them instead of letting one silently
5
7
  overwrite the other. This page is the precise list of what you can count on:
6
8
  confirmed writes, stale-write protection, claims, and the audit trail behind
7
9
  every change.
@@ -1,5 +1,7 @@
1
1
  # How Ablo Works
2
2
 
3
+ > You write through Ablo, Ablo writes to your Postgres, and the write-ahead log confirms it.
4
+
3
5
  You write through Ablo, and Ablo writes to your Postgres. That one sentence is the
4
6
  whole model — everything below explains what it means and how to use it.
5
7
 
@@ -8,14 +10,14 @@ whole model — everything below explains what it means and how to use it.
8
10
  await ablo.tasks.update({ id: 'task_42', data: { status: 'done' }, wait: 'confirmed' });
9
11
 
10
12
  // Reads come back live, kept current from your database.
11
- const task = ablo.tasks.get('task_42');
13
+ const task = ablo.tasks.local.retrieve('task_42');
12
14
  ```
13
15
 
14
16
  ## The mental model — read this once
15
17
 
16
- Ablo is a **coordination layer in front of your Postgres**. Humans, agents, and
17
- background jobs all change the same application data through one API, and Ablo
18
- makes sure their writes don't clobber each other.
18
+ Ablo is a **coordination layer in front of your Postgres**. Agents, background
19
+ jobs, and the people alongside them all change the same application data through
20
+ one API, and Ablo makes sure their writes don't clobber each other.
19
21
 
20
22
  - **Writes go through Ablo.** `ablo.<model>.create / update / delete` enter Ablo's
21
23
  commit chokepoint — where claims, ordering, and idempotency are enforced — and
@@ -57,7 +59,7 @@ Registering the database is the whole switch. There is no tier or flag to choose
57
59
  npm install @abloatai/ablo
58
60
  npx ablo init
59
61
 
60
- # 2. Push your schema (the models humans and agents edit together).
62
+ # 2. Push your schema (the models your agents edit together).
61
63
  npx ablo push
62
64
 
63
65
  # 3. Connect your database — one command, admin credential used once and discarded.
@@ -80,13 +82,13 @@ export const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
80
82
  await ablo.tasks.update({ id: 'task_42', data: { status: 'done' }, wait: 'confirmed' });
81
83
 
82
84
  // 6. Read — live, no fetch loop.
83
- const task = ablo.tasks.get('task_42');
85
+ const task = ablo.tasks.local.retrieve('task_42');
84
86
 
85
87
  // 7. Coordinate when more than one actor can touch a row. Hold a claim and Ablo
86
88
  // serializes writes on that key against everyone else; read after claiming,
87
89
  // then write. The lease releases automatically at the end of the scope.
88
90
  await using _hold = await ablo.tasks.claim('task_42');
89
- const latest = ablo.tasks.get('task_42'); // read after claiming, not from memory
91
+ const latest = ablo.tasks.local.retrieve('task_42'); // read after claiming, not from memory
90
92
  await ablo.tasks.update({ id: 'task_42', data: { status: 'done' } });
91
93
  ```
92
94
 
@@ -0,0 +1,126 @@
1
+ # Idempotency
2
+
3
+ > Make a retried write safe: the same key never applies the same change twice.
4
+
5
+ An agent retries. A socket drops mid-commit, a worker restarts, a queue redelivers — and the write
6
+ you already sent arrives again. An idempotency key is how Ablo tells a retry from a new intention.
7
+
8
+ Every model write carries one. The SDK generates a key when you omit it, which makes an in-process
9
+ retry safe automatically. It cannot make a retry across a process restart safe, because a new
10
+ process generates a new key — so for anything that must survive a crash, supply your own.
11
+
12
+ ```ts
13
+ await ablo.tasks.update({
14
+ id: taskId,
15
+ data: { status: 'done' },
16
+ idempotencyKey: `task:${taskId}:mark-done:v1`,
17
+ wait: 'confirmed',
18
+ });
19
+ ```
20
+
21
+ ## The one rule
22
+
23
+ **Derive the key from the business event, not from the attempt.** A key built from
24
+ `crypto.randomUUID()` at the call site is regenerated on every retry, so it protects nothing — each
25
+ attempt looks like a new intention and the write lands twice. A key built from the thing that
26
+ happened (`task:42:mark-done:v1`) is identical on every retry by construction, which is the whole
27
+ point.
28
+
29
+ The same rule stated as its failure: never derive a key from a timestamp, an attempt counter, or a
30
+ random value. If two retries of one operation can produce different keys, you have no idempotency.
31
+
32
+ ## How it works
33
+
34
+ The key is not a lookup that happens before the write — it is the **execution lock on the write
35
+ itself**. Ablo inserts a pending row keyed by the caller and the key inside the same transaction as
36
+ the mutation, and a unique index makes that insert the lock:
37
+
38
+ - **Insert wins** — this transaction owns the execution and runs the write.
39
+ - **Insert conflicts** — someone else owns it. The second caller waits for the owner to finish and
40
+ then replays its recorded result.
41
+ - **Insert conflicts, different request** — the key was reused to mean something else. Rejected.
42
+
43
+ Because the lock and the write share a transaction, there is no window in which a write has happened
44
+ but its key has not been recorded.
45
+
46
+ Keys are scoped to **the organization and the participant**, not globally. Two agents can use the
47
+ same key string without colliding, and one agent can never replay another's result.
48
+
49
+ ## The four outcomes
50
+
51
+ | You send | Ablo does |
52
+ |---|---|
53
+ | A new key | Runs the write. |
54
+ | The same key, the same request, already finished | Replays the recorded result. The write does not run again. |
55
+ | The same key, the same request, still running | Waits for the in-flight attempt, then replays its result. If the original is still running after a short wait, rejects with `idempotency_conflict` (409) — retry the same key. |
56
+ | The same key, a **different** request | Rejects with `idempotency_conflict` (409). A key is bound to the request it first arrived with. |
57
+
58
+ Both conflict cases return the same code, so tell them apart by what your own
59
+ client did. If you retried an identical request, the original is still in flight
60
+ — wait and retry the same key. If you changed the request, that is a client bug:
61
+ use a new key.
62
+
63
+ ## Failures are not replayed — they re-run
64
+
65
+ This is where Ablo deliberately differs from Stripe and from most payment APIs, and it is the
66
+ behaviour most likely to surprise you.
67
+
68
+ **Only successful writes are recorded.** A write that failed leaves no idempotency record, so
69
+ retrying it with the same key **executes fresh** rather than replaying the error.
70
+
71
+ That is the right default here because most failures are ones you can fix and legitimately want to
72
+ re-attempt — a validation error, a stale premise, a claim held by someone else. Replaying the
73
+ original error for 24 hours would strand the caller behind a decision that is no longer true.
74
+
75
+ The consequence to hold onto: a retry after a failure is a real execution. If a write failed in a
76
+ way that leaves you unsure whether it landed — a timeout, a dropped socket — do not assume the retry
77
+ is a no-op. Retry with the **same key**: if the original did land, the recorded success replays; if
78
+ it did not, the write runs now. That is exactly the case idempotency exists for.
79
+
80
+ ## The window
81
+
82
+ A recorded result is retained for **24 hours**, then expires. Within that window a repeated key
83
+ replays. After it, the key is forgotten and reusing it starts a genuinely new write.
84
+
85
+ Treat 24 hours as *how long a retry is guaranteed safe*, not as permanent deduplication. A nightly
86
+ job that reuses yesterday's key will execute again.
87
+
88
+ Writes routed to a registered data source are the exception: their intent is retained **permanently**
89
+ rather than expiring, because letting that record lapse would make a reused key indistinguishable
90
+ from old work against your database. A key whose retained intent has expired is rejected with
91
+ `idempotency_key_expired` (409) rather than being silently re-executed.
92
+
93
+ ## Route pinning
94
+
95
+ A key is bound to the route its first attempt took. If an earlier attempt was applied through a
96
+ direct data source and a retry arrives when the endpoint fallback is active, Ablo rejects it with
97
+ `source_transport_pinned` (409) instead of switching.
98
+
99
+ That refusal is deliberate: switching routes on a retry risks applying a write that the first route
100
+ may already have committed. Restore the original route and retry the same key.
101
+
102
+ ## When to retry
103
+
104
+ | Situation | Do |
105
+ |---|---|
106
+ | Timeout or dropped connection, no response | Retry with the **same** key, with backoff. You get the recorded success, or the write runs now. |
107
+ | `source_unreachable` (503) | Retry with the **same** key once connectivity recovers. The write stays pinned to its route. |
108
+ | `replication_lag_timeout` (504) | The write may have materialized. Retry with the **same** key, or wait for source ingestion to catch up. |
109
+ | `AbloStaleContextError` | Re-read the row, regenerate, then write under a **new** key — the new write is a new intention. |
110
+ | `AbloClaimedError` | Someone else holds the row. Wait or yield; the key is unused, so reuse it when you retry. |
111
+ | `idempotency_conflict` (409) after an identical retry | The original is still in flight. Wait, then retry the **same** key. |
112
+ | `idempotency_conflict` (409) after changing the request | A client bug: a key is bound to the first request sent under it. Use a **new** key. |
113
+ | `idempotency_key_too_long` (400) | The key exceeds 255 characters. A UUID or a short business string works. |
114
+
115
+ ## Keys
116
+
117
+ - Up to **255 characters**. Longer is rejected, not truncated.
118
+ - Unique per logical operation, identical across every retry of that operation.
119
+ - Generate a new one only when a genuinely new operation begins.
120
+ - Never put secrets in a key — it is stored and appears in support diagnostics.
121
+
122
+ ## Related
123
+
124
+ - [Client Behavior](./client-behavior.md) — every write option, and which errors retry.
125
+ - [Guarantees](./guarantees.md) — what `queued` and `confirmed` promise.
126
+ - [Errors](./errors.md) — the full code registry, including every code named above.