@absolutejs/sync 1.18.1 → 1.19.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.
@@ -266,6 +266,36 @@ export type SyncEngine = {
266
266
  * Added in 1.13.0.
267
267
  */
268
268
  metrics: () => EngineMetrics;
269
+ /**
270
+ * Capture the engine's change log + version as a serializable
271
+ * {@link ChangeLogSnapshot} the host can persist (disk, S3, the cluster
272
+ * bus) and restore on the next boot via
273
+ * {@link SyncEngineOptions.initialChangeLog} or
274
+ * {@link SyncEngine.importChangeLog}. The receiving engine MUST share this
275
+ * engine's `instanceId` — otherwise the resume contract silently breaks.
276
+ *
277
+ * Cheap: the snapshot's `entries` is a shallow copy of the bounded log
278
+ * (capped by `changeLogSize` / `changeLogRetainMs`). Call on a timer or on
279
+ * graceful shutdown — both are fine; the snapshot is monotonic in commit
280
+ * order, so a partial roll-forward (apply entries newer than the snapshot
281
+ * from another source) is safe.
282
+ *
283
+ * Added in 1.19.0.
284
+ */
285
+ exportChangeLog: () => ChangeLogSnapshot;
286
+ /**
287
+ * Adopt a {@link ChangeLogSnapshot} into a running engine that has not yet
288
+ * committed any local changes (its `version` is 0). The snapshot's
289
+ * `instanceId` MUST match this engine's `instanceId`. Throws otherwise.
290
+ *
291
+ * Convenience for hosts that want to set up the engine, register surfaces,
292
+ * AND THEN restore. Equivalent to passing the snapshot via
293
+ * `createSyncEngine({ initialChangeLog })` if you have it at construction
294
+ * time. Returns the number of entries imported.
295
+ *
296
+ * Added in 1.19.0.
297
+ */
298
+ importChangeLog: (snapshot: ChangeLogSnapshot) => number;
269
299
  /**
270
300
  * Subscribe to the live engine activity stream (changes, mutation outcomes,
271
301
  * subscribe/unsubscribe). Returns an unsubscribe. Powers the devtools feed.
@@ -384,6 +414,35 @@ export declare class CdcConsumerSlowError extends Error {
384
414
  readonly lastDeliveredVersion: number;
385
415
  constructor(maxBuffer: number, lastDeliveredVersion: number);
386
416
  }
417
+ /**
418
+ * Serializable snapshot of an engine's change log + monotonic version, returned
419
+ * by {@link SyncEngine.exportChangeLog} and consumed by
420
+ * {@link SyncEngineOptions.initialChangeLog} or
421
+ * {@link SyncEngine.importChangeLog}.
422
+ *
423
+ * The PaaS host persists this on shard rotation (every N seconds or on graceful
424
+ * shutdown) and hands it back to the replacement engine so resume cursors
425
+ * referencing this `instanceId` keep working past the restart. Bounded by the
426
+ * receiving engine's `changeLogSize` + `changeLogRetainMs` policies — entries
427
+ * that exceed either cap on import are trimmed exactly as if they had been
428
+ * logged live.
429
+ *
430
+ * Added in 1.19.0.
431
+ */
432
+ export type ChangeLogSnapshot = {
433
+ /** The exporting engine's `instanceId`. Receiver MUST match. */
434
+ instanceId: string;
435
+ /** The exporting engine's monotonic version at snapshot time. */
436
+ version: number;
437
+ /** Every retained log entry, in commit order (oldest first). */
438
+ entries: ReadonlyArray<LoggedChange>;
439
+ /**
440
+ * Optional version-stamp the host may use to compare snapshots without
441
+ * deserializing the entries (e.g. for incremental persistence). Set to
442
+ * `Date.now()` at export time. Receivers ignore this field.
443
+ */
444
+ exportedAt?: number;
445
+ };
387
446
  export type SyncEngineOptions = {
388
447
  /**
389
448
  * Stable identifier for this engine instance. Defaults to a per-process
@@ -482,6 +541,22 @@ export type SyncEngineOptions = {
482
541
  * @see {@link BridgeFetchConfig}
483
542
  */
484
543
  bridgeFetch?: BridgeFetchConfig;
544
+ /**
545
+ * Seed the engine's change log on boot from a prior snapshot — produced by
546
+ * {@link SyncEngine.exportChangeLog} on the previous instance, persisted by
547
+ * the host across a shard reboot, then handed back here. Cursors that
548
+ * referenced this engine's `instanceId` stay resumable past the restart
549
+ * (provided their last-seen point still lives in the retained log).
550
+ *
551
+ * The snapshot's `instanceId` MUST match `options.instanceId` (otherwise
552
+ * `createSyncEngine` throws — a wrong-id restore would silently break the
553
+ * resume contract). Snapshot `version` becomes this engine's local
554
+ * monotonic version; entries are inserted in version order. Subscribers,
555
+ * permissions, schemas, schedules, packs, mutations, and the reactive
556
+ * cache are NOT in the snapshot — re-register them as normal after
557
+ * `createSyncEngine` returns. Added in 1.19.0.
558
+ */
559
+ initialChangeLog?: ChangeLogSnapshot;
485
560
  };
486
561
  /**
487
562
  * The Tier 3 sync engine: a registry of collections plus the view syncer. It is
package/dist/index.js CHANGED
@@ -1309,6 +1309,31 @@ var createSyncEngine = (options = {}) => {
1309
1309
  const runInTransaction = options.transaction;
1310
1310
  const instanceId = options.instanceId ?? globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
1311
1311
  let clusterBus;
1312
+ const importChangeLog = (snapshot) => {
1313
+ if (version !== 0) {
1314
+ throw new Error(`[sync] importChangeLog: engine already has version ${version}; ` + `restore must happen before any local writes commit.`);
1315
+ }
1316
+ if (snapshot.instanceId !== instanceId) {
1317
+ throw new Error(`[sync] importChangeLog: snapshot instanceId "${snapshot.instanceId}" ` + `does not match this engine's instanceId "${instanceId}". ` + `Pass options.instanceId = "${snapshot.instanceId}" to createSyncEngine.`);
1318
+ }
1319
+ version = snapshot.version;
1320
+ for (const entry of snapshot.entries) {
1321
+ changeLog.push(entry);
1322
+ }
1323
+ while (changeLog.length > changeLogSize) {
1324
+ changeLog.shift();
1325
+ }
1326
+ if (changeLogRetainMs !== null && changeLogRetainMs > 0) {
1327
+ const cutoff = Date.now() - changeLogRetainMs;
1328
+ while (changeLog.length > 0 && changeLog[0].at < cutoff) {
1329
+ changeLog.shift();
1330
+ }
1331
+ }
1332
+ return snapshot.entries.length;
1333
+ };
1334
+ if (options.initialChangeLog !== undefined) {
1335
+ importChangeLog(options.initialChangeLog);
1336
+ }
1312
1337
  const broadcast = (changes, originVersion) => {
1313
1338
  if (clusterBus !== undefined && changes.length > 0) {
1314
1339
  clusterBus.publish({ changes, origin: instanceId, originVersion });
@@ -1832,13 +1857,20 @@ var createSyncEngine = (options = {}) => {
1832
1857
  oldestPerOrigin.set(entry.origin, entry.originVersion);
1833
1858
  }
1834
1859
  }
1860
+ const oldestLogVersion = changeLog[0]?.version;
1835
1861
  for (const [origin, lastSeen] of Object.entries(sinceVec)) {
1836
1862
  if (origin === instanceId) {
1837
1863
  if (lastSeen >= version)
1838
1864
  continue;
1839
1865
  const oldestLocal = oldestPerOrigin.get(instanceId);
1840
- if (oldestLocal === undefined || oldestLocal > lastSeen + 1)
1866
+ if (oldestLocal !== undefined) {
1867
+ if (oldestLocal > lastSeen + 1)
1868
+ return false;
1869
+ continue;
1870
+ }
1871
+ if (oldestLogVersion !== undefined && oldestLogVersion > lastSeen + 1) {
1841
1872
  return false;
1873
+ }
1842
1874
  } else {
1843
1875
  const oldestPeer = oldestPerOrigin.get(origin);
1844
1876
  if (oldestPeer === undefined) {
@@ -2571,6 +2603,13 @@ var createSyncEngine = (options = {}) => {
2571
2603
  }))
2572
2604
  };
2573
2605
  },
2606
+ exportChangeLog: () => ({
2607
+ entries: changeLog.slice(),
2608
+ exportedAt: Date.now(),
2609
+ instanceId,
2610
+ version
2611
+ }),
2612
+ importChangeLog,
2574
2613
  metrics: () => {
2575
2614
  const now = Date.now();
2576
2615
  const byCollection = {};
@@ -2986,5 +3025,5 @@ export {
2986
3025
  createPresenceHub
2987
3026
  };
2988
3027
 
2989
- //# debugId=8942CF1B0DE51E1664756E2164756E21
3028
+ //# debugId=EB8C381967D0311A64756E2164756E21
2990
3029
  //# sourceMappingURL=index.js.map