@absolutejs/sync 1.19.0 → 1.20.1
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 +13 -0
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +181 -74
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/syncEngine.d.ts +76 -0
- package/dist/index.js +179 -74
- package/dist/index.js.map +3 -3
- package/dist/testing.js +179 -74
- package/dist/testing.js.map +3 -3
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -625,6 +625,28 @@ class CdcConsumerSlowError extends Error {
|
|
|
625
625
|
this.lastDeliveredVersion = lastDeliveredVersion;
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
|
+
|
|
629
|
+
class MutationQueueOverflowError extends Error {
|
|
630
|
+
queueLimit;
|
|
631
|
+
constructor(queueLimit) {
|
|
632
|
+
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.`);
|
|
633
|
+
this.name = "MutationQueueOverflowError";
|
|
634
|
+
this.queueLimit = queueLimit;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
class SubscriptionLimitError extends Error {
|
|
639
|
+
tenantKey;
|
|
640
|
+
limit;
|
|
641
|
+
active;
|
|
642
|
+
constructor(tenantKey, limit, active) {
|
|
643
|
+
super(`Tenant "${tenantKey}" is at the subscription cap ` + `(${active}/${limit}). Close an existing subscription before opening another.`);
|
|
644
|
+
this.name = "SubscriptionLimitError";
|
|
645
|
+
this.tenantKey = tenantKey;
|
|
646
|
+
this.limit = limit;
|
|
647
|
+
this.active = active;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
628
650
|
var defaultKey = (row) => row.id;
|
|
629
651
|
var shallowEqual3 = (a, b) => {
|
|
630
652
|
if (a === b) {
|
|
@@ -741,6 +763,65 @@ var createSyncEngine = (options = {}) => {
|
|
|
741
763
|
let mutationsFailed = 0;
|
|
742
764
|
let mutationsRetried = 0;
|
|
743
765
|
let mutationsInFlight = 0;
|
|
766
|
+
const mutationWaiters = [];
|
|
767
|
+
let mutationsQueued = 0;
|
|
768
|
+
const acquireMutationSlot = async () => {
|
|
769
|
+
const limit = options.mutationConcurrency;
|
|
770
|
+
if (limit === undefined) {
|
|
771
|
+
mutationsInFlight += 1;
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (mutationsInFlight < limit && mutationWaiters.length === 0) {
|
|
775
|
+
mutationsInFlight += 1;
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const queueLimit = options.mutationQueueLimit;
|
|
779
|
+
if (queueLimit !== undefined && mutationsQueued >= queueLimit) {
|
|
780
|
+
throw new MutationQueueOverflowError(queueLimit);
|
|
781
|
+
}
|
|
782
|
+
mutationsQueued += 1;
|
|
783
|
+
try {
|
|
784
|
+
await new Promise((resolve) => {
|
|
785
|
+
mutationWaiters.push(resolve);
|
|
786
|
+
});
|
|
787
|
+
} finally {
|
|
788
|
+
mutationsQueued -= 1;
|
|
789
|
+
}
|
|
790
|
+
mutationsInFlight += 1;
|
|
791
|
+
};
|
|
792
|
+
const releaseMutationSlot = () => {
|
|
793
|
+
mutationsInFlight -= 1;
|
|
794
|
+
if (options.mutationConcurrency === undefined)
|
|
795
|
+
return;
|
|
796
|
+
const next = mutationWaiters.shift();
|
|
797
|
+
if (next !== undefined)
|
|
798
|
+
next();
|
|
799
|
+
};
|
|
800
|
+
const subscriptionsByTenant = new Map;
|
|
801
|
+
const acquireSubscriptionSlot = (ctx, args) => {
|
|
802
|
+
const cap = options.subscriptionLimit;
|
|
803
|
+
if (cap === undefined)
|
|
804
|
+
return;
|
|
805
|
+
const tenantKey = cap.key(ctx, args);
|
|
806
|
+
if (tenantKey === undefined)
|
|
807
|
+
return;
|
|
808
|
+
const active2 = subscriptionsByTenant.get(tenantKey) ?? 0;
|
|
809
|
+
if (active2 >= cap.max) {
|
|
810
|
+
throw new SubscriptionLimitError(tenantKey, cap.max, active2);
|
|
811
|
+
}
|
|
812
|
+
subscriptionsByTenant.set(tenantKey, active2 + 1);
|
|
813
|
+
return tenantKey;
|
|
814
|
+
};
|
|
815
|
+
const releaseSubscriptionSlot = (tenantKey) => {
|
|
816
|
+
if (tenantKey === undefined)
|
|
817
|
+
return;
|
|
818
|
+
const active2 = subscriptionsByTenant.get(tenantKey);
|
|
819
|
+
if (active2 === undefined || active2 <= 1) {
|
|
820
|
+
subscriptionsByTenant.delete(tenantKey);
|
|
821
|
+
} else {
|
|
822
|
+
subscriptionsByTenant.set(tenantKey, active2 - 1);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
744
825
|
const reactiveCacheMax = options.reactiveCache?.max ?? 256;
|
|
745
826
|
const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
|
|
746
827
|
const cachedReruns = new Map;
|
|
@@ -1578,84 +1659,103 @@ var createSyncEngine = (options = {}) => {
|
|
|
1578
1659
|
if (registered === undefined) {
|
|
1579
1660
|
throw new Error(`Unknown collection "${collection}"`);
|
|
1580
1661
|
}
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1662
|
+
const tenantSlot = acquireSubscriptionSlot(ctx, { collection });
|
|
1663
|
+
let slotHandedOff = false;
|
|
1664
|
+
try {
|
|
1665
|
+
const typedOnDiff = onDiff;
|
|
1666
|
+
const subscribeSet = subsFor(collection);
|
|
1667
|
+
const wrapReturn = (sub) => {
|
|
1668
|
+
checkAborted(signal);
|
|
1669
|
+
const innerUnsubscribe = sub.unsubscribe;
|
|
1670
|
+
let released = false;
|
|
1671
|
+
const wrappedUnsubscribe = () => {
|
|
1672
|
+
if (released)
|
|
1673
|
+
return;
|
|
1674
|
+
released = true;
|
|
1675
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
1676
|
+
innerUnsubscribe();
|
|
1677
|
+
};
|
|
1678
|
+
const wrapped = { ...sub, unsubscribe: wrappedUnsubscribe };
|
|
1679
|
+
linkAbortToUnsubscribe(signal, wrappedUnsubscribe);
|
|
1680
|
+
slotHandedOff = true;
|
|
1681
|
+
return wrapped;
|
|
1682
|
+
};
|
|
1683
|
+
const registeredKind = registered.kind;
|
|
1684
|
+
if (registeredKind === "join") {
|
|
1685
|
+
const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1686
|
+
return wrapReturn(joined);
|
|
1687
|
+
}
|
|
1688
|
+
if (registeredKind === "graph") {
|
|
1689
|
+
const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1690
|
+
return wrapReturn(graphed);
|
|
1691
|
+
}
|
|
1692
|
+
if (registeredKind === "reactive") {
|
|
1693
|
+
const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1694
|
+
return wrapReturn(reactived);
|
|
1695
|
+
}
|
|
1696
|
+
if (registeredKind === "search") {
|
|
1697
|
+
const searched = await subscribeSearch(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1698
|
+
return wrapReturn(searched);
|
|
1699
|
+
}
|
|
1700
|
+
const definition = registered;
|
|
1701
|
+
if (definition.authorize !== undefined) {
|
|
1702
|
+
const allowed = await definition.authorize(params, ctx);
|
|
1703
|
+
if (!allowed) {
|
|
1704
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const key = definition.key ?? defaultKey;
|
|
1708
|
+
const match = definition.match;
|
|
1709
|
+
const tables = definition.tables ?? [collection];
|
|
1710
|
+
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
1711
|
+
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
1712
|
+
const rehydrate = async () => {
|
|
1713
|
+
const raw = [...await definition.hydrate(params, ctx)];
|
|
1714
|
+
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
1715
|
+
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
1716
|
+
};
|
|
1717
|
+
const incremental = match !== undefined && tables.length === 1;
|
|
1718
|
+
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
1719
|
+
const view = createMaterializedView({
|
|
1720
|
+
key,
|
|
1721
|
+
match: boundMatch
|
|
1722
|
+
});
|
|
1723
|
+
const resuming = since !== undefined && canResume(since, incremental);
|
|
1724
|
+
view.hydrate([...await rehydrate()]);
|
|
1725
|
+
const atVersion = version;
|
|
1726
|
+
const subscription = {
|
|
1727
|
+
kind: "view",
|
|
1728
|
+
collection,
|
|
1729
|
+
view,
|
|
1730
|
+
incremental,
|
|
1731
|
+
rehydrate,
|
|
1732
|
+
key,
|
|
1733
|
+
onDiff: typedOnDiff
|
|
1734
|
+
};
|
|
1735
|
+
subscribeSet.add(subscription);
|
|
1736
|
+
const unsubscribe = () => {
|
|
1737
|
+
subscribeSet.delete(subscription);
|
|
1738
|
+
};
|
|
1739
|
+
if (resuming) {
|
|
1740
|
+
return wrapReturn({
|
|
1741
|
+
initial: [],
|
|
1742
|
+
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
1743
|
+
cursor: currentCursor(),
|
|
1744
|
+
version: atVersion,
|
|
1745
|
+
unsubscribe
|
|
1746
|
+
});
|
|
1610
1747
|
}
|
|
1611
|
-
}
|
|
1612
|
-
const key = definition.key ?? defaultKey;
|
|
1613
|
-
const match = definition.match;
|
|
1614
|
-
const tables = definition.tables ?? [collection];
|
|
1615
|
-
const scopedTable = tables.length === 1 ? tables[0] : undefined;
|
|
1616
|
-
const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
|
|
1617
|
-
const rehydrate = async () => {
|
|
1618
|
-
const raw = [...await definition.hydrate(params, ctx)];
|
|
1619
|
-
const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
|
|
1620
|
-
return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
|
|
1621
|
-
};
|
|
1622
|
-
const incremental = match !== undefined && tables.length === 1;
|
|
1623
|
-
const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
|
|
1624
|
-
const view = createMaterializedView({
|
|
1625
|
-
key,
|
|
1626
|
-
match: boundMatch
|
|
1627
|
-
});
|
|
1628
|
-
const resuming = since !== undefined && canResume(since, incremental);
|
|
1629
|
-
view.hydrate([...await rehydrate()]);
|
|
1630
|
-
const atVersion = version;
|
|
1631
|
-
const subscription = {
|
|
1632
|
-
kind: "view",
|
|
1633
|
-
collection,
|
|
1634
|
-
view,
|
|
1635
|
-
incremental,
|
|
1636
|
-
rehydrate,
|
|
1637
|
-
key,
|
|
1638
|
-
onDiff: typedOnDiff
|
|
1639
|
-
};
|
|
1640
|
-
subscribeSet.add(subscription);
|
|
1641
|
-
const unsubscribe = () => {
|
|
1642
|
-
subscribeSet.delete(subscription);
|
|
1643
|
-
};
|
|
1644
|
-
if (resuming) {
|
|
1645
1748
|
return wrapReturn({
|
|
1646
|
-
initial:
|
|
1647
|
-
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
1749
|
+
initial: view.rows(),
|
|
1648
1750
|
cursor: currentCursor(),
|
|
1649
1751
|
version: atVersion,
|
|
1650
1752
|
unsubscribe
|
|
1651
1753
|
});
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
if (!slotHandedOff)
|
|
1756
|
+
releaseSubscriptionSlot(tenantSlot);
|
|
1757
|
+
throw error;
|
|
1652
1758
|
}
|
|
1653
|
-
return wrapReturn({
|
|
1654
|
-
initial: view.rows(),
|
|
1655
|
-
cursor: currentCursor(),
|
|
1656
|
-
version: atVersion,
|
|
1657
|
-
unsubscribe
|
|
1658
|
-
});
|
|
1659
1759
|
},
|
|
1660
1760
|
hydrate: async (collection, params, ctx, options2) => {
|
|
1661
1761
|
const signal = options2?.signal;
|
|
@@ -1768,6 +1868,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1768
1868
|
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
1769
1869
|
}
|
|
1770
1870
|
}
|
|
1871
|
+
await acquireMutationSlot();
|
|
1771
1872
|
const sandboxRunner = sandboxRunners.get(name);
|
|
1772
1873
|
const invokeHandler = sandboxRunner !== undefined ? sandboxRunner : (a, c, actions) => Promise.resolve(mutation.handler(a, c, actions));
|
|
1773
1874
|
const runHandler = async (tx) => {
|
|
@@ -1783,7 +1884,6 @@ var createSyncEngine = (options = {}) => {
|
|
|
1783
1884
|
const startedAt = Date.now();
|
|
1784
1885
|
let lastError;
|
|
1785
1886
|
let attemptsMade = 0;
|
|
1786
|
-
mutationsInFlight += 1;
|
|
1787
1887
|
try {
|
|
1788
1888
|
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
1789
1889
|
attemptsMade = attempt;
|
|
@@ -1836,7 +1936,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1836
1936
|
}
|
|
1837
1937
|
throw lastError;
|
|
1838
1938
|
} finally {
|
|
1839
|
-
|
|
1939
|
+
releaseMutationSlot();
|
|
1840
1940
|
}
|
|
1841
1941
|
},
|
|
1842
1942
|
runMutations: async (specs, ctx) => {
|
|
@@ -1849,6 +1949,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1849
1949
|
}
|
|
1850
1950
|
return { args: spec.args, mutation, name: spec.name };
|
|
1851
1951
|
});
|
|
1952
|
+
await acquireMutationSlot();
|
|
1852
1953
|
const runBatch = async (tx) => {
|
|
1853
1954
|
const results = [];
|
|
1854
1955
|
const accumulated = [];
|
|
@@ -1886,6 +1987,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
1886
1987
|
status: "error"
|
|
1887
1988
|
});
|
|
1888
1989
|
throw error;
|
|
1990
|
+
} finally {
|
|
1991
|
+
releaseMutationSlot();
|
|
1889
1992
|
}
|
|
1890
1993
|
},
|
|
1891
1994
|
registerSchedule: (schedule) => {
|
|
@@ -2105,6 +2208,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2105
2208
|
completed: mutationsCompleted,
|
|
2106
2209
|
failed: mutationsFailed,
|
|
2107
2210
|
inFlight: mutationsInFlight,
|
|
2211
|
+
queued: mutationsQueued,
|
|
2108
2212
|
retried: mutationsRetried
|
|
2109
2213
|
},
|
|
2110
2214
|
reactiveCache: {
|
|
@@ -2116,6 +2220,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2116
2220
|
},
|
|
2117
2221
|
subscriptions: {
|
|
2118
2222
|
byCollection,
|
|
2223
|
+
byTenant: Object.fromEntries(subscriptionsByTenant),
|
|
2119
2224
|
total: totalSubscriptions
|
|
2120
2225
|
},
|
|
2121
2226
|
uptimeMs: now - engineStartedAt,
|
|
@@ -2225,5 +2330,5 @@ export {
|
|
|
2225
2330
|
createTestEngine
|
|
2226
2331
|
};
|
|
2227
2332
|
|
|
2228
|
-
//# debugId=
|
|
2333
|
+
//# debugId=5456F0E468513B7264756E2164756E21
|
|
2229
2334
|
//# sourceMappingURL=testing.js.map
|