@abloatai/ablo 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (483) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +684 -5
  3. package/README.md +39 -22
  4. package/dist/BaseSyncedStore.d.ts +152 -44
  5. package/dist/BaseSyncedStore.js +300 -184
  6. package/dist/Database.d.ts +9 -24
  7. package/dist/Database.js +37 -22
  8. package/dist/InstanceCache.d.ts +25 -4
  9. package/dist/InstanceCache.js +48 -15
  10. package/dist/LazyReferenceCollection.d.ts +3 -3
  11. package/dist/LazyReferenceCollection.js +4 -4
  12. package/dist/Model.d.ts +6 -6
  13. package/dist/Model.js +10 -10
  14. package/dist/ModelRegistry.d.ts +4 -4
  15. package/dist/ModelRegistry.js +3 -3
  16. package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
  17. package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
  18. package/dist/SyncClient.d.ts +42 -32
  19. package/dist/SyncClient.js +166 -110
  20. package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
  21. package/dist/ai-sdk/coordinatedTool.js +1 -1
  22. package/dist/ai-sdk/coordinationContext.d.ts +2 -2
  23. package/dist/ai-sdk/coordinationContext.js +1 -1
  24. package/dist/ai-sdk/wrap.d.ts +3 -3
  25. package/dist/ai-sdk/wrap.js +2 -2
  26. package/dist/auth/index.d.ts +1 -156
  27. package/dist/auth/index.js +8 -301
  28. package/dist/cli.cjs +3459 -1126
  29. package/dist/client/Ablo.d.ts +42 -287
  30. package/dist/client/Ablo.js +118 -963
  31. package/dist/client/abloClient.d.ts +309 -0
  32. package/dist/client/abloClient.js +13 -0
  33. package/dist/client/clientPrelude.d.ts +52 -0
  34. package/dist/client/clientPrelude.js +60 -0
  35. package/dist/client/consoleLogger.d.ts +2 -2
  36. package/dist/client/coreClient.d.ts +60 -0
  37. package/dist/client/coreClient.js +118 -0
  38. package/dist/client/createInternalComponents.d.ts +4 -4
  39. package/dist/client/createInternalComponents.js +9 -8
  40. package/dist/client/createModelProxy.d.ts +78 -373
  41. package/dist/client/createModelProxy.js +114 -86
  42. package/dist/client/humans.d.ts +48 -0
  43. package/dist/client/humans.js +52 -0
  44. package/dist/client/modelRegistration.d.ts +1 -1
  45. package/dist/client/modelRegistration.js +9 -9
  46. package/dist/client/options.d.ts +73 -17
  47. package/dist/client/reactiveEngine.d.ts +48 -0
  48. package/dist/client/reactiveEngine.js +910 -0
  49. package/dist/client/resourceTypes.d.ts +9 -250
  50. package/dist/client/resourceTypes.js +8 -5
  51. package/dist/client/schemaConfig.d.ts +4 -4
  52. package/dist/client/schemaConfig.js +6 -2
  53. package/dist/client/validateAbloOptions.d.ts +3 -2
  54. package/dist/client/validateAbloOptions.js +1 -1
  55. package/dist/client/wsMutationExecutor.d.ts +3 -3
  56. package/dist/client/wsMutationExecutor.js +3 -3
  57. package/dist/context.d.ts +9 -9
  58. package/dist/context.js +10 -9
  59. package/dist/coordination/ClaimLog.d.ts +26 -0
  60. package/dist/coordination/ClaimLog.js +32 -0
  61. package/dist/coordination/index.d.ts +1 -15
  62. package/dist/coordination/index.js +8 -31
  63. package/dist/core/DatabaseManager.js +1 -1
  64. package/dist/core/QueryView.d.ts +1 -1
  65. package/dist/core/QueryView.js +1 -1
  66. package/dist/core/StoreManager.d.ts +4 -23
  67. package/dist/core/StoreManager.js +5 -55
  68. package/dist/core/index.d.ts +2 -2
  69. package/dist/core/index.js +2 -2
  70. package/dist/core/storeContract.d.ts +2 -2
  71. package/dist/docs/catalog.d.ts +72 -0
  72. package/dist/docs/catalog.js +227 -0
  73. package/dist/docs/index.d.ts +10 -0
  74. package/dist/docs/index.js +10 -0
  75. package/dist/environment.d.ts +1 -40
  76. package/dist/environment.js +8 -37
  77. package/dist/index.d.ts +40 -34
  78. package/dist/index.js +26 -20
  79. package/dist/interfaces/index.d.ts +44 -134
  80. package/dist/keys/index.d.ts +1 -77
  81. package/dist/keys/index.js +8 -190
  82. package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
  83. package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
  84. package/dist/mutators/Transaction.d.ts +1 -1
  85. package/dist/mutators/Transaction.js +1 -1
  86. package/dist/mutators/UndoManager.d.ts +6 -6
  87. package/dist/mutators/UndoManager.js +5 -5
  88. package/dist/mutators/defineMutators.d.ts +3 -3
  89. package/dist/mutators/defineMutators.js +1 -1
  90. package/dist/mutators/inverseOp.js +2 -2
  91. package/dist/mutators/mutateActions.d.ts +3 -3
  92. package/dist/mutators/mutateActions.js +1 -1
  93. package/dist/mutators/readerActions.d.ts +1 -1
  94. package/dist/mutators/undoApply.d.ts +1 -1
  95. package/dist/mutators/undoApply.js +1 -1
  96. package/dist/policy/index.d.ts +2 -2
  97. package/dist/policy/index.js +1 -1
  98. package/dist/query/client.d.ts +2 -2
  99. package/dist/query/client.js +4 -4
  100. package/dist/query/types.d.ts +6 -41
  101. package/dist/query/types.js +2 -2
  102. package/dist/react/AbloProvider.d.ts +6 -8
  103. package/dist/react/AbloProvider.js +5 -7
  104. package/dist/react/context.d.ts +1 -1
  105. package/dist/react/context.js +1 -1
  106. package/dist/react/index.d.ts +5 -5
  107. package/dist/react/index.js +3 -3
  108. package/dist/react/internalContext.d.ts +1 -1
  109. package/dist/react/useAblo.d.ts +3 -3
  110. package/dist/react/useAblo.js +1 -1
  111. package/dist/react/useCurrentUserId.js +1 -1
  112. package/dist/react/useErrorListener.js +1 -1
  113. package/dist/react/useMutationFailureListener.d.ts +2 -2
  114. package/dist/react/useMutationFailureListener.js +1 -1
  115. package/dist/react/useMutators.d.ts +3 -3
  116. package/dist/react/useMutators.js +3 -3
  117. package/dist/react/useUndoScope.d.ts +5 -5
  118. package/dist/react/useUndoScope.js +1 -1
  119. package/dist/schema/coordination.d.ts +69 -10
  120. package/dist/schema/coordination.js +86 -9
  121. package/dist/schema/ddl.js +2 -2
  122. package/dist/schema/diff.d.ts +1 -1
  123. package/dist/schema/generate.js +1 -1
  124. package/dist/schema/index.d.ts +10 -10
  125. package/dist/schema/index.js +18 -18
  126. package/dist/schema/queries.d.ts +27 -27
  127. package/dist/schema/queries.js +23 -23
  128. package/dist/schema/select.d.ts +3 -3
  129. package/dist/schema/select.js +3 -3
  130. package/dist/schema/serialize.d.ts +15 -6
  131. package/dist/schema/serialize.js +17 -3
  132. package/dist/schema/sugar.d.ts +6 -7
  133. package/dist/schema/sugar.js +9 -12
  134. package/dist/schema/syncDeltaRow.d.ts +4 -152
  135. package/dist/schema/syncDeltaRow.js +4 -105
  136. package/dist/server/adapter.d.ts +18 -1
  137. package/dist/server/commit.d.ts +10 -16
  138. package/dist/server/index.d.ts +1 -1
  139. package/dist/server/index.js +1 -1
  140. package/dist/server/readConfig.d.ts +1 -1
  141. package/dist/source/adapters/drizzle.d.ts +1 -1
  142. package/dist/source/adapters/drizzle.js +2 -2
  143. package/dist/source/adapters/kysely.d.ts +1 -1
  144. package/dist/source/adapters/kysely.js +1 -1
  145. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  146. package/dist/source/adapters/kyselyMutationCore.js +2 -2
  147. package/dist/source/adapters/memory.js +1 -1
  148. package/dist/source/adapters/prisma.d.ts +8 -3
  149. package/dist/source/adapters/prisma.js +1 -1
  150. package/dist/source/connector.js +1 -1
  151. package/dist/source/connectorProtocol.d.ts +2 -8
  152. package/dist/source/connectorProtocol.js +3 -2
  153. package/dist/source/contract.d.ts +29 -17
  154. package/dist/source/contract.js +27 -22
  155. package/dist/source/factory.d.ts +1 -1
  156. package/dist/source/footprint.d.ts +111 -0
  157. package/dist/source/footprint.js +0 -0
  158. package/dist/source/idempotency.js +2 -2
  159. package/dist/source/index.d.ts +1 -0
  160. package/dist/source/index.js +3 -0
  161. package/dist/source/next.d.ts +1 -1
  162. package/dist/source/signing.d.ts +9 -2
  163. package/dist/source/signing.js +4 -1
  164. package/dist/source/types.d.ts +6 -4
  165. package/dist/source/types.js +1 -1
  166. package/dist/stores/ObjectStore.d.ts +1 -1
  167. package/dist/stores/SyncActionStore.d.ts +1 -1
  168. package/dist/stores/SyncActionStore.js +2 -10
  169. package/dist/stores/syncAction.d.ts +26 -0
  170. package/dist/stores/syncAction.js +16 -0
  171. package/dist/surface.d.ts +3 -3
  172. package/dist/surface.js +6 -4
  173. package/dist/sync/BootstrapFetcher.d.ts +123 -6
  174. package/dist/sync/BootstrapFetcher.js +492 -66
  175. package/dist/sync/ConnectionManager.d.ts +6 -198
  176. package/dist/sync/ConnectionManager.js +6 -677
  177. package/dist/sync/OnDemandLoader.d.ts +2 -2
  178. package/dist/sync/OnDemandLoader.js +60 -21
  179. package/dist/sync/SubscriptionManager.d.ts +13 -2
  180. package/dist/sync/SubscriptionManager.js +23 -5
  181. package/dist/sync/SyncWebSocket.d.ts +27 -510
  182. package/dist/sync/SyncWebSocket.js +76 -954
  183. package/dist/sync/awaitClaimGrant.d.ts +4 -44
  184. package/dist/sync/awaitClaimGrant.js +4 -109
  185. package/dist/sync/commitFrames.d.ts +6 -40
  186. package/dist/sync/commitFrames.js +6 -97
  187. package/dist/sync/contextPorts.d.ts +18 -0
  188. package/dist/sync/contextPorts.js +31 -0
  189. package/dist/sync/createClaimStream.d.ts +5 -49
  190. package/dist/sync/createClaimStream.js +5 -469
  191. package/dist/sync/createPresenceStream.d.ts +26 -4
  192. package/dist/sync/createPresenceStream.js +28 -20
  193. package/dist/sync/createSnapshot.d.ts +2 -2
  194. package/dist/sync/createSnapshot.js +1 -1
  195. package/dist/sync/credentialLifecycle.d.ts +5 -173
  196. package/dist/sync/credentialLifecycle.js +5 -320
  197. package/dist/sync/deltaPipeline.d.ts +1 -1
  198. package/dist/sync/participants.d.ts +5 -4
  199. package/dist/sync/participants.js +29 -22
  200. package/dist/sync/schemaDrift.d.ts +55 -0
  201. package/dist/sync/schemaDrift.js +53 -0
  202. package/dist/sync/schemas.d.ts +21 -32
  203. package/dist/sync/schemas.js +26 -17
  204. package/dist/sync/syncPlan.d.ts +3 -3
  205. package/dist/sync/wsFrameHandlers.d.ts +6 -114
  206. package/dist/sync/wsFrameHandlers.js +6 -392
  207. package/dist/testing/fixtures/bootstrap.d.ts +1 -1
  208. package/dist/testing/fixtures/deltas.d.ts +1 -1
  209. package/dist/testing/fixtures/httpResponses.d.ts +70 -0
  210. package/dist/testing/fixtures/httpResponses.js +90 -0
  211. package/dist/testing/fixtures/models.js +1 -1
  212. package/dist/testing/helpers/wait.js +1 -1
  213. package/dist/testing/mocks/MockMutationExecutor.d.ts +2 -2
  214. package/dist/testing/mocks/MockMutationExecutor.js +8 -14
  215. package/dist/testing/mocks/MockSyncContext.d.ts +11 -11
  216. package/dist/testing/mocks/MockSyncContext.js +10 -9
  217. package/dist/testing/mocks/MockSyncStore.js +1 -1
  218. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  219. package/dist/transaction/ablo.d.ts +88 -0
  220. package/dist/transaction/ablo.js +33 -0
  221. package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
  222. package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
  223. package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
  224. package/dist/transaction/auth/bootstrapScope.js +1 -0
  225. package/dist/transaction/auth/capability.d.ts +177 -0
  226. package/dist/transaction/auth/capability.js +199 -0
  227. package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
  228. package/dist/{client → transaction/auth}/identity.d.ts +8 -7
  229. package/dist/{client → transaction/auth}/identity.js +1 -1
  230. package/dist/transaction/auth/index.d.ts +162 -0
  231. package/dist/transaction/auth/index.js +304 -0
  232. package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
  233. package/dist/{auth → transaction/auth}/schemas.js +13 -13
  234. package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
  235. package/dist/{client → transaction/auth}/sessionMint.js +4 -7
  236. package/dist/transaction/coordination/awaitClaimGrant.d.ts +49 -0
  237. package/dist/transaction/coordination/awaitClaimGrant.js +112 -0
  238. package/dist/transaction/coordination/claimMeta.d.ts +49 -0
  239. package/dist/transaction/coordination/claimMeta.js +52 -0
  240. package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
  241. package/dist/transaction/coordination/createClaimStream.js +475 -0
  242. package/dist/transaction/coordination/events.d.ts +74 -0
  243. package/dist/transaction/coordination/events.js +7 -0
  244. package/dist/transaction/coordination/index.d.ts +19 -0
  245. package/dist/transaction/coordination/index.js +44 -0
  246. package/dist/transaction/coordination/locator.d.ts +83 -0
  247. package/dist/transaction/coordination/locator.js +82 -0
  248. package/dist/transaction/coordination/schema.d.ts +1473 -0
  249. package/dist/{coordination → transaction/coordination}/schema.js +490 -55
  250. package/dist/transaction/coordination/targetConflict.d.ts +2 -0
  251. package/dist/transaction/coordination/targetConflict.js +103 -0
  252. package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
  253. package/dist/{coordination → transaction/coordination}/trace.js +18 -25
  254. package/dist/transaction/durableWrites.d.ts +62 -0
  255. package/dist/{client → transaction}/durableWrites.js +28 -3
  256. package/dist/transaction/environment.d.ts +105 -0
  257. package/dist/transaction/environment.js +108 -0
  258. package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +11 -11
  259. package/dist/{errorCodes.js → transaction/errorCodes.js} +35 -12
  260. package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
  261. package/dist/{errors.js → transaction/errors.js} +85 -16
  262. package/dist/transaction/index.d.ts +20 -0
  263. package/dist/transaction/index.js +20 -0
  264. package/dist/transaction/keys/index.d.ts +87 -0
  265. package/dist/transaction/keys/index.js +207 -0
  266. package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
  267. package/dist/transaction/log/syncDeltaRow.js +95 -0
  268. package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
  269. package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
  270. package/dist/transaction/logger.d.ts +16 -0
  271. package/dist/transaction/logger.js +7 -0
  272. package/dist/transaction/observability.d.ts +53 -0
  273. package/dist/transaction/observability.js +19 -0
  274. package/dist/transaction/plugin.d.ts +192 -0
  275. package/dist/transaction/plugin.js +87 -0
  276. package/dist/{policy → transaction/policy}/types.d.ts +3 -3
  277. package/dist/{policy → transaction/policy}/types.js +2 -0
  278. package/dist/transaction/resources/httpResources.d.ts +266 -0
  279. package/dist/transaction/resources/httpResources.js +7 -0
  280. package/dist/transaction/resources/modelOperations.d.ts +319 -0
  281. package/dist/transaction/resources/modelOperations.js +12 -0
  282. package/dist/transaction/resources/mutationOptions.d.ts +66 -0
  283. package/dist/transaction/resources/mutationOptions.js +9 -0
  284. package/dist/transaction/resources/where.d.ts +85 -0
  285. package/dist/transaction/resources/where.js +70 -0
  286. package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
  287. package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
  288. package/dist/{schema → transaction/schema}/field.d.ts +5 -5
  289. package/dist/{schema → transaction/schema}/field.js +5 -5
  290. package/dist/transaction/schema/loadStrategy.d.ts +45 -0
  291. package/dist/transaction/schema/loadStrategy.js +46 -0
  292. package/dist/{schema → transaction/schema}/model.d.ts +50 -35
  293. package/dist/{schema → transaction/schema}/model.js +30 -20
  294. package/dist/transaction/schema/openapi.d.ts +57 -0
  295. package/dist/transaction/schema/openapi.js +340 -0
  296. package/dist/{schema → transaction/schema}/relation.d.ts +14 -14
  297. package/dist/{schema → transaction/schema}/relation.js +7 -7
  298. package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
  299. package/dist/{schema → transaction/schema}/residency.js +0 -5
  300. package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
  301. package/dist/{schema → transaction/schema}/roles.js +5 -5
  302. package/dist/{schema → transaction/schema}/schema.d.ts +12 -10
  303. package/dist/{schema → transaction/schema}/schema.js +4 -3
  304. package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
  305. package/dist/{schema → transaction/schema}/tenancy.js +7 -4
  306. package/dist/transaction/transactionLayer.d.ts +82 -0
  307. package/dist/transaction/transactionLayer.js +24 -0
  308. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +4 -5
  309. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
  310. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
  311. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
  312. package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +10 -36
  313. package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
  314. package/dist/transaction/transport/commitFrames.d.ts +90 -0
  315. package/dist/transaction/transport/commitFrames.js +134 -0
  316. package/dist/transaction/transport/connectionManager.d.ts +215 -0
  317. package/dist/transaction/transport/connectionManager.js +673 -0
  318. package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
  319. package/dist/transaction/transport/credentialLifecycle.js +324 -0
  320. package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
  321. package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
  322. package/dist/{client → transaction/transport}/httpClient.d.ts +59 -16
  323. package/dist/{client → transaction/transport}/httpClient.js +5 -5
  324. package/dist/transaction/transport/httpOptions.d.ts +33 -0
  325. package/dist/transaction/transport/httpOptions.js +12 -0
  326. package/dist/{client → transaction/transport}/httpTransport.js +171 -85
  327. package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
  328. package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
  329. package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
  330. package/dist/transaction/transport/wsFrameHandlers.js +429 -0
  331. package/dist/transaction/transport/wsTransport.d.ts +576 -0
  332. package/dist/transaction/transport/wsTransport.js +1017 -0
  333. package/dist/transaction/types/assertExact.d.ts +17 -0
  334. package/dist/transaction/types/assertExact.js +1 -0
  335. package/dist/{types → transaction/types}/global.d.ts +17 -2
  336. package/dist/{types → transaction/types}/global.js +2 -1
  337. package/dist/{types → transaction/types}/index.d.ts +14 -46
  338. package/dist/{types → transaction/types}/index.js +7 -16
  339. package/dist/{types → transaction/types}/streams.d.ts +63 -45
  340. package/dist/{utils → transaction/utils}/json.d.ts +18 -0
  341. package/dist/transaction/utils/json.js +276 -0
  342. package/dist/transaction/wire/accountResponses.d.ts +351 -0
  343. package/dist/transaction/wire/accountResponses.js +255 -0
  344. package/dist/transaction/wire/auth.d.ts +49 -0
  345. package/dist/transaction/wire/auth.js +57 -0
  346. package/dist/transaction/wire/claimEvent.d.ts +76 -0
  347. package/dist/transaction/wire/claimEvent.js +73 -0
  348. package/dist/transaction/wire/claims.d.ts +463 -0
  349. package/dist/transaction/wire/claims.js +229 -0
  350. package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
  351. package/dist/{wire → transaction/wire}/commit.js +68 -47
  352. package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
  353. package/dist/{wire → transaction/wire}/delta.js +37 -13
  354. package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
  355. package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
  356. package/dist/transaction/wire/feedCursor.d.ts +60 -0
  357. package/dist/transaction/wire/feedCursor.js +82 -0
  358. package/dist/transaction/wire/feedEvent.d.ts +177 -0
  359. package/dist/transaction/wire/feedEvent.js +39 -0
  360. package/dist/transaction/wire/frames.d.ts +194 -0
  361. package/dist/transaction/wire/frames.js +50 -0
  362. package/dist/transaction/wire/inboundFrames.d.ts +552 -0
  363. package/dist/transaction/wire/inboundFrames.js +116 -0
  364. package/dist/transaction/wire/index.d.ts +50 -0
  365. package/dist/transaction/wire/index.js +74 -0
  366. package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
  367. package/dist/transaction/wire/listEnvelope.js +42 -0
  368. package/dist/transaction/wire/modelResponses.d.ts +85 -0
  369. package/dist/transaction/wire/modelResponses.js +43 -0
  370. package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +79 -38
  371. package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +110 -59
  372. package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
  373. package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
  374. package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
  375. package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
  376. package/dist/transactions/mutations/commitLatency.d.ts +52 -0
  377. package/dist/transactions/mutations/commitLatency.js +130 -0
  378. package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
  379. package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
  380. package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +16 -15
  381. package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +12 -12
  382. package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +11 -11
  383. package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +7 -7
  384. package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
  385. package/dist/transactions/mutations/durableWriteStore.js +12 -0
  386. package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
  387. package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +3 -3
  388. package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +4 -3
  389. package/dist/utils/mobxSetup.d.ts +1 -1
  390. package/dist/utils/mobxSetup.js +5 -2
  391. package/dist/webhooks/events.d.ts +2 -2
  392. package/dist/wire/index.d.ts +1 -34
  393. package/dist/wire/index.js +8 -49
  394. package/docs/agent-messaging.md +3 -3
  395. package/docs/agents.md +19 -12
  396. package/docs/api-keys.md +8 -4
  397. package/docs/api.md +22 -18
  398. package/docs/audit.md +2 -0
  399. package/docs/cli.md +31 -3
  400. package/docs/client-behavior.md +8 -6
  401. package/docs/concurrency-convention.md +30 -24
  402. package/docs/coordination.md +48 -38
  403. package/docs/data-sources.md +3 -1
  404. package/docs/debugging.md +5 -3
  405. package/docs/deployment.md +267 -0
  406. package/docs/examples/agent-human.md +49 -42
  407. package/docs/examples/ai-sdk-tool.md +69 -44
  408. package/docs/examples/existing-python-backend.md +8 -6
  409. package/docs/examples/nextjs.md +129 -47
  410. package/docs/examples/scoped-agent.md +45 -44
  411. package/docs/examples/server-agent.md +46 -26
  412. package/docs/groups.md +32 -29
  413. package/docs/guarantees.md +4 -2
  414. package/docs/how-it-works.md +9 -7
  415. package/docs/idempotency.md +126 -0
  416. package/docs/identity.md +58 -54
  417. package/docs/index.md +172 -84
  418. package/docs/integration-guide.md +17 -16
  419. package/docs/interaction-model.md +6 -4
  420. package/docs/mcp.md +41 -16
  421. package/docs/migration.md +63 -5
  422. package/docs/operating-on-your-database.md +111 -0
  423. package/docs/projects.md +2 -0
  424. package/docs/quickstart.md +22 -5
  425. package/docs/react.md +12 -10
  426. package/docs/schema-contract.md +5 -3
  427. package/docs/session-settings.md +108 -0
  428. package/docs/sessions.md +3 -1
  429. package/docs/webhooks.md +3 -1
  430. package/llms.txt +47 -17
  431. package/package.json +10 -8
  432. package/dist/agent/Agent.d.ts +0 -366
  433. package/dist/agent/Agent.js +0 -514
  434. package/dist/agent/index.d.ts +0 -115
  435. package/dist/agent/index.js +0 -128
  436. package/dist/agent/session.d.ts +0 -93
  437. package/dist/agent/session.js +0 -149
  438. package/dist/agent/types.d.ts +0 -68
  439. package/dist/agent/types.js +0 -9
  440. package/dist/client/durableWrites.d.ts +0 -21
  441. package/dist/coordination/schema.d.ts +0 -722
  442. package/dist/schema/openapi.d.ts +0 -29
  443. package/dist/schema/openapi.js +0 -124
  444. package/dist/transactions/durableWriteStore.js +0 -30
  445. package/dist/utils/json.js +0 -88
  446. package/dist/wire/errorEnvelope.d.ts +0 -55
  447. package/dist/wire/frames.d.ts +0 -197
  448. package/dist/wire/frames.js +0 -49
  449. package/dist/wire/listEnvelope.js +0 -18
  450. /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
  451. /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
  452. /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
  453. /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
  454. /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
  455. /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
  456. /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
  457. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +0 -0
  458. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +0 -0
  459. /package/dist/{client → transaction}/persistence.d.ts +0 -0
  460. /package/dist/{client → transaction}/persistence.js +0 -0
  461. /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
  462. /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
  463. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
  464. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
  465. /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
  466. /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
  467. /package/dist/{types → transaction/types}/modelData.js +0 -0
  468. /package/dist/{types → transaction/types}/participant.d.ts +0 -0
  469. /package/dist/{types → transaction/types}/participant.js +0 -0
  470. /package/dist/{types → transaction/types}/streams.js +0 -0
  471. /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
  472. /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
  473. /package/dist/{utils → transaction/utils}/duration.d.ts +0 -0
  474. /package/dist/{utils → transaction/utils}/duration.js +0 -0
  475. /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
  476. /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
  477. /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
  478. /package/dist/{wire → transaction/wire}/protocol.js +0 -0
  479. /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
  480. /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
  481. /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
  482. /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
  483. /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
