@abloatai/ablo 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +101 -85
  3. package/dist/BaseSyncedStore.d.ts +85 -88
  4. package/dist/BaseSyncedStore.js +131 -147
  5. package/dist/Database.d.ts +54 -68
  6. package/dist/Database.js +97 -113
  7. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  8. package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
  9. package/dist/LazyReferenceCollection.d.ts +11 -15
  10. package/dist/LazyReferenceCollection.js +12 -16
  11. package/dist/Model.d.ts +37 -52
  12. package/dist/Model.js +46 -61
  13. package/dist/ModelRegistry.d.ts +21 -19
  14. package/dist/ModelRegistry.js +23 -27
  15. package/dist/NetworkMonitor.d.ts +5 -6
  16. package/dist/NetworkMonitor.js +5 -6
  17. package/dist/SyncClient.d.ts +112 -112
  18. package/dist/SyncClient.js +165 -172
  19. package/dist/adapters/alwaysOnline.d.ts +6 -8
  20. package/dist/adapters/alwaysOnline.js +6 -8
  21. package/dist/adapters/inMemoryStorage.d.ts +9 -9
  22. package/dist/adapters/inMemoryStorage.js +9 -9
  23. package/dist/agent/Agent.d.ts +27 -32
  24. package/dist/agent/Agent.js +18 -19
  25. package/dist/agent/index.d.ts +4 -4
  26. package/dist/agent/index.js +5 -5
  27. package/dist/agent/session.d.ts +47 -44
  28. package/dist/agent/session.js +37 -48
  29. package/dist/agent/types.d.ts +26 -31
  30. package/dist/agent/types.js +6 -7
  31. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  32. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  33. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  34. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +26 -33
  35. package/dist/ai-sdk/index.d.ts +25 -22
  36. package/dist/ai-sdk/index.js +25 -22
  37. package/dist/ai-sdk/wrap.d.ts +6 -7
  38. package/dist/ai-sdk/wrap.js +1 -1
  39. package/dist/auth/credentialPolicy.d.ts +69 -74
  40. package/dist/auth/credentialPolicy.js +51 -56
  41. package/dist/auth/credentialSource.d.ts +6 -5
  42. package/dist/auth/credentialSource.js +9 -10
  43. package/dist/auth/index.d.ts +59 -58
  44. package/dist/auth/index.js +31 -37
  45. package/dist/auth/schemas.d.ts +5 -4
  46. package/dist/auth/schemas.js +5 -4
  47. package/dist/batching/index.d.ts +19 -21
  48. package/dist/batching/index.js +14 -17
  49. package/dist/cli.cjs +167 -119
  50. package/dist/client/Ablo.d.ts +73 -73
  51. package/dist/client/Ablo.js +125 -160
  52. package/dist/client/ApiClient.d.ts +30 -19
  53. package/dist/client/ApiClient.js +133 -38
  54. package/dist/client/auth.d.ts +47 -47
  55. package/dist/client/auth.js +108 -117
  56. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  57. package/dist/client/claimHeartbeatLoop.js +88 -0
  58. package/dist/client/consoleLogger.d.ts +5 -6
  59. package/dist/client/consoleLogger.js +5 -6
  60. package/dist/client/createInternalComponents.d.ts +14 -17
  61. package/dist/client/createInternalComponents.js +25 -30
  62. package/dist/client/createModelProxy.d.ts +130 -120
  63. package/dist/client/createModelProxy.js +152 -122
  64. package/dist/client/credentialEndpoint.d.ts +40 -42
  65. package/dist/client/credentialEndpoint.js +35 -36
  66. package/dist/client/functionalUpdate.d.ts +29 -27
  67. package/dist/client/functionalUpdate.js +21 -21
  68. package/dist/client/hostedEndpoints.d.ts +9 -12
  69. package/dist/client/hostedEndpoints.js +9 -12
  70. package/dist/client/httpClient.d.ts +57 -53
  71. package/dist/client/httpClient.js +29 -31
  72. package/dist/client/identity.d.ts +15 -20
  73. package/dist/client/identity.js +47 -58
  74. package/dist/client/modelRegistration.d.ts +5 -9
  75. package/dist/client/modelRegistration.js +67 -87
  76. package/dist/client/options.d.ts +134 -157
  77. package/dist/client/options.js +3 -7
  78. package/dist/client/registerDataSource.d.ts +9 -9
  79. package/dist/client/registerDataSource.js +15 -16
  80. package/dist/client/resourceTypes.d.ts +64 -75
  81. package/dist/client/resourceTypes.js +4 -10
  82. package/dist/client/schemaConfig.d.ts +31 -43
  83. package/dist/client/schemaConfig.js +38 -50
  84. package/dist/client/sessionMint.d.ts +16 -12
  85. package/dist/client/sessionMint.js +26 -31
  86. package/dist/client/validateAbloOptions.d.ts +12 -14
  87. package/dist/client/validateAbloOptions.js +8 -9
  88. package/dist/client/writeOptionsSchema.d.ts +18 -16
  89. package/dist/client/writeOptionsSchema.js +23 -20
  90. package/dist/client/wsMutationExecutor.d.ts +15 -20
  91. package/dist/client/wsMutationExecutor.js +17 -23
  92. package/dist/context.d.ts +6 -4
  93. package/dist/context.js +6 -4
  94. package/dist/coordination/index.d.ts +10 -8
  95. package/dist/coordination/index.js +14 -12
  96. package/dist/coordination/schema.d.ts +176 -128
  97. package/dist/coordination/schema.js +197 -133
  98. package/dist/coordination/trace.d.ts +9 -10
  99. package/dist/coordination/trace.js +13 -14
  100. package/dist/core/DatabaseManager.d.ts +5 -7
  101. package/dist/core/DatabaseManager.js +15 -19
  102. package/dist/core/QueryProcessor.d.ts +7 -9
  103. package/dist/core/QueryProcessor.js +22 -28
  104. package/dist/core/QueryView.d.ts +8 -8
  105. package/dist/core/QueryView.js +2 -2
  106. package/dist/core/StoreManager.d.ts +12 -14
  107. package/dist/core/StoreManager.js +21 -24
  108. package/dist/core/ViewRegistry.d.ts +5 -5
  109. package/dist/core/ViewRegistry.js +4 -4
  110. package/dist/core/index.d.ts +17 -12
  111. package/dist/core/index.js +32 -26
  112. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  113. package/dist/core/openIDBWithTimeout.js +42 -43
  114. package/dist/core/queryUtils.d.ts +45 -0
  115. package/dist/core/queryUtils.js +69 -0
  116. package/dist/core/storeContract.d.ts +63 -61
  117. package/dist/core/storeContract.js +8 -12
  118. package/dist/environment.d.ts +28 -0
  119. package/dist/environment.js +21 -0
  120. package/dist/errorCodes.d.ts +107 -99
  121. package/dist/errorCodes.js +131 -132
  122. package/dist/errors.d.ts +160 -166
  123. package/dist/errors.js +155 -158
  124. package/dist/index.d.ts +30 -27
  125. package/dist/index.js +89 -86
  126. package/dist/interfaces/index.d.ts +102 -113
  127. package/dist/interfaces/index.js +5 -4
  128. package/dist/keys/index.d.ts +27 -29
  129. package/dist/keys/index.js +41 -40
  130. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  131. package/dist/mutators/RecordingTransaction.js +31 -37
  132. package/dist/mutators/Transaction.d.ts +18 -26
  133. package/dist/mutators/Transaction.js +14 -20
  134. package/dist/mutators/UndoManager.d.ts +122 -131
  135. package/dist/mutators/UndoManager.js +145 -156
  136. package/dist/mutators/defineMutators.d.ts +23 -34
  137. package/dist/mutators/defineMutators.js +14 -20
  138. package/dist/mutators/inverseOp.d.ts +12 -15
  139. package/dist/mutators/inverseOp.js +12 -15
  140. package/dist/mutators/mutateActions.d.ts +10 -9
  141. package/dist/mutators/mutateActions.js +1 -1
  142. package/dist/mutators/readerActions.d.ts +9 -8
  143. package/dist/mutators/readerActions.js +2 -2
  144. package/dist/mutators/undoApply.d.ts +31 -27
  145. package/dist/mutators/undoApply.js +26 -24
  146. package/dist/policy/index.d.ts +5 -3
  147. package/dist/policy/index.js +5 -3
  148. package/dist/policy/types.d.ts +104 -100
  149. package/dist/policy/types.js +67 -66
  150. package/dist/query/client.d.ts +28 -23
  151. package/dist/query/client.js +45 -43
  152. package/dist/query/types.d.ts +37 -60
  153. package/dist/query/types.js +13 -33
  154. package/dist/react/AbloProvider.d.ts +1 -1
  155. package/dist/react/AbloProvider.js +2 -2
  156. package/dist/react/context.d.ts +25 -28
  157. package/dist/react/context.js +9 -10
  158. package/dist/react/index.d.ts +41 -42
  159. package/dist/react/index.js +37 -38
  160. package/dist/react/internalContext.d.ts +17 -19
  161. package/dist/react/useAblo.d.ts +23 -22
  162. package/dist/react/useAblo.js +16 -14
  163. package/dist/react/useCurrentUserId.d.ts +8 -7
  164. package/dist/react/useCurrentUserId.js +8 -7
  165. package/dist/react/useErrorListener.d.ts +7 -7
  166. package/dist/react/useErrorListener.js +10 -11
  167. package/dist/react/useMutationFailureListener.d.ts +8 -8
  168. package/dist/react/useMutationFailureListener.js +8 -8
  169. package/dist/react/useMutators.d.ts +11 -11
  170. package/dist/react/useMutators.js +3 -3
  171. package/dist/react/useReactive.js +2 -2
  172. package/dist/react/useSyncStatus.d.ts +4 -6
  173. package/dist/react/useUndoScope.d.ts +7 -9
  174. package/dist/react/useUndoScope.js +1 -1
  175. package/dist/schema/coordination.d.ts +21 -25
  176. package/dist/schema/coordination.js +21 -25
  177. package/dist/schema/ddl.d.ts +43 -39
  178. package/dist/schema/ddl.js +75 -68
  179. package/dist/schema/ddlLock.d.ts +20 -24
  180. package/dist/schema/ddlLock.js +18 -23
  181. package/dist/schema/diff.d.ts +99 -61
  182. package/dist/schema/diff.js +43 -34
  183. package/dist/schema/field.d.ts +37 -42
  184. package/dist/schema/field.js +35 -48
  185. package/dist/schema/generate.d.ts +12 -12
  186. package/dist/schema/generate.js +12 -12
  187. package/dist/schema/index.d.ts +2 -2
  188. package/dist/schema/index.js +21 -23
  189. package/dist/schema/model.d.ts +118 -143
  190. package/dist/schema/model.js +22 -33
  191. package/dist/schema/openapi.d.ts +10 -9
  192. package/dist/schema/openapi.js +5 -3
  193. package/dist/schema/queries.d.ts +29 -31
  194. package/dist/schema/queries.js +23 -25
  195. package/dist/schema/relation.d.ts +89 -99
  196. package/dist/schema/relation.js +13 -13
  197. package/dist/schema/residency.d.ts +16 -13
  198. package/dist/schema/residency.js +16 -13
  199. package/dist/schema/roles.d.ts +36 -43
  200. package/dist/schema/roles.js +31 -37
  201. package/dist/schema/schema.d.ts +33 -42
  202. package/dist/schema/schema.js +31 -32
  203. package/dist/schema/select.d.ts +13 -13
  204. package/dist/schema/select.js +13 -13
  205. package/dist/schema/serialize.d.ts +28 -31
  206. package/dist/schema/serialize.js +27 -31
  207. package/dist/schema/sugar.d.ts +17 -32
  208. package/dist/schema/sugar.js +14 -29
  209. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +26 -49
  210. package/dist/schema/syncDeltaRow.js +89 -0
  211. package/dist/schema/tenancy.d.ts +44 -46
  212. package/dist/schema/tenancy.js +46 -48
  213. package/dist/server/adapter.d.ts +58 -58
  214. package/dist/server/adapter.js +13 -14
  215. package/dist/server/commit.d.ts +60 -64
  216. package/dist/server/index.d.ts +9 -10
  217. package/dist/server/index.js +1 -1
  218. package/dist/server/readConfig.d.ts +70 -0
  219. package/dist/server/readConfig.js +8 -0
  220. package/dist/server/storageMode.d.ts +23 -0
  221. package/dist/server/storageMode.js +17 -0
  222. package/dist/source/adapter.d.ts +30 -25
  223. package/dist/source/adapter.js +10 -10
  224. package/dist/source/adapters/drizzle.d.ts +28 -23
  225. package/dist/source/adapters/drizzle.js +30 -25
  226. package/dist/source/adapters/kysely.d.ts +27 -25
  227. package/dist/source/adapters/kysely.js +24 -23
  228. package/dist/source/adapters/memory.d.ts +8 -7
  229. package/dist/source/adapters/memory.js +9 -8
  230. package/dist/source/adapters/prisma.d.ts +13 -12
  231. package/dist/source/adapters/prisma.js +22 -25
  232. package/dist/source/conformance.d.ts +18 -11
  233. package/dist/source/conformance.js +17 -11
  234. package/dist/source/connector.d.ts +31 -32
  235. package/dist/source/connector.js +28 -28
  236. package/dist/source/connectorProtocol.d.ts +160 -0
  237. package/dist/source/connectorProtocol.js +162 -0
  238. package/dist/source/contract.d.ts +26 -27
  239. package/dist/source/contract.js +28 -29
  240. package/dist/source/factory.d.ts +46 -58
  241. package/dist/source/factory.js +22 -27
  242. package/dist/source/index.d.ts +7 -9
  243. package/dist/source/index.js +12 -14
  244. package/dist/source/migrations.d.ts +9 -9
  245. package/dist/source/migrations.js +9 -9
  246. package/dist/source/next.d.ts +9 -10
  247. package/dist/source/next.js +6 -7
  248. package/dist/source/pushQueue.d.ts +69 -47
  249. package/dist/source/pushQueue.js +32 -28
  250. package/dist/source/signing.d.ts +46 -17
  251. package/dist/source/signing.js +28 -11
  252. package/dist/source/types.d.ts +121 -104
  253. package/dist/source/types.js +13 -14
  254. package/dist/stores/ObjectStore.d.ts +10 -11
  255. package/dist/stores/ObjectStore.js +11 -12
  256. package/dist/stores/ObjectStoreContract.d.ts +12 -15
  257. package/dist/stores/SyncActionStore.d.ts +7 -11
  258. package/dist/stores/SyncActionStore.js +13 -17
  259. package/dist/surface.d.ts +27 -20
  260. package/dist/surface.js +27 -20
  261. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
  262. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
  263. package/dist/sync/ConnectionManager.d.ts +39 -50
  264. package/dist/sync/ConnectionManager.js +55 -66
  265. package/dist/sync/NetworkProbe.d.ts +24 -29
  266. package/dist/sync/NetworkProbe.js +63 -69
  267. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
  268. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
  269. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
  270. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
  271. package/dist/sync/SyncWebSocket.d.ts +139 -165
  272. package/dist/sync/SyncWebSocket.js +191 -223
  273. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  274. package/dist/sync/awaitClaimGrant.js +11 -11
  275. package/dist/sync/bootstrapApply.d.ts +34 -24
  276. package/dist/sync/bootstrapApply.js +27 -19
  277. package/dist/sync/commitFrames.d.ts +21 -20
  278. package/dist/sync/commitFrames.js +18 -18
  279. package/dist/sync/createClaimStream.d.ts +23 -22
  280. package/dist/sync/createClaimStream.js +105 -23
  281. package/dist/sync/createPresenceStream.d.ts +19 -18
  282. package/dist/sync/createPresenceStream.js +25 -26
  283. package/dist/sync/createSnapshot.d.ts +12 -14
  284. package/dist/sync/createSnapshot.js +20 -26
  285. package/dist/sync/credentialLifecycle.d.ts +104 -104
  286. package/dist/sync/credentialLifecycle.js +140 -147
  287. package/dist/sync/deltaPipeline.d.ts +36 -34
  288. package/dist/sync/deltaPipeline.js +64 -65
  289. package/dist/sync/groupChange.d.ts +63 -61
  290. package/dist/sync/groupChange.js +74 -78
  291. package/dist/sync/heartbeat.d.ts +34 -33
  292. package/dist/sync/heartbeat.js +31 -31
  293. package/dist/sync/participants.d.ts +19 -19
  294. package/dist/sync/schemas.d.ts +3 -2
  295. package/dist/sync/schemas.js +14 -10
  296. package/dist/sync/syncCursor.d.ts +17 -21
  297. package/dist/sync/syncCursor.js +17 -21
  298. package/dist/sync/syncPlan.d.ts +28 -36
  299. package/dist/sync/syncPlan.js +18 -19
  300. package/dist/sync/syncPosition.d.ts +54 -49
  301. package/dist/sync/syncPosition.js +57 -52
  302. package/dist/sync/wsFrameHandlers.d.ts +35 -36
  303. package/dist/sync/wsFrameHandlers.js +63 -67
  304. package/dist/testing/fixtures/bootstrap.d.ts +12 -6
  305. package/dist/testing/fixtures/bootstrap.js +12 -6
  306. package/dist/testing/fixtures/deltas.d.ts +30 -33
  307. package/dist/testing/fixtures/deltas.js +30 -33
  308. package/dist/testing/fixtures/models.d.ts +11 -10
  309. package/dist/testing/fixtures/models.js +11 -10
  310. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  311. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  312. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -15
  313. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
  314. package/dist/testing/helpers/wait.d.ts +13 -8
  315. package/dist/testing/helpers/wait.js +13 -8
  316. package/dist/testing/index.d.ts +3 -3
  317. package/dist/testing/index.js +2 -2
  318. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  319. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  320. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  321. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  322. package/dist/testing/mocks/MockSyncContext.d.ts +20 -17
  323. package/dist/testing/mocks/MockSyncContext.js +15 -13
  324. package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
  325. package/dist/testing/mocks/MockSyncStore.js +11 -11
  326. package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
  327. package/dist/testing/mocks/MockWebSocket.js +22 -21
  328. package/dist/transactions/TransactionQueue.d.ts +181 -176
  329. package/dist/transactions/TransactionQueue.js +338 -350
  330. package/dist/transactions/TransactionStore.d.ts +6 -4
  331. package/dist/transactions/TransactionStore.js +6 -4
  332. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  333. package/dist/transactions/UnconfirmedWrites.js +104 -0
  334. package/dist/transactions/coalesceRules.d.ts +41 -17
  335. package/dist/transactions/coalesceRules.js +40 -17
  336. package/dist/transactions/commitPayload.d.ts +48 -52
  337. package/dist/transactions/commitPayload.js +48 -57
  338. package/dist/transactions/deltaConfirmation.d.ts +20 -22
  339. package/dist/transactions/deltaConfirmation.js +37 -45
  340. package/dist/transactions/optimisticApply.d.ts +49 -0
  341. package/dist/transactions/optimisticApply.js +65 -0
  342. package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
  343. package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
  344. package/dist/types/global.d.ts +46 -41
  345. package/dist/types/global.js +20 -19
  346. package/dist/types/index.d.ts +71 -77
  347. package/dist/types/index.js +22 -22
  348. package/dist/types/modelData.d.ts +6 -8
  349. package/dist/types/modelData.js +5 -7
  350. package/dist/types/participant.d.ts +10 -11
  351. package/dist/types/participant.js +6 -8
  352. package/dist/types/streams.d.ts +208 -195
  353. package/dist/types/streams.js +7 -7
  354. package/dist/utils/asyncIterator.d.ts +25 -32
  355. package/dist/utils/asyncIterator.js +25 -32
  356. package/dist/utils/duration.d.ts +12 -15
  357. package/dist/utils/duration.js +12 -15
  358. package/dist/utils/mobxSetup.d.ts +53 -0
  359. package/dist/utils/{mobx-setup.js → mobxSetup.js} +42 -98
  360. package/dist/webhooks/events.d.ts +21 -16
  361. package/dist/webhooks/events.js +10 -8
  362. package/dist/webhooks/index.d.ts +5 -7
  363. package/dist/webhooks/index.js +5 -7
  364. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  365. package/dist/wire/delta.js +114 -0
  366. package/dist/wire/errorEnvelope.d.ts +30 -31
  367. package/dist/wire/errorEnvelope.js +34 -40
  368. package/dist/wire/frames.d.ts +79 -86
  369. package/dist/wire/frames.js +26 -33
  370. package/dist/wire/index.d.ts +14 -12
  371. package/dist/wire/index.js +30 -26
  372. package/dist/wire/listEnvelope.d.ts +16 -23
  373. package/dist/wire/listEnvelope.js +7 -6
  374. package/dist/wire/protocol.d.ts +25 -32
  375. package/dist/wire/protocol.js +25 -32
  376. package/dist/wire/protocolVersion.d.ts +44 -40
  377. package/dist/wire/protocolVersion.js +44 -40
  378. package/docs/coordination.md +59 -0
  379. package/package.json +11 -10
  380. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  381. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  382. package/dist/core/query-utils.d.ts +0 -34
  383. package/dist/core/query-utils.js +0 -59
  384. package/dist/schema/sync-delta-row.js +0 -103
  385. package/dist/schema/sync-delta-wire.js +0 -102
  386. package/dist/server/read-config.d.ts +0 -67
  387. package/dist/server/read-config.js +0 -8
  388. package/dist/server/storage-mode.d.ts +0 -8
  389. package/dist/server/storage-mode.js +0 -28
  390. package/dist/source/connector-protocol.d.ts +0 -159
  391. package/dist/source/connector-protocol.js +0 -161
  392. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  393. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  394. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  395. package/dist/transactions/mutation-error-handler.js +0 -39
  396. package/dist/transactions/optimistic.d.ts +0 -24
  397. package/dist/transactions/optimistic.js +0 -45
  398. package/dist/utils/mobx-setup.d.ts +0 -42
