@abloatai/ablo 0.34.1 → 0.36.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 (536) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +758 -5
  3. package/README.md +56 -502
  4. package/bin/ablo.cjs +39 -0
  5. package/dist/BaseSyncedStore.d.ts +176 -48
  6. package/dist/BaseSyncedStore.js +346 -214
  7. package/dist/Database.d.ts +17 -44
  8. package/dist/Database.js +96 -79
  9. package/dist/InstanceCache.d.ts +31 -6
  10. package/dist/InstanceCache.js +65 -30
  11. package/dist/LazyReferenceCollection.d.ts +3 -3
  12. package/dist/LazyReferenceCollection.js +4 -4
  13. package/dist/Model.d.ts +23 -13
  14. package/dist/Model.js +27 -17
  15. package/dist/ModelRegistry.d.ts +8 -4
  16. package/dist/ModelRegistry.js +20 -18
  17. package/dist/NetworkMonitor.d.ts +3 -1
  18. package/dist/NetworkMonitor.js +7 -5
  19. package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
  20. package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
  21. package/dist/SyncClient.d.ts +47 -47
  22. package/dist/SyncClient.js +215 -156
  23. package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
  24. package/dist/ai-sdk/coordinatedTool.js +1 -1
  25. package/dist/ai-sdk/coordinationContext.d.ts +2 -2
  26. package/dist/ai-sdk/coordinationContext.js +1 -1
  27. package/dist/ai-sdk/wrap.d.ts +3 -3
  28. package/dist/ai-sdk/wrap.js +2 -2
  29. package/dist/auth/index.d.ts +1 -156
  30. package/dist/auth/index.js +8 -301
  31. package/dist/client/Ablo.d.ts +42 -287
  32. package/dist/client/Ablo.js +129 -963
  33. package/dist/client/abloClient.d.ts +309 -0
  34. package/dist/client/abloClient.js +13 -0
  35. package/dist/client/clientPrelude.d.ts +52 -0
  36. package/dist/client/clientPrelude.js +60 -0
  37. package/dist/client/consoleLogger.d.ts +2 -2
  38. package/dist/client/coreClient.d.ts +60 -0
  39. package/dist/client/coreClient.js +118 -0
  40. package/dist/client/createInternalComponents.d.ts +8 -4
  41. package/dist/client/createInternalComponents.js +17 -10
  42. package/dist/client/createModelProxy.d.ts +98 -373
  43. package/dist/client/createModelProxy.js +233 -139
  44. package/dist/client/humans.d.ts +69 -0
  45. package/dist/client/humans.js +78 -0
  46. package/dist/client/modelRegistration.d.ts +1 -1
  47. package/dist/client/modelRegistration.js +9 -9
  48. package/dist/client/options.d.ts +73 -17
  49. package/dist/client/reactiveEngine.d.ts +53 -0
  50. package/dist/client/reactiveEngine.js +688 -0
  51. package/dist/client/resourceTypes.d.ts +9 -250
  52. package/dist/client/resourceTypes.js +8 -5
  53. package/dist/client/schemaConfig.d.ts +4 -4
  54. package/dist/client/schemaConfig.js +6 -2
  55. package/dist/client/storeCluster.d.ts +47 -0
  56. package/dist/client/storeCluster.js +118 -0
  57. package/dist/client/storeLifecycle.d.ts +61 -0
  58. package/dist/client/storeLifecycle.js +231 -0
  59. package/dist/client/validateAbloOptions.d.ts +3 -2
  60. package/dist/client/validateAbloOptions.js +1 -1
  61. package/dist/client/wsMutationExecutor.d.ts +3 -3
  62. package/dist/client/wsMutationExecutor.js +3 -3
  63. package/dist/context.d.ts +22 -9
  64. package/dist/context.js +33 -9
  65. package/dist/coordination/ClaimLog.d.ts +26 -0
  66. package/dist/coordination/ClaimLog.js +32 -0
  67. package/dist/coordination/index.d.ts +1 -15
  68. package/dist/coordination/index.js +8 -31
  69. package/dist/core/index.d.ts +3 -3
  70. package/dist/core/index.js +2 -2
  71. package/dist/docs/catalog.d.ts +72 -0
  72. package/dist/docs/catalog.js +230 -0
  73. package/dist/docs/index.d.ts +10 -0
  74. package/dist/docs/index.js +10 -0
  75. package/dist/environment.d.ts +1 -40
  76. package/dist/environment.js +8 -37
  77. package/dist/index.d.ts +44 -36
  78. package/dist/index.js +30 -22
  79. package/dist/interfaces/index.d.ts +44 -134
  80. package/dist/keys/index.d.ts +1 -77
  81. package/dist/keys/index.js +8 -190
  82. package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
  83. package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
  84. package/dist/mutators/Transaction.d.ts +1 -1
  85. package/dist/mutators/Transaction.js +1 -1
  86. package/dist/mutators/UndoManager.d.ts +6 -6
  87. package/dist/mutators/UndoManager.js +5 -5
  88. package/dist/mutators/defineMutators.d.ts +3 -3
  89. package/dist/mutators/defineMutators.js +1 -1
  90. package/dist/mutators/inverseOp.js +2 -2
  91. package/dist/mutators/mutateActions.d.ts +3 -3
  92. package/dist/mutators/mutateActions.js +1 -1
  93. package/dist/mutators/readerActions.d.ts +1 -1
  94. package/dist/mutators/undoApply.d.ts +1 -1
  95. package/dist/mutators/undoApply.js +1 -1
  96. package/dist/policy/index.d.ts +2 -2
  97. package/dist/policy/index.js +1 -1
  98. package/dist/query/client.d.ts +5 -2
  99. package/dist/query/client.js +10 -9
  100. package/dist/query/types.d.ts +6 -41
  101. package/dist/query/types.js +2 -2
  102. package/dist/react/AbloProvider.d.ts +18 -8
  103. package/dist/react/AbloProvider.js +10 -9
  104. package/dist/react/context.d.ts +3 -3
  105. package/dist/react/context.js +1 -1
  106. package/dist/react/createAbloReact.d.ts +56 -0
  107. package/dist/react/createAbloReact.js +51 -0
  108. package/dist/react/index.d.ts +6 -5
  109. package/dist/react/index.js +6 -3
  110. package/dist/react/internalContext.d.ts +1 -1
  111. package/dist/react/useAblo.d.ts +12 -5
  112. package/dist/react/useAblo.js +26 -8
  113. package/dist/react/useCurrentUserId.js +1 -1
  114. package/dist/react/useErrorListener.js +1 -1
  115. package/dist/react/useMutationFailureListener.d.ts +2 -2
  116. package/dist/react/useMutationFailureListener.js +1 -1
  117. package/dist/react/useMutators.d.ts +3 -3
  118. package/dist/react/useMutators.js +3 -3
  119. package/dist/react/useUndoScope.d.ts +5 -5
  120. package/dist/react/useUndoScope.js +1 -1
  121. package/dist/schema/coordination.d.ts +69 -10
  122. package/dist/schema/coordination.js +90 -9
  123. package/dist/schema/ddl.js +2 -2
  124. package/dist/schema/diff.d.ts +1 -1
  125. package/dist/schema/generate.js +1 -1
  126. package/dist/schema/index.d.ts +11 -10
  127. package/dist/schema/index.js +22 -18
  128. package/dist/schema/queries.d.ts +27 -27
  129. package/dist/schema/queries.js +23 -23
  130. package/dist/schema/select.d.ts +3 -3
  131. package/dist/schema/select.js +6 -3
  132. package/dist/schema/serialize.d.ts +15 -6
  133. package/dist/schema/serialize.js +20 -3
  134. package/dist/schema/sugar.d.ts +6 -7
  135. package/dist/schema/sugar.js +9 -12
  136. package/dist/schema/syncDeltaRow.d.ts +4 -152
  137. package/dist/schema/syncDeltaRow.js +4 -105
  138. package/dist/server/adapter.d.ts +18 -1
  139. package/dist/server/commit.d.ts +10 -16
  140. package/dist/server/index.d.ts +1 -1
  141. package/dist/server/index.js +1 -1
  142. package/dist/server/readConfig.d.ts +1 -1
  143. package/dist/source/adapter.d.ts +7 -5
  144. package/dist/source/adapter.js +7 -5
  145. package/dist/source/adapters/drizzle.d.ts +1 -1
  146. package/dist/source/adapters/drizzle.js +2 -2
  147. package/dist/source/adapters/kysely.d.ts +1 -1
  148. package/dist/source/adapters/kysely.js +1 -1
  149. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  150. package/dist/source/adapters/kyselyMutationCore.js +2 -2
  151. package/dist/source/adapters/memory.js +1 -1
  152. package/dist/source/adapters/prisma.d.ts +8 -3
  153. package/dist/source/adapters/prisma.js +1 -1
  154. package/dist/source/connector.js +1 -1
  155. package/dist/source/connectorProtocol.d.ts +2 -8
  156. package/dist/source/connectorProtocol.js +3 -2
  157. package/dist/source/contract.d.ts +29 -17
  158. package/dist/source/contract.js +27 -22
  159. package/dist/source/factory.d.ts +1 -1
  160. package/dist/source/idempotency.js +2 -2
  161. package/dist/source/index.d.ts +1 -0
  162. package/dist/source/index.js +3 -0
  163. package/dist/source/next.d.ts +1 -1
  164. package/dist/source/signing.d.ts +9 -2
  165. package/dist/source/signing.js +4 -1
  166. package/dist/source/types.d.ts +6 -4
  167. package/dist/source/types.js +1 -1
  168. package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
  169. package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
  170. package/dist/{core → stores}/DatabaseManager.js +14 -13
  171. package/dist/stores/ObjectStore.d.ts +1 -1
  172. package/dist/{core → stores}/StoreManager.d.ts +9 -26
  173. package/dist/{core → stores}/StoreManager.js +29 -77
  174. package/dist/stores/SyncActionStore.d.ts +4 -2
  175. package/dist/stores/SyncActionStore.js +11 -17
  176. package/dist/stores/syncAction.d.ts +26 -0
  177. package/dist/stores/syncAction.js +16 -0
  178. package/dist/surface.d.ts +3 -3
  179. package/dist/surface.js +6 -4
  180. package/dist/sync/BootstrapFetcher.d.ts +127 -6
  181. package/dist/sync/BootstrapFetcher.js +511 -83
  182. package/dist/sync/ConnectionManager.d.ts +6 -198
  183. package/dist/sync/ConnectionManager.js +6 -677
  184. package/dist/sync/OnDemandLoader.d.ts +5 -2
  185. package/dist/sync/OnDemandLoader.js +61 -21
  186. package/dist/sync/SubscriptionManager.d.ts +13 -2
  187. package/dist/sync/SubscriptionManager.js +23 -5
  188. package/dist/sync/SyncWebSocket.d.ts +27 -510
  189. package/dist/sync/SyncWebSocket.js +76 -954
  190. package/dist/sync/awaitClaimGrant.d.ts +4 -44
  191. package/dist/sync/awaitClaimGrant.js +4 -109
  192. package/dist/sync/bootstrapApply.d.ts +3 -0
  193. package/dist/sync/bootstrapApply.js +2 -2
  194. package/dist/sync/commitFrames.d.ts +6 -40
  195. package/dist/sync/commitFrames.js +6 -97
  196. package/dist/sync/contextPorts.d.ts +18 -0
  197. package/dist/sync/contextPorts.js +31 -0
  198. package/dist/sync/createClaimStream.d.ts +5 -49
  199. package/dist/sync/createClaimStream.js +5 -469
  200. package/dist/sync/createPresenceStream.d.ts +26 -4
  201. package/dist/sync/createPresenceStream.js +28 -20
  202. package/dist/sync/createSnapshot.d.ts +2 -2
  203. package/dist/sync/createSnapshot.js +1 -1
  204. package/dist/sync/credentialLifecycle.d.ts +5 -173
  205. package/dist/sync/credentialLifecycle.js +5 -320
  206. package/dist/sync/deltaPipeline.d.ts +13 -12
  207. package/dist/sync/deltaPipeline.js +21 -4
  208. package/dist/sync/groupChange.d.ts +3 -0
  209. package/dist/sync/groupChange.js +16 -14
  210. package/dist/sync/participants.d.ts +24 -6
  211. package/dist/sync/participants.js +32 -23
  212. package/dist/sync/schemaDrift.d.ts +55 -0
  213. package/dist/sync/schemaDrift.js +53 -0
  214. package/dist/sync/schemas.d.ts +23 -33
  215. package/dist/sync/schemas.js +29 -20
  216. package/dist/sync/syncPlan.d.ts +3 -3
  217. package/dist/sync/wsFrameHandlers.d.ts +6 -114
  218. package/dist/sync/wsFrameHandlers.js +6 -392
  219. package/dist/syncLog/contract.d.ts +20 -0
  220. package/dist/syncLog/contract.js +19 -0
  221. package/dist/syncLog/index.d.ts +1 -0
  222. package/dist/syncLog/index.js +1 -0
  223. package/dist/transaction/ablo.d.ts +88 -0
  224. package/dist/transaction/ablo.js +33 -0
  225. package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
  226. package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
  227. package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
  228. package/dist/transaction/auth/bootstrapScope.js +1 -0
  229. package/dist/transaction/auth/capability.d.ts +212 -0
  230. package/dist/transaction/auth/capability.js +224 -0
  231. package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
  232. package/dist/{client → transaction/auth}/identity.d.ts +8 -7
  233. package/dist/{client → transaction/auth}/identity.js +1 -1
  234. package/dist/transaction/auth/index.d.ts +162 -0
  235. package/dist/transaction/auth/index.js +304 -0
  236. package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
  237. package/dist/{auth → transaction/auth}/schemas.js +13 -13
  238. package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
  239. package/dist/{client → transaction/auth}/sessionMint.js +4 -7
  240. package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
  241. package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
  242. package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
  243. package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
  244. package/dist/transaction/coordination/claimMeta.d.ts +49 -0
  245. package/dist/transaction/coordination/claimMeta.js +52 -0
  246. package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
  247. package/dist/transaction/coordination/createClaimStream.js +475 -0
  248. package/dist/transaction/coordination/events.d.ts +74 -0
  249. package/dist/transaction/coordination/events.js +7 -0
  250. package/dist/transaction/coordination/index.d.ts +19 -0
  251. package/dist/transaction/coordination/index.js +45 -0
  252. package/dist/transaction/coordination/locator.d.ts +104 -0
  253. package/dist/transaction/coordination/locator.js +102 -0
  254. package/dist/transaction/coordination/schema.d.ts +1536 -0
  255. package/dist/transaction/coordination/schema.js +1177 -0
  256. package/dist/transaction/coordination/targetConflict.d.ts +2 -0
  257. package/dist/transaction/coordination/targetConflict.js +107 -0
  258. package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
  259. package/dist/{coordination → transaction/coordination}/trace.js +18 -25
  260. package/dist/transaction/durableWrites.d.ts +62 -0
  261. package/dist/{client → transaction}/durableWrites.js +28 -3
  262. package/dist/transaction/environment.d.ts +105 -0
  263. package/dist/transaction/environment.js +108 -0
  264. package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
  265. package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
  266. package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
  267. package/dist/{errors.js → transaction/errors.js} +85 -16
  268. package/dist/transaction/footprint.d.ts +111 -0
  269. package/dist/transaction/footprint.js +0 -0
  270. package/dist/transaction/index.d.ts +20 -0
  271. package/dist/transaction/index.js +20 -0
  272. package/dist/transaction/keys/index.d.ts +87 -0
  273. package/dist/transaction/keys/index.js +207 -0
  274. package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
  275. package/dist/transaction/log/syncDeltaRow.js +95 -0
  276. package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
  277. package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
  278. package/dist/transaction/logger.d.ts +16 -0
  279. package/dist/transaction/logger.js +7 -0
  280. package/dist/transaction/observability.d.ts +53 -0
  281. package/dist/transaction/observability.js +19 -0
  282. package/dist/transaction/plugin.d.ts +285 -0
  283. package/dist/transaction/plugin.js +106 -0
  284. package/dist/{policy → transaction/policy}/types.d.ts +3 -3
  285. package/dist/{policy → transaction/policy}/types.js +2 -0
  286. package/dist/transaction/resources/httpResources.d.ts +321 -0
  287. package/dist/transaction/resources/httpResources.js +7 -0
  288. package/dist/transaction/resources/modelOperations.d.ts +427 -0
  289. package/dist/transaction/resources/modelOperations.js +12 -0
  290. package/dist/transaction/resources/mutationOptions.d.ts +66 -0
  291. package/dist/transaction/resources/mutationOptions.js +9 -0
  292. package/dist/transaction/resources/where.d.ts +101 -0
  293. package/dist/transaction/resources/where.js +115 -0
  294. package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
  295. package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
  296. package/dist/{schema → transaction/schema}/field.d.ts +17 -23
  297. package/dist/{schema → transaction/schema}/field.js +5 -5
  298. package/dist/transaction/schema/fieldRef.d.ts +38 -0
  299. package/dist/transaction/schema/fieldRef.js +11 -0
  300. package/dist/transaction/schema/loadStrategy.d.ts +45 -0
  301. package/dist/transaction/schema/loadStrategy.js +46 -0
  302. package/dist/{schema → transaction/schema}/model.d.ts +50 -35
  303. package/dist/{schema → transaction/schema}/model.js +30 -20
  304. package/dist/transaction/schema/openapi.d.ts +58 -0
  305. package/dist/transaction/schema/openapi.js +501 -0
  306. package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
  307. package/dist/{schema → transaction/schema}/relation.js +7 -7
  308. package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
  309. package/dist/{schema → transaction/schema}/residency.js +0 -5
  310. package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
  311. package/dist/{schema → transaction/schema}/roles.js +5 -5
  312. package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
  313. package/dist/{schema → transaction/schema}/schema.js +24 -3
  314. package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
  315. package/dist/{schema → transaction/schema}/tenancy.js +7 -4
  316. package/dist/transaction/transactionLayer.d.ts +82 -0
  317. package/dist/transaction/transactionLayer.js +24 -0
  318. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
  319. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
  320. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
  321. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
  322. package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
  323. package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
  324. package/dist/transaction/transport/commitFrames.d.ts +90 -0
  325. package/dist/transaction/transport/commitFrames.js +134 -0
  326. package/dist/transaction/transport/connectionManager.d.ts +215 -0
  327. package/dist/transaction/transport/connectionManager.js +673 -0
  328. package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
  329. package/dist/transaction/transport/credentialLifecycle.js +324 -0
  330. package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
  331. package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
  332. package/dist/transaction/transport/httpClient.d.ts +131 -0
  333. package/dist/{client → transaction/transport}/httpClient.js +6 -5
  334. package/dist/transaction/transport/httpOptions.d.ts +33 -0
  335. package/dist/transaction/transport/httpOptions.js +12 -0
  336. package/dist/{client → transaction/transport}/httpTransport.js +295 -97
  337. package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
  338. package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
  339. package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
  340. package/dist/transaction/transport/wsFrameHandlers.js +429 -0
  341. package/dist/transaction/transport/wsTransport.d.ts +574 -0
  342. package/dist/transaction/transport/wsTransport.js +1023 -0
  343. package/dist/transaction/types/assertExact.d.ts +17 -0
  344. package/dist/transaction/types/assertExact.js +1 -0
  345. package/dist/{types → transaction/types}/global.d.ts +17 -2
  346. package/dist/{types → transaction/types}/global.js +2 -1
  347. package/dist/{types → transaction/types}/index.d.ts +14 -46
  348. package/dist/{types → transaction/types}/index.js +7 -16
  349. package/dist/{types → transaction/types}/streams.d.ts +73 -45
  350. package/dist/transaction/utils/duration.d.ts +50 -0
  351. package/dist/{utils → transaction/utils}/duration.js +32 -0
  352. package/dist/{utils → transaction/utils}/json.d.ts +18 -0
  353. package/dist/transaction/utils/json.js +276 -0
  354. package/dist/transaction/wire/accountResponses.d.ts +420 -0
  355. package/dist/transaction/wire/accountResponses.js +290 -0
  356. package/dist/transaction/wire/auth.d.ts +56 -0
  357. package/dist/transaction/wire/auth.js +63 -0
  358. package/dist/transaction/wire/claimEvent.d.ts +76 -0
  359. package/dist/transaction/wire/claimEvent.js +73 -0
  360. package/dist/transaction/wire/claims.d.ts +530 -0
  361. package/dist/transaction/wire/claims.js +327 -0
  362. package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
  363. package/dist/{wire → transaction/wire}/commit.js +68 -47
  364. package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
  365. package/dist/{wire → transaction/wire}/delta.js +37 -13
  366. package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
  367. package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
  368. package/dist/transaction/wire/feedCursor.d.ts +60 -0
  369. package/dist/transaction/wire/feedCursor.js +82 -0
  370. package/dist/transaction/wire/feedEvent.d.ts +204 -0
  371. package/dist/transaction/wire/feedEvent.js +65 -0
  372. package/dist/transaction/wire/frames.d.ts +194 -0
  373. package/dist/transaction/wire/frames.js +50 -0
  374. package/dist/transaction/wire/inboundFrames.d.ts +562 -0
  375. package/dist/transaction/wire/inboundFrames.js +116 -0
  376. package/dist/transaction/wire/index.d.ts +54 -0
  377. package/dist/transaction/wire/index.js +83 -0
  378. package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
  379. package/dist/transaction/wire/listEnvelope.js +42 -0
  380. package/dist/transaction/wire/modelMutations.d.ts +31 -0
  381. package/dist/transaction/wire/modelMutations.js +52 -0
  382. package/dist/transaction/wire/modelResponses.d.ts +85 -0
  383. package/dist/transaction/wire/modelResponses.js +43 -0
  384. package/dist/transaction/wire/modelShape.d.ts +78 -0
  385. package/dist/transaction/wire/modelShape.js +74 -0
  386. package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
  387. package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
  388. package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
  389. package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
  390. package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
  391. package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
  392. package/dist/transactions/mutations/commitLatency.d.ts +52 -0
  393. package/dist/transactions/mutations/commitLatency.js +130 -0
  394. package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
  395. package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
  396. package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
  397. package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
  398. package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
  399. package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
  400. package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
  401. package/dist/transactions/mutations/durableWriteStore.js +12 -0
  402. package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
  403. package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
  404. package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
  405. package/dist/utils/mobxSetup.d.ts +1 -1
  406. package/dist/utils/mobxSetup.js +5 -2
  407. package/dist/{core → views}/QueryView.d.ts +2 -2
  408. package/dist/{core → views}/QueryView.js +2 -2
  409. package/dist/{core → views}/ViewRegistry.d.ts +1 -1
  410. package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
  411. package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
  412. package/dist/webhooks/events.d.ts +2 -2
  413. package/dist/wire/index.d.ts +1 -34
  414. package/dist/wire/index.js +8 -49
  415. package/docs/agent-messaging.md +3 -3
  416. package/docs/agents.md +20 -13
  417. package/docs/api-keys.md +14 -10
  418. package/docs/api.md +27 -61
  419. package/docs/audit.md +6 -3
  420. package/docs/cli.md +41 -13
  421. package/docs/client-behavior.md +11 -9
  422. package/docs/concurrency-convention.md +49 -57
  423. package/docs/coordination.md +283 -121
  424. package/docs/data-sources.md +7 -5
  425. package/docs/debugging.md +39 -15
  426. package/docs/deployment.md +267 -0
  427. package/docs/examples/agent-human.md +49 -42
  428. package/docs/examples/ai-sdk-tool.md +69 -44
  429. package/docs/examples/existing-python-backend.md +8 -6
  430. package/docs/examples/nextjs.md +129 -47
  431. package/docs/examples/scoped-agent.md +46 -45
  432. package/docs/examples/server-agent.md +46 -26
  433. package/docs/groups.md +87 -30
  434. package/docs/guarantees.md +41 -12
  435. package/docs/how-it-works.md +38 -12
  436. package/docs/idempotency.md +126 -0
  437. package/docs/identity.md +77 -74
  438. package/docs/index.md +172 -86
  439. package/docs/integration-guide.md +31 -19
  440. package/docs/mcp.md +46 -21
  441. package/docs/migration.md +95 -18
  442. package/docs/operating-on-your-database.md +3 -1
  443. package/docs/projects.md +3 -1
  444. package/docs/quickstart.md +22 -5
  445. package/docs/react.md +31 -18
  446. package/docs/schema-contract.md +5 -3
  447. package/docs/session-settings.md +108 -0
  448. package/docs/sessions.md +4 -2
  449. package/docs/webhooks.md +12 -10
  450. package/llms.txt +48 -18
  451. package/package.json +21 -26
  452. package/dist/agent/Agent.d.ts +0 -366
  453. package/dist/agent/Agent.js +0 -514
  454. package/dist/agent/index.d.ts +0 -115
  455. package/dist/agent/index.js +0 -128
  456. package/dist/agent/session.d.ts +0 -93
  457. package/dist/agent/session.js +0 -149
  458. package/dist/agent/types.d.ts +0 -68
  459. package/dist/agent/types.js +0 -9
  460. package/dist/cli.cjs +0 -286329
  461. package/dist/client/durableWrites.d.ts +0 -21
  462. package/dist/client/httpClient.d.ts +0 -80
  463. package/dist/coordination/schema.d.ts +0 -722
  464. package/dist/coordination/schema.js +0 -578
  465. package/dist/schema/openapi.d.ts +0 -29
  466. package/dist/schema/openapi.js +0 -124
  467. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  468. package/dist/testing/fixtures/bootstrap.js +0 -59
  469. package/dist/testing/fixtures/deltas.d.ts +0 -83
  470. package/dist/testing/fixtures/deltas.js +0 -136
  471. package/dist/testing/fixtures/models.d.ts +0 -83
  472. package/dist/testing/fixtures/models.js +0 -272
  473. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  474. package/dist/testing/helpers/reactWrapper.js +0 -67
  475. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  476. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  477. package/dist/testing/helpers/wait.d.ts +0 -30
  478. package/dist/testing/helpers/wait.js +0 -49
  479. package/dist/testing/index.d.ts +0 -23
  480. package/dist/testing/index.js +0 -33
  481. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  482. package/dist/testing/mocks/FakeDatabase.js +0 -10
  483. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  484. package/dist/testing/mocks/MockMutationExecutor.js +0 -192
  485. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  486. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  487. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  488. package/dist/testing/mocks/MockSyncContext.js +0 -71
  489. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  490. package/dist/testing/mocks/MockSyncStore.js +0 -171
  491. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  492. package/dist/testing/mocks/MockWebSocket.js +0 -118
  493. package/dist/transactions/durableWriteStore.js +0 -30
  494. package/dist/utils/duration.d.ts +0 -25
  495. package/dist/utils/json.js +0 -88
  496. package/dist/wire/errorEnvelope.d.ts +0 -55
  497. package/dist/wire/frames.d.ts +0 -197
  498. package/dist/wire/frames.js +0 -49
  499. package/dist/wire/listEnvelope.js +0 -18
  500. package/docs/interaction-model.md +0 -97
  501. /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
  502. /package/dist/{core → query}/QueryProcessor.js +0 -0
  503. /package/dist/{core/storeContract.js → storeContract.js} +0 -0
  504. /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
  505. /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
  506. /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
  507. /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
  508. /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
  509. /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
  510. /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
  511. /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
  512. /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
  513. /package/dist/{client → transaction}/persistence.d.ts +0 -0
  514. /package/dist/{client → transaction}/persistence.js +0 -0
  515. /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
  516. /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
  517. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
  518. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
  519. /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
  520. /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
  521. /package/dist/{types → transaction/types}/modelData.js +0 -0
  522. /package/dist/{types → transaction/types}/participant.d.ts +0 -0
  523. /package/dist/{types → transaction/types}/participant.js +0 -0
  524. /package/dist/{types → transaction/types}/streams.js +0 -0
  525. /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
  526. /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
  527. /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
  528. /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
  529. /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
  530. /package/dist/{wire → transaction/wire}/protocol.js +0 -0
  531. /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
  532. /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
  533. /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
  534. /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
  535. /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
  536. /package/dist/{core → views}/ViewRegistry.js +0 -0
