@absolutejs/sync 1.12.3 → 1.14.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/devtools.d.ts +53 -0
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +110 -49
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +28 -1
- package/dist/index.js +110 -49
- package/dist/index.js.map +3 -3
- package/dist/testing.js +110 -49
- package/dist/testing.js.map +3 -3
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import type { PermissionsDefinition, TablePermissions } from './permissions';
|
|
|
7
7
|
import type { SyncPack } from './pack';
|
|
8
8
|
import type { SearchCollectionDefinition } from './search';
|
|
9
9
|
import type { ScheduleDefinition } from './schedule';
|
|
10
|
-
import type { EngineActivity, EngineInspection } from './devtools';
|
|
10
|
+
import type { EngineActivity, EngineInspection, EngineMetrics } from './devtools';
|
|
11
11
|
import type { SchemaDefinition, TableSchema } from './schema';
|
|
12
12
|
import type { CrdtMergeable } from '../crdt';
|
|
13
13
|
import type { ClusterBus } from './cluster';
|
|
@@ -206,6 +206,16 @@ export type SyncEngine = {
|
|
|
206
206
|
* writers, the change-feed version, and recent changes. See `syncDevtools`.
|
|
207
207
|
*/
|
|
208
208
|
inspect: () => EngineInspection;
|
|
209
|
+
/**
|
|
210
|
+
* Operator-shaped engine metrics — counters + memory estimates + throughput
|
|
211
|
+
* totals since engine start. Distinct from {@link SyncEngine.inspect}: this
|
|
212
|
+
* is what a PaaS host scrapes on an interval to answer "is this engine
|
|
213
|
+
* healthy" and "what's its resource footprint." Feed it to
|
|
214
|
+
* `@absolutejs/metering` for per-engine cost attribution.
|
|
215
|
+
*
|
|
216
|
+
* Added in 1.13.0.
|
|
217
|
+
*/
|
|
218
|
+
metrics: () => EngineMetrics;
|
|
209
219
|
/**
|
|
210
220
|
* Subscribe to the live engine activity stream (changes, mutation outcomes,
|
|
211
221
|
* subscribe/unsubscribe). Returns an unsubscribe. Powers the devtools feed.
|
|
@@ -259,6 +269,13 @@ export type LoggedChange = {
|
|
|
259
269
|
version: number;
|
|
260
270
|
table: string;
|
|
261
271
|
change: RowChange<unknown>;
|
|
272
|
+
/**
|
|
273
|
+
* Wall-clock when this change was logged (Date.now()). Used by the
|
|
274
|
+
* engine's time-based retention sweep (`changeLogRetainMs`) and
|
|
275
|
+
* surfaced as the change-log age in {@link SyncEngine.metrics}.
|
|
276
|
+
* Added in 1.13.0; pre-1.13.0 consumers of `LoggedChange` ignore it.
|
|
277
|
+
*/
|
|
278
|
+
at: number;
|
|
262
279
|
};
|
|
263
280
|
/** Thrown by {@link SyncEngine.streamChanges} when `since` is older than the
|
|
264
281
|
* oldest entry retained in the bounded change log (i.e. the consumer was
|
|
@@ -305,6 +322,16 @@ export type SyncEngineOptions = {
|
|
|
305
322
|
* snapshot. Defaults to 1024.
|
|
306
323
|
*/
|
|
307
324
|
changeLogSize?: number;
|
|
325
|
+
/**
|
|
326
|
+
* Time-based change-log retention: drop entries older than this many ms,
|
|
327
|
+
* in addition to the count cap above. Lets a high-throughput engine keep
|
|
328
|
+
* a SHORT log (e.g. "60s of changes") regardless of count, which both
|
|
329
|
+
* bounds memory and bounds the catch-up work on reconnect. Defaults to
|
|
330
|
+
* `null` — only the count cap (`changeLogSize`) applies.
|
|
331
|
+
*
|
|
332
|
+
* Added in 1.13.0.
|
|
333
|
+
*/
|
|
334
|
+
changeLogRetainMs?: number | null;
|
|
308
335
|
/**
|
|
309
336
|
* Run every mutation inside your database's transaction (see
|
|
310
337
|
* {@link TransactionRunner}): the handler's writes commit all-or-nothing, and
|
package/dist/index.js
CHANGED
|
@@ -1130,8 +1130,14 @@ var createSyncEngine = (options = {}) => {
|
|
|
1130
1130
|
const active = new Map;
|
|
1131
1131
|
const tableIndex = new Map;
|
|
1132
1132
|
const changeLogSize = options.changeLogSize ?? 1024;
|
|
1133
|
+
const changeLogRetainMs = options.changeLogRetainMs ?? null;
|
|
1133
1134
|
const changeLog = [];
|
|
1134
1135
|
let version = 0;
|
|
1136
|
+
const engineStartedAt = Date.now();
|
|
1137
|
+
let mutationsCompleted = 0;
|
|
1138
|
+
let mutationsFailed = 0;
|
|
1139
|
+
let mutationsRetried = 0;
|
|
1140
|
+
let mutationsInFlight = 0;
|
|
1135
1141
|
const reactiveCacheMax = options.reactiveCache?.max ?? 256;
|
|
1136
1142
|
const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
|
|
1137
1143
|
const cachedReruns = new Map;
|
|
@@ -1542,6 +1548,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
1542
1548
|
if (changeLog.length > changeLogSize) {
|
|
1543
1549
|
changeLog.shift();
|
|
1544
1550
|
}
|
|
1551
|
+
if (changeLogRetainMs !== null && changeLogRetainMs > 0) {
|
|
1552
|
+
const cutoff = entry.at - changeLogRetainMs;
|
|
1553
|
+
while (changeLog.length > 0 && changeLog[0].at < cutoff) {
|
|
1554
|
+
changeLog.shift();
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1545
1557
|
for (const subscriber of streamSubscribers) {
|
|
1546
1558
|
subscriber(entry);
|
|
1547
1559
|
}
|
|
@@ -1549,10 +1561,11 @@ var createSyncEngine = (options = {}) => {
|
|
|
1549
1561
|
const applyChange = async (table, change, shouldBroadcast = true) => {
|
|
1550
1562
|
version += 1;
|
|
1551
1563
|
const changeVersion = version;
|
|
1552
|
-
|
|
1564
|
+
const at = Date.now();
|
|
1565
|
+
logChange(changeVersion, { version: changeVersion, table, change, at });
|
|
1553
1566
|
emitActivity({
|
|
1554
1567
|
type: "change",
|
|
1555
|
-
at
|
|
1568
|
+
at,
|
|
1556
1569
|
table,
|
|
1557
1570
|
op: change.op,
|
|
1558
1571
|
version: changeVersion
|
|
@@ -1583,11 +1596,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
1583
1596
|
const batchVersion = version;
|
|
1584
1597
|
const perSubscription = new Map;
|
|
1585
1598
|
const reactiveChanges = [];
|
|
1599
|
+
const batchAt = Date.now();
|
|
1586
1600
|
for (const { table, change } of changes) {
|
|
1587
|
-
logChange(batchVersion, { version: batchVersion, table, change });
|
|
1601
|
+
logChange(batchVersion, { version: batchVersion, table, change, at: batchAt });
|
|
1588
1602
|
emitActivity({
|
|
1589
1603
|
type: "change",
|
|
1590
|
-
at:
|
|
1604
|
+
at: batchAt,
|
|
1591
1605
|
table,
|
|
1592
1606
|
op: change.op,
|
|
1593
1607
|
version: batchVersion
|
|
@@ -2038,53 +2052,61 @@ var createSyncEngine = (options = {}) => {
|
|
|
2038
2052
|
const startedAt = Date.now();
|
|
2039
2053
|
let lastError;
|
|
2040
2054
|
let attemptsMade = 0;
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2055
|
+
mutationsInFlight += 1;
|
|
2056
|
+
try {
|
|
2057
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
2058
|
+
attemptsMade = attempt;
|
|
2059
|
+
try {
|
|
2060
|
+
const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
2061
|
+
await applyChangeBatch(buffered);
|
|
2062
|
+
mutationsCompleted += 1;
|
|
2063
|
+
emitActivity({
|
|
2064
|
+
type: "mutation",
|
|
2065
|
+
at: Date.now(),
|
|
2066
|
+
name,
|
|
2067
|
+
status: "ok"
|
|
2068
|
+
});
|
|
2069
|
+
return result;
|
|
2070
|
+
} catch (error) {
|
|
2071
|
+
lastError = error;
|
|
2072
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2073
|
+
const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
|
|
2074
|
+
if (!canRetry)
|
|
2075
|
+
break;
|
|
2076
|
+
mutationsRetried += 1;
|
|
2077
|
+
const rawDelay = computeDelay(attempt);
|
|
2078
|
+
const remaining = maxElapsedMs - elapsedMs;
|
|
2079
|
+
if (remaining <= 0)
|
|
2080
|
+
break;
|
|
2081
|
+
const delayMs = Math.max(0, Math.min(rawDelay, remaining));
|
|
2082
|
+
emitActivity({
|
|
2083
|
+
type: "mutationRetry",
|
|
2084
|
+
at: Date.now(),
|
|
2085
|
+
name,
|
|
2086
|
+
attempt,
|
|
2087
|
+
delayMs,
|
|
2088
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
2089
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
2090
|
+
});
|
|
2091
|
+
if (delayMs > 0) {
|
|
2092
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2093
|
+
}
|
|
2075
2094
|
}
|
|
2076
2095
|
}
|
|
2096
|
+
mutationsFailed += 1;
|
|
2097
|
+
emitActivity({
|
|
2098
|
+
type: "mutation",
|
|
2099
|
+
at: Date.now(),
|
|
2100
|
+
name,
|
|
2101
|
+
status: "error"
|
|
2102
|
+
});
|
|
2103
|
+
if (attemptsMade > 1) {
|
|
2104
|
+
throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
|
|
2105
|
+
}
|
|
2106
|
+
throw lastError;
|
|
2107
|
+
} finally {
|
|
2108
|
+
mutationsInFlight -= 1;
|
|
2077
2109
|
}
|
|
2078
|
-
emitActivity({
|
|
2079
|
-
type: "mutation",
|
|
2080
|
-
at: Date.now(),
|
|
2081
|
-
name,
|
|
2082
|
-
status: "error"
|
|
2083
|
-
});
|
|
2084
|
-
if (attemptsMade > 1) {
|
|
2085
|
-
throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
|
|
2086
|
-
}
|
|
2087
|
-
throw lastError;
|
|
2088
2110
|
},
|
|
2089
2111
|
runMutations: async (specs, ctx) => {
|
|
2090
2112
|
if (specs.length === 0)
|
|
@@ -2323,6 +2345,45 @@ var createSyncEngine = (options = {}) => {
|
|
|
2323
2345
|
}))
|
|
2324
2346
|
};
|
|
2325
2347
|
},
|
|
2348
|
+
metrics: () => {
|
|
2349
|
+
const now = Date.now();
|
|
2350
|
+
const byCollection = {};
|
|
2351
|
+
let totalSubscriptions = 0;
|
|
2352
|
+
for (const [name, subs] of active) {
|
|
2353
|
+
byCollection[name] = subs.size;
|
|
2354
|
+
totalSubscriptions += subs.size;
|
|
2355
|
+
}
|
|
2356
|
+
const oldest = changeLog[0];
|
|
2357
|
+
return {
|
|
2358
|
+
at: now,
|
|
2359
|
+
changeLog: {
|
|
2360
|
+
capacity: changeLogSize,
|
|
2361
|
+
entries: changeLog.length,
|
|
2362
|
+
oldestAgeMs: oldest ? now - oldest.at : null,
|
|
2363
|
+
oldestVersion: oldest ? oldest.version : null,
|
|
2364
|
+
retainMs: changeLogRetainMs
|
|
2365
|
+
},
|
|
2366
|
+
mutations: {
|
|
2367
|
+
completed: mutationsCompleted,
|
|
2368
|
+
failed: mutationsFailed,
|
|
2369
|
+
inFlight: mutationsInFlight,
|
|
2370
|
+
retried: mutationsRetried
|
|
2371
|
+
},
|
|
2372
|
+
reactiveCache: {
|
|
2373
|
+
capacity: reactiveCacheMax,
|
|
2374
|
+
entries: cachedReruns.size
|
|
2375
|
+
},
|
|
2376
|
+
schedules: {
|
|
2377
|
+
registered: schedules.size
|
|
2378
|
+
},
|
|
2379
|
+
subscriptions: {
|
|
2380
|
+
byCollection,
|
|
2381
|
+
total: totalSubscriptions
|
|
2382
|
+
},
|
|
2383
|
+
uptimeMs: now - engineStartedAt,
|
|
2384
|
+
version
|
|
2385
|
+
};
|
|
2386
|
+
},
|
|
2326
2387
|
onActivity: (listener) => {
|
|
2327
2388
|
activityListeners.add(listener);
|
|
2328
2389
|
return () => {
|
|
@@ -2698,5 +2759,5 @@ export {
|
|
|
2698
2759
|
createPresenceHub
|
|
2699
2760
|
};
|
|
2700
2761
|
|
|
2701
|
-
//# debugId=
|
|
2762
|
+
//# debugId=202C826A4AA9A02264756E2164756E21
|
|
2702
2763
|
//# sourceMappingURL=index.js.map
|