package/dist/surface.d.ts CHANGED
@@ -1,28 +1,35 @@
1
1
  /**
2
- * Machine-checked public API-surface manifest the SDK owns the description of
3
- * its OWN surface, bound to the real exported types at COMPILE TIME so the MCP
4
- * `get_api_surface` / docs can never drift from reality.
2
+ * A machine-checked manifest of this package's public API surface. It lists,
3
+ * as string tuples, the method names on `ablo.<model>`, the keys accepted by
4
+ * the list-style read options, and the keys of the client constructor
5
+ * options. Each tuple is proven equal to the keys of its source interface at
6
+ * compile time, so the lists cannot drift from the real types: add or remove
7
+ * a name on either side without updating the matching tuple and this file
8
+ * fails to compile, in both directions — no name the interface lacks, and no
9
+ * interface key the tuple omits.
5
10
  *
6
- * This exists because the hand-authored surface (apps/sync-web/.../api-surface.ts)
7
- * once named `load` / `count` / `scope` verbs/options that don't exist — with no
8
- * coupling to the code. The fix: the name lists live HERE, next to the types, and
9
- * each is proven EXACTLY equal to the keys of its source interface via
10
- * `Expect<Equal<…>>`. Add or remove a verb/option without updating the matching
11
- * tuple and THIS FILE FAILS TO COMPILE (the `Equal` constraint is checked eagerly
12
- * at the alias declaration both directions: no phantom name, no missing name).
13
- *
14
- * Consumers (the MCP `get_api_surface`) import these NAME tuples and build their
15
- * prose from them, so a summary can never reference a verb that doesn't exist.
16
- * NAMES are guaranteed; descriptions stay hand-written (prose can't be type-checked).
11
+ * Documentation tooling reads these tuples to describe the surface, which
12
+ * guarantees a generated summary never names a method or option that does
13
+ * not exist. The names are verified here; their prose descriptions are
14
+ * written by hand, since prose cannot be type-checked.
15
+ */
16
+ /**
17
+ * The names of every method available on `ablo.<model>`, matching the keys of
18
+ * the {@link ModelOperations} interface. Documentation tooling reads this
19
+ * tuple, so it is the one list of model-verb names a generated summary can
20
+ * describe.
17
21
  */
