@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.mjs CHANGED
@@ -678,7 +678,7 @@ function extractMethodFromSelector(fn) {
678
678
  return fn.__selectorMethod;
679
679
  }
680
680
 
681
- // src/state/manager.ts
681
+ // src/state/utils.ts
682
682
  function createInitialState() {
683
683
  return {
684
684
  data: void 0,
@@ -694,6 +694,8 @@ function generateSelfTagFromKey(key) {
694
694
  return void 0;
695
695
  }
696
696
  }
697
+
698
+ // src/state/manager.ts
697
699
  function createStateManager() {
698
700
  const cache = /* @__PURE__ */ new Map();
699
701
  const subscribers = /* @__PURE__ */ new Map();
@@ -1225,7 +1227,7 @@ function createClient(baseUrl, defaultOptions) {
1225
1227
  });
1226
1228
  }
1227
1229
 
1228
- // src/operations/controller.ts
1230
+ // src/controllers/base/controller.ts
1229
1231
  function createOperationController(options) {
1230
1232
  const {
1231
1233
  operationType,
@@ -1389,23 +1391,7 @@ function createOperationController(options) {
1389
1391
  return controller;
1390
1392
  }
1391
1393
 
1392
- // src/operations/infinite-controller.ts
1393
- function createTrackerKey(path, method, baseOptions) {
1394
- return JSON.stringify({
1395
- path,
1396
- method,
1397
- baseOptions,
1398
- type: "infinite-tracker"
1399
- });
1400
- }
1401
- function createPageKey(path, method, baseOptions, pageRequest) {
1402
- return JSON.stringify({
1403
- path,
1404
- method,
1405
- baseOptions,
1406
- pageRequest
1407
- });
1408
- }
1394
+ // src/controllers/infinite/utils.ts
1409
1395
  function shallowMergeRequest(initial, override) {
1410
1396
  return {
1411
1397
  query: override.query ? { ...initial.query, ...override.query } : initial.query,
@@ -1413,38 +1399,26 @@ function shallowMergeRequest(initial, override) {
1413
1399
  body: override.body !== void 0 ? override.body : initial.body
1414
1400
  };
1415
1401
  }
1416
- function collectPageData(pageKeys, stateManager, pageRequests, initialRequest) {
1417
- const allResponses = [];
1418
- const allRequests = [];
1419
- for (const key of pageKeys) {
1420
- const cached = stateManager.getCache(key);
1421
- if (cached?.state?.data !== void 0) {
1422
- allResponses.push(cached.state.data);
1423
- allRequests.push(pageRequests.get(key) ?? initialRequest);
1424
- }
1425
- }
1426
- return { allResponses, allRequests };
1427
- }
1428
1402
  function createInitialInfiniteState() {
1429
1403
  return {
1430
1404
  data: void 0,
1431
- allResponses: void 0,
1432
- allRequests: void 0,
1405
+ pages: [],
1433
1406
  canFetchNext: false,
1434
1407
  canFetchPrev: false,
1435
1408
  error: void 0
1436
1409
  };
1437
1410
  }
1411
+
1412
+ // src/controllers/infinite/controller.ts
1438
1413
  function createInfiniteReadController(options) {
1439
1414
  const {
1440
1415
  path,
1441
1416
  method,
1442
1417
  tags,
1443
1418
  initialRequest,
1444
- baseOptionsForKey,
1445
- canFetchNext,
1419
+ canFetchNext = () => false,
1446
1420
  canFetchPrev,
1447
- nextPageRequest,
1421
+ nextPageRequest = () => ({}),
1448
1422
  prevPageRequest,
1449
1423
  merger,
1450
1424
  stateManager,
@@ -1461,32 +1435,10 @@ function createInfiniteReadController(options) {
1461
1435
  let pluginOptions = void 0;
1462
1436
  let fetchingDirection = null;
1463
1437
  let latestError = void 0;
1438
+ let activeInitialRequest = initialRequest;
1464
1439
  let cachedState = createInitialInfiniteState();
1465
- const trackerKey = createTrackerKey(path, method, baseOptionsForKey);
1466
1440
  let pageSubscriptions = [];
1467
- let trackerSubscription = null;
1468
1441
  let refetchUnsubscribe = null;
1469
- const loadFromTracker = () => {
1470
- const cached = stateManager.getCache(trackerKey);
1471
- const trackerData = cached?.state?.data;
1472
- if (trackerData) {
1473
- pageKeys = trackerData.pageKeys;
1474
- pageRequests = new Map(Object.entries(trackerData.pageRequests));
1475
- }
1476
- };
1477
- const saveToTracker = () => {
1478
- stateManager.setCache(trackerKey, {
1479
- state: {
1480
- data: {
1481
- pageKeys,
1482
- pageRequests: Object.fromEntries(pageRequests)
1483
- },
1484
- error: void 0,
1485
- timestamp: Date.now()
1486
- },
1487
- tags
1488
- });
1489
- };
1490
1442
  const computeState = () => {
1491
1443
  if (pageKeys.length === 0) {
1492
1444
  return {
@@ -1494,41 +1446,44 @@ function createInfiniteReadController(options) {
1494
1446
  error: latestError
1495
1447
  };
1496
1448
  }
1497
- const { allResponses, allRequests } = collectPageData(
1498
- pageKeys,
1499
- stateManager,
1500
- pageRequests,
1501
- initialRequest
1449
+ const computedPages = pageKeys.map(
1450
+ (key) => {
1451
+ const cached = stateManager.getCache(key);
1452
+ const meta = cached?.meta ? Object.fromEntries(cached.meta) : void 0;
1453
+ const input = pageRequests.get(key) ?? activeInitialRequest;
1454
+ let status = "pending";
1455
+ if (pendingFetches.has(key)) {
1456
+ status = "loading";
1457
+ } else if (cached?.state?.error) {
1458
+ status = "error";
1459
+ } else if (cached?.state?.data !== void 0) {
1460
+ status = cached?.stale ? "stale" : "success";
1461
+ }
1462
+ return {
1463
+ status,
1464
+ data: cached?.state?.data,
1465
+ error: cached?.state?.error,
1466
+ meta,
1467
+ input
1468
+ };
1469
+ }
1502
1470
  );
1503
- if (allResponses.length === 0) {
1504
- return {
1505
- data: void 0,
1506
- allResponses: void 0,
1507
- allRequests: void 0,
1508
- canFetchNext: false,
1509
- canFetchPrev: false,
1510
- error: latestError
1511
- };
1512
- }
1513
- const lastResponse = allResponses.at(-1);
1514
- const firstResponse = allResponses.at(0);
1515
- const lastRequest = allRequests.at(-1) ?? initialRequest;
1516
- const firstRequest = allRequests.at(0) ?? initialRequest;
1471
+ const lastPage = computedPages.at(-1);
1472
+ const firstPage = computedPages.at(0);
1517
1473
  const canNext = canFetchNext({
1518
- response: lastResponse,
1519
- allResponses,
1520
- request: lastRequest
1474
+ lastPage,
1475
+ pages: computedPages,
1476
+ request: lastPage?.input ?? activeInitialRequest
1521
1477
  });
1522
1478
  const canPrev = canFetchPrev ? canFetchPrev({
1523
- response: firstResponse,
1524
- allResponses,
1525
- request: firstRequest
1479
+ firstPage,
1480
+ pages: computedPages,
1481
+ request: firstPage?.input ?? activeInitialRequest
1526
1482
  }) : false;
1527
- const mergedData = merger(allResponses);
1483
+ const mergedData = computedPages.length > 0 ? merger(computedPages) : void 0;
1528
1484
  return {
1529
1485
  data: mergedData,
1530
- allResponses,
1531
- allRequests,
1486
+ pages: computedPages,
1532
1487
  canFetchNext: canNext,
1533
1488
  canFetchPrev: canPrev,
1534
1489
  error: latestError
@@ -1546,7 +1501,7 @@ function createInfiniteReadController(options) {
1546
1501
  };
1547
1502
  const createContext = (pageKey, requestOptions) => {
1548
1503
  return pluginExecutor.createContext({
1549
- operationType: "infiniteRead",
1504
+ operationType: "pages",
1550
1505
  path,
1551
1506
  method,
1552
1507
  queryKey: pageKey,
@@ -1566,13 +1521,15 @@ function createInfiniteReadController(options) {
1566
1521
  });
1567
1522
  };
1568
1523
  const doFetch = async (direction, requestOverride) => {
1569
- const mergedRequest = shallowMergeRequest(initialRequest, requestOverride);
1570
- const pageKey = createPageKey(
1524
+ const mergedRequest = shallowMergeRequest(
1525
+ activeInitialRequest,
1526
+ requestOverride
1527
+ );
1528
+ const pageKey = stateManager.createQueryKey({
1571
1529
  path,
1572
1530
  method,
1573
- baseOptionsForKey,
1574
- mergedRequest
1575
- );
1531
+ options: mergedRequest
1532
+ });
1576
1533
  const pendingPromise = stateManager.getPendingPromise(pageKey);
1577
1534
  if (pendingPromise || pendingFetches.has(pageKey)) {
1578
1535
  return;
@@ -1586,7 +1543,7 @@ function createInfiniteReadController(options) {
1586
1543
  const context = createContext(pageKey, mergedRequest);
1587
1544
  const coreFetch = async () => {
1588
1545
  try {
1589
- const response = await fetchFn(mergedRequest, signal);
1546
+ const response = await fetchFn(context.request, signal);
1590
1547
  if (signal.aborted) {
1591
1548
  return {
1592
1549
  status: 0,
@@ -1613,7 +1570,7 @@ function createInfiniteReadController(options) {
1613
1570
  }
1614
1571
  };
1615
1572
  const middlewarePromise = pluginExecutor.executeMiddleware(
1616
- "infiniteRead",
1573
+ "pages",
1617
1574
  context,
1618
1575
  coreFetch
1619
1576
  );
@@ -1634,7 +1591,6 @@ function createInfiniteReadController(options) {
1634
1591
  pageKeys = [pageKey, ...pageKeys];
1635
1592
  }
1636
1593
  }
1637
- saveToTracker();
1638
1594
  subscribeToPages();
1639
1595
  stateManager.setCache(pageKey, {
1640
1596
  state: {
@@ -1666,112 +1622,190 @@ function createInfiniteReadController(options) {
1666
1622
  await doFetch("next", {});
1667
1623
  return;
1668
1624
  }
1669
- const { allResponses, allRequests } = collectPageData(
1670
- pageKeys,
1671
- stateManager,
1672
- pageRequests,
1673
- initialRequest
1674
- );
1675
- if (allResponses.length === 0) return;
1676
- const lastResponse = allResponses.at(-1);
1677
- const lastRequest = allRequests.at(-1) ?? initialRequest;
1625
+ const state = computeState();
1626
+ const { pages } = state;
1627
+ if (pages.length === 0) return;
1628
+ const lastPage = pages.at(-1);
1678
1629
  const canNext = canFetchNext({
1679
- response: lastResponse,
1680
- allResponses,
1681
- request: lastRequest
1630
+ lastPage,
1631
+ pages,
1632
+ request: lastPage?.input ?? activeInitialRequest
1682
1633
  });
1683
1634
  if (!canNext) return;
1684
1635
  const nextRequest = nextPageRequest({
1685
- response: lastResponse,
1686
- allResponses,
1687
- request: lastRequest
1636
+ lastPage,
1637
+ pages,
1638
+ request: lastPage?.input ?? activeInitialRequest
1688
1639
  });
1689
1640
  await doFetch("next", nextRequest);
1690
1641
  },
1691
1642
  async fetchPrev() {
1692
1643
  if (!canFetchPrev || !prevPageRequest) return;
1693
1644
  if (pageKeys.length === 0) return;
1694
- const { allResponses, allRequests } = collectPageData(
1695
- pageKeys,
1696
- stateManager,
1697
- pageRequests,
1698
- initialRequest
1699
- );
1700
- if (allResponses.length === 0) return;
1701
- const firstResponse = allResponses.at(0);
1702
- const firstRequest = allRequests.at(0) ?? initialRequest;
1645
+ const state = computeState();
1646
+ const { pages } = state;
1647
+ if (pages.length === 0) return;
1648
+ const firstPage = pages.at(0);
1703
1649
  const canPrev = canFetchPrev({
1704
- response: firstResponse,
1705
- allResponses,
1706
- request: firstRequest
1650
+ firstPage,
1651
+ pages,
1652
+ request: firstPage?.input ?? activeInitialRequest
1707
1653
  });
1708
1654
  if (!canPrev) return;
1709
1655
  const prevRequest = prevPageRequest({
1710
- response: firstResponse,
1711
- allResponses,
1712
- request: firstRequest
1656
+ firstPage,
1657
+ pages,
1658
+ request: firstPage?.input ?? activeInitialRequest
1713
1659
  });
1714
1660
  await doFetch("prev", prevRequest);
1715
1661
  },
1716
- async refetch() {
1662
+ async trigger(options2) {
1663
+ const { force = true, ...requestOverride } = options2 ?? {};
1664
+ if (abortController) {
1665
+ abortController.abort();
1666
+ abortController = null;
1667
+ }
1717
1668
  for (const key of pageKeys) {
1718
- stateManager.deleteCache(key);
1669
+ stateManager.setPendingPromise(key, void 0);
1719
1670
  }
1720
- pageKeys = [];
1721
- pageRequests.clear();
1671
+ if (force) {
1672
+ const allPathCaches = stateManager.getCacheEntriesBySelfTag(path);
1673
+ for (const { key } of allPathCaches) {
1674
+ stateManager.setCache(key, { stale: true });
1675
+ }
1676
+ }
1677
+ pendingFetches.clear();
1678
+ fetchingDirection = null;
1679
+ if (requestOverride && Object.keys(requestOverride).length > 0) {
1680
+ activeInitialRequest = shallowMergeRequest(
1681
+ initialRequest,
1682
+ requestOverride
1683
+ );
1684
+ } else {
1685
+ activeInitialRequest = initialRequest;
1686
+ }
1687
+ const newFirstPageKey = stateManager.createQueryKey({
1688
+ path,
1689
+ method,
1690
+ options: activeInitialRequest
1691
+ });
1722
1692
  pageSubscriptions.forEach((unsub) => unsub());
1723
1693
  pageSubscriptions = [];
1694
+ pageKeys = [];
1695
+ pageRequests = /* @__PURE__ */ new Map();
1724
1696
  latestError = void 0;
1725
- saveToTracker();
1726
1697
  fetchingDirection = "next";
1727
1698
  notify();
1728
- await doFetch("next", {});
1699
+ abortController = new AbortController();
1700
+ const signal = abortController.signal;
1701
+ const context = createContext(newFirstPageKey, activeInitialRequest);
1702
+ if (force) {
1703
+ context.forceRefetch = true;
1704
+ }
1705
+ const coreFetch = async () => {
1706
+ try {
1707
+ const response = await fetchFn(context.request, signal);
1708
+ if (signal.aborted) {
1709
+ return {
1710
+ status: 0,
1711
+ data: void 0,
1712
+ aborted: true
1713
+ };
1714
+ }
1715
+ return response;
1716
+ } catch (err) {
1717
+ if (signal.aborted) {
1718
+ return {
1719
+ status: 0,
1720
+ data: void 0,
1721
+ aborted: true
1722
+ };
1723
+ }
1724
+ return {
1725
+ status: 0,
1726
+ error: err,
1727
+ data: void 0
1728
+ };
1729
+ }
1730
+ };
1731
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1732
+ "pages",
1733
+ context,
1734
+ coreFetch
1735
+ );
1736
+ stateManager.setPendingPromise(newFirstPageKey, middlewarePromise);
1737
+ const finalResponse = await middlewarePromise;
1738
+ pendingFetches.delete(newFirstPageKey);
1739
+ fetchingDirection = null;
1740
+ stateManager.setPendingPromise(newFirstPageKey, void 0);
1741
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1742
+ pageKeys = [newFirstPageKey];
1743
+ pageRequests = /* @__PURE__ */ new Map([[newFirstPageKey, activeInitialRequest]]);
1744
+ stateManager.setCache(newFirstPageKey, {
1745
+ state: {
1746
+ data: finalResponse.data,
1747
+ error: void 0,
1748
+ timestamp: Date.now()
1749
+ },
1750
+ tags,
1751
+ stale: false
1752
+ });
1753
+ subscribeToPages();
1754
+ latestError = void 0;
1755
+ } else if (finalResponse.error) {
1756
+ latestError = finalResponse.error;
1757
+ }
1758
+ notify();
1729
1759
  },
1730
1760
  abort() {
1731
1761
  abortController?.abort();
1732
1762
  abortController = null;
1733
1763
  },
1734
1764
  mount() {
1735
- loadFromTracker();
1736
1765
  cachedState = computeState();
1737
1766
  subscribeToPages();
1738
- trackerSubscription = stateManager.subscribeCache(trackerKey, notify);
1739
- const context = createContext(trackerKey, initialRequest);
1740
- pluginExecutor.executeLifecycle("onMount", "infiniteRead", context);
1767
+ const firstPageKey = stateManager.createQueryKey({
1768
+ path,
1769
+ method,
1770
+ options: initialRequest
1771
+ });
1772
+ const context = createContext(firstPageKey, initialRequest);
1773
+ pluginExecutor.executeLifecycle("onMount", "pages", context);
1741
1774
  refetchUnsubscribe = eventEmitter.on("refetch", (event) => {
1742
- const isRelevant = event.queryKey === trackerKey || pageKeys.includes(event.queryKey);
1743
- if (isRelevant) {
1744
- controller.refetch();
1775
+ if (pageKeys.includes(event.queryKey)) {
1776
+ controller.trigger();
1745
1777
  }
1746
1778
  });
1747
- const isStale = pageKeys.some((key) => {
1748
- const cached = stateManager.getCache(key);
1749
- return cached?.stale === true;
1750
- });
1751
- if (isStale) {
1752
- controller.refetch();
1753
- }
1754
1779
  },
1755
1780
  unmount() {
1756
- const context = createContext(trackerKey, initialRequest);
1757
- pluginExecutor.executeLifecycle("onUnmount", "infiniteRead", context);
1781
+ const firstPageKey = stateManager.createQueryKey({
1782
+ path,
1783
+ method,
1784
+ options: initialRequest
1785
+ });
1786
+ const context = createContext(firstPageKey, initialRequest);
1787
+ pluginExecutor.executeLifecycle("onUnmount", "pages", context);
1758
1788
  pageSubscriptions.forEach((unsub) => unsub());
1759
1789
  pageSubscriptions = [];
1760
- trackerSubscription?.();
1761
- trackerSubscription = null;
1762
1790
  refetchUnsubscribe?.();
1763
1791
  refetchUnsubscribe = null;
1764
1792
  },
1765
1793
  update(previousContext) {
1766
- const context = createContext(trackerKey, initialRequest);
1767
- pluginExecutor.executeUpdateLifecycle(
1768
- "infiniteRead",
1769
- context,
1770
- previousContext
1771
- );
1794
+ const firstPageKey = stateManager.createQueryKey({
1795
+ path,
1796
+ method,
1797
+ options: activeInitialRequest
1798
+ });
1799
+ const context = createContext(firstPageKey, activeInitialRequest);
1800
+ pluginExecutor.executeUpdateLifecycle("pages", context, previousContext);
1772
1801
  },
1773
1802
  getContext() {
1774
- return createContext(trackerKey, initialRequest);
1803
+ const firstPageKey = stateManager.createQueryKey({
1804
+ path,
1805
+ method,
1806
+ options: activeInitialRequest
1807
+ });
1808
+ return createContext(firstPageKey, activeInitialRequest);
1775
1809
  },
1776
1810
  setPluginOptions(opts) {
1777
1811
  pluginOptions = opts;
@@ -1780,7 +1814,7 @@ function createInfiniteReadController(options) {
1780
1814
  return controller;
1781
1815
  }
1782
1816
 
1783
- // src/queue/semaphore.ts
1817
+ // src/controllers/queue/semaphore.ts
1784
1818
  var Semaphore = class {
1785
1819
  constructor(max) {
1786
1820
  this.max = max;
@@ -1829,7 +1863,7 @@ var Semaphore = class {
1829
1863
  }
1830
1864
  };
1831
1865
 
1832
- // src/queue/controller.ts
1866
+ // src/controllers/queue/controller.ts
1833
1867
  var DEFAULT_CONCURRENCY = 3;
1834
1868
  function createQueueController(config, context) {
1835
1869
  const { path, method, operationType, hookOptions = {} } = config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [