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