@abloatai/ablo 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +104 -88
  4. package/dist/BaseSyncedStore.d.ts +140 -266
  5. package/dist/BaseSyncedStore.js +338 -739
  6. package/dist/Database.d.ts +62 -77
  7. package/dist/Database.js +106 -127
  8. package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
  9. package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
  10. package/dist/LazyReferenceCollection.d.ts +11 -15
  11. package/dist/LazyReferenceCollection.js +16 -15
  12. package/dist/Model.d.ts +37 -52
  13. package/dist/Model.js +52 -69
  14. package/dist/ModelRegistry.d.ts +46 -25
  15. package/dist/ModelRegistry.js +32 -30
  16. package/dist/NetworkMonitor.d.ts +5 -6
  17. package/dist/NetworkMonitor.js +6 -7
  18. package/dist/SyncClient.d.ts +119 -109
  19. package/dist/SyncClient.js +303 -224
  20. package/dist/SyncEngineContext.d.ts +1 -3
  21. package/dist/SyncEngineContext.js +1 -2
  22. package/dist/adapters/alwaysOnline.d.ts +6 -8
  23. package/dist/adapters/alwaysOnline.js +6 -8
  24. package/dist/adapters/inMemoryStorage.d.ts +9 -9
  25. package/dist/adapters/inMemoryStorage.js +9 -9
  26. package/dist/agent/Agent.d.ts +39 -31
  27. package/dist/agent/Agent.js +35 -23
  28. package/dist/agent/index.d.ts +4 -4
  29. package/dist/agent/index.js +5 -5
  30. package/dist/agent/session.d.ts +47 -44
  31. package/dist/agent/session.js +37 -48
  32. package/dist/agent/types.d.ts +26 -31
  33. package/dist/agent/types.js +6 -7
  34. package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
  35. package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
  36. package/dist/ai-sdk/coordinationContext.d.ts +46 -0
  37. package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +30 -31
  38. package/dist/ai-sdk/index.d.ts +25 -22
  39. package/dist/ai-sdk/index.js +25 -22
  40. package/dist/ai-sdk/wrap.d.ts +7 -8
  41. package/dist/ai-sdk/wrap.js +2 -2
  42. package/dist/auth/credentialPolicy.d.ts +74 -71
  43. package/dist/auth/credentialPolicy.js +51 -56
  44. package/dist/auth/credentialSource.d.ts +7 -18
  45. package/dist/auth/credentialSource.js +10 -18
  46. package/dist/auth/index.d.ts +59 -58
  47. package/dist/auth/index.js +34 -40
  48. package/dist/auth/schemas.d.ts +5 -4
  49. package/dist/auth/schemas.js +5 -4
  50. package/dist/batching/index.d.ts +19 -21
  51. package/dist/batching/index.js +14 -17
  52. package/dist/cli.cjs +483 -369
  53. package/dist/client/Ablo.d.ts +107 -836
  54. package/dist/client/Ablo.js +174 -833
  55. package/dist/client/ApiClient.d.ts +44 -20
  56. package/dist/client/ApiClient.js +193 -44
  57. package/dist/client/auth.d.ts +51 -60
  58. package/dist/client/auth.js +137 -110
  59. package/dist/client/claimHeartbeatLoop.d.ts +50 -0
  60. package/dist/client/claimHeartbeatLoop.js +88 -0
  61. package/dist/client/consoleLogger.d.ts +35 -0
  62. package/dist/client/consoleLogger.js +44 -0
  63. package/dist/client/createInternalComponents.d.ts +14 -17
  64. package/dist/client/createInternalComponents.js +26 -31
  65. package/dist/client/createModelProxy.d.ts +130 -120
  66. package/dist/client/createModelProxy.js +158 -124
  67. package/dist/client/credentialEndpoint.d.ts +61 -0
  68. package/dist/client/credentialEndpoint.js +86 -0
  69. package/dist/client/functionalUpdate.d.ts +29 -27
  70. package/dist/client/functionalUpdate.js +21 -21
  71. package/dist/client/hostedEndpoints.d.ts +21 -0
  72. package/dist/client/hostedEndpoints.js +21 -0
  73. package/dist/client/httpClient.d.ts +58 -54
  74. package/dist/client/httpClient.js +29 -31
  75. package/dist/client/identity.d.ts +15 -20
  76. package/dist/client/identity.js +49 -59
  77. package/dist/client/modelRegistration.d.ts +10 -0
  78. package/dist/client/modelRegistration.js +301 -0
  79. package/dist/client/options.d.ts +373 -0
  80. package/dist/client/options.js +6 -0
  81. package/dist/client/registerDataSource.d.ts +9 -9
  82. package/dist/client/registerDataSource.js +15 -16
  83. package/dist/client/resourceTypes.d.ts +333 -0
  84. package/dist/client/resourceTypes.js +7 -0
  85. package/dist/client/schemaConfig.d.ts +44 -0
  86. package/dist/client/schemaConfig.js +176 -0
  87. package/dist/client/sessionMint.d.ts +17 -13
  88. package/dist/client/sessionMint.js +26 -31
  89. package/dist/client/validateAbloOptions.d.ts +12 -14
  90. package/dist/client/validateAbloOptions.js +9 -10
  91. package/dist/client/writeOptionsSchema.d.ts +18 -16
  92. package/dist/client/writeOptionsSchema.js +23 -20
  93. package/dist/client/wsMutationExecutor.d.ts +28 -0
  94. package/dist/client/wsMutationExecutor.js +71 -0
  95. package/dist/context.d.ts +6 -4
  96. package/dist/context.js +6 -7
  97. package/dist/coordination/index.d.ts +13 -4
  98. package/dist/coordination/index.js +29 -4
  99. package/dist/coordination/schema.d.ts +176 -128
  100. package/dist/coordination/schema.js +197 -133
  101. package/dist/coordination/trace.d.ts +9 -11
  102. package/dist/coordination/trace.js +13 -15
  103. package/dist/core/DatabaseManager.d.ts +5 -8
  104. package/dist/core/DatabaseManager.js +38 -40
  105. package/dist/core/QueryProcessor.d.ts +7 -9
  106. package/dist/core/QueryProcessor.js +27 -34
  107. package/dist/core/QueryView.d.ts +17 -5
  108. package/dist/core/QueryView.js +6 -7
  109. package/dist/core/StoreManager.d.ts +14 -16
  110. package/dist/core/StoreManager.js +26 -25
  111. package/dist/core/ViewRegistry.d.ts +5 -5
  112. package/dist/core/ViewRegistry.js +4 -4
  113. package/dist/core/index.d.ts +18 -13
  114. package/dist/core/index.js +32 -26
  115. package/dist/core/openIDBWithTimeout.d.ts +38 -36
  116. package/dist/core/openIDBWithTimeout.js +57 -54
  117. package/dist/core/queryUtils.d.ts +45 -0
  118. package/dist/core/queryUtils.js +69 -0
  119. package/dist/core/storeContract.d.ts +145 -0
  120. package/dist/core/storeContract.js +12 -0
  121. package/dist/environment.d.ts +28 -0
  122. package/dist/environment.js +21 -0
  123. package/dist/errorCodes.d.ts +118 -101
  124. package/dist/errorCodes.js +277 -260
  125. package/dist/errors.d.ts +170 -165
  126. package/dist/errors.js +161 -151
  127. package/dist/index.d.ts +30 -27
  128. package/dist/index.js +90 -82
  129. package/dist/interfaces/index.d.ts +108 -133
  130. package/dist/interfaces/index.js +5 -4
  131. package/dist/keys/index.d.ts +27 -29
  132. package/dist/keys/index.js +59 -49
  133. package/dist/mutators/RecordingTransaction.d.ts +16 -16
  134. package/dist/mutators/RecordingTransaction.js +31 -37
  135. package/dist/mutators/Transaction.d.ts +18 -26
  136. package/dist/mutators/Transaction.js +14 -20
  137. package/dist/mutators/UndoManager.d.ts +122 -131
  138. package/dist/mutators/UndoManager.js +149 -155
  139. package/dist/mutators/defineMutators.d.ts +24 -37
  140. package/dist/mutators/defineMutators.js +14 -20
  141. package/dist/mutators/inverseOp.d.ts +12 -15
  142. package/dist/mutators/inverseOp.js +12 -15
  143. package/dist/mutators/mutateActions.d.ts +10 -9
  144. package/dist/mutators/mutateActions.js +1 -1
  145. package/dist/mutators/readerActions.d.ts +9 -8
  146. package/dist/mutators/readerActions.js +2 -2
  147. package/dist/mutators/undoApply.d.ts +31 -27
  148. package/dist/mutators/undoApply.js +26 -24
  149. package/dist/policy/index.d.ts +5 -3
  150. package/dist/policy/index.js +5 -3
  151. package/dist/policy/types.d.ts +105 -101
  152. package/dist/policy/types.js +67 -66
  153. package/dist/query/client.d.ts +32 -16
  154. package/dist/query/client.js +103 -72
  155. package/dist/query/types.d.ts +37 -60
  156. package/dist/query/types.js +13 -33
  157. package/dist/react/AbloProvider.d.ts +7 -11
  158. package/dist/react/AbloProvider.js +24 -17
  159. package/dist/react/context.d.ts +27 -146
  160. package/dist/react/context.js +9 -10
  161. package/dist/react/index.d.ts +41 -42
  162. package/dist/react/index.js +37 -38
  163. package/dist/react/internalContext.d.ts +17 -19
  164. package/dist/react/useAblo.d.ts +23 -22
  165. package/dist/react/useAblo.js +17 -15
  166. package/dist/react/useCurrentUserId.d.ts +8 -7
  167. package/dist/react/useCurrentUserId.js +8 -7
  168. package/dist/react/useErrorListener.d.ts +7 -7
  169. package/dist/react/useErrorListener.js +11 -12
  170. package/dist/react/useMutationFailureListener.d.ts +8 -8
  171. package/dist/react/useMutationFailureListener.js +9 -9
  172. package/dist/react/useMutators.d.ts +11 -11
  173. package/dist/react/useMutators.js +10 -4
  174. package/dist/react/useReactive.js +2 -3
  175. package/dist/react/useSyncStatus.d.ts +4 -6
  176. package/dist/react/useUndoScope.d.ts +7 -9
  177. package/dist/react/useUndoScope.js +3 -3
  178. package/dist/schema/coordination.d.ts +21 -25
  179. package/dist/schema/coordination.js +21 -25
  180. package/dist/schema/ddl.d.ts +43 -39
  181. package/dist/schema/ddl.js +75 -68
  182. package/dist/schema/ddlLock.d.ts +35 -0
  183. package/dist/schema/ddlLock.js +46 -0
  184. package/dist/schema/diff.d.ts +99 -61
  185. package/dist/schema/diff.js +43 -34
  186. package/dist/schema/field.d.ts +37 -42
  187. package/dist/schema/field.js +36 -49
  188. package/dist/schema/generate.d.ts +12 -12
  189. package/dist/schema/generate.js +12 -12
  190. package/dist/schema/index.d.ts +5 -4
  191. package/dist/schema/index.js +29 -21
  192. package/dist/schema/model.d.ts +121 -146
  193. package/dist/schema/model.js +24 -35
  194. package/dist/schema/openapi.d.ts +10 -9
  195. package/dist/schema/openapi.js +7 -1
  196. package/dist/schema/queries.d.ts +30 -32
  197. package/dist/schema/queries.js +24 -25
  198. package/dist/schema/relation.d.ts +89 -99
  199. package/dist/schema/relation.js +13 -13
  200. package/dist/schema/residency.d.ts +38 -0
  201. package/dist/schema/residency.js +30 -0
  202. package/dist/schema/roles.d.ts +45 -27
  203. package/dist/schema/roles.js +52 -21
  204. package/dist/schema/schema.d.ts +36 -45
  205. package/dist/schema/schema.js +42 -39
  206. package/dist/schema/select.d.ts +13 -13
  207. package/dist/schema/select.js +13 -13
  208. package/dist/schema/serialize.d.ts +36 -39
  209. package/dist/schema/serialize.js +27 -31
  210. package/dist/schema/sugar.d.ts +17 -32
  211. package/dist/schema/sugar.js +14 -29
  212. package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +27 -50
  213. package/dist/schema/syncDeltaRow.js +89 -0
  214. package/dist/schema/tenancy.d.ts +44 -46
  215. package/dist/schema/tenancy.js +46 -48
  216. package/dist/server/adapter.d.ts +58 -58
  217. package/dist/server/adapter.js +13 -14
  218. package/dist/server/commit.d.ts +60 -64
  219. package/dist/server/index.d.ts +9 -10
  220. package/dist/server/index.js +1 -1
  221. package/dist/server/readConfig.d.ts +70 -0
  222. package/dist/server/readConfig.js +8 -0
  223. package/dist/server/storageMode.d.ts +23 -0
  224. package/dist/server/storageMode.js +17 -0
  225. package/dist/source/adapter.d.ts +31 -26
  226. package/dist/source/adapter.js +10 -10
  227. package/dist/source/adapters/drizzle.d.ts +28 -23
  228. package/dist/source/adapters/drizzle.js +34 -28
  229. package/dist/source/adapters/kysely.d.ts +27 -25
  230. package/dist/source/adapters/kysely.js +28 -26
  231. package/dist/source/adapters/memory.d.ts +8 -7
  232. package/dist/source/adapters/memory.js +10 -9
  233. package/dist/source/adapters/prisma.d.ts +13 -12
  234. package/dist/source/adapters/prisma.js +27 -29
  235. package/dist/source/conformance.d.ts +18 -11
  236. package/dist/source/conformance.js +27 -19
  237. package/dist/source/connector.d.ts +31 -32
  238. package/dist/source/connector.js +30 -28
  239. package/dist/source/connectorProtocol.d.ts +160 -0
  240. package/dist/source/connectorProtocol.js +162 -0
  241. package/dist/source/contract.d.ts +26 -27
  242. package/dist/source/contract.js +28 -29
  243. package/dist/source/factory.d.ts +94 -0
  244. package/dist/source/factory.js +268 -0
  245. package/dist/source/index.d.ts +10 -462
  246. package/dist/source/index.js +17 -421
  247. package/dist/source/migrations.d.ts +9 -9
  248. package/dist/source/migrations.js +9 -9
  249. package/dist/source/next.d.ts +10 -11
  250. package/dist/source/next.js +7 -8
  251. package/dist/source/pushQueue.d.ts +70 -48
  252. package/dist/source/pushQueue.js +36 -29
  253. package/dist/source/signing.d.ts +88 -0
  254. package/dist/source/signing.js +159 -0
  255. package/dist/source/types.d.ts +351 -0
  256. package/dist/source/types.js +43 -0
  257. package/dist/stores/ObjectStore.d.ts +11 -12
  258. package/dist/stores/ObjectStore.js +34 -35
  259. package/dist/stores/ObjectStoreContract.d.ts +12 -15
  260. package/dist/stores/SyncActionStore.d.ts +8 -12
  261. package/dist/stores/SyncActionStore.js +77 -46
  262. package/dist/surface.d.ts +28 -21
  263. package/dist/surface.js +28 -20
  264. package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
  265. package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
  266. package/dist/sync/ConnectionManager.d.ts +47 -50
  267. package/dist/sync/ConnectionManager.js +74 -70
  268. package/dist/sync/NetworkProbe.d.ts +27 -31
  269. package/dist/sync/NetworkProbe.js +67 -72
  270. package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
  271. package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
  272. package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
  273. package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
  274. package/dist/sync/SyncWebSocket.d.ts +175 -250
  275. package/dist/sync/SyncWebSocket.js +431 -769
  276. package/dist/sync/awaitClaimGrant.d.ts +18 -18
  277. package/dist/sync/awaitClaimGrant.js +38 -30
  278. package/dist/sync/bootstrapApply.d.ts +70 -0
  279. package/dist/sync/bootstrapApply.js +73 -0
  280. package/dist/sync/commitFrames.d.ts +44 -0
  281. package/dist/sync/commitFrames.js +94 -0
  282. package/dist/sync/createClaimStream.d.ts +23 -22
  283. package/dist/sync/createClaimStream.js +108 -25
  284. package/dist/sync/createPresenceStream.d.ts +19 -18
  285. package/dist/sync/createPresenceStream.js +25 -26
  286. package/dist/sync/createSnapshot.d.ts +13 -17
  287. package/dist/sync/createSnapshot.js +20 -26
  288. package/dist/sync/credentialLifecycle.d.ts +175 -0
  289. package/dist/sync/credentialLifecycle.js +322 -0
  290. package/dist/sync/deltaPipeline.d.ts +113 -0
  291. package/dist/sync/deltaPipeline.js +261 -0
  292. package/dist/sync/groupChange.d.ts +113 -0
  293. package/dist/sync/groupChange.js +242 -0
  294. package/dist/sync/heartbeat.d.ts +63 -0
  295. package/dist/sync/heartbeat.js +91 -0
  296. package/dist/sync/participants.d.ts +27 -27
  297. package/dist/sync/schemas.d.ts +3 -2
  298. package/dist/sync/schemas.js +14 -10
  299. package/dist/sync/syncCursor.d.ts +40 -0
  300. package/dist/sync/syncCursor.js +55 -0
  301. package/dist/sync/syncPlan.d.ts +54 -0
  302. package/dist/sync/syncPlan.js +50 -0
  303. package/dist/sync/syncPosition.d.ts +54 -49
  304. package/dist/sync/syncPosition.js +57 -52
  305. package/dist/sync/wsFrameHandlers.d.ts +116 -0
  306. package/dist/sync/wsFrameHandlers.js +374 -0
  307. package/dist/testing/fixtures/bootstrap.d.ts +21 -17
  308. package/dist/testing/fixtures/bootstrap.js +12 -6
  309. package/dist/testing/fixtures/deltas.d.ts +31 -34
  310. package/dist/testing/fixtures/deltas.js +30 -33
  311. package/dist/testing/fixtures/models.d.ts +11 -10
  312. package/dist/testing/fixtures/models.js +12 -10
  313. package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
  314. package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
  315. package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -18
  316. package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
  317. package/dist/testing/helpers/wait.d.ts +13 -8
  318. package/dist/testing/helpers/wait.js +13 -8
  319. package/dist/testing/index.d.ts +4 -4
  320. package/dist/testing/index.js +3 -3
  321. package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
  322. package/dist/testing/mocks/MockMutationExecutor.js +15 -14
  323. package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
  324. package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
  325. package/dist/testing/mocks/MockSyncContext.d.ts +21 -34
  326. package/dist/testing/mocks/MockSyncContext.js +16 -45
  327. package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
  328. package/dist/testing/mocks/MockSyncStore.js +11 -11
  329. package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
  330. package/dist/testing/mocks/MockWebSocket.js +22 -21
  331. package/dist/transactions/TransactionQueue.d.ts +190 -221
  332. package/dist/transactions/TransactionQueue.js +424 -822
  333. package/dist/transactions/TransactionStore.d.ts +20 -0
  334. package/dist/transactions/TransactionStore.js +53 -0
  335. package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
  336. package/dist/transactions/UnconfirmedWrites.js +104 -0
  337. package/dist/transactions/coalesceRules.d.ts +58 -0
  338. package/dist/transactions/coalesceRules.js +140 -0
  339. package/dist/transactions/commitPayload.d.ts +130 -0
  340. package/dist/transactions/commitPayload.js +143 -0
  341. package/dist/transactions/deltaConfirmation.d.ts +58 -0
  342. package/dist/transactions/deltaConfirmation.js +215 -0
  343. package/dist/transactions/optimisticApply.d.ts +49 -0
  344. package/dist/transactions/optimisticApply.js +65 -0
  345. package/dist/transactions/replayValidation.d.ts +99 -0
  346. package/dist/transactions/replayValidation.js +111 -0
  347. package/dist/types/global.d.ts +46 -41
  348. package/dist/types/global.js +20 -19
  349. package/dist/types/index.d.ts +74 -80
  350. package/dist/types/index.js +22 -27
  351. package/dist/types/modelData.d.ts +10 -0
  352. package/dist/types/modelData.js +9 -0
  353. package/dist/types/participant.d.ts +20 -0
  354. package/dist/types/participant.js +10 -0
  355. package/dist/types/streams.d.ts +216 -209
  356. package/dist/types/streams.js +7 -7
  357. package/dist/utils/asyncIterator.d.ts +25 -32
  358. package/dist/utils/asyncIterator.js +25 -32
  359. package/dist/utils/duration.d.ts +12 -15
  360. package/dist/utils/duration.js +12 -15
  361. package/dist/utils/mobxSetup.d.ts +53 -0
  362. package/dist/utils/{mobx-setup.js → mobxSetup.js} +44 -100
  363. package/dist/webhooks/events.d.ts +21 -16
  364. package/dist/webhooks/events.js +10 -8
  365. package/dist/webhooks/index.d.ts +5 -7
  366. package/dist/webhooks/index.js +5 -7
  367. package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
  368. package/dist/wire/delta.js +114 -0
  369. package/dist/wire/errorEnvelope.d.ts +35 -27
  370. package/dist/wire/errorEnvelope.js +38 -32
  371. package/dist/wire/frames.d.ts +150 -67
  372. package/dist/wire/frames.js +48 -1
  373. package/dist/wire/index.d.ts +18 -13
  374. package/dist/wire/index.js +36 -13
  375. package/dist/wire/listEnvelope.d.ts +16 -23
  376. package/dist/wire/listEnvelope.js +7 -6
  377. package/dist/wire/protocol.d.ts +38 -0
  378. package/dist/wire/protocol.js +38 -0
  379. package/dist/wire/protocolVersion.d.ts +60 -0
  380. package/dist/wire/protocolVersion.js +67 -0
  381. package/docs/api-keys.md +4 -3
  382. package/docs/coordination.md +59 -0
  383. package/docs/examples/existing-python-backend.md +3 -3
  384. package/docs/identity.md +4 -4
  385. package/docs/integration-guide.md +1 -1
  386. package/docs/react.md +1 -1
  387. package/docs/sessions.md +5 -7
  388. package/package.json +24 -21
  389. package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
  390. package/dist/ai-sdk/coordination-context.d.ts +0 -52
  391. package/dist/client/index.d.ts +0 -36
  392. package/dist/client/index.js +0 -33
  393. package/dist/config/index.d.ts +0 -10
  394. package/dist/config/index.js +0 -12
  395. package/dist/core/query-utils.d.ts +0 -34
  396. package/dist/core/query-utils.js +0 -59
  397. package/dist/interfaces/headless.d.ts +0 -95
  398. package/dist/interfaces/headless.js +0 -41
  399. package/dist/query/index.d.ts +0 -6
  400. package/dist/query/index.js +0 -5
  401. package/dist/realtime/index.d.ts +0 -10
  402. package/dist/realtime/index.js +0 -9
  403. package/dist/schema/plane.d.ts +0 -23
  404. package/dist/schema/plane.js +0 -19
  405. package/dist/schema/sync-delta-row.js +0 -103
  406. package/dist/schema/sync-delta-wire.js +0 -102
  407. package/dist/server/next.d.ts +0 -51
  408. package/dist/server/next.js +0 -47
  409. package/dist/server/read-config.d.ts +0 -67
  410. package/dist/server/read-config.js +0 -8
  411. package/dist/server/storage-mode.d.ts +0 -1
  412. package/dist/server/storage-mode.js +0 -18
  413. package/dist/source/connector-protocol.d.ts +0 -159
  414. package/dist/source/connector-protocol.js +0 -161
  415. package/dist/sync/OfflineFlush.d.ts +0 -9
  416. package/dist/sync/OfflineFlush.js +0 -22
  417. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  418. package/dist/sync/OfflineTransactionStore.js +0 -263
  419. package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
  420. package/dist/transactions/OptimisticEchoTracker.js +0 -104
  421. package/dist/transactions/index.d.ts +0 -16
  422. package/dist/transactions/index.js +0 -7
  423. package/dist/transactions/mutation-error-handler.d.ts +0 -5
  424. package/dist/transactions/mutation-error-handler.js +0 -39
  425. package/dist/utils/mobx-setup.d.ts +0 -42