@@ -4,24 +4,26 @@
4
4
  * optimistic changes, holds them while the client is offline, sends them to
5
5
  * the server when connectivity returns, and resolves conflicts when the
6
6
  * server's version of a row disagrees with the local one. It sits between the
7
- * reactive object pool and the {@link TransactionQueue} that delivers writes
7
+ * reactive object pool and the {@link MutationQueue} that delivers writes
8
8
  * over the network.
9
9
  */
10
10
  import { runInAction } from 'mobx';
11
11
  import { v4 as uuid } from 'uuid';
12
12
  import { InstanceCache, ModelScope } from './InstanceCache.js';
13
13
  import { Model } from './Model.js';
14
+ import { snapshotJsonValue } from './transaction/utils/json.js';
14
15
  // ModelRegistry instance accessed via this.objectPool.registry
15
- import { LoadStrategy } from './types/index.js';
16
- import { getContext } from './context.js';
17
- import { AbloAuthenticationError, AbloError, AbloValidationError } from './errors.js';
16
+ import { LoadStrategy } from './transaction/types/index.js';
17
+ import { globalRuntime } from './context.js';
18
+ import { AbloAuthenticationError, AbloError, AbloValidationError } from './transaction/errors.js';
18
19
  import { EventEmitter } from 'events';
