@abloatai/ablo 0.24.0 → 0.26.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 (246) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +33 -0
  3. package/README.md +45 -36
  4. package/dist/BaseSyncedStore.d.ts +72 -195
  5. package/dist/BaseSyncedStore.js +240 -625
  6. package/dist/Database.d.ts +8 -9
  7. package/dist/Database.js +9 -14
  8. package/dist/LazyReferenceCollection.js +5 -0
  9. package/dist/Model.js +6 -8
  10. package/dist/ModelRegistry.d.ts +25 -6
  11. package/dist/ModelRegistry.js +12 -6
  12. package/dist/NetworkMonitor.js +1 -1
  13. package/dist/ObjectPool.js +9 -3
  14. package/dist/SyncClient.d.ts +15 -5
  15. package/dist/SyncClient.js +152 -66
  16. package/dist/SyncEngineContext.d.ts +1 -3
  17. package/dist/SyncEngineContext.js +1 -2
  18. package/dist/agent/Agent.d.ts +21 -8
  19. package/dist/agent/Agent.js +26 -13
  20. package/dist/ai-sdk/coordination-context.js +9 -3
  21. package/dist/ai-sdk/wrap.d.ts +1 -1
  22. package/dist/ai-sdk/wrap.js +1 -1
  23. package/dist/auth/credentialPolicy.d.ts +10 -2
  24. package/dist/auth/credentialSource.d.ts +1 -13
  25. package/dist/auth/credentialSource.js +6 -13
  26. package/dist/auth/index.js +3 -3
  27. package/dist/cli.cjs +583 -287
  28. package/dist/client/Ablo.d.ts +35 -764
  29. package/dist/client/Ablo.js +64 -688
  30. package/dist/client/ApiClient.d.ts +14 -1
  31. package/dist/client/ApiClient.js +63 -9
  32. package/dist/client/auth.d.ts +8 -17
  33. package/dist/client/auth.js +42 -6
  34. package/dist/client/consoleLogger.d.ts +36 -0
  35. package/dist/client/consoleLogger.js +45 -0
  36. package/dist/client/createInternalComponents.js +1 -1
  37. package/dist/client/createModelProxy.js +10 -6
  38. package/dist/client/credentialEndpoint.d.ts +63 -0
  39. package/dist/client/credentialEndpoint.js +87 -0
  40. package/dist/client/hostedEndpoints.d.ts +24 -0
  41. package/dist/client/hostedEndpoints.js +24 -0
  42. package/dist/client/httpClient.d.ts +1 -1
  43. package/dist/client/identity.js +2 -1
  44. package/dist/client/modelRegistration.d.ts +14 -0
  45. package/dist/client/modelRegistration.js +321 -0
  46. package/dist/client/options.d.ts +396 -0
  47. package/dist/client/options.js +10 -0
  48. package/dist/client/resourceTypes.d.ts +344 -0
  49. package/dist/client/resourceTypes.js +13 -0
  50. package/dist/client/schemaConfig.d.ts +56 -0
  51. package/dist/client/schemaConfig.js +188 -0
  52. package/dist/client/sessionMint.d.ts +1 -1
  53. package/dist/client/validateAbloOptions.js +1 -1
  54. package/dist/client/wsMutationExecutor.d.ts +33 -0
  55. package/dist/client/wsMutationExecutor.js +77 -0
  56. package/dist/context.js +0 -3
  57. package/dist/coordination/index.d.ts +8 -1
  58. package/dist/coordination/index.js +24 -1
  59. package/dist/coordination/trace.d.ts +0 -1
  60. package/dist/coordination/trace.js +0 -1
  61. package/dist/core/DatabaseManager.d.ts +0 -1
  62. package/dist/core/DatabaseManager.js +26 -24
  63. package/dist/core/QueryProcessor.js +7 -8
  64. package/dist/core/QueryView.d.ts +14 -2
  65. package/dist/core/QueryView.js +4 -5
  66. package/dist/core/StoreManager.d.ts +2 -2
  67. package/dist/core/StoreManager.js +5 -1
  68. package/dist/core/index.d.ts +1 -1
  69. package/dist/core/openIDBWithTimeout.js +15 -11
  70. package/dist/core/query-utils.js +1 -1
  71. package/dist/core/storeContract.d.ts +143 -0
  72. package/dist/core/storeContract.js +16 -0
  73. package/dist/errorCodes.d.ts +11 -2
  74. package/dist/errorCodes.js +155 -137
  75. package/dist/errors.d.ts +18 -7
  76. package/dist/errors.js +14 -1
  77. package/dist/index.js +5 -0
  78. package/dist/interfaces/index.d.ts +6 -20
  79. package/dist/keys/index.js +18 -9
  80. package/dist/mutators/UndoManager.js +6 -1
  81. package/dist/mutators/defineMutators.d.ts +1 -3
  82. package/dist/policy/types.d.ts +1 -1
  83. package/dist/query/client.d.ts +11 -0
  84. package/dist/query/client.js +81 -52
  85. package/dist/react/AbloProvider.d.ts +6 -10
  86. package/dist/react/AbloProvider.js +22 -15
  87. package/dist/react/context.d.ts +2 -118
  88. package/dist/react/useAblo.js +1 -1
  89. package/dist/react/useErrorListener.js +1 -1
  90. package/dist/react/useMutationFailureListener.js +1 -1
  91. package/dist/react/useMutators.js +7 -1
  92. package/dist/react/useReactive.js +0 -1
  93. package/dist/react/useUndoScope.js +2 -2
  94. package/dist/schema/ddlLock.d.ts +39 -0
  95. package/dist/schema/ddlLock.js +51 -0
  96. package/dist/schema/field.js +1 -1
  97. package/dist/schema/index.d.ts +4 -3
  98. package/dist/schema/index.js +15 -5
  99. package/dist/schema/model.d.ts +3 -3
  100. package/dist/schema/model.js +2 -2
  101. package/dist/schema/openapi.js +5 -1
  102. package/dist/schema/queries.d.ts +1 -1
  103. package/dist/schema/queries.js +1 -0
  104. package/dist/schema/residency.d.ts +35 -0
  105. package/dist/schema/residency.js +27 -0
  106. package/dist/schema/roles.d.ts +25 -0
  107. package/dist/schema/roles.js +37 -0
  108. package/dist/schema/schema.d.ts +3 -3
  109. package/dist/schema/schema.js +11 -7
  110. package/dist/schema/serialize.d.ts +8 -8
  111. package/dist/schema/sync-delta-row.d.ts +2 -2
  112. package/dist/schema/sync-delta-row.js +2 -2
  113. package/dist/server/storage-mode.d.ts +8 -1
  114. package/dist/server/storage-mode.js +14 -4
  115. package/dist/source/adapter.d.ts +1 -1
  116. package/dist/source/adapters/drizzle.js +4 -3
  117. package/dist/source/adapters/kysely.js +4 -3
  118. package/dist/source/adapters/memory.js +1 -1
  119. package/dist/source/adapters/prisma.js +5 -4
  120. package/dist/source/conformance.js +10 -8
  121. package/dist/source/connector.js +4 -2
  122. package/dist/source/factory.d.ts +106 -0
  123. package/dist/source/factory.js +273 -0
  124. package/dist/source/index.d.ts +11 -461
  125. package/dist/source/index.js +14 -416
  126. package/dist/source/next.d.ts +1 -1
  127. package/dist/source/next.js +1 -1
  128. package/dist/source/pushQueue.d.ts +1 -1
  129. package/dist/source/pushQueue.js +6 -3
  130. package/dist/source/signing.d.ts +59 -0
  131. package/dist/source/signing.js +142 -0
  132. package/dist/source/types.d.ts +334 -0
  133. package/dist/source/types.js +44 -0
  134. package/dist/stores/ObjectStore.d.ts +1 -1
  135. package/dist/stores/ObjectStore.js +23 -23
  136. package/dist/stores/SyncActionStore.d.ts +1 -1
  137. package/dist/stores/SyncActionStore.js +70 -35
  138. package/dist/surface.d.ts +1 -1
  139. package/dist/surface.js +1 -0
  140. package/dist/sync/AreaOfInterestManager.d.ts +2 -2
  141. package/dist/sync/AreaOfInterestManager.js +1 -1
  142. package/dist/sync/BootstrapHelper.d.ts +1 -3
  143. package/dist/sync/BootstrapHelper.js +29 -8
  144. package/dist/sync/ConnectionManager.d.ts +8 -0
  145. package/dist/sync/ConnectionManager.js +19 -4
  146. package/dist/sync/HydrationCoordinator.d.ts +12 -0
  147. package/dist/sync/HydrationCoordinator.js +25 -5
  148. package/dist/sync/NetworkProbe.d.ts +3 -2
  149. package/dist/sync/NetworkProbe.js +7 -6
  150. package/dist/sync/SyncWebSocket.d.ts +71 -120
  151. package/dist/sync/SyncWebSocket.js +324 -630
  152. package/dist/sync/awaitClaimGrant.js +27 -19
  153. package/dist/sync/bootstrapApply.d.ts +60 -0
  154. package/dist/sync/bootstrapApply.js +65 -0
  155. package/dist/sync/commitFrames.d.ts +43 -0
  156. package/dist/sync/commitFrames.js +94 -0
  157. package/dist/sync/createClaimStream.js +3 -2
  158. package/dist/sync/createSnapshot.d.ts +1 -3
  159. package/dist/sync/credentialLifecycle.d.ts +175 -0
  160. package/dist/sync/credentialLifecycle.js +329 -0
  161. package/dist/sync/deltaPipeline.d.ts +111 -0
  162. package/dist/sync/deltaPipeline.js +262 -0
  163. package/dist/sync/groupChange.d.ts +111 -0
  164. package/dist/sync/groupChange.js +246 -0
  165. package/dist/sync/heartbeat.d.ts +62 -0
  166. package/dist/sync/heartbeat.js +91 -0
  167. package/dist/sync/participants.d.ts +8 -8
  168. package/dist/sync/syncCursor.d.ts +44 -0
  169. package/dist/sync/syncCursor.js +59 -0
  170. package/dist/sync/syncPlan.d.ts +62 -0
  171. package/dist/sync/syncPlan.js +51 -0
  172. package/dist/sync/wsFrameHandlers.d.ts +117 -0
  173. package/dist/sync/wsFrameHandlers.js +378 -0
  174. package/dist/testing/fixtures/bootstrap.d.ts +9 -11
  175. package/dist/testing/fixtures/deltas.d.ts +1 -1
  176. package/dist/testing/fixtures/models.js +1 -0
  177. package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
  178. package/dist/testing/helpers/sync-engine-harness.js +2 -1
  179. package/dist/testing/index.d.ts +1 -1
  180. package/dist/testing/index.js +1 -1
  181. package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
  182. package/dist/testing/mocks/MockSyncContext.js +1 -32
  183. package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
  184. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  185. package/dist/transactions/TransactionQueue.d.ts +20 -56
  186. package/dist/transactions/TransactionQueue.js +101 -487
  187. package/dist/transactions/TransactionStore.d.ts +18 -0
  188. package/dist/transactions/TransactionStore.js +51 -0
  189. package/dist/transactions/coalesceRules.d.ts +34 -0
  190. package/dist/transactions/coalesceRules.js +117 -0
  191. package/dist/transactions/commitPayload.d.ts +134 -0
  192. package/dist/transactions/commitPayload.js +152 -0
  193. package/dist/transactions/deltaConfirmation.d.ts +60 -0
  194. package/dist/transactions/deltaConfirmation.js +223 -0
  195. package/dist/transactions/optimistic.d.ts +24 -0
  196. package/dist/transactions/optimistic.js +45 -0
  197. package/dist/transactions/persistedReplay.d.ts +93 -0
  198. package/dist/transactions/persistedReplay.js +105 -0
  199. package/dist/types/index.d.ts +3 -3
  200. package/dist/types/index.js +0 -5
  201. package/dist/types/modelData.d.ts +12 -0
  202. package/dist/types/modelData.js +11 -0
  203. package/dist/types/participant.d.ts +21 -0
  204. package/dist/types/participant.js +12 -0
  205. package/dist/types/streams.d.ts +8 -14
  206. package/dist/utils/mobx-setup.d.ts +1 -1
  207. package/dist/utils/mobx-setup.js +2 -2
  208. package/dist/wire/errorEnvelope.d.ts +11 -2
  209. package/dist/wire/errorEnvelope.js +14 -2
  210. package/dist/wire/frames.d.ts +90 -0
  211. package/dist/wire/frames.js +55 -1
  212. package/dist/wire/index.d.ts +4 -1
  213. package/dist/wire/index.js +20 -1
  214. package/dist/wire/protocol.d.ts +45 -0
  215. package/dist/wire/protocol.js +45 -0
  216. package/dist/wire/protocolVersion.d.ts +56 -0
  217. package/dist/wire/protocolVersion.js +63 -0
  218. package/docs/api-keys.md +4 -3
  219. package/docs/data-sources.md +12 -5
  220. package/docs/examples/existing-python-backend.md +3 -3
  221. package/docs/identity.md +4 -4
  222. package/docs/integration-guide.md +1 -1
  223. package/docs/react.md +1 -1
  224. package/docs/sessions.md +5 -7
  225. package/llms.txt +4 -2
  226. package/package.json +15 -13
  227. package/dist/client/index.d.ts +0 -36
  228. package/dist/client/index.js +0 -33
  229. package/dist/config/index.d.ts +0 -10
  230. package/dist/config/index.js +0 -12
  231. package/dist/interfaces/headless.d.ts +0 -95
  232. package/dist/interfaces/headless.js +0 -41
  233. package/dist/query/index.d.ts +0 -6
  234. package/dist/query/index.js +0 -5
  235. package/dist/realtime/index.d.ts +0 -10
  236. package/dist/realtime/index.js +0 -9
  237. package/dist/schema/plane.d.ts +0 -23
  238. package/dist/schema/plane.js +0 -19
  239. package/dist/server/next.d.ts +0 -51
  240. package/dist/server/next.js +0 -47
  241. package/dist/sync/OfflineFlush.d.ts +0 -9
  242. package/dist/sync/OfflineFlush.js +0 -22
  243. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  244. package/dist/sync/OfflineTransactionStore.js +0 -263
  245. package/dist/transactions/index.d.ts +0 -16
  246. package/dist/transactions/index.js +0 -7