@@ -11,17 +11,18 @@
11
11
  * in-memory models, and {@link ModelRegistry} holds their metadata.
12
12
  */
13
13
  import { makeObservable, observable, action, computed, runInAction } from 'mobx';
14
- import { AbloConnectionError, AbloValidationError, toAbloError } from './errors.js';
14
+ import { AbloConnectionError, AbloValidationError, toAbloError } from './transaction/errors.js';
15
15
  import { ConnectionManager } from './sync/ConnectionManager.js';
16
+ import { contextLogger, contextSocketObservability } from './sync/contextPorts.js';
16
17
  import { SubscriptionManager } from './sync/SubscriptionManager.js';
17
18
  import { resolveParticipantSyncGroups, } from './sync/participants.js';
18
19
  import { ModelRegistry } from './ModelRegistry.js';
19
- import { PropertyType } from './types/index.js';
20
+ import { PropertyType } from './transaction/types/index.js';
20
21
  import { SyncWebSocket, } from './sync/SyncWebSocket.js';
21
22
  import { QueryProcessor } from './core/QueryProcessor.js';
22
23
  import { Model, rowAsModel } from './Model.js';
23
24
  import { getContext } from './context.js';
24
- import { SyncSessionError, isAccessCredentialExpiryCloseReason } from './errors.js';
25
+ import { AbloSessionError, isAccessCredentialExpiryCloseReason } from './transaction/errors.js';
25
26
  import { ModelScope } from './InstanceCache.js';
