@abloatai/ablo 0.26.0 → 0.28.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 (418) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +102 -86
  3. package/dist/BaseSyncedStore.d.ts +85 -88
  4. package/dist/BaseSyncedStore.js +134 -151
  5. package/dist/Database.d.ts +68 -69
  6. package/dist/Database.js +316 -135
  7. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  8. package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
  9. package/dist/LazyReferenceCollection.d.ts +11 -15
  10. package/dist/LazyReferenceCollection.js +12 -16
  11. package/dist/Model.d.ts +54 -52
  12. package/dist/Model.js +78 -62
  13. package/dist/ModelRegistry.d.ts +21 -19
  14. package/dist/ModelRegistry.js +23 -27
  15. package/dist/NetworkMonitor.d.ts +5 -6
  16. package/dist/NetworkMonitor.js +5 -6
  17. package/dist/SyncClient.d.ts +122 -118
  18. package/dist/SyncClient.js +541 -245
  19. package/dist/adapters/alwaysOnline.d.ts +6 -8
  20. package/dist/adapters/alwaysOnline.js +6 -8
  21. package/dist/adapters/inMemoryStorage.d.ts +10 -9
  22. package/dist/adapters/inMemoryStorage.js +21 -9
  23. package/dist/agent/Agent.d.ts +27 -32
  24. package/dist/agent/Agent.js +18 -19
  25. package/dist/agent/index.d.ts +4 -4
  26. package/dist/agent/index.js +5 -5
  27. package/dist/agent/session.d.ts +47 -44
  28. package/dist/agent/session.js +37 -48
  29. package/dist/agent/types.d.ts +26 -31
  30. package/dist/agent/types.js +6 -7
  31. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  32. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  33. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  34. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
  35. package/dist/ai-sdk/index.d.ts +25 -22
  36. package/dist/ai-sdk/index.js +25 -22
  37. package/dist/ai-sdk/wrap.d.ts +6 -7
  38. package/dist/ai-sdk/wrap.js +1 -1
  39. package/dist/auth/credentialPolicy.d.ts +69 -74
  40. package/dist/auth/credentialPolicy.js +51 -56
  41. package/dist/auth/credentialSource.d.ts +6 -5
  42. package/dist/auth/credentialSource.js +9 -10
  43. package/dist/auth/index.d.ts +59 -58
  44. package/dist/auth/index.js +31 -37
  45. package/dist/auth/schemas.d.ts +5 -4
  46. package/dist/auth/schemas.js +5 -4
  47. package/dist/batching/index.d.ts +19 -21
  48. package/dist/batching/index.js +14 -17
  49. package/dist/cli.cjs +173 -121
  50. package/dist/client/Ablo.d.ts +97 -74
  51. package/dist/client/Ablo.js +129 -163
  52. package/dist/client/ApiClient.d.ts +30 -19
  53. package/dist/client/ApiClient.js +442 -81
  54. package/dist/client/auth.d.ts +47 -47
  55. package/dist/client/auth.js +108 -117
  56. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  57. package/dist/client/claimHeartbeatLoop.js +88 -0
  58. package/dist/client/consoleLogger.d.ts +5 -6
  59. package/dist/client/consoleLogger.js +5 -6
  60. package/dist/client/createInternalComponents.d.ts +16 -17
  61. package/dist/client/createInternalComponents.js +26 -31
  62. package/dist/client/createModelProxy.d.ts +130 -120
  63. package/dist/client/createModelProxy.js +152 -122
  64. package/dist/client/credentialEndpoint.d.ts +40 -42
  65. package/dist/client/credentialEndpoint.js +35 -36
  66. package/dist/client/functionalUpdate.d.ts +29 -27
  67. package/dist/client/functionalUpdate.js +21 -21
  68. package/dist/client/hostedEndpoints.d.ts +9 -12
  69. package/dist/client/hostedEndpoints.js +9 -12
  70. package/dist/client/httpClient.d.ts +59 -53
  71. package/dist/client/httpClient.js +29 -31
  72. package/dist/client/identity.d.ts +15 -20
  73. package/dist/client/identity.js +47 -58
  74. package/dist/client/modelRegistration.d.ts +5 -9
  75. package/dist/client/modelRegistration.js +78 -87
  76. package/dist/client/options.d.ts +157 -157
  77. package/dist/client/options.js +3 -7
  78. package/dist/client/registerDataSource.d.ts +9 -9
  79. package/dist/client/registerDataSource.js +15 -16
  80. package/dist/client/resourceTypes.d.ts +64 -75
  81. package/dist/client/resourceTypes.js +4 -10
  82. package/dist/client/schemaConfig.d.ts +31 -43
  83. package/dist/client/schemaConfig.js +38 -50
  84. package/dist/client/sessionMint.d.ts +16 -12
  85. package/dist/client/sessionMint.js +26 -31
  86. package/dist/client/validateAbloOptions.d.ts +12 -14
  87. package/dist/client/validateAbloOptions.js +8 -9
  88. package/dist/client/writeOptionsSchema.d.ts +18 -16
  89. package/dist/client/writeOptionsSchema.js +23 -20
  90. package/dist/client/wsMutationExecutor.d.ts +16 -20
  91. package/dist/client/wsMutationExecutor.js +18 -23
  92. package/dist/commit/contract.d.ts +493 -0
  93. package/dist/commit/contract.js +187 -0
  94. package/dist/commit/index.d.ts +6 -0
  95. package/dist/commit/index.js +5 -0
  96. package/dist/context.d.ts +6 -4
  97. package/dist/context.js +6 -4
  98. package/dist/coordination/index.d.ts +10 -8
  99. package/dist/coordination/index.js +14 -12
  100. package/dist/coordination/schema.d.ts +176 -128
  101. package/dist/coordination/schema.js +197 -133
  102. package/dist/coordination/trace.d.ts +9 -10
  103. package/dist/coordination/trace.js +13 -14
  104. package/dist/core/DatabaseManager.d.ts +5 -7
  105. package/dist/core/DatabaseManager.js +15 -19
  106. package/dist/core/QueryProcessor.d.ts +7 -9
  107. package/dist/core/QueryProcessor.js +22 -28
  108. package/dist/core/QueryView.d.ts +8 -8
  109. package/dist/core/QueryView.js +2 -2
  110. package/dist/core/StoreManager.d.ts +14 -14
  111. package/dist/core/StoreManager.js +33 -24
  112. package/dist/core/ViewRegistry.d.ts +5 -5
  113. package/dist/core/ViewRegistry.js +4 -4
  114. package/dist/core/index.d.ts +17 -12
  115. package/dist/core/index.js +32 -26
  116. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  117. package/dist/core/openIDBWithTimeout.js +42 -43
  118. package/dist/core/queryUtils.d.ts +45 -0
  119. package/dist/core/queryUtils.js +69 -0
  120. package/dist/core/storeContract.d.ts +63 -61
  121. package/dist/core/storeContract.js +8 -12
  122. package/dist/environment.d.ts +28 -0
  123. package/dist/environment.js +21 -0
  124. package/dist/errorCodes.d.ts +107 -99
  125. package/dist/errorCodes.js +137 -134
  126. package/dist/errors.d.ts +160 -166
  127. package/dist/errors.js +155 -158
  128. package/dist/index.d.ts +36 -27
  129. package/dist/index.js +91 -86
  130. package/dist/interfaces/index.d.ts +102 -113
  131. package/dist/interfaces/index.js +5 -4
  132. package/dist/keys/index.d.ts +27 -29
  133. package/dist/keys/index.js +41 -40
  134. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  135. package/dist/mutators/RecordingTransaction.js +31 -37
  136. package/dist/mutators/Transaction.d.ts +18 -26
  137. package/dist/mutators/Transaction.js +14 -20
  138. package/dist/mutators/UndoManager.d.ts +124 -131
  139. package/dist/mutators/UndoManager.js +177 -156
  140. package/dist/mutators/defineMutators.d.ts +23 -34
  141. package/dist/mutators/defineMutators.js +14 -20
  142. package/dist/mutators/inverseOp.d.ts +12 -15
  143. package/dist/mutators/inverseOp.js +12 -15
  144. package/dist/mutators/mutateActions.d.ts +10 -9
  145. package/dist/mutators/mutateActions.js +1 -1
  146. package/dist/mutators/readerActions.d.ts +9 -8
  147. package/dist/mutators/readerActions.js +2 -2
  148. package/dist/mutators/undoApply.d.ts +31 -27
  149. package/dist/mutators/undoApply.js +26 -24
  150. package/dist/policy/index.d.ts +5 -3
  151. package/dist/policy/index.js +5 -3
  152. package/dist/policy/types.d.ts +104 -100
  153. package/dist/policy/types.js +67 -66
  154. package/dist/query/client.d.ts +28 -23
  155. package/dist/query/client.js +45 -43
  156. package/dist/query/types.d.ts +37 -60
  157. package/dist/query/types.js +13 -33
  158. package/dist/react/AbloProvider.d.ts +1 -1
  159. package/dist/react/AbloProvider.js +2 -2
  160. package/dist/react/context.d.ts +25 -28
  161. package/dist/react/context.js +9 -10
  162. package/dist/react/index.d.ts +41 -42
  163. package/dist/react/index.js +37 -38
  164. package/dist/react/internalContext.d.ts +17 -19
  165. package/dist/react/useAblo.d.ts +28 -25
  166. package/dist/react/useAblo.js +41 -17
  167. package/dist/react/useCurrentUserId.d.ts +8 -7
  168. package/dist/react/useCurrentUserId.js +8 -7
  169. package/dist/react/useErrorListener.d.ts +7 -7
  170. package/dist/react/useErrorListener.js +10 -11
  171. package/dist/react/useMutationFailureListener.d.ts +8 -8
  172. package/dist/react/useMutationFailureListener.js +8 -8
  173. package/dist/react/useMutators.d.ts +11 -11
  174. package/dist/react/useMutators.js +3 -3
  175. package/dist/react/useReactive.js +2 -2
  176. package/dist/react/useSyncStatus.d.ts +4 -6
  177. package/dist/react/useUndoScope.d.ts +7 -9
  178. package/dist/react/useUndoScope.js +1 -1
  179. package/dist/schema/coordination.d.ts +21 -25
  180. package/dist/schema/coordination.js +21 -25
  181. package/dist/schema/ddl.d.ts +43 -39
  182. package/dist/schema/ddl.js +75 -68
  183. package/dist/schema/ddlLock.d.ts +20 -24
  184. package/dist/schema/ddlLock.js +18 -23
  185. package/dist/schema/diff.d.ts +99 -61
  186. package/dist/schema/diff.js +43 -34
  187. package/dist/schema/field.d.ts +37 -42
  188. package/dist/schema/field.js +35 -48
  189. package/dist/schema/generate.d.ts +12 -12
  190. package/dist/schema/generate.js +12 -12
  191. package/dist/schema/index.d.ts +3 -3
  192. package/dist/schema/index.js +21 -23
  193. package/dist/schema/model.d.ts +118 -143
  194. package/dist/schema/model.js +22 -33
  195. package/dist/schema/openapi.d.ts +10 -9
  196. package/dist/schema/openapi.js +5 -3
  197. package/dist/schema/queries.d.ts +29 -31
  198. package/dist/schema/queries.js +23 -25
  199. package/dist/schema/relation.d.ts +89 -99
  200. package/dist/schema/relation.js +13 -13
  201. package/dist/schema/residency.d.ts +16 -13
  202. package/dist/schema/residency.js +16 -13
  203. package/dist/schema/roles.d.ts +36 -43
  204. package/dist/schema/roles.js +31 -37
  205. package/dist/schema/schema.d.ts +64 -43
  206. package/dist/schema/schema.js +31 -32
  207. package/dist/schema/select.d.ts +13 -13
  208. package/dist/schema/select.js +13 -13
  209. package/dist/schema/serialize.d.ts +28 -31
  210. package/dist/schema/serialize.js +27 -31
  211. package/dist/schema/sugar.d.ts +17 -32
  212. package/dist/schema/sugar.js +14 -29
  213. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
  214. package/dist/schema/syncDeltaRow.js +89 -0
  215. package/dist/schema/tenancy.d.ts +44 -46
  216. package/dist/schema/tenancy.js +46 -48
  217. package/dist/server/adapter.d.ts +58 -58
  218. package/dist/server/adapter.js +13 -14
  219. package/dist/server/commit.d.ts +60 -64
  220. package/dist/server/index.d.ts +9 -10
  221. package/dist/server/index.js +1 -1
  222. package/dist/server/readConfig.d.ts +70 -0
  223. package/dist/server/readConfig.js +8 -0
  224. package/dist/server/storageMode.d.ts +23 -0
  225. package/dist/server/storageMode.js +17 -0
  226. package/dist/source/adapter.d.ts +30 -25
  227. package/dist/source/adapter.js +10 -10
  228. package/dist/source/adapters/drizzle.d.ts +28 -23
  229. package/dist/source/adapters/drizzle.js +30 -25
  230. package/dist/source/adapters/kysely.d.ts +27 -25
  231. package/dist/source/adapters/kysely.js +24 -23
  232. package/dist/source/adapters/memory.d.ts +8 -7
  233. package/dist/source/adapters/memory.js +9 -8
  234. package/dist/source/adapters/prisma.d.ts +13 -12
  235. package/dist/source/adapters/prisma.js +22 -25
  236. package/dist/source/conformance.d.ts +18 -11
  237. package/dist/source/conformance.js +17 -11
  238. package/dist/source/connector.d.ts +31 -32
  239. package/dist/source/connector.js +28 -28
  240. package/dist/source/connectorProtocol.d.ts +160 -0
  241. package/dist/source/connectorProtocol.js +162 -0
  242. package/dist/source/contract.d.ts +26 -27
  243. package/dist/source/contract.js +28 -29
  244. package/dist/source/factory.d.ts +46 -58
  245. package/dist/source/factory.js +22 -27
  246. package/dist/source/index.d.ts +7 -9
  247. package/dist/source/index.js +12 -14
  248. package/dist/source/migrations.d.ts +9 -9
  249. package/dist/source/migrations.js +9 -9
  250. package/dist/source/next.d.ts +9 -10
  251. package/dist/source/next.js +6 -7
  252. package/dist/source/pushQueue.d.ts +69 -47
  253. package/dist/source/pushQueue.js +32 -28
  254. package/dist/source/signing.d.ts +46 -17
  255. package/dist/source/signing.js +28 -11
  256. package/dist/source/types.d.ts +121 -104
  257. package/dist/source/types.js +13 -14
  258. package/dist/stores/ObjectStore.d.ts +24 -12
  259. package/dist/stores/ObjectStore.js +38 -16
  260. package/dist/stores/ObjectStoreContract.d.ts +14 -15
  261. package/dist/stores/SyncActionStore.d.ts +7 -11
  262. package/dist/stores/SyncActionStore.js +13 -17
  263. package/dist/surface.d.ts +28 -21
  264. package/dist/surface.js +29 -20
  265. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
  266. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
  267. package/dist/sync/ConnectionManager.d.ts +39 -50
  268. package/dist/sync/ConnectionManager.js +55 -66
  269. package/dist/sync/NetworkProbe.d.ts +24 -29
  270. package/dist/sync/NetworkProbe.js +63 -69
  271. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
  272. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
  273. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
  274. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
  275. package/dist/sync/SyncWebSocket.d.ts +141 -166
  276. package/dist/sync/SyncWebSocket.js +191 -223
  277. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  278. package/dist/sync/awaitClaimGrant.js +11 -11
  279. package/dist/sync/bootstrapApply.d.ts +34 -24
  280. package/dist/sync/bootstrapApply.js +27 -19
  281. package/dist/sync/commitFrames.d.ts +21 -20
  282. package/dist/sync/commitFrames.js +18 -18
  283. package/dist/sync/createClaimStream.d.ts +23 -22
  284. package/dist/sync/createClaimStream.js +105 -23
  285. package/dist/sync/createPresenceStream.d.ts +19 -18
  286. package/dist/sync/createPresenceStream.js +25 -26
  287. package/dist/sync/createSnapshot.d.ts +12 -14
  288. package/dist/sync/createSnapshot.js +20 -26
  289. package/dist/sync/credentialLifecycle.d.ts +104 -104
  290. package/dist/sync/credentialLifecycle.js +140 -147
  291. package/dist/sync/deltaPipeline.d.ts +36 -34
  292. package/dist/sync/deltaPipeline.js +64 -65
  293. package/dist/sync/groupChange.d.ts +63 -61
  294. package/dist/sync/groupChange.js +74 -78
  295. package/dist/sync/heartbeat.d.ts +34 -33
  296. package/dist/sync/heartbeat.js +31 -31
  297. package/dist/sync/participants.d.ts +19 -19
  298. package/dist/sync/persistedPrefix.d.ts +12 -0
  299. package/dist/sync/persistedPrefix.js +22 -0
  300. package/dist/sync/schemas.d.ts +3 -2
  301. package/dist/sync/schemas.js +14 -10
  302. package/dist/sync/syncCursor.d.ts +17 -21
  303. package/dist/sync/syncCursor.js +17 -21
  304. package/dist/sync/syncPlan.d.ts +28 -36
  305. package/dist/sync/syncPlan.js +18 -19
  306. package/dist/sync/syncPosition.d.ts +54 -49
  307. package/dist/sync/syncPosition.js +57 -52
  308. package/dist/sync/wsFrameHandlers.d.ts +35 -36
  309. package/dist/sync/wsFrameHandlers.js +63 -67
  310. package/dist/testing/fixtures/bootstrap.d.ts +12 -6
  311. package/dist/testing/fixtures/bootstrap.js +12 -6
  312. package/dist/testing/fixtures/deltas.d.ts +30 -33
  313. package/dist/testing/fixtures/deltas.js +30 -33
  314. package/dist/testing/fixtures/models.d.ts +11 -10
  315. package/dist/testing/fixtures/models.js +11 -10
  316. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  317. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  318. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
  319. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
  320. package/dist/testing/helpers/wait.d.ts +13 -8
  321. package/dist/testing/helpers/wait.js +13 -8
  322. package/dist/testing/index.d.ts +5 -3
  323. package/dist/testing/index.js +3 -2
  324. package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
  325. package/dist/testing/mocks/FakeDatabase.js +10 -0
  326. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  327. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  328. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  329. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  330. package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
  331. package/dist/testing/mocks/MockSyncContext.js +15 -13
  332. package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
  333. package/dist/testing/mocks/MockSyncStore.js +11 -11
  334. package/dist/testing/mocks/MockWebSocket.d.ts +28 -23
  335. package/dist/testing/mocks/MockWebSocket.js +22 -21
  336. package/dist/transactions/TransactionQueue.d.ts +244 -181
  337. package/dist/transactions/TransactionQueue.js +929 -423
  338. package/dist/transactions/TransactionStore.d.ts +6 -4
  339. package/dist/transactions/TransactionStore.js +6 -4
  340. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  341. package/dist/transactions/UnconfirmedWrites.js +104 -0
  342. package/dist/transactions/coalesceRules.d.ts +41 -17
  343. package/dist/transactions/coalesceRules.js +40 -17
  344. package/dist/transactions/commitEnvelope.d.ts +132 -0
  345. package/dist/transactions/commitEnvelope.js +139 -0
  346. package/dist/transactions/commitOutboxStore.d.ts +32 -0
  347. package/dist/transactions/commitOutboxStore.js +26 -0
  348. package/dist/transactions/commitPayload.d.ts +63 -52
  349. package/dist/transactions/commitPayload.js +54 -57
  350. package/dist/transactions/deltaConfirmation.d.ts +20 -22
  351. package/dist/transactions/deltaConfirmation.js +37 -45
  352. package/dist/transactions/httpCommitEnvelope.d.ts +43 -0
  353. package/dist/transactions/httpCommitEnvelope.js +179 -0
  354. package/dist/transactions/optimisticApply.d.ts +49 -0
  355. package/dist/transactions/optimisticApply.js +65 -0
  356. package/dist/transactions/replayValidation.d.ts +182 -0
  357. package/dist/transactions/replayValidation.js +156 -0
  358. package/dist/types/global.d.ts +46 -41
  359. package/dist/types/global.js +20 -19
  360. package/dist/types/index.d.ts +71 -77
  361. package/dist/types/index.js +22 -22
  362. package/dist/types/modelData.d.ts +6 -8
  363. package/dist/types/modelData.js +5 -7
  364. package/dist/types/participant.d.ts +10 -11
  365. package/dist/types/participant.js +6 -8
  366. package/dist/types/streams.d.ts +208 -195
  367. package/dist/types/streams.js +7 -7
  368. package/dist/utils/asyncIterator.d.ts +25 -32
  369. package/dist/utils/asyncIterator.js +25 -32
  370. package/dist/utils/duration.d.ts +12 -15
  371. package/dist/utils/duration.js +12 -15
  372. package/dist/utils/mobxSetup.d.ts +53 -0
  373. package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
  374. package/dist/webhooks/events.d.ts +21 -16
  375. package/dist/webhooks/events.js +10 -8
  376. package/dist/webhooks/index.d.ts +5 -7
  377. package/dist/webhooks/index.js +5 -7
  378. package/dist/wire/bootstrapReason.d.ts +9 -0
  379. package/dist/wire/bootstrapReason.js +8 -0
  380. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  381. package/dist/wire/delta.js +114 -0
  382. package/dist/wire/errorEnvelope.d.ts +30 -31
  383. package/dist/wire/errorEnvelope.js +34 -40
  384. package/dist/wire/frames.d.ts +315 -86
  385. package/dist/wire/frames.js +47 -33
  386. package/dist/wire/index.d.ts +18 -14
  387. package/dist/wire/index.js +32 -27
  388. package/dist/wire/listEnvelope.d.ts +16 -23
  389. package/dist/wire/listEnvelope.js +7 -6
  390. package/dist/wire/protocol.d.ts +25 -32
  391. package/dist/wire/protocol.js +25 -32
  392. package/dist/wire/protocolVersion.d.ts +44 -40
  393. package/dist/wire/protocolVersion.js +44 -40
  394. package/docs/api.md +10 -10
  395. package/docs/coordination.md +59 -0
  396. package/docs/mcp.md +1 -1
  397. package/package.json +17 -11
  398. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  399. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  400. package/dist/core/query-utils.d.ts +0 -34
  401. package/dist/core/query-utils.js +0 -59
  402. package/dist/schema/sync-delta-row.js +0 -103
  403. package/dist/schema/sync-delta-wire.js +0 -102
  404. package/dist/server/read-config.d.ts +0 -67
  405. package/dist/server/read-config.js +0 -8
  406. package/dist/server/storage-mode.d.ts +0 -8
  407. package/dist/server/storage-mode.js +0 -28
  408. package/dist/source/connector-protocol.d.ts +0 -159
  409. package/dist/source/connector-protocol.js +0 -161
  410. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  411. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  412. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  413. package/dist/transactions/mutation-error-handler.js +0 -39
  414. package/dist/transactions/optimistic.d.ts +0 -24
  415. package/dist/transactions/optimistic.js +0 -45
  416. package/dist/transactions/persistedReplay.d.ts +0 -93
  417. package/dist/transactions/persistedReplay.js +0 -105
  418. package/dist/utils/mobx-setup.d.ts +0 -42