@@ -1,34 +1,42 @@
1
1
  /**
2
- * Transport-driven ClaimStream factory.
2
+ * Creates a {@link ClaimStream} over a live sync connection. A claim is a
3
+ * short-lived, advisory lease a participant takes on an entity (or a field of
4
+ * one) to signal "I'm working on this"; the stream lets you take claims, see
5
+ * everyone else's, and watch the wait queue when a claim is contended.
3
6
  *
4
- * Mirrors `createPresenceStream` built directly on `SyncWebSocket`,
5
- * no SyncAgent wrapper. Claims derive their `others` view from the
6
- * same `presence_update` frames the presence stream consumes (the
7
- * Hub piggybacks `activeClaims` on every presence frame). Outbound
8
- * announce/revoke ride the same socket via `claim_begin` /
7
+ * The stream is built directly on the sync WebSocket and shares that one
8
+ * connection. It learns about other participants' claims from the same
9
+ * `presence_update` frames the {@link createPresenceStream} presence stream
10
+ * consumes — the server piggybacks each participant's `activeClaims` on every
11
+ * presence frame and sends its own claims as `claim_begin` and
9
12
  * `claim_abandon` frames.
10
13
  *
11
- * Wire contract (apps/sync-server/src/hub/types.ts):
12
- * • Outbound: `{ type: 'claim_begin', payload: { claimId,
13
- * entityType, entityId, reason, field?, estimatedMs? } }`
14
- * • Outbound: `{ type: 'claim_abandon', payload: { claimId,
15
- * entityType?, entityId? } }`
16
- * • Inbound (via presence): `event.activeClaims: Claim[]`
17
- * stamped with `declaredAt`, `expiresAt`.
18
- * • Inbound: `claim_rejected` event with conflict metadata.
19
- *
20
- * After the dual-engine collapse (step #36), this is the only
21
- * ClaimStream factory in the SDK; the older compatibility path
22
- * deletes.
14
+ * Wire frames:
15
+ * • Outbound `claim_begin` announce a claim: `{ claimId, entityType,
16
+ * entityId, reason, field?, estimatedMs? }`.
17
+ * • Outbound `claim_abandon` release it: `{ claimId, entityType?,
18
+ * entityId? }`.
19
+ * • Inbound, via presence `event.activeClaims`, each stamped with
20
+ * `declaredAt` and `expiresAt`.
21
+ * • Inbound `claim_rejected` the server refused the claim, with conflict
22
+ * metadata.
23
23
  */