@@ -11,6 +11,7 @@
11
11
  * implementation stays in the app's SyncedStore.ts until we incrementally
12
12
  * pull generic methods into this base class.
13
13
  */
14
+ import type { RecoveryClass } from './errorCodes.js';
14
15
  import { ConnectionManager } from './sync/ConnectionManager.js';
15
16
  import { AreaOfInterestManager } from './sync/AreaOfInterestManager.js';
16
17
  import { type ParticipantScope } from './sync/participants.js';
@@ -18,19 +19,22 @@ import type { SyncClient } from './SyncClient.js';
18
19
  import type { Database, BootstrapResult } from './Database.js';
19
20
  import type { ObjectPool } from './ObjectPool.js';
20
21
  import { ModelRegistry } from './ModelRegistry.js';
21
- import { SyncWebSocket, type SyncDelta, type SyncGroupChangePayload, type GroupAddedPayload, type GroupRemovedPayload, type VersionVector, type BootstrapHint, type BootstrapDataEvent, type PresenceUpdateEvent, type EventMap, type DefaultCollaborationEvents } from './sync/SyncWebSocket.js';
22
+ import { SyncWebSocket, type SyncDelta, type SyncGroupChangePayload, type GroupAddedPayload, type GroupRemovedPayload, type BootstrapHint, type BootstrapDataEvent, type PresenceUpdateEvent, type EventMap, type DefaultCollaborationEvents } from './sync/SyncWebSocket.js';
22
23
  import { QueryProcessor } from './core/QueryProcessor.js';