26
27
  import { LazyReferenceCollection } from './LazyReferenceCollection.js';
27
28
  import { deriveSyncPlanFromSchema } from './sync/syncPlan.js';
@@ -29,9 +30,15 @@ import { CredentialLifecycle } from './sync/credentialLifecycle.js';
29
30
  import * as groupChange from './sync/groupChange.js';
30
31
  import * as bootstrapApply from './sync/bootstrapApply.js';
31
32
  import * as deltaPipeline from './sync/deltaPipeline.js';
32
- /** Bootstrap timeout configuration */
33
+ /**
34
+ * Bootstrap retry configuration.
35
+ *
36
+ * There is deliberately no overall timeout here. How long one attempt may run
37
+ * is not a policy this layer gets to invent — it is a property of the fetcher's
38
+ * watchdogs, read from `BootstrapFetcher.budgetMs`. A second number kept here
39
+ * would only be able to disagree with them, which is exactly what it used to do.
40
+ */
33
41
  export const BOOTSTRAP_CONFIG = {
34
- OVERALL_TIMEOUT_MS: 15_000,
35
42
  MAX_RETRY_ATTEMPTS: 3,
36
43
  RETRY_DELAY_MS: 500,
37
44
  };
@@ -58,13 +65,13 @@ export { deriveSyncPlanFromSchema } from './sync/syncPlan.js';
58
65
  * the underlying SyncWebSocket without casts:
