@abloatai/ablo 0.26.0 → 0.27.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 (398) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +101 -85
  3. package/dist/BaseSyncedStore.d.ts +85 -88
  4. package/dist/BaseSyncedStore.js +131 -147
  5. package/dist/Database.d.ts +54 -68
  6. package/dist/Database.js +97 -113
  7. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  8. package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
  9. package/dist/LazyReferenceCollection.d.ts +11 -15
  10. package/dist/LazyReferenceCollection.js +12 -16
  11. package/dist/Model.d.ts +37 -52
  12. package/dist/Model.js +46 -61
  13. package/dist/ModelRegistry.d.ts +21 -19
  14. package/dist/ModelRegistry.js +23 -27
  15. package/dist/NetworkMonitor.d.ts +5 -6
  16. package/dist/NetworkMonitor.js +5 -6
  17. package/dist/SyncClient.d.ts +112 -112
  18. package/dist/SyncClient.js +165 -172
  19. package/dist/adapters/alwaysOnline.d.ts +6 -8
  20. package/dist/adapters/alwaysOnline.js +6 -8
  21. package/dist/adapters/inMemoryStorage.d.ts +9 -9
  22. package/dist/adapters/inMemoryStorage.js +9 -9
  23. package/dist/agent/Agent.d.ts +27 -32
  24. package/dist/agent/Agent.js +18 -19
  25. package/dist/agent/index.d.ts +4 -4
  26. package/dist/agent/index.js +5 -5
  27. package/dist/agent/session.d.ts +47 -44
  28. package/dist/agent/session.js +37 -48
  29. package/dist/agent/types.d.ts +26 -31
  30. package/dist/agent/types.js +6 -7
  31. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  32. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  33. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  34. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
  35. package/dist/ai-sdk/index.d.ts +25 -22
  36. package/dist/ai-sdk/index.js +25 -22
  37. package/dist/ai-sdk/wrap.d.ts +6 -7
  38. package/dist/ai-sdk/wrap.js +1 -1
  39. package/dist/auth/credentialPolicy.d.ts +69 -74
  40. package/dist/auth/credentialPolicy.js +51 -56
  41. package/dist/auth/credentialSource.d.ts +6 -5
  42. package/dist/auth/credentialSource.js +9 -10
  43. package/dist/auth/index.d.ts +59 -58
  44. package/dist/auth/index.js +31 -37
  45. package/dist/auth/schemas.d.ts +5 -4
  46. package/dist/auth/schemas.js +5 -4
  47. package/dist/batching/index.d.ts +19 -21
  48. package/dist/batching/index.js +14 -17
  49. package/dist/cli.cjs +167 -119
  50. package/dist/client/Ablo.d.ts +73 -73
  51. package/dist/client/Ablo.js +125 -160
  52. package/dist/client/ApiClient.d.ts +30 -19
  53. package/dist/client/ApiClient.js +133 -38
  54. package/dist/client/auth.d.ts +47 -47
  55. package/dist/client/auth.js +108 -117
  56. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  57. package/dist/client/claimHeartbeatLoop.js +88 -0
  58. package/dist/client/consoleLogger.d.ts +5 -6
  59. package/dist/client/consoleLogger.js +5 -6
  60. package/dist/client/createInternalComponents.d.ts +14 -17
  61. package/dist/client/createInternalComponents.js +25 -30
  62. package/dist/client/createModelProxy.d.ts +130 -120
  63. package/dist/client/createModelProxy.js +152 -122
  64. package/dist/client/credentialEndpoint.d.ts +40 -42
  65. package/dist/client/credentialEndpoint.js +35 -36
  66. package/dist/client/functionalUpdate.d.ts +29 -27
  67. package/dist/client/functionalUpdate.js +21 -21
  68. package/dist/client/hostedEndpoints.d.ts +9 -12
  69. package/dist/client/hostedEndpoints.js +9 -12
  70. package/dist/client/httpClient.d.ts +57 -53
  71. package/dist/client/httpClient.js +29 -31
  72. package/dist/client/identity.d.ts +15 -20
  73. package/dist/client/identity.js +47 -58
  74. package/dist/client/modelRegistration.d.ts +5 -9
  75. package/dist/client/modelRegistration.js +67 -87
  76. package/dist/client/options.d.ts +134 -157
  77. package/dist/client/options.js +3 -7
  78. package/dist/client/registerDataSource.d.ts +9 -9
  79. package/dist/client/registerDataSource.js +15 -16
  80. package/dist/client/resourceTypes.d.ts +64 -75
  81. package/dist/client/resourceTypes.js +4 -10
  82. package/dist/client/schemaConfig.d.ts +31 -43
  83. package/dist/client/schemaConfig.js +38 -50
  84. package/dist/client/sessionMint.d.ts +16 -12
  85. package/dist/client/sessionMint.js +26 -31
  86. package/dist/client/validateAbloOptions.d.ts +12 -14
  87. package/dist/client/validateAbloOptions.js +8 -9
  88. package/dist/client/writeOptionsSchema.d.ts +18 -16
  89. package/dist/client/writeOptionsSchema.js +23 -20
  90. package/dist/client/wsMutationExecutor.d.ts +15 -20
  91. package/dist/client/wsMutationExecutor.js +17 -23
  92. package/dist/context.d.ts +6 -4
  93. package/dist/context.js +6 -4
  94. package/dist/coordination/index.d.ts +10 -8
  95. package/dist/coordination/index.js +14 -12
  96. package/dist/coordination/schema.d.ts +176 -128
  97. package/dist/coordination/schema.js +197 -133
  98. package/dist/coordination/trace.d.ts +9 -10
  99. package/dist/coordination/trace.js +13 -14
  100. package/dist/core/DatabaseManager.d.ts +5 -7
  101. package/dist/core/DatabaseManager.js +15 -19
  102. package/dist/core/QueryProcessor.d.ts +7 -9
  103. package/dist/core/QueryProcessor.js +22 -28
  104. package/dist/core/QueryView.d.ts +8 -8
  105. package/dist/core/QueryView.js +2 -2
  106. package/dist/core/StoreManager.d.ts +12 -14
  107. package/dist/core/StoreManager.js +21 -24
  108. package/dist/core/ViewRegistry.d.ts +5 -5
  109. package/dist/core/ViewRegistry.js +4 -4
  110. package/dist/core/index.d.ts +17 -12
  111. package/dist/core/index.js +32 -26
  112. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  113. package/dist/core/openIDBWithTimeout.js +42 -43
  114. package/dist/core/queryUtils.d.ts +45 -0
  115. package/dist/core/queryUtils.js +69 -0
  116. package/dist/core/storeContract.d.ts +63 -61
  117. package/dist/core/storeContract.js +8 -12
  118. package/dist/environment.d.ts +28 -0
  119. package/dist/environment.js +21 -0
  120. package/dist/errorCodes.d.ts +107 -99
  121. package/dist/errorCodes.js +131 -132
  122. package/dist/errors.d.ts +160 -166
  123. package/dist/errors.js +155 -158
  124. package/dist/index.d.ts +30 -27
  125. package/dist/index.js +89 -86
  126. package/dist/interfaces/index.d.ts +102 -113
  127. package/dist/interfaces/index.js +5 -4
  128. package/dist/keys/index.d.ts +27 -29
  129. package/dist/keys/index.js +41 -40
  130. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  131. package/dist/mutators/RecordingTransaction.js +31 -37
  132. package/dist/mutators/Transaction.d.ts +18 -26
  133. package/dist/mutators/Transaction.js +14 -20
  134. package/dist/mutators/UndoManager.d.ts +122 -131
  135. package/dist/mutators/UndoManager.js +145 -156
  136. package/dist/mutators/defineMutators.d.ts +23 -34
  137. package/dist/mutators/defineMutators.js +14 -20
  138. package/dist/mutators/inverseOp.d.ts +12 -15
  139. package/dist/mutators/inverseOp.js +12 -15
  140. package/dist/mutators/mutateActions.d.ts +10 -9
  141. package/dist/mutators/mutateActions.js +1 -1
  142. package/dist/mutators/readerActions.d.ts +9 -8
  143. package/dist/mutators/readerActions.js +2 -2
  144. package/dist/mutators/undoApply.d.ts +31 -27
  145. package/dist/mutators/undoApply.js +26 -24
  146. package/dist/policy/index.d.ts +5 -3
  147. package/dist/policy/index.js +5 -3
  148. package/dist/policy/types.d.ts +104 -100
  149. package/dist/policy/types.js +67 -66
  150. package/dist/query/client.d.ts +28 -23
  151. package/dist/query/client.js +45 -43
  152. package/dist/query/types.d.ts +37 -60
  153. package/dist/query/types.js +13 -33
  154. package/dist/react/AbloProvider.d.ts +1 -1
  155. package/dist/react/AbloProvider.js +2 -2
  156. package/dist/react/context.d.ts +25 -28
  157. package/dist/react/context.js +9 -10
  158. package/dist/react/index.d.ts +41 -42
  159. package/dist/react/index.js +37 -38
  160. package/dist/react/internalContext.d.ts +17 -19
  161. package/dist/react/useAblo.d.ts +23 -22
  162. package/dist/react/useAblo.js +16 -14
  163. package/dist/react/useCurrentUserId.d.ts +8 -7
  164. package/dist/react/useCurrentUserId.js +8 -7
  165. package/dist/react/useErrorListener.d.ts +7 -7
  166. package/dist/react/useErrorListener.js +10 -11
  167. package/dist/react/useMutationFailureListener.d.ts +8 -8
  168. package/dist/react/useMutationFailureListener.js +8 -8
  169. package/dist/react/useMutators.d.ts +11 -11
  170. package/dist/react/useMutators.js +3 -3
  171. package/dist/react/useReactive.js +2 -2
  172. package/dist/react/useSyncStatus.d.ts +4 -6
  173. package/dist/react/useUndoScope.d.ts +7 -9
  174. package/dist/react/useUndoScope.js +1 -1
  175. package/dist/schema/coordination.d.ts +21 -25
  176. package/dist/schema/coordination.js +21 -25
  177. package/dist/schema/ddl.d.ts +43 -39
  178. package/dist/schema/ddl.js +75 -68
  179. package/dist/schema/ddlLock.d.ts +20 -24
  180. package/dist/schema/ddlLock.js +18 -23
  181. package/dist/schema/diff.d.ts +99 -61
  182. package/dist/schema/diff.js +43 -34
  183. package/dist/schema/field.d.ts +37 -42
  184. package/dist/schema/field.js +35 -48
  185. package/dist/schema/generate.d.ts +12 -12
  186. package/dist/schema/generate.js +12 -12
  187. package/dist/schema/index.d.ts +2 -2
  188. package/dist/schema/index.js +21 -23
  189. package/dist/schema/model.d.ts +118 -143
  190. package/dist/schema/model.js +22 -33
  191. package/dist/schema/openapi.d.ts +10 -9
  192. package/dist/schema/openapi.js +5 -3
  193. package/dist/schema/queries.d.ts +29 -31
  194. package/dist/schema/queries.js +23 -25
  195. package/dist/schema/relation.d.ts +89 -99
  196. package/dist/schema/relation.js +13 -13
  197. package/dist/schema/residency.d.ts +16 -13
  198. package/dist/schema/residency.js +16 -13
  199. package/dist/schema/roles.d.ts +36 -43
  200. package/dist/schema/roles.js +31 -37
  201. package/dist/schema/schema.d.ts +33 -42
  202. package/dist/schema/schema.js +31 -32
  203. package/dist/schema/select.d.ts +13 -13
  204. package/dist/schema/select.js +13 -13
  205. package/dist/schema/serialize.d.ts +28 -31
  206. package/dist/schema/serialize.js +27 -31
  207. package/dist/schema/sugar.d.ts +17 -32
  208. package/dist/schema/sugar.js +14 -29
  209. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
  210. package/dist/schema/syncDeltaRow.js +89 -0
  211. package/dist/schema/tenancy.d.ts +44 -46
  212. package/dist/schema/tenancy.js +46 -48
  213. package/dist/server/adapter.d.ts +58 -58
  214. package/dist/server/adapter.js +13 -14
  215. package/dist/server/commit.d.ts +60 -64
  216. package/dist/server/index.d.ts +9 -10
  217. package/dist/server/index.js +1 -1
  218. package/dist/server/readConfig.d.ts +70 -0
  219. package/dist/server/readConfig.js +8 -0
  220. package/dist/server/storageMode.d.ts +23 -0
  221. package/dist/server/storageMode.js +17 -0
  222. package/dist/source/adapter.d.ts +30 -25
  223. package/dist/source/adapter.js +10 -10
  224. package/dist/source/adapters/drizzle.d.ts +28 -23
  225. package/dist/source/adapters/drizzle.js +30 -25
  226. package/dist/source/adapters/kysely.d.ts +27 -25
  227. package/dist/source/adapters/kysely.js +24 -23
  228. package/dist/source/adapters/memory.d.ts +8 -7
  229. package/dist/source/adapters/memory.js +9 -8
  230. package/dist/source/adapters/prisma.d.ts +13 -12
  231. package/dist/source/adapters/prisma.js +22 -25
  232. package/dist/source/conformance.d.ts +18 -11
  233. package/dist/source/conformance.js +17 -11
  234. package/dist/source/connector.d.ts +31 -32
  235. package/dist/source/connector.js +28 -28
  236. package/dist/source/connectorProtocol.d.ts +160 -0
  237. package/dist/source/connectorProtocol.js +162 -0
  238. package/dist/source/contract.d.ts +26 -27
  239. package/dist/source/contract.js +28 -29
  240. package/dist/source/factory.d.ts +46 -58
  241. package/dist/source/factory.js +22 -27
  242. package/dist/source/index.d.ts +7 -9
  243. package/dist/source/index.js +12 -14
  244. package/dist/source/migrations.d.ts +9 -9
  245. package/dist/source/migrations.js +9 -9
  246. package/dist/source/next.d.ts +9 -10
  247. package/dist/source/next.js +6 -7
  248. package/dist/source/pushQueue.d.ts +69 -47
  249. package/dist/source/pushQueue.js +32 -28
  250. package/dist/source/signing.d.ts +46 -17
  251. package/dist/source/signing.js +28 -11
  252. package/dist/source/types.d.ts +121 -104
  253. package/dist/source/types.js +13 -14
  254. package/dist/stores/ObjectStore.d.ts +10 -11
  255. package/dist/stores/ObjectStore.js +11 -12
  256. package/dist/stores/ObjectStoreContract.d.ts +12 -15
  257. package/dist/stores/SyncActionStore.d.ts +7 -11
  258. package/dist/stores/SyncActionStore.js +13 -17
  259. package/dist/surface.d.ts +27 -20
  260. package/dist/surface.js +27 -20
  261. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
  262. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
  263. package/dist/sync/ConnectionManager.d.ts +39 -50
  264. package/dist/sync/ConnectionManager.js +55 -66
  265. package/dist/sync/NetworkProbe.d.ts +24 -29
  266. package/dist/sync/NetworkProbe.js +63 -69
  267. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
  268. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
  269. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
  270. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
  271. package/dist/sync/SyncWebSocket.d.ts +139 -165
  272. package/dist/sync/SyncWebSocket.js +191 -223
  273. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  274. package/dist/sync/awaitClaimGrant.js +11 -11
  275. package/dist/sync/bootstrapApply.d.ts +34 -24
  276. package/dist/sync/bootstrapApply.js +27 -19
  277. package/dist/sync/commitFrames.d.ts +21 -20
  278. package/dist/sync/commitFrames.js +18 -18
  279. package/dist/sync/createClaimStream.d.ts +23 -22
  280. package/dist/sync/createClaimStream.js +105 -23
  281. package/dist/sync/createPresenceStream.d.ts +19 -18
  282. package/dist/sync/createPresenceStream.js +25 -26
  283. package/dist/sync/createSnapshot.d.ts +12 -14
  284. package/dist/sync/createSnapshot.js +20 -26
  285. package/dist/sync/credentialLifecycle.d.ts +104 -104
  286. package/dist/sync/credentialLifecycle.js +140 -147
  287. package/dist/sync/deltaPipeline.d.ts +36 -34
  288. package/dist/sync/deltaPipeline.js +64 -65
  289. package/dist/sync/groupChange.d.ts +63 -61
  290. package/dist/sync/groupChange.js +74 -78
  291. package/dist/sync/heartbeat.d.ts +34 -33
  292. package/dist/sync/heartbeat.js +31 -31
  293. package/dist/sync/participants.d.ts +19 -19
  294. package/dist/sync/schemas.d.ts +3 -2
  295. package/dist/sync/schemas.js +14 -10
  296. package/dist/sync/syncCursor.d.ts +17 -21
  297. package/dist/sync/syncCursor.js +17 -21
  298. package/dist/sync/syncPlan.d.ts +28 -36
  299. package/dist/sync/syncPlan.js +18 -19
  300. package/dist/sync/syncPosition.d.ts +54 -49
  301. package/dist/sync/syncPosition.js +57 -52
  302. package/dist/sync/wsFrameHandlers.d.ts +35 -36
  303. package/dist/sync/wsFrameHandlers.js +63 -67
  304. package/dist/testing/fixtures/bootstrap.d.ts +12 -6
  305. package/dist/testing/fixtures/bootstrap.js +12 -6
  306. package/dist/testing/fixtures/deltas.d.ts +30 -33
  307. package/dist/testing/fixtures/deltas.js +30 -33
  308. package/dist/testing/fixtures/models.d.ts +11 -10
  309. package/dist/testing/fixtures/models.js +11 -10
  310. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  311. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  312. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
  313. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
  314. package/dist/testing/helpers/wait.d.ts +13 -8
  315. package/dist/testing/helpers/wait.js +13 -8
  316. package/dist/testing/index.d.ts +3 -3
  317. package/dist/testing/index.js +2 -2
  318. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  319. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  320. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  321. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  322. package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
  323. package/dist/testing/mocks/MockSyncContext.js +15 -13
  324. package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
  325. package/dist/testing/mocks/MockSyncStore.js +11 -11
  326. package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
  327. package/dist/testing/mocks/MockWebSocket.js +22 -21
  328. package/dist/transactions/TransactionQueue.d.ts +181 -176
  329. package/dist/transactions/TransactionQueue.js +338 -350
  330. package/dist/transactions/TransactionStore.d.ts +6 -4
  331. package/dist/transactions/TransactionStore.js +6 -4
  332. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  333. package/dist/transactions/UnconfirmedWrites.js +104 -0
  334. package/dist/transactions/coalesceRules.d.ts +41 -17
  335. package/dist/transactions/coalesceRules.js +40 -17
  336. package/dist/transactions/commitPayload.d.ts +48 -52
  337. package/dist/transactions/commitPayload.js +48 -57
  338. package/dist/transactions/deltaConfirmation.d.ts +20 -22
  339. package/dist/transactions/deltaConfirmation.js +37 -45
  340. package/dist/transactions/optimisticApply.d.ts +49 -0
  341. package/dist/transactions/optimisticApply.js +65 -0
  342. package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
  343. package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
  344. package/dist/types/global.d.ts +46 -41
  345. package/dist/types/global.js +20 -19
  346. package/dist/types/index.d.ts +71 -77
  347. package/dist/types/index.js +22 -22
  348. package/dist/types/modelData.d.ts +6 -8
  349. package/dist/types/modelData.js +5 -7
  350. package/dist/types/participant.d.ts +10 -11
  351. package/dist/types/participant.js +6 -8
  352. package/dist/types/streams.d.ts +208 -195
  353. package/dist/types/streams.js +7 -7
  354. package/dist/utils/asyncIterator.d.ts +25 -32
  355. package/dist/utils/asyncIterator.js +25 -32
  356. package/dist/utils/duration.d.ts +12 -15
  357. package/dist/utils/duration.js +12 -15
  358. package/dist/utils/mobxSetup.d.ts +53 -0
  359. package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
  360. package/dist/webhooks/events.d.ts +21 -16
  361. package/dist/webhooks/events.js +10 -8
  362. package/dist/webhooks/index.d.ts +5 -7
  363. package/dist/webhooks/index.js +5 -7
  364. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  365. package/dist/wire/delta.js +114 -0
  366. package/dist/wire/errorEnvelope.d.ts +30 -31
  367. package/dist/wire/errorEnvelope.js +34 -40
  368. package/dist/wire/frames.d.ts +79 -86
  369. package/dist/wire/frames.js +26 -33
  370. package/dist/wire/index.d.ts +14 -12
  371. package/dist/wire/index.js +30 -26
  372. package/dist/wire/listEnvelope.d.ts +16 -23
  373. package/dist/wire/listEnvelope.js +7 -6
  374. package/dist/wire/protocol.d.ts +25 -32
  375. package/dist/wire/protocol.js +25 -32
  376. package/dist/wire/protocolVersion.d.ts +44 -40
  377. package/dist/wire/protocolVersion.js +44 -40
  378. package/docs/coordination.md +59 -0
  379. package/package.json +11 -10
  380. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  381. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  382. package/dist/core/query-utils.d.ts +0 -34
  383. package/dist/core/query-utils.js +0 -59
  384. package/dist/schema/sync-delta-row.js +0 -103
  385. package/dist/schema/sync-delta-wire.js +0 -102
  386. package/dist/server/read-config.d.ts +0 -67
  387. package/dist/server/read-config.js +0 -8
  388. package/dist/server/storage-mode.d.ts +0 -8
  389. package/dist/server/storage-mode.js +0 -28
  390. package/dist/source/connector-protocol.d.ts +0 -159
  391. package/dist/source/connector-protocol.js +0 -161
  392. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  393. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  394. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  395. package/dist/transactions/mutation-error-handler.js +0 -39
  396. package/dist/transactions/optimistic.d.ts +0 -24
  397. package/dist/transactions/optimistic.js +0 -45
  398. package/dist/utils/mobx-setup.d.ts +0 -42
