@absolutejs/sync 1.23.0 → 1.25.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/adapters/tanstack-db/index.js.map +3 -3
- package/dist/angular/index.js.map +3 -3
- package/dist/client/index.js.map +6 -6
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.js +104 -5
- package/dist/engine/index.js.map +7 -6
- package/dist/engine/migrate.d.ts +131 -0
- package/dist/engine/syncEngine.d.ts +56 -0
- package/dist/index.js +146 -45
- package/dist/index.js.map +10 -9
- package/dist/plugin.d.ts +1 -40
- package/dist/react/index.js.map +3 -3
- package/dist/svelte/index.js.map +3 -3
- package/dist/testing.js +103 -5
- package/dist/testing.js.map +5 -4
- package/dist/vue/index.js.map +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant migration primitives. Closes G7 from the deep-research
|
|
3
|
+
* audit: "move a tenant from engine A to engine B."
|
|
4
|
+
*
|
|
5
|
+
* The substrate offers three composable verbs:
|
|
6
|
+
*
|
|
7
|
+
* - **`engine.fence({ reason })`** — pause new mutations on the
|
|
8
|
+
* source so its captured state stops drifting. Subscribers continue
|
|
9
|
+
* to read; only `runMutation` rejects (with
|
|
10
|
+
* {@link EngineFencedError}). Returns a {@link FenceHandle} with
|
|
11
|
+
* `lift()` to undo.
|
|
12
|
+
* - **`engine.exportSnapshot({ tables?, ctx? })`** — walk the
|
|
13
|
+
* registered readers and return a portable
|
|
14
|
+
* {@link EngineSnapshot} carrying the source `instanceId`,
|
|
15
|
+
* `version`, and current rows per table. Cheap and synchronous from
|
|
16
|
+
* the operator's POV.
|
|
17
|
+
* - **`engine.importSnapshot(snapshot, options?)`** — on the target,
|
|
18
|
+
* bulk-load the rows via each table's registered writer. Tracks
|
|
19
|
+
* per-table progress. Returns a {@link MigrationImportResult} with
|
|
20
|
+
* row counts.
|
|
21
|
+
*
|
|
22
|
+
* The intended choreography for a cross-region tenant move:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* // ── on the source ──
|
|
26
|
+
* const fence = source.fence({ reason: 'tenant-7 → us-east-2' });
|
|
27
|
+
* try {
|
|
28
|
+
* const snapshot = await source.exportSnapshot();
|
|
29
|
+
* await transport(snapshot); // S3, message bus, etc.
|
|
30
|
+
* // ── on the target ──
|
|
31
|
+
* await target.importSnapshot(snapshot, {
|
|
32
|
+
* onProgress: (table, done, total) =>
|
|
33
|
+
* console.log(`${table}: ${done}/${total}`)
|
|
34
|
+
* });
|
|
35
|
+
* await cutoverDns(); // direct clients at target
|
|
36
|
+
* } finally {
|
|
37
|
+
* fence.lift();
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Out of scope: out-of-band writes (CDC drivers, raw SQL) — the
|
|
42
|
+
* caller is responsible for pausing those before fencing, otherwise
|
|
43
|
+
* the captured snapshot drifts.
|
|
44
|
+
*
|
|
45
|
+
* Added in 1.24.0.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Portable per-tenant state captured by
|
|
49
|
+
* {@link SyncEngine.exportSnapshot}. Consumed by
|
|
50
|
+
* {@link SyncEngine.importSnapshot} on the target engine.
|
|
51
|
+
*/
|
|
52
|
+
export type EngineSnapshot = {
|
|
53
|
+
/** The exporting engine's `instanceId` (for audit / forensics). */
|
|
54
|
+
sourceInstanceId: string;
|
|
55
|
+
/** Source engine's monotonic version at snapshot time. */
|
|
56
|
+
version: number;
|
|
57
|
+
/** `Date.now()` at export — used by hosts for staleness checks. */
|
|
58
|
+
exportedAt: number;
|
|
59
|
+
/** Current rows per table, read from each table's registered reader. */
|
|
60
|
+
tables: Record<string, ReadonlyArray<unknown>>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Returned by {@link SyncEngine.importSnapshot}.
|
|
64
|
+
*/
|
|
65
|
+
export type MigrationImportResult = {
|
|
66
|
+
/** Number of tables that had at least one row imported. */
|
|
67
|
+
tablesImported: number;
|
|
68
|
+
/** Total rows inserted across all tables. */
|
|
69
|
+
rowsImported: number;
|
|
70
|
+
/** Rows inserted per table. Tables with zero rows are still listed. */
|
|
71
|
+
perTable: Record<string, number>;
|
|
72
|
+
/**
|
|
73
|
+
* Tables present in the snapshot that the target engine has no
|
|
74
|
+
* registered writer for — skipped silently. Surface this to
|
|
75
|
+
* operators so they can catch "I forgot to register `tasks` on
|
|
76
|
+
* the new shard" cleanly.
|
|
77
|
+
*/
|
|
78
|
+
skipped: ReadonlyArray<string>;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Returned by {@link SyncEngine.fence}. Hold this and call `lift()`
|
|
82
|
+
* to re-enable mutations. Holding multiple fences is supported — the
|
|
83
|
+
* engine stays fenced until every handle has been lifted.
|
|
84
|
+
*/
|
|
85
|
+
export type FenceHandle = {
|
|
86
|
+
/** `Date.now()` at fence time. */
|
|
87
|
+
fencedAt: number;
|
|
88
|
+
/** Human-readable reason — surfaced on {@link EngineFencedError}. */
|
|
89
|
+
reason: string;
|
|
90
|
+
/** Re-enable mutations. Idempotent (later calls are no-ops). */
|
|
91
|
+
lift: () => void;
|
|
92
|
+
};
|
|
93
|
+
export type ExportSnapshotOptions = {
|
|
94
|
+
/**
|
|
95
|
+
* Narrow the export to a subset of registered tables. Useful for
|
|
96
|
+
* per-tenant cuts when readers expose `ctx`-scoped data.
|
|
97
|
+
*/
|
|
98
|
+
tables?: ReadonlyArray<string>;
|
|
99
|
+
/**
|
|
100
|
+
* Context passed to each reader's `all(ctx)`. The default `{}`
|
|
101
|
+
* works for engines whose readers ignore context.
|
|
102
|
+
*/
|
|
103
|
+
ctx?: unknown;
|
|
104
|
+
};
|
|
105
|
+
export type ImportSnapshotOptions = {
|
|
106
|
+
/**
|
|
107
|
+
* Narrow the import to a subset of tables in the snapshot.
|
|
108
|
+
* Tables outside the filter are skipped (NOT recorded in
|
|
109
|
+
* `skipped`; that field is for tables with no writer).
|
|
110
|
+
*/
|
|
111
|
+
tables?: ReadonlyArray<string>;
|
|
112
|
+
/**
|
|
113
|
+
* Called for each row insertion. Fires synchronously inside the
|
|
114
|
+
* import loop; keep it cheap or schedule heavy work elsewhere.
|
|
115
|
+
*/
|
|
116
|
+
onProgress?: (table: string, done: number, total: number) => void;
|
|
117
|
+
/**
|
|
118
|
+
* Context passed to each writer's `insert(data, ctx, tx)`. The
|
|
119
|
+
* default `{}` works for writers that ignore context.
|
|
120
|
+
*/
|
|
121
|
+
ctx?: unknown;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Thrown by `runMutation` when the engine is fenced. The reason
|
|
125
|
+
* carries through so operators can correlate denied calls to the
|
|
126
|
+
* fence that caused them.
|
|
127
|
+
*/
|
|
128
|
+
export declare class EngineFencedError extends Error {
|
|
129
|
+
readonly reason: string;
|
|
130
|
+
constructor(reason: string);
|
|
131
|
+
}
|
|
@@ -10,6 +10,7 @@ import type { SearchCollectionDefinition } from './search';
|
|
|
10
10
|
import type { ScheduleDefinition } from './schedule';
|
|
11
11
|
import type { EngineActivity, EngineInspection, EngineMetrics } from './devtools';
|
|
12
12
|
import type { SchemaDefinition, TableSchema } from './schema';
|
|
13
|
+
import { type EngineSnapshot, type ExportSnapshotOptions, type FenceHandle, type ImportSnapshotOptions, type MigrationImportResult } from './migrate';
|
|
13
14
|
import type { CrdtMergeable } from '../crdt';
|
|
14
15
|
import type { ClusterBus } from './cluster';
|
|
15
16
|
import type { ChangeSource, RowChange, ViewDiff } from './types';
|
|
@@ -324,6 +325,61 @@ export type SyncEngine = {
|
|
|
324
325
|
* ```
|
|
325
326
|
*/
|
|
326
327
|
replayTo: (options: ReplayOptions) => Promise<ReplayResult>;
|
|
328
|
+
/**
|
|
329
|
+
* Pause new mutations on the engine — the source half of the G7 tenant
|
|
330
|
+
* migration contract. While at least one fence is held, `runMutation`
|
|
331
|
+
* rejects with {@link EngineFencedError}; subscribe/hydrate continue to
|
|
332
|
+
* work, so live readers stay served while the snapshot is in flight.
|
|
333
|
+
*
|
|
334
|
+
* Multiple fence handles compose — the engine stays fenced until every
|
|
335
|
+
* handle has been `lift()`-ed. Lifting is idempotent.
|
|
336
|
+
*
|
|
337
|
+
* Out of scope: out-of-band writes (CDC drivers, raw SQL). The caller
|
|
338
|
+
* is responsible for halting those before fencing, otherwise the
|
|
339
|
+
* snapshot will drift between `exportSnapshot` and import on the target.
|
|
340
|
+
*
|
|
341
|
+
* Added in 1.24.0.
|
|
342
|
+
*/
|
|
343
|
+
fence: (options: {
|
|
344
|
+
reason: string;
|
|
345
|
+
}) => FenceHandle;
|
|
346
|
+
/**
|
|
347
|
+
* Capture the engine's current per-table state into a portable
|
|
348
|
+
* {@link EngineSnapshot}. Walks every registered reader's `all(ctx)`
|
|
349
|
+
* and collects the rows. Used to ship a tenant between engines (G7).
|
|
350
|
+
*
|
|
351
|
+
* Pair with `fence()` on the source to stop drift, then
|
|
352
|
+
* `importSnapshot()` on the target. The shape is intentionally
|
|
353
|
+
* detached from `ChangeLogSnapshot` — snapshots carry live state, not
|
|
354
|
+
* history. Use `exportChangeLog()` separately if you need forensic
|
|
355
|
+
* continuity at the target instanceId.
|
|
356
|
+
*
|
|
357
|
+
* Added in 1.24.0.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* const fence = source.fence({ reason: 'tenant move' });
|
|
361
|
+
* try {
|
|
362
|
+
* const snapshot = await source.exportSnapshot();
|
|
363
|
+
* await target.importSnapshot(snapshot);
|
|
364
|
+
* } finally { fence.lift(); }
|
|
365
|
+
*/
|
|
366
|
+
exportSnapshot: (options?: ExportSnapshotOptions) => Promise<EngineSnapshot>;
|
|
367
|
+
/**
|
|
368
|
+
* Bulk-load an {@link EngineSnapshot} into this engine via each table's
|
|
369
|
+
* registered writer. Tables present in the snapshot but missing a
|
|
370
|
+
* writer here are surfaced in `result.skipped` so the operator can
|
|
371
|
+
* detect a misconfigured target. The target half of the G7 migration
|
|
372
|
+
* contract.
|
|
373
|
+
*
|
|
374
|
+
* Inserts do NOT emit change events to subscribers — the import is
|
|
375
|
+
* meant to land on a fresh target whose clients will re-hydrate after
|
|
376
|
+
* the DNS cutover. If you need to fan changes out (e.g. mid-flight
|
|
377
|
+
* cutover), drain the change log via `streamChanges()` and
|
|
378
|
+
* `applyChange()` separately.
|
|
379
|
+
*
|
|
380
|
+
* Added in 1.24.0.
|
|
381
|
+
*/
|
|
382
|
+
importSnapshot: (snapshot: EngineSnapshot, options?: ImportSnapshotOptions) => Promise<MigrationImportResult>;
|
|
327
383
|
/**
|
|
328
384
|
* Subscribe to the live engine activity stream (changes, mutation outcomes,
|
|
329
385
|
* subscribe/unsubscribe). Returns an unsubscribe. Powers the devtools feed.
|
package/dist/index.js
CHANGED
|
@@ -139,51 +139,54 @@ var sync = ({
|
|
|
139
139
|
path = "/sync",
|
|
140
140
|
resolveTopics = defaultResolveTopics,
|
|
141
141
|
heartbeatMs = 25000
|
|
142
|
-
}) =>
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
142
|
+
}) => {
|
|
143
|
+
const app = new Elysia({ name: "@absolutejs/sync" }).get(path, (context) => {
|
|
144
|
+
const topics = resolveTopics({
|
|
145
|
+
query: context.query,
|
|
146
|
+
request: context.request
|
|
147
|
+
});
|
|
148
|
+
const encoder = new TextEncoder;
|
|
149
|
+
const stream = new ReadableStream({
|
|
150
|
+
start(controller) {
|
|
151
|
+
const write = (chunk) => {
|
|
152
|
+
try {
|
|
153
|
+
controller.enqueue(encoder.encode(chunk));
|
|
154
|
+
} catch {}
|
|
155
|
+
};
|
|
156
|
+
const send = (event) => {
|
|
157
|
+
write(`data: ${JSON.stringify(event)}
|
|
157
158
|
|
|
158
159
|
`);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
160
|
+
};
|
|
161
|
+
send({
|
|
162
|
+
topic: SYNC_OPEN_TOPIC,
|
|
163
|
+
at: Date.now(),
|
|
164
|
+
payload: { topics }
|
|
165
|
+
});
|
|
166
|
+
const unsubscribe = topics.length > 0 ? hub.subscribe(topics, send) : () => {};
|
|
167
|
+
const heartbeat = setInterval(() => write(`: ping
|
|
167
168
|
|
|
168
169
|
`), heartbeatMs);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
170
|
+
context.request.signal.addEventListener("abort", () => {
|
|
171
|
+
clearInterval(heartbeat);
|
|
172
|
+
unsubscribe();
|
|
173
|
+
try {
|
|
174
|
+
controller.close();
|
|
175
|
+
} catch {}
|
|
176
|
+
}, { once: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return new Response(stream, {
|
|
180
|
+
headers: {
|
|
181
|
+
"cache-control": "no-cache, no-transform",
|
|
182
|
+
connection: "keep-alive",
|
|
183
|
+
"content-type": "text/event-stream",
|
|
184
|
+
"x-accel-buffering": "no"
|
|
185
|
+
}
|
|
186
|
+
});
|
|
185
187
|
});
|
|
186
|
-
|
|
188
|
+
return app;
|
|
189
|
+
};
|
|
187
190
|
// src/engine/socket.ts
|
|
188
191
|
import { Elysia as Elysia2 } from "elysia";
|
|
189
192
|
|
|
@@ -1143,6 +1146,16 @@ var defineSearchCollection = (definition) => ({
|
|
|
1143
1146
|
kind: "search"
|
|
1144
1147
|
});
|
|
1145
1148
|
|
|
1149
|
+
// src/engine/migrate.ts
|
|
1150
|
+
class EngineFencedError extends Error {
|
|
1151
|
+
reason;
|
|
1152
|
+
constructor(reason) {
|
|
1153
|
+
super(`[sync] Engine is fenced for migration: ${reason}`);
|
|
1154
|
+
this.name = "EngineFencedError";
|
|
1155
|
+
this.reason = reason;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1146
1159
|
// src/engine/syncEngine.ts
|
|
1147
1160
|
class UnauthorizedError extends Error {
|
|
1148
1161
|
constructor(subject) {
|
|
@@ -1345,6 +1358,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1345
1358
|
let mutationsInFlight = 0;
|
|
1346
1359
|
const mutationWaiters = [];
|
|
1347
1360
|
let mutationsQueued = 0;
|
|
1361
|
+
const activeFences = new Set;
|
|
1348
1362
|
const acquireMutationSlot = async () => {
|
|
1349
1363
|
const limit = options.mutationConcurrency;
|
|
1350
1364
|
if (limit === undefined) {
|
|
@@ -1471,7 +1485,11 @@ var createSyncEngine = (options = {}) => {
|
|
|
1471
1485
|
}
|
|
1472
1486
|
const broadcast = (changes, originVersion) => {
|
|
1473
1487
|
if (clusterBus !== undefined && changes.length > 0) {
|
|
1474
|
-
clusterBus.publish({
|
|
1488
|
+
clusterBus.publish({
|
|
1489
|
+
changes,
|
|
1490
|
+
origin: instanceId,
|
|
1491
|
+
originVersion
|
|
1492
|
+
});
|
|
1475
1493
|
}
|
|
1476
1494
|
};
|
|
1477
1495
|
const subsFor = (collection) => {
|
|
@@ -2234,7 +2252,14 @@ var createSyncEngine = (options = {}) => {
|
|
|
2234
2252
|
registerSearch: (collection) => {
|
|
2235
2253
|
registry.set(collection.name, collection);
|
|
2236
2254
|
},
|
|
2237
|
-
subscribe: async ({
|
|
2255
|
+
subscribe: async ({
|
|
2256
|
+
collection,
|
|
2257
|
+
params,
|
|
2258
|
+
ctx,
|
|
2259
|
+
onDiff,
|
|
2260
|
+
since,
|
|
2261
|
+
signal
|
|
2262
|
+
}) => {
|
|
2238
2263
|
const subscribeSpan = tracer.startSpan("sync.subscribe", {
|
|
2239
2264
|
attributes: {
|
|
2240
2265
|
[ABS_ATTRS.engineId]: instanceId,
|
|
@@ -2263,7 +2288,10 @@ var createSyncEngine = (options = {}) => {
|
|
|
2263
2288
|
releaseSubscriptionSlot(tenantSlot);
|
|
2264
2289
|
innerUnsubscribe();
|
|
2265
2290
|
};
|
|
2266
|
-
const wrapped = {
|
|
2291
|
+
const wrapped = {
|
|
2292
|
+
...sub,
|
|
2293
|
+
unsubscribe: wrappedUnsubscribe
|
|
2294
|
+
};
|
|
2267
2295
|
linkAbortToUnsubscribe(signal, wrappedUnsubscribe);
|
|
2268
2296
|
slotHandedOff = true;
|
|
2269
2297
|
return wrapped;
|
|
@@ -2298,7 +2326,9 @@ var createSyncEngine = (options = {}) => {
|
|
|
2298
2326
|
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
2299
2327
|
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
2300
2328
|
const rehydrate = async () => {
|
|
2301
|
-
const raw = [
|
|
2329
|
+
const raw = [
|
|
2330
|
+
...await definition.hydrate(params, ctx)
|
|
2331
|
+
];
|
|
2302
2332
|
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
2303
2333
|
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
2304
2334
|
};
|
|
@@ -2463,6 +2493,10 @@ var createSyncEngine = (options = {}) => {
|
|
|
2463
2493
|
}
|
|
2464
2494
|
});
|
|
2465
2495
|
try {
|
|
2496
|
+
if (activeFences.size > 0) {
|
|
2497
|
+
const oldest = activeFences.values().next().value;
|
|
2498
|
+
throw new EngineFencedError(oldest.reason);
|
|
2499
|
+
}
|
|
2466
2500
|
const mutation = mutations.get(name);
|
|
2467
2501
|
if (mutation === undefined) {
|
|
2468
2502
|
throw new Error(`Unknown mutation "${name}"`);
|
|
@@ -2838,6 +2872,73 @@ var createSyncEngine = (options = {}) => {
|
|
|
2838
2872
|
}
|
|
2839
2873
|
return { asOfAt, asOfVersion, rows, truncated };
|
|
2840
2874
|
},
|
|
2875
|
+
fence: ({ reason }) => {
|
|
2876
|
+
const handle = {
|
|
2877
|
+
fencedAt: Date.now(),
|
|
2878
|
+
reason,
|
|
2879
|
+
lift: () => {
|
|
2880
|
+
activeFences.delete(handle);
|
|
2881
|
+
}
|
|
2882
|
+
};
|
|
2883
|
+
activeFences.add(handle);
|
|
2884
|
+
return handle;
|
|
2885
|
+
},
|
|
2886
|
+
exportSnapshot: async ({
|
|
2887
|
+
tables,
|
|
2888
|
+
ctx = {}
|
|
2889
|
+
} = {}) => {
|
|
2890
|
+
const tableFilter = tables !== undefined ? new Set(tables) : undefined;
|
|
2891
|
+
const rows = {};
|
|
2892
|
+
for (const [table, reader] of readers) {
|
|
2893
|
+
if (tableFilter !== undefined && !tableFilter.has(table)) {
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const iterable = await reader.all(ctx);
|
|
2897
|
+
rows[table] = [...iterable];
|
|
2898
|
+
}
|
|
2899
|
+
return {
|
|
2900
|
+
exportedAt: Date.now(),
|
|
2901
|
+
sourceInstanceId: instanceId,
|
|
2902
|
+
tables: rows,
|
|
2903
|
+
version
|
|
2904
|
+
};
|
|
2905
|
+
},
|
|
2906
|
+
importSnapshot: async (snapshot, { tables, onProgress, ctx = {} } = {}) => {
|
|
2907
|
+
const tableFilter = tables !== undefined ? new Set(tables) : undefined;
|
|
2908
|
+
const perTable = {};
|
|
2909
|
+
const skipped = [];
|
|
2910
|
+
let tablesImported = 0;
|
|
2911
|
+
let rowsImported = 0;
|
|
2912
|
+
for (const [table, snapshotRows] of Object.entries(snapshot.tables)) {
|
|
2913
|
+
if (tableFilter !== undefined && !tableFilter.has(table)) {
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
const writer = writers.get(table);
|
|
2917
|
+
if (writer === undefined) {
|
|
2918
|
+
skipped.push(table);
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
const total = snapshotRows.length;
|
|
2922
|
+
let done = 0;
|
|
2923
|
+
for (const row of snapshotRows) {
|
|
2924
|
+
await writer.insert(row, ctx, undefined);
|
|
2925
|
+
done += 1;
|
|
2926
|
+
rowsImported += 1;
|
|
2927
|
+
if (onProgress !== undefined) {
|
|
2928
|
+
onProgress(table, done, total);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
perTable[table] = done;
|
|
2932
|
+
if (done > 0)
|
|
2933
|
+
tablesImported += 1;
|
|
2934
|
+
}
|
|
2935
|
+
return {
|
|
2936
|
+
perTable,
|
|
2937
|
+
rowsImported,
|
|
2938
|
+
skipped,
|
|
2939
|
+
tablesImported
|
|
2940
|
+
};
|
|
2941
|
+
},
|
|
2841
2942
|
metrics: () => {
|
|
2842
2943
|
const now = Date.now();
|
|
2843
2944
|
const byCollection = {};
|
|
@@ -3366,5 +3467,5 @@ export {
|
|
|
3366
3467
|
createPresenceHub
|
|
3367
3468
|
};
|
|
3368
3469
|
|
|
3369
|
-
//# debugId=
|
|
3470
|
+
//# debugId=62313B55CB7C7F6064756E2164756E21
|
|
3370
3471
|
//# sourceMappingURL=index.js.map
|