@absolutejs/sync 1.20.1 → 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 +289 -164
- package/dist/engine/index.js.map +5 -4
- package/dist/engine/syncEngine.d.ts +91 -0
- package/dist/index.js +289 -164
- package/dist/index.js.map +5 -4
- package/dist/testing.js +289 -164
- package/dist/testing.js.map +5 -4
- package/package.json +5 -1
package/dist/engine/index.js
CHANGED
|
@@ -1119,6 +1119,59 @@ class RetriesExhaustedError extends Error {
|
|
|
1119
1119
|
this.cause = cause;
|
|
1120
1120
|
}
|
|
1121
1121
|
}
|
|
1122
|
+
// node_modules/@absolutejs/telemetry/dist/index.js
|
|
1123
|
+
var NOOP_SPAN_CONTEXT = {
|
|
1124
|
+
spanId: "0000000000000000",
|
|
1125
|
+
traceFlags: 0,
|
|
1126
|
+
traceId: "00000000000000000000000000000000"
|
|
1127
|
+
};
|
|
1128
|
+
var noopSpan = {
|
|
1129
|
+
addEvent: () => noopSpan,
|
|
1130
|
+
end: () => {},
|
|
1131
|
+
isRecording: () => false,
|
|
1132
|
+
recordException: () => {},
|
|
1133
|
+
setAttribute: () => noopSpan,
|
|
1134
|
+
setAttributes: () => noopSpan,
|
|
1135
|
+
setStatus: () => noopSpan,
|
|
1136
|
+
spanContext: () => NOOP_SPAN_CONTEXT,
|
|
1137
|
+
updateName: () => noopSpan
|
|
1138
|
+
};
|
|
1139
|
+
var startActiveSpanNoop = (_name, optionsOrFn, maybeFn) => {
|
|
1140
|
+
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
1141
|
+
return fn(noopSpan);
|
|
1142
|
+
};
|
|
1143
|
+
var noopTracer = {
|
|
1144
|
+
startActiveSpan: startActiveSpanNoop,
|
|
1145
|
+
startSpan: () => noopSpan
|
|
1146
|
+
};
|
|
1147
|
+
var tracerOrNoop = (provider, name, version) => provider !== undefined ? provider.getTracer(name, version) : noopTracer;
|
|
1148
|
+
var ABS_ATTRS = {
|
|
1149
|
+
tenant: "abs.tenant",
|
|
1150
|
+
shardId: "abs.shard.id",
|
|
1151
|
+
engineId: "abs.engine.id",
|
|
1152
|
+
collection: "abs.collection",
|
|
1153
|
+
mutation: "abs.mutation",
|
|
1154
|
+
mutationAttempt: "abs.mutation.attempt",
|
|
1155
|
+
subscriptionId: "abs.subscription.id",
|
|
1156
|
+
batchSize: "abs.batch.size",
|
|
1157
|
+
clusterMessageOrigin: "abs.cluster.origin",
|
|
1158
|
+
jobId: "abs.job.id",
|
|
1159
|
+
jobKind: "abs.job.kind",
|
|
1160
|
+
jobAttempt: "abs.job.attempt",
|
|
1161
|
+
jobMaxAttempts: "abs.job.max_attempts",
|
|
1162
|
+
workerId: "abs.worker.id",
|
|
1163
|
+
runtimeKey: "abs.runtime.key",
|
|
1164
|
+
runtimePid: "abs.runtime.pid",
|
|
1165
|
+
runtimePort: "abs.runtime.port",
|
|
1166
|
+
runtimeExitReason: "abs.runtime.exit_reason",
|
|
1167
|
+
runtimeReadinessMs: "abs.runtime.readiness_ms",
|
|
1168
|
+
routeShard: "abs.route.shard",
|
|
1169
|
+
routeDecision: "abs.route.decision",
|
|
1170
|
+
secretName: "abs.secret.name",
|
|
1171
|
+
secretFingerprint: "abs.secret.fingerprint",
|
|
1172
|
+
auditKind: "abs.audit.kind"
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1122
1175
|
// src/engine/sandbox.ts
|
|
1123
1176
|
var isolatedJscModule;
|
|
1124
1177
|
var loadIsolatedJsc = async () => {
|
|
@@ -1648,6 +1701,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1648
1701
|
const runInTransaction = options.transaction;
|
|
1649
1702
|
const instanceId = options.instanceId ?? globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
|
|
1650
1703
|
let clusterBus;
|
|
1704
|
+
const tracer = tracerOrNoop(options.tracerProvider, "@absolutejs/sync");
|
|
1651
1705
|
const importChangeLog = (snapshot) => {
|
|
1652
1706
|
if (version !== 0) {
|
|
1653
1707
|
throw new Error(`[sync] importChangeLog: engine already has version ${version}; ` + `restore must happen before any local writes commit.`);
|
|
@@ -2439,107 +2493,124 @@ var createSyncEngine = (options = {}) => {
|
|
|
2439
2493
|
registry.set(collection.name, collection);
|
|
2440
2494
|
},
|
|
2441
2495
|
subscribe: async ({ collection, params, ctx, onDiff, since, signal }) => {
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
let slotHandedOff = false;
|
|
2496
|
+
const subscribeSpan = tracer.startSpan("sync.subscribe", {
|
|
2497
|
+
attributes: {
|
|
2498
|
+
[ABS_ATTRS.engineId]: instanceId,
|
|
2499
|
+
[ABS_ATTRS.collection]: collection
|
|
2500
|
+
}
|
|
2501
|
+
});
|
|
2449
2502
|
try {
|
|
2450
|
-
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2503
|
+
checkAborted(signal);
|
|
2504
|
+
const registered = registry.get(collection);
|
|
2505
|
+
if (registered === undefined) {
|
|
2506
|
+
throw new Error(`Unknown collection "${collection}"`);
|
|
2507
|
+
}
|
|
2508
|
+
const tenantSlot = acquireSubscriptionSlot(ctx, { collection });
|
|
2509
|
+
let slotHandedOff = false;
|
|
2510
|
+
try {
|
|
2511
|
+
const typedOnDiff = onDiff;
|
|
2512
|
+
const subscribeSet = subsFor(collection);
|
|
2513
|
+
const wrapReturn = (sub) => {
|
|
2514
|
+
checkAborted(signal);
|
|
2515
|
+
const innerUnsubscribe = sub.unsubscribe;
|
|
2516
|
+
let released = false;
|
|
2517
|
+
const wrappedUnsubscribe = () => {
|
|
2518
|
+
if (released)
|
|
2519
|
+
return;
|
|
2520
|
+
released = true;
|
|
2521
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2522
|
+
innerUnsubscribe();
|
|
2523
|
+
};
|
|
2524
|
+
const wrapped = { ...sub, unsubscribe: wrappedUnsubscribe };
|
|
2525
|
+
linkAbortToUnsubscribe(signal, wrappedUnsubscribe);
|
|
2526
|
+
slotHandedOff = true;
|
|
2527
|
+
return wrapped;
|
|
2462
2528
|
};
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
const
|
|
2488
|
-
|
|
2489
|
-
|
|
2529
|
+
const registeredKind = registered.kind;
|
|
2530
|
+
if (registeredKind === "join") {
|
|
2531
|
+
const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2532
|
+
return wrapReturn(joined);
|
|
2533
|
+
}
|
|
2534
|
+
if (registeredKind === "graph") {
|
|
2535
|
+
const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2536
|
+
return wrapReturn(graphed);
|
|
2537
|
+
}
|
|
2538
|
+
if (registeredKind === "reactive") {
|
|
2539
|
+
const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2540
|
+
return wrapReturn(reactived);
|
|
2541
|
+
}
|
|
2542
|
+
if (registeredKind === "search") {
|
|
2543
|
+
const searched = await subscribeSearch(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
2544
|
+
return wrapReturn(searched);
|
|
2545
|
+
}
|
|
2546
|
+
const definition = registered;
|
|
2547
|
+
if (definition.authorize !== undefined) {
|
|
2548
|
+
const allowed = await definition.authorize(params, ctx);
|
|
2549
|
+
if (!allowed) {
|
|
2550
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
const key = definition.key ?? defaultKey;
|
|
2554
|
+
const match = definition.match;
|
|
2555
|
+
const tables = definition.tables ?? [collection];
|
|
2556
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2557
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2558
|
+
const rehydrate = async () => {
|
|
2559
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
2560
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2561
|
+
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2562
|
+
};
|
|
2563
|
+
const incremental = match !== undefined && tables.length === 1;
|
|
2564
|
+
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2565
|
+
const view = createMaterializedView({
|
|
2566
|
+
key,
|
|
2567
|
+
match: boundMatch
|
|
2568
|
+
});
|
|
2569
|
+
const resuming = since !== undefined && canResume(since, incremental);
|
|
2570
|
+
view.hydrate([...await rehydrate()]);
|
|
2571
|
+
const atVersion = version;
|
|
2572
|
+
const subscription = {
|
|
2573
|
+
kind: "view",
|
|
2574
|
+
collection,
|
|
2575
|
+
view,
|
|
2576
|
+
incremental,
|
|
2577
|
+
rehydrate,
|
|
2578
|
+
key,
|
|
2579
|
+
onDiff: typedOnDiff
|
|
2580
|
+
};
|
|
2581
|
+
subscribeSet.add(subscription);
|
|
2582
|
+
const unsubscribe = () => {
|
|
2583
|
+
subscribeSet.delete(subscription);
|
|
2584
|
+
};
|
|
2585
|
+
if (resuming) {
|
|
2586
|
+
return wrapReturn({
|
|
2587
|
+
initial: [],
|
|
2588
|
+
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2589
|
+
cursor: currentCursor(),
|
|
2590
|
+
version: atVersion,
|
|
2591
|
+
unsubscribe
|
|
2592
|
+
});
|
|
2490
2593
|
}
|
|
2491
|
-
}
|
|
2492
|
-
const key = definition.key ?? defaultKey;
|
|
2493
|
-
const match = definition.match;
|
|
2494
|
-
const tables = definition.tables ?? [collection];
|
|
2495
|
-
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2496
|
-
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2497
|
-
const rehydrate = async () => {
|
|
2498
|
-
const raw = [...await definition.hydrate(params, ctx)];
|
|
2499
|
-
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2500
|
-
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2501
|
-
};
|
|
2502
|
-
const incremental = match !== undefined && tables.length === 1;
|
|
2503
|
-
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
2504
|
-
const view = createMaterializedView({
|
|
2505
|
-
key,
|
|
2506
|
-
match: boundMatch
|
|
2507
|
-
});
|
|
2508
|
-
const resuming = since !== undefined && canResume(since, incremental);
|
|
2509
|
-
view.hydrate([...await rehydrate()]);
|
|
2510
|
-
const atVersion = version;
|
|
2511
|
-
const subscription = {
|
|
2512
|
-
kind: "view",
|
|
2513
|
-
collection,
|
|
2514
|
-
view,
|
|
2515
|
-
incremental,
|
|
2516
|
-
rehydrate,
|
|
2517
|
-
key,
|
|
2518
|
-
onDiff: typedOnDiff
|
|
2519
|
-
};
|
|
2520
|
-
subscribeSet.add(subscription);
|
|
2521
|
-
const unsubscribe = () => {
|
|
2522
|
-
subscribeSet.delete(subscription);
|
|
2523
|
-
};
|
|
2524
|
-
if (resuming) {
|
|
2525
2594
|
return wrapReturn({
|
|
2526
|
-
initial:
|
|
2527
|
-
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
2595
|
+
initial: view.rows(),
|
|
2528
2596
|
cursor: currentCursor(),
|
|
2529
2597
|
version: atVersion,
|
|
2530
2598
|
unsubscribe
|
|
2531
2599
|
});
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
if (!slotHandedOff)
|
|
2602
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
2603
|
+
throw error;
|
|
2532
2604
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2605
|
+
} catch (spanError) {
|
|
2606
|
+
subscribeSpan.recordException(spanError);
|
|
2607
|
+
subscribeSpan.setStatus({
|
|
2608
|
+
code: 2,
|
|
2609
|
+
message: spanError instanceof Error ? spanError.message : String(spanError)
|
|
2538
2610
|
});
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
throw error;
|
|
2611
|
+
throw spanError;
|
|
2612
|
+
} finally {
|
|
2613
|
+
subscribeSpan.end();
|
|
2543
2614
|
}
|
|
2544
2615
|
},
|
|
2545
2616
|
hydrate: async (collection, params, ctx, options2) => {
|
|
@@ -2643,85 +2714,102 @@ var createSyncEngine = (options = {}) => {
|
|
|
2643
2714
|
},
|
|
2644
2715
|
migrate: (table, row) => migrateRow(table, row),
|
|
2645
2716
|
runMutation: async (name, args, ctx) => {
|
|
2646
|
-
const
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
if (mutation.authorize !== undefined) {
|
|
2651
|
-
const allowed = await mutation.authorize(args, ctx);
|
|
2652
|
-
if (!allowed) {
|
|
2653
|
-
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
2717
|
+
const span = tracer.startSpan("sync.runMutation", {
|
|
2718
|
+
attributes: {
|
|
2719
|
+
[ABS_ATTRS.engineId]: instanceId,
|
|
2720
|
+
[ABS_ATTRS.mutation]: name
|
|
2654
2721
|
}
|
|
2655
|
-
}
|
|
2656
|
-
await acquireMutationSlot();
|
|
2657
|
-
const sandboxRunner = sandboxRunners.get(name);
|
|
2658
|
-
const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
|
|
2659
|
-
const runHandler = async (tx) => {
|
|
2660
|
-
const { actions, buffered } = makeActions(tx, ctx, true);
|
|
2661
|
-
const result = await invokeHandler(args, ctx, actions);
|
|
2662
|
-
return { buffered, result };
|
|
2663
|
-
};
|
|
2664
|
-
const retry = mutation.retry;
|
|
2665
|
-
const maxAttempts = retry === undefined ? 1 : retry.maxAttempts ?? 5;
|
|
2666
|
-
const isRetryable = retry?.isRetryable ?? isSerializationFailure;
|
|
2667
|
-
const computeDelay = retry?.backoff ?? exponentialBackoff();
|
|
2668
|
-
const maxElapsedMs = retry?.maxElapsedMs ?? 30000;
|
|
2669
|
-
const startedAt = Date.now();
|
|
2670
|
-
let lastError;
|
|
2671
|
-
let attemptsMade = 0;
|
|
2722
|
+
});
|
|
2672
2723
|
try {
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2724
|
+
const mutation = mutations.get(name);
|
|
2725
|
+
if (mutation === undefined) {
|
|
2726
|
+
throw new Error(`Unknown mutation "${name}"`);
|
|
2727
|
+
}
|
|
2728
|
+
if (mutation.authorize !== undefined) {
|
|
2729
|
+
const allowed = await mutation.authorize(args, ctx);
|
|
2730
|
+
if (!allowed) {
|
|
2731
|
+
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
await acquireMutationSlot();
|
|
2735
|
+
const sandboxRunner = sandboxRunners.get(name);
|
|
2736
|
+
const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
|
|
2737
|
+
const runHandler = async (tx) => {
|
|
2738
|
+
const { actions, buffered } = makeActions(tx, ctx, true);
|
|
2739
|
+
const result = await invokeHandler(args, ctx, actions);
|
|
2740
|
+
return { buffered, result };
|
|
2741
|
+
};
|
|
2742
|
+
const retry = mutation.retry;
|
|
2743
|
+
const maxAttempts = retry === undefined ? 1 : retry.maxAttempts ?? 5;
|
|
2744
|
+
const isRetryable = retry?.isRetryable ?? isSerializationFailure;
|
|
2745
|
+
const computeDelay = retry?.backoff ?? exponentialBackoff();
|
|
2746
|
+
const maxElapsedMs = retry?.maxElapsedMs ?? 30000;
|
|
2747
|
+
const startedAt = Date.now();
|
|
2748
|
+
let lastError;
|
|
2749
|
+
let attemptsMade = 0;
|
|
2750
|
+
try {
|
|
2751
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
2752
|
+
attemptsMade = attempt;
|
|
2753
|
+
try {
|
|
2754
|
+
const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
2755
|
+
await applyChangeBatch(buffered);
|
|
2756
|
+
mutationsCompleted += 1;
|
|
2757
|
+
emitActivity({
|
|
2758
|
+
type: "mutation",
|
|
2759
|
+
at: Date.now(),
|
|
2760
|
+
name,
|
|
2761
|
+
status: "ok"
|
|
2762
|
+
});
|
|
2763
|
+
return result;
|
|
2764
|
+
} catch (error) {
|
|
2765
|
+
lastError = error;
|
|
2766
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2767
|
+
const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
|
|
2768
|
+
if (!canRetry)
|
|
2769
|
+
break;
|
|
2770
|
+
mutationsRetried += 1;
|
|
2771
|
+
const rawDelay = computeDelay(attempt);
|
|
2772
|
+
const remaining = maxElapsedMs - elapsedMs;
|
|
2773
|
+
if (remaining <= 0)
|
|
2774
|
+
break;
|
|
2775
|
+
const delayMs = Math.max(0, Math.min(rawDelay, remaining));
|
|
2776
|
+
emitActivity({
|
|
2777
|
+
type: "mutationRetry",
|
|
2778
|
+
at: Date.now(),
|
|
2779
|
+
name,
|
|
2780
|
+
attempt,
|
|
2781
|
+
delayMs,
|
|
2782
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
2783
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
2784
|
+
});
|
|
2785
|
+
if (delayMs > 0) {
|
|
2786
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2787
|
+
}
|
|
2709
2788
|
}
|
|
2710
2789
|
}
|
|
2790
|
+
mutationsFailed += 1;
|
|
2791
|
+
emitActivity({
|
|
2792
|
+
type: "mutation",
|
|
2793
|
+
at: Date.now(),
|
|
2794
|
+
name,
|
|
2795
|
+
status: "error"
|
|
2796
|
+
});
|
|
2797
|
+
if (attemptsMade > 1) {
|
|
2798
|
+
throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
|
|
2799
|
+
}
|
|
2800
|
+
throw lastError;
|
|
2801
|
+
} finally {
|
|
2802
|
+
releaseMutationSlot();
|
|
2711
2803
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
status: "error"
|
|
2804
|
+
} catch (spanError) {
|
|
2805
|
+
span.recordException(spanError);
|
|
2806
|
+
span.setStatus({
|
|
2807
|
+
code: 2,
|
|
2808
|
+
message: spanError instanceof Error ? spanError.message : String(spanError)
|
|
2718
2809
|
});
|
|
2719
|
-
|
|
2720
|
-
throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
|
|
2721
|
-
}
|
|
2722
|
-
throw lastError;
|
|
2810
|
+
throw spanError;
|
|
2723
2811
|
} finally {
|
|
2724
|
-
|
|
2812
|
+
span.end();
|
|
2725
2813
|
}
|
|
2726
2814
|
},
|
|
2727
2815
|
runMutations: async (specs, ctx) => {
|
|
@@ -2971,6 +3059,43 @@ var createSyncEngine = (options = {}) => {
|
|
|
2971
3059
|
version
|
|
2972
3060
|
}),
|
|
2973
3061
|
importChangeLog,
|
|
3062
|
+
replayTo: async ({ at, tables }) => {
|
|
3063
|
+
const filterTables = tables !== undefined ? new Set(tables) : undefined;
|
|
3064
|
+
const state = new Map;
|
|
3065
|
+
let asOfVersion = 0;
|
|
3066
|
+
let asOfAt = 0;
|
|
3067
|
+
const oldest = changeLog[0];
|
|
3068
|
+
const truncated = oldest !== undefined && oldest.version > 1 && oldest.at > at;
|
|
3069
|
+
for (const entry of changeLog) {
|
|
3070
|
+
if (entry.at > at)
|
|
3071
|
+
break;
|
|
3072
|
+
if (filterTables !== undefined && !filterTables.has(entry.table)) {
|
|
3073
|
+
continue;
|
|
3074
|
+
}
|
|
3075
|
+
let tableState = state.get(entry.table);
|
|
3076
|
+
if (tableState === undefined) {
|
|
3077
|
+
tableState = new Map;
|
|
3078
|
+
state.set(entry.table, tableState);
|
|
3079
|
+
}
|
|
3080
|
+
const reader = readers.get(entry.table);
|
|
3081
|
+
const key = reader?.key?.(entry.change.row) ?? entry.change.row?.id;
|
|
3082
|
+
if (key === undefined) {
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
if (entry.change.op === "delete") {
|
|
3086
|
+
tableState.delete(key);
|
|
3087
|
+
} else {
|
|
3088
|
+
tableState.set(key, entry.change.row);
|
|
3089
|
+
}
|
|
3090
|
+
asOfVersion = entry.version;
|
|
3091
|
+
asOfAt = entry.at;
|
|
3092
|
+
}
|
|
3093
|
+
const rows = {};
|
|
3094
|
+
for (const [table, map] of state) {
|
|
3095
|
+
rows[table] = [...map.values()];
|
|
3096
|
+
}
|
|
3097
|
+
return { asOfAt, asOfVersion, rows, truncated };
|
|
3098
|
+
},
|
|
2974
3099
|
metrics: () => {
|
|
2975
3100
|
const now = Date.now();
|
|
2976
3101
|
const byCollection = {};
|
|
@@ -3545,5 +3670,5 @@ export {
|
|
|
3545
3670
|
CdcConsumerSlowError
|
|
3546
3671
|
};
|
|
3547
3672
|
|
|
3548
|
-
//# debugId=
|
|
3673
|
+
//# debugId=04E0C6606054D14564756E2164756E21
|
|
3549
3674
|
//# sourceMappingURL=index.js.map
|