19
20
  import { NetworkMonitor } from './NetworkMonitor.js';
20
- import { TransactionQueue } from './transactions/TransactionQueue.js';
21
- import { legacyPendingMutationRecordSchema, PENDING_MUTATION_REPLAY_WINDOW_MS, pendingMutationRecordId, pendingMutationRecordSchema, persistedMutationSchema, } from './transactions/replayValidation.js';
22
- import { UnconfirmedWrites, } from './transactions/UnconfirmedWrites.js';
23
- import { SyncPosition } from './sync/syncPosition.js';
24
- import { DatabaseCommitOutboxStore, } from './transactions/commitOutboxStore.js';
21
+ import { MutationQueue } from './transactions/mutations/MutationQueue.js';
22
+ import { observeCommitLatency, } from './transactions/mutations/commitLatency.js';
23
+ import { legacyPendingMutationRecordSchema, PENDING_MUTATION_REPLAY_WINDOW_MS, pendingMutationRecordId, pendingMutationRecordSchema, persistedMutationSchema, } from './transactions/mutations/replayValidation.js';
24
+ import { UnconfirmedWrites, } from './transactions/mutations/UnconfirmedWrites.js';
25
+ import { LogPosition } from './transaction/logPosition.js';
26
+ import { DatabaseCommitOutboxStore, } from './transactions/mutations/commitOutboxStore.js';
25
27
  /**
26
28
  * Reports whether an incoming snapshot record is strictly newer than the
27
29
  * model already in the pool. The comparison uses the server-stamped
@@ -65,15 +67,16 @@ function toEpochMs(value) {
65
67
  return 0;
66
68
  }
67
69
  export class SyncClient extends EventEmitter {
70
+ runtime;
68
71
  objectPool;
69
72
  database;
70
- get mutationExecutor() { return getContext().mutationExecutor; }
73
+ get mutationExecutor() { return this.runtime.mutationExecutor; }
71
74
  networkMonitor;
72
75
  /**
73
76
  * @internal — test seam, stripped from the published declarations by
74
77
  * `stripInternal`. Unit suites deliver queue lifecycle events directly.
75
78
  */
76
- transactionQueue;
79
+ mutationQueue;
77
80
  observers = new Set();
78
81
  // Authentication context
79
82
  userId = null;