18
- /** Every method on `ablo.<model>` (the stateful `ModelOperations`). The single
19
- * source of truth for the model-verb names the docs/MCP may describe. */
20
22
  export declare const PUBLIC_MODEL_VERBS: readonly ["retrieve", "list", "get", "getAll", "getCount", "create", "update", "delete", "claim", "watch", "onChange"];
21
- /** Keys accepted by `list`/`getAll`/`onChange` options (`LocalReadOptions`).
22
- * Note `state` (lifecycle filter) NOT `scope` (a historic doc drift). */
23
+ /**
24
+ * The option keys accepted by `list`, `getAll`, and `onChange`, matching the
25
+ * keys of {@link LocalReadOptions}. Note that the lifecycle filter is named
26
+ * `state`, not `scope`.
27
+ */
23
28
  export declare const PUBLIC_LIST_OPTION_KEYS: readonly ["where", "filter", "orderBy", "limit", "offset", "state"];
24
- /** Public keys of `AbloOptions`. `schema` is required; the rest are optional
25
- * (the locked happy path is `Ablo({ schema, apiKey, databaseUrl, transport })`). */
29
+ /**
30
+ * The keys of the client constructor options, {@link AbloOptions}. Only
31
+ * `schema` is required; every other key is optional.
32
+ */
26
33
  export declare const PUBLIC_ABLO_OPTION_KEYS: readonly ["schema", "apiKey", "authEndpoint", "databaseUrl", "persistence", "transport", "debug", "logLevel", "authToken", "baseURL", "fetch", "defaultHeaders", "defaultQuery", "dangerouslyAllowBrowser"];