@@ -1,13 +1,12 @@
1
1
  /**
2
- * Commit-payload projection the queue's wire vocabulary, lifted out of
3
- * `TransactionQueue.ts` as a pure leaf (no queue state, no timers).
4
- *
5
- * Owns the `Transaction` record shape plus every helper that turns a local
6
- * transaction into a wire-safe commit operation: schema-field projection
7
- * (`projectCommitPayload`), write-option projection (`applyWriteOptions`),
8
- * FK-aware priority scoring, and the transport-error duck-typing used to
9
- * classify server rejections. Because nothing here touches the queue's
10
- * runtime, the HTTP commit path can share this projection too.
2
+ * The vocabulary a local transaction uses on the wire. This module defines the
3
+ * {@link Transaction} 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.
11
10
  */
12
11
  import { getContext } from '../context.js';
13
12
  import { getActiveRegistry } from '../ModelRegistry.js';
@@ -19,24 +18,22 @@ import { MutationOperationType } from '../types/index.js';
19
18
  */
20
19
  const FRAMEWORK_KEYS = new Set(['__class', '__typename', 'clientId', 'syncStatus']);
21
20
  /**
22
- * Project a Model's serialized data onto its schema-declared fields
23
- * and return a wire-safe commit payload. Two jobs:
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:
24
23
  *
25
- * 1. Drop framework internals (`__class`, `__typename`, `clientId`,
24
+ * 1. Drops framework-internal keys (`__class`, `__typename`, `clientId`,
26
25
  * `syncStatus`) and anything not declared on the model's schema.
27
- * 2. JSON.stringify values typed as `field.json()` TEXT columns
28
- * storing JSON need explicit stringification; postgres.js won't
29
- * auto-serialize for non-JSONB columns.
26
+ * 2. Passes each declared field's value through unchanged, including
27
+ * JSON-typed fields, which are sent as objects rather than pre-serialized
28
+ * strings (see the note in the body for why).
30
29
  *
31
- * For updates (`dropUndefined: true`), `undefined` values are also
32
- * stripped so they don't translate to `SET column = NULL` on the
33
- * server side.
30
+ * For updates (`dropUndefined: true`), `undefined` values are also removed so
31
+ * they are not written as `SET column = NULL` on the server.
34
32
  *
35
- * Fields are read from `ModelRegistry`, populated by
36
- * `registerModelsFromSchema` at SDK initialization. If the model
37
- * isn't registered with field metadata (edge case e.g., tests or
38
- * manually registered models), projection falls back to identity and
39
- * the caller gets whatever the Model serialized.
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 modelprojection passes the serialized
36
+ * data through unchanged apart from dropping the framework keys.
40
37
  */