@@ -104,20 +107,22 @@ export class SyncClient extends EventEmitter {
104
107
  isDisposed = false;
105
108
  /**
106
109
  * The client's position in the global delta order, held as the single
107
- * canonical {@link SyncPosition} instance. The store advances `applied` and
110
+ * canonical {@link LogPosition} instance. The store advances `applied` and
108
111
  * `persisted` as deltas land, the queue advances `acked` on commit
109
112
  * responses, and snapshots and claims read `readFloor`.
110
113
  */
111
- position = new SyncPosition();
112
- constructor(objectPool, database, commitOutbox = new DatabaseCommitOutboxStore(database), commitOutboxNamespace = 'default') {
114
+ position = new LogPosition();
115
+ constructor(objectPool, database, commitOutbox = new DatabaseCommitOutboxStore(database), commitOutboxNamespace = 'default', runtime = globalRuntime) {
113
116
  super();
117
+ this.runtime = runtime;
114
118
  this.objectPool = objectPool;
115
119
  this.database = database;
116
120
  this.commitOutboxNamespace = commitOutboxNamespace;
117
- this.networkMonitor = new NetworkMonitor();
118
- // Initialize TransactionQueue with proper configuration
119
- this.transactionQueue = new TransactionQueue({
121
+ this.networkMonitor = new NetworkMonitor(this.runtime);
122
+ // Initialize MutationQueue with proper configuration
123
+ this.mutationQueue = new MutationQueue({
120
124
  position: this.position,
125
+ runtime: this.runtime,
121
126
  maxBatchSize: 50, // Larger batches keep the batch count low for bulk operations
122
127
  // A short delay keeps writes responsive; coalescing still groups them
123
128
  batchDelay: 150,
@@ -128,8 +133,8 @@ export class SyncClient extends EventEmitter {
128
133
  strategy: 'last-write-wins',
129
134
  },
130
135
  });
131
- this.transactionQueue.setCommitOutbox(commitOutbox);
132
- this.transactionQueue.on('commit:envelope_persisted', (event) => {
136
+ this.mutationQueue.setCommitOutbox(commitOutbox);
137
+ this.mutationQueue.on('commit:envelope_persisted', (event) => {
133
138
  if (event.sourceMutationIds.length === 0)
134
139
  return;
135
140
  const consumed = new Set(event.sourceMutationIds);
@@ -140,7 +145,7 @@ export class SyncClient extends EventEmitter {
140
145
  this.scheduleSync();
141
146
  }
142
147
  });
143
- this.transactionQueue.on('transaction:completed', (transaction) => {
148
+ this.mutationQueue.on('transaction:completed', (transaction) => {
144
149
  const completed = new Set(transaction.sourceMutationIds ?? []);
145
150
  if (completed.size > 0) {
146
151
  this.pendingMutations = this.pendingMutations.filter((mutation) => !completed.has(mutation.mutationId));
@@ -155,7 +160,7 @@ export class SyncClient extends EventEmitter {
155
160
  this.scheduleSync();
156
161
  }
157
162
  });
158
- this.transactionQueue.on('transaction:failed', ({ transaction }) => {
163
+ this.mutationQueue.on('transaction:failed', ({ transaction }) => {
159
164
  const failed = transaction.sourceMutationIds ?? [];
160
165
  if (failed.length === 0)
161
166
  return;
@@ -175,8 +180,8 @@ export class SyncClient extends EventEmitter {
175
180
  this.scheduleSync();
176
181
  }
177
182
  });
178
- // Provide connection state to TransactionQueue - prevents rollbacks during disconnection
179
- this.transactionQueue.setConnectionChecker(() => this.connectionState === 'connected');
183
+ // Provide connection state to MutationQueue - prevents rollbacks during disconnection
184
+ this.mutationQueue.setConnectionChecker(() => this.connectionState === 'connected');
180
185
  // Restore object-pool state when a transaction is rolled back. If the
181
186
  // server rejects a write or it times out, the model's previous state is
182
187
  // put back. Because writes are no longer applied to IndexedDB
@@ -201,7 +206,7 @@ export class SyncClient extends EventEmitter {
201
206
  // losing a failed reconnect flush silently.
202
207
  this.networkMonitor.on('online', () => {
203
208
  void this.handleReconnection().catch((error) => {
204
- getContext().observability.captureTransactionFailure({
209
+ this.runtime.observability.captureMutationFailure({
205
210
  context: 'network-online-reconnection',
206
211
  error: error instanceof Error ? error : new Error(String(error)),
207
212
  });
@@ -209,7 +214,7 @@ export class SyncClient extends EventEmitter {
209
214
  });
210
215
  this.networkMonitor.on('offline', () => {
211
216
  void this.handleDisconnection().catch((error) => {
212
- getContext().observability.captureTransactionFailure({
217
+ this.runtime.observability.captureMutationFailure({
213
218
  context: 'network-offline-handler',
214
219
  error: error instanceof Error ? error : new Error(String(error)),
215
220
  });
@@ -233,12 +238,12 @@ export class SyncClient extends EventEmitter {
233
238
  * discard the optimistic state silently.
234
239
  *
235
240
  * Treating both paths the same caused the deletion-flicker bug: every
236
- * cancelled update on a multi-layer chart fired a per-model observer
241
+ * cancelled update on a multi-child record fired a per-model observer
237
242
  * event and a `[SyncClient.rollback]` warn, producing N renders and N
238
243
  * spam log lines for one user-initiated delete.
239
244
  */
240
245
  setupTransactionRollbackHandling() {
241
- this.transactionQueue.on('optimistic:rollback', (event) => {
246
+ this.mutationQueue.on('optimistic:rollback', (event) => {
242
247
  const { model, previousState, transaction, reason, error } = event;
243
248
  // Local cleanup path — discard quietly. The optimistic state was
244
249
  // applied to a model that's already disposed by the cascading
@@ -253,13 +258,13 @@ export class SyncClient extends EventEmitter {
253
258
  // `AbloServerError` with `httpStatus: 500`). Falling back to
254
259
  // generic message lets us still see unstructured errors.
255
260
  // Mechanic-level breadcrumb only. The authoritative, user-facing
256
- // reason is logged once at `warn` by `TransactionQueue.handleFailure`
261
+ // reason is logged once at `warn` by `MutationQueue.handleFailure`
257
262
  // (`Permanent error - rolling back`). Logging the same typed cause
258
263
  // again here at `warn` is what produced three identical dumps per
259
264
  // rejected write — keep it at `debug` so the rollback mechanics are
260
265
  // available when debugging but don't double the console noise.
261
266
  const abloErr = error instanceof AbloError ? error : undefined;
262
- getContext().logger.debug('[SyncClient.rollback]', {
267
+ this.runtime.logger.debug('[SyncClient.rollback]', {
263
268
  txType: transaction.type,
264
269
  modelName: transaction.modelName,
265
270
  modelId: transaction.modelId.slice(0, 12),
@@ -270,7 +275,7 @@ export class SyncClient extends EventEmitter {
270
275
  requestId: abloErr?.requestId,
271
276
  message: error?.message,
272
277
  });
273
- getContext().observability.captureRollback({
278
+ this.runtime.observability.captureRollback({
274
279
  transactionType: transaction.type,
275
280
  modelName: transaction.modelName,
276
281
  modelId: transaction.modelId,
@@ -289,7 +294,7 @@ export class SyncClient extends EventEmitter {
289
294
  // DELETE "not found" rollback: the entity doesn't exist on the server.
290
295
  // Instead of restoring a ghost entity, remove it locally too.
291
296
  // Both sides agree: this entity should not exist.
292
- getContext().observability.breadcrumb('DELETE rolled back with "not found" - removing ghost entity', 'sync.conflict', 'info', {
297
+ this.runtime.observability.breadcrumb('DELETE rolled back with "not found" - removing ghost entity', 'sync.conflict', 'info', {
293
298
  modelId: transaction.modelId,
294
299
  modelName: transaction.modelName,
295
300
  });
@@ -303,11 +308,11 @@ export class SyncClient extends EventEmitter {
303
308
  if (model.disposed) {
304
309
  // Follow-on of an already-logged permanent error, not its own
305
310
  // problem: the tx that failed has already surfaced the cause in
306
- // TransactionQueue. Restoring a disposed model is a no-op by
311
+ // MutationQueue. Restoring a disposed model is a no-op by
307
312
  // design (can't revive the private isDisposed flag), so keep this
308
313
  // at `debug` instead of emitting a second `warn` that reads as a
309
314
  // distinct failure in the console.
310
- getContext().logger.debug('[SyncClient] Rollback skipped restore (model already disposed)', {
315
+ this.runtime.logger.debug('[SyncClient] Rollback skipped restore (model already disposed)', {
311
316
  modelId: transaction.modelId,
312
317
  modelName: transaction.modelName,
313
318
  reason,
@@ -334,7 +339,7 @@ export class SyncClient extends EventEmitter {
334
339
  });
335
340
  }
336
341
  catch (error) {
337
- getContext().observability.captureTransactionFailure({
342
+ this.runtime.observability.captureMutationFailure({
338
343
  context: 'rollback-failed',
339
344
  transactionId: transaction.id,
340
345
  modelName: transaction.modelName,
@@ -345,14 +350,14 @@ export class SyncClient extends EventEmitter {
345
350
  });
346
351
  }
347
352
  /**
348
- * Forward reconciliation requests from the {@link TransactionQueue} to the
353
+ * Forward reconciliation requests from the {@link MutationQueue} to the
349
354
  * sync layer. When delta confirmation times out, the queue emits
350
355
  * `reconciliation:needed` instead of rolling back, so optimistic state the
351
356
  * server may already have committed is never destroyed.
352
357
  */
353
358
  setupReconciliationForwarding() {
354
- this.transactionQueue.on('reconciliation:needed', (event) => {
355
- getContext().observability.captureReconciliation({
359
+ this.mutationQueue.on('reconciliation:needed', (event) => {
360
+ this.runtime.observability.captureReconciliation({
356
361
  reason: event.reason,
357
362
  model: event.model,
358
363
  modelId: event.modelId,
@@ -372,12 +377,12 @@ export class SyncClient extends EventEmitter {
372
377
  * deliver the missing deltas and confirm the transaction.
373
378
  */
374
379
  setupAwaitingTransactionPersistence() {
375
- this.transactionQueue.on('transaction:persist_awaiting', (event) => {
380
+ this.mutationQueue.on('transaction:persist_awaiting', (event) => {
376
381
  // void is safe: the handler's body is fully try/catch'd.
377
382
  void this.persistAwaitingTransaction(event);
378
383
  });
379
384
  // Clean up persisted awaiting transactions when they're finally confirmed
380
- this.transactionQueue.on('transaction:completed', (tx) => {
385
+ this.mutationQueue.on('transaction:completed', (tx) => {
381
386
  // void is safe: the handler's body is fully try/catch'd.
382
387
  void this.removeAwaitingTransaction(tx.id);
383
388
  });
@@ -389,11 +394,11 @@ export class SyncClient extends EventEmitter {
389
394
  // server processes it, we drain on rollback too so a stale id
390
395
  // doesn't permanently silence a foreign delta sharing the same id
391
396
  // (vanishingly unlikely for UUIDs, but cheap insurance).
392
- this.transactionQueue.on('transaction:created', (tx) => {
397
+ this.mutationQueue.on('transaction:created', (tx) => {
393
398
  if (!tx.localOnly)
394
399
  this.echoTracker.markPending(tx.id);
395
400
  });
396
- this.transactionQueue.on('optimistic:rollback', (event) => {
401
+ this.mutationQueue.on('optimistic:rollback', (event) => {
397
402
  this.echoTracker.drainOnRollback(event.transaction.id);
398
403
  });
399
404
  }
@@ -413,14 +418,14 @@ export class SyncClient extends EventEmitter {
413
418
  operationType: event.operationType,
414
419
  },
415
420
  });
416
- getContext().observability.breadcrumb('Persisted unconfirmed transaction to IDB', 'sync.transaction', 'info', {
421
+ this.runtime.observability.breadcrumb('Persisted unconfirmed transaction to IDB', 'sync.transaction', 'info', {
417
422
  txId: event.txId,
418
423
  model: event.model,
419
424
  modelId: event.modelId,
420
425
  });
421
426
  }
422
427
  catch (error) {
423
- getContext().observability.captureTransactionFailure({
428
+ this.runtime.observability.captureMutationFailure({
424
429
  context: 'persist-awaiting-transaction',
425
430
  modelName: event.model,
426
431
  modelId: event.modelId,
@@ -445,8 +450,8 @@ export class SyncClient extends EventEmitter {
445
450
  async initialize(userId, organizationId) {
446
451
  this.userId = userId;
447
452
  this.organizationId = organizationId;
448
- getContext().observability.setContext(userId, organizationId);
449
- this.transactionQueue.setCommitOutboxScope({
453
+ this.runtime.observability.setContext(userId, organizationId);
454
+ this.mutationQueue.setCommitOutboxScope({
450
455
  organizationId,
451
456
  participantId: userId,
452
457
  namespace: this.commitOutboxNamespace,
@@ -461,14 +466,14 @@ export class SyncClient extends EventEmitter {
461
466
  // Restore exact, already-sealed requests first. The returned source ids
462
467
  // suppress any legacy queue entry left behind by an older non-atomic
463
468
  // handoff.
464
- const sealedMutationIds = await this.transactionQueue.restoreDurableCommits();
469
+ const sealedMutationIds = await this.mutationQueue.restoreDurableCommits();
465
470
  await this.restoreMutationQueue(sealedMutationIds);
466
471
  // Read the initial network status from the injected OnlineStatusProvider.
467
472
  // In the browser this reflects the host's connectivity signal; in Node it
468
473
  // reports online by default. NetworkMonitor drives the ongoing
469
474
  // online/offline transitions below — this read is only the initial
470
475
  // snapshot taken when identity is set.
471
- if (getContext().onlineStatus.isOnline()) {
476
+ if (this.runtime.onlineStatus.isOnline()) {
472
477
  this.setConnectionState('connected');
473
478
  }
474
479
  else {
@@ -501,7 +506,7 @@ export class SyncClient extends EventEmitter {
501
506
  * 2. **Required-field gate** — if the row is missing any field listed
502
507
  * in the model's `requiredFields`, return `null` so the caller
503
508
  * skips this record. Used for FK columns whose absence renders the
504
- * row unrecoverable (e.g. a SlideLayer with no slideId).
509
+ * row unrecoverable (e.g. a Block with no sectionId).
505
510
  *
506
511
  * The engine itself is product-neutral: model identity (which fields
507
512
  * to back-fill, which absences are fatal) lives entirely in the
@@ -521,7 +526,7 @@ export class SyncClient extends EventEmitter {
521
526
  const replacement = rule.from === 'organizationId' ? this.organizationId : this.userId;
522
527
  if (!replacement)
523
528
  continue;
524
- getContext().observability.captureSelfHealing({
529
+ this.runtime.observability.captureSelfHealing({
525
530
  modelName: modelType,
526
531
  modelId: idPrefix,
527
532
  field: rule.field,
@@ -535,7 +540,7 @@ export class SyncClient extends EventEmitter {
535
540
  for (const field of meta.requiredFields) {
536
541
  if (result[field])
537
542
  continue;
538
- getContext().observability.captureSelfHealing({
543
+ this.runtime.observability.captureSelfHealing({
539
544
  modelName: modelType,
540
545
  modelId: idPrefix,
541
546
  field,
@@ -559,7 +564,7 @@ export class SyncClient extends EventEmitter {
559
564
  // Get model types that should be hydrated on startup (skip lazy per LSE)
560
565
  const modelTypes = this.objectPool.registry.getRegisteredModelNames().filter((name) => {
561
566
  const meta = this.objectPool.registry.getMetadata(name);
562
- return (meta?.loadStrategy === LoadStrategy.instant || meta?.loadStrategy === LoadStrategy.partial);
567
+ return meta?.loadStrategy === LoadStrategy.instant;
563
568
  });
564
569
  const totalStart = typeof performance !== 'undefined' ? performance.now() : Date.now();
565
570
  // Phase 1: Fetch all data from IndexedDB and create model instances (async I/O).
@@ -598,7 +603,7 @@ export class SyncClient extends EventEmitter {
598
603
  allModelsToAdd.push(...modelsForType);
599
604
  // Persist healed records back to IndexedDB (fire-and-forget, non-blocking)
600
605
  if (recordsToHeal.length > 0 && this.database) {
601
- getContext().logger.info(`[SyncClient.hydrate] Persisting ${recordsToHeal.length} healed ${modelType} records to IndexedDB`);
606
+ this.runtime.logger.info(`[SyncClient.hydrate] Persisting ${recordsToHeal.length} healed ${modelType} records to IndexedDB`);
602
607
  // Use fire-and-forget to not block hydration.
603
608
  // void is safe: the handler's body is fully try/catch'd.
604
609
  void Promise.resolve().then(async () => {
@@ -606,10 +611,10 @@ export class SyncClient extends EventEmitter {
606
611
  for (const { id, data } of recordsToHeal) {
607
612
  await this.database.putRecord(modelType, id, data);
608
613
  }
609
- getContext().logger.info(`[SyncClient.hydrate] Successfully healed ${recordsToHeal.length} ${modelType} records`);
614
+ this.runtime.logger.info(`[SyncClient.hydrate] Successfully healed ${recordsToHeal.length} ${modelType} records`);
610
615
  }
611
616
  catch (err) {
612
- getContext().observability.captureTransactionFailure({
617
+ this.runtime.observability.captureMutationFailure({
613
618
  context: 'persist-healed-records',
614
619
  modelName: modelType,
615
620
  error: err instanceof Error ? err : new Error(String(err)),
@@ -627,7 +632,7 @@ export class SyncClient extends EventEmitter {
627
632
  });
628
633
  }
629
634
  catch (error) {
630
- getContext().observability.captureBootstrapFailure(error, { type: `hydrate-${modelType}` });
635
+ this.runtime.observability.captureBootstrapFailure(error, { type: `hydrate-${modelType}` });
631
636
  }
632
637
  }
633
638
  // Phase 2: Single MobX action — add ALL models across all types at once.
@@ -636,7 +641,7 @@ export class SyncClient extends EventEmitter {
636
641
  const addEnd = typeof performance !== 'undefined' ? performance.now() : Date.now();
637
642
  // Log per-type perf after the batched add (so logs still show per-type breakdown)
638
643
  for (const entry of perTypePerfLogs) {
639
- getContext().logger.debug('hydrate:type', parseFloat(entry.fetchMs) + parseFloat(entry.createMs), {
644
+ this.runtime.logger.debug('hydrate:type', parseFloat(entry.fetchMs) + parseFloat(entry.createMs), {
640
645
  type: entry.type,
641
646
  fetched: entry.fetched,
642
647
  added: entry.added,
@@ -645,7 +650,7 @@ export class SyncClient extends EventEmitter {
645
650
  });
646
651
  }
647
652
  const totalEnd = typeof performance !== 'undefined' ? performance.now() : Date.now();
648
- getContext().logger.debug('hydrate:total', totalEnd - totalStart, {
653
+ this.runtime.logger.debug('hydrate:total', totalEnd - totalStart, {
649
654
  totalModels: totalAdded,
650
655
  addBatchMs: (addEnd - addStart).toFixed(2),
651
656
  });
@@ -653,7 +658,7 @@ export class SyncClient extends EventEmitter {
653
658
  try {
654
659
  const preseededTypes = this.objectPool.registry.getRegisteredModelNames();
655
660
  const stats = this.objectPool.getStats();
656
- getContext().logger.info('startup_summary', {
661
+ this.runtime.logger.info('startup_summary', {
657
662
  typesPreseeded: preseededTypes.length,
658
663
  poolSize: stats.size,
659
664
  typeCounts: stats.typeCounts,
@@ -681,7 +686,7 @@ export class SyncClient extends EventEmitter {
681
686
  // Model types to rehydrate (same filter as hydrateFromDatabase)
682
687
  const modelTypes = this.objectPool.registry.getRegisteredModelNames().filter((name) => {
683
688
  const meta = this.objectPool.registry.getMetadata(name);
684
- return (meta?.loadStrategy === LoadStrategy.instant || meta?.loadStrategy === LoadStrategy.partial);
689
+ return meta?.loadStrategy === LoadStrategy.instant;
685
690
  });
686
691
  // ── Phase 1: Read from IndexedDB & create model instances (async I/O) ──
687
692
  const allModels = [];
@@ -734,7 +739,7 @@ export class SyncClient extends EventEmitter {
734
739
  }
735
740
  }
736
741
  catch (error) {
737
- getContext().observability.breadcrumb('Model creation failed during rehydration', 'sync.bootstrap', 'warning', {
742
+ this.runtime.observability.breadcrumb('Model creation failed during rehydration', 'sync.bootstrap', 'warning', {
738
743
  modelType,
739
744
  modelId: recordId?.slice(0, 8) ?? 'unknown',
740
745
  error: error instanceof Error ? error.message : String(error),
@@ -744,7 +749,7 @@ export class SyncClient extends EventEmitter {
744
749
  }
745
750
  }
746
751
  catch (error) {
747
- getContext().observability.captureBootstrapFailure(error, { type: `rehydrate-${modelType}` });
752
+ this.runtime.observability.captureBootstrapFailure(error, { type: `rehydrate-${modelType}` });
748
753
  }
749
754
  }
750
755
  // ── Phase 2: Upsert batch (single MobX action) ──
@@ -783,12 +788,12 @@ export class SyncClient extends EventEmitter {
783
788
  healed: healedCount,
784
789
  elapsedMs,
785
790
  };
786
- getContext().logger.info('[SyncClient.rehydrate] Complete', {
791
+ this.runtime.logger.info('[SyncClient.rehydrate] Complete', {
787
792
  ...stats,
788
793
  poolSize: this.objectPool.size,
789
794
  ghostIds: ghostIds.length > 0 ? ghostIds.slice(0, 5).map((id) => id.slice(0, 8)) : [],
790
795
  });
791
- getContext().observability.breadcrumb('Rehydration complete', 'sync.bootstrap', 'info', {
796
+ this.runtime.observability.breadcrumb('Rehydration complete', 'sync.bootstrap', 'info', {
792
797
  added: stats.added,
793
798
  updated: stats.updated,
794
799
  removed: stats.removed,
@@ -819,8 +824,9 @@ export class SyncClient extends EventEmitter {
819
824
  // object can reach here with `hasChanges === undefined`. `undefined === false`
820
825
  // is false → we fall through to the normal path rather than risk dropping a
821
826
  // real write. Only a genuine Model with an empty dirty-set is skipped.
822
- if (type === 'update' && model.hasChanges === false)
827
+ if (type === 'update' && model.hasChanges === false) {
823
828
  return;
829
+ }
824
830
  // Capture changes before the pool action runs. Pool operations —
825
831
  // upsert in particular — can clear the model's local changes, so
826
832
  // capturing first ensures they are never lost.
@@ -834,7 +840,7 @@ export class SyncClient extends EventEmitter {
834
840
  modelId: model.id,
835
841
  });
836
842
  // QueryProcessor uses `models:changed` to invalidate caches. Coalesce
837
- // to one event per microtask: a paste of 100 layers should re-run
843
+ // to one event per microtask: a paste of 100 rows should re-run
838
844
  // affected queries ONCE, not 100×.
839
845
  this.markModelChanged(model.getModelName());
840
846
  }
@@ -876,10 +882,10 @@ export class SyncClient extends EventEmitter {
876
882
  /**
877
883
  * Update existing model with pre-computed changes.
878
884
  * Used by saveManyOptimized when incoming models have empty change-tracking
879
- * (e.g. freshly constructed SpreadsheetCellModels from decomposeSpreadsheetDocument).
885
+ * (e.g. freshly constructed cell models from a bulk document decomposition).
880
886
  */
881
887
  updateWithChanges(model, changes) {
882
- getContext().logger.debug(`SyncClient.updateWithChanges`, {
888
+ this.runtime.logger.debug(`SyncClient.updateWithChanges`, {
883
889
  modelId: model.id,
884
890
  modelType: model.getModelName(),
885
891
  });
@@ -903,7 +909,7 @@ export class SyncClient extends EventEmitter {
903
909
  modelId: model.id,
904
910
  });
905
911
  }
906
- /** Expose the GraphQL client for atomic mutations (e.g., createSlideWithLayers).
912
+ /** Expose the GraphQL client for atomic mutations (e.g., createSectionWithBlocks).
907
913
  * Used by SyncedStore for operations that bypass the transaction queue
908
914
  * but still need optimistic pool updates at the sync layer. */
909
915
  get gql() {
@@ -917,7 +923,7 @@ export class SyncClient extends EventEmitter {
917
923
  }
918
924
  /**
919
925
  * Clear all pending mutations for a specific model
920
- * Called before deletion to prevent "layer not found" errors on the server
926
+ * Called before deletion to prevent "record not found" errors on the server
921
927
  */
922
928
  clearPendingMutationsForModel(modelId) {
923
929
  const beforeCount = this.pendingMutations.length;
@@ -925,13 +931,13 @@ export class SyncClient extends EventEmitter {
925
931
  this.pendingMutations = this.pendingMutations.filter((m) => m.model.id !== modelId);
926
932
  const afterCount = this.pendingMutations.length;
927
933
  if (beforeCount !== afterCount) {
928
- getContext().logger.debug('[SyncClient.clearPendingMutationsForModel] Cleared pending mutations', {
934
+ this.runtime.logger.debug('[SyncClient.clearPendingMutationsForModel] Cleared pending mutations', {
929
935
  modelId,
930
936
  clearedCount: beforeCount - afterCount,
931
937
  remainingCount: afterCount,
932
938
  });
933
939
  for (const mutation of removed) {
934
- // Once staged, TransactionQueue owns cancellation and transfers this
940
+ // Once staged, MutationQueue owns cancellation and transfers this
935
941
  // source id into the superseding delete envelope. Deleting the journal
936
942
  // row here would make that valid atomic promotion look like a
937
943
  // multi-tab loser. Truly unstaged work can be canceled locally.
@@ -946,7 +952,7 @@ export class SyncClient extends EventEmitter {
946
952
  }
947
953
  /**
948
954
  * Upload a file and create its attachment record. The upload runs through
949
- * the {@link TransactionQueue}, and a model is built from the server's
955
+ * the {@link MutationQueue}, and a model is built from the server's
950
956
  * response and added to the pool.
951
957
  */
952
958
  async uploadFile(file, options) {
@@ -956,8 +962,8 @@ export class SyncClient extends EventEmitter {
956
962
  });
957
963
  }
958
964
  try {
959
- // Use TransactionQueue to handle the upload mutation
960
- const result = await this.transactionQueue.uploadAttachment(file, {
965
+ // Use MutationQueue to handle the upload mutation
966
+ const result = await this.mutationQueue.uploadAttachment(file, {
961
967
  id: options.id,
962
968
  attachableType: options.attachableType,
963
969
  attachableId: options.attachableId,
@@ -985,7 +991,7 @@ export class SyncClient extends EventEmitter {
985
991
  return null;
986
992
  }
987
993
  catch (error) {
988
- getContext().observability.captureTransactionFailure({
994
+ this.runtime.observability.captureMutationFailure({
989
995
  context: 'file-upload',
990
996
  error: error instanceof Error ? error : new Error(String(error)),
991
997
  });
@@ -1015,7 +1021,7 @@ export class SyncClient extends EventEmitter {
1015
1021
  attachableId: options.attachableId,
1016
1022
  metadata: options.metadata,
1017
1023
  }));
1018
- const results = await this.transactionQueue.batchUploadAttachments(files, items, {
1024
+ const results = await this.mutationQueue.batchUploadAttachments(files, items, {
1019
1025
  userId: this.userId,
1020
1026
  organizationId: this.organizationId,
1021
1027
  });
@@ -1044,7 +1050,7 @@ export class SyncClient extends EventEmitter {
1044
1050
  * IndexedDB persistence and the server push are deferred to a microtask, so
1045
1051
  * many pushes within the same tick collapse into a single serialization and
1046
1052
  * a single process call. Without the deferral, queueing a hundred mutations
1047
- * at once — a large paste, a document import, bulk layer creation — would
1053
+ * at once — a large paste, a document import, bulk row creation — would
1048
1054
  * reserialize the whole growing queue a hundred times, an O(N²) cost in
1049
1055
  * `model.toJSON()`.
1050
1056
  *
@@ -1113,20 +1119,59 @@ export class SyncClient extends EventEmitter {
1113
1119
  return;
1114
1120
  }
1115
1121
  try {
1116
- const records = batch.map((mutation) => this.pendingMutationRecord(mutation));
1122
+ // Build each durable row independently. A malformed mutation must not
1123
+ // prevent unrelated writes from the same event-loop burst from being
1124
+ // journaled and dispatched.
1125
+ const entries = [];
1126
+ for (const mutation of batch) {
1127
+ try {
1128
+ entries.push({
1129
+ mutation,
1130
+ record: this.pendingMutationRecord(mutation),
1131
+ });
1132
+ }
1133
+ catch (error) {
1134
+ mutation.rejectJournal?.(error);
1135
+ }
1136
+ }
1137
+ if (entries.length === 0)
1138
+ return;
1117
1139
  const database = this.database;
1140
+ const saveOne = async (record) => {
1141
+ // Some adapters (including IndexedDB's put path) can throw before
1142
+ // returning a Promise. The async wrapper turns that into an individual
1143
+ // rejected outcome instead of aborting construction of the whole list.
1144
+ await database.saveTransaction(record);
1145
+ };
1146
+ let outcomes;
1118
1147
  if (database.saveTransactions) {
1119
- await database.saveTransactions(records);
1148
+ try {
1149
+ await database.saveTransactions(entries.map(({ record }) => record));
1150
+ outcomes = entries.map(() => ({
1151
+ status: 'fulfilled',
1152
+ value: undefined,
1153
+ }));
1154
+ }
1155
+ catch {
1156
+ // IndexedDB aborts a multi-row transaction when any value cannot be
1157
+ // cloned. Retry one row per transaction to identify the offender and
1158
+ // preserve every valid sibling. Re-putting a row is idempotent by id.
1159
+ outcomes = await Promise.allSettled(entries.map(({ record }) => saveOne(record)));
1160
+ }
1120
1161
  }
1121
1162
  else {
1122
- await Promise.all(records.map((record) => database.saveTransaction(record)));
1163
+ outcomes = await Promise.allSettled(entries.map(({ record }) => saveOne(record)));
1164
+ }
1165
+ for (const [index, entry] of entries.entries()) {
1166
+ const outcome = outcomes[index];
1167
+ if (outcome?.status === 'fulfilled') {
1168
+ entry.mutation.resolveJournal?.();
1169
+ }
1170
+ else {
1171
+ entry.mutation.rejectJournal?.(outcome?.reason ??
1172
+ new Error('Mutation journal persistence failed'));
1173
+ }
1123
1174
  }
1124
- for (const mutation of batch)
1125
- mutation.resolveJournal?.();
1126
- }
1127
- catch (error) {
1128
- for (const mutation of batch)
1129
- mutation.rejectJournal?.(error);
1130
1175
  }
1131
1176
  finally {
1132
1177
  for (const mutation of batch) {
@@ -1145,9 +1190,9 @@ export class SyncClient extends EventEmitter {
1145
1190
  : (cb) => Promise.resolve().then(cb);
1146
1191
  schedule(() => {
1147
1192
  this.syncScheduled = false;
1148
- if (getContext().onlineStatus.isOnline()) {
1193
+ if (this.runtime.onlineStatus.isOnline()) {
1149
1194
  this.processPendingMutations().catch((err) => {
1150
- getContext().observability.breadcrumb('Background sync failed', 'sync.transaction', 'warning', { error: err instanceof Error ? err.message : String(err) });
1195
+ this.runtime.observability.breadcrumb('Background sync failed', 'sync.transaction', 'warning', { error: err instanceof Error ? err.message : String(err) });
1151
1196
  });
1152
1197
  }
1153
1198
  });
@@ -1156,7 +1201,7 @@ export class SyncClient extends EventEmitter {
1156
1201
  if (!this.userId || !this.organizationId) {
1157
1202
  throw new AbloValidationError('Cannot persist a mutation before participant scope is initialized', { code: 'write_options_invalid' });
1158
1203
  }
1159
- return pendingMutationRecordSchema.parse({
1204
+ const candidate = {
1160
1205
  id: pendingMutationRecordId(mutation.mutationId),
1161
1206
  type: 'pending_mutation',
1162
1207
  storageVersion: 2,
@@ -1179,7 +1224,10 @@ export class SyncClient extends EventEmitter {
1179
1224
  namespace: this.commitOutboxNamespace,
1180
1225
  },
1181
1226
  timestamp: mutation.timestamp.getTime(),
1182
- });
1227
+ };
1228
+ // One framework-neutral boundary contract owns both Proxy unwrapping and
1229
+ // rejection of values JSON would silently corrupt before IndexedDB sees it.
1230
+ return pendingMutationRecordSchema.parse(snapshotJsonValue(candidate, '$.pendingMutation'));
1183
1231
  }
1184
1232
  async persistPendingMutation(mutation) {
1185
1233
  await this.database.saveTransaction(this.pendingMutationRecord(mutation));
@@ -1203,7 +1251,7 @@ export class SyncClient extends EventEmitter {
1203
1251
  const restore = async (mutation, migrateLegacy, legacyMutationId) => {
1204
1252
  const parsed = persistedMutationSchema.safeParse(mutation);
1205
1253
  if (!parsed.success) {
1206
- getContext().logger.debug('[SyncClient] Dropping malformed persisted mutation', {
1254
+ this.runtime.logger.debug('[SyncClient] Dropping malformed persisted mutation', {
1207
1255
  issues: parsed.error.issues.map((i) => i.path.join('.')).join(', '),
1208
1256
  });
1209
1257
  return;
@@ -1215,7 +1263,7 @@ export class SyncClient extends EventEmitter {
1215
1263
  const age = Date.now() - writtenAt;
1216
1264
  if (!(age < PENDING_MUTATION_REPLAY_WINDOW_MS)) {
1217
1265
  heldForReview += 1;
1218
- getContext().logger.warn('A saved local write is older than the server idempotency window and was held for review.');
1266
+ this.runtime.logger.warn('A saved local write is older than the server idempotency window and was held for review.');
1219
1267
  return;
1220
1268
  }
1221
1269
  const mutationId = parsed.data.mutationId ?? legacyMutationId ?? `mutation_${uuid()}`;
@@ -1259,7 +1307,7 @@ export class SyncClient extends EventEmitter {
1259
1307
  await restore(legacy.data.mutation, true);
1260
1308
  continue;
1261
1309
  }
1262
- getContext().logger.debug('[SyncClient] Dropping malformed pending mutation record', {
1310
+ this.runtime.logger.debug('[SyncClient] Dropping malformed pending mutation record', {
1263
1311
  rowId: row.id,
1264
1312
  });
1265
1313
  continue;
@@ -1267,7 +1315,7 @@ export class SyncClient extends EventEmitter {
1267
1315
  if (parsed.data.scope.organizationId !== this.organizationId ||
1268
1316
  parsed.data.scope.participantId !== this.userId ||
1269
1317
  parsed.data.scope.namespace !== this.commitOutboxNamespace) {
1270
- getContext().logger.warn('A saved local write belongs to a different account or server and was held for review.');
1318
+ this.runtime.logger.warn('A saved local write belongs to a different account or server and was held for review.');
1271
1319
  continue;
1272
1320
  }
1273
1321
  await restore(parsed.data.mutation, false);
@@ -1289,10 +1337,10 @@ export class SyncClient extends EventEmitter {
1289
1337
  // A restore failure means queued offline writes did NOT rehydrate.
1290
1338
  // Self-healing is impossible here (the record may be unreadable), but
1291
1339
  // the failure must be visible for diagnosis instead of silent loss.
1292
- getContext().logger.debug('[SyncClient] Failed to restore offline mutation queue', {
1340
+ this.runtime.logger.debug('[SyncClient] Failed to restore offline mutation queue', {
1293
1341
  error: error instanceof Error ? error.message : String(error),
1294
1342
  });
1295
- getContext().observability.captureTransactionFailure({
1343
+ this.runtime.observability.captureMutationFailure({
1296
1344
  context: 'restore-mutation-queue',
1297
1345
  error: error instanceof Error ? error : String(error),
1298
1346
  });
@@ -1303,7 +1351,7 @@ export class SyncClient extends EventEmitter {
1303
1351
  *
1304
1352
  * Best Practice: Only sync models that still exist locally (local-first principle)
1305
1353
  * - If a model was deleted locally → skip any pending updates/creates for it
1306
- * - This prevents "layer not found" errors from fast copy-paste-delete workflows
1354
+ * - This prevents "record not found" errors from fast copy-paste-delete workflows
1307
1355
  */
1308
1356
  async processPendingMutations() {
1309
1357
  if (this.pendingMutations.length === 0)
@@ -1322,14 +1370,14 @@ export class SyncClient extends EventEmitter {
1322
1370
  // identity, which surfaces downstream as "writes never confirm"; we do
1323
1371
  // NOT name internal wiring (`SyncClient.initialize`) here because that
1324
1372
  // method isn't part of the @abloatai/ablo surface a reader could act on.
1325
- getContext().logger.debug('[sync] writes waiting for identity (user/org not set yet) — queued, will retry', {
1373
+ this.runtime.logger.debug('[sync] writes waiting for identity (user/org not set yet) — queued, will retry', {
1326
1374
  pending: this.pendingMutations.length,
1327
1375
  userId: this.userId,
1328
1376
  organizationId: this.organizationId,
1329
1377
  });
1330
1378
  return;
1331
1379
  }
1332
- if (!getContext().onlineStatus.isOnline())
1380
+ if (!this.runtime.onlineStatus.isOnline())
1333
1381
  return; // Skip if offline
1334
1382
  if (this.isDisposed)
1335
1383
  return; // Skip if disposed
@@ -1374,7 +1422,7 @@ export class SyncClient extends EventEmitter {
1374
1422
  this.stagedMutationIds.delete(mutation.mutationId);
1375
1423
  this.pendingMutations = this.pendingMutations.filter((pending) => pending.mutationId !== mutation.mutationId);
1376
1424
  mutation.rejectStaged?.(outcome.reason);
1377
- getContext().observability.captureTransactionFailure({
1425
+ this.runtime.observability.captureMutationFailure({
1378
1426
  context: 'persist-pending-mutation',
1379
1427
  error: outcome.reason instanceof Error
1380
1428
  ? outcome.reason
@@ -1384,12 +1432,12 @@ export class SyncClient extends EventEmitter {
1384
1432
  // Stage every mutation synchronously within the same event-loop tick;
1385
1433
  // the transaction queue's microtask batches and sends them together.
1386
1434
  for (const mutation of journaledMutations) {
1387
- // Stage synchronously - TransactionQueue handles batching, retry, and errors
1435
+ // Stage synchronously - MutationQueue handles batching, retry, and errors
1388
1436
  this.stageMutation(mutation);
1389
1437
  }
1390
1438
  }
1391
1439
  /**
1392
- * Stage mutation to TransactionQueue - mutations in same tick are batched via microtask
1440
+ * Stage mutation to MutationQueue - mutations in same tick are batched via microtask
1393
1441
  *
1394
1442
  * @param mutation.capturedChanges - Pre-captured changes to use instead of re-reading from model
1395
1443
  */
@@ -1406,7 +1454,7 @@ export class SyncClient extends EventEmitter {
1406
1454
  // apply, store add). That means the write never entered the queue, so
1407
1455
  // capture it instead of dropping it silently.
1408
1456
  const captureStagingFailure = (error) => {
1409
- getContext().observability.captureTransactionFailure({
1457
+ this.runtime.observability.captureMutationFailure({
1410
1458
  context: `stage-mutation-${mutation.type}`,
1411
1459
  modelName: mutation.model.getModelName(),
1412
1460
  modelId: mutation.model.id,
@@ -1415,8 +1463,8 @@ export class SyncClient extends EventEmitter {
1415
1463
  this.stagedMutationIds.delete(mutation.mutationId);
1416
1464
  };
1417
1465
  const staging = mutation.type === 'update'
1418
- ? this.transactionQueue.update(mutation.model, ctx, mutation.capturedChanges, mutation.writeOptions, mutation.mutationId)
1419
- : this.transactionQueue[mutation.type].bind(this.transactionQueue)(mutation.model, ctx, mutation.writeOptions, mutation.mutationId);
1466
+ ? this.mutationQueue.update(mutation.model, ctx, mutation.capturedChanges, mutation.writeOptions, mutation.mutationId)
1467
+ : this.mutationQueue[mutation.type].bind(this.mutationQueue)(mutation.model, ctx, mutation.writeOptions, mutation.mutationId);
1420
1468
  staging
1421
1469
  .then(() => mutation.resolveStaged?.())
1422
1470
  .catch((error) => {
@@ -1439,7 +1487,7 @@ export class SyncClient extends EventEmitter {
1439
1487
  : new Date(localModel.updatedAt).getTime()
1440
1488
  : 0;
1441
1489
  const serverUpdatedAt = toEpochMs(serverData.updatedAt);
1442
- getContext().logger.debug('Conflict resolution', {
1490
+ this.runtime.logger.debug('Conflict resolution', {
1443
1491
  modelId: localModel.id,
1444
1492
  modelType: localModel.getModelName(),
1445
1493
  hasLocalChanges,
@@ -1453,7 +1501,7 @@ export class SyncClient extends EventEmitter {
1453
1501
  const criticalServerStates = this.extractCriticalState(serverData);
1454
1502
  const shouldForceAcceptServer = this.hasCriticalStateChange(criticalServerStates);
1455
1503
  if (shouldForceAcceptServer) {
1456
- getContext().logger.debug('Accepting server update - critical state change detected', {
1504
+ this.runtime.logger.debug('Accepting server update - critical state change detected', {
1457
1505
  modelId: localModel.id,
1458
1506
  criticalStates: criticalServerStates,
1459
1507
  });
@@ -1467,7 +1515,7 @@ export class SyncClient extends EventEmitter {
1467
1515
  // Keep locally changed fields; apply server for the rest.
1468
1516
  if (hasLocalChanges) {
1469
1517
  const localChanges = localModel.getChanges();
1470
- getContext().logger.debug('Merging server update with local dirty fields', {
1518
+ this.runtime.logger.debug('Merging server update with local dirty fields', {
1471
1519
  modelId: localModel.id,
1472
1520
  keptFields: Object.keys(localChanges || {}),
1473
1521
  });
@@ -1486,7 +1534,7 @@ export class SyncClient extends EventEmitter {
1486
1534
  // No local changes: fall back to LWW to converge
1487
1535
  // Accept server regardless of timestamp equality to stay in sync
1488
1536
  const acceptReason = serverUpdatedAt > localUpdatedAt ? 'server is newer' : 'no local changes';
1489
- getContext().logger.debug(`Accepting server update - ${acceptReason}`);
1537
+ this.runtime.logger.debug(`Accepting server update - ${acceptReason}`);
1490
1538
  localModel.updateFromData(serverData);
1491
1539
  localModel.clearChanges();
1492
1540
  localModel.markAsSynced();
@@ -1530,12 +1578,12 @@ export class SyncClient extends EventEmitter {
1530
1578
  * Handle network reconnection
1531
1579
  */
1532
1580
  async handleReconnection() {
1533
- getContext().observability.breadcrumb('Network reconnected', 'sync.offline');
1581
+ this.runtime.observability.breadcrumb('Network reconnected', 'sync.offline');
1534
1582
  this.emit('sync:reconnecting');
1535
1583
  try {
1536
1584
  // Prefer a single batch flush for pending mutations (fast path)
1537
1585
  try {
1538
- await this.transactionQueue.flushOfflineQueue();
1586
+ await this.mutationQueue.flushOfflineQueue();
1539
1587
  }
1540
1588
  catch { }
1541
1589
  // Process all queued mutations
@@ -1544,7 +1592,7 @@ export class SyncClient extends EventEmitter {
1544
1592
  this.emit('sync:reconnected');
1545
1593
  }
1546
1594
  catch (error) {
1547
- getContext().observability.captureTransactionFailure({
1595
+ this.runtime.observability.captureMutationFailure({
1548
1596
  context: 'reconnection-sync',
1549
1597
  error: error instanceof Error ? error : new Error(String(error)),
1550
1598
  });
@@ -1555,7 +1603,7 @@ export class SyncClient extends EventEmitter {
1555
1603
  * Handle network disconnection
1556
1604
  */
1557
1605
  async handleDisconnection() {
1558
- getContext().observability.breadcrumb('Network disconnected', 'sync.offline');
1606
+ this.runtime.observability.breadcrumb('Network disconnected', 'sync.offline');
1559
1607
  this.setConnectionState('disconnected');
1560
1608
  this.emit('sync:offline');
1561
1609
  }
@@ -1577,15 +1625,15 @@ export class SyncClient extends EventEmitter {
1577
1625
  const oldState = this.connectionState;
1578
1626
  this.connectionState = state;
1579
1627
  if (oldState !== state) {
1580
- getContext().observability.setConnectionState(state);
1581
- getContext().observability.breadcrumb(`Connection: ${oldState} → ${state}`, 'sync.websocket');
1628
+ this.runtime.observability.setConnectionState(state);
1629
+ this.runtime.observability.breadcrumb(`Connection: ${oldState} → ${state}`, 'sync.websocket');
1582
1630
  if (state === 'connected') {
1583
1631
  this.emit('connection:established');
1584
- this.transactionQueue.setConnectionState('connected');
1632
+ this.mutationQueue.setConnectionState('connected');
1585
1633
  }
1586
1634
  else if (state === 'disconnected') {
1587
1635
  this.emit('connection:disconnected');
1588
- this.transactionQueue.setConnectionState('disconnected');
1636
+ this.mutationQueue.setConnectionState('disconnected');
1589
1637
  }
1590
1638
  }
1591
1639
  }
@@ -1621,7 +1669,7 @@ export class SyncClient extends EventEmitter {
1621
1669
  observer.onSync(event);
1622
1670
  }
1623
1671
  catch (error) {
1624
- getContext().observability.breadcrumb('Observer error', 'sync.transaction', 'error', {
1672
+ this.runtime.observability.breadcrumb('Observer error', 'sync.transaction', 'error', {
1625
1673
  error: error instanceof Error ? error.message : String(error),
1626
1674
  });
1627
1675
  }
@@ -1643,8 +1691,8 @@ export class SyncClient extends EventEmitter {
1643
1691
  // Browser online state may have marked the client connected before the
1644
1692
  // WebSocket itself was ready. Always kick both durable lanes on the real
1645
1693
  // socket event, even when the high-level state did not change.
1646
- void this.transactionQueue.flushOfflineQueue().catch((error) => {
1647
- getContext().observability.captureTransactionFailure({
1694
+ void this.mutationQueue.flushOfflineQueue().catch((error) => {
1695
+ this.runtime.observability.captureMutationFailure({
1648
1696
  context: 'restore-commit-outbox',
1649
1697
  error: error instanceof Error ? error : new Error(String(error)),
1650
1698
  });
@@ -1663,7 +1711,7 @@ export class SyncClient extends EventEmitter {
1663
1711
  this.removeAllListeners();
1664
1712
  }
1665
1713
  /**
1666
- * Notify the {@link TransactionQueue} of an incoming delta so it can confirm
1714
+ * Notify the {@link MutationQueue} of an incoming delta so it can confirm
1667
1715
  * hosted writes by sync-id threshold and queued forwards by their echoed
1668
1716
  * source-batch correlation id.
1669
1717
  * @param syncId - The sync id of the received delta.
@@ -1672,10 +1720,10 @@ export class SyncClient extends EventEmitter {
1672
1720
  */
1673
1721
  onDeltaReceived(syncId, transactionId, correlationId) {
1674
1722
  try {
1675
- this.transactionQueue.onDeltaReceived(syncId, transactionId, correlationId);
1723
+ this.mutationQueue.onDeltaReceived(syncId, transactionId, correlationId);
1676
1724
  }
1677
1725
  catch (e) {
1678
- getContext().observability.breadcrumb('Failed to notify delta received', 'sync.transaction', 'warning', {
1726
+ this.runtime.observability.breadcrumb('Failed to notify delta received', 'sync.transaction', 'warning', {
1679
1727
  syncId,
1680
1728
  transactionId,
1681
1729
  correlationId,
@@ -1687,20 +1735,20 @@ export class SyncClient extends EventEmitter {
1687
1735
  * deletion. The store calls this when a delete delta arrives for a parent,
1688
1736
  * cancelling any queued writes on children that reference it.
1689
1737
  *
1690
- * @param childModelName - The child model type (for example, `SlideLayer`).
1691
- * @param foreignKey - The foreign-key property name (for example, `slideId`).
1738
+ * @param childModelName - The child model type (for example, `Block`).
1739
+ * @param foreignKey - The foreign-key property name (for example, `sectionId`).
1692
1740
  * @param parentId - The id of the deleted parent.
1693
1741
  * @returns The number of transactions cancelled.
1694
1742
  */
1695
1743
  cancelTransactionsByForeignKey(childModelName, foreignKey, parentId) {
1696
- return this.transactionQueue.cancelTransactionsByForeignKey(childModelName, foreignKey, parentId);
1744
+ return this.mutationQueue.cancelTransactionsByForeignKey(childModelName, foreignKey, parentId);
1697
1745
  }
1698
1746
  /**
1699
1747
  * Wait for a transaction to be confirmed by its delta echo. Delegates to the
1700
- * {@link TransactionQueue}, which handles the confirmation timeout.
1748
+ * {@link MutationQueue}, which handles the confirmation timeout.
1701
1749
  */
1702
1750
  waitForDeltaConfirmation(transactionId) {
1703
- return this.transactionQueue.waitForConfirmation(transactionId);
1751
+ return this.mutationQueue.waitForConfirmation(transactionId);
1704
1752
  }
1705
1753
  /**
1706
1754
  * Force sync now - process pending mutations
@@ -1728,11 +1776,11 @@ export class SyncClient extends EventEmitter {
1728
1776
  };
1729
1777
  }
1730
1778
  /**
1731
- * Get pending transaction count from TransactionQueue
1779
+ * Get pending transaction count from MutationQueue
1732
1780
  * Used by SyncedStore to compute hasUnsyncedChanges
1733
1781
  */
1734
1782
  getPendingTransactionCount() {
1735
- const stats = this.transactionQueue.getStats();
1783
+ const stats = this.mutationQueue.getStats();
1736
1784
  // Include pending and executing as "unsynced"
1737
1785
  // awaiting_delta transactions are included in 'executing' until confirmed
1738
1786
  // Completed and failed are "synced" (either done or gave up)
@@ -1744,12 +1792,12 @@ export class SyncClient extends EventEmitter {
1744
1792
  */
1745
1793
  onTransactionEvent(event, callback) {
1746
1794
  const eventName = `transaction:${event}`;
1747
- this.transactionQueue.on(eventName, callback);
1748
- return () => this.transactionQueue.off(eventName, callback);
1795
+ this.mutationQueue.on(eventName, callback);
1796
+ return () => this.mutationQueue.off(eventName, callback);
1749
1797
  }
1750
1798
  /**
1751
1799
  * Subscribe to mutation failures with the full payload. Mirrors the
1752
- * underlying TransactionQueue 'transaction:failed' shape so consumers
1800
+ * underlying MutationQueue 'transaction:failed' shape so consumers
1753
1801
  * can render typed UI (toast keyed by `AbloError.type`, route-level
1754
1802
  * "this entity reverted" boundaries, telemetry).
1755
1803
  *
@@ -1759,15 +1807,26 @@ export class SyncClient extends EventEmitter {
1759
1807
  * this typed listener drives user-visible surfaces.
1760
1808
  */
1761
1809
  onMutationFailure(listener) {
1762
- this.transactionQueue.on('transaction:failed', listener);
1763
- return () => this.transactionQueue.off('transaction:failed', listener);
1810
+ this.mutationQueue.on('transaction:failed', listener);
1811
+ return () => this.mutationQueue.off('transaction:failed', listener);
1812
+ }
1813
+ /**
1814
+ * Subscribe to commit round-trip latency, split into the local seal and the
1815
+ * remote acknowledgement. Fires once per completed commit.
1816
+ *
1817
+ * Taps the {@link MutationQueue} emitter for the same reason
1818
+ * {@link onMutationFailure} does: the commit lifecycle events originate
1819
+ * there and the SyncClient's own emitter never rebroadcasts them.
1820
+ */
1821
+ onCommitLatency(listener) {
1822
+ return observeCommitLatency(this.mutationQueue, listener);
1764
1823
  }
1765
1824
  /**
1766
- * Subscribe to local transaction creation with the full {@link Transaction}
1825
+ * Subscribe to local transaction creation with the full {@link QueuedMutation}
1767
1826
  * payload (`type`, `modelName`, `modelId`, `data`, `previousData`). This is
1768
1827
  * the feed the store's local-mutation subscription taps for undo recording.
1769
1828
  *
1770
- * It subscribes to the {@link TransactionQueue}'s emitter directly, since
1829
+ * It subscribes to the {@link MutationQueue}'s emitter directly, since
1771
1830
  * that is the only emitter that fires `transaction:created`. The SyncClient's
1772
1831
  * own emitter (reached through {@link subscribe}) never rebroadcasts that
1773
1832
  * event, so routing undo through `subscribe('transaction:created')` would
@@ -1775,7 +1834,7 @@ export class SyncClient extends EventEmitter {
1775
1834
  * reason.
1776
1835
  */
1777
1836
  onLocalTransaction(listener) {
1778
- this.transactionQueue.on('transaction:created', listener);
1837
+ this.mutationQueue.on('transaction:created', listener);
1779
1838
  const snapshotsByCommit = new Map();
1780
1839
  const onCommitStaging = (payload) => {
1781
1840
  snapshotsByCommit.set(payload.clientTxId, payload.operations.map((operation) => {
@@ -1843,14 +1902,14 @@ export class SyncClient extends EventEmitter {
1843
1902
  });
1844
1903
  });
1845
1904
  };
1846
- this.transactionQueue.on('commit:staging', onCommitStaging);
1847
- this.transactionQueue.on('commit:seal_failed', onCommitSealFailed);
1848
- this.transactionQueue.on('commit:created', onCommitCreated);
1905
+ this.mutationQueue.on('commit:staging', onCommitStaging);
1906
+ this.mutationQueue.on('commit:seal_failed', onCommitSealFailed);
1907
+ this.mutationQueue.on('commit:created', onCommitCreated);
1849
1908
  return () => {
1850
- this.transactionQueue.off('transaction:created', listener);
1851
- this.transactionQueue.off('commit:staging', onCommitStaging);
1852
- this.transactionQueue.off('commit:seal_failed', onCommitSealFailed);
1853
- this.transactionQueue.off('commit:created', onCommitCreated);
1909
+ this.mutationQueue.off('transaction:created', listener);
1910
+ this.mutationQueue.off('commit:staging', onCommitStaging);
1911
+ this.mutationQueue.off('commit:seal_failed', onCommitSealFailed);
1912
+ this.mutationQueue.off('commit:created', onCommitCreated);
1854
1913
  snapshotsByCommit.clear();
1855
1914
  };
1856
1915
  }
@@ -1858,14 +1917,14 @@ export class SyncClient extends EventEmitter {
1858
1917
  * Wait for the latest in-flight transaction for (modelName, modelId)
1859
1918
  * to be confirmed by the server, or reject if it's rolled back.
1860
1919
  * Resolves immediately when no transaction is in flight — see
1861
- * `TransactionQueue.confirmationFor` for the lookup contract.
1920
+ * `MutationQueue.confirmationFor` for the lookup contract.
1862
1921
  *
1863
1922
  * Distinct from `waitForDeltaConfirmation(transactionId)` which keys
1864
1923
  * off a known tx id; this variant is for call sites that hold a
1865
1924
  * Model reference but never see the underlying transaction.
1866
1925
  */
1867
1926
  waitForConfirmation(modelName, modelId) {
1868
- return this.transactionQueue.confirmationFor(modelName, modelId);
1927
+ return this.mutationQueue.confirmationFor(modelName, modelId);
1869
1928
  }
1870
1929
  /**
1871
1930
  * Get detailed debug info for the sync debug page
@@ -1874,7 +1933,7 @@ export class SyncClient extends EventEmitter {
1874
1933
  return {
1875
1934
  connectionState: this.connectionState,
1876
1935
  pendingMutationsCount: this.pendingMutations.length,
1877
- transactionQueue: this.transactionQueue.getDebugInfo(),
1936
+ mutationQueue: this.mutationQueue.getDebugInfo(),
1878
1937
  };
1879
1938
  }
1880
1939
  // --- Best-practice assignment ops ---
@@ -1900,7 +1959,7 @@ export class SyncClient extends EventEmitter {
1900
1959
  * Mark a local transaction as optimistically applied. The matching
1901
1960
  * server delta (when it arrives with the same `transactionId`) will
1902
1961
  * be recognized as an echo and skip the pool mutation. Called
1903
- * automatically by `TransactionQueue` when a transaction is staged;
1962
+ * automatically by `MutationQueue` when a transaction is staged;
1904
1963
  * exposed publicly so tests can drive the API directly.
1905
1964
  */
1906
1965
  markTransactionPending(transactionId) {
@@ -1916,15 +1975,15 @@ export class SyncClient extends EventEmitter {
1916
1975
  return this.echoTracker.getMetrics();
1917
1976
  }
1918
1977
  /**
1919
- * Package-internal accessor for the {@link TransactionQueue}. Used by
1978
+ * Package-internal accessor for the {@link MutationQueue}. Used by
1920
1979
  * `Ablo.commits.create()` to route raw multi-operation envelopes through the
1921
1980
  * same retry-on-reconnect lane as the model proxy path, and by tests to
1922
1981
  * exercise the queue's interaction with {@link markTransactionPending} on the
1923
1982
  * real instance the SyncClient subscribes to. It is not re-exported to SDK
1924
1983
  * consumers; `Ablo` is the public surface.
1925
1984
  */
1926
- getTransactionQueue() {
1927
- return this.transactionQueue;
1985
+ getMutationQueue() {
1986
+ return this.mutationQueue;
1928
1987
  }
1929
1988
  applyDeltaBatchToPool(dbResults, enrichRelations) {
1930
1989
  const modelsToAdd = [];
@@ -1932,8 +1991,8 @@ export class SyncClient extends EventEmitter {
1932
1991
  const idsToRemove = [];
1933
1992
  const idsToArchive = [];
1934
1993
  // Pre-pass: collect every id slated for `remove` in this batch. The
1935
- // chart-delete flicker came from this exact pattern: a peer (or the
1936
- // user themself) deletes a chart with N layers; the commit produces
1994
+ // parent-delete flicker came from this exact pattern: a peer (or the
1995
+ // user themself) deletes a parent with N children; the commit produces
1937
1996
  // BOTH residual `update` deltas (from the optimistic edits that
1938
1997
  // happened just before the delete) AND `remove` deltas. The
1939
1998
  // `update` branch below would `createFromData` the row back into
@@ -1962,7 +2021,7 @@ export class SyncClient extends EventEmitter {
1962
2021
  }
1963
2022
  // If a later op in this batch will remove this id, skip earlier
1964
2023
  // add/update ops on it. Server FK ordering can produce
1965
- // U(layer)+D(layer) when an optimistic edit and a delete both
2024
+ // U(child)+D(child) when an optimistic edit and a delete both
1966
2025
  // commit in the same window; only the final state matters.
1967
2026
  if ((action === 'add' || action === 'update') && idsBeingRemoved.has(modelId)) {
1968
2027
  continue;
@@ -2015,7 +2074,7 @@ export class SyncClient extends EventEmitter {
2015
2074
  // fall-through.
2016
2075
  // Self-healing: the next catch-up poll / reconnect re-fetches and
2017
2076
  // re-applies this delta, so it's forensic, not consumer-actionable → debug.
2018
- getContext().logger.debug('[SyncClient.applyDeltaBatchToPool] skipping pool op for unpersisted delta', {
2077
+ this.runtime.logger.debug('[SyncClient.applyDeltaBatchToPool] skipping pool op for unpersisted delta', {
2019
2078
  modelName,
2020
2079
  modelId: modelId.slice(0, 12),
2021
2080
  });