@abloatai/humans 0.37.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 (404) hide show
  1. package/README.md +52 -0
  2. package/dist/Ablo.d.ts +208 -0
  3. package/dist/Ablo.js +120 -0
  4. package/dist/client.d.ts +317 -0
  5. package/dist/client.js +13 -0
  6. package/dist/core.d.ts +35 -0
  7. package/dist/core.js +48 -0
  8. package/dist/humans.d.ts +28 -0
  9. package/dist/humans.js +34 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +6 -0
  12. package/dist/local/BaseSyncedStore.d.ts +807 -0
  13. package/dist/local/BaseSyncedStore.js +1516 -0
  14. package/dist/local/Database.d.ts +322 -0
  15. package/dist/local/Database.js +1589 -0
  16. package/dist/local/InstanceCache.d.ts +255 -0
  17. package/dist/local/InstanceCache.js +1263 -0
  18. package/dist/local/LazyReferenceCollection.d.ts +177 -0
  19. package/dist/local/LazyReferenceCollection.js +461 -0
  20. package/dist/local/Model.d.ts +475 -0
  21. package/dist/local/Model.js +950 -0
  22. package/dist/local/ModelRegistry.d.ts +225 -0
  23. package/dist/local/ModelRegistry.js +539 -0
  24. package/dist/local/NetworkMonitor.d.ts +28 -0
  25. package/dist/local/NetworkMonitor.js +79 -0
  26. package/dist/local/RuntimeContext.d.ts +52 -0
  27. package/dist/local/RuntimeContext.js +80 -0
  28. package/dist/local/SyncClient.d.ts +516 -0
  29. package/dist/local/SyncClient.js +1754 -0
  30. package/dist/local/adapters/alwaysOnline.d.ts +14 -0
  31. package/dist/local/adapters/alwaysOnline.js +17 -0
  32. package/dist/local/adapters/inMemoryStorage.d.ts +37 -0
  33. package/dist/local/adapters/inMemoryStorage.js +122 -0
  34. package/dist/local/client/clientPrelude.d.ts +52 -0
  35. package/dist/local/client/clientPrelude.js +60 -0
  36. package/dist/local/client/consoleLogger.d.ts +35 -0
  37. package/dist/local/client/consoleLogger.js +44 -0
  38. package/dist/local/client/createInternalComponents.d.ts +50 -0
  39. package/dist/local/client/createInternalComponents.js +98 -0
  40. package/dist/local/client/createModelProxy.d.ts +248 -0
  41. package/dist/local/client/createModelProxy.js +880 -0
  42. package/dist/local/client/modelRegistration.d.ts +10 -0
  43. package/dist/local/client/modelRegistration.js +316 -0
  44. package/dist/local/client/options.d.ts +440 -0
  45. package/dist/local/client/options.js +7 -0
  46. package/dist/local/client/reactiveEngine.d.ts +53 -0
  47. package/dist/local/client/reactiveEngine.js +705 -0
  48. package/dist/local/client/resourceTypes.d.ts +12 -0
  49. package/dist/local/client/resourceTypes.js +10 -0
  50. package/dist/local/client/schemaConfig.d.ts +44 -0
  51. package/dist/local/client/schemaConfig.js +185 -0
  52. package/dist/local/client/storeCluster.d.ts +46 -0
  53. package/dist/local/client/storeCluster.js +133 -0
  54. package/dist/local/client/storeLifecycle.d.ts +61 -0
  55. package/dist/local/client/storeLifecycle.js +236 -0
  56. package/dist/local/client/validateAbloOptions.d.ts +42 -0
  57. package/dist/local/client/validateAbloOptions.js +43 -0
  58. package/dist/local/client/wsMutationExecutor.d.ts +27 -0
  59. package/dist/local/client/wsMutationExecutor.js +72 -0
  60. package/dist/local/context.d.ts +42 -0
  61. package/dist/local/context.js +81 -0
  62. package/dist/local/coordination/ClaimLog.d.ts +26 -0
  63. package/dist/local/coordination/ClaimLog.js +32 -0
  64. package/dist/local/interfaces/index.d.ts +311 -0
  65. package/dist/local/interfaces/index.js +9 -0
  66. package/dist/local/localModelContract.d.ts +14 -0
  67. package/dist/local/localModelContract.js +1 -0
  68. package/dist/local/logPosition.d.ts +31 -0
  69. package/dist/local/logPosition.js +53 -0
  70. package/dist/local/mutationPersistence.d.ts +6 -0
  71. package/dist/local/mutationPersistence.js +1 -0
  72. package/dist/local/mutators/RecordingMutation.d.ts +36 -0
  73. package/dist/local/mutators/RecordingMutation.js +182 -0
  74. package/dist/local/mutators/Transaction.d.ts +40 -0
  75. package/dist/local/mutators/Transaction.js +58 -0
  76. package/dist/local/mutators/UndoManager.d.ts +258 -0
  77. package/dist/local/mutators/UndoManager.js +665 -0
  78. package/dist/local/mutators/defineMutators.d.ts +60 -0
  79. package/dist/local/mutators/defineMutators.js +18 -0
  80. package/dist/local/mutators/inverseOp.d.ts +126 -0
  81. package/dist/local/mutators/inverseOp.js +71 -0
  82. package/dist/local/mutators/mutateActions.d.ts +45 -0
  83. package/dist/local/mutators/mutateActions.js +105 -0
  84. package/dist/local/mutators/readerActions.d.ts +33 -0
  85. package/dist/local/mutators/readerActions.js +57 -0
  86. package/dist/local/mutators/undoApply.d.ts +51 -0
  87. package/dist/local/mutators/undoApply.js +117 -0
  88. package/dist/local/persistence.d.ts +7 -0
  89. package/dist/local/persistence.js +9 -0
  90. package/dist/local/query/QueryProcessor.d.ts +75 -0
  91. package/dist/local/query/QueryProcessor.js +255 -0
  92. package/dist/local/query/client.d.ts +64 -0
  93. package/dist/local/query/client.js +138 -0
  94. package/dist/local/query/types.d.ts +85 -0
  95. package/dist/local/query/types.js +16 -0
  96. package/dist/local/schema/serialize.d.ts +1 -0
  97. package/dist/local/schema/serialize.js +1 -0
  98. package/dist/local/store/queryApi.d.ts +13 -0
  99. package/dist/local/store/queryApi.js +35 -0
  100. package/dist/local/storeContract.d.ts +145 -0
  101. package/dist/local/storeContract.js +12 -0
  102. package/dist/local/stores/DatabaseManager.d.ts +112 -0
  103. package/dist/local/stores/DatabaseManager.js +400 -0
  104. package/dist/local/stores/ObjectStore.d.ts +115 -0
  105. package/dist/local/stores/ObjectStore.js +393 -0
  106. package/dist/local/stores/ObjectStoreContract.d.ts +38 -0
  107. package/dist/local/stores/ObjectStoreContract.js +1 -0
  108. package/dist/local/stores/StoreManager.d.ts +114 -0
  109. package/dist/local/stores/StoreManager.js +304 -0
  110. package/dist/local/stores/SyncActionStore.d.ts +99 -0
  111. package/dist/local/stores/SyncActionStore.js +506 -0
  112. package/dist/local/stores/openIDBWithTimeout.d.ts +65 -0
  113. package/dist/local/stores/openIDBWithTimeout.js +153 -0
  114. package/dist/local/stores/persistenceCleanup.d.ts +7 -0
  115. package/dist/local/stores/persistenceCleanup.js +26 -0
  116. package/dist/local/stores/persistenceIdentity.d.ts +27 -0
  117. package/dist/local/stores/persistenceIdentity.js +38 -0
  118. package/dist/local/stores/syncAction.d.ts +26 -0
  119. package/dist/local/stores/syncAction.js +16 -0
  120. package/dist/local/stores/v1PersistenceDeletion.d.ts +8 -0
  121. package/dist/local/stores/v1PersistenceDeletion.js +16 -0
  122. package/dist/local/sync/BootstrapFetcher.d.ts +284 -0
  123. package/dist/local/sync/BootstrapFetcher.js +964 -0
  124. package/dist/local/sync/ConnectionManager.d.ts +8 -0
  125. package/dist/local/sync/ConnectionManager.js +8 -0
  126. package/dist/local/sync/OnDemandLoader.d.ts +231 -0
  127. package/dist/local/sync/OnDemandLoader.js +743 -0
  128. package/dist/local/sync/SubscriptionManager.d.ts +159 -0
  129. package/dist/local/sync/SubscriptionManager.js +243 -0
  130. package/dist/local/sync/SyncWebSocket.d.ts +173 -0
  131. package/dist/local/sync/SyncWebSocket.js +438 -0
  132. package/dist/local/sync/bootstrapApply.d.ts +73 -0
  133. package/dist/local/sync/bootstrapApply.js +73 -0
  134. package/dist/local/sync/commitFrames.d.ts +8 -0
  135. package/dist/local/sync/commitFrames.js +8 -0
  136. package/dist/local/sync/connectionManagerLifecycle.d.ts +23 -0
  137. package/dist/local/sync/connectionManagerLifecycle.js +126 -0
  138. package/dist/local/sync/contextPorts.d.ts +18 -0
  139. package/dist/local/sync/contextPorts.js +31 -0
  140. package/dist/local/sync/createClaimStream.d.ts +64 -0
  141. package/dist/local/sync/createClaimStream.js +475 -0
  142. package/dist/local/sync/createSnapshot.d.ts +29 -0
  143. package/dist/local/sync/createSnapshot.js +116 -0
  144. package/dist/local/sync/credentialLifecycle.d.ts +7 -0
  145. package/dist/local/sync/credentialLifecycle.js +7 -0
  146. package/dist/local/sync/deltaPipeline.d.ts +116 -0
  147. package/dist/local/sync/deltaPipeline.js +357 -0
  148. package/dist/local/sync/groupChange.d.ts +116 -0
  149. package/dist/local/sync/groupChange.js +244 -0
  150. package/dist/local/sync/initialize.d.ts +27 -0
  151. package/dist/local/sync/initialize.js +137 -0
  152. package/dist/local/sync/participants.d.ts +132 -0
  153. package/dist/local/sync/participants.js +342 -0
  154. package/dist/local/sync/persistedPrefix.d.ts +12 -0
  155. package/dist/local/sync/persistedPrefix.js +22 -0
  156. package/dist/local/sync/reconnect.d.ts +23 -0
  157. package/dist/local/sync/reconnect.js +55 -0
  158. package/dist/local/sync/schemaDrift.d.ts +55 -0
  159. package/dist/local/sync/schemaDrift.js +53 -0
  160. package/dist/local/sync/schemas.d.ts +71 -0
  161. package/dist/local/sync/schemas.js +94 -0
  162. package/dist/local/sync/socketEventWiring.d.ts +31 -0
  163. package/dist/local/sync/socketEventWiring.js +130 -0
  164. package/dist/local/sync/syncCursor.d.ts +40 -0
  165. package/dist/local/sync/syncCursor.js +55 -0
  166. package/dist/local/sync/syncPlan.d.ts +54 -0
  167. package/dist/local/sync/syncPlan.js +50 -0
  168. package/dist/local/sync/terminalSessionLifecycle.d.ts +20 -0
  169. package/dist/local/sync/terminalSessionLifecycle.js +50 -0
  170. package/dist/local/sync/wsFrameHandlers.d.ts +8 -0
  171. package/dist/local/sync/wsFrameHandlers.js +8 -0
  172. package/dist/local/transactions/databaseCommitOutbox.d.ts +15 -0
  173. package/dist/local/transactions/databaseCommitOutbox.js +16 -0
  174. package/dist/local/transactions/localMutation.d.ts +10 -0
  175. package/dist/local/transactions/localMutation.js +37 -0
  176. package/dist/local/transactions/mutations/MutationQueue.d.ts +511 -0
  177. package/dist/local/transactions/mutations/MutationQueue.js +1498 -0
  178. package/dist/local/transactions/mutations/MutationStore.d.ts +20 -0
  179. package/dist/local/transactions/mutations/MutationStore.js +53 -0
  180. package/dist/local/transactions/mutations/UnconfirmedWrites.d.ts +82 -0
  181. package/dist/local/transactions/mutations/UnconfirmedWrites.js +104 -0
  182. package/dist/local/transactions/mutations/batchProcessing.d.ts +64 -0
  183. package/dist/local/transactions/mutations/batchProcessing.js +349 -0
  184. package/dist/local/transactions/mutations/coalesceRules.d.ts +58 -0
  185. package/dist/local/transactions/mutations/coalesceRules.js +140 -0
  186. package/dist/local/transactions/mutations/commitApi.d.ts +19 -0
  187. package/dist/local/transactions/mutations/commitApi.js +74 -0
  188. package/dist/local/transactions/mutations/commitLane.d.ts +70 -0
  189. package/dist/local/transactions/mutations/commitLane.js +140 -0
  190. package/dist/local/transactions/mutations/commitLatency.d.ts +52 -0
  191. package/dist/local/transactions/mutations/commitLatency.js +130 -0
  192. package/dist/local/transactions/mutations/commitPayload.d.ts +165 -0
  193. package/dist/local/transactions/mutations/commitPayload.js +152 -0
  194. package/dist/local/transactions/mutations/commitTransport.d.ts +36 -0
  195. package/dist/local/transactions/mutations/commitTransport.js +104 -0
  196. package/dist/local/transactions/mutations/deltaConfirmation.d.ts +63 -0
  197. package/dist/local/transactions/mutations/deltaConfirmation.js +235 -0
  198. package/dist/local/transactions/mutations/durableCommitRestore.d.ts +17 -0
  199. package/dist/local/transactions/mutations/durableCommitRestore.js +95 -0
  200. package/dist/local/transactions/mutations/durableWriteStore.d.ts +14 -0
  201. package/dist/local/transactions/mutations/durableWriteStore.js +12 -0
  202. package/dist/local/transactions/mutations/executionSelection.d.ts +6 -0
  203. package/dist/local/transactions/mutations/executionSelection.js +42 -0
  204. package/dist/local/transactions/mutations/failureHandling.d.ts +16 -0
  205. package/dist/local/transactions/mutations/failureHandling.js +130 -0
  206. package/dist/local/transactions/mutations/failurePolicy.d.ts +13 -0
  207. package/dist/local/transactions/mutations/failurePolicy.js +48 -0
  208. package/dist/local/transactions/mutations/localMutation.d.ts +58 -0
  209. package/dist/local/transactions/mutations/localMutation.js +75 -0
  210. package/dist/local/transactions/mutations/modelOperations.d.ts +44 -0
  211. package/dist/local/transactions/mutations/modelOperations.js +144 -0
  212. package/dist/local/transactions/mutations/mutationPersistence.d.ts +25 -0
  213. package/dist/local/transactions/mutations/mutationPersistence.js +188 -0
  214. package/dist/local/transactions/mutations/pendingDrain.d.ts +33 -0
  215. package/dist/local/transactions/mutations/pendingDrain.js +112 -0
  216. package/dist/local/transactions/mutations/processingScheduler.d.ts +14 -0
  217. package/dist/local/transactions/mutations/processingScheduler.js +24 -0
  218. package/dist/local/transactions/mutations/queueCoalescing.d.ts +13 -0
  219. package/dist/local/transactions/mutations/queueCoalescing.js +35 -0
  220. package/dist/local/transactions/mutations/replayValidation.d.ts +187 -0
  221. package/dist/local/transactions/mutations/replayValidation.js +164 -0
  222. package/dist/local/transactions/reconnectDrain.d.ts +11 -0
  223. package/dist/local/transactions/reconnectDrain.js +13 -0
  224. package/dist/local/utils/mobxSetup.d.ts +53 -0
  225. package/dist/local/utils/mobxSetup.js +330 -0
  226. package/dist/local/views/QueryView.d.ts +79 -0
  227. package/dist/local/views/QueryView.js +218 -0
  228. package/dist/local/views/ViewRegistry.d.ts +20 -0
  229. package/dist/local/views/ViewRegistry.js +57 -0
  230. package/dist/local/views/incrementalView.d.ts +45 -0
  231. package/dist/local/views/incrementalView.js +69 -0
  232. package/dist/plugin.d.ts +285 -0
  233. package/dist/plugin.js +106 -0
  234. package/dist/presenceStream.d.ts +69 -0
  235. package/dist/presenceStream.js +200 -0
  236. package/dist/react/AbloProvider.d.ts +242 -0
  237. package/dist/react/AbloProvider.js +456 -0
  238. package/dist/react/ClientSideSuspense.d.ts +36 -0
  239. package/dist/react/ClientSideSuspense.js +17 -0
  240. package/dist/react/DefaultFallback.d.ts +24 -0
  241. package/dist/react/DefaultFallback.js +43 -0
  242. package/dist/react/context.d.ts +55 -0
  243. package/dist/react/context.js +29 -0
  244. package/dist/react/createAbloReact.d.ts +50 -0
  245. package/dist/react/createAbloReact.js +48 -0
  246. package/dist/react/internalContext.d.ts +33 -0
  247. package/dist/react/internalContext.js +3 -0
  248. package/dist/react/useAblo.d.ts +96 -0
  249. package/dist/react/useAblo.js +120 -0
  250. package/dist/react/useCurrentUserId.d.ts +2 -0
  251. package/dist/react/useCurrentUserId.js +12 -0
  252. package/dist/react/useErrorListener.d.ts +2 -0
  253. package/dist/react/useErrorListener.js +14 -0
  254. package/dist/react/useMutationFailureListener.d.ts +8 -0
  255. package/dist/react/useMutationFailureListener.js +19 -0
  256. package/dist/react/useMutators.d.ts +56 -0
  257. package/dist/react/useMutators.js +84 -0
  258. package/dist/react/useSyncStatus.d.ts +19 -0
  259. package/dist/react/useSyncStatus.js +37 -0
  260. package/dist/react/useUndoScope.d.ts +34 -0
  261. package/dist/react/useUndoScope.js +73 -0
  262. package/dist/react.d.ts +18 -0
  263. package/dist/react.js +14 -0
  264. package/dist/reactRuntime.d.ts +4 -0
  265. package/dist/reactRuntime.js +2 -0
  266. package/dist/surface.d.ts +36 -0
  267. package/dist/surface.js +77 -0
  268. package/dist/useReactive.d.ts +6 -0
  269. package/dist/useReactive.js +43 -0
  270. package/package.json +119 -0
  271. package/src/Ablo.ts +456 -0
  272. package/src/client.ts +374 -0
  273. package/src/core.ts +104 -0
  274. package/src/humans.ts +61 -0
  275. package/src/index.ts +40 -0
  276. package/src/local/BaseSyncedStore.ts +1991 -0
  277. package/src/local/Database.ts +2052 -0
  278. package/src/local/InstanceCache.ts +1503 -0
  279. package/src/local/LazyReferenceCollection.ts +563 -0
  280. package/src/local/Model.ts +1124 -0
  281. package/src/local/ModelRegistry.ts +762 -0
  282. package/src/local/NetworkMonitor.ts +88 -0
  283. package/src/local/RuntimeContext.ts +141 -0
  284. package/src/local/SyncClient.ts +2131 -0
  285. package/src/local/adapters/alwaysOnline.ts +20 -0
  286. package/src/local/adapters/inMemoryStorage.ts +141 -0
  287. package/src/local/client/clientPrelude.ts +112 -0
  288. package/src/local/client/consoleLogger.ts +60 -0
  289. package/src/local/client/createInternalComponents.ts +163 -0
  290. package/src/local/client/createModelProxy.ts +1390 -0
  291. package/src/local/client/modelRegistration.ts +350 -0
  292. package/src/local/client/options.ts +526 -0
  293. package/src/local/client/reactiveEngine.ts +935 -0
  294. package/src/local/client/resourceTypes.ts +37 -0
  295. package/src/local/client/schemaConfig.ts +194 -0
  296. package/src/local/client/storeCluster.ts +172 -0
  297. package/src/local/client/storeLifecycle.ts +343 -0
  298. package/src/local/client/validateAbloOptions.ts +95 -0
  299. package/src/local/client/wsMutationExecutor.ts +110 -0
  300. package/src/local/context.ts +96 -0
  301. package/src/local/coordination/ClaimLog.ts +39 -0
  302. package/src/local/interfaces/index.ts +468 -0
  303. package/src/local/localModelContract.ts +15 -0
  304. package/src/local/logPosition.ts +84 -0
  305. package/src/local/mutationPersistence.ts +7 -0
  306. package/src/local/mutators/RecordingMutation.ts +222 -0
  307. package/src/local/mutators/Transaction.ts +97 -0
  308. package/src/local/mutators/UndoManager.ts +741 -0
  309. package/src/local/mutators/defineMutators.ts +76 -0
  310. package/src/local/mutators/inverseOp.ts +83 -0
  311. package/src/local/mutators/mutateActions.ts +167 -0
  312. package/src/local/mutators/readerActions.ts +99 -0
  313. package/src/local/mutators/undoApply.ts +141 -0
  314. package/src/local/persistence.ts +16 -0
  315. package/src/local/query/QueryProcessor.ts +347 -0
  316. package/src/local/query/client.ts +197 -0
  317. package/src/local/query/types.ts +102 -0
  318. package/src/local/schema/serialize.ts +1 -0
  319. package/src/local/store/queryApi.ts +56 -0
  320. package/src/local/storeContract.ts +146 -0
  321. package/src/local/stores/DatabaseManager.ts +507 -0
  322. package/src/local/stores/ObjectStore.ts +449 -0
  323. package/src/local/stores/ObjectStoreContract.ts +48 -0
  324. package/src/local/stores/StoreManager.ts +388 -0
  325. package/src/local/stores/SyncActionStore.ts +579 -0
  326. package/src/local/stores/openIDBWithTimeout.ts +195 -0
  327. package/src/local/stores/persistenceCleanup.ts +43 -0
  328. package/src/local/stores/persistenceIdentity.ts +83 -0
  329. package/src/local/stores/syncAction.ts +21 -0
  330. package/src/local/stores/v1PersistenceDeletion.ts +21 -0
  331. package/src/local/sync/BootstrapFetcher.ts +1224 -0
  332. package/src/local/sync/ConnectionManager.ts +15 -0
  333. package/src/local/sync/OnDemandLoader.ts +927 -0
  334. package/src/local/sync/SubscriptionManager.ts +300 -0
  335. package/src/local/sync/SyncWebSocket.ts +584 -0
  336. package/src/local/sync/bootstrapApply.ts +130 -0
  337. package/src/local/sync/commitFrames.ts +16 -0
  338. package/src/local/sync/connectionManagerLifecycle.ts +158 -0
  339. package/src/local/sync/contextPorts.ts +37 -0
  340. package/src/local/sync/createClaimStream.ts +668 -0
  341. package/src/local/sync/createSnapshot.ts +160 -0
  342. package/src/local/sync/credentialLifecycle.ts +18 -0
  343. package/src/local/sync/deltaPipeline.ts +473 -0
  344. package/src/local/sync/groupChange.ts +343 -0
  345. package/src/local/sync/initialize.ts +205 -0
  346. package/src/local/sync/participants.ts +564 -0
  347. package/src/local/sync/persistedPrefix.ts +27 -0
  348. package/src/local/sync/reconnect.ts +88 -0
  349. package/src/local/sync/schemaDrift.ts +86 -0
  350. package/src/local/sync/schemas.ts +118 -0
  351. package/src/local/sync/socketEventWiring.ts +196 -0
  352. package/src/local/sync/syncCursor.ts +62 -0
  353. package/src/local/sync/syncPlan.ts +89 -0
  354. package/src/local/sync/terminalSessionLifecycle.ts +66 -0
  355. package/src/local/sync/wsFrameHandlers.ts +20 -0
  356. package/src/local/transactions/databaseCommitOutbox.ts +33 -0
  357. package/src/local/transactions/localMutation.ts +58 -0
  358. package/src/local/transactions/mutations/MutationQueue.ts +1998 -0
  359. package/src/local/transactions/mutations/MutationStore.ts +65 -0
  360. package/src/local/transactions/mutations/UnconfirmedWrites.ts +133 -0
  361. package/src/local/transactions/mutations/batchProcessing.ts +469 -0
  362. package/src/local/transactions/mutations/coalesceRules.ts +192 -0
  363. package/src/local/transactions/mutations/commitApi.ts +97 -0
  364. package/src/local/transactions/mutations/commitLane.ts +191 -0
  365. package/src/local/transactions/mutations/commitLatency.ts +164 -0
  366. package/src/local/transactions/mutations/commitPayload.ts +281 -0
  367. package/src/local/transactions/mutations/commitTransport.ts +174 -0
  368. package/src/local/transactions/mutations/deltaConfirmation.ts +298 -0
  369. package/src/local/transactions/mutations/durableCommitRestore.ts +128 -0
  370. package/src/local/transactions/mutations/durableWriteStore.ts +21 -0
  371. package/src/local/transactions/mutations/executionSelection.ts +42 -0
  372. package/src/local/transactions/mutations/failureHandling.ts +154 -0
  373. package/src/local/transactions/mutations/failurePolicy.ts +61 -0
  374. package/src/local/transactions/mutations/localMutation.ts +135 -0
  375. package/src/local/transactions/mutations/modelOperations.ts +210 -0
  376. package/src/local/transactions/mutations/mutationPersistence.ts +231 -0
  377. package/src/local/transactions/mutations/pendingDrain.ts +160 -0
  378. package/src/local/transactions/mutations/processingScheduler.ts +35 -0
  379. package/src/local/transactions/mutations/queueCoalescing.ts +45 -0
  380. package/src/local/transactions/mutations/replayValidation.ts +192 -0
  381. package/src/local/transactions/reconnectDrain.ts +24 -0
  382. package/src/local/utils/mobxSetup.ts +388 -0
  383. package/src/local/views/QueryView.ts +311 -0
  384. package/src/local/views/ViewRegistry.ts +61 -0
  385. package/src/local/views/incrementalView.ts +92 -0
  386. package/src/plugin.ts +396 -0
  387. package/src/presenceStream.ts +279 -0
  388. package/src/react/AbloProvider.tsx +744 -0
  389. package/src/react/ClientSideSuspense.tsx +57 -0
  390. package/src/react/DefaultFallback.tsx +60 -0
  391. package/src/react/context.ts +89 -0
  392. package/src/react/createAbloReact.ts +116 -0
  393. package/src/react/internalContext.ts +38 -0
  394. package/src/react/useAblo.ts +280 -0
  395. package/src/react/useCurrentUserId.ts +17 -0
  396. package/src/react/useErrorListener.ts +22 -0
  397. package/src/react/useMutationFailureListener.ts +34 -0
  398. package/src/react/useMutators.ts +184 -0
  399. package/src/react/useSyncStatus.ts +42 -0
  400. package/src/react/useUndoScope.ts +143 -0
  401. package/src/react.ts +69 -0
  402. package/src/reactRuntime.ts +10 -0
  403. package/src/surface.ts +106 -0
  404. package/src/useReactive.ts +51 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The vocabulary a local transaction uses on the wire. This module defines the