59
66
  *
60
67
  * @example
61
- * interface AbloEvents {
62
- * 'sheet:selection': [SheetSelectionEvent];
63
- * 'slide:cursor': [SlideCursorEvent];
68
+ * interface EditorEvents {
69
+ * 'document:selection': [SelectionEvent];
70
+ * 'document:cursor': [CursorEvent];
64
71
  * }
65
- * class SyncedStore extends BaseSyncedStore<AbloEvents> {
66
- * subscribeToSlideCursor(handler: (e: SlideCursorEvent) => void) {
67
- * return this.syncWebSocket?.subscribe('slide:cursor', handler);
72
+ * class EditorStore extends BaseSyncedStore<EditorEvents> {
73
+ * subscribeToCursor(handler: (e: CursorEvent) => void) {
74
+ * return this.syncWebSocket.subscribe('document:cursor', handler);
68
75
  * }
69
76
  * }
70
77
  */
@@ -88,15 +95,20 @@ export class BaseSyncedStore {
88
95
  */
89
96
  schema;
90
97
  // ── Real-time sync ──
91
- syncWebSocket = null;
98
+ /**
99
+ * The connection, owned by whoever built this store (ADR 0016 follow-up
100
+ * 3b): the host constructs it and hands it in, the store seeds its late
101
+ * values during `initialize()` and owns the lifecycle from there. One
102
+ * instance for the store's whole lifetime — reconnects replace the socket
103
+ * inside it, never the object.
104
+ */
105
+ syncWebSocket;
92
106
  /**
93
107
  * Dynamic read interest (area-of-interest) over the connection's sync
94
- * groups. Lives alongside `syncWebSocket` and is recreated with it; the
95
- * stable `enterScope`/`leaveScope`/`pinScope`/`unpinScope` methods forward
96
- * to whichever instance is current, so callers (the React participant
97
- * hook) never hold a stale reference. Null until `setupWebSocketSync`.
108
+ * groups. Constructed with the connection; the permanent base scopes are
109
+ * seeded in `setupWebSocketSync` once identity resolves.
98
110
  */
99
- areaOfInterest = null;
111
+ areaOfInterest;
100
112
  /** Sync groups whose current state has been backfilled into the pool
101
113
  * (hydrate-on-enter). Cleared when the pool is reset on (re)bootstrap. */
102
114
  hydratedGroups = new Set();
@@ -104,27 +116,50 @@ export class BaseSyncedStore {
104
116
  * enters of the same scope so they share one fetch. */
105
117
  hydratingGroups = new Map();
106
118
  _syncServerUrl;
119
+ /** Application-declared collaboration event types; empty unless configured. */
120
+ _collaborationEvents = [];
107
121
  /**
108
122
  * Public accessor for the underlying SyncWebSocket. Used by the
109
123
  * factory in `createSyncEngine` to wire the default mutation
110
124
  * executor — the executor needs the WS handle to send commit
111
125
  * frames, and the factory can't reach `protected` state through
112
- * normal typing. Returns null until WS is initialized during
113
- * `initialize()`.
126
+ * normal typing.
114
127
  */
115
128
  getSyncWebSocket() {
116
129
  return this.syncWebSocket;
117
130
  }
131
+ /**
132
+ * Subscribe to pushed frames — deltas, presence updates, claim grants and
133
+ * losses, connection changes, and this store's collaboration events.
134
+ * Durable by construction: the connection object exists for the store's
135
+ * whole lifetime (reconnects replace only the socket inside it), so a
136
+ * subscription made before the first connect starts delivering when the
137
+ * socket opens and keeps delivering across every reconnect. Returns the
138
+ * unsubscribe function.
139
+ */
140
+ subscribe(event, handler) {
141
+ return this.syncWebSocket.subscribe(event, handler);
142
+ }
143
+ /**
144
+ * Send a collaboration event (an app-specific real-time message from this
145
+ * store's `TCollaboration` map). A no-op while the connection is down —
146
+ * presence-grade traffic is not queued.
147
+ */
148
+ sendCollaborationEvent(messageType, payload) {
149
+ this.syncWebSocket.sendCollaborationEvent(messageType, payload);
150
+ }
118
151
  // ── Area-of-interest (dynamic read subscription) ─────────────────
119
152
  //
120
153
  // `enterScope`/`leaveScope` move the connection's read interest as the
121
- // user navigates (open or close a deck, sheet, or doc); `pinScope`/`unpinScope`
154
+ // user navigates (open or close a record); `pinScope`/`unpinScope`
122
155
  // express prominence (an active claim keeps a group subscribed). All four
123
156
  // resolve the scope to sync-group strings through the same resolver the
124
157
  // claim path uses (`resolveParticipantSyncGroups`), so read interest and
125
- // write claims always agree on the string for a given entity. They are
126
- // no-ops before the socket exists, and they never reject when the transport
127
- // is offline (see {@link SubscriptionManager.reconcile}).
158
+ // write claims always agree on the string for a given entity. Before the
159
+ // connection opens they record interest without a wire send, and they
160
+ // never reject when the transport is offline (see
161
+ // {@link SubscriptionManager.reconcile}); the on-connect `resync` pushes
162
+ // whatever interest accumulated.
128
163
  scopeToGroups(scope) {
129
164
  return resolveParticipantSyncGroups(scope, this.schema);
130
165
  }
@@ -137,11 +172,8 @@ export class BaseSyncedStore {
137
172
  * and the live delta stream keeps flowing regardless.
138
173
  */
139
174
  enterScope(scope, opts) {
140
- const mgr = this.areaOfInterest;
141
- if (!mgr)
142
- return Promise.resolve();
143
175
  const groups = this.scopeToGroups(scope);
144
- const subscribed = Promise.all(groups.map((g) => mgr.enter(g))).then(() => undefined);
176
+ const subscribed = Promise.all(groups.map((g) => this.areaOfInterest.enter(g))).then(() => undefined);
145
177
  if (!opts?.hydrate)
146
178
  return subscribed;
147
179
  return subscribed.then(() => this.hydrateGroups(groups));
@@ -188,24 +220,15 @@ export class BaseSyncedStore {
188
220
  }
189
221
  /** Leave a scope → its groups go warm (hysteresis), then drop on sweep. */
190
222
  leaveScope(scope) {
191
- const mgr = this.areaOfInterest;
192
- if (!mgr)
193
- return Promise.resolve();
194
- return Promise.all(this.scopeToGroups(scope).map((g) => mgr.leave(g))).then(() => undefined);
223
+ return Promise.all(this.scopeToGroups(scope).map((g) => this.areaOfInterest.leave(g))).then(() => undefined);
195
224
  }
196
225
  /** Pin a scope (active claim / prominence) → never warms while pinned. */
197
226
  pinScope(scope) {
198
- const mgr = this.areaOfInterest;
199
- if (!mgr)
200
- return Promise.resolve();
201
- return Promise.all(this.scopeToGroups(scope).map((g) => mgr.pin(g))).then(() => undefined);
227
+ return Promise.all(this.scopeToGroups(scope).map((g) => this.areaOfInterest.pin(g))).then(() => undefined);
202
228
  }
203
229
  /** Release a pin → the group transitions to warm rather than dropping. */
204
230
  unpinScope(scope) {
205
- const mgr = this.areaOfInterest;
206
- if (!mgr)
207
- return Promise.resolve();
208
- return Promise.all(this.scopeToGroups(scope).map((g) => mgr.unpin(g))).then(() => undefined);
231
+ return Promise.all(this.scopeToGroups(scope).map((g) => this.areaOfInterest.unpin(g))).then(() => undefined);
209
232
  }
210
233
  // ── Internal helpers ──
211
234
  queryProcessor;
@@ -237,18 +260,20 @@ export class BaseSyncedStore {
237
260
  pendingDeltas = [];
238
261
  batchTimer = null;
239
262
  syncPromise = null;
240
- /** Resume/ack cursor — delegates to the shared SyncPosition (see
241
- * sync/syncPosition.ts). Advances only after IDB persistence. */
263
+ /** Resume/ack cursor — delegates to the shared LogPosition (see
264
+ * logPosition.ts). Advances only after IDB persistence. */
242
265
  get lastAckedId() {
243
266
  return this.syncClient.position.persisted;
244
267
  }
245
- /** Pool-applied cursor — delegates to the shared SyncPosition. */
268
+ /** Pool-applied cursor — delegates to the shared LogPosition. */
246
269
  get highestProcessedSyncId() {
247
270
  return this.syncClient.position.applied;
248
271
  }
249
272
  // ── Delta queuing during bootstrap ──
250
273
  bootstrapDeltaQueue = null;
251
274
  activeBootstrapCount = 0;
275
+ /** The live deadline for the bootstrap attempt in flight, if any. */
276
+ bootstrapDeadlineTimer = null;
252
277
  // ── Delete tracking ──
253
278
  pendingDeletes = new Set();
254
279
  // ── Model type hydration ──
@@ -262,6 +287,30 @@ export class BaseSyncedStore {
262
287
  this.auth = dependencies.auth;
263
288
  this.schema = dependencies.schema;
264
289
  this._syncServerUrl = dependencies.url;
290
+ this._collaborationEvents = config.collaborationEvents ?? [];
291
+ // The connection exists from construction (ADR 0016 follow-up 3b): the
292
+ // host hands one in, or the store builds its own. `deferConnect` holds
293
+ // it closed until `initialize()` has seeded identity and read scope, so
294
+ // nothing can open an unscoped connection in between.
295
+ this.syncWebSocket =
296
+ dependencies.syncWebSocket ??
297
+ new SyncWebSocket({
298
+ baseUrl: this._syncServerUrl,
299
+ collaborationEvents: [...this._collaborationEvents],
300
+ getAuthToken: this.auth?.getAuthToken,
301
+ deferConnect: true,
302
+ capabilities: {
303
+ partialBootstrap: true,
304
+ compressedDeltas: true,
305
+ streamingBootstrap: true,
306
+ batchedDeltas: true,
307
+ },
308
+ });
309
+ this.areaOfInterest = new SubscriptionManager({ transport: this.syncWebSocket });
310
+ this.wireSocketEvents();
311
+ // QueuedMutation events for pendingChanges tracking — connection-
312
+ // independent, wired once for the store's lifetime.
313
+ this.disposers.push(this.syncClient.onTransactionEvent('created', () => { this.incrementPendingChanges(); }), this.syncClient.onTransactionEvent('completed', () => { this.decrementPendingChanges(); }), this.syncClient.onTransactionEvent('failed', () => { this.decrementPendingChanges(); }));
265
314
  // Set this store as the global Model store
266
315
  Model.setStore(this);
267
316
  // ── Schema-derived sync plan ───────────────────────────────────────
@@ -414,7 +463,7 @@ export class BaseSyncedStore {
414
463
  reportSessionExpired: () => {
415
464
  this.connectionManager?.send({ type: 'BOOTSTRAP_FAILED_SESSION' });
416
465
  },
417
- });
466
+ }, contextLogger);
418
467
  /**
419
468
  * Listeners registered via `subscribeSessionError()`. Fired when the
420
469
  * WebSocket closes with a session-invalid code (1008/4001/4003) or a
@@ -435,7 +484,7 @@ export class BaseSyncedStore {
435
484
  }
436
485
  /**
437
486
  * Subscribe to per-mutation failure payloads. Forwarded from the
438
- * underlying `SyncClient.transactionQueue` so consumers (toast layer,
487
+ * underlying `SyncClient.mutationQueue` so consumers (toast layer,
439
488
  * route-level reverted boundaries, telemetry) can react without
440
489
  * reaching across the store. Returns an unsubscribe function.
441
490
  *
@@ -448,6 +497,15 @@ export class BaseSyncedStore {
448
497
  subscribeMutationFailure(listener) {
449
498
  return this.syncClient.onMutationFailure(listener);
450
499
  }
500
+ /**
501
+ * Subscribe to commit round-trip latency. Forwarded from the underlying
502
+ * `SyncClient` for the same reason as `subscribeMutationFailure` — the
503
+ * React provider binds against this surface, so the engine's wiring stays
504
+ * private while the SDK keeps one hook to expose.
505
+ */
506
+ subscribeCommitLatency(listener) {
507
+ return this.syncClient.onCommitLatency(listener);
508
+ }
451
509
  /**
452
510
  * Wait for the in-flight transaction for (modelName, modelId) to be
453
511
  * confirmed by the server. See `SyncClient.waitForConfirmation` for the
@@ -459,19 +517,19 @@ export class BaseSyncedStore {
459
517
  /**
460
518
  * Observe the LOCAL mutation stream for undo recording (see
461
519
  * {@link import('./core/storeContract.js').LocalMutation}). Taps the
462
- * TransactionQueue's `transaction:created` event — fired once per local
520
+ * MutationQueue's `transaction:created` event — fired once per local
463
521
  * create/update/delete/archive with `previousData` already captured.
464
522
  * Remote/collaborator deltas apply via `applyDeltaBatchToPool` and never
465
523
  * emit here, so undo is naturally local-only (you can't undo a teammate).
466
524
  */
467
525
  subscribeLocalMutations(handler) {
468
- // Tap the TransactionQueue directly via `onLocalTransaction`. The previous
526
+ // Tap the MutationQueue directly via `onLocalTransaction`. The previous
469
527
  // `syncClient.subscribe('transaction:created', …)` route registered the
470
528
  // handler on SyncClient's OWN emitter, which never fires that event (only
471
529
  // the queue's emitter does) — so undo recorded nothing. See
472
530
  // `SyncClient.onLocalTransaction` for the full rationale.
473
531
  return this.syncClient.onLocalTransaction((tx) => {
474
- if (!tx?.type || !tx.modelName || !tx.modelId)
532
+ if (!tx.modelName || !tx.modelId)
475
533
  return;
476
534
  handler({
477
535
  type: tx.type,
@@ -489,69 +547,113 @@ export class BaseSyncedStore {
489
547
  */
490
548
  async executeBootstrapWithTimeout(bootstrapFn, _context, signal) {
491
549
  let lastError = null;
492
- for (let attempt = 1; attempt <= BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS; attempt++) {
493
- if (signal?.aborted) {
494
- throw new DOMException('Initialization aborted', 'AbortError');
495
- }
496
- // `navigator.onLine === false` is the MDN-reliable "definitely
497
- // offline" signal. Don't use `!navigator.onLine`: Node 22+ exposes
498
- // `globalThis.navigator` with `onLine === undefined`, so the
499
- // negation false-positives every server-side bootstrap (e.g. the
500
- // server-side agent.run dispatch path through `connectAgent`).
501
- if (typeof navigator !== 'undefined' && navigator.onLine === false) {
502
- getContext().observability.breadcrumb(`Bootstrap attempt ${attempt} skipped - offline`, 'sync.bootstrap', 'warning');
503
- throw new AbloConnectionError('Bootstrap skipped - device is offline', {
504
- code: 'bootstrap_offline',
505
- });
506
- }
507
- try {
508
- getContext().logger.info(`[BaseSyncedStore] Bootstrap attempt ${attempt}/${BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS}`);
509
- const result = (await Promise.race([
510
- bootstrapFn(),
511
- this.createBootstrapTimeout(attempt),
512
- ]));
513
- getContext().logger.info('[BaseSyncedStore] Bootstrap completed successfully', { attempt });
514
- return result;
515
- }
516
- catch (error) {
517
- lastError = error;
518
- const isTimeout = error instanceof Error && error.message.includes('timed out');
519
- const isAbort = error instanceof DOMException && error.name === 'AbortError';
520
- const isNetworkError = error instanceof TypeError && error.message.includes('fetch');
521
- if (isAbort)
522
- throw error;
523
- if (SyncSessionError.isSessionError(error))
524
- throw error;
525
- if (isNetworkError && typeof navigator !== 'undefined' && navigator.onLine === false) {
526
- getContext().observability.captureBootstrapFailure(error, { type: 'network-offline' });
527
- throw error;
550
+ // An aborted initialize has to stop the transfer, not just stop waiting for
551
+ // it. Without this the caller returns while a cold start keeps downloading,
552
+ // and those chunks are still in flight when the next initialize begins.
553
+ const onCallerAbort = () => { this.database.helper.abort(); };
554
+ signal?.addEventListener('abort', onCallerAbort, { once: true });
555
+ try {
556
+ for (let attempt = 1; attempt <= BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS; attempt++) {
557
+ if (signal?.aborted) {
558
+ throw new DOMException('Initialization aborted', 'AbortError');
528
559
  }
529
- getContext().observability.breadcrumb(`Bootstrap attempt ${attempt} failed`, 'sync.bootstrap', 'warning', { isTimeout, isNetworkError, willRetry: attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS });
530
- if (isTimeout && attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS) {
531
- getContext().logger.info('[BaseSyncedStore] Resetting state before bootstrap retry');
532
- this.resetBootstrapState();
533
- await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_CONFIG.RETRY_DELAY_MS));
560
+ // `navigator.onLine === false` is the MDN-reliable "definitely
561
+ // offline" signal. Don't use `!navigator.onLine`: Node 22+ exposes
562
+ // `globalThis.navigator` with `onLine === undefined`, so the
563
+ // negation false-positives every server-side bootstrap (e.g. the
564
+ // server-side agent.run dispatch path through `connectAgent`).
565
+ if (typeof navigator !== 'undefined' && navigator.onLine === false) {
566
+ getContext().observability.breadcrumb(`Bootstrap attempt ${attempt} skipped - offline`, 'sync.bootstrap', 'warning');
567
+ throw new AbloConnectionError('Bootstrap skipped - device is offline', {
568
+ code: 'bootstrap_offline',
569
+ });
534
570
  }
535
- else if (!isTimeout && attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS) {
536
- await new Promise((resolve) => setTimeout(resolve, 1000));
571
+ try {
572
+ getContext().logger.info(`[BaseSyncedStore] Bootstrap attempt ${attempt}/${BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS}`);
573
+ const result = (await Promise.race([
574
+ bootstrapFn(),
575
+ this.createBootstrapTimeout(attempt),
576
+ ]));
577
+ getContext().logger.info('[BaseSyncedStore] Bootstrap completed successfully', { attempt });
578
+ return result;
579
+ }
580
+ catch (error) {
581
+ lastError = error;
582
+ const isTimeout = error instanceof Error && error.message.includes('timed out');
583
+ const isAbort = error instanceof DOMException && error.name === 'AbortError';
584
+ const isNetworkError = error instanceof TypeError && error.message.includes('fetch');
585
+ if (isAbort)
586
+ throw error;
587
+ if (AbloSessionError.isSessionError(error))
588
+ throw error;
589
+ if (isNetworkError && typeof navigator !== 'undefined' && navigator.onLine === false) {
590
+ getContext().observability.captureBootstrapFailure(error, { type: 'network-offline' });
591
+ throw error;
592
+ }
593
+ getContext().observability.breadcrumb(`Bootstrap attempt ${attempt} failed`, 'sync.bootstrap', 'warning', { isTimeout, isNetworkError, willRetry: attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS });
594
+ if (isTimeout && attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS) {
595
+ getContext().logger.info('[BaseSyncedStore] Resetting state before bootstrap retry');
596
+ this.resetBootstrapState();
597
+ await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_CONFIG.RETRY_DELAY_MS));
598
+ }
599
+ else if (!isTimeout && attempt < BOOTSTRAP_CONFIG.MAX_RETRY_ATTEMPTS) {
600
+ await new Promise((resolve) => setTimeout(resolve, 1000));
601
+ }
602
+ }
603
+ finally {
604
+ // Disarm this attempt's deadline the moment it settles — a live timer
605
+ // would abort whatever the next attempt puts in flight.
606
+ this.clearBootstrapDeadline();
537
607
  }
538
608
  }
609
+ throw lastError
610
+ ? toAbloError(lastError)
611
+ : new AbloConnectionError('Bootstrap failed after all retry attempts', {
612
+ code: 'bootstrap_fetch_timeout',
613
+ });
614
+ }
615
+ finally {
616
+ signal?.removeEventListener('abort', onCallerAbort);
617
+ this.clearBootstrapDeadline();
539
618
  }
540
- throw lastError
541
- ? toAbloError(lastError)
542
- : new AbloConnectionError('Bootstrap failed after all retry attempts', {
543
- code: 'bootstrap_fetch_timeout',
544
- });
545
619
  }
546
- /** Create a timeout promise for bootstrap attempts */
620
+ /**
621
+ * The outer deadline for one bootstrap attempt.
622
+ *
623
+ * The length is DERIVED from the fetcher's own watchdog budget, not chosen. A
624
+ * chosen number is what broke this: the previous fixed 15s was shorter than a
625
+ * single model chunk's allowance — 20s waiting for response headers plus 15s
626
+ * of stall grace — so on any workspace with one slow model the deadline fired
627
+ * before the watchdogs it was meant to backstop, and every attempt timed out
628
+ * by construction. The watchdogs below are progress-based and already
629
+ * guarantee termination; this deadline exists only for a hang somewhere other
630
+ * than the network, so it must sit above them, and it can only do that
631
+ * reliably by asking them how long they take.
632
+ *
633
+ * Reaching it aborts the work in flight. `Promise.race` merely stops waiting:
634
+ * without the abort the losing bootstrap keeps running, keeps its sockets, and
635
+ * races the retry that replaced it — which is how one page load turned into
636
+ * dozens of overlapping requests.
637
+ */
547
638
  createBootstrapTimeout(attempt) {
548
- const timeoutMs = BOOTSTRAP_CONFIG.OVERALL_TIMEOUT_MS + (attempt - 1) * 3_000;
639
+ const timeoutMs = this.database.helper.budgetMs;
549
640
  return new Promise((_, reject) => {
550
- setTimeout(() => {
641
+ this.clearBootstrapDeadline();
642
+ this.bootstrapDeadlineTimer = setTimeout(() => {
643
+ this.database.helper.abort();
551
644
  reject(new AbloConnectionError(`Bootstrap timed out after ${timeoutMs}ms (attempt ${attempt})`, { code: 'bootstrap_fetch_timeout' }));
552
645
  }, timeoutMs);
553
646
  });
554
647
  }
648
+ /** Disarm the deadline once its attempt has settled. Load-bearing now that
649
+ * firing it aborts real work: a leftover timer would cancel a later,
650
+ * unrelated bootstrap. */
651
+ clearBootstrapDeadline() {
652
+ if (this.bootstrapDeadlineTimer !== null) {
653
+ clearTimeout(this.bootstrapDeadlineTimer);
654
+ this.bootstrapDeadlineTimer = null;
655
+ }
656
+ }
555
657
  /** Reset bootstrap-related state for a clean retry */
556
658
  resetBootstrapState() {
557
659
  try {
@@ -588,7 +690,7 @@ export class BaseSyncedStore {
588
690
  await this.syncClient.hydrateFromDatabase();
589
691
  this.dataReady = true;
590
692
  }
591
- if (this.syncWebSocket && !this.syncWebSocket.isConnected()) {
693
+ if (!this.syncWebSocket.isConnected()) {
592
694
  this.syncWebSocket.resetReconnectAttempts();
593
695
  this.syncWebSocket.connect();
594
696
  }
@@ -597,9 +699,9 @@ export class BaseSyncedStore {
597
699
  }
598
700
  catch (error) {
599
701
  getContext().observability.captureBootstrapFailure(error, { type: 'connection-store-reconnect' });
600
- if (SyncSessionError.isSessionError(error)) {
601
- this.syncWebSocket?.setSessionErrorDetected();
602
- this.syncWebSocket?.disconnect();
702
+ if (AbloSessionError.isSessionError(error)) {
703
+ this.syncWebSocket.setSessionErrorDetected();
704
+ this.syncWebSocket.disconnect();
603
705
  this.updateSyncStatus({ state: 'error', error: error });
604
706
  // SECURITY: Clear locally cached data when session is invalid
605
707
  this.database.clear({ includeWriteJournal: true }).catch(() => { });
@@ -609,10 +711,14 @@ export class BaseSyncedStore {
609
711
  if (!this.dataReady && this.objectPool.size === 0) {
610
712
  try {
611
713
  await this.syncClient.hydrateFromDatabase();
612
- if (this.objectPool.size > 0) {
714
+ // Re-read through a local: the guard above narrowed `size` to 0, and
715
+ // the compiler carries that narrowing across the await even though
716
+ // hydrating is precisely what fills the pool.
717
+ const hydratedSize = this.objectPool.size;
718
+ if (hydratedSize > 0) {
613
719
  this.dataReady = true;
614
720
  getContext().logger.info('[BaseSyncedStore] Hydrated from local fallback', {
615
- objectPoolSize: this.objectPool.size,
721
+ objectPoolSize: hydratedSize,
616
722
  });
617
723
  }
618
724
  }
@@ -688,10 +794,10 @@ export class BaseSyncedStore {
688
794
  return {
689
795
  database: this.database,
690
796
  objectPool: this.objectPool,
691
- getSubscribedSyncGroups: () => this.syncWebSocket?.getSyncGroups() ?? [],
797
+ getSubscribedSyncGroups: () => this.syncWebSocket.getSyncGroups(),
692
798
  getCurrentSyncGroups: () => this.userContext ? this.resolveSyncGroups(this.userContext) : null,
693
799
  getBootstrapMode: () => this.userContext?.bootstrapMode,
694
- disconnectWebSocket: () => { this.syncWebSocket?.disconnect(); },
800
+ disconnectWebSocket: () => { this.syncWebSocket.disconnect(); },
695
801
  emitConnectionEvent: (event) => { this.onConnectionEvent?.(event); },
696
802
  handleGroupAdded: (payload, syncId) => this.handleGroupAdded(payload, syncId),
697
803
  computeUpdatedSyncGroups: (payload) => this.computeUpdatedSyncGroups(payload),
@@ -877,11 +983,11 @@ export class BaseSyncedStore {
877
983
  this.updateSyncStatus({ state: 'idle', progress: 0 });
878
984
  return { success: false, error: error };
879
985
  }
880
- const isSession = SyncSessionError.isSessionError(error);
986
+ const isSession = AbloSessionError.isSessionError(error);
881
987
  getContext().observability.captureBootstrapFailure(error, { type: 'initialize' });
882
988
  if (isSession) {
883
- this.syncWebSocket?.setSessionErrorDetected();
884
- this.syncWebSocket?.disconnect();
989
+ this.syncWebSocket.setSessionErrorDetected();
990
+ this.syncWebSocket.disconnect();
885
991
  this.updateSyncStatus({ state: 'error', error: error });
886
992
  return { success: false, error: error };
887
993
  }
@@ -895,7 +1001,7 @@ export class BaseSyncedStore {
895
1001
  if (this.objectPool.size > 0) {
896
1002
  this.dataReady = true;
897
1003
  this.initialized = true;
898
- this.updateSyncStatus(this.syncWebSocket?.isConnected()
1004
+ this.updateSyncStatus(this.syncWebSocket.isConnected()
899
1005
  ? { state: 'idle', progress: 100 }
900
1006
  : { state: 'offline', offlineSince: new Date() });
901
1007
  return { success: true };
@@ -920,12 +1026,12 @@ export class BaseSyncedStore {
920
1026
  cause: error,
921
1027
  });
922
1028
  getContext().observability.captureBootstrapFailure(error, { type: 'background' });
923
- if (SyncSessionError.isSessionError(error)) {
924
- this.syncWebSocket?.setSessionErrorDetected();
925
- this.syncWebSocket?.disconnect();
1029
+ if (AbloSessionError.isSessionError(error)) {
1030
+ this.syncWebSocket.setSessionErrorDetected();
1031
+ this.syncWebSocket.disconnect();
926
1032
  this.updateSyncStatus({ state: 'error', error: error });
927
1033
  }
928
- else if (!this.syncWebSocket?.isConnected()) {
1034
+ else if (!this.syncWebSocket.isConnected()) {
929
1035
  this.updateSyncStatus({ state: 'offline', offlineSince: new Date() });
930
1036
  }
931
1037
  }
@@ -984,10 +1090,19 @@ export class BaseSyncedStore {
984
1090
  return null;
985
1091
  return new ConnectionManager({
986
1092
  baseUrl: this._syncServerUrl,
987
- getAuthToken: () => this.auth?.getAuthToken() ?? this.syncWebSocket?.getAuthToken() ?? null,
1093
+ getAuthToken: () => this.auth?.getAuthToken() ?? this.syncWebSocket.getAuthToken() ?? null,
1094
+ logger: contextLogger,
1095
+ observability: contextSocketObservability,
988
1096
  });
989
1097
  }
990
- /** Disconnect and clean up all resources */
1098
+ /**
1099
+ * Disconnect and clean up all resources. Terminal: this means "the client
1100
+ * is finished", not "close and reopen later" — the connection object stays
1101
+ * assigned but closed, the event wiring is torn down, and nothing
1102
+ * re-initializes a disconnected store. (Mid-session closes during recovery
1103
+ * go through the connection FSM's `onDisconnectWebSocket`, which closes
1104
+ * the transport without touching the store.)
1105
+ */
991
1106
  async disconnect() {
992
1107
  this.stopCredentialLifecycle();
993
1108
  if (this.batchTimer) {
@@ -1003,15 +1118,12 @@ export class BaseSyncedStore {
1003
1118
  this.connectionManager = null;
1004
1119
  }
1005
1120
  try {
1006
- const last = this.syncWebSocket?.getLastSyncId?.() || 0;
1121
+ const last = this.syncWebSocket.getLastSyncId();
1007
1122
  if (last > 0)
1008
1123
  await this.database.updateWorkspaceMetadata({ lastSyncId: last });
1009
1124
  }
1010
1125
  catch { }
1011
- if (this.syncWebSocket) {
1012
- this.syncWebSocket.disconnect();
1013
- this.syncWebSocket = null;
1014
- }
1126
+ this.syncWebSocket.disconnect();
1015
1127
  this.syncClient.disconnect();
1016
1128
  this.queryProcessor.clearCache();
1017
1129
  // Stop the pool's GC interval — the one timer the pool arms itself.
@@ -1081,8 +1193,6 @@ export class BaseSyncedStore {
1081
1193
  */
1082
1194
  async waitForWebSocketConnected(timeoutMs) {
1083
1195
  const ws = this.syncWebSocket;
1084
- if (!ws)
1085
- return false;
1086
1196
  if (ws.isConnected())
1087
1197
  return true;
1088
1198
  return new Promise((resolve) => {
@@ -1105,35 +1215,48 @@ export class BaseSyncedStore {
1105
1215
  }, timeoutMs);
1106
1216
  });
1107
1217
  }
1218
+ /**
1219
+ * Seed the connection's late values and open it. The socket itself exists
1220
+ * from construction; what identity resolution supplies — the participant
1221
+ * kind, the credential, the read scope, and the resume cursor — is seeded
1222
+ * here, and only then is the held first connect released. A retried
1223
+ * `initialize()` after a failed `ready()` re-runs this against the same
1224
+ * connection object: the reconnect counter is reset for a clean slate,
1225
+ * while the session-error latch deliberately survives (only the
1226
+ * credential-expiry recovery clears it).
1227
+ */
1108
1228
  setupWebSocketSync(context, lastSyncId) {
1109
1229
  if (!context.userId || !context.organizationId) {
1110
1230
  getContext().observability.breadcrumb('Cannot setup WebSocket sync without user context', 'sync.websocket', 'warning');
1111
1231
  return;
1112
1232
  }
1113
- this.syncWebSocket = new SyncWebSocket({
1114
- baseUrl: this._syncServerUrl,
1115
- userId: context.userId,
1116
- organizationId: context.organizationId,
1117
- syncGroups: [...this.resolveSyncGroups(context)],
1118
- lastSyncId,
1119
- kind: context.kind,
1120
- capabilityToken: context.capabilityToken,
1121
- getAuthToken: this.auth?.getAuthToken,
1122
- capabilities: {
1123
- partialBootstrap: true,
1124
- compressedDeltas: true,
1125
- streamingBootstrap: true,
1126
- batchedDeltas: true,
1127
- },
1128
- });
1129
- // Area-of-interest manager — owns dynamic read-subscription over this
1130
- // connection. baseGroups (the org/user scopes) are always subscribed;
1131
- // enterScope/leaveScope move per-entity interest. Recreated with the
1132
- // socket; torn down via the disposer pushed below.
1133
- this.areaOfInterest = new SubscriptionManager({
1134
- transport: this.syncWebSocket,
1135
- baseGroups: this.resolveSyncGroups(context),
1136
- });
1233
+ if (context.kind)
1234
+ this.syncWebSocket.setKind(context.kind);
1235
+ if (context.capabilityToken) {
1236
+ this.syncWebSocket.setCapabilityToken(context.capabilityToken);
1237
+ }
1238
+ const syncGroups = this.resolveSyncGroups(context);
1239
+ this.syncWebSocket.setSyncGroups(syncGroups);
1240
+ this.syncWebSocket.setLastSyncId(lastSyncId || 0);
1241
+ // The permanent base scopes for read interest — same set the connection
1242
+ // subscribes to at upgrade, so the two can never disagree.
1243
+ this.areaOfInterest.setBaseGroups(syncGroups);
1244
+ // ── Connection FSM ────────────────────────────────────────────
1245
+ // Instantiate + start the SDK's ConnectionManager so every consumer
1246
+ // gets correct online/offline recovery. Guarded: a retried
1247
+ // `initialize()` reuses the manager it already started.
1248
+ if (!this.connectionManager)
1249
+ this.startConnectionManager(context.kind);
1250
+ this.syncWebSocket.resetReconnectAttempts();
1251
+ this.syncWebSocket.allowConnect();
1252
+ this.syncWebSocket.connect();
1253
+ }
1254
+ /**
1255
+ * Wire the store's handlers onto the connection. Runs once, at
1256
+ * construction — the connection object is stable for the store's
1257
+ * lifetime, so the wiring is too.
1258
+ */
1259
+ wireSocketEvents() {
1137
1260
  // Connection events → forward to connection lifecycle callback
1138
1261
  const onConnected = this.syncWebSocket.subscribe('connected', () => {
1139
1262
  this.syncClient.markConnected();
@@ -1149,7 +1272,7 @@ export class BaseSyncedStore {
1149
1272
  // changed while offline; after a full reconnect the new socket's URL
1150
1273
  // carries only base groups. `resync` re-pushes the current desired set
1151
1274
  // so the server-side index matches what the user is actually viewing.
1152
- void this.areaOfInterest?.resync();
1275
+ void this.areaOfInterest.resync();
1153
1276
  });
1154
1277
  const onDisconnected = this.syncWebSocket.subscribe('disconnected', () => {
1155
1278
  this.syncClient.disconnect();
@@ -1209,7 +1332,7 @@ export class BaseSyncedStore {
1209
1332
  this.objectPool.clear();
1210
1333
  };
1211
1334
  const onSessionError = this.syncWebSocket.subscribe('session_error', (error) => {
1212
- // WS analog of HTTP's `apikey_expired` (see SyncSessionError.
1335
+ // WS analog of HTTP's `apikey_expired` (see AbloSessionError.
1213
1336
  // isSessionErrorResponse): the hub's keepalive reaper closes sockets
1214
1337
  // whose SHORT-LIVED access credential (`ek_`/`rk_`) passed its expiry
1215
1338
  // with `4001 credential_expired`. That is re-mintable from the
@@ -1218,12 +1341,12 @@ export class BaseSyncedStore {
1218
1341
  // Only a mint that answers `null` (the login itself is gone) falls
1219
1342
  // through to the terminal path. Without this branch, every credential
1220
1343
  // TTL elapse wedged the socket behind the write-once session latch.
1221
- if (SyncSessionError.isSessionError(error) && isAccessCredentialExpiryCloseReason(error.message)) {
1344
+ if (AbloSessionError.isSessionError(error) && isAccessCredentialExpiryCloseReason(error.message)) {
1222
1345
  getContext().observability.breadcrumb('WebSocket closed for expired access credential — re-minting', 'sync.websocket', 'warning');
1223
1346
  // Un-latch BEFORE the async mint so the FSM's own recovery
1224
1347
  // (probe → refreshing_credential → reconnect) is never blocked on
1225
1348
  // our .then() ordering.
1226
- this.syncWebSocket?.clearSessionError();
1349
+ this.syncWebSocket.clearSessionError();
1227
1350
  void this.performCredentialRefresh().then((outcome) => {
1228
1351
  if (outcome === 'refreshed') {
1229
1352
  if (this.connectionManager) {
@@ -1236,8 +1359,8 @@ export class BaseSyncedStore {
1236
1359
  // (createConnectionManager returns null for kind 'agent') —
1237
1360
  // reconnect the socket directly; connect() reads the
1238
1361
  // freshly-minted credential from the credential source.
1239
- this.syncWebSocket?.resetReconnectAttempts();
1240
- this.syncWebSocket?.connect();
1362
+ this.syncWebSocket.resetReconnectAttempts();
1363
+ this.syncWebSocket.connect();
1241
1364
  }
1242
1365
  return;
1243
1366
  }
@@ -1245,7 +1368,7 @@ export class BaseSyncedStore {
1245
1368
  // The mint endpoint rejected: the long-lived login is gone.
1246
1369
  // Re-latch so writes reject with the permanent session type
1247
1370
  // (see SyncWebSocket.notConnectedError) instead of parking.
1248
- this.syncWebSocket?.setSessionErrorDetected();
1371
+ this.syncWebSocket.setSessionErrorDetected();
1249
1372
  handleTerminalSessionError(error);
1250
1373
  }
1251
1374
  // 'network_error' → transient mint failure. The WS_DISCONNECTED
@@ -1271,15 +1394,16 @@ export class BaseSyncedStore {
1271
1394
  getContext().logger.debug('[BaseSyncedStore] WebSocket reconnection gave up', { attempts });
1272
1395
  this.updateSyncStatus({ state: 'reconnecting' });
1273
1396
  });
1274
- this.disposers.push(onConnected, onDisconnected, onReconnecting, onDelta, onDeltaBatch, onBootstrapRequired, onBootstrapData, onPresenceUpdate, onError, onSessionError, onHandshakeFailed, onReconnectFailed, () => { this.areaOfInterest?.dispose(); this.areaOfInterest = null; });
1275
- // ── Connection FSM ────────────────────────────────────────────
1276
- // Instantiate + start the SDK's ConnectionManager so every
1277
- // consumer gets correct online/offline recovery. Previously this
1278
- // was an external concern (each app rebuilt its own FSM); now
1279
- // it's default behavior. The `onConnectionEvent` hook stays as
1280
- // the bridge WS events fire the hook, the hook forwards into
1281
- // the FSM.
1282
- this.connectionManager = this.createConnectionManager(context.kind);
1397
+ this.disposers.push(onConnected, onDisconnected, onReconnecting, onDelta, onDeltaBatch, onBootstrapRequired, onBootstrapData, onPresenceUpdate, onError, onSessionError, onHandshakeFailed, onReconnectFailed, () => { this.areaOfInterest.dispose(); });
1398
+ }
1399
+ /**
1400
+ * Build and start the connection FSM. The `onConnectionEvent` hook is the
1401
+ * bridge WS events fire the hook, the hook forwards into the FSM. Called
1402
+ * from `setupWebSocketSync` because the FSM's shape depends on the resolved
1403
+ * participant kind (agents get none see {@link createConnectionManager}).
1404
+ */
1405
+ startConnectionManager(kind) {
1406
+ this.connectionManager = this.createConnectionManager(kind);
1283
1407
  if (this.connectionManager) {
1284
1408
  const manager = this.connectionManager;
1285
1409
  // Preserve any externally-set onConnectionEvent — chain rather
@@ -1310,7 +1434,7 @@ export class BaseSyncedStore {
1310
1434
  onReconnect: () => this.performReconnect(),
1311
1435
  onRefreshCredential: () => this.performCredentialRefresh(),
1312
1436
  onSessionExpired: () => {
1313
- const err = new SyncSessionError('Session expired');
1437
+ const err = new AbloSessionError('Session expired');
1314
1438
  for (const listener of this.sessionErrorListeners) {
1315
1439
  try {
1316
1440
  listener(err);
@@ -1319,7 +1443,7 @@ export class BaseSyncedStore {
1319
1443
  }
1320
1444
  },
1321
1445
  onDisconnectWebSocket: () => {
1322
- this.syncWebSocket?.disconnect();
1446
+ this.syncWebSocket.disconnect();
1323
1447
  },
1324
1448
  // Mirror FSM transitions into the visible `syncStatus.state` so
1325
1449
  // the UI can show "Reconnecting…" while the FSM cycles through
@@ -1371,12 +1495,6 @@ export class BaseSyncedStore {
1371
1495
  },
1372
1496
  });
1373
1497
  }
1374
- // Transaction events for pendingChanges tracking
1375
- const unsubCreated = this.syncClient.onTransactionEvent('created', () => { this.incrementPendingChanges(); });
1376
- const unsubCompleted = this.syncClient.onTransactionEvent('completed', () => { this.decrementPendingChanges(); });
1377
- const unsubFailed = this.syncClient.onTransactionEvent('failed', () => { this.decrementPendingChanges(); });
1378
- this.disposers.push(unsubCreated, unsubCompleted, unsubFailed);
1379
- this.syncWebSocket.connect();
1380
1498
  }
1381
1499
  // ── Delta processing pipeline ─────────────────────────────────────────────
1382
1500
  //
@@ -1415,7 +1533,7 @@ export class BaseSyncedStore {
1415
1533
  applyDeltaBatchToPool: (results) => {
1416
1534
  this.syncClient.applyDeltaBatchToPool(results, (name, data) => this.enrichRelations(name, data));
1417
1535
  },
1418
- acknowledge: (syncId) => { this.syncWebSocket?.acknowledge?.(syncId); },
1536
+ acknowledge: (syncId) => { this.syncWebSocket.acknowledge(syncId); },
1419
1537
  get objectPool() { return store.objectPool; },
1420
1538
  // Dynamic-dispatch hooks — protected override points on this class.
1421
1539
  getStateFields: (modelName) => this.getStateFields(modelName),
@@ -1525,7 +1643,7 @@ export class BaseSyncedStore {
1525
1643
  * schema build time) to find children. The previous implementation did
1526
1644
  * `getByType(ctor).filter(e => e.toJSON()[foreignKey] === parentId)` —
1527
1645
  * a full pool scan per child model + a `toJSON()` allocation per
1528
- * candidate. For a deck delete with 10K layers in the pool, that was
1646
+ * candidate. For a report delete with 10K blocks in the pool, that was
1529
1647
  * 10K toJSON allocations per cascade level. The FK-indexed lookup
1530
1648
  * skips both the scan AND the allocation.
1531
1649
  */
@@ -1584,7 +1702,7 @@ export class BaseSyncedStore {
1584
1702
  * Save a model (create or update).
1585
1703
  *
1586
1704
  * Accepts any entity shape with `{ id: string }` so consumers can pass the
1587
- * Zod-inferred model types from `InferModel<Schema, K>` without knowing
1705
+ * Zod-inferred model types from `Model<Schema, K>` without knowing
1588
1706
  * about the internal `Model` base class. At runtime, every entity reaching
1589
1707
  * this method came through the object pool (via `store.create`, a query
1590
1708
  * accessor, or an optimistic insert) and IS a `Model` instance — the one
@@ -1607,7 +1725,7 @@ export class BaseSyncedStore {
1607
1725
  this.syncClient.update(model);
1608
1726
  }
1609
1727
  }
1610
- /** Save with an atomic server mutation (e.g., createSlideWithLayers) */
1728
+ /** Save with an atomic server mutation (e.g., createSectionWithBlocks) */
1611
1729
  async saveWithAtomicMutation(model, mutation) {
1612
1730
  this.objectPool.add(model, ModelScope.live);
1613
1731
  await mutation(this.syncClient.gql);
@@ -1632,7 +1750,7 @@ export class BaseSyncedStore {
1632
1750
  this.syncClient.update(model);
1633
1751
  }
1634
1752
  // ── Query API ────────────────────────────────────────────────────────────
1635
- // `ablo.<model>.get` / `ablo.<model>.getAll` is the read surface for
1753
+ // `ablo.<model>.local.retrieve` / `.local.list` is the read surface for
1636
1754
  // application code. Custom mutators read transactionally through
1637
1755
  // `tx.<model>`, backed by `createReaderActions`.
1638
1756
  /** Retrieve a single entity by id. Synchronous pool read. */
@@ -1655,13 +1773,13 @@ export class BaseSyncedStore {
1655
1773
  * Create a model instance locally, typed via the schema.
1656
1774
  *
1657
1775
  * ```ts
1658
- * const sheet = store.create('spreadsheetSheets', { name, spreadsheetId });
1659
- * // sheet: SpreadsheetSheet | null — no cast needed
1776
+ * const ledger = store.create('ledgers', { name, reportId });
1777
+ * // ledger: Ledger | null — no cast needed
1660
1778
  * ```
1661
1779
  *
1662
1780
  * The `typename` arg is the schema key (camelCase plural, e.g.
1663
- * `'spreadsheetSheets'`); the returned instance has the
1664
- * `InferModel<Schema, K>` shape including computeds + relation accessors.
1781
+ * `'ledgers'`); the returned instance has the
1782
+ * `Model<Schema, K>` shape including computeds + relation accessors.
1665
1783
  * Wraps `pool.create(...)` — the underlying runtime is unchanged, just
1666
1784
  * type-narrowed.
1667
1785
  */
@@ -1718,14 +1836,14 @@ export class BaseSyncedStore {
1718
1836
  /**
1719
1837
  * Get all models of a type. Returns Model[] honestly — callers that need
1720
1838
  * narrow types should use `useAblo((ablo) => ablo.<model>.list(...))`
1721
- * which does proper inference via `InferModel<S, K>`.
1839
+ * which does proper inference via `Model<S, K>`.
1722
1840
  */
1723
1841
  allModelsOfType(modelClass, scope) {
1724
1842
  return this.objectPool.getByType(modelClass, scope ?? ModelScope.live);
1725
1843
  }
1726
1844
  /** Error handler for fire-and-forget flushPendingDeltas calls */
1727
1845
  handleFlushError = (error) => {
1728
- getContext().observability.captureTransactionFailure({
1846
+ getContext().observability.captureMutationFailure({
1729
1847
  context: 'flush-pending-deltas',
1730
1848
  modelName: 'batch',
1731
1849
  modelId: 'batch',
@@ -1744,8 +1862,6 @@ export class BaseSyncedStore {
1744
1862
  modelId: delta.modelId,
1745
1863
  data: typeof delta.data === 'string' ? JSON.parse(delta.data) : delta.data,
1746
1864
  });
1747
- if (!dbResult)
1748
- return;
1749
1865
  // Track pending deletes for query filtering
1750
1866
  if (dbResult.action === 'remove') {
1751
1867
  this.pendingDeletes.add(dbResult.modelId);