@abloatai/ablo 0.35.0 → 0.36.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 (215) hide show
  1. package/CHANGELOG.md +713 -629
  2. package/README.md +56 -519
  3. package/bin/ablo.cjs +39 -0
  4. package/dist/BaseSyncedStore.d.ts +24 -4
  5. package/dist/BaseSyncedStore.js +53 -37
  6. package/dist/Database.d.ts +8 -20
  7. package/dist/Database.js +61 -59
  8. package/dist/InstanceCache.d.ts +6 -2
  9. package/dist/InstanceCache.js +18 -16
  10. package/dist/Model.d.ts +17 -7
  11. package/dist/Model.js +17 -7
  12. package/dist/ModelRegistry.d.ts +4 -0
  13. package/dist/ModelRegistry.js +17 -15
  14. package/dist/NetworkMonitor.d.ts +3 -1
  15. package/dist/NetworkMonitor.js +7 -5
  16. package/dist/SyncClient.d.ts +5 -15
  17. package/dist/SyncClient.js +60 -57
  18. package/dist/client/Ablo.js +30 -19
  19. package/dist/client/createInternalComponents.d.ts +4 -0
  20. package/dist/client/createInternalComponents.js +8 -2
  21. package/dist/client/createModelProxy.d.ts +21 -1
  22. package/dist/client/createModelProxy.js +123 -57
  23. package/dist/client/humans.d.ts +30 -9
  24. package/dist/client/humans.js +45 -19
  25. package/dist/client/reactiveEngine.d.ts +16 -11
  26. package/dist/client/reactiveEngine.js +113 -335
  27. package/dist/client/storeCluster.d.ts +47 -0
  28. package/dist/client/storeCluster.js +118 -0
  29. package/dist/client/storeLifecycle.d.ts +61 -0
  30. package/dist/client/storeLifecycle.js +231 -0
  31. package/dist/context.d.ts +13 -0
  32. package/dist/context.js +23 -0
  33. package/dist/core/index.d.ts +1 -1
  34. package/dist/docs/catalog.js +6 -3
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +4 -2
  37. package/dist/query/client.d.ts +3 -0
  38. package/dist/query/client.js +6 -5
  39. package/dist/react/AbloProvider.d.ts +13 -1
  40. package/dist/react/AbloProvider.js +5 -2
  41. package/dist/react/context.d.ts +2 -2
  42. package/dist/react/createAbloReact.d.ts +56 -0
  43. package/dist/react/createAbloReact.js +51 -0
  44. package/dist/react/index.d.ts +1 -0
  45. package/dist/react/index.js +3 -0
  46. package/dist/react/useAblo.d.ts +9 -2
  47. package/dist/react/useAblo.js +25 -7
  48. package/dist/schema/coordination.js +5 -1
  49. package/dist/schema/index.d.ts +1 -0
  50. package/dist/schema/index.js +4 -0
  51. package/dist/schema/select.js +3 -0
  52. package/dist/schema/serialize.js +3 -0
  53. package/dist/source/adapter.d.ts +7 -5
  54. package/dist/source/adapter.js +7 -5
  55. package/dist/source/index.d.ts +1 -1
  56. package/dist/source/index.js +1 -1
  57. package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
  58. package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
  59. package/dist/{core → stores}/DatabaseManager.js +13 -12
  60. package/dist/{core → stores}/StoreManager.d.ts +5 -3
  61. package/dist/{core → stores}/StoreManager.js +24 -22
  62. package/dist/stores/SyncActionStore.d.ts +3 -1
  63. package/dist/stores/SyncActionStore.js +9 -7
  64. package/dist/sync/BootstrapFetcher.d.ts +4 -0
  65. package/dist/sync/BootstrapFetcher.js +28 -26
  66. package/dist/sync/OnDemandLoader.d.ts +3 -0
  67. package/dist/sync/OnDemandLoader.js +1 -0
  68. package/dist/sync/bootstrapApply.d.ts +3 -0
  69. package/dist/sync/bootstrapApply.js +2 -2
  70. package/dist/sync/deltaPipeline.d.ts +13 -12
  71. package/dist/sync/deltaPipeline.js +21 -4
  72. package/dist/sync/groupChange.d.ts +3 -0
  73. package/dist/sync/groupChange.js +16 -14
  74. package/dist/sync/participants.d.ts +19 -2
  75. package/dist/sync/participants.js +3 -1
  76. package/dist/sync/schemas.d.ts +2 -1
  77. package/dist/sync/schemas.js +3 -3
  78. package/dist/syncLog/contract.d.ts +20 -0
  79. package/dist/syncLog/contract.js +19 -0
  80. package/dist/syncLog/index.d.ts +1 -0
  81. package/dist/syncLog/index.js +1 -0
  82. package/dist/transaction/auth/capability.d.ts +35 -0
  83. package/dist/transaction/auth/capability.js +25 -0
  84. package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
  85. package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
  86. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
  87. package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
  88. package/dist/transaction/coordination/index.d.ts +4 -4
  89. package/dist/transaction/coordination/index.js +4 -3
  90. package/dist/transaction/coordination/locator.d.ts +23 -2
  91. package/dist/transaction/coordination/locator.js +22 -2
  92. package/dist/transaction/coordination/schema.d.ts +125 -62
  93. package/dist/transaction/coordination/schema.js +228 -64
  94. package/dist/transaction/coordination/targetConflict.js +32 -28
  95. package/dist/transaction/errorCodes.d.ts +2 -2
  96. package/dist/transaction/errorCodes.js +13 -9
  97. package/dist/transaction/plugin.d.ts +95 -2
  98. package/dist/transaction/plugin.js +21 -2
  99. package/dist/transaction/resources/httpResources.d.ts +57 -2
  100. package/dist/transaction/resources/modelOperations.d.ts +148 -40
  101. package/dist/transaction/resources/where.d.ts +16 -0
  102. package/dist/transaction/resources/where.js +45 -0
  103. package/dist/transaction/schema/field.d.ts +12 -18
  104. package/dist/transaction/schema/fieldRef.d.ts +38 -0
  105. package/dist/transaction/schema/fieldRef.js +11 -0
  106. package/dist/transaction/schema/openapi.d.ts +16 -15
  107. package/dist/transaction/schema/openapi.js +186 -25
  108. package/dist/transaction/schema/relation.d.ts +7 -2
  109. package/dist/transaction/schema/schema.d.ts +27 -0
  110. package/dist/transaction/schema/schema.js +20 -0
  111. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
  112. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
  113. package/dist/transaction/transport/httpClient.d.ts +9 -1
  114. package/dist/transaction/transport/httpClient.js +1 -0
  115. package/dist/transaction/transport/httpTransport.js +156 -44
  116. package/dist/transaction/transport/wsTransport.d.ts +2 -4
  117. package/dist/transaction/transport/wsTransport.js +16 -10
  118. package/dist/transaction/types/streams.d.ts +10 -0
  119. package/dist/transaction/utils/duration.d.ts +25 -0
  120. package/dist/transaction/utils/duration.js +32 -0
  121. package/dist/transaction/wire/accountResponses.d.ts +69 -0
  122. package/dist/transaction/wire/accountResponses.js +36 -1
  123. package/dist/transaction/wire/auth.d.ts +9 -2
  124. package/dist/transaction/wire/auth.js +7 -1
  125. package/dist/transaction/wire/claims.d.ts +164 -97
  126. package/dist/transaction/wire/claims.js +126 -28
  127. package/dist/transaction/wire/commit.d.ts +1 -1
  128. package/dist/transaction/wire/feedEvent.d.ts +27 -0
  129. package/dist/transaction/wire/feedEvent.js +27 -1
  130. package/dist/transaction/wire/frames.d.ts +2 -2
  131. package/dist/transaction/wire/inboundFrames.d.ts +12 -2
  132. package/dist/transaction/wire/index.d.ts +10 -6
  133. package/dist/transaction/wire/index.js +12 -3
  134. package/dist/transaction/wire/modelMutations.d.ts +31 -0
  135. package/dist/transaction/wire/modelMutations.js +52 -0
  136. package/dist/transaction/wire/modelShape.d.ts +78 -0
  137. package/dist/transaction/wire/modelShape.js +74 -0
  138. package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
  139. package/dist/transactions/mutations/MutationQueue.js +55 -45
  140. package/dist/transactions/mutations/commitPayload.d.ts +3 -2
  141. package/dist/transactions/mutations/commitPayload.js +5 -5
  142. package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
  143. package/dist/transactions/mutations/deltaConfirmation.js +10 -8
  144. package/dist/transactions/mutations/replayValidation.d.ts +2 -1
  145. package/dist/transactions/mutations/replayValidation.js +3 -2
  146. package/dist/{core → views}/QueryView.d.ts +1 -1
  147. package/dist/{core → views}/QueryView.js +1 -1
  148. package/dist/{core → views}/ViewRegistry.d.ts +1 -1
  149. package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
  150. package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
  151. package/docs/agents.md +1 -1
  152. package/docs/api-keys.md +6 -6
  153. package/docs/api.md +5 -43
  154. package/docs/audit.md +4 -3
  155. package/docs/cli.md +11 -11
  156. package/docs/client-behavior.md +4 -4
  157. package/docs/concurrency-convention.md +28 -42
  158. package/docs/coordination.md +235 -83
  159. package/docs/data-sources.md +4 -4
  160. package/docs/debugging.md +34 -12
  161. package/docs/deployment.md +8 -8
  162. package/docs/examples/scoped-agent.md +3 -3
  163. package/docs/groups.md +57 -3
  164. package/docs/guarantees.md +37 -10
  165. package/docs/how-it-works.md +29 -5
  166. package/docs/idempotency.md +6 -6
  167. package/docs/identity.md +22 -23
  168. package/docs/index.md +8 -8
  169. package/docs/integration-guide.md +14 -3
  170. package/docs/mcp.md +7 -7
  171. package/docs/migration.md +34 -15
  172. package/docs/projects.md +1 -1
  173. package/docs/react.md +19 -8
  174. package/docs/sessions.md +1 -1
  175. package/docs/webhooks.md +9 -9
  176. package/llms.txt +4 -4
  177. package/package.json +13 -20
  178. package/dist/cli.cjs +0 -288600
  179. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  180. package/dist/testing/fixtures/bootstrap.js +0 -59
  181. package/dist/testing/fixtures/deltas.d.ts +0 -83
  182. package/dist/testing/fixtures/deltas.js +0 -136
  183. package/dist/testing/fixtures/httpResponses.d.ts +0 -70
  184. package/dist/testing/fixtures/httpResponses.js +0 -90
  185. package/dist/testing/fixtures/models.d.ts +0 -83
  186. package/dist/testing/fixtures/models.js +0 -272
  187. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  188. package/dist/testing/helpers/reactWrapper.js +0 -67
  189. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  190. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  191. package/dist/testing/helpers/wait.d.ts +0 -30
  192. package/dist/testing/helpers/wait.js +0 -49
  193. package/dist/testing/index.d.ts +0 -23
  194. package/dist/testing/index.js +0 -33
  195. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  196. package/dist/testing/mocks/FakeDatabase.js +0 -10
  197. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  198. package/dist/testing/mocks/MockMutationExecutor.js +0 -186
  199. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  200. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  201. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  202. package/dist/testing/mocks/MockSyncContext.js +0 -72
  203. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  204. package/dist/testing/mocks/MockSyncStore.js +0 -171
  205. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  206. package/dist/testing/mocks/MockWebSocket.js +0 -118
  207. package/docs/interaction-model.md +0 -99
  208. /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
  209. /package/dist/{core → query}/QueryProcessor.js +0 -0
  210. /package/dist/{core/storeContract.js → storeContract.js} +0 -0
  211. /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
  212. /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
  213. /package/dist/{source → transaction}/footprint.d.ts +0 -0
  214. /package/dist/{source → transaction}/footprint.js +0 -0
  215. /package/dist/{core → views}/ViewRegistry.js +0 -0
