@absolutejs/sync 1.12.3 → 1.13.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.
@@ -48,6 +48,59 @@ export type EngineInspection = {
48
48
  readsTables: string[];
49
49
  }[];
50
50
  };
51
+ /**
52
+ * Operator-shaped point-in-time engine state (see {@link SyncEngine.metrics}) —
53
+ * numeric counters + memory estimates + throughput totals since engine start.
54
+ *
55
+ * Distinct from {@link EngineInspection}, which is devtools-shaped (named
56
+ * collections, recent-change tail, registered packs). `metrics()` is what a
57
+ * PaaS host scrapes on an interval to answer "is this engine healthy" and
58
+ * "what's its resource footprint" — feed it to `@absolutejs/metering` to
59
+ * attribute cost per engine.
60
+ */
61
+ export type EngineMetrics = {
62
+ /** `Date.now()` when this snapshot was taken. */
63
+ at: number;
64
+ /** How long this engine has been running, in milliseconds. */
65
+ uptimeMs: number;
66
+ /** Current change-feed version (monotonic). */
67
+ version: number;
68
+ changeLog: {
69
+ /** Number of entries currently retained. */
70
+ entries: number;
71
+ /** Hard cap on entries (from `SyncEngineOptions.changeLogSize`). */
72
+ capacity: number;
73
+ /** Time-based retention window, when set (`SyncEngineOptions.changeLogRetainMs`). */
74
+ retainMs: number | null;
75
+ /** Version of the oldest retained entry, or `null` when empty. */
76
+ oldestVersion: number | null;
77
+ /** Wall-clock age of the oldest retained entry in ms, or `null` when empty. */
78
+ oldestAgeMs: number | null;
79
+ };
80
+ subscriptions: {
81
+ /** Active subscriptions across every collection. */
82
+ total: number;
83
+ /** Per-collection breakdown — the values sum to `total`. */
84
+ byCollection: Record<string, number>;
85
+ };
86
+ reactiveCache: {
87
+ entries: number;
88
+ capacity: number;
89
+ };
90
+ mutations: {
91
+ /** Mutations completed successfully since engine start. */
92
+ completed: number;
93
+ /** Mutations that exhausted their retry budget and failed. */
94
+ failed: number;
95
+ /** Per-attempt retries fired since start (a single mutation may bump this multiple times). */
96
+ retried: number;
97
+ /** Currently running, not yet committed or failed. */
98
+ inFlight: number;
99
+ };
100
+ schedules: {
101
+ registered: number;
102
+ };
103
+ };
51
104
  /**
52
105
  * A live engine event (see {@link SyncEngine.onActivity}): a committed change or
53
106
  * a mutation outcome. `at` is `Date.now()`. (Live subscription counts come from
@@ -54,7 +54,7 @@ export type { SyncCdcOptions } from './cdc';
54
54
  export type { CrdtMergeable } from '../crdt';
55
55
  export { defineSchema, field } from './schema';
56
56
  export type { FieldValidator, SchemaDefinition, TableSchema } from './schema';
57
- export type { CollectionInspection, CollectionKind, EngineActivity, EngineInspection } from './devtools';
57
+ export type { CollectionInspection, CollectionKind, EngineActivity, EngineInspection, EngineMetrics } from './devtools';
58
58
  export { hydrateRoute, mutateRoute } from './routes';
59
59
  export type { SyncRouteContext } from './routes';
60
60
  export { createSyncConnection } from './connection';
@@ -1492,8 +1492,14 @@ var createSyncEngine = (options = {}) => {
1492
1492
  const active = new Map;
1493
1493
  const tableIndex = new Map;
1494
1494
  const changeLogSize = options.changeLogSize ?? 1024;
1495
+ const changeLogRetainMs = options.changeLogRetainMs ?? null;
1495
1496
  const changeLog = [];
1496
1497
  let version = 0;
1498
+ const engineStartedAt = Date.now();
1499
+ let mutationsCompleted = 0;
1500
+ let mutationsFailed = 0;
1501
+ let mutationsRetried = 0;
1502
+ let mutationsInFlight = 0;
1497
1503
  const reactiveCacheMax = options.reactiveCache?.max ?? 256;
1498
1504
  const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
1499
1505
  const cachedReruns = new Map;
@@ -1904,6 +1910,12 @@ var createSyncEngine = (options = {}) => {
1904
1910
  if (changeLog.length > changeLogSize) {
1905
1911
  changeLog.shift();
1906
1912
  }
1913
+ if (changeLogRetainMs !== null && changeLogRetainMs > 0) {
1914
+ const cutoff = entry.at - changeLogRetainMs;
1915
+ while (changeLog.length > 0 && changeLog[0].at < cutoff) {
1916
+ changeLog.shift();
1917
+ }
1918
+ }
1907
1919
  for (const subscriber of streamSubscribers) {
1908
1920
  subscriber(entry);
1909
1921
  }
@@ -1911,10 +1923,11 @@ var createSyncEngine = (options = {}) => {
1911
1923
  const applyChange = async (table, change, shouldBroadcast = true) => {
1912
1924
  version += 1;
1913
1925
  const changeVersion = version;
1914
- logChange(changeVersion, { version: changeVersion, table, change });
1926
+ const at = Date.now();
1927
+ logChange(changeVersion, { version: changeVersion, table, change, at });
1915
1928
  emitActivity({
1916
1929
  type: "change",
1917
- at: Date.now(),
1930
+ at,
1918
1931
  table,
1919
1932
  op: change.op,
1920
1933
  version: changeVersion
@@ -1945,11 +1958,12 @@ var createSyncEngine = (options = {}) => {
1945
1958
  const batchVersion = version;
1946
1959
  const perSubscription = new Map;
1947
1960
  const reactiveChanges = [];
1961
+ const batchAt = Date.now();
1948
1962
  for (const { table, change } of changes) {
1949
- logChange(batchVersion, { version: batchVersion, table, change });
1963
+ logChange(batchVersion, { version: batchVersion, table, change, at: batchAt });
1950
1964
  emitActivity({
1951
1965
  type: "change",
1952
- at: Date.now(),
1966
+ at: batchAt,
1953
1967
  table,
1954
1968
  op: change.op,
1955
1969
  version: batchVersion
@@ -2400,53 +2414,61 @@ var createSyncEngine = (options = {}) => {
2400
2414
  const startedAt = Date.now();
2401
2415
  let lastError;
2402
2416
  let attemptsMade = 0;
2403
- for (let attempt = 1;attempt <= maxAttempts; attempt++) {
2404
- attemptsMade = attempt;
2405
- try {
2406
- const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
2407
- await applyChangeBatch(buffered);
2408
- emitActivity({
2409
- type: "mutation",
2410
- at: Date.now(),
2411
- name,
2412
- status: "ok"
2413
- });
2414
- return result;
2415
- } catch (error) {
2416
- lastError = error;
2417
- const elapsedMs = Date.now() - startedAt;
2418
- const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
2419
- if (!canRetry)
2420
- break;
2421
- const rawDelay = computeDelay(attempt);
2422
- const remaining = maxElapsedMs - elapsedMs;
2423
- if (remaining <= 0)
2424
- break;
2425
- const delayMs = Math.max(0, Math.min(rawDelay, remaining));
2426
- emitActivity({
2427
- type: "mutationRetry",
2428
- at: Date.now(),
2429
- name,
2430
- attempt,
2431
- delayMs,
2432
- errorName: error instanceof Error ? error.name : "Error",
2433
- errorMessage: error instanceof Error ? error.message : String(error)
2434
- });
2435
- if (delayMs > 0) {
2436
- await new Promise((resolve) => setTimeout(resolve, delayMs));
2417
+ mutationsInFlight += 1;
2418
+ try {
2419
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
2420
+ attemptsMade = attempt;
2421
+ try {
2422
+ const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
2423
+ await applyChangeBatch(buffered);
2424
+ mutationsCompleted += 1;
2425
+ emitActivity({
2426
+ type: "mutation",
2427
+ at: Date.now(),
2428
+ name,
2429
+ status: "ok"
2430
+ });
2431
+ return result;
2432
+ } catch (error) {
2433
+ lastError = error;
2434
+ const elapsedMs = Date.now() - startedAt;
2435
+ const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
2436
+ if (!canRetry)
2437
+ break;
2438
+ mutationsRetried += 1;
2439
+ const rawDelay = computeDelay(attempt);
2440
+ const remaining = maxElapsedMs - elapsedMs;
2441
+ if (remaining <= 0)
2442
+ break;
2443
+ const delayMs = Math.max(0, Math.min(rawDelay, remaining));
2444
+ emitActivity({
2445
+ type: "mutationRetry",
2446
+ at: Date.now(),
2447
+ name,
2448
+ attempt,
2449
+ delayMs,
2450
+ errorName: error instanceof Error ? error.name : "Error",
2451
+ errorMessage: error instanceof Error ? error.message : String(error)
2452
+ });
2453
+ if (delayMs > 0) {
2454
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2455
+ }
2437
2456
  }
2438
2457
  }
2458
+ mutationsFailed += 1;
2459
+ emitActivity({
2460
+ type: "mutation",
2461
+ at: Date.now(),
2462
+ name,
2463
+ status: "error"
2464
+ });
2465
+ if (attemptsMade > 1) {
2466
+ throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
2467
+ }
2468
+ throw lastError;
2469
+ } finally {
2470
+ mutationsInFlight -= 1;
2439
2471
  }
2440
- emitActivity({
2441
- type: "mutation",
2442
- at: Date.now(),
2443
- name,
2444
- status: "error"
2445
- });
2446
- if (attemptsMade > 1) {
2447
- throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
2448
- }
2449
- throw lastError;
2450
2472
  },
2451
2473
  runMutations: async (specs, ctx) => {
2452
2474
  if (specs.length === 0)
@@ -2685,6 +2707,45 @@ var createSyncEngine = (options = {}) => {
2685
2707
  }))
2686
2708
  };
2687
2709
  },
2710
+ metrics: () => {
2711
+ const now = Date.now();
2712
+ const byCollection = {};
2713
+ let totalSubscriptions = 0;
2714
+ for (const [name, subs] of active) {
2715
+ byCollection[name] = subs.size;
2716
+ totalSubscriptions += subs.size;
2717
+ }
2718
+ const oldest = changeLog[0];
2719
+ return {
2720
+ at: now,
2721
+ changeLog: {
2722
+ capacity: changeLogSize,
2723
+ entries: changeLog.length,
2724
+ oldestAgeMs: oldest ? now - oldest.at : null,
2725
+ oldestVersion: oldest ? oldest.version : null,
2726
+ retainMs: changeLogRetainMs
2727
+ },
2728
+ mutations: {
2729
+ completed: mutationsCompleted,
2730
+ failed: mutationsFailed,
2731
+ inFlight: mutationsInFlight,
2732
+ retried: mutationsRetried
2733
+ },
2734
+ reactiveCache: {
2735
+ capacity: reactiveCacheMax,
2736
+ entries: cachedReruns.size
2737
+ },
2738
+ schedules: {
2739
+ registered: schedules.size
2740
+ },
2741
+ subscriptions: {
2742
+ byCollection,
2743
+ total: totalSubscriptions
2744
+ },
2745
+ uptimeMs: now - engineStartedAt,
2746
+ version
2747
+ };
2748
+ },
2688
2749
  onActivity: (listener) => {
2689
2750
  activityListeners.add(listener);
2690
2751
  return () => {
@@ -3161,5 +3222,5 @@ export {
3161
3222
  CdcConsumerSlowError
3162
3223
  };
3163
3224
 
3164
- //# debugId=3473D89C765057E364756E2164756E21
3225
+ //# debugId=61A7A6BDD6B4D12064756E2164756E21
3165
3226
  //# sourceMappingURL=index.js.map