27
34
  export type ModelVerb = (typeof PUBLIC_MODEL_VERBS)[number];
28
35
  export type ListOptionKey = (typeof PUBLIC_LIST_OPTION_KEYS)[number];
package/dist/surface.js CHANGED
@@ -1,23 +1,25 @@
1
1
  /**
2
- * Machine-checked public API-surface manifest the SDK owns the description of
3
- * its OWN surface, bound to the real exported types at COMPILE TIME so the MCP
4
- * `get_api_surface` / docs can never drift from reality.
2
+ * A machine-checked manifest of this package's public API surface. It lists,
3
+ * as string tuples, the method names on `ablo.<model>`, the keys accepted by
4
+ * the list-style read options, and the keys of the client constructor
5
+ * options. Each tuple is proven equal to the keys of its source interface at
6
+ * compile time, so the lists cannot drift from the real types: add or remove
7
+ * a name on either side without updating the matching tuple and this file
8
+ * fails to compile, in both directions — no name the interface lacks, and no
9
+ * interface key the tuple omits.
5
10
  *
6
- * This exists because the hand-authored surface (apps/sync-web/.../api-surface.ts)
7
- * once named `load` / `count` / `scope` verbs/options that don't exist — with no
8
- * coupling to the code. The fix: the name lists live HERE, next to the types, and
9
- * each is proven EXACTLY equal to the keys of its source interface via
10
- * `Expect<Equal<…>>`. Add or remove a verb/option without updating the matching
11
- * tuple and THIS FILE FAILS TO COMPILE (the `Equal` constraint is checked eagerly
12
- * at the alias declaration — both directions: no phantom name, no missing name).
13
- *
14
- * Consumers (the MCP `get_api_surface`) import these NAME tuples and build their
15
- * prose from them, so a summary can never reference a verb that doesn't exist.
16
- * NAMES are guaranteed; descriptions stay hand-written (prose can't be type-checked).
11
+ * Documentation tooling reads these tuples to describe the surface, which
12
+ * guarantees a generated summary never names a method or option that does
13
+ * not exist. The names are verified here; their prose descriptions are
14
+ * written by hand, since prose cannot be type-checked.
17
15
  */