package/dist/Model.d.ts CHANGED
@@ -246,9 +246,16 @@ export declare abstract class Model {
246
246
  * written, `onWrite(key, oldValue, newValue)` is invoked with the value
247
247
  * captured immediately before assignment. `applyChanges` passes a hook
248
248
  * that records the change in `modifiedProperties`; `updateFromData`
249
- * passes none (hydration must not generate outbound mutations). This
250
- * is the single source of mutation tracking now that the `mobx-setup`
251
- * `observe()` bridge has been removed (one write path: the SDK proxy).
249
+ * passes none (hydration must not generate outbound mutations).
250
+ *
251
+ * This is not the ONLY tracking path. `mobxSetup`'s `observe()` bridge is
252
+ * live and forwards a DIRECT assignment (`layer.position = next`) to
253
+ * `propertyChanged` as well — product code writes model fields directly in
254
+ * plenty of places (keyboard nudge, formatting, AI tools), and those writes
255
+ * have to reach the server and the undo stream too. The two paths compose:
256
+ * a direct write establishes the first-old-wins baseline, and a later
257
+ * `applyChanges` carrying the value the model already holds is a no-op that
258
+ * leaves that baseline intact.
252
259
  */
253
260
  private assignFieldsFromData;
254
261
  /**
@@ -260,10 +267,13 @@ export declare abstract class Model {
260
267
  * brand-new outbound mutation and the record would echo forever. For a
261
268
  * local user edit, use `applyChanges` instead.
262
269
  *
263
- * Suppression is belt-and-suspenders: we pass no `onWrite` hook AND
264
- * clear/restore `modifiedProperties` around the assignment, so any
265
- * remaining `mobx-setup` `observe()` side-channel writes are discarded
266
- * too. (The clear/restore is a harmless no-op once that bridge is gone.)
270
+ * Suppression takes two forms and BOTH are load-bearing: we pass no
271
+ * `onWrite` hook, and we clear/restore `modifiedProperties` around the
272
+ * assignment. The second is what catches `mobxSetup`'s `observe()` bridge,
273
+ * which fires on the assignment itself and would otherwise record an
274
+ * inbound delta as a local edit — queueing an outbound mutation that echoes
275
+ * forever, and putting a collaborator's change on your undo stack. Do not
276
+ * remove it while that bridge exists (`mobxSetup.M1`).
267
277
  */
268
278
  updateFromData(data: ModelData): void;
269
279
  /**
package/dist/Model.js CHANGED
@@ -428,9 +428,16 @@ export class Model {
428
428
  * written, `onWrite(key, oldValue, newValue)` is invoked with the value
429
429
  * captured immediately before assignment. `applyChanges` passes a hook
430
430
  * that records the change in `modifiedProperties`; `updateFromData`
431
- * passes none (hydration must not generate outbound mutations). This
432
- * is the single source of mutation tracking now that the `mobx-setup`
433
- * `observe()` bridge has been removed (one write path: the SDK proxy).
431
+ * passes none (hydration must not generate outbound mutations).
432
+ *
433
+ * This is not the ONLY tracking path. `mobxSetup`'s `observe()` bridge is
434
+ * live and forwards a DIRECT assignment (`layer.position = next`) to
435
+ * `propertyChanged` as well — product code writes model fields directly in
436
+ * plenty of places (keyboard nudge, formatting, AI tools), and those writes
437
+ * have to reach the server and the undo stream too. The two paths compose:
438
+ * a direct write establishes the first-old-wins baseline, and a later
439
+ * `applyChanges` carrying the value the model already holds is a no-op that
440
+ * leaves that baseline intact.
434
441
  */
435
442
  assignFieldsFromData(data, onWrite) {
436
443
  // Update properties with safety checks for read-only/computed accessors
@@ -489,10 +496,13 @@ export class Model {
489
496
  * brand-new outbound mutation and the record would echo forever. For a
490
497
  * local user edit, use `applyChanges` instead.
491
498
  *
492
- * Suppression is belt-and-suspenders: we pass no `onWrite` hook AND
493
- * clear/restore `modifiedProperties` around the assignment, so any
494
- * remaining `mobx-setup` `observe()` side-channel writes are discarded
495
- * too. (The clear/restore is a harmless no-op once that bridge is gone.)
499
+ * Suppression takes two forms and BOTH are load-bearing: we pass no
500
+ * `onWrite` hook, and we clear/restore `modifiedProperties` around the
501
+ * assignment. The second is what catches `mobxSetup`'s `observe()` bridge,
502
+ * which fires on the assignment itself and would otherwise record an
503
+ * inbound delta as a local edit — queueing an outbound mutation that echoes
504
+ * forever, and putting a collaborator's change on your undo stack. Do not
505
+ * remove it while that bridge exists (`mobxSetup.M1`).
496
506
  */
497
507
  updateFromData(data) {
498
508
  if (this.isDisposed) {
@@ -8,6 +8,7 @@
8
8
  * another model that is registered later.
9
9
  */
10
10
  import { type ModelMetadata, type PropertyMetadata, type ReferenceMetadata, LoadStrategy } from './transaction/types/index.js';
11
+ import type { RuntimeContext } from './RuntimeContext.js';
11
12
  import type { Model } from './Model.js';
12
13
  import type { ConcreteModelConstructor } from './BaseSyncedStore.js';
13
14
  /**
@@ -61,6 +62,8 @@ export interface BackReferenceMetadata {
61
62
  interface RegistryConfig {
62
63
  validateOnRegister?: boolean;
63
64
  allowLateReferences?: boolean;
65
+ /** The owning client's runtime. Defaults to the module-global bridge. */
66
+ runtime?: RuntimeContext;
64
67
  }
65
68
  /** Set the active ModelRegistry instance (called by createSyncEngine) */
66
69
  export declare function setActiveRegistry(registry: ModelRegistry): void;
@@ -80,6 +83,7 @@ export declare class ModelRegistry {
80
83
  private backReferences;
81
84
  private schemaHash?;
82
85
  private config;
86
+ private readonly runtime;
83
87
  private registeredModels;
84
88
  private pendingHashUpdate;
85
89
  constructor(config?: RegistryConfig);
@@ -9,7 +9,7 @@
9
9
  */
10
10
  // Removed Node.js crypto import for browser compatibility
11
11
  import { PropertyType, LoadStrategy, } from './transaction/types/index.js';
12
- import { getContext } from './context.js';
12
+ import { globalRuntime } from './context.js';
13
13
  import { AbloValidationError } from './transaction/errors.js';
14
14
  /**
15
15
  * Module-level active registry. Set by createSyncEngine so that Model instances
@@ -51,6 +51,7 @@ export class ModelRegistry {
51
51
  backReferences = new Map();
52
52
  schemaHash;
53
53
  config;
54
+ runtime;
54
55
  registeredModels = new Set();
55
56
  pendingHashUpdate = false;
56
57
  constructor(config = {}) {
@@ -58,6 +59,7 @@ export class ModelRegistry {
58
59
  validateOnRegister: config.validateOnRegister ?? true,
59
60
  allowLateReferences: config.allowLateReferences ?? true,
60
61
  };
62
+ this.runtime = config.runtime ?? globalRuntime;
61
63
  }
62
64
  validateModelConstructor(name, constructor) {
63
65
  if (typeof constructor !== 'function') {
@@ -71,7 +73,7 @@ export class ModelRegistry {
71
73
  const required = ['updateFromData', 'toJSON', 'getModelName'];
72
74
  for (const method of required) {
73
75
  if (typeof prototype[method] !== 'function') {
74
- getContext().logger.debug('Model missing required method', name, { method });
76
+ this.runtime.logger.debug('Model missing required method', name, { method });
75
77
  }
76
78
  }
77
79
  }
@@ -99,7 +101,7 @@ export class ModelRegistry {
99
101
  this.pendingReferences.set(targetName, pending);
100
102
  }
101
103
  pending.push({ modelName, propertyName, metadata });
102
- getContext().logger.debug('Reference deferred', `${modelName}.${propertyName}`, { targetModel: targetName });
104
+ this.runtime.logger.debug('Reference deferred', `${modelName}.${propertyName}`, { targetModel: targetName });
103
105
  }
104
106
  resolvePendingReferences(targetModelName) {
105
107
  const pending = this.pendingReferences.get(targetModelName);
@@ -108,12 +110,12 @@ export class ModelRegistry {
108
110
  for (const ref of pending) {
109
111
  try {
110
112
  this.completeReferenceRegistration(ref.modelName, ref.propertyName, ref.metadata);
111
- getContext().logger.debug('Reference resolved', `${ref.modelName}.${ref.propertyName}`, {
113
+ this.runtime.logger.debug('Reference resolved', `${ref.modelName}.${ref.propertyName}`, {
112
114
  targetModel: targetModelName,
113
115
  });
114
116
  }
115
117
  catch (error) {
116
- getContext().observability.breadcrumb(`Failed to resolve reference ${ref.modelName}.${ref.propertyName}`, 'sync.database', 'error', {
118
+ this.runtime.observability.breadcrumb(`Failed to resolve reference ${ref.modelName}.${ref.propertyName}`, 'sync.database', 'error', {
117
119
  error: error instanceof Error ? error.message : String(error),
118
120
  });
119
121
  }
@@ -154,10 +156,10 @@ export class ModelRegistry {
154
156
  }
155
157
  // Check for duplicate
156
158
  if (this.models.has(name)) {
157
- getContext().logger.debug('Model already registered, skipping', name);
159
+ this.runtime.logger.debug('Model already registered, skipping', name);
158
160
  return;
159
161
  }
160
- getContext().logger.debug('Registering model', name);
162
+ this.runtime.logger.debug('Registering model', name);
161
163
  // Register. The one cast in this file: input is any Model-subclass
162
164
  // constructor (validated above); registered classes are concrete
163
165
  // subclasses constructible with `data?` and carrying Model's statics —
@@ -180,7 +182,7 @@ export class ModelRegistry {
180
182
  this.resolvePendingReferences(name);
181
183
  // Invalidate schema hash
182
184
  this.schemaHash = undefined;
183
- getContext().logger.debug('Model registered', name, metadata);
185
+ this.runtime.logger.debug('Model registered', name, metadata);
184
186
  }
185
187
  /**
186
188
  * Register property with validation
@@ -201,7 +203,7 @@ export class ModelRegistry {
201
203
  if (existing) {
202
204
  if (this.arePropertiesCompatible(existing, metadata)) {
203
205
  // Properties are compatible, skip re-registration
204
- getContext().logger.debug('Property already registered (compatible)', `${modelName}.${propertyName}`);
206
+ this.runtime.logger.debug('Property already registered (compatible)', `${modelName}.${propertyName}`);
205
207
  return;
206
208
  }
207
209
  else {
@@ -210,7 +212,7 @@ export class ModelRegistry {
210
212
  }
211
213
  props.set(propertyName, metadata);
212
214
  this.schemaHash = undefined;
213
- getContext().logger.debug('Property registered', `${modelName}.${propertyName}`, metadata);
215
+ this.runtime.logger.debug('Property registered', `${modelName}.${propertyName}`, metadata);
214
216
  }
215
217
  /**
216
218
  * Register reference with lazy resolution
@@ -265,7 +267,7 @@ export class ModelRegistry {
265
267
  }
266
268
  // Reverse lookup (parent → children) is derived on demand by
267
269
  // `getChildModels`, which scans this map.
268
- getContext().logger.debug('BackReference registered', `${childModelName} -> ${metadata.parentModel}`, {
270
+ this.runtime.logger.debug('BackReference registered', `${childModelName} -> ${metadata.parentModel}`, {
269
271
  foreignKey: metadata.foreignKey,
270
272
  cascadeDelete: metadata.cascadeDelete,
271
273
  });
@@ -365,7 +367,7 @@ export class ModelRegistry {
365
367
  const sorted = JSON.stringify(schema, Object.keys(schema).sort());
366
368
  // Create hash - browser-compatible simple hash
367
369
  this.schemaHash = this.simpleHash(sorted);
368
- getContext().logger.debug('Schema hash updated', this.schemaHash);
370
+ this.runtime.logger.debug('Schema hash updated', this.schemaHash);
369
371
  return this.schemaHash;
370
372
  }
371
373
  /**
@@ -410,10 +412,10 @@ export class ModelRegistry {
410
412
  }
411
413
  const isValid = errors.length === 0;
412
414
  if (isValid) {
413
- getContext().logger.info('All model references are valid');
415
+ this.runtime.logger.info('All model references are valid');
414
416
  }
415
417
  else {
416
- getContext().observability.breadcrumb('Reference validation failed', 'sync.database', 'error');
418
+ this.runtime.observability.breadcrumb('Reference validation failed', 'sync.database', 'error');
417
419
  }
418
420
  return {
419
421
  valid: isValid,
@@ -458,7 +460,7 @@ export class ModelRegistry {
458
460
  this.constructorToModelName.clear();
459
461
  this.schemaHash = undefined;
460
462
  this.pendingHashUpdate = false;
461
- getContext().logger.info('ModelRegistry cleared');
463
+ this.runtime.logger.info('ModelRegistry cleared');
462
464
  }
463
465
  /**
464
466
  * Static wrapper for backward compatibility
@@ -6,10 +6,12 @@
6
6
  * firing, so returning to the tab emits a recovery signal the store can act on.
7
7
  */
8
8
  import { EventEmitter } from 'events';
9
+ import type { RuntimeContext } from './RuntimeContext.js';
9
10
  export declare class NetworkMonitor extends EventEmitter {
11
+ private readonly runtime;
10
12
  private isOnline;
11
13
  private lastOnlineCheck;
12
- constructor();
14
+ constructor(runtime?: RuntimeContext);
13
15
  private handleOnline;
14
16
  private handleOffline;
15
17
  /**
@@ -6,15 +6,17 @@
6
6
  * firing, so returning to the tab emits a recovery signal the store can act on.
7
7
  */
8
8
  import { EventEmitter } from 'events';
9
- import { getContext } from './context.js';
9
+ import { globalRuntime } from './context.js';
10
10
  export class NetworkMonitor extends EventEmitter {
11
+ runtime;
11
12
  // Only `navigator.onLine === false` means offline. Node 18+ exposes a global
12
13
  // `navigator` with `onLine === undefined`, so the naive `navigator.onLine`
13
14
  // would seed `false` (offline) on every server client — start optimistic.
14
15
  isOnline = !(typeof navigator !== 'undefined' && navigator.onLine === false);
15
16
  lastOnlineCheck = new Date();
16
- constructor() {
17
+ constructor(runtime = globalRuntime) {
17
18
  super();
19
+ this.runtime = runtime;
18
20
  this.setupListeners();
19
21
  }
20
22
  handleOnline = () => {
@@ -22,7 +24,7 @@ export class NetworkMonitor extends EventEmitter {
22
24
  this.isOnline = true;
23
25
  this.lastOnlineCheck = new Date();
24
26
  if (wasOffline) {
25
- getContext().logger.info('Network connection restored');
27
+ this.runtime.logger.info('Network connection restored');
26
28
  this.emit('online');
27
29
  }
28
30
  };
@@ -32,7 +34,7 @@ export class NetworkMonitor extends EventEmitter {
32
34
  if (wasOnline) {
33
35
  // Symmetric with 'Network connection restored' (info) — expected,
34
36
  // transient connectivity state, not an actionable warning.
35
- getContext().logger.info('Network connection lost');
37
+ this.runtime.logger.info('Network connection lost');
36
38
  this.emit('offline');
37
39
  }
38
40
  };
@@ -49,7 +51,7 @@ export class NetworkMonitor extends EventEmitter {
49
51
  this.isOnline = navigator.onLine;
50
52
  this.lastOnlineCheck = new Date();
51
53
  if (this.isOnline) {
52
- getContext().logger.info('Tab became visible with network available — emitting visibility_online');
54
+ this.runtime.logger.info('Tab became visible with network available — emitting visibility_online');
53
55
  this.emit('visibility_online');
54
56
  }
55
57
  };
@@ -9,6 +9,8 @@
9
9
  */
10
10
  import { InstanceCache } from './InstanceCache.js';
11
11
  import { Model } from './Model.js';
12
+ import type { AppliedChange } from './transaction/plugin.js';
13
+ import type { RuntimeContext } from './RuntimeContext.js';
12
14
  import { EventEmitter } from 'events';
13
15
  import { MutationQueue } from './transactions/mutations/MutationQueue.js';
14
16
  import { type CommitLatencySample } from './transactions/mutations/commitLatency.js';
@@ -42,6 +44,7 @@ export interface RehydrationStats {
42
44
  elapsedMs: number;
43
45
  }
44
46
  export declare class SyncClient extends EventEmitter {
47
+ private readonly runtime;
45
48
  private objectPool;
46
49
  private database;
47
50
  private get mutationExecutor();
@@ -77,7 +80,7 @@ export declare class SyncClient extends EventEmitter {
77
80
  * responses, and snapshots and claims read `readFloor`.
78
81
  */
79
82
  readonly position: LogPosition;
80
- constructor(objectPool: InstanceCache, database: Database, commitOutbox?: DurableWriteStore, commitOutboxNamespace?: string);
83
+ constructor(objectPool: InstanceCache, database: Database, commitOutbox?: DurableWriteStore, commitOutboxNamespace?: string, runtime?: RuntimeContext);
81
84
  /**
82
85
  * Setup network monitoring handlers
83
86
  */
@@ -508,20 +511,7 @@ export declare class SyncClient extends EventEmitter {
508
511
  * consumers; `Ablo` is the public surface.
509
512
  */
510
513
  getMutationQueue(): MutationQueue;
511
- applyDeltaBatchToPool(dbResults: {
512
- action: string;
513
- modelName: string;
514
- modelId: string;
515
- data?: Record<string, unknown> | null;
516
- /**
517
- * Server-stamped transaction id, echoing the client's commit op
518
- * id. Used by echo detection to recognize "this is the
519
- * confirmation of a mutation I've already applied locally."
520
- * Optional because system-emitted deltas (sync_group changes,
521
- * schema-derived deltas, etc.) don't have a client transaction.
522
- */
523
- transactionId?: string;
524
- }[], enrichRelations: (modelName: string, data: Record<string, unknown>) => Record<string, unknown>): void;
514
+ applyDeltaBatchToPool(dbResults: readonly AppliedChange[], enrichRelations: (modelName: string, data: Record<string, unknown>) => Record<string, unknown>): void;
525
515
  /**
526
516
  * Apply bootstrap data to the InstanceCache with ghost removal.
527
517
  * Owns: model creation, batch upsert, ghost detection + removal.