@abloatai/ablo 0.25.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 (425) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +104 -88
  4. package/dist/BaseSyncedStore.d.ts +140 -266
  5. package/dist/BaseSyncedStore.js +338 -739
  6. package/dist/Database.d.ts +62 -77
  7. package/dist/Database.js +106 -127
  8. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  9. package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
  10. package/dist/LazyReferenceCollection.d.ts +11 -15
  11. package/dist/LazyReferenceCollection.js +16 -15
  12. package/dist/Model.d.ts +37 -52
  13. package/dist/Model.js +52 -69
  14. package/dist/ModelRegistry.d.ts +46 -25
  15. package/dist/ModelRegistry.js +32 -30
  16. package/dist/NetworkMonitor.d.ts +5 -6
  17. package/dist/NetworkMonitor.js +6 -7
  18. package/dist/SyncClient.d.ts +119 -109
  19. package/dist/SyncClient.js +303 -224
  20. package/dist/SyncEngineContext.d.ts +1 -3
  21. package/dist/SyncEngineContext.js +1 -2
  22. package/dist/adapters/alwaysOnline.d.ts +6 -8
  23. package/dist/adapters/alwaysOnline.js +6 -8
  24. package/dist/adapters/inMemoryStorage.d.ts +9 -9
  25. package/dist/adapters/inMemoryStorage.js +9 -9
  26. package/dist/agent/Agent.d.ts +39 -31
  27. package/dist/agent/Agent.js +35 -23
  28. package/dist/agent/index.d.ts +4 -4
  29. package/dist/agent/index.js +5 -5
  30. package/dist/agent/session.d.ts +47 -44
  31. package/dist/agent/session.js +37 -48
  32. package/dist/agent/types.d.ts +26 -31
  33. package/dist/agent/types.js +6 -7
  34. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  35. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  36. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  37. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +30 -31
  38. package/dist/ai-sdk/index.d.ts +25 -22
  39. package/dist/ai-sdk/index.js +25 -22
  40. package/dist/ai-sdk/wrap.d.ts +7 -8
  41. package/dist/ai-sdk/wrap.js +2 -2
  42. package/dist/auth/credentialPolicy.d.ts +74 -71
  43. package/dist/auth/credentialPolicy.js +51 -56
  44. package/dist/auth/credentialSource.d.ts +7 -18
  45. package/dist/auth/credentialSource.js +10 -18
  46. package/dist/auth/index.d.ts +59 -58
  47. package/dist/auth/index.js +34 -40
  48. package/dist/auth/schemas.d.ts +5 -4
  49. package/dist/auth/schemas.js +5 -4
  50. package/dist/batching/index.d.ts +19 -21
  51. package/dist/batching/index.js +14 -17
  52. package/dist/cli.cjs +483 -369
  53. package/dist/client/Ablo.d.ts +107 -836
  54. package/dist/client/Ablo.js +174 -833
  55. package/dist/client/ApiClient.d.ts +44 -20
  56. package/dist/client/ApiClient.js +193 -44
  57. package/dist/client/auth.d.ts +51 -60
  58. package/dist/client/auth.js +137 -110
  59. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  60. package/dist/client/claimHeartbeatLoop.js +88 -0
  61. package/dist/client/consoleLogger.d.ts +35 -0
  62. package/dist/client/consoleLogger.js +44 -0
  63. package/dist/client/createInternalComponents.d.ts +14 -17
  64. package/dist/client/createInternalComponents.js +26 -31
  65. package/dist/client/createModelProxy.d.ts +130 -120
  66. package/dist/client/createModelProxy.js +158 -124
  67. package/dist/client/credentialEndpoint.d.ts +61 -0
  68. package/dist/client/credentialEndpoint.js +86 -0
  69. package/dist/client/functionalUpdate.d.ts +29 -27
  70. package/dist/client/functionalUpdate.js +21 -21
  71. package/dist/client/hostedEndpoints.d.ts +21 -0
  72. package/dist/client/hostedEndpoints.js +21 -0
  73. package/dist/client/httpClient.d.ts +58 -54
  74. package/dist/client/httpClient.js +29 -31
  75. package/dist/client/identity.d.ts +15 -20
  76. package/dist/client/identity.js +49 -59
  77. package/dist/client/modelRegistration.d.ts +10 -0
  78. package/dist/client/modelRegistration.js +301 -0
  79. package/dist/client/options.d.ts +373 -0
  80. package/dist/client/options.js +6 -0
  81. package/dist/client/registerDataSource.d.ts +9 -9
  82. package/dist/client/registerDataSource.js +15 -16
  83. package/dist/client/resourceTypes.d.ts +333 -0
  84. package/dist/client/resourceTypes.js +7 -0
  85. package/dist/client/schemaConfig.d.ts +44 -0
  86. package/dist/client/schemaConfig.js +176 -0
  87. package/dist/client/sessionMint.d.ts +17 -13
  88. package/dist/client/sessionMint.js +26 -31
  89. package/dist/client/validateAbloOptions.d.ts +12 -14
  90. package/dist/client/validateAbloOptions.js +9 -10
  91. package/dist/client/writeOptionsSchema.d.ts +18 -16
  92. package/dist/client/writeOptionsSchema.js +23 -20
  93. package/dist/client/wsMutationExecutor.d.ts +28 -0
  94. package/dist/client/wsMutationExecutor.js +71 -0
  95. package/dist/context.d.ts +6 -4
  96. package/dist/context.js +6 -7
  97. package/dist/coordination/index.d.ts +13 -4
  98. package/dist/coordination/index.js +29 -4
  99. package/dist/coordination/schema.d.ts +176 -128
  100. package/dist/coordination/schema.js +197 -133
  101. package/dist/coordination/trace.d.ts +9 -11
  102. package/dist/coordination/trace.js +13 -15
  103. package/dist/core/DatabaseManager.d.ts +5 -8
  104. package/dist/core/DatabaseManager.js +38 -40
  105. package/dist/core/QueryProcessor.d.ts +7 -9
  106. package/dist/core/QueryProcessor.js +27 -34
  107. package/dist/core/QueryView.d.ts +17 -5
  108. package/dist/core/QueryView.js +6 -7
  109. package/dist/core/StoreManager.d.ts +14 -16
  110. package/dist/core/StoreManager.js +26 -25
  111. package/dist/core/ViewRegistry.d.ts +5 -5
  112. package/dist/core/ViewRegistry.js +4 -4
  113. package/dist/core/index.d.ts +18 -13
  114. package/dist/core/index.js +32 -26
  115. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  116. package/dist/core/openIDBWithTimeout.js +57 -54
  117. package/dist/core/queryUtils.d.ts +45 -0
  118. package/dist/core/queryUtils.js +69 -0
  119. package/dist/core/storeContract.d.ts +145 -0
  120. package/dist/core/storeContract.js +12 -0
  121. package/dist/environment.d.ts +28 -0
  122. package/dist/environment.js +21 -0
  123. package/dist/errorCodes.d.ts +118 -101
  124. package/dist/errorCodes.js +277 -260
  125. package/dist/errors.d.ts +170 -165
  126. package/dist/errors.js +161 -151
  127. package/dist/index.d.ts +30 -27
  128. package/dist/index.js +90 -82
  129. package/dist/interfaces/index.d.ts +108 -133
  130. package/dist/interfaces/index.js +5 -4
  131. package/dist/keys/index.d.ts +27 -29
  132. package/dist/keys/index.js +59 -49
  133. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  134. package/dist/mutators/RecordingTransaction.js +31 -37
  135. package/dist/mutators/Transaction.d.ts +18 -26
  136. package/dist/mutators/Transaction.js +14 -20
  137. package/dist/mutators/UndoManager.d.ts +122 -131
  138. package/dist/mutators/UndoManager.js +149 -155
  139. package/dist/mutators/defineMutators.d.ts +24 -37
  140. package/dist/mutators/defineMutators.js +14 -20
  141. package/dist/mutators/inverseOp.d.ts +12 -15
  142. package/dist/mutators/inverseOp.js +12 -15
  143. package/dist/mutators/mutateActions.d.ts +10 -9
  144. package/dist/mutators/mutateActions.js +1 -1
  145. package/dist/mutators/readerActions.d.ts +9 -8
  146. package/dist/mutators/readerActions.js +2 -2
  147. package/dist/mutators/undoApply.d.ts +31 -27
  148. package/dist/mutators/undoApply.js +26 -24
  149. package/dist/policy/index.d.ts +5 -3
  150. package/dist/policy/index.js +5 -3
  151. package/dist/policy/types.d.ts +105 -101
  152. package/dist/policy/types.js +67 -66
  153. package/dist/query/client.d.ts +32 -16
  154. package/dist/query/client.js +103 -72
  155. package/dist/query/types.d.ts +37 -60
  156. package/dist/query/types.js +13 -33
  157. package/dist/react/AbloProvider.d.ts +7 -11
  158. package/dist/react/AbloProvider.js +24 -17
  159. package/dist/react/context.d.ts +27 -146
  160. package/dist/react/context.js +9 -10
  161. package/dist/react/index.d.ts +41 -42
  162. package/dist/react/index.js +37 -38
  163. package/dist/react/internalContext.d.ts +17 -19
  164. package/dist/react/useAblo.d.ts +23 -22
  165. package/dist/react/useAblo.js +17 -15
  166. package/dist/react/useCurrentUserId.d.ts +8 -7
  167. package/dist/react/useCurrentUserId.js +8 -7
  168. package/dist/react/useErrorListener.d.ts +7 -7
  169. package/dist/react/useErrorListener.js +11 -12
  170. package/dist/react/useMutationFailureListener.d.ts +8 -8
  171. package/dist/react/useMutationFailureListener.js +9 -9
  172. package/dist/react/useMutators.d.ts +11 -11
  173. package/dist/react/useMutators.js +10 -4
  174. package/dist/react/useReactive.js +2 -3
  175. package/dist/react/useSyncStatus.d.ts +4 -6
  176. package/dist/react/useUndoScope.d.ts +7 -9
  177. package/dist/react/useUndoScope.js +3 -3
  178. package/dist/schema/coordination.d.ts +21 -25
  179. package/dist/schema/coordination.js +21 -25
  180. package/dist/schema/ddl.d.ts +43 -39
  181. package/dist/schema/ddl.js +75 -68
  182. package/dist/schema/ddlLock.d.ts +35 -0
  183. package/dist/schema/ddlLock.js +46 -0
  184. package/dist/schema/diff.d.ts +99 -61
  185. package/dist/schema/diff.js +43 -34
  186. package/dist/schema/field.d.ts +37 -42
  187. package/dist/schema/field.js +36 -49
  188. package/dist/schema/generate.d.ts +12 -12
  189. package/dist/schema/generate.js +12 -12
  190. package/dist/schema/index.d.ts +5 -4
  191. package/dist/schema/index.js +29 -21
  192. package/dist/schema/model.d.ts +121 -146
  193. package/dist/schema/model.js +24 -35
  194. package/dist/schema/openapi.d.ts +10 -9
  195. package/dist/schema/openapi.js +7 -1
  196. package/dist/schema/queries.d.ts +30 -32
  197. package/dist/schema/queries.js +24 -25
  198. package/dist/schema/relation.d.ts +89 -99
  199. package/dist/schema/relation.js +13 -13
  200. package/dist/schema/residency.d.ts +38 -0
  201. package/dist/schema/residency.js +30 -0
  202. package/dist/schema/roles.d.ts +45 -27
  203. package/dist/schema/roles.js +52 -21
  204. package/dist/schema/schema.d.ts +36 -45
  205. package/dist/schema/schema.js +42 -39
  206. package/dist/schema/select.d.ts +13 -13
  207. package/dist/schema/select.js +13 -13
  208. package/dist/schema/serialize.d.ts +36 -39
  209. package/dist/schema/serialize.js +27 -31
  210. package/dist/schema/sugar.d.ts +17 -32
  211. package/dist/schema/sugar.js +14 -29
  212. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +27 -50
  213. package/dist/schema/syncDeltaRow.js +89 -0
  214. package/dist/schema/tenancy.d.ts +44 -46
  215. package/dist/schema/tenancy.js +46 -48
  216. package/dist/server/adapter.d.ts +58 -58
  217. package/dist/server/adapter.js +13 -14
  218. package/dist/server/commit.d.ts +60 -64
  219. package/dist/server/index.d.ts +9 -10
  220. package/dist/server/index.js +1 -1
  221. package/dist/server/readConfig.d.ts +70 -0
  222. package/dist/server/readConfig.js +8 -0
  223. package/dist/server/storageMode.d.ts +23 -0
  224. package/dist/server/storageMode.js +17 -0
  225. package/dist/source/adapter.d.ts +31 -26
  226. package/dist/source/adapter.js +10 -10
  227. package/dist/source/adapters/drizzle.d.ts +28 -23
  228. package/dist/source/adapters/drizzle.js +34 -28
  229. package/dist/source/adapters/kysely.d.ts +27 -25
  230. package/dist/source/adapters/kysely.js +28 -26
  231. package/dist/source/adapters/memory.d.ts +8 -7
  232. package/dist/source/adapters/memory.js +10 -9
  233. package/dist/source/adapters/prisma.d.ts +13 -12
  234. package/dist/source/adapters/prisma.js +27 -29
  235. package/dist/source/conformance.d.ts +18 -11
  236. package/dist/source/conformance.js +27 -19
  237. package/dist/source/connector.d.ts +31 -32
  238. package/dist/source/connector.js +30 -28
  239. package/dist/source/connectorProtocol.d.ts +160 -0
  240. package/dist/source/connectorProtocol.js +162 -0
  241. package/dist/source/contract.d.ts +26 -27
  242. package/dist/source/contract.js +28 -29
  243. package/dist/source/factory.d.ts +94 -0
  244. package/dist/source/factory.js +268 -0
  245. package/dist/source/index.d.ts +10 -462
  246. package/dist/source/index.js +17 -421
  247. package/dist/source/migrations.d.ts +9 -9
  248. package/dist/source/migrations.js +9 -9
  249. package/dist/source/next.d.ts +10 -11
  250. package/dist/source/next.js +7 -8
  251. package/dist/source/pushQueue.d.ts +70 -48
  252. package/dist/source/pushQueue.js +36 -29
  253. package/dist/source/signing.d.ts +88 -0
  254. package/dist/source/signing.js +159 -0
  255. package/dist/source/types.d.ts +351 -0
  256. package/dist/source/types.js +43 -0
  257. package/dist/stores/ObjectStore.d.ts +11 -12
  258. package/dist/stores/ObjectStore.js +34 -35
  259. package/dist/stores/ObjectStoreContract.d.ts +12 -15
  260. package/dist/stores/SyncActionStore.d.ts +8 -12
  261. package/dist/stores/SyncActionStore.js +77 -46
  262. package/dist/surface.d.ts +28 -21
  263. package/dist/surface.js +28 -20
  264. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
  265. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
  266. package/dist/sync/ConnectionManager.d.ts +47 -50
  267. package/dist/sync/ConnectionManager.js +74 -70
  268. package/dist/sync/NetworkProbe.d.ts +27 -31
  269. package/dist/sync/NetworkProbe.js +67 -72
  270. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
  271. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
  272. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
  273. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
  274. package/dist/sync/SyncWebSocket.d.ts +175 -250
  275. package/dist/sync/SyncWebSocket.js +431 -769
  276. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  277. package/dist/sync/awaitClaimGrant.js +38 -30
  278. package/dist/sync/bootstrapApply.d.ts +70 -0
  279. package/dist/sync/bootstrapApply.js +73 -0
  280. package/dist/sync/commitFrames.d.ts +44 -0
  281. package/dist/sync/commitFrames.js +94 -0
  282. package/dist/sync/createClaimStream.d.ts +23 -22
  283. package/dist/sync/createClaimStream.js +108 -25
  284. package/dist/sync/createPresenceStream.d.ts +19 -18
  285. package/dist/sync/createPresenceStream.js +25 -26
  286. package/dist/sync/createSnapshot.d.ts +13 -17
  287. package/dist/sync/createSnapshot.js +20 -26
  288. package/dist/sync/credentialLifecycle.d.ts +175 -0
  289. package/dist/sync/credentialLifecycle.js +322 -0
  290. package/dist/sync/deltaPipeline.d.ts +113 -0
  291. package/dist/sync/deltaPipeline.js +261 -0
  292. package/dist/sync/groupChange.d.ts +113 -0
  293. package/dist/sync/groupChange.js +242 -0
  294. package/dist/sync/heartbeat.d.ts +63 -0
  295. package/dist/sync/heartbeat.js +91 -0
  296. package/dist/sync/participants.d.ts +27 -27
  297. package/dist/sync/schemas.d.ts +3 -2
  298. package/dist/sync/schemas.js +14 -10
  299. package/dist/sync/syncCursor.d.ts +40 -0
  300. package/dist/sync/syncCursor.js +55 -0
  301. package/dist/sync/syncPlan.d.ts +54 -0
  302. package/dist/sync/syncPlan.js +50 -0
  303. package/dist/sync/syncPosition.d.ts +54 -49
  304. package/dist/sync/syncPosition.js +57 -52
  305. package/dist/sync/wsFrameHandlers.d.ts +116 -0
  306. package/dist/sync/wsFrameHandlers.js +374 -0
  307. package/dist/testing/fixtures/bootstrap.d.ts +21 -17
  308. package/dist/testing/fixtures/bootstrap.js +12 -6
  309. package/dist/testing/fixtures/deltas.d.ts +31 -34
  310. package/dist/testing/fixtures/deltas.js +30 -33
  311. package/dist/testing/fixtures/models.d.ts +11 -10
  312. package/dist/testing/fixtures/models.js +12 -10
  313. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  314. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  315. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -18
  316. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
  317. package/dist/testing/helpers/wait.d.ts +13 -8
  318. package/dist/testing/helpers/wait.js +13 -8
  319. package/dist/testing/index.d.ts +4 -4
  320. package/dist/testing/index.js +3 -3
  321. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  322. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  323. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  324. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  325. package/dist/testing/mocks/MockSyncContext.d.ts +21 -34
  326. package/dist/testing/mocks/MockSyncContext.js +16 -45
  327. package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
  328. package/dist/testing/mocks/MockSyncStore.js +11 -11
  329. package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
  330. package/dist/testing/mocks/MockWebSocket.js +22 -21
  331. package/dist/transactions/TransactionQueue.d.ts +190 -221
  332. package/dist/transactions/TransactionQueue.js +424 -822
  333. package/dist/transactions/TransactionStore.d.ts +20 -0
  334. package/dist/transactions/TransactionStore.js +53 -0
  335. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  336. package/dist/transactions/UnconfirmedWrites.js +104 -0
  337. package/dist/transactions/coalesceRules.d.ts +58 -0
  338. package/dist/transactions/coalesceRules.js +140 -0
  339. package/dist/transactions/commitPayload.d.ts +130 -0
  340. package/dist/transactions/commitPayload.js +143 -0
  341. package/dist/transactions/deltaConfirmation.d.ts +58 -0
  342. package/dist/transactions/deltaConfirmation.js +215 -0
  343. package/dist/transactions/optimisticApply.d.ts +49 -0
  344. package/dist/transactions/optimisticApply.js +65 -0
  345. package/dist/transactions/replayValidation.d.ts +99 -0
  346. package/dist/transactions/replayValidation.js +111 -0
  347. package/dist/types/global.d.ts +46 -41
  348. package/dist/types/global.js +20 -19
  349. package/dist/types/index.d.ts +74 -80
  350. package/dist/types/index.js +22 -27
  351. package/dist/types/modelData.d.ts +10 -0
  352. package/dist/types/modelData.js +9 -0
  353. package/dist/types/participant.d.ts +20 -0
  354. package/dist/types/participant.js +10 -0
  355. package/dist/types/streams.d.ts +216 -209
  356. package/dist/types/streams.js +7 -7
  357. package/dist/utils/asyncIterator.d.ts +25 -32
  358. package/dist/utils/asyncIterator.js +25 -32
  359. package/dist/utils/duration.d.ts +12 -15
  360. package/dist/utils/duration.js +12 -15
  361. package/dist/utils/mobxSetup.d.ts +53 -0
  362. package/dist/utils/{mobx-setup.js → mobxSetup.js} +44 -100
  363. package/dist/webhooks/events.d.ts +21 -16
  364. package/dist/webhooks/events.js +10 -8
  365. package/dist/webhooks/index.d.ts +5 -7
  366. package/dist/webhooks/index.js +5 -7
  367. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  368. package/dist/wire/delta.js +114 -0
  369. package/dist/wire/errorEnvelope.d.ts +35 -27
  370. package/dist/wire/errorEnvelope.js +38 -32
  371. package/dist/wire/frames.d.ts +150 -67
  372. package/dist/wire/frames.js +48 -1
  373. package/dist/wire/index.d.ts +18 -13
  374. package/dist/wire/index.js +36 -13
  375. package/dist/wire/listEnvelope.d.ts +16 -23
  376. package/dist/wire/listEnvelope.js +7 -6
  377. package/dist/wire/protocol.d.ts +38 -0
  378. package/dist/wire/protocol.js +38 -0
  379. package/dist/wire/protocolVersion.d.ts +60 -0
  380. package/dist/wire/protocolVersion.js +67 -0
  381. package/docs/api-keys.md +4 -3
  382. package/docs/coordination.md +59 -0
  383. package/docs/examples/existing-python-backend.md +3 -3
  384. package/docs/identity.md +4 -4
  385. package/docs/integration-guide.md +1 -1
  386. package/docs/react.md +1 -1
  387. package/docs/sessions.md +5 -7
  388. package/package.json +24 -21
  389. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  390. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  391. package/dist/client/index.d.ts +0 -36
  392. package/dist/client/index.js +0 -33
  393. package/dist/config/index.d.ts +0 -10
  394. package/dist/config/index.js +0 -12
  395. package/dist/core/query-utils.d.ts +0 -34
  396. package/dist/core/query-utils.js +0 -59
  397. package/dist/interfaces/headless.d.ts +0 -95
  398. package/dist/interfaces/headless.js +0 -41
  399. package/dist/query/index.d.ts +0 -6
  400. package/dist/query/index.js +0 -5
  401. package/dist/realtime/index.d.ts +0 -10
  402. package/dist/realtime/index.js +0 -9
  403. package/dist/schema/plane.d.ts +0 -23
  404. package/dist/schema/plane.js +0 -19
  405. package/dist/schema/sync-delta-row.js +0 -103
  406. package/dist/schema/sync-delta-wire.js +0 -102
  407. package/dist/server/next.d.ts +0 -51
  408. package/dist/server/next.js +0 -47
  409. package/dist/server/read-config.d.ts +0 -67
  410. package/dist/server/read-config.js +0 -8
  411. package/dist/server/storage-mode.d.ts +0 -1
  412. package/dist/server/storage-mode.js +0 -18
  413. package/dist/source/connector-protocol.d.ts +0 -159
  414. package/dist/source/connector-protocol.js +0 -161
  415. package/dist/sync/OfflineFlush.d.ts +0 -9
  416. package/dist/sync/OfflineFlush.js +0 -22
  417. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  418. package/dist/sync/OfflineTransactionStore.js +0 -263
  419. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  420. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  421. package/dist/transactions/index.d.ts +0 -16
  422. package/dist/transactions/index.js +0 -7
  423. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  424. package/dist/transactions/mutation-error-handler.js +0 -39
  425. package/dist/utils/mobx-setup.d.ts +0 -42