23
24
  import { Model } from './Model.js';
24
25
  import { ModelScope } from './ObjectPool.js';
25
26
  import type { Schema } from './schema/schema.js';
26
- import type { LocalMutation } from './react/context.js';
27
+ import type { SyncStatus, LocalMutation } from './core/storeContract.js';
27
28
  import type { AuthCredentialSource } from './auth/credentialSource.js';
29
+ import type { ModelData } from './types/modelData.js';
30
+ import type { EnrichmentPlanEntry, ForeignKeyIndexSpec } from './sync/syncPlan.js';
31
+ import { type CredentialRefresher } from './sync/credentialLifecycle.js';
32
+ import type { RehydrationStats } from './sync/bootstrapApply.js';
28
33
  /** Constructor type for Model subclasses (accepts abstract classes) */
29
34
  export type ModelConstructor<T extends Model> = abstract new (...args: never[]) => T;
30
35
  /** Concrete constructor type for instantiation */
31
36
  export type ConcreteModelConstructor<T extends Model> = new (data?: any) => T;
32
- /** Generic record type for model data */
33
- export type ModelData = Record<string, unknown>;
37
+ export type { ModelData } from './types/modelData.js';
34
38
  /** Query result interface */
35
39
  export interface QueryResult<T extends Model> {
36
40
  data: T[];
@@ -38,55 +42,12 @@ export interface QueryResult<T extends Model> {
38
42
  hasMore: boolean;
39
43
  fromCache?: boolean;
40
44
  }
41
- /** A foreign-key index to register on the ObjectPool at construction time. */
42
- export interface ForeignKeyIndexSpec {
43
- /**
44
- * The child model name (where the FK field lives) — this is the type
45
- * that will be passed to `pool.registerForeignKey(modelName, fieldName)`
46
- * and later to `pool.getByForeignKey(modelName, fieldName, value)`.
47
- *
48
- * Use the wire `__typename` casing (e.g., `'SlideLayer'`, not
49
- * `'slideLayer'`) — that's the value `createFromData` stamps onto
50
- * models and the pool indexes by.
51
- */
52
- readonly modelName: string;
53
- /** The FK field name on the child model, e.g. `'slideId'`. */
54
- readonly fieldName: string;
55
- }
56
- /**
57
- * A declarative enrichment rule for the delta-apply path.
58
- *
59
- * When a delta for `modelName` arrives, after the model is constructed
60
- * the base store reads `data[foreignKey]` from the payload, looks up
61
- * the matching parent in the ObjectPool, and attaches it as
62
- * `data[relationKey]`. Best-effort: if the parent isn't yet in the
63
- * pool (e.g., arrived later in the same bootstrap batch), enrichment
64
- * silently no-ops.
65
- *
66
- * Replaces the previous pattern of overriding `enrichRelations` on a
67
- * subclass to hardcode per-model enrichment logic.
68
- */
69
- export interface EnrichmentPlanEntry {
70
- /** The child model whose incoming deltas should be enriched. */
71
- readonly modelName: string;
72
- /** The FK field on the child that points at the parent's id. */
73
- readonly foreignKey: string;
74
- /** The property name under which to attach the parent model. */
75
- readonly relationKey: string;
76
- }
45
+ export type { ForeignKeyIndexSpec, EnrichmentPlanEntry } from './sync/syncPlan.js';
77
46
  /** Configuration for SyncedStore behavior */
78
47
  export interface SyncedStoreConfig {
79
48
  enableOffline?: boolean;
80
49
  enableCache?: boolean;
81
50
  enableTelemetry?: boolean;
82
- /**
83
- * Initial version vector keys, each seeded to 0. Merged with the
84
- * schema-derived set (if a schema is provided to the constructor) —
85
- * explicit keys here layer on top of derived ones. Replaces the
86
- * subclass pattern of hardcoding `this.versionVector = { tasks: 0, ... }`
87
- * in the constructor.
88
- */
89
- versionVectorKeys?: readonly string[];
90
51
  /**
91
52
  * Declarative enrichment plan consumed by `enrichRelations`. Replaces
92
53
  * the subclass override of `enrichRelations` for per-model parent
@@ -104,17 +65,7 @@ export interface SyncedStoreConfig {
104
65
  */
105
66
  foreignKeyIndexes?: readonly ForeignKeyIndexSpec[];
106
67
  }
107
- /** Sync status for UI binding */
108
- export interface SyncStatus {
109
- state: 'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting';
110
- progress: number;
111
- error?: Error;
112
- /** When true, the error is a session/auth error requiring re-authentication. */
113
- isSessionError: boolean;
114
- lastSyncAt?: Date;
115
- pendingChanges: number;
116
- offlineSince?: Date;
117
- }
68
+ export type { SyncStatus } from './core/storeContract.js';
118
69
  /** User context for initialization */
119
70
  export interface UserContext {
120
71
  userId: string;
@@ -157,15 +108,7 @@ export interface SmartSyncOptions {
157
108
  batchingDelay?: number;
158
109
  maxBatchSize?: number;
159
110
  }
160
- /** Rehydration statistics from bootstrap */
161
- export interface RehydrationStats {
162
- added: number;
163
- updated: number;
164
- removed: number;
165
- skipped: number;
166
- healed: number;
167
- elapsedMs: number;
168
- }
111
+ export type { RehydrationStats } from './sync/bootstrapApply.js';
169
112
  /** Bootstrap timeout configuration */
170
113
  export declare const BOOTSTRAP_CONFIG: {
171
114
  readonly OVERALL_TIMEOUT_MS: 15000;
@@ -173,7 +116,8 @@ export declare const BOOTSTRAP_CONFIG: {
173
116
  readonly RETRY_DELAY_MS: 500;
174
117
  };
175
118
  export { ModelScope };
176
- export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemovedPayload, VersionVector, BootstrapHint, BootstrapDataEvent, PresenceUpdateEvent, };
119
+ export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemovedPayload, BootstrapHint, BootstrapDataEvent, PresenceUpdateEvent, };
120
+ export { deriveSyncPlanFromSchema } from './sync/syncPlan.js';
177
121
  /**
178
122
  * BaseSyncedStore — abstract base for app-specific sync stores.
179
123
  *
@@ -202,30 +146,6 @@ export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemoved
202
146
  * }
203
147
  * }
204
148
  */
205
- /**
206
- * Walk a schema and derive the three sync-plan arrays consumed by
207
- * `BaseSyncedStore`'s constructor: version-vector keys, FK indexes to
208
- * register on the pool, and the enrichment plan.
209
- *
210
- * Version vector keys are derived from each model's `typename` (lowercased
211
- * to match the server's event-type convention — `'Task'` → `'task'`,
212
- * `'SlideLayer'` → `'slidelayer'`). A fallback to the schema key applies
213
- * when `typename` is unset, though `defineSchema()` now always resolves
214
- * it during assembly so the fallback is defensive-only.
215
- *
216
- * FK indexes and enrichment entries are pulled from each `belongsTo`
217
- * relation where `options.index` / `options.enrich` is set. Relations
218
- * without those options are skipped — this is an opt-in mechanism so
219
- * adding a `belongsTo` never silently changes delta or lookup semantics.
220
- *
221
- * Pure function: takes a Schema, returns three arrays. No side effects,
222
- * no class state. Called once at construction time from `BaseSyncedStore`.
223
- */
224
- export declare function deriveSyncPlanFromSchema(schema: Schema): {
225
- versionVectorKeys: string[];
226
- enrichmentPlan: EnrichmentPlanEntry[];
227
- foreignKeyIndexes: ForeignKeyIndexSpec[];
228
- };
229
149
  export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaboration> = DefaultCollaborationEvents, TSchema extends Schema = Schema> {
230
150
  syncStatus: SyncStatus;
231
151
  protected readonly syncClient: SyncClient;
@@ -291,18 +211,16 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
291
211
  unpinScope(scope: ParticipantScope): Promise<void>;
292
212
  protected readonly queryProcessor: QueryProcessor;
293
213
  /**
294
- * Runtime behavior flags only — the three schema/config arrays
295
- * (`versionVectorKeys`, `enrichmentPlan`, `foreignKeyIndexes`) are
296
- * consumed at construction time and stored on the instance as
297
- * `versionVector`, `enrichmentPlan`, and pool-registered indexes.
298
- * They don't need to persist on `this.config`.
214
+ * Runtime behavior flags only — the schema/config arrays
215
+ * (`enrichmentPlan`, `foreignKeyIndexes`) are consumed at construction
216
+ * time and stored on the instance as `enrichmentPlan` and
217
+ * pool-registered indexes. They don't need to persist on `this.config`.
299
218
  */
300
219
  protected readonly config: Required<Pick<SyncedStoreConfig, 'enableOffline' | 'enableCache' | 'enableTelemetry'>>;
301
- protected disposers: Array<() => void>;
220
+ protected disposers: (() => void)[];
302
221
  protected initialized: boolean;
303
222
  protected dataReady: boolean;
304
223
  protected userContext: UserContext | null;
305
- protected versionVector: VersionVector;
306
224
  /**
307
225
  * Declarative enrichment plan: "for model X, when a delta arrives,
308
226
  * read data[foreignKey] and attach the matching parent from the pool
@@ -332,11 +250,11 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
332
250
  modelRegistry: ModelRegistry;
333
251
  /**
334
252
  * Optional schema. When provided, `deriveSyncPlanFromSchema` walks
335
- * the schema's models + relations to auto-populate version vector
336
- * keys, FK indexes, and the enrichment plan from declarative
337
- * annotations. Class-based subclass users (like Ablo's legacy
338
- * SyncedStore) typically pass explicit `config.versionVectorKeys`
339
- * / `config.foreignKeyIndexes` / `config.enrichmentPlan` instead.
253
+ * the schema's models + relations to auto-populate FK indexes and
254
+ * the enrichment plan from declarative annotations. Class-based
255
+ * subclass users (like Ablo's legacy SyncedStore) typically pass
256
+ * explicit `config.foreignKeyIndexes` / `config.enrichmentPlan`
257
+ * instead.
340
258
  */
341
259
  schema?: TSchema;
342
260
  /** Sync server URL for WebSocket connection. Converted to wss:// automatically. */
@@ -391,23 +309,15 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
391
309
  */
392
310
  protected connectionManager: import('./sync/ConnectionManager.js').ConnectionManager | null;
393
311
  /**
394
- * Re-mint hook for the short-lived access credential (the Stripe-style
395
- * `ek_`/`rk_`). Wired by the React provider from its `getToken`/`authEndpoint`
396
- * the engine owns WHEN to refresh (a stale-credential probe / an external
397
- * nudge), the integrator owns HOW to mint. Mirrors the `getToken` contract:
398
- * resolves a token string on success, `null` when the long-lived login is
399
- * gone (terminal), and THROWS on a transient/offline failure. Used by
400
- * {@link performCredentialRefresh}. Absent no silent re-mint (e.g. a static
401
- * `apiKey` deployment whose credential source refreshes out-of-band).
402
- */
403
- private credentialRefresher;
404
- /** Single-flight guard so a wake nudge + an in-flight request + a probe don't
405
- * all mint at once (the classic "token thrash → random logout" bug). */
406
- private inFlightCredentialRefresh;
407
- /** Teardown for the proactive credential lifecycle (refresh timer + wake/
408
- * online/focus listeners) installed by {@link startCredentialLifecycle};
409
- * cleared on {@link disconnect}. Null when no resolver is wired. */
410
- private credentialLifecycleTeardown;
312
+ * Access-credential re-mint + proactive pre-roll extracted to
313
+ * sync/credentialLifecycle.ts. Owns the refresher hook, the single-flight
314
+ * guard, and the browser-only refresh timer / wake listener; talks back
315
+ * through three lazily-resolved callbacks (the ConnectionManager doesn't
316
+ * exist until `setupWebSocketSync`). The `setCredentialRefresher` /
317
+ * `performCredentialRefresh` / `startCredentialLifecycle` methods below
318
+ * are thin delegates so the store's public surface is unchanged.
319
+ */
320
+ private readonly credentialLifecycle;
411
321
  /**
412
322
  * Listeners registered via `subscribeSessionError()`. Fired when the
413
323
  * WebSocket closes with a session-invalid code (1008/4001/4003) or a
@@ -448,7 +358,7 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
448
358
  waitForConfirmation(modelName: string, modelId: string): Promise<void>;
449
359
  /**
450
360
  * Observe the LOCAL mutation stream for undo recording (see
451
- * {@link import('./react/context.js').LocalMutation}). Taps the
361
+ * {@link import('./core/storeContract.js').LocalMutation}). Taps the
452
362
  * TransactionQueue's `transaction:created` event — fired once per local
453
363
  * create/update/delete/archive with `previousData` already captured.
454
364
  * Remote/collaborator deltas apply via `applyDeltaBatchToPool` and never
@@ -469,28 +379,24 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
469
379
  /**
470
380
  * Register the access-credential re-mint hook. Called by the React provider
471
381
  * with a thunk that mints a fresh `ek_`/`rk_` (typically its `getToken`).
472
- * See {@link credentialRefresher}.
382
+ * See {@link CredentialLifecycle.setRefresher}.
473
383
  */
474
- setCredentialRefresher(refresher: (() => Promise<string | null>) | null): void;
384
+ setCredentialRefresher(refresher: CredentialRefresher | null): void;
475
385
  /**
476
386
  * Re-mint the short-lived access credential and push it into the credential
477
387
  * source, reporting a tri-state outcome the {@link ConnectionManager} maps to
478
- * its FSM. The contract mirrors `getToken` (and PowerSync's `fetchCredentials`
479
- * / Liveblocks' `authEndpoint`, but made explicit instead of overloading
480
- * return/throw):
481
- * - token string → `'refreshed'` (fresh key in place; re-probe & reconnect)
482
- * - `null` → `'session_error'` (login itself is gone → terminal, sign out)
483
- * - throw → `'network_error'` (couldn't reach the mint endpoint → transient)
484
- *
485
- * SINGLE-FLIGHT: concurrent callers (a wake nudge, an in-flight request, the
486
- * probe) share one in-flight promise so we never double-mint — the canonical
487
- * fix for the "every 401 mints a token → thrash → spurious logout" anti-pattern.
488
- *
489
- * No refresher wired ⇒ `'refreshed'` (a no-op re-probe): a static-`apiKey`
490
- * deployment has no session to re-mint from; its credential source refreshes
491
- * out-of-band, so we just re-probe with whatever it currently holds.
388
+ * its FSM. Single-flight; no refresher wired ⇒ `'refreshed'` (a no-op
389
+ * re-probe). Full contract on {@link CredentialLifecycle.refresh}.
492
390
  */
493
391
  performCredentialRefresh(): Promise<'refreshed' | 'session_error' | 'network_error'>;
392
+ /**
393
+ * THE auth-recovery backbone for HTTP transports (lazy query lane etc.):
394
+ * classify-driven single-flight re-mint with the same FSM outcome routing
395
+ * the WS probe and proactive pre-roll use. `'retry'` ⇒ a fresh credential
396
+ * is in the credential source, replay the request ONCE. Full contract on
397
+ * {@link CredentialLifecycle.recoverFromAuthRejection}.
398
+ */
399
+ recoverFromAuthRejection(recovery: RecoveryClass): Promise<'retry' | 'stop'>;
494
400
  /**
495
401
  * Nudge the connection FSM to re-probe with the current credential. Idempotent
496
402
  * and safe in any state (ignored while `connected`). Call after pushing a
@@ -500,72 +406,39 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
500
406
  */
501
407
  nudgeReconnect(): void;
502
408
  /**
503
- * Install the access-credential lifecycle the CLIENT owns (this used to live
504
- * in the React provider wrong layer). Two parts:
505
- * 1. REACTIVE register `getToken` as the re-mint hook the FSM calls when a
506
- * probe finds the key stale (`credential_stale`) or on a nudge.
507
- * 2. PROACTIVE keep the short-lived key fresh ahead of trouble: a refresh
508
- * timer inside the TTL, plus re-mint on OS wake. The ENTIRE proactive
509
- * block is browser-gated (`typeof window`): server/SSR has no socket to
510
- * keep warm and the resolver is browser-oriented, so arming it in Node
511
- * would fire a relative-URL fetch and throw. (Agents pass a static
512
- * `apiKey` with no resolver, so this method is never called for them.)
513
- *
514
- * Config-driven and invisible, like Supabase's `autoRefreshToken` — consumers
515
- * never call a refresh method. Idempotent (a second call replaces the first);
516
- * torn down on {@link disconnect}.
409
+ * Install the access-credential lifecycle the CLIENT owns: register
410
+ * `getToken` as the reactive re-mint hook AND arm the browser-only
411
+ * proactive pre-roll (refresh timer + OS-wake re-mint). Idempotent
412
+ * (a second call replaces the first); torn down on {@link disconnect}.
413
+ * Full rationale on {@link CredentialLifecycle.start}.
517
414
  */
518
- startCredentialLifecycle(getToken: () => Promise<string | null>): void;
415
+ startCredentialLifecycle(getToken: CredentialRefresher, opts?: {
416
+ proactiveInNode?: boolean;
417
+ }): void;
519
418
  /** Tear down the proactive credential lifecycle (idempotent). */
520
419
  private stopCredentialLifecycle;
420
+ /** Narrow context the group-change leaf talks back through. */
421
+ private groupChangeContext;
521
422
  /**
522
- * Handle an actionType 'G' delta.
523
- *
524
- * The server emits 'G' via two distinct pathways, distinguished by payload
525
- * shape:
526
- *
527
- * Incremental (EmitGroupAdded): { group, userId }
528
- * - The recipient was added to a single sync group.
529
- * - Subsequent 'C' (Covering) deltas deliver each newly-visible entity.
530
- * - No re-bootstrap — entities arrive via the normal insert path.
531
- *
532
- * Legacy (EmitGroupChange): { addedGroups, removedGroups }
533
- * - Single delta carrying the full group membership diff.
534
- * - Forces a full re-bootstrap (disconnect + reconnect + fetch all).
535
- * - Deprecated on the server; kept here for wire-level backward compat.
423
+ * Handle an actionType 'G' delta — incremental `{ group, userId }` or
424
+ * legacy `{ addedGroups, removedGroups }` payloads. Full pathway doc on
425
+ * {@link groupChange.handleSyncGroupChange}.
536
426
  */
537
427
  protected handleSyncGroupChange(delta: SyncDelta): Promise<void>;
538
428
  /**
539
- * Handle an incremental GroupAdded delta.
540
- *
541
- * Adds the new group to the subscription metadata without triggering a
542
- * re-bootstrap. The server will follow up with 'C' (Covering) deltas for
543
- * each newly-visible entity, which flow through the normal insert path.
429
+ * Handle an incremental GroupAdded delta — metadata only, no re-bootstrap
430
+ * (covering deltas bring the entities). See {@link groupChange.handleGroupAdded}.
544
431
  */
545
432
  protected handleGroupAdded(payload: GroupAddedPayload, syncId: number): Promise<void>;
546
433
  /**
547
- * Handle an actionType 'S' (GroupRemoved) delta.
548
- *
549
- * Signals that the recipient has lost access to a sync group. Because
550
- * the client does not track per-entity group membership, we can't
551
- * selectively purge entities belonging to that group. The safe fallback
552
- * is the legacy behavior: clear local state and force a re-bootstrap
553
- * with the updated group list.
554
- *
555
- * Future optimization: track group membership in the ObjectPool so 'S'
556
- * can do a targeted purge instead of a full re-bootstrap.
434
+ * Handle an actionType 'S' (GroupRemoved) delta — SECURITY clear of local
435
+ * state + full re-bootstrap. See {@link groupChange.handleGroupRemoved}.
557
436
  */
558
437
  protected handleGroupRemoved(delta: SyncDelta): Promise<void>;
559
438
  /** Compute new sync groups after applying additions and removals */
560
439
  protected computeUpdatedSyncGroups(payload: SyncGroupChangePayload): string[];
561
- /** Force a full re-bootstrap via connection lifecycle event.
562
- *
563
- * No-op for `bootstrapMode: 'none'` participants — they never pull
564
- * baseline state, so a "force re-bootstrap" trigger (sync-group
565
- * shrink, scope revocation) instead just flushes the local pool and
566
- * relies on covering deltas to repopulate the data they actually
567
- * subscribe to.
568
- */
440
+ /** Force a full re-bootstrap via connection lifecycle event (no-op for
441
+ * `bootstrapMode: 'none'` participants — see {@link groupChange.forceFullRebootstrap}). */
569
442
  protected forceFullRebootstrap(): void;
570
443
  /**
571
444
  * Single source of truth for the sync-group list this session is
@@ -578,6 +451,8 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
578
451
  protected resolveSyncGroups(context: UserContext): readonly string[];
579
452
  /** Check if sync groups shrank since last session — force full bootstrap if so */
580
453
  protected checkSyncGroupShrinkage(): Promise<void>;
454
+ /** Narrow context the bootstrap-apply leaf talks back through. */
455
+ private poolContext;
581
456
  /** Apply bootstrap data to the ObjectPool with ghost removal */
582
457
  /** Apply bootstrap data to the ObjectPool. Delegates pool writes to SyncClient. */
583
458
  protected applyBootstrapToPool(bootstrapResult: BootstrapResult, protectedIds?: ReadonlySet<string>): RehydrationStats;
@@ -595,7 +470,7 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
595
470
  protected withDeltaQueuing<T>(fn: () => Promise<T>): Promise<T>;
596
471
  /** Collect IDs that must survive ghost removal (added by deltas during bootstrap) */
597
472
  protected collectDeltaProtectedIds(preBootstrapIds: ReadonlySet<string>): Set<string>;
598
- /** Replay deltas queued during bootstrap */
473
+ /** Replay deltas queued during bootstrap (atomically, via `applyDeltaFrame`). */
599
474
  protected replayQueuedDeltas(): void;
600
475
  /**
601
476
  * Factory for the internal `ConnectionManager`. Override to return
@@ -664,11 +539,13 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
664
539
  */
665
540
  protected waitForWebSocketConnected(timeoutMs: number): Promise<boolean>;
666
541
  protected setupWebSocketSync(context: UserContext, lastSyncId: number): void;
667
- /** State signature for delta deduplication */
668
- private extractStateSignature;
542
+ /** Memoized pipeline context — `enqueueDelta` runs once per delta, so the
543
+ * accessor object is built once and reused (the get/set accessors always
544
+ * read the live host fields). */
545
+ private _deltaPipelineContext;
546
+ private get deltaPipelineContext();
669
547
  /** Get fields that represent meaningful state for deduplication. Override for model-specific fields. */
670
548
  protected getStateFields(_modelName: string): string[];
671
- private isSameState;
672
549
  /** Deduplicate deltas to the same entity — keep meaningful state transitions only */
673
550
  protected deduplicateDeltas(deltas: SyncDelta[]): SyncDelta[];
674
551
  /** Process incoming delta with smart batching */