@absolutejs/sync 1.18.2 → 1.20.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.
@@ -96,6 +96,11 @@ export type EngineMetrics = {
96
96
  retried: number;
97
97
  /** Currently running, not yet committed or failed. */
98
98
  inFlight: number;
99
+ /**
100
+ * Waiting in the `mutationConcurrency` queue. Always `0` when
101
+ * `mutationConcurrency` is unset (no semaphore). Added in 1.20.0.
102
+ */
103
+ queued: number;
99
104
  };
100
105
  schedules: {
101
106
  registered: number;
@@ -47,8 +47,8 @@ export type { MutationActions, MutationDefinition, MutationHandler, TableWriter,
47
47
  export type { BridgeFetchConfig, BridgeFetchEndpoint, BridgeFetchResponse, HandlerMetricsHook, HandlerMetricsRecord, SandboxConfig } from './sandbox';
48
48
  export { exponentialBackoff, isSerializationFailure, RetriesExhaustedError } from './retry';
49
49
  export type { ExponentialBackoffOptions, RetryPolicy } from './retry';
50
- export { CdcConsumerSlowError, createSyncEngine, MissedChangesError, SchemaError, UnauthorizedError } from './syncEngine';
51
- export type { CrdtFields, LoggedChange, StreamChangesOptions, SubscribeArgs, Subscription, SyncEngine } from './syncEngine';
50
+ export { CdcConsumerSlowError, createSyncEngine, MissedChangesError, MutationQueueOverflowError, SchemaError, UnauthorizedError } from './syncEngine';
51
+ export type { ChangeLogSnapshot, CrdtFields, LoggedChange, StreamChangesOptions, SubscribeArgs, Subscription, SyncEngine, SyncEngineOptions } from './syncEngine';
52
52
  export { syncCdc } from './cdc';
53
53
  export type { SyncCdcOptions } from './cdc';
54
54
  export type { CrdtMergeable } from '../crdt';
@@ -1410,6 +1410,15 @@ class CdcConsumerSlowError extends Error {
1410
1410
  this.lastDeliveredVersion = lastDeliveredVersion;
1411
1411
  }
1412
1412
  }
1413
+
1414
+ class MutationQueueOverflowError extends Error {
1415
+ queueLimit;
1416
+ constructor(queueLimit) {
1417
+ super(`Mutation queue overflowed (limit ${queueLimit}); the engine is at ` + `its mutationConcurrency cap and the waiting queue is full. ` + `Retry later or shed load at the gateway.`);
1418
+ this.name = "MutationQueueOverflowError";
1419
+ this.queueLimit = queueLimit;
1420
+ }
1421
+ }
1413
1422
  var defaultKey = (row) => row.id;
1414
1423
  var shallowEqual4 = (a, b) => {
1415
1424
  if (a === b) {
@@ -1526,6 +1535,40 @@ var createSyncEngine = (options = {}) => {
1526
1535
  let mutationsFailed = 0;
1527
1536
  let mutationsRetried = 0;
1528
1537
  let mutationsInFlight = 0;
1538
+ const mutationWaiters = [];
1539
+ let mutationsQueued = 0;
1540
+ const acquireMutationSlot = async () => {
1541
+ const limit = options.mutationConcurrency;
1542
+ if (limit === undefined) {
1543
+ mutationsInFlight += 1;
1544
+ return;
1545
+ }
1546
+ if (mutationsInFlight < limit && mutationWaiters.length === 0) {
1547
+ mutationsInFlight += 1;
1548
+ return;
1549
+ }
1550
+ const queueLimit = options.mutationQueueLimit;
1551
+ if (queueLimit !== undefined && mutationsQueued >= queueLimit) {
1552
+ throw new MutationQueueOverflowError(queueLimit);
1553
+ }
1554
+ mutationsQueued += 1;
1555
+ try {
1556
+ await new Promise((resolve) => {
1557
+ mutationWaiters.push(resolve);
1558
+ });
1559
+ } finally {
1560
+ mutationsQueued -= 1;
1561
+ }
1562
+ mutationsInFlight += 1;
1563
+ };
1564
+ const releaseMutationSlot = () => {
1565
+ mutationsInFlight -= 1;
1566
+ if (options.mutationConcurrency === undefined)
1567
+ return;
1568
+ const next = mutationWaiters.shift();
1569
+ if (next !== undefined)
1570
+ next();
1571
+ };
1529
1572
  const reactiveCacheMax = options.reactiveCache?.max ?? 256;
1530
1573
  const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
1531
1574
  const cachedReruns = new Map;
@@ -1567,6 +1610,31 @@ var createSyncEngine = (options = {}) => {
1567
1610
  const runInTransaction = options.transaction;
1568
1611
  const instanceId = options.instanceId ?? globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
1569
1612
  let clusterBus;
1613
+ const importChangeLog = (snapshot) => {
1614
+ if (version !== 0) {
1615
+ throw new Error(`[sync] importChangeLog: engine already has version ${version}; ` + `restore must happen before any local writes commit.`);
1616
+ }
1617
+ if (snapshot.instanceId !== instanceId) {
1618
+ throw new Error(`[sync] importChangeLog: snapshot instanceId "${snapshot.instanceId}" ` + `does not match this engine's instanceId "${instanceId}". ` + `Pass options.instanceId = "${snapshot.instanceId}" to createSyncEngine.`);
1619
+ }
1620
+ version = snapshot.version;
1621
+ for (const entry of snapshot.entries) {
1622
+ changeLog.push(entry);
1623
+ }
1624
+ while (changeLog.length > changeLogSize) {
1625
+ changeLog.shift();
1626
+ }
1627
+ if (changeLogRetainMs !== null && changeLogRetainMs > 0) {
1628
+ const cutoff = Date.now() - changeLogRetainMs;
1629
+ while (changeLog.length > 0 && changeLog[0].at < cutoff) {
1630
+ changeLog.shift();
1631
+ }
1632
+ }
1633
+ return snapshot.entries.length;
1634
+ };
1635
+ if (options.initialChangeLog !== undefined) {
1636
+ importChangeLog(options.initialChangeLog);
1637
+ }
1570
1638
  const broadcast = (changes, originVersion) => {
1571
1639
  if (clusterBus !== undefined && changes.length > 0) {
1572
1640
  clusterBus.publish({ changes, origin: instanceId, originVersion });
@@ -2528,6 +2596,7 @@ var createSyncEngine = (options = {}) => {
2528
2596
  throw new UnauthorizedError(`run mutation "${name}"`);
2529
2597
  }
2530
2598
  }
2599
+ await acquireMutationSlot();
2531
2600
  const sandboxRunner = sandboxRunners.get(name);
2532
2601
  const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
2533
2602
  const runHandler = async (tx) => {
@@ -2543,7 +2612,6 @@ var createSyncEngine = (options = {}) => {
2543
2612
  const startedAt = Date.now();
2544
2613
  let lastError;
2545
2614
  let attemptsMade = 0;
2546
- mutationsInFlight += 1;
2547
2615
  try {
2548
2616
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
2549
2617
  attemptsMade = attempt;
@@ -2596,7 +2664,7 @@ var createSyncEngine = (options = {}) => {
2596
2664
  }
2597
2665
  throw lastError;
2598
2666
  } finally {
2599
- mutationsInFlight -= 1;
2667
+ releaseMutationSlot();
2600
2668
  }
2601
2669
  },
2602
2670
  runMutations: async (specs, ctx) => {
@@ -2609,6 +2677,7 @@ var createSyncEngine = (options = {}) => {
2609
2677
  }
2610
2678
  return { args: spec.args, mutation, name: spec.name };
2611
2679
  });
2680
+ await acquireMutationSlot();
2612
2681
  const runBatch = async (tx) => {
2613
2682
  const results = [];
2614
2683
  const accumulated = [];
@@ -2646,6 +2715,8 @@ var createSyncEngine = (options = {}) => {
2646
2715
  status: "error"
2647
2716
  });
2648
2717
  throw error;
2718
+ } finally {
2719
+ releaseMutationSlot();
2649
2720
  }
2650
2721
  },
2651
2722
  registerSchedule: (schedule) => {
@@ -2836,6 +2907,13 @@ var createSyncEngine = (options = {}) => {
2836
2907
  }))
2837
2908
  };
2838
2909
  },
2910
+ exportChangeLog: () => ({
2911
+ entries: changeLog.slice(),
2912
+ exportedAt: Date.now(),
2913
+ instanceId,
2914
+ version
2915
+ }),
2916
+ importChangeLog,
2839
2917
  metrics: () => {
2840
2918
  const now = Date.now();
2841
2919
  const byCollection = {};
@@ -2858,6 +2936,7 @@ var createSyncEngine = (options = {}) => {
2858
2936
  completed: mutationsCompleted,
2859
2937
  failed: mutationsFailed,
2860
2938
  inFlight: mutationsInFlight,
2939
+ queued: mutationsQueued,
2861
2940
  retried: mutationsRetried
2862
2941
  },
2863
2942
  reactiveCache: {
@@ -3402,9 +3481,10 @@ export {
3402
3481
  RetriesExhaustedError,
3403
3482
  PackTableConflictError,
3404
3483
  PackMissingDependencyError,
3484
+ MutationQueueOverflowError,
3405
3485
  MissedChangesError,
3406
3486
  CdcConsumerSlowError
3407
3487
  };
3408
3488
 
3409
- //# debugId=83816548811B9BFD64756E2164756E21
3489
+ //# debugId=E104F5FFDBD631C764756E2164756E21
3410
3490
  //# sourceMappingURL=index.js.map