41
38
  export function projectCommitPayload(modelName, source, opts) {
42
39
  const metadata = getActiveRegistry().getMetadata(modelName);
@@ -53,31 +50,27 @@ export function projectCommitPayload(modelName, source, opts) {
53
50
  }
54
51
  return out;
55
52
  }
56
- for (const [key, meta] of Object.entries(fields)) {
53
+ for (const key of Object.keys(fields)) {
57
54
  if (!(key in source))
58
55
  continue;
59
56
  const value = source[key];
60
57
  if (opts.dropUndefined && value === undefined)
61
58
  continue;
62
- // JSON-typed fields (`jsonb` on the server): ship as OBJECTS over
63
- // the wire, not pre-stringified strings. Previously we stringified
64
- // here, which round-tripped incorrectly:
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:
65
61
  //
66
- // 1. Client stringifies `position: {x, y}` `'{"x":...}'`
67
- // 2. Server writes to jsonb column (parses string → jsonb object, fine)
68
- // 3. Server's delta echoes `data: JSON.stringify(op.input)` where
69
- // `op.input.position` is still the STRING from step 1
70
- // 4. Client merges delta `model.position = "{...}"` (STRING)
71
- // 5. Next drag: `{ ...layer.position, x, y }` spreads the STRING
72
- // char-by-char, producing corrupted char-indexed objects like
73
- // `{"0":"{","1":"\"","2":"x",...,"x":null,"y":null,...}`
74
- // 6. That corrupt object lands in the next commit, stored in jsonb.
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.
75
70
  //
76
- // Sending objects avoids the round-trip mismatch: the wire carries
77
- // the object through delta + commit unchanged, and `postgres-js`
78
- // serializes JS objects to jsonb correctly via its own
79
- // `json.serialize` (triggered by Postgres's ParameterDescription
80
- // response identifying the column as type 3802 / jsonb).
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.
81
74
  out[key] = value;
82
75
  }
83
76
  return out;
@@ -85,19 +78,17 @@ export function projectCommitPayload(modelName, source, opts) {
85
78
  export const normalizeModelKey = (modelName) => modelName.replace('Model', '').toLowerCase();
86
79
  export const stripModelSuffix = (modelName) => modelName.replace('Model', '');
87
80
  /**
88
- * FK-ordered create priority.
81
+ * Returns the priority score used to order create operations by foreign-key
82
+ * depth, so a parent row commits before its children.
89
83
  *
90
- * Reads `config.modelCreatePriority` out of the runtime SyncEngineContext
91
- * this map is populated once at `createSyncEngine(...)` time by walking the
92
- * schema's `belongsTo` graph (see `computeFKDepthPriority` in
93
- * `client/createSyncEngine.ts`). The queue stays schema-agnostic: no model
94
- * names appear here, and consumer applications can override specific
95
- * priorities via `configOverrides.modelCreatePriority` without touching the
96
- * SDK.
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`.
97
88
  *
98
- * Non-create ops (update/delete/archive/unarchive) don't need FK ordering
99
- * because the row already exists, so they all share
100
- * `config.defaultNonCreatePriority`.
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.
101
92
  */
102
93
  export const computePriorityScore = (type, modelName) => {
103
94
  const { modelCreatePriority, defaultCreatePriority, defaultNonCreatePriority } = getContext().config;
@@ -117,11 +108,11 @@ export function hasStaleWriteOptions(options) {
117
108
  options?.onStale !== undefined);
118
109
  }
119
110
  /**
120
- * Project a transaction's `writeOptions` onto the wire operation. Stale
121
- * guards (`readAt`/`onStale`) ride at the op root; `idempotencyKey`/`label`
122
- * ride in the op's `options` slot (`MutationOperation.options` — the
123
- * mutation_log cache key + audit tag). This is the single place the
124
- * caller-supplied write vocabulary crosses onto the wire.
111
+ * Copies a transaction's `writeOptions` onto the wire operation. The
112
+ * stale-context guards (`readAt` and `onStale`) sit at the operation's root,
113
+ * while `idempotencyKey` and `label` go in its `options` slot — the
114
+ * `mutation_log` cache key and audit tag. This is the one place caller-supplied
115
+ * write options cross onto the wire.
125
116
  */
126
117
  export function applyWriteOptions(op, transaction) {
127
118
  const operation = op;
@@ -1,22 +1,22 @@
1
1
  /**
2
- * Delta-confirmation tracking the queue's "did my write's echo arrive?"
3
- * machinery, lifted out of `TransactionQueue.ts` as a stateful leaf.
4
- *
5
- * Owns the ack watermark (via the shared `SyncPosition`), the per-transaction
6
- * confirmation timeout map, and the retry-with-backoff/reconciliation policy
7
- * for `awaiting_delta` transactions. Talks back to the queue through the
8
- * minimal `DeltaConfirmationContext` interface (never the host class type),
9
- * so the leaf stays cycle-free and testable in isolation.
2
+ * Tracks whether the confirming delta for a write has arrived. It holds the
3
+ * acknowledgement watermark (through the shared {@link SyncPosition}), 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 TransactionQueue} 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.
10
9
  */
11
10
  import type { SyncPosition } from '../sync/syncPosition.js';
12
11
  import type { Transaction } from './commitPayload.js';
13
12
  /**
14
- * The slice of the queue a confirmation tracker needs: store lookups +
15
- * status flips, dropping optimistic entries on confirm, the host's event
16
- * surface (`transaction:completed`, `reconciliation:needed`, …), the
17
- * connection check (timeouts re-schedule instead of escalating while
18
- * offline), and the shared client position (`noteAck` advances `acked`;
19
- * diagnostics read `applied`).
13
+ * The subset of {@link TransactionQueue} that the confirmation tracker needs:
14
+ * store lookups and status changes, removing optimistic entries once a write
15
+ * confirms, the queue's event emitter (`transaction:completed`,
16
+ * `reconciliation:needed`, and so on), a connection check (so timeouts
17
+ * re-schedule instead of escalating while offline), and the shared client
18
+ * position (`noteAck` advances the acknowledgement cursor; diagnostics read the
19
+ * applied cursor).
20
20
  */
21
21
  export interface DeltaConfirmationContext {
22
22
  store: {
@@ -34,7 +34,6 @@ export interface DeltaConfirmationContext {
34
34
  export declare class DeltaConfirmationTracker {
35
35
  private readonly ctx;
36
36
  private static readonly DELTA_MAX_RETRIES;
37
- private static readonly DELTA_INITIAL_TIMEOUT_MS;
38
37
  private static readonly DELTA_MAX_TIMEOUT_MS;
39
38
  private deltaConfirmationTimeouts;
40
39
  private deltaConfirmationRetries;
@@ -43,18 +42,17 @@ export declare class DeltaConfirmationTracker {
43
42
  private get lastSeenSyncId();
44
43
  noteAck(lastSyncId: number | undefined): void;
45
44
  /**
46
- * LINEAR PATTERN: Confirm all awaiting transactions when delta with syncId >= threshold arrives.
47
- * This replaces clientMutationId echoing - transactions are confirmed by sync ID threshold.
48
- * @param syncId - The sync ID of the received delta
45
+ * Confirms every awaiting transaction whose sync-id threshold this delta
46
+ * meets or exceeds.
47
+ * @param syncId - The sync id of the received delta.
49
48
  */
50
49
  onDeltaReceived(syncId: number): void;
51
50
  scheduleDeltaConfirmationTimeout(tx: Transaction, timeoutMs: number): void;
52
51
  private cancelDeltaConfirmationTimeout;
53
52
  /**
54
- * Tear down every armed confirmation timer (30–120s each, one per
55
- * in-flight transaction). Called from `TransactionQueue.dispose()`
56
- * without it a disposed queue kept the Node process alive and fired
57
- * callbacks against an already-cleared store (T1.19).
53
+ * Clears every armed confirmation timer, one per in-flight transaction.
54
+ * {@link TransactionQueue.dispose} calls this; without it a disposed queue
55
+ * would keep the process alive and fire callbacks against a cleared store.
58
56
  */
59
57
  dispose(): void;
60
58
  }
@@ -1,25 +1,22 @@
1
1
  /**
2
- * Delta-confirmation tracking the queue's "did my write's echo arrive?"
3
- * machinery, lifted out of `TransactionQueue.ts` as a stateful leaf.
4
- *
5
- * Owns the ack watermark (via the shared `SyncPosition`), the per-transaction
6
- * confirmation timeout map, and the retry-with-backoff/reconciliation policy
7
- * for `awaiting_delta` transactions. Talks back to the queue through the
8
- * minimal `DeltaConfirmationContext` interface (never the host class type),
9
- * so the leaf stays cycle-free and testable in isolation.
2
+ * Tracks whether the confirming delta for a write has arrived. It holds the
3
+ * acknowledgement watermark (through the shared {@link SyncPosition}), 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 TransactionQueue} 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.
10
9
  */
11
10
  import { getContext } from '../context.js';
12
11
  export class DeltaConfirmationTracker {
13
12
  ctx;
14
- // Delta confirmation retry config (Replicache-style exponential backoff)
15
- // Max retries before requesting full reconciliation
13
+ // Retry configuration for delta confirmation, using exponential backoff.
14
+ // Maximum retries before requesting a full reconciliation.
16
15
  static DELTA_MAX_RETRIES = 5;
17
- // Initial timeout (first attempt)
18
- static DELTA_INITIAL_TIMEOUT_MS = 30_000;
19
- // Max timeout cap (like Replicache's maxDelayMs of 60s)
16
+ // Upper bound on the backoff timeout.
20
17
  static DELTA_MAX_TIMEOUT_MS = 120_000;
21
- // LINEAR PATTERN: Track delta confirmation timeouts for awaiting_delta transactions
22
- // Following Replicache/PowerSync pattern: retry with backoff instead of rolling back
18
+ // Pending confirmation timeouts for transactions awaiting their delta. On
19
+ // timeout the tracker retries with backoff rather than rolling back.
23
20
  deltaConfirmationTimeouts = new Map();
24
21
  // Track retry attempts per transaction for exponential backoff
25
22
  deltaConfirmationRetries = new Map();
@@ -34,12 +31,12 @@ export class DeltaConfirmationTracker {
34
31
  this.ctx.position.noteAck(lastSyncId);
35
32
  }
36
33
  /**
37
- * LINEAR PATTERN: Confirm all awaiting transactions when delta with syncId >= threshold arrives.
38
- * This replaces clientMutationId echoing - transactions are confirmed by sync ID threshold.
39
- * @param syncId - The sync ID of the received delta
34
+ * Confirms every awaiting transaction whose sync-id threshold this delta
35
+ * meets or exceeds.
36
+ * @param syncId - The sync id of the received delta.
40
37
  */
41
38
  onDeltaReceived(syncId) {
42
- // Cursor advancing happens where the delta is APPLIED (the store calls
39
+ // The cursor advances where the delta is applied (the store calls
43
40
  // position.advanceApplied / advancePersisted); this hook only resolves
44
41
  // confirmation thresholds against the incoming id.
45
42
  const awaitingTxs = this.ctx.store.getByStatus('awaiting_delta');
@@ -82,7 +79,7 @@ export class DeltaConfirmationTracker {
82
79
  }
83
80
  // Log batch summary only if we confirmed something
84
81
  if (confirmedCount > 0) {
85
- // Use warn for staging visibility when transactions confirm
82
+ // Leave a breadcrumb when transactions confirm.
86
83
  getContext().observability.breadcrumb('Transactions confirmed via delta', 'sync.transaction', 'info', {
87
84
  count: confirmedCount,
88
85
  syncId,
@@ -90,15 +87,16 @@ export class DeltaConfirmationTracker {
90
87
  });
91
88
  }
92
89
  }
93
- // REPLICACHE/POWERSYNC PATTERN: Schedule delta confirmation with retry + reconciliation
94
- // Instead of rolling back on timeout (which destroys confirmed server state),
95
- // retry with exponential backoff and request reconciliation to catch up on missed deltas.
96
- // Only rollback on explicit server rejection, never on timeout.
90
+ // Schedule the confirmation wait for a transaction. On timeout the tracker
91
+ // retries with exponential backoff and requests reconciliation to catch up on
92
+ // missed deltas, rather than rolling back, which would discard state the
93
+ // server has already confirmed. A rollback happens only on an explicit server
94
+ // rejection, never on a timeout.
97
95
  scheduleDeltaConfirmationTimeout(tx, timeoutMs) {
98
96
  // Cancel any existing timeout for this transaction
99
97
  this.cancelDeltaConfirmationTimeout(tx.id);
100
- // NB: deliberately NOT an async callback the body is fully synchronous,
101
- // and `setTimeout(async …)` turns any throw into an unhandled promise
98
+ // Deliberately not an async callback: the body is fully synchronous, and
99
+ // `setTimeout(async …)` would turn any throw into an unhandled promise
102
100
  // rejection instead of a catchable synchronous error.
103
101
  const timeoutHandle = setTimeout(() => {
104
102
  const currentTx = this.ctx.store.get(tx.id);
@@ -119,12 +117,6 @@ export class DeltaConfirmationTracker {
119
117
  return;
120
118
  }
121
119
  const retryCount = this.deltaConfirmationRetries.get(tx.id) ?? 0;
122
- const diagnosis = this.lastSeenSyncId === 0
123
- ? 'No deltas received - delta pipeline may be broken'
124
- : currentTx.syncIdNeededForCompletion &&
125
- this.lastSeenSyncId < currentTx.syncIdNeededForCompletion
126
- ? 'Delta not yet received - may be lost or delayed'
127
- : 'Delta should have confirmed - possible race condition';
128
120
  getContext().observability.captureReconciliation({
129
121
  reason: 'delta_timeout',
130
122
  model: tx.modelName,
@@ -135,14 +127,15 @@ export class DeltaConfirmationTracker {
135
127
  connectionState: this.ctx.isConnected() ? 'connected' : 'disconnected',
136
128
  });
137
129
  if (retryCount < DeltaConfirmationTracker.DELTA_MAX_RETRIES) {
138
- // RETRY: Request reconciliation and re-schedule with exponential backoff
139
- // The server already committed this mutation we just need the delta to arrive
130
+ // Retry: request reconciliation and re-schedule with exponential
131
+ // backoff. The server has already committed the mutation; only the
132
+ // delta is outstanding.
140
133
  this.deltaConfirmationRetries.set(tx.id, retryCount + 1);
141
134
  this.deltaConfirmationTimeouts.delete(tx.id);
142
135
  // Exponential backoff: 30s → 60s → 120s → 120s → 120s (capped)
143
136
  const nextTimeout = Math.min(timeoutMs * 2, DeltaConfirmationTracker.DELTA_MAX_TIMEOUT_MS);
144
- // Emit reconciliation request so SyncedStore can cycle the WebSocket
145
- // to trigger delta catch-up from the server
137
+ // Request reconciliation so the client can cycle the connection and
138
+ // catch up on missed deltas from the server.
146
139
  this.ctx.emit('reconciliation:needed', {
147
140
  reason: 'delta_confirmation_timeout',
148
141
  txId: tx.id,
@@ -163,10 +156,10 @@ export class DeltaConfirmationTracker {
163
156
  this.scheduleDeltaConfirmationTimeout(tx, nextTimeout);
164
157
  }
165
158
  else {
166
- // LINEAR PATTERN: Retries exhausted persist to IndexedDB instead of rolling back.
167
- // The transaction succeeded on the server (HTTP 200), so the data exists server-side.
168
- // Persist the awaiting state so it survives tab close. On next session, the WebSocket
169
- // reconnect + delta catch-up will naturally confirm it (like Linear's IndexedDB caching).
159
+ // Retries exhausted: persist the awaiting state instead of rolling back.
160
+ // The commit succeeded on the server, so the data exists there. Saving
161
+ // the awaiting state lets it survive the page closing; on the next
162
+ // session, reconnecting and catching up on deltas will confirm it.
170
163
  this.deltaConfirmationRetries.delete(tx.id);
171
164
  this.deltaConfirmationTimeouts.delete(tx.id);
172
165
  getContext().observability.captureDeltaRetryExhausted({
@@ -176,7 +169,7 @@ export class DeltaConfirmationTracker {
176
169
  retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
177
170
  syncIdNeeded: currentTx.syncIdNeededForCompletion,
178
171
  });
179
- // Emit persist event SyncClient handles the IDB write
172
+ // Emit the persist event; the client performs the write to local storage.
180
173
  this.ctx.emit('transaction:persist_awaiting', {
181
174
  txId: tx.id,
182
175
  model: tx.modelName,
@@ -208,10 +201,9 @@ export class DeltaConfirmationTracker {
208
201
  this.deltaConfirmationRetries.delete(id);
209
202
  }
210
203
  /**
211
- * Tear down every armed confirmation timer (30–120s each, one per
212
- * in-flight transaction). Called from `TransactionQueue.dispose()`
213
- * without it a disposed queue kept the Node process alive and fired
214
- * callbacks against an already-cleared store (T1.19).
204
+ * Clears every armed confirmation timer, one per in-flight transaction.
205
+ * {@link TransactionQueue.dispose} calls this; without it a disposed queue
206
+ * would keep the process alive and fire callbacks against a cleared store.
215
207
  */
216
208
  dispose() {
217
209
  for (const timeoutHandle of this.deltaConfirmationTimeouts.values()) {
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Local-first apply and rollback bookkeeping for pending mutations.
3
+ *
4
+ * When a change is written before the server confirms it, these functions
5
+ * record it in a ledger and emit an `optimistic:*` event; separate listeners
6
+ * apply the change to the in-memory data and, on rollback, restore the value
7
+ * it replaced. Each function takes the ledger and a small event emitter rather
8
+ * than any larger object, so the rules have no dependencies of their own and
9
+ * can be tested in isolation.
10
+ */
11
+ import type { Model } from '../Model.js';
12
+ import type { MutationInput, Transaction } from './commitPayload.js';
13
+ /**
14
+ * One tracked optimistic mutation: the live model plus the value it held
15
+ * before the change, kept so a rollback can restore it.
16
+ */
17
+ export interface OptimisticUpdateEntry {
18
+ model: Model;
19
+ previousState: MutationInput | null | undefined;
20
+ transaction: Transaction;
21
+ }
22
+ /**
23
+ * The event emitter these functions use to announce each optimistic change and
24
+ * its rollback. You supply the implementation.
25
+ */
26
+ export interface OptimisticEmitter {
27
+ emit(event: string, payload: unknown): void;
28
+ }
29
+ /**
30
+ * Record an optimistic create and announce it. There is no prior value to
31
+ * restore, so the rollback pre-image is `null`.
32
+ */
33
+ export declare function applyOptimisticCreate(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
34
+ /**
35
+ * Record an optimistic update and announce it, keeping the row's prior value so
36
+ * a rollback can put it back.
37
+ */
38
+ export declare function applyOptimisticUpdate(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
39
+ /**
40
+ * Record an optimistic delete and announce it, keeping the deleted row so a
41
+ * rollback can restore it.
42
+ */
43
+ export declare function applyOptimisticDelete(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, model: Model, transaction: Transaction): void;
44
+ /**
45
+ * Undo an optimistic mutation by its transaction id: emit `optimistic:rollback`
46
+ * with the saved pre-image so listeners can restore the prior value, then drop
47
+ * the ledger entry. Does nothing if the transaction was never tracked.
48
+ */
49
+ export declare function rollbackOptimistic(optimisticUpdates: Map<string, OptimisticUpdateEntry>, emitter: OptimisticEmitter, transaction: Transaction, reason?: string, error?: Error): Promise<void>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Local-first apply and rollback bookkeeping for pending mutations.
3
+ *
4
+ * When a change is written before the server confirms it, these functions
5
+ * record it in a ledger and emit an `optimistic:*` event; separate listeners
6
+ * apply the change to the in-memory data and, on rollback, restore the value
7
+ * it replaced. Each function takes the ledger and a small event emitter rather
8
+ * than any larger object, so the rules have no dependencies of their own and
9
+ * can be tested in isolation.
10
+ */
11
+ /**
12
+ * Record an optimistic create and announce it. There is no prior value to
13
+ * restore, so the rollback pre-image is `null`.
14
+ */
15
+ export function applyOptimisticCreate(optimisticUpdates, emitter, model, transaction) {
16
+ optimisticUpdates.set(transaction.id, {
17
+ model,
18
+ previousState: null,
19
+ transaction,
20
+ });
21
+ emitter.emit('optimistic:create', { model, transaction });
22
+ }
23
+ /**
24
+ * Record an optimistic update and announce it, keeping the row's prior value so
25
+ * a rollback can put it back.
26
+ */
27
+ export function applyOptimisticUpdate(optimisticUpdates, emitter, model, transaction) {
28
+ optimisticUpdates.set(transaction.id, {
29
+ model,
30
+ previousState: transaction.previousData,
31
+ transaction,
32
+ });
33
+ emitter.emit('optimistic:update', { model, transaction });
34
+ }
35
+ /**
36
+ * Record an optimistic delete and announce it, keeping the deleted row so a
37
+ * rollback can restore it.
38
+ */
39
+ export function applyOptimisticDelete(optimisticUpdates, emitter, model, transaction) {
40
+ optimisticUpdates.set(transaction.id, {
41
+ model,
42
+ previousState: transaction.previousData,
43
+ transaction,
44
+ });
45
+ emitter.emit('optimistic:delete', { model, transaction });
46
+ }
47
+ /**
48
+ * Undo an optimistic mutation by its transaction id: emit `optimistic:rollback`
49
+ * with the saved pre-image so listeners can restore the prior value, then drop
50
+ * the ledger entry. Does nothing if the transaction was never tracked.
51
+ */
52
+ export function rollbackOptimistic(optimisticUpdates, emitter, transaction, reason, error) {
53
+ const optimistic = optimisticUpdates.get(transaction.id);
54
+ if (!optimistic)
55
+ return Promise.resolve();
56
+ emitter.emit('optimistic:rollback', {
57
+ model: optimistic.model,
58
+ previousState: optimistic.previousState,
59
+ transaction,
60
+ reason: reason ?? 'unknown',
61
+ error,
62
+ });
63
+ optimisticUpdates.delete(transaction.id);
64
+ return Promise.resolve();
65
+ }
@@ -1,26 +1,27 @@
1
1
  /**
2
- * persistedReplay the Zod boundary for IDB commit replay (T1.8).
2
+ * The validation boundary for replaying persisted transactions after a restart.
3
3
  *
4
- * Rows read back from the persisted-transaction store were written by a
5
- * PREVIOUS session — possibly by an older SDK version, possibly corrupted.
6
- * Spreading them straight into a `Transaction` re-entered the commit path
7
- * with zero validation. These schemas validate exactly the fields the queue
8
- * (and the offline mutation-queue restore) actually read on replay; rows
9
- * that don't parse are DROPPED (and surfaced through observability), never
10
- * replayed as garbage commits.
4
+ * Rows read back from the on-disk transaction store may have been written by an
5
+ * earlier run — possibly by an older version of this package, possibly
6
+ * corrupted. Rather than trust them, the schemas here validate exactly the
7
+ * fields the transaction queue and the offline-mutation restore read during
8
+ * replay. A row that fails to parse is dropped and reported, never replayed as
9
+ * a malformed commit.
11
10
  *
12
- * The same IDB store also holds two NON-replayable row kinds written by
13
- * other subsystems — the `'queue'` record (`SyncClient.persistMutationQueue`)
14
- * and `'awaiting_delta'` markers (`setupAwaitingTransactionPersistence`).
15
- * Those are recognized and skipped silently: they are expected neighbors,
16
- * not corruption.
11
+ * The same store also holds two kinds of rows that are not replayable
12
+ * transactions — the offline mutation queue (`type: 'queue'`) and delta-await
13
+ * markers (`type: 'awaiting_delta'`), each owned by another part of the client.
14
+ * {@link isNonReplayablePersistedRow} recognizes them so they are skipped
15
+ * quietly rather than flagged as corruption.
17
16
  */
18
17
  import { z } from 'zod';
19
18
  import type { Transaction } from './commitPayload.js';
20
19
  /**
21
- * A replayable persisted transaction the fields `TransactionQueue`
22
- * actually reads when re-enqueueing (id/type/model addressing + payload +
23
- * identity context). Everything else is defaulted at rehydration time.
20
+ * The shape of a persisted transaction that can be replayed: the fields the
21
+ * transaction queue reads when it re-enqueues the row its id, operation type,
22
+ * model addressing, payload, and identity context. Any remaining bookkeeping is
23
+ * filled in with defaults when the row is rehydrated by
24
+ * {@link deserializePersistedTransaction}.
24
25
  */
25
26
  export declare const persistedTransactionSchema: z.ZodObject<{
26
27
  id: z.ZodString;
@@ -57,17 +58,22 @@ export declare const persistedTransactionSchema: z.ZodObject<{
57
58
  localOnly: z.ZodOptional<z.ZodBoolean>;
58
59
  }, z.core.$loose>;
59
60
  export type PersistedReplayableTransaction = z.infer<typeof persistedTransactionSchema>;
60
- /** Whether a persisted row belongs to another subsystem (skip, don't flag). */
61
+ /**
62
+ * Reports whether a stored row is one of the non-transaction kinds, so callers
63
+ * skip it instead of treating it as a corrupt transaction.
64
+ */
61
65
  export declare function isNonReplayablePersistedRow(row: unknown): boolean;
62
66
  /**
63
- * Validate + rehydrate one persisted row into a replayable `Transaction`,
64
- * or `null` when the row doesn't parse. Missing bookkeeping fields are
65
- * re-derived exactly like a fresh staging would derive them.
67
+ * Validates one stored row and rehydrates it into a {@link Transaction} ready
68
+ * to replay, or returns `null` when the row fails validation. Bookkeeping
69
+ * fields the stored row lacks status, attempts, priority, timestamp — are
70
+ * re-derived the same way a freshly staged transaction derives them.
66
71
  */
67
72
  export declare function deserializePersistedTransaction(row: unknown): Transaction | null;
68
73
  /**
69
- * One entry of the persisted offline mutation queue (the `'queue'` row's
70
- * `mutations` array) the fields `SyncClient.restoreMutationQueue` reads.
74
+ * The shape of one entry in the persisted offline mutation queue an item of
75
+ * the `'queue'` row's `mutations` array, carrying the fields read when the
76
+ * queue is restored on reconnect.
71
77
  */
72
78
  export declare const persistedMutationSchema: z.ZodObject<{
73
79
  type: z.ZodEnum<{