18
16
  // ── the per-`ablo.<model>` verb surface ────────────────────────────────────
19
- /** Every method on `ablo.<model>` (the stateful `ModelOperations`). The single
20
- * source of truth for the model-verb names the docs/MCP may describe. */
17
+ /**
18
+ * The names of every method available on `ablo.<model>`, matching the keys of
19
+ * the {@link ModelOperations} interface. Documentation tooling reads this
20
+ * tuple, so it is the one list of model-verb names a generated summary can
21
+ * describe.
22
+ */
21
23
  export const PUBLIC_MODEL_VERBS = [
22
24
  'retrieve',
23
25
  'list',
@@ -32,8 +34,11 @@ export const PUBLIC_MODEL_VERBS = [
32
34
  'onChange',
33
35
  ];
34
36
  // ── the read/list query option surface ─────────────────────────────────────
35
- /** Keys accepted by `list`/`getAll`/`onChange` options (`LocalReadOptions`).
36
- * Note `state` (lifecycle filter) NOT `scope` (a historic doc drift). */
37
+ /**
38
+ * The option keys accepted by `list`, `getAll`, and `onChange`, matching the
39
+ * keys of {@link LocalReadOptions}. Note that the lifecycle filter is named
40
+ * `state`, not `scope`.
41
+ */
37
42
  export const PUBLIC_LIST_OPTION_KEYS = [
38
43
  'where',
39
44
  'filter',
@@ -43,8 +48,10 @@ export const PUBLIC_LIST_OPTION_KEYS = [
43
48
  'state',
44
49
  ];
45
50
  // ── the `Ablo({ … })` constructor option surface ───────────────────────────
46
- /** Public keys of `AbloOptions`. `schema` is required; the rest are optional
47
- * (the locked happy path is `Ablo({ schema, apiKey, databaseUrl, transport })`). */
51
+ /**
52
+ * The keys of the client constructor options, {@link AbloOptions}. Only
53
+ * `schema` is required; every other key is optional.
54
+ */
48
55
  export const PUBLIC_ABLO_OPTION_KEYS = [
49
56
  'schema',
50
57
  'apiKey',
@@ -1,25 +1,30 @@
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
  export interface BootstrapData {
6
10
  type: 'full' | 'partial';
7
11
  lastSyncId: number;
8
12
  /**
9
- * Model rows keyed by typename. Each row is opaque to the SDK at this
10
- * boundary the per-model shape is asserted by the consumer (sync
11
- * engine reduce + IDB write) using the registered schema.
13
+ * Model rows keyed by type name. Each row is opaque at this boundary; the
14
+ * engine asserts the per-model shape against the registered schema when it
15
+ * reduces the rows and writes them to local storage.
12
16
  */
13
17
  models?: Record<string, unknown[]>;
14
18
  deltas?: ValidatedServerDelta[];
15
19
  deltaCount?: number;
16
- /** Model types whose server-side query failed (timeout, RLS error, etc.) */
20
+ /** Model types whose server-side query failed (timeout, RLS error, and the like). */
17
21
  failedModels?: string[];
18
22
  timestamp: number;
19
23
  /**
20
- * The server's ACTIVE schema content hash for this tenant (same `schemaHash`
21
- * the CLI push computes). Present once the tenant has pushed a schema; the
22
- * client compares it to its own `config.expectedSchemaHash` to warn on drift.
24
+ * The content hash of the schema the server currently has active for this
25
+ * tenant — the same hash `ablo push` computes. Present once a schema has been
26
+ * pushed. The client compares it against its own `expectedSchemaHash` to warn
27
+ * when the app's schema and the deployed schema have drifted apart.
23
28
  */
24
29
  schemaHash?: string;
25
30
  }
@@ -40,40 +45,38 @@ export interface BootstrapOptions {
40
45
  */
41
46
  baseUrl?: string;
42
47
  /**
43
- * Private cache namespace for offline bootstrap fallback. Hosted SDK
44
- * callers do not pass this; Ablo sets it after auth resolves the
45
- * account scope.
48
+ * Namespace for the offline bootstrap cache. Most callers leave this unset;
49
+ * the SDK fills it in once authentication has resolved the account scope, so
50
+ * the fallback cache is partitioned per account.
46
51
  */
47
52
  cacheScope?: string | null;
48
53
  /**
49
- * @deprecated Use `cacheScope`. Kept so older self-hosted code that
50
- * still constructs BootstrapHelper directly keeps its cache namespace.
54
+ * @deprecated Use `cacheScope`. Retained so code that constructs
55
+ * {@link BootstrapFetcher} directly keeps its cache namespace.
51
56
  */
52
57
  organizationId?: string;
53
58
  syncGroups?: string[];
54
59
  maxRetries?: number;
55
60
  retryDelay?: number;
56
- /** Timeout for individual fetch requests in ms (default: 30000) */
61
+ /** How long to wait for a single fetch before timing out, in milliseconds. Default 10000 (10 seconds). */
57
62
  fetchTimeout?: number;
58
63
  /**
59
- * Model names to request in bootstrap. When set, the server only returns
60
- * these models everything else is skipped. Derived from the schema's
61
- * `load` strategy: only models with `load: 'instant'` (or unset, which
62
- * defaults to instant) are included.
63
- *
64
- * When absent, the server returns all models (backward compatible with
65
- * old clients that don't send a models param).
64
+ * The model names to request. When set, the server returns only these models
65
+ * and skips the rest. This is derived from each model's `load` strategy: only
66
+ * models loaded instantly (the default) are included. When unset, the server
67
+ * returns every model.
66
68
  */
67
69
  instantModels?: string[];
68
70
  /**
69
- * Shared SDK credential getter. Preferred over `setAuthToken`; read at
70
- * request time so token refreshes apply without recreating BootstrapHelper.
71
+ * Getter for the current credential, read at request time so a refreshed
72
+ * token takes effect without recreating the helper. Preferred over
73
+ * {@link BootstrapFetcher.setAuthToken}.
71
74
  */
72
75
  getAuthToken?: AuthTokenGetter;
73
76
  }
74
77
  import { type AuthTokenGetter } from '../auth/credentialSource.js';
75
78
  import { type ValidatedServerDelta } from './schemas.js';
76
- export declare class BootstrapHelper {
79
+ export declare class BootstrapFetcher {
77
80
  private options;
78
81
  private abortController;
79
82
  /** Warn about schema drift at most once per helper. */
@@ -95,20 +98,10 @@ export declare class BootstrapHelper {
95
98
  setCacheScope(cacheScope: string): void;
96
99
  setSyncGroups(syncGroups: readonly string[] | undefined): void;
97
100
  /**
98
- * Compatibility setter for direct BootstrapHelper users. The SDK-owned
99
- * `Ablo()` path passes `getAuthToken` and does not mutate this helper.
101
+ * Sets a fixed credential for callers that construct the helper directly.
102
+ * The SDK instead supplies `getAuthToken` and never calls this.
100
103
  */
101
104
  setAuthToken(authToken: string | undefined): void;
102
- /**
103
- * Create a promise that rejects after a timeout
104
- * Used to race against fetch requests that may hang indefinitely
105
- */
106
- private createTimeoutPromise;
107
- /**
108
- * Wrap a promise with a timeout - if the promise doesn't resolve within
109
- * the timeout period, the AbortController is triggered and an error is thrown
110
- */
111
- private withTimeout;
112
105
  /**
113
106
  * Fetch bootstrap data from sync engine with partial bootstrap support
114
107
  * @param lastSyncId - Optional: client's current lastSyncId for partial bootstrap
@@ -116,11 +109,12 @@ export declare class BootstrapHelper {
116
109
  */
117
110
  fetchBootstrap(lastSyncId?: number,
118
111
  /**
119
- * Per-call sync-group override for SCOPED hydrate-on-enter. When provided,
120
- * the request uses THESE groups instead of `this.options.syncGroups`,
121
- * WITHOUT mutating the shared options (so a concurrent full bootstrap is
122
- * unaffected). Also bypasses the offline full-snapshot cache below, which
123
- * holds the connection's full bootstrap and would be wrong for a subset.
112
+ * A per-call set of sync groups for a scoped hydrate-on-enter. When given,
113
+ * the request uses these groups instead of the configured `syncGroups`, and
114
+ * does so without mutating the shared options, so a concurrent full
115
+ * bootstrap is unaffected. It also bypasses the offline snapshot cache,
116
+ * which holds the full bootstrap and would be a wrong answer to a subset
117
+ * request.
124
118
  */
125
119
  syncGroupsOverride?: readonly string[]): Promise<BootstrapData>;
126
120
  /**
@@ -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
  }