@absolutejs/sync 1.20.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/testing.js CHANGED
@@ -634,6 +634,19 @@ class MutationQueueOverflowError extends Error {
634
634
  this.queueLimit = queueLimit;
635
635
  }
636
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
+ }
637
650
  var defaultKey = (row) => row.id;
638
651
  var shallowEqual3 = (a, b) => {
639
652
  if (a === b) {
@@ -784,6 +797,31 @@ var createSyncEngine = (options = {}) => {
784
797
  if (next !== undefined)
785
798
  next();
786
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
+ };
787
825
  const reactiveCacheMax = options.reactiveCache?.max ?? 256;
788
826
  const reactiveCacheTtlMs = options.reactiveCache?.ttlMs ?? 60000;
789
827
  const cachedReruns = new Map;
@@ -1621,84 +1659,103 @@ var createSyncEngine = (options = {}) => {
1621
1659
  if (registered === undefined) {
1622
1660
  throw new Error(`Unknown collection "${collection}"`);
1623
1661
  }
1624
- const typedOnDiff = onDiff;
1625
- const subscribeSet = subsFor(collection);
1626
- const wrapReturn = (sub) => {
1627
- checkAborted(signal);
1628
- linkAbortToUnsubscribe(signal, sub.unsubscribe);
1629
- return sub;
1630
- };
1631
- const registeredKind = registered.kind;
1632
- if (registeredKind === "join") {
1633
- const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1634
- return wrapReturn(joined);
1635
- }
1636
- if (registeredKind === "graph") {
1637
- const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1638
- return wrapReturn(graphed);
1639
- }
1640
- if (registeredKind === "reactive") {
1641
- const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1642
- return wrapReturn(reactived);
1643
- }
1644
- if (registeredKind === "search") {
1645
- const searched = await subscribeSearch(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1646
- return wrapReturn(searched);
1647
- }
1648
- const definition = registered;
1649
- if (definition.authorize !== undefined) {
1650
- const allowed = await definition.authorize(params, ctx);
1651
- if (!allowed) {
1652
- throw new UnauthorizedError(`subscribe to collection "${collection}"`);
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
+ });
1653
1747
  }
1654
- }
1655
- const key = definition.key ?? defaultKey;
1656
- const match = definition.match;
1657
- const tables = definition.tables ?? [collection];
1658
- const scopedTable = tables.length === 1 ? tables[0] : undefined;
1659
- const readRule = scopedTable !== undefined ? readRuleFor(scopedTable) : undefined;
1660
- const rehydrate = async () => {
1661
- const raw = [...await definition.hydrate(params, ctx)];
1662
- const rows = scopedTable !== undefined ? raw.map((row) => migrateRow(scopedTable, row)) : raw;
1663
- return readRule ? rows.filter((row) => readRule(ctx, row)) : rows;
1664
- };
1665
- const incremental = match !== undefined && tables.length === 1;
1666
- const boundMatch = incremental ? (row) => match(row, params, ctx) && (readRule ? readRule(ctx, row) : true) : () => true;
1667
- const view = createMaterializedView({
1668
- key,
1669
- match: boundMatch
1670
- });
1671
- const resuming = since !== undefined && canResume(since, incremental);
1672
- view.hydrate([...await rehydrate()]);
1673
- const atVersion = version;
1674
- const subscription = {
1675
- kind: "view",
1676
- collection,
1677
- view,
1678
- incremental,
1679
- rehydrate,
1680
- key,
1681
- onDiff: typedOnDiff
1682
- };
1683
- subscribeSet.add(subscription);
1684
- const unsubscribe = () => {
1685
- subscribeSet.delete(subscription);
1686
- };
1687
- if (resuming) {
1688
1748
  return wrapReturn({
1689
- initial: [],
1690
- catchup: buildCatchup(since, tables, key, boundMatch),
1749
+ initial: view.rows(),
1691
1750
  cursor: currentCursor(),
1692
1751
  version: atVersion,
1693
1752
  unsubscribe
1694
1753
  });
1754
+ } catch (error) {
1755
+ if (!slotHandedOff)
1756
+ releaseSubscriptionSlot(tenantSlot);
1757
+ throw error;
1695
1758
  }
1696
- return wrapReturn({
1697
- initial: view.rows(),
1698
- cursor: currentCursor(),
1699
- version: atVersion,
1700
- unsubscribe
1701
- });
1702
1759
  },
1703
1760
  hydrate: async (collection, params, ctx, options2) => {
1704
1761
  const signal = options2?.signal;
@@ -2163,6 +2220,7 @@ var createSyncEngine = (options = {}) => {
2163
2220
  },
2164
2221
  subscriptions: {
2165
2222
  byCollection,
2223
+ byTenant: Object.fromEntries(subscriptionsByTenant),
2166
2224
  total: totalSubscriptions
2167
2225
  },
2168
2226
  uptimeMs: now - engineStartedAt,
@@ -2272,5 +2330,5 @@ export {
2272
2330
  createTestEngine
2273
2331
  };
2274
2332
 
2275
- //# debugId=261DFADB673FBB5664756E2164756E21
2333
+ //# debugId=5456F0E468513B7264756E2164756E21
2276
2334
  //# sourceMappingURL=testing.js.map