@spoosh/core 0.14.1 → 0.15.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/index.js CHANGED
@@ -37,6 +37,7 @@ __export(src_exports, {
37
37
  createProxyHandler: () => createProxyHandler,
38
38
  createQueueController: () => createQueueController,
39
39
  createSelectorProxy: () => createSelectorProxy,
40
+ createSpooshPlugin: () => createSpooshPlugin,
40
41
  createStateManager: () => createStateManager,
41
42
  createTracer: () => createTracer,
42
43
  executeFetch: () => executeFetch,
@@ -747,7 +748,7 @@ function extractMethodFromSelector(fn) {
747
748
  return fn.__selectorMethod;
748
749
  }
749
750
 
750
- // src/state/manager.ts
751
+ // src/state/utils.ts
751
752
  function createInitialState() {
752
753
  return {
753
754
  data: void 0,
@@ -763,6 +764,8 @@ function generateSelfTagFromKey(key) {
763
764
  return void 0;
764
765
  }
765
766
  }
767
+
768
+ // src/state/manager.ts
766
769
  function createStateManager() {
767
770
  const cache = /* @__PURE__ */ new Map();
768
771
  const subscribers = /* @__PURE__ */ new Map();
@@ -1082,6 +1085,11 @@ function createPluginRegistry(plugins) {
1082
1085
  };
1083
1086
  }
1084
1087
 
1088
+ // src/plugins/create.ts
1089
+ function createSpooshPlugin(definition) {
1090
+ return definition;
1091
+ }
1092
+
1085
1093
  // src/Spoosh.ts
1086
1094
  var Spoosh = class _Spoosh {
1087
1095
  baseUrl;
@@ -1294,7 +1302,7 @@ function createClient(baseUrl, defaultOptions) {
1294
1302
  });
1295
1303
  }
1296
1304
 
1297
- // src/operations/controller.ts
1305
+ // src/controllers/base/controller.ts
1298
1306
  function createOperationController(options) {
1299
1307
  const {
1300
1308
  operationType,
@@ -1458,23 +1466,7 @@ function createOperationController(options) {
1458
1466
  return controller;
1459
1467
  }
1460
1468
 
1461
- // src/operations/infinite-controller.ts
1462
- function createTrackerKey(path, method, baseOptions) {
1463
- return JSON.stringify({
1464
- path,
1465
- method,
1466
- baseOptions,
1467
- type: "infinite-tracker"
1468
- });
1469
- }
1470
- function createPageKey(path, method, baseOptions, pageRequest) {
1471
- return JSON.stringify({
1472
- path,
1473
- method,
1474
- baseOptions,
1475
- pageRequest
1476
- });
1477
- }
1469
+ // src/controllers/infinite/utils.ts
1478
1470
  function shallowMergeRequest(initial, override) {
1479
1471
  return {
1480
1472
  query: override.query ? { ...initial.query, ...override.query } : initial.query,
@@ -1482,38 +1474,26 @@ function shallowMergeRequest(initial, override) {
1482
1474
  body: override.body !== void 0 ? override.body : initial.body
1483
1475
  };
1484
1476
  }
1485
- function collectPageData(pageKeys, stateManager, pageRequests, initialRequest) {
1486
- const allResponses = [];
1487
- const allRequests = [];
1488
- for (const key of pageKeys) {
1489
- const cached = stateManager.getCache(key);
1490
- if (cached?.state?.data !== void 0) {
1491
- allResponses.push(cached.state.data);
1492
- allRequests.push(pageRequests.get(key) ?? initialRequest);
1493
- }
1494
- }
1495
- return { allResponses, allRequests };
1496
- }
1497
1477
  function createInitialInfiniteState() {
1498
1478
  return {
1499
1479
  data: void 0,
1500
- allResponses: void 0,
1501
- allRequests: void 0,
1480
+ pages: [],
1502
1481
  canFetchNext: false,
1503
1482
  canFetchPrev: false,
1504
1483
  error: void 0
1505
1484
  };
1506
1485
  }
1486
+
1487
+ // src/controllers/infinite/controller.ts
1507
1488
  function createInfiniteReadController(options) {
1508
1489
  const {
1509
1490
  path,
1510
1491
  method,
1511
1492
  tags,
1512
1493
  initialRequest,
1513
- baseOptionsForKey,
1514
- canFetchNext,
1494
+ canFetchNext = () => false,
1515
1495
  canFetchPrev,
1516
- nextPageRequest,
1496
+ nextPageRequest = () => ({}),
1517
1497
  prevPageRequest,
1518
1498
  merger,
1519
1499
  stateManager,
@@ -1530,32 +1510,10 @@ function createInfiniteReadController(options) {
1530
1510
  let pluginOptions = void 0;
1531
1511
  let fetchingDirection = null;
1532
1512
  let latestError = void 0;
1513
+ let activeInitialRequest = initialRequest;
1533
1514
  let cachedState = createInitialInfiniteState();
1534
- const trackerKey = createTrackerKey(path, method, baseOptionsForKey);
1535
1515
  let pageSubscriptions = [];
1536
- let trackerSubscription = null;
1537
1516
  let refetchUnsubscribe = null;
1538
- const loadFromTracker = () => {
1539
- const cached = stateManager.getCache(trackerKey);
1540
- const trackerData = cached?.state?.data;
1541
- if (trackerData) {
1542
- pageKeys = trackerData.pageKeys;
1543
- pageRequests = new Map(Object.entries(trackerData.pageRequests));
1544
- }
1545
- };
1546
- const saveToTracker = () => {
1547
- stateManager.setCache(trackerKey, {
1548
- state: {
1549
- data: {
1550
- pageKeys,
1551
- pageRequests: Object.fromEntries(pageRequests)
1552
- },
1553
- error: void 0,
1554
- timestamp: Date.now()
1555
- },
1556
- tags
1557
- });
1558
- };
1559
1517
  const computeState = () => {
1560
1518
  if (pageKeys.length === 0) {
1561
1519
  return {
@@ -1563,41 +1521,44 @@ function createInfiniteReadController(options) {
1563
1521
  error: latestError
1564
1522
  };
1565
1523
  }
1566
- const { allResponses, allRequests } = collectPageData(
1567
- pageKeys,
1568
- stateManager,
1569
- pageRequests,
1570
- initialRequest
1524
+ const computedPages = pageKeys.map(
1525
+ (key) => {
1526
+ const cached = stateManager.getCache(key);
1527
+ const meta = cached?.meta ? Object.fromEntries(cached.meta) : void 0;
1528
+ const input = pageRequests.get(key) ?? activeInitialRequest;
1529
+ let status = "pending";
1530
+ if (pendingFetches.has(key)) {
1531
+ status = "loading";
1532
+ } else if (cached?.state?.error) {
1533
+ status = "error";
1534
+ } else if (cached?.state?.data !== void 0) {
1535
+ status = cached?.stale ? "stale" : "success";
1536
+ }
1537
+ return {
1538
+ status,
1539
+ data: cached?.state?.data,
1540
+ error: cached?.state?.error,
1541
+ meta,
1542
+ input
1543
+ };
1544
+ }
1571
1545
  );
1572
- if (allResponses.length === 0) {
1573
- return {
1574
- data: void 0,
1575
- allResponses: void 0,
1576
- allRequests: void 0,
1577
- canFetchNext: false,
1578
- canFetchPrev: false,
1579
- error: latestError
1580
- };
1581
- }
1582
- const lastResponse = allResponses.at(-1);
1583
- const firstResponse = allResponses.at(0);
1584
- const lastRequest = allRequests.at(-1) ?? initialRequest;
1585
- const firstRequest = allRequests.at(0) ?? initialRequest;
1546
+ const lastPage = computedPages.at(-1);
1547
+ const firstPage = computedPages.at(0);
1586
1548
  const canNext = canFetchNext({
1587
- response: lastResponse,
1588
- allResponses,
1589
- request: lastRequest
1549
+ lastPage,
1550
+ pages: computedPages,
1551
+ request: lastPage?.input ?? activeInitialRequest
1590
1552
  });
1591
1553
  const canPrev = canFetchPrev ? canFetchPrev({
1592
- response: firstResponse,
1593
- allResponses,
1594
- request: firstRequest
1554
+ firstPage,
1555
+ pages: computedPages,
1556
+ request: firstPage?.input ?? activeInitialRequest
1595
1557
  }) : false;
1596
- const mergedData = merger(allResponses);
1558
+ const mergedData = computedPages.length > 0 ? merger(computedPages) : void 0;
1597
1559
  return {
1598
1560
  data: mergedData,
1599
- allResponses,
1600
- allRequests,
1561
+ pages: computedPages,
1601
1562
  canFetchNext: canNext,
1602
1563
  canFetchPrev: canPrev,
1603
1564
  error: latestError
@@ -1615,7 +1576,7 @@ function createInfiniteReadController(options) {
1615
1576
  };
1616
1577
  const createContext = (pageKey, requestOptions) => {
1617
1578
  return pluginExecutor.createContext({
1618
- operationType: "infiniteRead",
1579
+ operationType: "pages",
1619
1580
  path,
1620
1581
  method,
1621
1582
  queryKey: pageKey,
@@ -1635,13 +1596,15 @@ function createInfiniteReadController(options) {
1635
1596
  });
1636
1597
  };
1637
1598
  const doFetch = async (direction, requestOverride) => {
1638
- const mergedRequest = shallowMergeRequest(initialRequest, requestOverride);
1639
- const pageKey = createPageKey(
1599
+ const mergedRequest = shallowMergeRequest(
1600
+ activeInitialRequest,
1601
+ requestOverride
1602
+ );
1603
+ const pageKey = stateManager.createQueryKey({
1640
1604
  path,
1641
1605
  method,
1642
- baseOptionsForKey,
1643
- mergedRequest
1644
- );
1606
+ options: mergedRequest
1607
+ });
1645
1608
  const pendingPromise = stateManager.getPendingPromise(pageKey);
1646
1609
  if (pendingPromise || pendingFetches.has(pageKey)) {
1647
1610
  return;
@@ -1655,7 +1618,7 @@ function createInfiniteReadController(options) {
1655
1618
  const context = createContext(pageKey, mergedRequest);
1656
1619
  const coreFetch = async () => {
1657
1620
  try {
1658
- const response = await fetchFn(mergedRequest, signal);
1621
+ const response = await fetchFn(context.request, signal);
1659
1622
  if (signal.aborted) {
1660
1623
  return {
1661
1624
  status: 0,
@@ -1682,7 +1645,7 @@ function createInfiniteReadController(options) {
1682
1645
  }
1683
1646
  };
1684
1647
  const middlewarePromise = pluginExecutor.executeMiddleware(
1685
- "infiniteRead",
1648
+ "pages",
1686
1649
  context,
1687
1650
  coreFetch
1688
1651
  );
@@ -1703,7 +1666,6 @@ function createInfiniteReadController(options) {
1703
1666
  pageKeys = [pageKey, ...pageKeys];
1704
1667
  }
1705
1668
  }
1706
- saveToTracker();
1707
1669
  subscribeToPages();
1708
1670
  stateManager.setCache(pageKey, {
1709
1671
  state: {
@@ -1735,112 +1697,190 @@ function createInfiniteReadController(options) {
1735
1697
  await doFetch("next", {});
1736
1698
  return;
1737
1699
  }
1738
- const { allResponses, allRequests } = collectPageData(
1739
- pageKeys,
1740
- stateManager,
1741
- pageRequests,
1742
- initialRequest
1743
- );
1744
- if (allResponses.length === 0) return;
1745
- const lastResponse = allResponses.at(-1);
1746
- const lastRequest = allRequests.at(-1) ?? initialRequest;
1700
+ const state = computeState();
1701
+ const { pages } = state;
1702
+ if (pages.length === 0) return;
1703
+ const lastPage = pages.at(-1);
1747
1704
  const canNext = canFetchNext({
1748
- response: lastResponse,
1749
- allResponses,
1750
- request: lastRequest
1705
+ lastPage,
1706
+ pages,
1707
+ request: lastPage?.input ?? activeInitialRequest
1751
1708
  });
1752
1709
  if (!canNext) return;
1753
1710
  const nextRequest = nextPageRequest({
1754
- response: lastResponse,
1755
- allResponses,
1756
- request: lastRequest
1711
+ lastPage,
1712
+ pages,
1713
+ request: lastPage?.input ?? activeInitialRequest
1757
1714
  });
1758
1715
  await doFetch("next", nextRequest);
1759
1716
  },
1760
1717
  async fetchPrev() {
1761
1718
  if (!canFetchPrev || !prevPageRequest) return;
1762
1719
  if (pageKeys.length === 0) return;
1763
- const { allResponses, allRequests } = collectPageData(
1764
- pageKeys,
1765
- stateManager,
1766
- pageRequests,
1767
- initialRequest
1768
- );
1769
- if (allResponses.length === 0) return;
1770
- const firstResponse = allResponses.at(0);
1771
- const firstRequest = allRequests.at(0) ?? initialRequest;
1720
+ const state = computeState();
1721
+ const { pages } = state;
1722
+ if (pages.length === 0) return;
1723
+ const firstPage = pages.at(0);
1772
1724
  const canPrev = canFetchPrev({
1773
- response: firstResponse,
1774
- allResponses,
1775
- request: firstRequest
1725
+ firstPage,
1726
+ pages,
1727
+ request: firstPage?.input ?? activeInitialRequest
1776
1728
  });
1777
1729
  if (!canPrev) return;
1778
1730
  const prevRequest = prevPageRequest({
1779
- response: firstResponse,
1780
- allResponses,
1781
- request: firstRequest
1731
+ firstPage,
1732
+ pages,
1733
+ request: firstPage?.input ?? activeInitialRequest
1782
1734
  });
1783
1735
  await doFetch("prev", prevRequest);
1784
1736
  },
1785
- async refetch() {
1737
+ async trigger(options2) {
1738
+ const { force = true, ...requestOverride } = options2 ?? {};
1739
+ if (abortController) {
1740
+ abortController.abort();
1741
+ abortController = null;
1742
+ }
1786
1743
  for (const key of pageKeys) {
1787
- stateManager.deleteCache(key);
1744
+ stateManager.setPendingPromise(key, void 0);
1788
1745
  }
1789
- pageKeys = [];
1790
- pageRequests.clear();
1746
+ if (force) {
1747
+ const allPathCaches = stateManager.getCacheEntriesBySelfTag(path);
1748
+ for (const { key } of allPathCaches) {
1749
+ stateManager.setCache(key, { stale: true });
1750
+ }
1751
+ }
1752
+ pendingFetches.clear();
1753
+ fetchingDirection = null;
1754
+ if (requestOverride && Object.keys(requestOverride).length > 0) {
1755
+ activeInitialRequest = shallowMergeRequest(
1756
+ initialRequest,
1757
+ requestOverride
1758
+ );
1759
+ } else {
1760
+ activeInitialRequest = initialRequest;
1761
+ }
1762
+ const newFirstPageKey = stateManager.createQueryKey({
1763
+ path,
1764
+ method,
1765
+ options: activeInitialRequest
1766
+ });
1791
1767
  pageSubscriptions.forEach((unsub) => unsub());
1792
1768
  pageSubscriptions = [];
1769
+ pageKeys = [];
1770
+ pageRequests = /* @__PURE__ */ new Map();
1793
1771
  latestError = void 0;
1794
- saveToTracker();
1795
1772
  fetchingDirection = "next";
1796
1773
  notify();
1797
- await doFetch("next", {});
1774
+ abortController = new AbortController();
1775
+ const signal = abortController.signal;
1776
+ const context = createContext(newFirstPageKey, activeInitialRequest);
1777
+ if (force) {
1778
+ context.forceRefetch = true;
1779
+ }
1780
+ const coreFetch = async () => {
1781
+ try {
1782
+ const response = await fetchFn(context.request, signal);
1783
+ if (signal.aborted) {
1784
+ return {
1785
+ status: 0,
1786
+ data: void 0,
1787
+ aborted: true
1788
+ };
1789
+ }
1790
+ return response;
1791
+ } catch (err) {
1792
+ if (signal.aborted) {
1793
+ return {
1794
+ status: 0,
1795
+ data: void 0,
1796
+ aborted: true
1797
+ };
1798
+ }
1799
+ return {
1800
+ status: 0,
1801
+ error: err,
1802
+ data: void 0
1803
+ };
1804
+ }
1805
+ };
1806
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1807
+ "pages",
1808
+ context,
1809
+ coreFetch
1810
+ );
1811
+ stateManager.setPendingPromise(newFirstPageKey, middlewarePromise);
1812
+ const finalResponse = await middlewarePromise;
1813
+ pendingFetches.delete(newFirstPageKey);
1814
+ fetchingDirection = null;
1815
+ stateManager.setPendingPromise(newFirstPageKey, void 0);
1816
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1817
+ pageKeys = [newFirstPageKey];
1818
+ pageRequests = /* @__PURE__ */ new Map([[newFirstPageKey, activeInitialRequest]]);
1819
+ stateManager.setCache(newFirstPageKey, {
1820
+ state: {
1821
+ data: finalResponse.data,
1822
+ error: void 0,
1823
+ timestamp: Date.now()
1824
+ },
1825
+ tags,
1826
+ stale: false
1827
+ });
1828
+ subscribeToPages();
1829
+ latestError = void 0;
1830
+ } else if (finalResponse.error) {
1831
+ latestError = finalResponse.error;
1832
+ }
1833
+ notify();
1798
1834
  },
1799
1835
  abort() {
1800
1836
  abortController?.abort();
1801
1837
  abortController = null;
1802
1838
  },
1803
1839
  mount() {
1804
- loadFromTracker();
1805
1840
  cachedState = computeState();
1806
1841
  subscribeToPages();
1807
- trackerSubscription = stateManager.subscribeCache(trackerKey, notify);
1808
- const context = createContext(trackerKey, initialRequest);
1809
- pluginExecutor.executeLifecycle("onMount", "infiniteRead", context);
1842
+ const firstPageKey = stateManager.createQueryKey({
1843
+ path,
1844
+ method,
1845
+ options: initialRequest
1846
+ });
1847
+ const context = createContext(firstPageKey, initialRequest);
1848
+ pluginExecutor.executeLifecycle("onMount", "pages", context);
1810
1849
  refetchUnsubscribe = eventEmitter.on("refetch", (event) => {
1811
- const isRelevant = event.queryKey === trackerKey || pageKeys.includes(event.queryKey);
1812
- if (isRelevant) {
1813
- controller.refetch();
1850
+ if (pageKeys.includes(event.queryKey)) {
1851
+ controller.trigger();
1814
1852
  }
1815
1853
  });
1816
- const isStale = pageKeys.some((key) => {
1817
- const cached = stateManager.getCache(key);
1818
- return cached?.stale === true;
1819
- });
1820
- if (isStale) {
1821
- controller.refetch();
1822
- }
1823
1854
  },
1824
1855
  unmount() {
1825
- const context = createContext(trackerKey, initialRequest);
1826
- pluginExecutor.executeLifecycle("onUnmount", "infiniteRead", context);
1856
+ const firstPageKey = stateManager.createQueryKey({
1857
+ path,
1858
+ method,
1859
+ options: initialRequest
1860
+ });
1861
+ const context = createContext(firstPageKey, initialRequest);
1862
+ pluginExecutor.executeLifecycle("onUnmount", "pages", context);
1827
1863
  pageSubscriptions.forEach((unsub) => unsub());
1828
1864
  pageSubscriptions = [];
1829
- trackerSubscription?.();
1830
- trackerSubscription = null;
1831
1865
  refetchUnsubscribe?.();
1832
1866
  refetchUnsubscribe = null;
1833
1867
  },
1834
1868
  update(previousContext) {
1835
- const context = createContext(trackerKey, initialRequest);
1836
- pluginExecutor.executeUpdateLifecycle(
1837
- "infiniteRead",
1838
- context,
1839
- previousContext
1840
- );
1869
+ const firstPageKey = stateManager.createQueryKey({
1870
+ path,
1871
+ method,
1872
+ options: activeInitialRequest
1873
+ });
1874
+ const context = createContext(firstPageKey, activeInitialRequest);
1875
+ pluginExecutor.executeUpdateLifecycle("pages", context, previousContext);
1841
1876
  },
1842
1877
  getContext() {
1843
- return createContext(trackerKey, initialRequest);
1878
+ const firstPageKey = stateManager.createQueryKey({
1879
+ path,
1880
+ method,
1881
+ options: activeInitialRequest
1882
+ });
1883
+ return createContext(firstPageKey, activeInitialRequest);
1844
1884
  },
1845
1885
  setPluginOptions(opts) {
1846
1886
  pluginOptions = opts;
@@ -1849,7 +1889,7 @@ function createInfiniteReadController(options) {
1849
1889
  return controller;
1850
1890
  }
1851
1891
 
1852
- // src/queue/semaphore.ts
1892
+ // src/controllers/queue/semaphore.ts
1853
1893
  var Semaphore = class {
1854
1894
  constructor(max) {
1855
1895
  this.max = max;
@@ -1898,7 +1938,7 @@ var Semaphore = class {
1898
1938
  }
1899
1939
  };
1900
1940
 
1901
- // src/queue/controller.ts
1941
+ // src/controllers/queue/controller.ts
1902
1942
  var DEFAULT_CONCURRENCY = 3;
1903
1943
  function createQueueController(config, context) {
1904
1944
  const { path, method, operationType, hookOptions = {} } = config;