24
24
  import { asyncIteratorFrom } from '../utils/asyncIterator.js';
25
25
  import { toMs } from '../utils/duration.js';
26
- import { descriptionFromMeta, participantKindFromWire, } from '../coordination/schema.js';
26
+ import { claimHeartbeatAckPayloadSchema, descriptionFromMeta, participantKindFromWire, } from '../coordination/schema.js';
27
+ import { AbloClaimedError, AbloConnectionError } from '../errors.js';
28
+ import { resolveHeartbeatOptions } from '../client/claimHeartbeatLoop.js';
27
29
  import { getContext } from '../context.js';
28
30
  /** Readable target for the coordination trace: `documents:abc` / `documents:abc.title`. */
29
31
  function claimLabel(type, id, field) {
30
32
  return field ? `${type}:${id}.${field}` : `${type}:${id}`;
31
33
  }
34
+ /**
35
+ * How long a heartbeat waits for its `claim_heartbeat_ack` before giving up
36
+ * as transient (the auto-heartbeat loop's next tick retries). Comfortably
37
+ * above a round trip, comfortably below the ttl/3 beat cadence.
38
+ */
39
+ const HEARTBEAT_ACK_TIMEOUT_MS = 10_000;
32
40
  export function createClaimStream(config, transport = null) {
33
41
  const { participantId } = config;
34
42
  // ── State: others' open claims, keyed by claimId ───────────────
@@ -49,6 +57,16 @@ export function createClaimStream(config, transport = null) {
49
57
  const listeners = new Set();
50
58
  const rejectionListeners = new Set();
51
59
  const lostListeners = new Set();
60
+ // ── State: in-flight heartbeats awaiting their ack, keyed by claimId ──
61
+ const pendingHeartbeats = new Map();
62
+ const settleHeartbeat = (claimId, settle) => {
63
+ const pending = pendingHeartbeats.get(claimId);
64
+ if (!pending)
65
+ return;
66
+ pendingHeartbeats.delete(claimId);
67
+ clearTimeout(pending.timer);
68
+ settle(pending);
69
+ };
52
70
  const notifyListeners = () => {
53
71
  claimsSnapshot = Object.freeze(Array.from(activeByClaimId.values()));
54
72
  for (const l of listeners) {
@@ -151,8 +169,9 @@ export function createClaimStream(config, transport = null) {
151
169
  }
152
170
  }
153
171
  }));
