@absolutejs/sync 1.21.0 → 1.22.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.
- package/dist/engine/index.js +38 -1
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +69 -0
- package/dist/index.js +38 -1
- package/dist/index.js.map +3 -3
- package/dist/testing.js +38 -1
- package/dist/testing.js.map +3 -3
- package/package.json +1 -1
|
@@ -297,6 +297,33 @@ export type SyncEngine = {
|
|
|
297
297
|
* Added in 1.19.0.
|
|
298
298
|
*/
|
|
299
299
|
importChangeLog: (snapshot: ChangeLogSnapshot) => number;
|
|
300
|
+
/**
|
|
301
|
+
* Reconstruct the state of registered tables as of a target
|
|
302
|
+
* timestamp by walking the change log forward and folding each op
|
|
303
|
+
* into a per-table view. Useful for forensic incident response
|
|
304
|
+
* ("what did the tenant see at 14:32?") and the "I deleted prod
|
|
305
|
+
* — restore us to 2h ago" recovery story.
|
|
306
|
+
*
|
|
307
|
+
* The reconstruction is exact when the log spans `targetAt` (i.e.
|
|
308
|
+
* the log's oldest entry is at version 1). When the log has been
|
|
309
|
+
* trimmed (`changeLogSize` / `changeLogRetainMs` evicted older
|
|
310
|
+
* entries) AND `targetAt` falls in the gap, the result is
|
|
311
|
+
* best-effort: state walked forward from the OLDEST retained
|
|
312
|
+
* entry, with `truncated: true` so the caller knows.
|
|
313
|
+
*
|
|
314
|
+
* Added in 1.22.0.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;
|
|
319
|
+
* const result = await engine.replayTo({ at: twoHoursAgo, tables: ['orders'] });
|
|
320
|
+
* if (result.truncated) {
|
|
321
|
+
* console.warn('Replay truncated — log retention window too short.');
|
|
322
|
+
* }
|
|
323
|
+
* console.log(result.rows.orders); // orders as of two hours ago
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
replayTo: (options: ReplayOptions) => Promise<ReplayResult>;
|
|
300
327
|
/**
|
|
301
328
|
* Subscribe to the live engine activity stream (changes, mutation outcomes,
|
|
302
329
|
* subscribe/unsubscribe). Returns an unsubscribe. Powers the devtools feed.
|
|
@@ -467,6 +494,48 @@ export type ChangeLogSnapshot = {
|
|
|
467
494
|
*/
|
|
468
495
|
exportedAt?: number;
|
|
469
496
|
};
|
|
497
|
+
/**
|
|
498
|
+
* Options for {@link SyncEngine.replayTo}. Added in 1.22.0.
|
|
499
|
+
*/
|
|
500
|
+
export type ReplayOptions = {
|
|
501
|
+
/**
|
|
502
|
+
* Target timestamp (`Date.now()`-shaped). The engine walks the
|
|
503
|
+
* change log forward, applying entries with `at <= targetAt`. The
|
|
504
|
+
* result is the state as-of `targetAt` (or as close as the log
|
|
505
|
+
* permits — see `truncated`).
|
|
506
|
+
*/
|
|
507
|
+
at: number;
|
|
508
|
+
/**
|
|
509
|
+
* Optional table filter. When set, only entries whose `table` is
|
|
510
|
+
* in this list are folded into the result; entries for other
|
|
511
|
+
* tables are skipped. Useful for "show me what `tasks` looked
|
|
512
|
+
* like at T" without paying to reconstruct every table.
|
|
513
|
+
*/
|
|
514
|
+
tables?: ReadonlyArray<string>;
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* Returned by {@link SyncEngine.replayTo}. Added in 1.22.0.
|
|
518
|
+
*
|
|
519
|
+
* - `rows` — per-table arrays of rows that existed as of `asOfAt`.
|
|
520
|
+
* Keys are table names; values are the row objects (in last-write
|
|
521
|
+
* order — last write wins for duplicate-keyed inserts).
|
|
522
|
+
* - `asOfVersion` / `asOfAt` — the version + wall-clock of the LAST
|
|
523
|
+
* entry folded into the result. May be earlier than `targetAt` if
|
|
524
|
+
* no entries existed between the last-included entry and the
|
|
525
|
+
* target.
|
|
526
|
+
* - `truncated` — `true` when the log has been trimmed past the
|
|
527
|
+
* target window (`changeLog[0].version > 1 && changeLog[0].at >
|
|
528
|
+
* targetAt`). In this case, `rows` represents the state walked
|
|
529
|
+
* forward from the OLDEST retained entry — NOT the actual state
|
|
530
|
+
* at `targetAt`. The caller should treat the result as
|
|
531
|
+
* "best-effort given retention window" and warn the operator.
|
|
532
|
+
*/
|
|
533
|
+
export type ReplayResult = {
|
|
534
|
+
asOfVersion: number;
|
|
535
|
+
asOfAt: number;
|
|
536
|
+
rows: Record<string, ReadonlyArray<unknown>>;
|
|
537
|
+
truncated: boolean;
|
|
538
|
+
};
|
|
470
539
|
export type SyncEngineOptions = {
|
|
471
540
|
/**
|
|
472
541
|
* Stable identifier for this engine instance. Defaults to a per-process
|
package/dist/index.js
CHANGED
|
@@ -2801,6 +2801,43 @@ var createSyncEngine = (options = {}) => {
|
|
|
2801
2801
|
version
|
|
2802
2802
|
}),
|
|
2803
2803
|
importChangeLog,
|
|
2804
|
+
replayTo: async ({ at, tables }) => {
|
|
2805
|
+
const filterTables = tables !== undefined ? new Set(tables) : undefined;
|
|
2806
|
+
const state = new Map;
|
|
2807
|
+
let asOfVersion = 0;
|
|
2808
|
+
let asOfAt = 0;
|
|
2809
|
+
const oldest = changeLog[0];
|
|
2810
|
+
const truncated = oldest !== undefined && oldest.version > 1 && oldest.at > at;
|
|
2811
|
+
for (const entry of changeLog) {
|
|
2812
|
+
if (entry.at > at)
|
|
2813
|
+
break;
|
|
2814
|
+
if (filterTables !== undefined && !filterTables.has(entry.table)) {
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
let tableState = state.get(entry.table);
|
|
2818
|
+
if (tableState === undefined) {
|
|
2819
|
+
tableState = new Map;
|
|
2820
|
+
state.set(entry.table, tableState);
|
|
2821
|
+
}
|
|
2822
|
+
const reader = readers.get(entry.table);
|
|
2823
|
+
const key = reader?.key?.(entry.change.row) ?? entry.change.row?.id;
|
|
2824
|
+
if (key === undefined) {
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
if (entry.change.op === "delete") {
|
|
2828
|
+
tableState.delete(key);
|
|
2829
|
+
} else {
|
|
2830
|
+
tableState.set(key, entry.change.row);
|
|
2831
|
+
}
|
|
2832
|
+
asOfVersion = entry.version;
|
|
2833
|
+
asOfAt = entry.at;
|
|
2834
|
+
}
|
|
2835
|
+
const rows = {};
|
|
2836
|
+
for (const [table, map] of state) {
|
|
2837
|
+
rows[table] = [...map.values()];
|
|
2838
|
+
}
|
|
2839
|
+
return { asOfAt, asOfVersion, rows, truncated };
|
|
2840
|
+
},
|
|
2804
2841
|
metrics: () => {
|
|
2805
2842
|
const now = Date.now();
|
|
2806
2843
|
const byCollection = {};
|
|
@@ -3218,5 +3255,5 @@ export {
|
|
|
3218
3255
|
createPresenceHub
|
|
3219
3256
|
};
|
|
3220
3257
|
|
|
3221
|
-
//# debugId=
|
|
3258
|
+
//# debugId=6DBA546C7A7B1DC564756E2164756E21
|
|
3222
3259
|
//# sourceMappingURL=index.js.map
|