@@ -0,0 +1,143 @@
1
+ /**
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.
10
+ */
11
+ import { getContext } from '../context.js';
12
+ import { getActiveRegistry } from '../ModelRegistry.js';
13
+ import { MutationOperationType } from '../types/index.js';
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. 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).
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 passes the serialized
36
+ * data through unchanged apart from dropping the framework keys.
37
+ */
38
+ export function projectCommitPayload(modelName, source, opts) {
39
+ const metadata = getActiveRegistry().getMetadata(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 out;
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 out;
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) => {
94
+ const { modelCreatePriority, defaultCreatePriority, defaultNonCreatePriority } = getContext().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
+ /**
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.
116
+ */
117
+ export function applyWriteOptions(op, transaction) {
118
+ const operation = op;
119
+ const writeOptions = transaction.writeOptions;
120
+ if (!writeOptions)
121
+ return operation;
122
+ if (writeOptions.readAt !== undefined) {
123
+ operation.readAt = writeOptions.readAt;
124
+ }
125
+ if (writeOptions.onStale !== undefined) {
126
+ operation.onStale = writeOptions.onStale;
127
+ }
128
+ if (writeOptions.idempotencyKey != null || writeOptions.label !== undefined) {
129
+ operation.options = {
130
+ ...(writeOptions.idempotencyKey != null
131
+ ? { idempotencyKey: writeOptions.idempotencyKey }
132
+ : {}),
133
+ ...(writeOptions.label !== undefined ? { label: writeOptions.label } : {}),
134
+ };
135
+ }
136
+ return operation;
137
+ }
138
+ export function asTransportError(value) {
139
+ return (value && typeof value === 'object' ? value : {});
140
+ }
141
+ export function extractStatusCode(error) {
142
+ return asTransportError(error).response?.status;
143
+ }
@@ -0,0 +1,58 @@
1
+ /**
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.
9
+ */
10
+ import type { SyncPosition } from '../sync/syncPosition.js';
11
+ import type { Transaction } from './commitPayload.js';
12
+ /**
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
+ */
21
+ export interface DeltaConfirmationContext {
22
+ store: {
23
+ get(id: string): Transaction | undefined;
24
+ getByStatus(status: Transaction['status']): Transaction[];
25
+ updateStatus(id: string, status: Transaction['status']): void;
26
+ };
27
+ optimisticUpdates: {
28
+ delete(id: string): boolean;
29
+ };
30
+ emit(event: string, payload?: unknown): void;
31
+ isConnected(): boolean;
32
+ position: SyncPosition;
33
+ }
34
+ export declare class DeltaConfirmationTracker {
35
+ private readonly ctx;
36
+ private static readonly DELTA_MAX_RETRIES;
37
+ private static readonly DELTA_MAX_TIMEOUT_MS;
38
+ private deltaConfirmationTimeouts;
39
+ private deltaConfirmationRetries;
40
+ constructor(ctx: DeltaConfirmationContext);
41
+ /** Applied-cursor alias, kept so the read sites below stay legible. */
42
+ private get lastSeenSyncId();
43
+ noteAck(lastSyncId: number | undefined): void;
44
+ /**
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.
48
+ */
49
+ onDeltaReceived(syncId: number): void;
50
+ scheduleDeltaConfirmationTimeout(tx: Transaction, timeoutMs: number): void;
51
+ private cancelDeltaConfirmationTimeout;
52
+ /**
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.
56
+ */
57
+ dispose(): void;
58
+ }
@@ -0,0 +1,215 @@
1
+ /**
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.
9
+ */
10
+ import { getContext } 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
+ constructor(ctx) {
24
+ this.ctx = ctx;
25
+ }
26
+ /** Applied-cursor alias, kept so the read sites below stay legible. */
27
+ get lastSeenSyncId() {
28
+ return this.ctx.position.applied;
29
+ }
30
+ noteAck(lastSyncId) {
31
+ this.ctx.position.noteAck(lastSyncId);
32
+ }
33
+ /**
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.
37
+ */
38
+ onDeltaReceived(syncId) {
39
+ // The cursor advances where the delta is applied (the store calls
40
+ // position.advanceApplied / advancePersisted); this hook only resolves
41
+ // confirmation thresholds against the incoming id.
42
+ const awaitingTxs = this.ctx.store.getByStatus('awaiting_delta');
43
+ const executingTxs = this.ctx.store.getByStatus('executing');
44
+ // Debug: Show state when delta arrives
45
+ if (awaitingTxs.length > 0 || executingTxs.length > 0) {
46
+ getContext().logger.debug('tx:delta_received', {
47
+ syncId,
48
+ lastSeenSyncId: this.lastSeenSyncId,
49
+ awaitingCount: awaitingTxs.length,
50
+ executingCount: executingTxs.length,
51
+ awaitingThresholds: awaitingTxs.map((tx) => ({
52
+ txId: tx.id.slice(0, 8),
53
+ model: tx.modelName,
54
+ needed: tx.syncIdNeededForCompletion,
55
+ willConfirm: tx.syncIdNeededForCompletion !== undefined && syncId >= tx.syncIdNeededForCompletion,
56
+ })),
57
+ });
58
+ }
59
+ // Fast path: no awaiting transactions
60
+ if (awaitingTxs.length === 0)
61
+ return;
62
+ let confirmedCount = 0;
63
+ for (const tx of awaitingTxs) {
64
+ // Confirm if this delta's ID meets or exceeds the threshold
65
+ if (tx.syncIdNeededForCompletion !== undefined && syncId >= tx.syncIdNeededForCompletion) {
66
+ this.cancelDeltaConfirmationTimeout(tx.id);
67
+ this.ctx.store.updateStatus(tx.id, 'completed');
68
+ this.ctx.emit('transaction:completed', tx);
69
+ this.ctx.emit(`transaction:completed:${tx.id}`, tx);
70
+ this.ctx.optimisticUpdates.delete(tx.id);
71
+ confirmedCount++;
72
+ getContext().logger.debug('tx:confirm_via_delta', {
73
+ txId: tx.id.slice(0, 8),
74
+ model: tx.modelName,
75
+ neededSyncId: tx.syncIdNeededForCompletion,
76
+ receivedSyncId: syncId,
77
+ });
78
+ }
79
+ }
80
+ // Log batch summary only if we confirmed something
81
+ if (confirmedCount > 0) {
82
+ // Leave a breadcrumb when transactions confirm.
83
+ getContext().observability.breadcrumb('Transactions confirmed via delta', 'sync.transaction', 'info', {
84
+ count: confirmedCount,
85
+ syncId,
86
+ remainingAwaiting: awaitingTxs.length - confirmedCount,
87
+ });
88
+ }
89
+ }
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.
95
+ scheduleDeltaConfirmationTimeout(tx, timeoutMs) {
96
+ // Cancel any existing timeout for this transaction
97
+ this.cancelDeltaConfirmationTimeout(tx.id);
98
+ // Deliberately not an async callback: the body is fully synchronous, and
99
+ // `setTimeout(async …)` would turn any throw into an unhandled promise
100
+ // rejection instead of a catchable synchronous error.
101
+ const timeoutHandle = setTimeout(() => {
102
+ const currentTx = this.ctx.store.get(tx.id);
103
+ if (!currentTx || currentTx.status !== 'awaiting_delta') {
104
+ this.deltaConfirmationRetries.delete(tx.id);
105
+ return; // Already confirmed or failed
106
+ }
107
+ // If disconnected, re-schedule with same timeout (no backoff while offline)
108
+ if (!this.ctx.isConnected()) {
109
+ // Self-healing: re-schedule the confirmation wait while offline, no
110
+ // consumer action needed → debug.
111
+ getContext().logger.debug('[TransactionQueue] Timeout fired while disconnected - re-scheduling', {
112
+ txId: tx.id.slice(0, 8),
113
+ model: tx.modelName,
114
+ });
115
+ this.deltaConfirmationTimeouts.delete(tx.id);
116
+ this.scheduleDeltaConfirmationTimeout(tx, timeoutMs);
117
+ return;
118
+ }
119
+ const retryCount = this.deltaConfirmationRetries.get(tx.id) ?? 0;
120
+ getContext().observability.captureReconciliation({
121
+ reason: 'delta_timeout',
122
+ model: tx.modelName,
123
+ modelId: tx.modelId,
124
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
125
+ lastSeenSyncId: this.lastSeenSyncId,
126
+ retryCount,
127
+ connectionState: this.ctx.isConnected() ? 'connected' : 'disconnected',
128
+ });
129
+ if (retryCount < DeltaConfirmationTracker.DELTA_MAX_RETRIES) {
130
+ // Retry: request reconciliation and re-schedule with exponential
131
+ // backoff. The server has already committed the mutation; only the
132
+ // delta is outstanding.
133
+ this.deltaConfirmationRetries.set(tx.id, retryCount + 1);
134
+ this.deltaConfirmationTimeouts.delete(tx.id);
135
+ // Exponential backoff: 30s → 60s → 120s → 120s → 120s (capped)
136
+ const nextTimeout = Math.min(timeoutMs * 2, DeltaConfirmationTracker.DELTA_MAX_TIMEOUT_MS);
137
+ // Request reconciliation so the client can cycle the connection and
138
+ // catch up on missed deltas from the server.
139
+ this.ctx.emit('reconciliation:needed', {
140
+ reason: 'delta_confirmation_timeout',
141
+ txId: tx.id,
142
+ model: tx.modelName,
143
+ modelId: tx.modelId,
144
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
145
+ lastSeenSyncId: this.lastSeenSyncId,
146
+ retryCount: retryCount + 1,
147
+ });
148
+ // Self-healing retry with backoff — the server already committed; we're
149
+ // just waiting on the delta. No consumer action → debug.
150
+ getContext().logger.debug('[TransactionQueue] Re-scheduling with backoff', {
151
+ txId: tx.id.slice(0, 8),
152
+ model: tx.modelName,
153
+ nextTimeoutMs: nextTimeout,
154
+ retry: retryCount + 1,
155
+ });
156
+ this.scheduleDeltaConfirmationTimeout(tx, nextTimeout);
157
+ }
158
+ else {
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.
163
+ this.deltaConfirmationRetries.delete(tx.id);
164
+ this.deltaConfirmationTimeouts.delete(tx.id);
165
+ getContext().observability.captureDeltaRetryExhausted({
166
+ txId: tx.id,
167
+ model: tx.modelName,
168
+ modelId: tx.modelId,
169
+ retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
170
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
171
+ });
172
+ // Emit the persist event; the client performs the write to local storage.
173
+ this.ctx.emit('transaction:persist_awaiting', {
174
+ txId: tx.id,
175
+ model: tx.modelName,
176
+ modelId: tx.modelId,
177
+ operationType: tx.type,
178
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
179
+ });
180
+ // Also request one final reconciliation cycle
181
+ this.ctx.emit('reconciliation:needed', {
182
+ reason: 'delta_retries_exhausted',
183
+ txId: tx.id,
184
+ model: tx.modelName,
185
+ modelId: tx.modelId,
186
+ syncIdNeeded: currentTx.syncIdNeededForCompletion,
187
+ lastSeenSyncId: this.lastSeenSyncId,
188
+ retryCount: DeltaConfirmationTracker.DELTA_MAX_RETRIES,
189
+ });
190
+ }
191
+ }, timeoutMs);
192
+ this.deltaConfirmationTimeouts.set(tx.id, timeoutHandle);
193
+ }
194
+ // Cancel a pending delta confirmation timeout and clean up retry tracking
195
+ cancelDeltaConfirmationTimeout(id) {
196
+ const timeoutHandle = this.deltaConfirmationTimeouts.get(id);
197
+ if (timeoutHandle) {
198
+ clearTimeout(timeoutHandle);
199
+ this.deltaConfirmationTimeouts.delete(id);
200
+ }
201
+ this.deltaConfirmationRetries.delete(id);
202
+ }
203
+ /**
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.
207
+ */
208
+ dispose() {
209
+ for (const timeoutHandle of this.deltaConfirmationTimeouts.values()) {
210
+ clearTimeout(timeoutHandle);
211
+ }
212
+ this.deltaConfirmationTimeouts.clear();
213
+ this.deltaConfirmationRetries.clear();
214
+ }
215
+ }
@@ -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
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The validation boundary for replaying persisted transactions after a restart.
3
+ *
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.
10
+ *
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.
16
+ */
17
+ import { z } from 'zod';
18
+ import type { Transaction } from './commitPayload.js';
19
+ /**
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}.
25
+ */
26
+ export declare const persistedTransactionSchema: z.ZodObject<{
27
+ id: z.ZodString;
28
+ type: z.ZodEnum<{
29
+ update: "update";
30
+ create: "create";
31
+ delete: "delete";
32
+ archive: "archive";
33
+ unarchive: "unarchive";
34
+ }>;
35
+ modelName: z.ZodString;
36
+ modelId: z.ZodString;
37
+ modelKey: z.ZodOptional<z.ZodString>;
38
+ data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
39
+ previousData: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
40
+ context: z.ZodObject<{
41
+ userId: z.ZodString;
42
+ organizationId: z.ZodString;
43
+ role: z.ZodOptional<z.ZodString>;
44
+ teamIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
45
+ }, z.core.$strip>;
46
+ createdAt: z.ZodOptional<z.ZodNumber>;
47
+ batchId: z.ZodOptional<z.ZodString>;
48
+ writeOptions: z.ZodOptional<z.ZodObject<{
49
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
50
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
51
+ reject: "reject";
52
+ overwrite: "overwrite";
53
+ notify: "notify";
54
+ }>>>;
55
+ idempotencyKey: z.ZodOptional<z.ZodString>;
56
+ label: z.ZodOptional<z.ZodString>;
57
+ }, z.core.$loose>>;
58
+ localOnly: z.ZodOptional<z.ZodBoolean>;
59
+ }, z.core.$loose>;
60
+ export type PersistedReplayableTransaction = z.infer<typeof persistedTransactionSchema>;
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
+ */
65
+ export declare function isNonReplayablePersistedRow(row: unknown): boolean;
66
+ /**
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.
71
+ */
72
+ export declare function deserializePersistedTransaction(row: unknown): Transaction | null;
73
+ /**
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.
77
+ */
78
+ export declare const persistedMutationSchema: z.ZodObject<{
79
+ type: z.ZodEnum<{
80
+ update: "update";
81
+ create: "create";
82
+ delete: "delete";
83
+ archive: "archive";
84
+ }>;
85
+ modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
86
+ modelName: z.ZodString;
87
+ timestamp: z.ZodString;
88
+ writeOptions: z.ZodOptional<z.ZodObject<{
89
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
90
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
91
+ reject: "reject";
92
+ overwrite: "overwrite";
93
+ notify: "notify";
94
+ }>>>;
95
+ idempotencyKey: z.ZodOptional<z.ZodString>;
96
+ label: z.ZodOptional<z.ZodString>;
97
+ }, z.core.$loose>>;
98
+ }, z.core.$loose>;
99
+ export type PersistedQueuedMutation = z.infer<typeof persistedMutationSchema>;