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