154
- // (2a) Server-side LOSS frames — you held it, then lost it (preempted /
155
- // expired). Distinct from a rejection (a claim the server refused).
172
+ // (2a) Server-side loss frames — you held the claim, then lost it
173
+ // (preempted or expired). Distinct from a rejection, which is a claim
174
+ // the server refused.
156
175
  unsubs.push(t.subscribe('claim_lost', (payload) => {
157
176
  const lost = payload;
158
177
  if (!lost.claimId)
@@ -186,11 +205,12 @@ export function createClaimStream(config, transport = null) {
186
205
  queueByEntity.delete(key);
187
206
  else
188
207
  queueByEntity.set(key, Object.freeze([...line]));
189
- // If WE are in this line, trace our position (the "agent queued behind a
208
+ // If we are in this line, trace our position (the "agent queued behind a
190
209
  // claim" moment) — once per position change, so advancing is visible.
191
210
  const ourIndex = line.findIndex((c) => ownClaims.has(c.id));
192
- if (ourIndex >= 0) {
193
- const ourId = line[ourIndex].id;
211
+ const ourClaim = ourIndex >= 0 ? line[ourIndex] : undefined;
212
+ if (ourClaim) {
213
+ const ourId = ourClaim.id;
194
214
  if (lastLoggedQueuePos.get(ourId) !== ourIndex) {
195
215
  lastLoggedQueuePos.set(ourId, ourIndex);
196
216
  getContext().logger.info(`claim: queued for ${claimLabel(p.target.type, p.target.id)} — position ${ourIndex + 1} of ${line.length}, waiting`, { claimId: ourId });
@@ -198,6 +218,30 @@ export function createClaimStream(config, transport = null) {
198
218
  }
199
219
  notifyListeners();
200
220
  }));
221
+ // (2c) Heartbeat replies — correlate back to the awaiting beat by
222
+ // claimId. `held` resolves with the extended expiry; `queued` and
223
+ // `lost` reject with a typed claimed error, because a heartbeat on
224
+ // a handle we thought we held coming back as anything but `held`
225
+ // means the lease is no longer ours.
226
+ unsubs.push(t.subscribe('claim_heartbeat_ack', (payload) => {
227
+ const parsed = claimHeartbeatAckPayloadSchema.safeParse(payload);
228
+ if (!parsed.success)
229
+ return;
230
+ const ack = parsed.data;
231
+ settleHeartbeat(ack.claimId, ({ resolve, reject }) => {
232
+ if (ack.status === 'held' && ack.expiresAt !== undefined) {
233
+ resolve({
234
+ expiresAt: ack.expiresAt,
235
+ ...(ack.queueDepth !== undefined
236
+ ? { queueDepth: ack.queueDepth }
237
+ : {}),
238
+ });
239
+ return;
240
+ }
241
+ const c = ownClaims.get(ack.claimId);
242
+ reject(new AbloClaimedError(`The lease behind ${c ? claimLabel(c.entityType, c.entityId, c.field) : `claim ${ack.claimId}`} is no longer held — it expired or was granted onward while this participant was working. Re-acquire the claim and retry; a write attempted under the old lease is rejected by its \`readAt\` guard.`, { code: 'claim_lost' }));
243
+ });
244
+ }));
201
245
  // (3) On reconnect, re-announce every open self-claim — the
202
246
  // server's claim state is in-memory and is lost across
203
247
  // restarts. Without this, peers would see our claims vanish
@@ -244,6 +288,39 @@ export function createClaimStream(config, transport = null) {
244
288
  },
245
289
  });
246
290
  }
291
+ /**
292
+ * Send one heartbeat and await its ack. Rejects with
293
+ * {@link AbloConnectionError} (transient — the auto-heartbeat loop retries
294
+ * on its next tick) when the socket is down or the ack times out, and with
295
+ * {@link AbloClaimedError} (definitive) when the server answers that the
296
+ * lease is no longer ours.
297
+ */
298
+ function sendHeartbeat(claimId, claim, options) {
299
+ if (!attached?.isConnected()) {
300
+ return Promise.reject(new AbloConnectionError(`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down. The keepalive renews held leases automatically on reconnect; the next beat retries.`));
301
+ }
302
+ return new Promise((resolve, reject) => {
303
+ settleHeartbeat(claimId, ({ reject: rejectPrior }) => {
304
+ rejectPrior(new AbloConnectionError('A newer heartbeat for this claim superseded the one still awaiting its reply.'));
305
+ });
306
+ const timer = setTimeout(() => {
307
+ settleHeartbeat(claimId, ({ reject: rejectTimeout }) => {
308
+ rejectTimeout(new AbloConnectionError(`No reply to the heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} arrived within ${HEARTBEAT_ACK_TIMEOUT_MS / 1000}s. The next beat retries.`));
309
+ });
310
+ }, HEARTBEAT_ACK_TIMEOUT_MS);
311
+ pendingHeartbeats.set(claimId, { resolve, reject, timer });
312
+ attached?.send({
313
+ type: 'claim_heartbeat',
314
+ payload: {
315
+ claimId,
316
+ entityType: claim.entityType,
317
+ entityId: claim.entityId,
318
+ ...(options.ttl !== undefined ? { ttlMs: toMs(options.ttl) } : {}),
319
+ ...(options.details !== undefined ? { details: options.details } : {}),
320
+ },
321
+ });
322
+ });
323
+ }
247
324
  function sendAbandon(claimId, claim) {
248
325
  if (!attached?.isConnected())
249
326
  return;
@@ -280,7 +357,7 @@ export function createClaimStream(config, transport = null) {
280
357
  };
281
358
  ownClaims.set(claimId, claim);
282
359
  sendBegin(claimId, claim);
283
- // Coordination trace (info): the creator can SEE their human/agent claims.
360
+ // Coordination trace (info): the creator can see their human/agent claims.
284
361
  getContext().logger.info(`claim: requesting ${claimLabel(claim.entityType, claim.entityId, claim.field)} for "${claim.reason}"` +
285
362
  (claim.queue ? ' (will queue if contended)' : ''), { claimId });
286
363
  let revoked = false;
@@ -309,6 +386,7 @@ export function createClaimStream(config, transport = null) {
309
386
  revoke();
310
387
  },
311
388
  revoke,
389
+ heartbeat: (options) => sendHeartbeat(claimId, claim, resolveHeartbeatOptions(options)),
312
390
  [Symbol.asyncDispose]: async () => {
313
391
  revoke();
314
392
  },
@@ -376,6 +454,11 @@ export function createClaimStream(config, transport = null) {
376
454
  for (const off of unsubs)
377
455
  off();
378
456
  unsubs.length = 0;
457
+ for (const claimId of [...pendingHeartbeats.keys()]) {
458
+ settleHeartbeat(claimId, ({ reject }) => {
459
+ reject(new AbloConnectionError('The claim stream was disposed while this heartbeat was awaiting its reply.'));
460
+ });
461
+ }
379
462
  listeners.clear();
380
463
  rejectionListeners.clear();
381
464
  lostListeners.clear();
@@ -1,25 +1,26 @@
1
1
  /**
2
- * Transport-driven PresenceStream factory.
2
+ * Creates a {@link PresenceStream} over a live sync connection. Presence is the
3
+ * lightweight, ephemeral "who's here and what are they doing" view: each
4
+ * participant broadcasts a status and an activity, and sees everyone else's.
5
+ * The stream is built directly on the sync WebSocket and adds no second
6
+ * connection. It is the sibling of {@link createClaimStream}, which reuses the
7
+ * same presence frames.
3
8
  *
4
- * This is the engine's home for presence — built directly on
5
- * `SyncWebSocket`, no SyncAgent wrapper, no second connection. The
6
- * older compatibility path predates this and will be deleted when
7
- * the dual-engine collapse completes.
9
+ * There are two ways to construct it:
8
10
  *
9
- * Two construction modes:
11
+ * 1. Direct pass an already-open `transport`, for example an agent worker
12
+ * or a test.
13
+ * 2. Deferred — construct without a transport and call `attach(transport)`
14
+ * once the connection is ready. The returned stream object is stable from
15
+ * construction, so callers can hold the reference and let attachment
16
+ * happen later.
10
17
  *
11
- * 1. Direct — pass `transport: SyncWebSocket` when it's already
12
- * open (agent worker, tests).
13
- * 2. Deferred pass `attachLater: true` and call `.attach(transport)`
14
- * once the engine's WS lifecycle has produced one. The returned
15
- * stream object is stable from construction; attachment can
16
- * happen later without callers having to re-grab the reference.
17
- *
18
- * Wire contract (apps/sync-server/src/hub/types.ts):
19
- * • Outbound: `{ type: 'presence_update', payload: { status, activity? } }`
20
- * — server stamps `userId`, `kind`, `timestamp`, `isAgent` and
21
- * broadcasts to other clients on the same sync groups.
22
- * • Inbound: same frame, with `kind: 'enter' | 'update' | 'leave'`.
18
+ * Wire frames:
19
+ * Outbound `presence_update` — `{ status, activity? }`. The server stamps
20
+ * `userId`, `kind`, `timestamp`, and `isAgent`, then broadcasts to the
21
+ * other participants on the same sync groups.
22
+ * Inbound the same frame, with `kind` one of `enter`, `update`, or
23
+ * `leave`.
23
24
  */
24
25
  import type { SyncWebSocket } from './SyncWebSocket.js';
25
26
  import type { PresenceStream } from '../types/streams.js';
@@ -1,25 +1,26 @@
1
1
  /**
2
- * Transport-driven PresenceStream factory.
2
+ * Creates a {@link PresenceStream} over a live sync connection. Presence is the
3
+ * lightweight, ephemeral "who's here and what are they doing" view: each
4
+ * participant broadcasts a status and an activity, and sees everyone else's.
5
+ * The stream is built directly on the sync WebSocket and adds no second
6
+ * connection. It is the sibling of {@link createClaimStream}, which reuses the
7
+ * same presence frames.
3
8
  *
4
- * This is the engine's home for presence — built directly on
5
- * `SyncWebSocket`, no SyncAgent wrapper, no second connection. The
6
- * older compatibility path predates this and will be deleted when
7
- * the dual-engine collapse completes.
9
+ * There are two ways to construct it:
8
10
  *
9
- * Two construction modes:
11
+ * 1. Direct pass an already-open `transport`, for example an agent worker
12
+ * or a test.
13
+ * 2. Deferred — construct without a transport and call `attach(transport)`
14
+ * once the connection is ready. The returned stream object is stable from
15
+ * construction, so callers can hold the reference and let attachment
16
+ * happen later.
10
17
  *
11
- * 1. Direct — pass `transport: SyncWebSocket` when it's already
12
- * open (agent worker, tests).
13
- * 2. Deferred pass `attachLater: true` and call `.attach(transport)`
14
- * once the engine's WS lifecycle has produced one. The returned
15
- * stream object is stable from construction; attachment can
16
- * happen later without callers having to re-grab the reference.
17
- *
18
- * Wire contract (apps/sync-server/src/hub/types.ts):
19
- * • Outbound: `{ type: 'presence_update', payload: { status, activity? } }`
20
- * — server stamps `userId`, `kind`, `timestamp`, `isAgent` and
21
- * broadcasts to other clients on the same sync groups.
22
- * • Inbound: same frame, with `kind: 'enter' | 'update' | 'leave'`.
18
+ * Wire frames:
19
+ * Outbound `presence_update` — `{ status, activity? }`. The server stamps
20
+ * `userId`, `kind`, `timestamp`, and `isAgent`, then broadcasts to the
21
+ * other participants on the same sync groups.
22
+ * Inbound the same frame, with `kind` one of `enter`, `update`, or
23
+ * `leave`.
23
24
  */
24
25
  import { asyncIteratorFrom } from '../utils/asyncIterator.js';
25
26
  import { participantKindFromWire } from '../coordination/schema.js';
@@ -67,10 +68,9 @@ export function createPresenceStream(config, transport = null) {
67
68
  if (self.activity.entityId)
68
69
  sendUpdate(self.activity);
69
70
  }));
70
- // Inbound presence frames translate the legacy wire vocabulary
71
- // (userId / isAgent / timestamp) into the engine shape
72
- // (participantId / participantKind / lastActive). When the server
73
- // adopts the engine names this block collapses to a pass-through.
71
+ // Inbound presence frames arrive in the wire vocabulary
72
+ // (userId / isAgent / timestamp); translate them into the shape this
73
+ // stream exposes (participantId / participantKind / lastActive).
74
74
  unsubs.push(t.subscribe('presence_update', (event) => {
75
75
  if (event.userId === participantId)
76
76
  return; // own echo
@@ -117,10 +117,9 @@ export function createPresenceStream(config, transport = null) {
117
117
  if (transport)
118
118
  attach(transport);
119
119
  // ── Outbound ────────────────────────────────────────────────────
120
- // Note: do NOT include `isAgent` in the payload. Server derives it
121
- // authoritatively from the connection's identity prefix; clients
122
- // self-declaring `isAgent` caused human sessions to broadcast as
123
- // agents to peers (real bug we caught earlier).
120
+ // Do not include `isAgent` in the payload. The server derives it
121
+ // authoritatively from the connection's identity, and letting a client
122
+ // self-declare it once caused human sessions to broadcast as agents to peers.
124
123
  function sendUpdate(activity) {
125
124
  if (!attached?.isConnected())
126
125
  return; // no-op until connected
@@ -1,24 +1,22 @@
1
1
  /**
2
- * Engine-attached snapshot factory.
2
+ * Captures a {@link Snapshot} of a chosen set of entities, along with a
3
+ * watermark, so a caller can detect when that state has gone stale. This is
4
+ * what an LLM caller threads into a prompt: `stamp` flows into later writes as
5
+ * `readAt`, so the server rejects a mutation premised on data that has since
6
+ * changed; `signal` is an `AbortSignal` that fires as soon as any captured
7
+ * entity receives a delta, so a mid-generation invalidation can abort the token
8
+ * stream instead of producing output against stale context.
3
9
  *
4
- * Captures the engine's current entity state + watermark for context-
5
- * staleness detection. The returned Snapshot is what an LLM caller
6
- * threads into a prompt: `stamp` flows into writes as `readAt` so the
7
- * server rejects mutations against now-stale data; `signal` fires on
8
- * any captured-entity delta so mid-generation invalidations abort
9
- * the token stream rather than producing output against dead context.
10
- *
11
- * Reads from the engine's MobX-reactive ObjectPool, picks up the
12
- * engine's `lastSyncId`, and subscribes to delta frames on the
13
- * engine's transport. Same socket as entity sync — no second
14
- * connection.
10
+ * It reads the current entity state from the in-memory pool, reads the engine's
11
+ * current `lastSyncId` as the watermark, and subscribes to delta frames on the
12
+ * existing sync connection no second connection.
15
13
  */
16
- import type { ObjectPool } from '../ObjectPool.js';
14
+ import type { InstanceCache } from '../InstanceCache.js';
17
15
  import type { Schema } from '../schema/schema.js';
18
16
  import type { SyncWebSocket } from './SyncWebSocket.js';
19
17
  import type { Snapshot } from '../types/streams.js';
20
18
  export interface CreateSnapshotArgs<TSchema extends Schema = Schema, K extends keyof TSchema['models'] & string = keyof TSchema['models'] & string> {
21
- pool: ObjectPool;
19
+ pool: InstanceCache;
22
20
  /** Live transport for delta subscriptions. May be null if the engine
23
21
  * hasn't connected yet — the snapshot still resolves with current
24
22
  * pool state, but `signal` won't fire until reconnect. */
@@ -26,8 +24,6 @@ export interface CreateSnapshotArgs<TSchema extends Schema = Schema, K extends k
26
24
  /** Returns the engine's current `lastSyncId`. Read at snapshot time
27
25
  * to stamp the watermark; not re-read after. */
28
26
  getLastSyncId: () => number;
29
- entities: {
30
- readonly [M in K]: string | readonly string[];
31
- };
27
+ entities: Readonly<Record<K, string | readonly string[]>>;
32
28
  }
33
29
  export declare function createSnapshot<TSchema extends Schema, K extends keyof TSchema['models'] & string>(args: CreateSnapshotArgs<TSchema, K>): Snapshot<TSchema, K>;
@@ -1,24 +1,22 @@
1
1
  /**
2
- * Engine-attached snapshot factory.
2
+ * Captures a {@link Snapshot} of a chosen set of entities, along with a
3
+ * watermark, so a caller can detect when that state has gone stale. This is
4
+ * what an LLM caller threads into a prompt: `stamp` flows into later writes as
5
+ * `readAt`, so the server rejects a mutation premised on data that has since
6
+ * changed; `signal` is an `AbortSignal` that fires as soon as any captured
7
+ * entity receives a delta, so a mid-generation invalidation can abort the token
8
+ * stream instead of producing output against stale context.
3
9
  *
4
- * Captures the engine's current entity state + watermark for context-
5
- * staleness detection. The returned Snapshot is what an LLM caller
6
- * threads into a prompt: `stamp` flows into writes as `readAt` so the
7
- * server rejects mutations against now-stale data; `signal` fires on
8
- * any captured-entity delta so mid-generation invalidations abort
9
- * the token stream rather than producing output against dead context.
10
- *
11
- * Reads from the engine's MobX-reactive ObjectPool, picks up the
12
- * engine's `lastSyncId`, and subscribes to delta frames on the
13
- * engine's transport. Same socket as entity sync — no second
14
- * connection.
10
+ * It reads the current entity state from the in-memory pool, reads the engine's
11
+ * current `lastSyncId` as the watermark, and subscribes to delta frames on the
12
+ * existing sync connection no second connection.
15
13
  */
16
14
  import { AbloValidationError } from '../errors.js';
17
15
  import { Model, modelAsRow } from '../Model.js';
18
16
  /**
19
- * Three top-level keys that conflict with the per-model buckets if a
20
- * customer's schema declares a model named `stamp` / `signal` /
21
- * `onChange`. Throw at snapshot time so the collision is loud.
17
+ * The snapshot result exposes `stamp`, `signal`, and `onChange` at its top
18
+ * level, alongside one bucket per model. If a schema declares a model with one
19
+ * of these names, the two would collide, so snapshot creation throws instead.
22
20
  */
23
21
  const RESERVED_SNAPSHOT_KEYS = new Set([
24
22
  'stamp',
@@ -80,9 +78,7 @@ export function createSnapshot(args) {
80
78
  const key = `${delta.modelName}:${delta.modelId}`;
81
79
  if (!watched.has(key))
82
80
  return;
83
- // The snapshot API treats every delta as 'semantic' severity.
84
- // Future: distinguish metadata-only deltas (e.g., updatedAt
85
- // bumps) from content changes — that's a separate scope.
81
+ // Every delta to a captured entity is reported as 'semantic' severity.
86
82
  fireChange({
87
83
  model: delta.modelName,
88
84
  id: delta.modelId,
@@ -96,16 +92,14 @@ export function createSnapshot(args) {
96
92
  signal: controller.signal,
97
93
  onChange: (listener) => {
98
94
  listeners.add(listener);
99
- // Caller is responsible for unsubscribing when they're done.
100
- // The delta subscription itself stays for the snapshot's life;
101
- // there's no public dispose because snapshots are short-lived
102
- // (one LLM call's worth) and the transport-level subscription
103
- // is cheap. If a long-lived consumer needs explicit teardown,
104
- // we can add `.dispose()` in a follow-up.
95
+ // The caller unsubscribes its own listener via the returned function.
96
+ // The underlying delta subscription lives for the snapshot's lifetime;
97
+ // there is no explicit dispose because a snapshot is short-lived (one
98
+ // LLM call's worth) and the subscription is cheap.
105
99
  return () => {
106
100
  listeners.delete(listener);
107
- // If the last listener AND the abort fired, drop the delta
108
- // subscription too — no one's listening anymore.
101
+ // Once the last listener is gone and the abort has fired, drop the
102
+ // delta subscription too — nothing is listening anymore.
109
103
  if (listeners.size === 0 && controller.signal.aborted && unsubDelta) {
110
104
  unsubDelta();
111
105
  unsubDelta = null;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Keeps the short-lived access credential fresh. It owns the re-mint hook, a
3
+ * single-flight guard that stops concurrent triggers from minting more than
4
+ * once, and a browser-only proactive refresh — a timer plus an OS-wake listener
5
+ * — that renews the credential ahead of expiry. It reaches the rest of the
6
+ * client only through the small {@link CredentialLifecycleContext} interface,
7
+ * so the two can reference each other without an import cycle.
8
+ */
9
+ import type { RecoveryClass } from '../errorCodes.js';
10
+ /**
11
+ * Tri-state outcome of a credential re-mint, mirroring the `getToken`
12
+ * contract (see {@link CredentialLifecycle.refresh}).
13
+ */
14
+ export type CredentialRefreshOutcome = 'refreshed' | 'session_error' | 'network_error';
15
+ /**
16
+ * What an auth-rejected transport should do after recovery has run. `'retry'`
17
+ * means a fresh credential is now in place — replay the request once. `'stop'`
18
+ * means don't replay: either the session is gone for good, the rejection is one
19
+ * a re-mint can't cure, or the mint failed transiently and the caller's own
20
+ * retry path will recover later.
21
+ */
22
+ export type CredentialRecoveryOutcome = 'retry' | 'stop';
23
+ /**
24
+ * What a credential refresher may resolve with. The plain-string form returns
25
+ * just the token. The object form also carries the mint response's `expiresAt`
26
+ * (an ISO string, epoch milliseconds, or a `Date`), which lets the proactive
27
+ * pre-roll schedule against the credential's real lifetime rather than assume
28
+ * the server's default expiry.
29
+ */
30
+ export type CredentialRefreshResult = string | {
31
+ readonly token: string;
32
+ readonly expiresAt?: string | number | Date;
33
+ } | null;
34
+ /** A credential re-mint hook. `Promise<string | null>` resolvers remain
35
+ * assignable — the widened result type is a superset. */
36
+ export type CredentialRefresher = () => Promise<CredentialRefreshResult>;
37
+ /** The fallback pre-roll interval, and also its ceiling: 10 minutes, which sits
38
+ * comfortably inside the server's 15-minute default credential lifetime. Used
39
+ * as-is when the refresher reports no expiry. */
40
+ export declare const DEFAULT_PREROLL_INTERVAL_MS: number;
41
+ /** Floor so a very short (or already-elapsed) TTL can't hot-loop the mint. */
42
+ export declare const MIN_PREROLL_DELAY_MS: number;
43
+ /**
44
+ * Computes how long to wait before pre-rolling a credential that expires at
45
+ * `expiresAtMs`: about two-thirds of the remaining lifetime (a 15-minute
46
+ * credential yields 10 minutes), clamped to
47
+ * [{@link MIN_PREROLL_DELAY_MS}, {@link DEFAULT_PREROLL_INTERVAL_MS}]. An
48
+ * unknown expiry (`null`) falls back to the 10-minute interval. Exported for
49
+ * unit tests.
50
+ */
51
+ export declare function computePrerollDelayMs(expiresAtMs: number | null, nowMs: number): number;
52
+ /**
53
+ * The callbacks this lifecycle needs back from the surrounding client. It is
54
+ * deliberately minimal — three callbacks, each resolved lazily at call time,
55
+ * because the connection machinery behind two of them isn't constructed until
56
+ * the sync connection is set up.
57
+ */
58
+ export interface CredentialLifecycleContext {
59
+ /** Push a freshly-minted access token into the shared credential source
60
+ * (no-op when the deployment wired no credential source). */
61
+ setAuthToken(token: string): void;
62
+ /** Nudge the connection to re-probe using the credential now in place. */
63
+ nudgeReconnect(): void;
64
+ /** Report that the long-lived login is gone, so the connection can move to
65
+ * its signed-out state. */
66
+ reportSessionExpired(): void;
67
+ }
68
+ export declare class CredentialLifecycle {
69
+ private readonly ctx;
70
+ /**
71
+ * The hook that mints a fresh short-lived access credential (the `ek_`/`rk_`
72
+ * key). An integrator wires it from their own token endpoint: this lifecycle
73
+ * decides when to refresh (a stale-credential probe or an external nudge),
74
+ * and the hook decides how to mint. It follows the same contract as a
75
+ * `getToken` function — it resolves a token string on success, `null` when
76
+ * the long-lived login is gone (a terminal state), and throws on a transient
77
+ * or offline failure. Used by {@link refresh}. When it is absent there is no
78
+ * silent re-mint, as with a static `apiKey` whose credential source is
79
+ * refreshed elsewhere.
80
+ */
81
+ private credentialRefresher;
82
+ /** Single-flight guard so a wake nudge + an in-flight request + a probe don't
83
+ * all mint at once (the classic "token thrash → random logout" bug). */
84
+ private inFlightCredentialRefresh;
85
+ /** Tears down the proactive credential lifecycle (the refresh timer and the
86
+ * OS-wake listener) installed by {@link start}; cleared when the client
87
+ * disconnects. Null when no refresher is wired. */
88
+ private credentialLifecycleTeardown;
89
+ /** Epoch milliseconds at which the current credential expires, when the
90
+ * refresher reports it (the object form of {@link CredentialRefreshResult}).
91
+ * `null` for the string-form resolver, in which case the pre-roll uses its
92
+ * fixed interval. */
93
+ private credentialExpiresAtMs;
94
+ /** Re-arms the proactive pre-roll timer (set by {@link start}). Called after
95
+ * every successful mint, so a refresh triggered reactively (by a probe, an
96
+ * OS wake, or the first mint) re-anchors the schedule to the fresh
97
+ * credential's real expiry instead of a stale fixed delay. */
98
+ private prerollReschedule;
99
+ constructor(ctx: CredentialLifecycleContext);
100
+ /**
101
+ * Registers the re-mint hook for the access credential — a function that
102
+ * mints a fresh `ek_`/`rk_` key, typically the integrator's `getToken`. See
103
+ * {@link credentialRefresher}.
104
+ */
105
+ setRefresher(refresher: CredentialRefresher | null): void;
106
+ /**
107
+ * Re-mints the short-lived access credential, pushes it into the credential
108
+ * source, and reports a three-way outcome the connection layer acts on:
109
+ * - a token string → `'refreshed'` (the fresh key is in place; re-probe and reconnect)
110
+ * - `null` → `'session_error'` (the login itself is gone — terminal, sign out)
111
+ * - a thrown error → `'network_error'` (the mint endpoint was unreachable — transient)
112
+ *
113
+ * The call is single-flight: concurrent triggers (an OS wake, an in-flight
114
+ * request, a probe) share one in-flight promise, so the credential is never
115
+ * minted twice at once. This avoids the failure where every rejected request
116
+ * mints a new token and the resulting thrash logs the user out.
117
+ *
118
+ * With no refresher wired, it resolves `'refreshed'` as a no-op re-probe: a
119
+ * static-`apiKey` client has no session to mint from and its credential
120
+ * source is refreshed elsewhere, so it simply re-probes with what it holds.
121
+ */
122
+ refresh(): Promise<CredentialRefreshOutcome>;
123
+ /**
124
+ * Interprets a refresh outcome and drives the connection accordingly. This is
125
+ * the single place the three-way outcome is acted on, shared by the proactive
126
+ * pre-roll, the OS-wake nudge, and the HTTP auth-recovery path, so every
127
+ * trigger converges on the same behavior.
128
+ */
129
+ private routeRefreshOutcome;
130
+ /**
131
+ * The shared recovery path for a request rejected on authentication, over any
132
+ * transport. The WebSocket probe already routes its own 401s; HTTP callers
133
+ * call this instead of inventing their own handling, so every path shares one
134
+ * single-flight mint, one outcome routing, and one taxonomy. It classifies
135
+ * the same recovery codes the connection probe does:
136
+ * - `access_credential_expiry` — the routine case, an expired `ek_`/`rk_`:
137
+ * silently re-mint through the single-flight {@link refresh} and tell the
138
+ * caller to replay once on success. This never signs out on its own; the
139
+ * only terminal path is the mint resolving `null`.
140
+ * - `session_expiry` — the login is gone: report it (which drives sign-out)
141
+ * and stop, since replaying is pointless.
142
+ * - `auth_blocked`, `permission`, and everything else — re-minting would
143
+ * produce the same rejected credential, so stop and leave the connection
144
+ * alone.
145
+ */
146
+ recoverFromAuthRejection(recovery: RecoveryClass): Promise<CredentialRecoveryOutcome>;
147
+ /**
148
+ * Installs the credential lifecycle. It has two parts:
149
+ * 1. Reactive — registers `getToken` as the re-mint hook the connection
150
+ * calls when a probe finds the key stale, or on a nudge.
151
+ * 2. Proactive — keeps the short-lived key fresh ahead of expiry with a
152
+ * refresh timer inside the credential's lifetime, plus a re-mint on OS
153
+ * wake. The whole proactive block is browser-gated on `typeof window`,
154
+ * because a server render has no socket to keep warm and the resolver is
155
+ * browser-oriented; arming it under Node would fire a relative-URL fetch
156
+ * and throw. (Agents pass a static `apiKey` with no resolver, so this
157
+ * method is never called for them.)
158
+ *
159
+ * Refreshing is automatic — a consumer never calls a refresh method. The call
160
+ * is idempotent: a second call replaces the first, and it is torn down when
161
+ * the client disconnects.
162
+ *
163
+ * `opts.proactiveInNode` arms the refresh timer on a windowless host as well.
164
+ * Set it for agent or system participants — long-lived server sockets whose
165
+ * `rk_`/`ek_` must renew before the server's keepalive check closes them.
166
+ * Node timers are `unref`ed, so a finishing script is never held alive by the
167
+ * pre-roll. The OS-wake listener stays browser-only regardless, since there
168
+ * is no `window` to listen on.
169
+ */
170
+ start(getToken: CredentialRefresher, opts?: {
171
+ proactiveInNode?: boolean;
172
+ }): void;
173
+ /** Tear down the proactive credential lifecycle (idempotent). */
174
+ stop(): void;
175
+ }