@@ -1,13 +1,17 @@
1
1
  /**
2
- * BootstrapHelper - Fixed to always fetch fresh data
3
- * Removed problematic caching that was serving stale data
2
+ * Fetches the initial snapshot the sync engine needs before it can go live: the
3
+ * current rows for the requested models plus the sync position from which to
4
+ * resume live updates. It calls the sync server's `/sync/bootstrap` HTTP
5
+ * endpoint, retries transient failures with backoff, and can fall back to a
6
+ * cached snapshot when the device is offline. {@link BootstrapData} is the
7
+ * shape it returns; {@link BootstrapOptions} configures it.
4
8
  */
5
9
  import { getContext } from '../context.js';
6
10
  import { SyncSessionError, AbloConnectionError, translateHttpError, toAbloError, isRetryableCode } from '../errors.js';
7
11
  import { withAuthHeaders } from '../auth/credentialSource.js';
8
12
  // SyncObservability replaced by getContext().observability
9
13
  import { parseBootstrapResponse } from './schemas.js';
10
- export class BootstrapHelper {
14
+ export class BootstrapFetcher {
11
15
  options;
12
16
  abortController = null;
13
17
  /** Warn about schema drift at most once per helper. */
@@ -39,23 +43,20 @@ export class BootstrapHelper {
39
43
  `\`ablo push\` to deploy your schema (or update this app to match the deployed one).`, { clientSchemaHash: clientHash, serverSchemaHash: serverHash });
40
44
  }
41
45
  constructor(options) {
42
- // Defaults are spread first; the explicit `baseUrl` then takes precedence
43
- // and is computed from `options.baseUrl` (or the localhost fallback).
44
- //
45
- // Historical note: a previous version of this constructor placed
46
- // `baseUrl: \`${baseUrl}/api\`` BEFORE the `...options` spread, which
47
- // meant the spread silently overwrote it back to the caller's value
48
- // and the `/api` suffix was dead code. Both Ablo and `createSyncEngine`
49
- // already pass `${url}/api` explicitly, so removing the suffix here
50
- // preserves the actual on-the-wire behavior while making the contract
51
- // explicit: callers pass the full base URL including `/api`.
46
+ // Defaults are spread first; the explicit `baseUrl` then takes precedence,
47
+ // resolved from `options.baseUrl` or the localhost fallback. Callers pass
48
+ // the full base URL, including the `/api` prefix.
52
49
  this.options = {
53
50
  syncGroups: [],
54
51
  maxRetries: 3,
55
52
  retryDelay: 1000,
56
53
  fetchTimeout: 10_000, // 10 second timeout per request - fail fast for good UX
57
54
  ...options,
58
- baseUrl: options.baseUrl || 'http://localhost:8080/api',
55
+ baseUrl: options.baseUrl ?? 'http://localhost:8080/api',
56
+ // Reading the deprecated `organizationId` is deliberate: it preserves the
57
+ // cache namespace for callers that still construct BootstrapFetcher
58
+ // directly with the old field instead of `cacheScope`.
59
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
59
60
  cacheScope: options.cacheScope ?? options.organizationId ?? null,
60
61
  };
61
62
  // Do not clear cache here; keep offline fallback available
@@ -73,8 +74,8 @@ export class BootstrapHelper {
73
74
  this.options.syncGroups = [...(syncGroups ?? [])];
74
75
  }
75
76
  /**
76
- * Compatibility setter for direct BootstrapHelper users. The SDK-owned
77
- * `Ablo()` path passes `getAuthToken` and does not mutate this helper.
77
+ * Sets a fixed credential for callers that construct the helper directly.
78
+ * The SDK instead supplies `getAuthToken` and never calls this.
78
79
  */
79
80
  setAuthToken(authToken) {
80
81
  if (!authToken) {
@@ -83,26 +84,6 @@ export class BootstrapHelper {
83
84
  }
84
85
  this.options.authToken = authToken;
85
86
  }
86
- /**
87
- * Create a promise that rejects after a timeout
88
- * Used to race against fetch requests that may hang indefinitely
89
- */
90
- createTimeoutPromise(ms, operation) {
91
- return new Promise((_, reject) => {
92
- setTimeout(() => {
93
- reject(new AbloConnectionError(`Bootstrap ${operation} timed out after ${ms}ms`, {
94
- code: 'bootstrap_fetch_timeout',
95
- }));
96
- }, ms);
97
- });
98
- }
99
- /**
100
- * Wrap a promise with a timeout - if the promise doesn't resolve within
101
- * the timeout period, the AbortController is triggered and an error is thrown
102
- */
103
- async withTimeout(promise, timeoutMs, operation) {
104
- return Promise.race([promise, this.createTimeoutPromise(timeoutMs, operation)]);
105
- }
106
87
  /**
107
88
  * Fetch bootstrap data from sync engine with partial bootstrap support
108
89
  * @param lastSyncId - Optional: client's current lastSyncId for partial bootstrap
@@ -110,11 +91,12 @@ export class BootstrapHelper {
110
91
  */
111
92
  async fetchBootstrap(lastSyncId,
112
93
  /**
113
- * Per-call sync-group override for SCOPED hydrate-on-enter. When provided,
114
- * the request uses THESE groups instead of `this.options.syncGroups`,
115
- * WITHOUT mutating the shared options (so a concurrent full bootstrap is
116
- * unaffected). Also bypasses the offline full-snapshot cache below, which
117
- * holds the connection's full bootstrap and would be wrong for a subset.
94
+ * A per-call set of sync groups for a scoped hydrate-on-enter. When given,
95
+ * the request uses these groups instead of the configured `syncGroups`, and
96
+ * does so without mutating the shared options, so a concurrent full
97
+ * bootstrap is unaffected. It also bypasses the offline snapshot cache,
98
+ * which holds the full bootstrap and would be a wrong answer to a subset
99
+ * request.
118
100
  */
119
101
  syncGroupsOverride) {
120
102
  // organizationId omitted — server reads it from auth identity.
@@ -135,10 +117,19 @@ export class BootstrapHelper {
135
117
  params.append('models', this.options.instantModels.join(','));
136
118
  }
137
119
  const url = `${this.options.baseUrl}/sync/bootstrap?${params.toString()}`;
138
- // If offline, try cached bootstrap. Skipped for a scoped override the
139
- // cache holds the FULL snapshot, which is not a valid answer to a subset
120
+ // If offline, try the cached bootstrap. Skipped for a scoped override: the
121
+ // cache holds the full snapshot, which is not a valid answer to a subset
140
122
  // request; a scoped hydrate just soft-fails offline and retries on re-enter.
141
- if (!syncGroupsOverride && typeof navigator !== 'undefined' && navigator && navigator.onLine === false) {
123
+ //
124
+ // Only an explicit `false` means offline. `navigator.onLine` is *typed*
125
+ // `boolean`, but at runtime it is `boolean | undefined`: Node 21+ exposes a
126
+ // global `navigator` whose `onLine` is `undefined`. Reading `!navigator.onLine`
127
+ // would treat that `undefined` as offline and falsely short-circuit to the
128
+ // (empty, under `persistence: 'memory'`) cache — throwing instead of fetching.
129
+ // Capturing it at its true runtime type keeps the `=== false` honest (and lets
130
+ // the boolean-literal-compare lint rule see the nullable it really is).
131
+ const navigatorOnline = typeof navigator !== 'undefined' ? navigator.onLine : undefined;
132
+ if (!syncGroupsOverride && navigatorOnline === false) {
142
133
  const cached = this.options.cacheScope
143
134
  ? this.loadCachedBootstrap(this.options.cacheScope)
144
135
  : null;
@@ -160,7 +151,7 @@ export class BootstrapHelper {
160
151
  type: data.type,
161
152
  lastSyncId: data.lastSyncId,
162
153
  modelCount: data.models ? Object.keys(data.models).length : 0,
163
- deltaCount: data.deltaCount || 0,
154
+ deltaCount: data.deltaCount ?? 0,
164
155
  totalItems: data.models
165
156
  ? Object.values(data.models).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0)
166
157
  : 0,
@@ -220,11 +211,9 @@ export class BootstrapHelper {
220
211
  * Fetch bootstrap with ETag, returning 304 hints
221
212
  */
222
213
  async fetchBootstrapWithETag() {
223
- // organizationId is intentionally NOT sent. Server resolves it from
224
- // the authenticated identity (`c.var.identity.organizationId`)
225
- // see `apps/sync-server/src/routes/bootstrap.ts`. Sending it
226
- // client-side was historical: it predated the auth-context pipeline
227
- // and forced a cross-org guard to defend against the SDK lying.
214
+ // The organization id is intentionally not sent. The server resolves it
215
+ // from the authenticated identity, so the client cannot select or spoof an
216
+ // organization it is not scoped to.
228
217
  const params = new URLSearchParams();
229
218
  this.options.syncGroups.forEach((g) => { params.append('syncGroups', g); });
230
219
  if (this.options.instantModels && this.options.instantModels.length > 0) {
@@ -252,7 +241,10 @@ export class BootstrapHelper {
252
241
  }
253
242
  if (!res.ok) {
254
243
  const bodyText = await res.text().catch(() => '');
255
- let parsed = bodyText;
244
+ // Map an empty body to undefined so the `??` below falls through to the
245
+ // synthetic message — translateHttpError renders an empty string body as
246
+ // an empty error message, which is useless to the caller.
247
+ let parsed = bodyText || undefined;
256
248
  if (bodyText) {
257
249
  try {
258
250
  parsed = JSON.parse(bodyText);
@@ -261,12 +253,13 @@ export class BootstrapHelper {
261
253
  // Keep as string.
262
254
  }
263
255
  }
264
- // Translate the canonical envelope FIRST so the server's specific code +
265
- // message survive (e.g. `api_key_required`, `jwt_issuer_untrusted`).
266
- const translated = translateHttpError(res.status, parsed || `Bootstrap fetch failed: ${res.status} ${res.statusText}`, res.headers.get('x-request-id') ?? undefined);
267
- // Only a genuine session/JWT EXPIRY or a bare auth failure carrying no
268
- // structured code should drive the sign-in redirect. A specific auth
269
- // code like `api_key_required` is NOT an expired session: re-logging-in
256
+ // Translate the canonical envelope first so the server's specific code
257
+ // and message survive (for example `api_key_required` or
258
+ // `jwt_issuer_untrusted`).
259
+ const translated = translateHttpError(res.status, parsed ?? `Bootstrap fetch failed: ${res.status} ${res.statusText}`, res.headers.get('x-request-id') ?? undefined);
260
+ // Only a genuine session or JWT expiry or a bare auth failure carrying
261
+ // no structured code should drive the sign-in redirect. A specific auth
262
+ // code like `api_key_required` is not an expired session: signing in again
270
263
  // mints the same credential and loops. Surface it as its real typed error
271
264
  // instead of a `session_expired` wrapping the stringified body.
272
265
  if (translated.code === 'session_expired' ||
@@ -277,8 +270,7 @@ export class BootstrapHelper {
277
270
  }
278
271
  throw translated;
279
272
  }
280
- const rawJson = await res.json();
281
- const data = parseBootstrapResponse(rawJson);
273
+ const data = parseBootstrapResponse(await res.json());
282
274
  this.warnOnSchemaDrift(data.schemaHash);
283
275
  // Persist payload for offline
284
276
  try {
@@ -286,7 +278,10 @@ export class BootstrapHelper {
286
278
  this.saveCachedBootstrap(this.options.cacheScope, data);
287
279
  }
288
280
  }
289
- catch { }
281
+ catch {
282
+ // Offline persistence is best-effort; a failed cache write must not
283
+ // block returning the freshly fetched data.
284
+ }
290
285
  getContext().logger.info('[Bootstrap] 200 OK - received new data');
291
286
  return { notModified: false, data, etag };
292
287
  }
@@ -329,7 +324,9 @@ export class BootstrapHelper {
329
324
  clearTimeout(timeoutId);
330
325
  if (!response.ok) {
331
326
  const bodyText = await response.text().catch(() => '');
332
- let parsed = bodyText;
327
+ // Map an empty body to undefined so the `??` below falls through to the
328
+ // synthetic message (see the note on the primary fetch path).
329
+ let parsed = bodyText || undefined;
333
330
  if (bodyText) {
334
331
  try {
335
332
  parsed = JSON.parse(bodyText);
@@ -341,7 +338,7 @@ export class BootstrapHelper {
341
338
  // Same code-aware handling as the primary bootstrap fetch: preserve the
342
339
  // server's specific code/message; only a genuine expiry (or a bare,
343
340
  // code-less auth failure) drives the sign-in redirect.
344
- const translated = translateHttpError(response.status, parsed || `Bootstrap fetch failed: ${response.status} ${response.statusText}`, response.headers.get('x-request-id') ?? undefined);
341
+ const translated = translateHttpError(response.status, parsed ?? `Bootstrap fetch failed: ${response.status} ${response.statusText}`, response.headers.get('x-request-id') ?? undefined);
345
342
  if (translated.code === 'session_expired' ||
346
343
  translated.code === 'jwt_expired' ||
347
344
  ((response.status === 401 || response.status === 403) &&
@@ -350,8 +347,7 @@ export class BootstrapHelper {
350
347
  }
351
348
  throw translated;
352
349
  }
353
- const rawJson = await response.json();
354
- const data = parseBootstrapResponse(rawJson);
350
+ const data = parseBootstrapResponse(await response.json());
355
351
  this.warnOnSchemaDrift(data.schemaHash);
356
352
  // Save a copy for offline
357
353
  try {
@@ -359,7 +355,10 @@ export class BootstrapHelper {
359
355
  this.saveCachedBootstrap(this.options.cacheScope, data);
360
356
  }
361
357
  }
362
- catch { }
358
+ catch {
359
+ // Offline persistence is best-effort; a failed cache write must not
360
+ // block returning the freshly fetched data.
361
+ }
363
362
  return data;
364
363
  }
365
364
  /**
@@ -369,10 +368,9 @@ export class BootstrapHelper {
369
368
  */
370
369
  async fetchEntity(modelName, id) {
371
370
  const url = `${this.options.baseUrl}/sync/entity/${modelName}/${id}`;
372
- // Same `fetchTimeout` deadline `performFetch` uses — this was the one
373
- // fetch in this file with no AbortSignal, so a hung self-heal read could
374
- // stall its caller forever. A LOCAL controller (not `this.abortController`)
375
- // so an entity self-heal never cancels a concurrent bootstrap fetch.
371
+ // Uses the same `fetchTimeout` deadline as `performFetch`. A local
372
+ // AbortController, rather than the shared `this.abortController`, means an
373
+ // entity self-heal never cancels a concurrent bootstrap fetch.
376
374
  const controller = new AbortController();
377
375
  const timeoutId = setTimeout(() => { controller.abort(); }, this.options.fetchTimeout);
378
376
  let response;
@@ -401,7 +399,9 @@ export class BootstrapHelper {
401
399
  }
402
400
  if (!response.ok) {
403
401
  const bodyText = await response.text().catch(() => '');
404
- let parsed = bodyText;
402
+ // Map an empty body to undefined so the `??` below falls through to the
403
+ // synthetic message (see the note on the primary fetch path).
404
+ let parsed = bodyText || undefined;
405
405
  if (bodyText) {
406
406
  try {
407
407
  parsed = JSON.parse(bodyText);
@@ -410,9 +410,9 @@ export class BootstrapHelper {
410
410
  // Keep as string.
411
411
  }
412
412
  }
413
- throw translateHttpError(response.status, parsed || `Entity fetch failed: ${response.status} ${response.statusText}`, response.headers.get('x-request-id') ?? undefined);
413
+ throw translateHttpError(response.status, parsed ?? `Entity fetch failed: ${response.status} ${response.statusText}`, response.headers.get('x-request-id') ?? undefined);
414
414
  }
415
- return await response.json();
415
+ return (await response.json());
416
416
  }
417
417
  // ─────────────────────────────────────────────────────────────────────
418
418
  /**
@@ -426,7 +426,7 @@ export class BootstrapHelper {
426
426
  const keysToRemove = [];
427
427
  for (let i = 0; i < localStorage.length; i++) {
428
428
  const key = localStorage.key(i);
429
- if (key && key.includes('sync-bootstrap')) {
429
+ if (key?.includes('sync-bootstrap')) {
430
430
  keysToRemove.push(key);
431
431
  }
432
432
  }
@@ -495,10 +495,10 @@ export class BootstrapHelper {
495
495
  });
496
496
  if (!response.ok)
497
497
  return false;
498
- const data = await response.json();
499
- return data.status === 'healthy';
498
+ const body = (await response.json());
499
+ return body.status === 'healthy';
500
500
  }
501
- catch (error) {
501
+ catch {
502
502
  getContext().observability.breadcrumb('Health check failed', 'sync.bootstrap', 'warning');
503
503
  return false;
504
504
  }
@@ -1,24 +1,18 @@
1
1
  /**
2
- * ConnectionManager single source of truth for the sync engine's
3
- * connection lifecycle. Absorbs the FSM every SDK consumer used to
4
- * rebuild by hand (apps/web's `ConnectionStore` was the reference
5
- * implementation 605 LOC of FSM + watchdog + backoff).
2
+ * Owns the sync engine's connection lifecycle: the state machine that carries a
3
+ * client from a healthy connection, through a dropout, and back to live. It
4
+ * watches the browser's online/offline and visibility events, probes real
5
+ * connectivity and session validity with {@link probeNetwork}, applies retry
6
+ * backoff with a ceiling and jitter, and runs a watchdog for the cases where
7
+ * the browser never fires an event (VPN flips, captive portals). When it
8
+ * decides to recover, it drives the reconnect sequence — bootstrap, then
9
+ * WebSocket connect — through the {@link ConnectionCallbacks.onReconnect}
10
+ * callback and reacts to the outcome.
6
11
  *
7
- * What it owns:
8
- * - Browser online/offline + visibility events
9
- * - Network probe orchestration (via `probeNetwork`)
10
- * - Session-validity checks (HEAD /api/auth/check)
11
- * - Retry backoff with ceiling, jitter, and offline-aware parking
12
- * - Watchdog for browser events that never fire (VPN, captive portal)
13
- * - The reconnect → bootstrap → WebSocket connect sequence
14
- *
15
- * What it DOES NOT own:
16
- * - The actual bootstrap / IndexedDB / ObjectPool work — that lives in
17
- * `BaseSyncedStore.performReconnect()`. This class calls it via the
18
- * `onReconnect` callback and reacts to the outcome.
19
- *
20
- * Designed to be embedded by `BaseSyncedStore`: one instance per store,
21
- * started on first successful connect, disposed on teardown.
12
+ * It deliberately does not perform the bootstrap, local-storage, or object-pool
13
+ * work itself; that lives in the embedding store, which the manager reaches
14
+ * only through its callbacks. One manager is created per store, started on the
15
+ * first successful connect and disposed on teardown.
22
16
  *
23
17
  * CONNECTED ──(socket drop)──► PROBING_NETWORK ──► RECONNECTING ──► CONNECTED
24
18
  * │ │ │
@@ -29,20 +23,15 @@
29
23
  * ▼
30
24
  * WAITING_FOR_NETWORK
31
25
  *
32
- * Includes three fixes over the original app-side FSM:
33
- * 1. `backoff` accepts `NETWORK_ONLINE` / `TAB_VISIBLE` jumps to
34
- * probing immediately when the network comes back, without
35
- * waiting for the backoff timer to elapse.
36
- * 2. `scheduleBackoff` parks in `waiting_for_network` (resetting
37
- * `attempt`) when `navigator.onLine === false` at max retries,
38
- * instead of hard-reloading an already-offline browser.
39
- * 3. A socket drop (`WS_DISCONNECTED`, typically code 1006) goes
40
- * STRAIGHT to `probing_network`, not the passive `offline` state.
41
- * 1006 is browser-local and carries no connectivity signal, so on a
42
- * healthy machine no `online`/`offline` event ever fires — parking in
43
- * `offline` stranded recovery until the 30s watchdog, long enough for
44
- * queued commits to roll back. Only a genuine OS-level `NETWORK_LOST`
45
- * parks in `offline` and waits for the `online` event.
26
+ * Three behaviors are worth calling out. A network return or tab focus during a
27
+ * backoff delay jumps straight to probing instead of waiting out the full
28
+ * interval. Reaching the retry ceiling while the browser reports offline parks
29
+ * in `waiting_for_network` rather than hard-reloading a browser that has no
30
+ * network. And a socket drop (close code 1006) goes straight to probing rather
31
+ * than the passive `offline` state 1006 is generated locally and carries no
32
+ * connectivity signal, so on a healthy machine no online/offline event ever
33
+ * fires; only a genuine operating-system network loss parks in `offline` to
34
+ * wait for the `online` event.
46
35
  */
47
36
  import { type ProbeResult } from './NetworkProbe.js';
48
37
  import type { AuthTokenGetter } from '../auth/credentialSource.js';
@@ -93,17 +82,18 @@ export interface ConnectionCallbacks {
93
82
  /** Run bootstrap + WebSocket reconnect. Returns the outcome. */
94
83
  onReconnect: () => Promise<'success' | 'session_error' | 'network_error'>;
95
84
  /**
96
- * Re-mint the short-lived access credential (the Stripe-style `ek_`/`rk_`)
97
- * and push it into the credential source, then report the outcome. Invoked
98
- * on `refreshing_credential` — i.e. when a probe found the access key stale
99
- * (`PROBE_CREDENTIAL_STALE`). Mirrors the `getToken` contract:
100
- * - `'refreshed'` → a fresh credential is in place; re-probe & reconnect.
101
- * - `'session_error'` → the LONG-LIVED login is gone (mint returned null →
102
- * 401/403); terminal sign out.
103
- * - `'network_error'` → couldn't reach the mint endpoint (offline/5xx/throw);
104
- * transient back off and retry, never sign out.
105
- * Optional: a deployment with no re-mint path (e.g. a static `apiKey`) omits
106
- * it, and the FSM falls back to a plain re-probe.
85
+ * Re-mints the short-lived access credential (the `ek_`/`rk_`) and pushes it
86
+ * into the credential source, then reports the outcome. Invoked in the
87
+ * `refreshing_credential` state that is, when a probe found the access key
88
+ * stale (`PROBE_CREDENTIAL_STALE`). The three outcomes map onto recovery:
89
+ * - `'refreshed'` → a fresh credential is in place; re-probe and reconnect.
90
+ * - `'session_error'` → the long-lived login itself is gone (the mint
91
+ * returned null, a 401/403); terminal, so sign out.
92
+ * - `'network_error'` → the mint endpoint was unreachable (offline, 5xx, or
93
+ * a throw); transient, so back off and retry rather
94
+ * than sign out.
95
+ * Optional: a deployment with no re-mint path (for example a static `apiKey`)
96
+ * omits it, and the state machine falls back to a plain re-probe.
107
97
  */
108
98
  onRefreshCredential?: () => Promise<'refreshed' | 'session_error' | 'network_error'>;
109
99
  /** Called when the session is confirmed expired — route to signin. */
@@ -111,12 +101,11 @@ export interface ConnectionCallbacks {
111
101
  /** Called to tear down the WebSocket when entering a dead state. */
112
102
  onDisconnectWebSocket: () => void;
113
103
  /**
114
- * Fired on every FSM state transition. Lets the embedding store
115
- * mirror recovery progress into its visible `syncStatus` so the UI
116
- * can show "Reconnecting…" instead of a sticky "offline" while the
117
- * FSM cycles through `probing_network` → `reconnecting` → `backoff`.
118
- * Optional omitting it preserves the previous behavior where the
119
- * FSM was opaque to the UI.
104
+ * Fires on every state transition. It lets the embedding store mirror
105
+ * recovery progress into its visible `syncStatus`, so the UI can show
106
+ * "Reconnecting…" instead of a sticky "offline" while the machine cycles
107
+ * through `probing_network` → `reconnecting` → `backoff`. Optional; when
108
+ * omitted, the state machine is simply opaque to the UI.
120
109
  */
121
110
  onStateChange?: (next: ConnectionState, prev: ConnectionState) => void;
122
111
  }
@@ -1,24 +1,18 @@
1
1
  /**
2
- * ConnectionManager single source of truth for the sync engine's
3
- * connection lifecycle. Absorbs the FSM every SDK consumer used to
4
- * rebuild by hand (apps/web's `ConnectionStore` was the reference
5
- * implementation 605 LOC of FSM + watchdog + backoff).
2
+ * Owns the sync engine's connection lifecycle: the state machine that carries a
3
+ * client from a healthy connection, through a dropout, and back to live. It
4
+ * watches the browser's online/offline and visibility events, probes real
5
+ * connectivity and session validity with {@link probeNetwork}, applies retry
6
+ * backoff with a ceiling and jitter, and runs a watchdog for the cases where
7
+ * the browser never fires an event (VPN flips, captive portals). When it
8
+ * decides to recover, it drives the reconnect sequence — bootstrap, then
9
+ * WebSocket connect — through the {@link ConnectionCallbacks.onReconnect}
10
+ * callback and reacts to the outcome.
6
11
  *
7
- * What it owns:
8
- * - Browser online/offline + visibility events
9
- * - Network probe orchestration (via `probeNetwork`)
10
- * - Session-validity checks (HEAD /api/auth/check)
11
- * - Retry backoff with ceiling, jitter, and offline-aware parking
12
- * - Watchdog for browser events that never fire (VPN, captive portal)
13
- * - The reconnect → bootstrap → WebSocket connect sequence
14
- *
15
- * What it DOES NOT own:
16
- * - The actual bootstrap / IndexedDB / ObjectPool work — that lives in
17
- * `BaseSyncedStore.performReconnect()`. This class calls it via the
18
- * `onReconnect` callback and reacts to the outcome.
19
- *
20
- * Designed to be embedded by `BaseSyncedStore`: one instance per store,
21
- * started on first successful connect, disposed on teardown.
12
+ * It deliberately does not perform the bootstrap, local-storage, or object-pool
13
+ * work itself; that lives in the embedding store, which the manager reaches
14
+ * only through its callbacks. One manager is created per store, started on the
15
+ * first successful connect and disposed on teardown.
22
16
  *
23
17
  * CONNECTED ──(socket drop)──► PROBING_NETWORK ──► RECONNECTING ──► CONNECTED
24
18
  * │ │ │
@@ -29,20 +23,15 @@
29
23
  * ▼
30
24
  * WAITING_FOR_NETWORK
31
25
  *
32
- * Includes three fixes over the original app-side FSM:
33
- * 1. `backoff` accepts `NETWORK_ONLINE` / `TAB_VISIBLE` jumps to
34
- * probing immediately when the network comes back, without
35
- * waiting for the backoff timer to elapse.
36
- * 2. `scheduleBackoff` parks in `waiting_for_network` (resetting
37
- * `attempt`) when `navigator.onLine === false` at max retries,
38
- * instead of hard-reloading an already-offline browser.
39
- * 3. A socket drop (`WS_DISCONNECTED`, typically code 1006) goes
40
- * STRAIGHT to `probing_network`, not the passive `offline` state.
41
- * 1006 is browser-local and carries no connectivity signal, so on a
42
- * healthy machine no `online`/`offline` event ever fires — parking in
43
- * `offline` stranded recovery until the 30s watchdog, long enough for
44
- * queued commits to roll back. Only a genuine OS-level `NETWORK_LOST`
45
- * parks in `offline` and waits for the `online` event.
26
+ * Three behaviors are worth calling out. A network return or tab focus during a
27
+ * backoff delay jumps straight to probing instead of waiting out the full
28
+ * interval. Reaching the retry ceiling while the browser reports offline parks
29
+ * in `waiting_for_network` rather than hard-reloading a browser that has no
30
+ * network. And a socket drop (close code 1006) goes straight to probing rather
31
+ * than the passive `offline` state 1006 is generated locally and carries no
32
+ * connectivity signal, so on a healthy machine no online/offline event ever
33
+ * fires; only a genuine operating-system network loss parks in `offline` to
34
+ * wait for the `online` event.
46
35
  */
47
36
  import { makeAutoObservable, runInAction } from 'mobx';
48
37
  import { getContext } from '../context.js';
@@ -158,13 +147,13 @@ export class ConnectionManager {
158
147
  // work.
159
148
  return 'offline';
160
149
  case 'WS_DISCONNECTED':
161
- // The socket died (typically code 1006) but the OS network is
162
- // almost certainly fine — 1006 is generated locally when the TCP
163
- // conn vanishes and carries NO connectivity signal, so the browser
164
- // fires no online/offline event. Probe IMMEDIATELY rather than
165
- // landing in the passive `offline` dead-end (which only escaped via
166
- // the 30s watchdog, long after queued commits rolled back). The
167
- // probe fast-fails if we genuinely ARE offline → waiting_for_network.
150
+ // The socket died (typically code 1006) but the operating-system
151
+ // network is almost certainly fine — 1006 is generated locally when
152
+ // the TCP connection vanishes and carries no connectivity signal, so
153
+ // the browser fires no online/offline event. Probe immediately rather
154
+ // than landing in the passive `offline` dead-end (which only escaped
155
+ // via the 30s watchdog, long after queued commits rolled back). The
156
+ // probe fast-fails if we genuinely are offline → waiting_for_network.
168
157
  return 'probing_network';
169
158
  case 'WS_SESSION_ERROR':
170
159
  case 'BOOTSTRAP_FAILED_SESSION':
@@ -233,15 +222,15 @@ export class ConnectionManager {
233
222
  return null;
234
223
  }
235
224
  case 'refreshing_credential':
236
- // Re-minting the short-lived access key (the Stripe-style `ek_`/`rk_`).
237
- // The login is presumed valid; this is NOT a sign-out state.
225
+ // Re-minting the short-lived access key (the `ek_`/`rk_`). The login is
226
+ // presumed valid; this is not a sign-out state.
238
227
  switch (event.type) {
239
228
  case 'CREDENTIAL_REFRESHED':
240
229
  // Fresh key in hand — re-probe so we reconnect with it.
241
230
  return 'probing_network';
242
231
  case 'BOOTSTRAP_FAILED_SESSION':
243
232
  // The re-mint hit a genuine 401/403: the long-lived login itself is
244
- // gone. THIS is the only path from here to sign-out.
233
+ // gone. This is the only path from here to sign-out.
245
234
  return 'session_expired';
246
235
  case 'RECONNECT_FAILED':
247
236
  // Couldn't reach the mint endpoint (offline/5xx/throw) — transient.
@@ -336,15 +325,15 @@ export class ConnectionManager {
336
325
  this.callbacks?.onDisconnectWebSocket();
337
326
  break;
338
327
  case 'probing_network':
339
- // A socket drop (`WS_DISCONNECTED`) now lands here directly so recovery
340
- // starts immediately. Tear the dead socket down FIRSTthis is what
328
+ // A socket drop (`WS_DISCONNECTED`) lands here directly so recovery
329
+ // starts immediately. Tear the dead socket down firstthat is what
341
330
  // sets SyncWebSocket's `isManualClose=true` and suppresses its own
342
- // scheduleReconnect, keeping the FSM the single reconnect authority on
343
- // the human path. The teardown runs synchronously inside the
344
- // `disconnected` emit, before `SyncWebSocket.onclose` checks the flag,
345
- // so the timing matches the previous `offline`-entry teardown. We gate
346
- // on the drop event specifically: the other paths into `probing_network`
347
- // (TAB_VISIBLE re-validation, handshake retry, backoff elapse) must NOT
331
+ // reconnect, keeping this machine the single reconnect authority on the
332
+ // human path. The teardown runs synchronously inside the `disconnected`
333
+ // emit, before `SyncWebSocket.onclose` checks the flag, so the timing
334
+ // matches the previous `offline`-entry teardown. We gate on the drop
335
+ // event specifically: the other paths into `probing_network`
336
+ // (tab-focus re-validation, handshake retry, backoff elapse) must not
348
337
  // tear down a socket that may still be live.
349
338
  if (event.type === 'WS_DISCONNECTED') {
350
339
  this.callbacks?.onDisconnectWebSocket();
@@ -364,11 +353,12 @@ export class ConnectionManager {
364
353
  this.scheduleBackoff();
365
354
  break;
366
355
  case 'auth_blocked':
367
- // Stop — reachable but the credential was rejected (e.g.
368
- // api_key_required / jwt_issuer_untrusted from the data plane). Neither
369
- // reconnecting nor re-auth fixes it. Drop the socket and wait for a
370
- // manual retry / re-probe. Crucially NOT onSessionExpired (no sign-out)
371
- // and NOT a reconnect — that's the whole point of this state.
356
+ // Stop here the server is reachable but rejected the credential (for
357
+ // example api_key_required or jwt_issuer_untrusted from the data plane).
358
+ // Neither reconnecting nor re-authenticating fixes it. Drop the socket
359
+ // and wait for a manual retry or a re-probe. This deliberately does not
360
+ // call onSessionExpired (no sign-out) and does not reconnect — that is
361
+ // the whole point of the state.
372
362
  this.clearBackoffTimer();
373
363
  this.callbacks?.onDisconnectWebSocket();
374
364
  getContext().observability.breadcrumb('Auth blocked — reachable but credential rejected; not reconnecting or signing out', 'sync.offline', 'error');
@@ -602,16 +592,15 @@ export class ConnectionManager {
602
592
  this.watchdogTimer = setInterval(() => {
603
593
  if (this.disposed)
604
594
  return;
605
- // "Stuck" = parked in a non-active recovery state (offline,
606
- // waiting_for_network, backoff). We deliberately do NOT gate on
607
- // `navigator.onLine === true` here: per MDN, `navigator.onLine`
608
- // is only reliable when it returns false ("definitely offline"),
609
- // and even that lies after laptop wake / VPN flips. Gating the
610
- // watchdog on `onLine` was the actual "offline forever" bug
611
- // when the browser briefly reported offline and never re-fired
612
- // the `online` event, the FSM had no escape from `'offline'`.
613
- // The probe itself fast-fails when truly offline (NetworkProbe.ts),
614
- // so an unconditional retry costs nothing in the genuine case.
595
+ // "Stuck" means parked in a non-active recovery state (offline,
596
+ // waiting_for_network, backoff). This deliberately does not gate on
597
+ // `navigator.onLine === true`: per MDN, `navigator.onLine` is only
598
+ // reliable when it returns false ("definitely offline"), and even that
599
+ // lies after laptop wake or VPN flips. Gating the watchdog on `onLine`
600
+ // was what stranded the machine in `offline` forever when the browser
601
+ // briefly reported offline and never re-fired the `online` event, there
602
+ // was no escape. The probe itself fast-fails when truly offline, so an
603
+ // unconditional retry costs nothing in the genuine case.
615
604
  const isStuck = this.state !== 'connected' &&
616
605
  this.state !== 'session_expired' &&
617
606
  this.state !== 'probing_network' &&