3
+ * {@link QueuedMutation} record and the helpers that turn a transaction into a
4
+ * wire-safe commit operation: schema-field projection
5
+ * ({@link projectCommitPayload}), write-option projection
6
+ * ({@link applyWriteOptions}), foreign-key-aware priority scoring
7
+ * ({@link computePriorityScore}), and the structural checks used to classify
8
+ * transport errors. Nothing here holds queue state or timers, so the batched
9
+ * queue path and the HTTP commit path share the same projection.
10
+ */
11
+ import { globalRuntime } from '../../context.js';
12
+ import { MutationOperationType } from '@abloatai/transaction/types';
13
+ import { snapshotJsonValue } from '@abloatai/transaction/utils/json';
14
+ /**
15
+ * Framework-internal keys added by `Model.toJSON()` that must never
16
+ * reach the wire. The server treats each top-level key as a target
17
+ * column, so shipping these would blow up the INSERT/UPDATE.
18
+ */
19
+ const FRAMEWORK_KEYS = new Set(['__class', '__typename', 'clientId', 'syncStatus']);
20
+ /**
21
+ * Projects a model's serialized data onto the fields declared in its schema and
22
+ * returns a wire-safe commit payload. It does two things:
23
+ *
24
+ * 1. Drops framework-internal keys (`__class`, `__typename`, `clientId`,
25
+ * `syncStatus`) and anything not declared on the model's schema.
26
+ * 2. Takes a fresh plain JSON snapshot of the projected values. JSON-typed
27
+ * fields stay objects rather than pre-serialized strings, while reactive
28
+ * proxies cannot leak into persistence or the wire payload.
29
+ *
30
+ * For updates (`dropUndefined: true`), `undefined` values are also removed so
31
+ * they are not written as `SET column = NULL` on the server.
32
+ *
33
+ * Field metadata comes from the model registry, populated when the schema is
34
+ * registered at initialization. If a model has no registered field metadata —
35
+ * for example a manually registered model — projection snapshots all serialized
36
+ * data apart from the framework keys.
37
+ */
38
+ export function projectCommitPayload(modelName, source, opts, runtime = globalRuntime) {
39
+ const metadata = runtime.getModelMetadata(modelName);
40
+ const fields = metadata?.fields;
41
+ const out = {};
42
+ if (!fields) {
43
+ // Unknown registration — strip framework keys and ship the rest.
44
+ for (const [k, v] of Object.entries(source)) {
45
+ if (FRAMEWORK_KEYS.has(k))
46
+ continue;
47
+ if (opts.dropUndefined && v === undefined)
48
+ continue;
49
+ out[k] = v;
50
+ }
51
+ return snapshotJsonValue(out, '$.input');
52
+ }
53
+ for (const key of Object.keys(fields)) {
54
+ if (!(key in source))
55
+ continue;
56
+ const value = source[key];
57
+ if (opts.dropUndefined && value === undefined)
58
+ continue;
59
+ // JSON-typed fields (stored as jsonb on the server) are sent as objects,
60
+ // not pre-serialized strings. Pre-stringifying here corrupts a round trip:
61
+ //
62
+ // 1. The client stringifies `position: {x, y}` to `'{"x":...}'`.
63
+ // 2. The server writes it to the jsonb column, parsing the string, fine.
64
+ // 3. The server's delta echoes the input, where `position` is still the
65
+ // string from step 1.
66
+ // 4. The client merges the delta and sets `model.position` to that string.
67
+ // 5. The next edit spreads the string character by character, producing a
68
+ // corrupted, index-keyed object.
69
+ // 6. That corrupt object lands in the next commit and is stored in jsonb.
70
+ //
71
+ // Sending objects avoids the mismatch: the value travels through the delta
72
+ // and the commit unchanged, and the Postgres driver serializes a JS object
73
+ // to jsonb correctly once the column is identified as jsonb.
74
+ out[key] = value;
75
+ }
76
+ return snapshotJsonValue(out, '$.input');
77
+ }
78
+ export const normalizeModelKey = (modelName) => modelName.replace('Model', '').toLowerCase();
79
+ export const stripModelSuffix = (modelName) => modelName.replace('Model', '');
80
+ /**
81
+ * Returns the priority score used to order create operations by foreign-key
82
+ * depth, so a parent row commits before its children.
83
+ *
84
+ * The score comes from a priority map on the runtime configuration, built once
85
+ * at initialization by walking the schema's `belongsTo` graph. No model names
86
+ * are hard-coded here, and an application can override specific priorities
87
+ * through `configOverrides.modelCreatePriority`.
88
+ *
89
+ * Non-create operations (update, delete, archive, unarchive) need no
90
+ * foreign-key ordering because the row already exists, so they all share the
91
+ * configured default non-create priority.
92
+ */
93
+ export const computePriorityScore = (type, modelName, runtime = globalRuntime) => {
94
+ const { modelCreatePriority, defaultCreatePriority, defaultNonCreatePriority } = runtime.config;
95
+ if (type !== 'create')
96
+ return defaultNonCreatePriority;
97
+ return modelCreatePriority.get(modelName) ?? defaultCreatePriority;
98
+ };
99
+ export const TX_TYPE_TO_MUTATION_OP = {
100
+ create: MutationOperationType.CREATE,
101
+ update: MutationOperationType.UPDATE,
102
+ delete: MutationOperationType.DELETE,
103
+ archive: MutationOperationType.ARCHIVE,
104
+ unarchive: MutationOperationType.UNARCHIVE,
105
+ };
106
+ export function hasStaleWriteOptions(options) {
107
+ return (options?.readAt !== undefined ||
108
+ options?.onStale !== undefined);
109
+ }
110
+ /** Options whose identity/audit semantics forbid merging two caller writes. */
111
+ export function hasCommitCoalescingBarrier(options) {
112
+ return (hasStaleWriteOptions(options) ||
113
+ typeof options?.idempotencyKey === 'string' ||
114
+ typeof options?.label === 'string');
115
+ }
116
+ /**
117
+ * Copies a transaction's `writeOptions` onto the wire operation. The
118
+ * stale-context guards (`readAt` and `onStale`) sit at the operation's root,
119
+ * while `idempotencyKey` and `label` go in its `options` slot — the
120
+ * `mutation_log` cache key and audit tag. This is the one place caller-supplied
121
+ * write options cross onto the wire.
122
+ */
123
+ export function applyWriteOptions(op, transaction) {
124
+ const operation = op;
125
+ const writeOptions = transaction.writeOptions;
126
+ if (!writeOptions)
127
+ return operation;
128
+ if (writeOptions.readAt !== undefined) {
129
+ operation.readAt = writeOptions.readAt;
130
+ }
131
+ if (writeOptions.onStale !== undefined) {
132
+ operation.onStale = writeOptions.onStale;
133
+ }
134
+ if (writeOptions.fenceToken !== undefined) {
135
+ operation.fenceToken = writeOptions.fenceToken;
136
+ }
137
+ if (writeOptions.idempotencyKey != null || writeOptions.label !== undefined) {
138
+ operation.options = {
139
+ ...(writeOptions.idempotencyKey != null
140
+ ? { idempotencyKey: writeOptions.idempotencyKey }
141
+ : {}),
142
+ ...(writeOptions.label !== undefined ? { label: writeOptions.label } : {}),
143
+ };
144
+ }
145
+ return operation;
146
+ }
147
+ export function asTransportError(value) {
148
+ return (value && typeof value === 'object' ? value : {});
149
+ }
150
+ export function extractStatusCode(error) {
151
+ return asTransportError(error).response?.status;
152
+ }
@@ -0,0 +1,36 @@
1
+ import type { RuntimeContext } from '../../RuntimeContext.js';
2
+ import type { MutationExecutor } from '../../interfaces/index.js';
3
+ import type { ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
4
+ import { type MutationCommitResult } from '@abloatai/transaction/wire/commit';
5
+ import { type CommitOutboxScope, type DurableCommitEnvelope, type DurableCommitOperation } from '@abloatai/transaction/transactions/settlement/commitEnvelope';
6
+ import type { DurableWriteStore } from './durableWriteStore.js';
7
+ export interface CommitTransportContext {
8
+ readonly runtime: RuntimeContext;
9
+ readonly config: {
10
+ enablePersistence: boolean;
11
+ commitDispatchTimeoutMs: number;
12
+ };
13
+ readonly commitOutbox: DurableWriteStore | null;
14
+ readonly commitOutboxScope: CommitOutboxScope | null;
15
+ readonly mutationExecutor: MutationExecutor;
16
+ readonly emitCommitLifecycle: (event: string, payload: object) => void;
17
+ }
18
+ export interface SealDurableCommitInput {
19
+ idempotencyKey: string;
20
+ origin: 'model_batch' | 'atomic_commit';
21
+ operations: DurableCommitOperationInput[];
22
+ sourceMutationIds?: string[];
23
+ commitOptions?: {
24
+ reads?: readonly ReadDependency[] | null;
25
+ track?: readonly TrackDependency[] | null;
26
+ };
27
+ createdAt: number;
28
+ sealedAt: number;
29
+ sequence?: number;
30
+ }
31
+ export type DurableCommitOperationInput = DurableCommitOperation;
32
+ export declare function sealDurableCommit(ctx: CommitTransportContext, input: SealDurableCommitInput, pendingMutationRecordId: (id: string) => string): Promise<DurableCommitEnvelope>;
33
+ export declare function removeDurableCommit(ctx: CommitTransportContext, idempotencyKey: string): Promise<void>;
34
+ export declare function persistDurableCommitAcceptance(ctx: CommitTransportContext, envelope: DurableCommitEnvelope, result: MutationCommitResult): Promise<DurableCommitEnvelope>;
35
+ export declare function parseMutationCommitResult(value: Awaited<ReturnType<MutationExecutor['commit']>>): MutationCommitResult;
36
+ export declare function dispatchCommitBounded(ctx: CommitTransportContext, ...args: Parameters<MutationExecutor['commit']>): ReturnType<MutationExecutor['commit']>;
@@ -0,0 +1,104 @@
1
+ import { AbloConnectionError, AbloError, AbloIdempotencyError, } from '@abloatai/transaction/errors';
2
+ import { mutationCommitResultSchema, } from '@abloatai/transaction/wire/commit';
3
+ import { createDurableCommitEnvelope, commitEnvelopeRecordId, durableCommitEnvelopeSchema, } from '@abloatai/transaction/transactions/settlement/commitEnvelope';
4
+ export async function sealDurableCommit(ctx, input, pendingMutationRecordId) {
5
+ const sourceMutationIds = [...new Set(input.sourceMutationIds ?? [])];
6
+ const envelope = createDurableCommitEnvelope({
7
+ idempotencyKey: input.idempotencyKey,
8
+ origin: input.origin,
9
+ operations: [...input.operations],
10
+ sourceMutationIds,
11
+ commitOptions: {
12
+ ...(input.commitOptions?.reads !== undefined
13
+ ? { reads: input.commitOptions.reads === null ? null : [...input.commitOptions.reads] }
14
+ : {}),
15
+ ...(input.commitOptions?.track !== undefined
16
+ ? { track: input.commitOptions.track === null ? null : [...input.commitOptions.track] }
17
+ : {}),
18
+ },
19
+ ...(ctx.commitOutboxScope ? { scope: ctx.commitOutboxScope } : {}),
20
+ createdAt: input.createdAt,
21
+ sealedAt: input.sealedAt,
22
+ sequence: input.sequence ?? input.sealedAt * 1_000,
23
+ });
24
+ if (ctx.config.enablePersistence && ctx.commitOutbox) {
25
+ try {
26
+ await ctx.commitOutbox.seal(envelope, sourceMutationIds.map(pendingMutationRecordId));
27
+ }
28
+ catch (cause) {
29
+ if (cause instanceof AbloError)
30
+ throw cause;
31
+ throw new AbloConnectionError('Could not persist the durable write before dispatch', {
32
+ code: 'db_not_opened',
33
+ cause,
34
+ });
35
+ }
36
+ ctx.emitCommitLifecycle('commit:envelope_persisted', {
37
+ idempotencyKey: envelope.idempotencyKey,
38
+ sourceMutationIds: envelope.sourceMutationIds,
39
+ });
40
+ }
41
+ return envelope;
42
+ }
43
+ export async function removeDurableCommit(ctx, idempotencyKey) {
44
+ if (!ctx.config.enablePersistence || !ctx.commitOutbox)
45
+ return;
46
+ try {
47
+ await ctx.commitOutbox.remove(commitEnvelopeRecordId(idempotencyKey));
48
+ }
49
+ catch (error) {
50
+ ctx.runtime.logger.debug('[MutationQueue] Durable-write cleanup deferred', {
51
+ idempotencyKey,
52
+ error: error instanceof Error ? error.message : String(error),
53
+ });
54
+ }
55
+ }
56
+ export async function persistDurableCommitAcceptance(ctx, envelope, result) {
57
+ if (result.status !== 'queued')
58
+ return envelope;
59
+ const correlationId = result.correlationId;
60
+ if (!correlationId) {
61
+ throw new AbloConnectionError('The source accepted the commit without durable correlation evidence.', {
62
+ code: 'commit_no_result',
63
+ });
64
+ }
65
+ if (envelope.correlationId !== undefined && envelope.correlationId !== correlationId) {
66
+ throw new AbloIdempotencyError('The same commit replay returned a different source correlation.', {
67
+ code: 'idempotency_conflict',
68
+ });
69
+ }
70
+ if (envelope.acceptedAt !== undefined)
71
+ return envelope;
72
+ const accepted = durableCommitEnvelopeSchema.parse({
73
+ ...envelope,
74
+ acceptedAt: Math.max(Date.now(), envelope.sealedAt),
75
+ correlationId,
76
+ });
77
+ if (ctx.config.enablePersistence && ctx.commitOutbox) {
78
+ try {
79
+ await ctx.commitOutbox.seal(accepted, []);
80
+ }
81
+ catch (cause) {
82
+ if (cause instanceof AbloError)
83
+ throw cause;
84
+ throw new AbloConnectionError('The source accepted the commit, but that acceptance could not be persisted locally.', { code: 'db_not_opened', cause });
85
+ }
86
+ }
87
+ return accepted;
88
+ }
89
+ export function parseMutationCommitResult(value) {
90
+ const parsed = mutationCommitResultSchema.safeParse(value);
91
+ if (parsed.success)
92
+ return parsed.data;
93
+ throw new AbloConnectionError('The mutation transport returned an invalid commit receipt; its outcome remains pending and is safe to retry.', { code: 'commit_no_result', cause: parsed.error });
94
+ }
95
+ export function dispatchCommitBounded(ctx, ...args) {
96
+ const dispatched = ctx.mutationExecutor.commit(...args);
97
+ const timeoutMs = ctx.config.commitDispatchTimeoutMs;
98
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
99
+ return dispatched;
100
+ return new Promise((resolve, reject) => {
101
+ const timer = setTimeout(() => reject(new AbloConnectionError('The mutation transport did not acknowledge the commit in time; its outcome remains pending and is safe to retry.', { code: 'commit_no_result' })), timeoutMs);
102
+ dispatched.then((value) => { clearTimeout(timer); resolve(value); }, (error) => { clearTimeout(timer); reject(error instanceof Error ? error : new Error(String(error))); });
103
+ });
104
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Tracks whether the confirming delta for a write has arrived. It holds the
3
+ * acknowledgement watermark (through the shared {@link LogPosition}), the
4
+ * per-transaction confirmation timeouts, and the retry-with-backoff and
5
+ * reconciliation policy for transactions in the `awaiting_delta` status. It
6
+ * reaches back to {@link MutationQueue} only through the small
7
+ * {@link DeltaConfirmationContext} interface, not the queue class itself, so it
8
+ * has no cyclic dependency and can be tested on its own.
9
+ */
10
+ import type { RuntimeContext } from '../../RuntimeContext.js';
11
+ import type { LogPositionPort } from '../../logPosition.js';
12
+ import type { QueuedMutation } from './commitPayload.js';
13
+ /**
14
+ * The subset of {@link MutationQueue} that the confirmation tracker needs:
15
+ * store lookups and status changes, removing optimistic entries once a write
16
+ * confirms, the queue's event emitter (`transaction:completed`,
17
+ * `reconciliation:needed`, and so on), a connection check (so timeouts
18
+ * re-schedule instead of escalating while offline), and the shared client
19
+ * position (`noteAck` advances the acknowledgement cursor; diagnostics read the
20
+ * applied cursor).
21
+ */
22
+ export interface DeltaConfirmationContext {
23
+ store: {
24
+ get(id: string): QueuedMutation | undefined;
25
+ getByStatus(status: QueuedMutation['status']): QueuedMutation[];
26
+ updateStatus(id: string, status: QueuedMutation['status']): void;
27
+ };
28
+ optimisticUpdates: {
29
+ delete(id: string): boolean;
30
+ };
31
+ emit(event: string, payload?: unknown): void;
32
+ isConnected(): boolean;
33
+ position: LogPositionPort;
34
+ /** The owning client's runtime. Defaults to the module-global bridge. */
35
+ runtime?: RuntimeContext;
36
+ }
37
+ export declare class DeltaConfirmationTracker {
38
+ private readonly ctx;
39
+ private static readonly DELTA_MAX_RETRIES;
40
+ private static readonly DELTA_MAX_TIMEOUT_MS;
41
+ private deltaConfirmationTimeouts;
42
+ private deltaConfirmationRetries;
43
+ private readonly runtime;
44
+ constructor(ctx: DeltaConfirmationContext);
45
+ /** Applied-cursor alias, kept so the read sites below stay legible. */
46
+ private get lastSeenSyncId();
47
+ private matchesSourceEcho;
48
+ noteAck(lastSyncId: number | undefined): void;
49
+ /**
50
+ * Confirms every awaiting transaction whose sync-id threshold this delta
51
+ * meets or exceeds.
52
+ * @param syncId - The sync id of the received delta.
53
+ */
54
+ onDeltaReceived(syncId: number, correlationId?: string): void;
55
+ scheduleDeltaConfirmationTimeout(tx: QueuedMutation, timeoutMs: number): void;
56
+ private cancelDeltaConfirmationTimeout;
57
+ /**
58
+ * Clears every armed confirmation timer, one per in-flight transaction.
59
+ * {@link MutationQueue.dispose} calls this; without it a disposed queue
60
+ * would keep the process alive and fire callbacks against a cleared store.
61
+ */
62
+ dispose(): void;
63
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Tracks whether the confirming delta for a write has arrived. It holds the
3
+ * acknowledgement watermark (through the shared {@link LogPosition}), the
4
+ * per-transaction confirmation timeouts, and the retry-with-backoff and
5
+ * reconciliation policy for transactions in the `awaiting_delta` status. It
6
+ * reaches back to {@link MutationQueue} only through the small
7
+ * {@link DeltaConfirmationContext} interface, not the queue class itself, so it
8
+ * has no cyclic dependency and can be tested on its own.
9
+ */
10
+ import { globalRuntime } from '../../context.js';
11
+ export class DeltaConfirmationTracker {
12
+ ctx;
13
+ // Retry configuration for delta confirmation, using exponential backoff.
14
+ // Maximum retries before requesting a full reconciliation.
15
+ static DELTA_MAX_RETRIES = 5;
16
+ // Upper bound on the backoff timeout.
17
+ static DELTA_MAX_TIMEOUT_MS = 120_000;
18
+ // Pending confirmation timeouts for transactions awaiting their delta. On
19
+ // timeout the tracker retries with backoff rather than rolling back.
20
+ deltaConfirmationTimeouts = new Map();
21
+ // Track retry attempts per transaction for exponential backoff
22
+ deltaConfirmationRetries = new Map();
23
+ runtime;
24
+ constructor(ctx) {
25
+ this.ctx = ctx;
26
+ this.runtime = ctx.runtime ?? globalRuntime;
27
+ }
28
+ /** Applied-cursor alias, kept so the read sites below stay legible. */
29
+ get lastSeenSyncId() {
30
+ return this.ctx.position.applied;
31
+ }
32
+ matchesSourceEcho(tx, correlationId) {
33
+ return (correlationId !== undefined &&
34
+ tx.correlationId !== undefined &&
35
+ correlationId === tx.correlationId);
36
+ }
37
+ noteAck(lastSyncId) {
38
+ this.ctx.position.noteAck(lastSyncId);
39
+ }
40
+ /**
41
+ * Confirms every awaiting transaction whose sync-id threshold this delta
42
+ * meets or exceeds.
43
+ * @param syncId - The sync id of the received delta.
44
+ */
45
+ onDeltaReceived(syncId, correlationId) {
46
+ // The cursor advances where the delta is applied (the store calls
47
+ // position.advanceApplied / advancePersisted); this hook only resolves
48
+ // confirmation thresholds against the incoming id.
49
+ const awaitingTxs = this.ctx.store.getByStatus('awaiting_delta');
50
+ const executingTxs = this.ctx.store.getByStatus('executing');
51
+ // Debug: Show state when delta arrives
52
+ if (awaitingTxs.length > 0 || executingTxs.length > 0) {
53
+ this.runtime.logger.debug('tx:delta_received', {
54
+ syncId,
55
+ lastSeenSyncId: this.lastSeenSyncId,
56
+ awaitingCount: awaitingTxs.length,
57
+ executingCount: executingTxs.length,
58
+ awaitingThresholds: awaitingTxs.map((tx) => ({
59
+ txId: tx.id.slice(0, 8),
60
+ model: tx.modelName,
61
+ needed: tx.syncIdNeededForCompletion,
62
+ requiresCorrelatedDelta: tx.requiresCorrelatedDelta === true,
63
+ willConfirm: tx.requiresCorrelatedDelta === true
64
+ ? this.matchesSourceEcho(tx, correlationId)
65
+ : tx.syncIdNeededForCompletion !== undefined &&
66
+ syncId >= tx.syncIdNeededForCompletion,
67
+ })),
68
+ });
69
+ }
70
+ // Fast path: no awaiting transactions
71
+ if (awaitingTxs.length === 0)
72
+ return;
73
+ let confirmedCount = 0;
74
+ for (const tx of awaitingTxs) {
75
+ // Queued forward receipts deliberately have no watermark. They confirm
76
+ // only when the authoritative source echoes the receipt's opaque,
77
+ // authenticated-scope batch identity; the legacy anomaly path continues
78
+ // to use its sync-id threshold.
79
+ const confirmedByCorrelation = tx.requiresCorrelatedDelta === true &&
80
+ this.matchesSourceEcho(tx, correlationId);
81
+ const confirmedByThreshold = tx.requiresCorrelatedDelta !== true &&
82
+ tx.syncIdNeededForCompletion !== undefined &&
83
+ syncId >= tx.syncIdNeededForCompletion;
84
+ if (confirmedByCorrelation || confirmedByThreshold) {
85
+ this.cancelDeltaConfirmationTimeout(tx.id);
86
+ this.ctx.store.updateStatus(tx.id, 'completed');
87
+ this.ctx.emit('transaction:completed', tx);
88
+ this.ctx.emit(`transaction:completed:${tx.id}`, tx);
89
+ this.ctx.optimisticUpdates.delete(tx.id);
90
+ confirmedCount++;
91
+ this.runtime.logger.debug('tx:confirm_via_delta', {
92
+ txId: tx.id.slice(0, 8),
93
+ model: tx.modelName,
94
+ neededSyncId: tx.syncIdNeededForCompletion,
95
+ receivedSyncId: syncId,
96
+ confirmation: confirmedByCorrelation ? 'source_correlation' : 'sync_id',
97
+ });
98
+ }
99
+ }
100
+ // Log batch summary only if we confirmed something
101
+ if (confirmedCount > 0) {
102
+ // Leave a breadcrumb when transactions confirm.
103
+ this.runtime.observability.breadcrumb('Transactions confirmed via delta', 'sync.transaction', 'info', {
104
+ count: confirmedCount,
105
+ syncId,
106
+ remainingAwaiting: awaitingTxs.length - confirmedCount,
107
+ });
108
+ }
109
+ }
110
+ // Schedule the confirmation wait for a transaction. On timeout the tracker
111
+ // retries with exponential backoff and requests reconciliation to catch up on
112
+ // missed deltas, rather than rolling back, which would discard state the
113
+ // server has already confirmed. A rollback happens only on an explicit server
114
+ // rejection, never on a timeout.
115
+ scheduleDeltaConfirmationTimeout(tx, timeoutMs) {
116
+ // Cancel any existing timeout for this transaction
117
+ this.cancelDeltaConfirmationTimeout(tx.id);
118
+ // Deliberately not an async callback: the body is fully synchronous, and
119
+ // `setTimeout(async …)` would turn any throw into an unhandled promise
120
+ // rejection instead of a catchable synchronous error.
121
+ const timeoutHandle = setTimeout(() => {
122
+ const currentTx = this.ctx.store.get(tx.id);
123
+ if (currentTx?.status !== 'awaiting_delta') {
124
+ this.deltaConfirmationRetries.delete(tx.id);
125
+ return; // Already confirmed or failed
126
+ }
127
+ // If disconnected, re-schedule with same timeout (no backoff while offline)
128
+ if (!this.ctx.isConnected()) {
129
+ // Self-healing: re-schedule the confirmation wait while offline, no
130
+ // consumer action needed → debug.
131
+ this.runtime.logger.debug('[MutationQueue] Timeout fired while disconnected - re-scheduling', {
132
+ txId: tx.id.slice(0, 8),
133
+ model: tx.modelName,
134
+ });
135
+ this.deltaConfirmationTimeouts.delete(tx.id);
136
+ this.scheduleDeltaConfirmationTimeout(tx, timeoutMs);
137
+ return;
138
+ }
139
+ const retryCount = this.deltaConfirmationRetries.get(tx.id) ?? 0;
140
+ this.runtime.observability.captureReconciliation({
141
+ reason: 'delta_timeout',
142
+ model: tx.modelName,
143
+ modelId: tx.modelId,
144
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
145
+ lastSeenSyncId: this.lastSeenSyncId,
146
+ retryCount,
147
+ connectionState: this.ctx.isConnected() ? 'connected' : 'disconnected',
148
+ });
149
+ if (retryCount < DeltaConfirmationTracker.DELTA_MAX_RETRIES) {
150
+ // Retry: request reconciliation and re-schedule with exponential
151
+ // backoff. The server has already committed the mutation; only the
152
+ // delta is outstanding.
153
+ this.deltaConfirmationRetries.set(tx.id, retryCount + 1);
154
+ this.deltaConfirmationTimeouts.delete(tx.id);
155
+ // Exponential backoff: 30s → 60s → 120s → 120s → 120s (capped)
156
+ const nextTimeout = Math.min(timeoutMs * 2, DeltaConfirmationTracker.DELTA_MAX_TIMEOUT_MS);
157
+ // Request reconciliation so the client can cycle the connection and
158
+ // catch up on missed deltas from the server.
159
+ this.ctx.emit('reconciliation:needed', {
160
+ reason: 'delta_confirmation_timeout',
161
+ txId: tx.id,
162
+ model: tx.modelName,
163
+ modelId: tx.modelId,
164
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
165
+ lastSeenSyncId: this.lastSeenSyncId,
166
+ retryCount: retryCount + 1,
167
+ });
168
+ // Self-healing retry with backoff — the server already committed; we're
169
+ // just waiting on the delta. No consumer action → debug.
170
+ this.runtime.logger.debug('[MutationQueue] Re-scheduling with backoff', {
171
+ txId: tx.id.slice(0, 8),
172
+ model: tx.modelName,
173
+ nextTimeoutMs: nextTimeout,
174
+ retry: retryCount + 1,
175
+ });
176
+ this.scheduleDeltaConfirmationTimeout(tx, nextTimeout);
177
+ }
178
+ else {
179
+ // Retries exhausted: persist the awaiting state instead of rolling back.
180
+ // The commit succeeded on the server, so the data exists there. Saving
181
+ // the awaiting state lets it survive the page closing; on the next
182
+ // session, reconnecting and catching up on deltas will confirm it.
183
+ this.deltaConfirmationRetries.delete(tx.id);
184
+ this.deltaConfirmationTimeouts.delete(tx.id);
185
+ this.runtime.observability.captureDeltaRetryExhausted({
186
+ txId: tx.id,
187
+ model: tx.modelName,
188
+ modelId: tx.modelId,
189
+ retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
190
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
191
+ });
192
+ // Emit the persist event; the client performs the write to local storage.
193
+ this.ctx.emit('transaction:persist_awaiting', {
194
+ txId: tx.id,
195
+ model: tx.modelName,
196
+ modelId: tx.modelId,
197
+ operationType: tx.type,
198
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
199
+ });
200
+ // Also request one final reconciliation cycle
201
+ this.ctx.emit('reconciliation:needed', {
202
+ reason: 'delta_retries_exhausted',
203
+ txId: tx.id,
204
+ model: tx.modelName,
205
+ modelId: tx.modelId,
206
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
207
+ lastSeenSyncId: this.lastSeenSyncId,
208
+ retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
209
+ });
210
+ }
211
+ }, timeoutMs);
212
+ this.deltaConfirmationTimeouts.set(tx.id, timeoutHandle);
213
+ }
214
+ // Cancel a pending delta confirmation timeout and clean up retry tracking
215
+ cancelDeltaConfirmationTimeout(id) {
216
+ const timeoutHandle = this.deltaConfirmationTimeouts.get(id);
217
+ if (timeoutHandle) {
218
+ clearTimeout(timeoutHandle);
219
+ this.deltaConfirmationTimeouts.delete(id);
220
+ }
221
+ this.deltaConfirmationRetries.delete(id);
222
+ }
223
+ /**
224
+ * Clears every armed confirmation timer, one per in-flight transaction.
225
+ * {@link MutationQueue.dispose} calls this; without it a disposed queue
226
+ * would keep the process alive and fire callbacks against a cleared store.
227
+ */
228
+ dispose() {
229
+ for (const timeoutHandle of this.deltaConfirmationTimeouts.values()) {
230
+ clearTimeout(timeoutHandle);
231
+ }
232
+ this.deltaConfirmationTimeouts.clear();
233
+ this.deltaConfirmationRetries.clear();
234
+ }
235
+ }
@@ -0,0 +1,17 @@
1
+ import type { RuntimeContext } from '../../RuntimeContext.js';
2
+ import type { CommitTransaction } from './commitLane.js';
3
+ import type { CommitOutboxScope } from '@abloatai/transaction/transactions/settlement/commitEnvelope';
4
+ import type { DurableWriteStore } from './durableWriteStore.js';
5
+ export interface DurableCommitRestoreContext {
6
+ readonly config: {
7
+ enablePersistence: boolean;
8
+ };
9
+ readonly commitOutbox: DurableWriteStore | null;
10
+ readonly commitOutboxScope: CommitOutboxScope | null;
11
+ readonly commitStore: Map<string, CommitTransaction>;
12
+ readonly commitLane: CommitTransaction[];
13
+ readonly runtime: RuntimeContext;
14
+ readonly processCommitLane: () => Promise<void>;
15
+ readonly durableReplayWindowMs: number;
16
+ }
17
+ export declare function restoreDurableCommits(ctx: DurableCommitRestoreContext): Promise<Set<string>>;