@teleporthq/teleport-plugin-next-data-source 0.44.0 → 0.44.4

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.
Files changed (94) hide show
  1. package/__tests__/_helpers/first-page-runtime.ts +184 -0
  2. package/__tests__/_helpers/pagination-widget-fixture.ts +67 -1
  3. package/__tests__/browser-row-policy.test.ts +202 -2
  4. package/__tests__/data-source-request-locale.test.ts +4 -3
  5. package/__tests__/fetchers.test.ts +6 -2
  6. package/__tests__/infinite-scroll.test.ts +210 -5
  7. package/__tests__/page-data-visitor-read.test.ts +196 -0
  8. package/__tests__/plain-list-sort.test.ts +1 -1
  9. package/__tests__/plain-list-starting-sort.test.ts +323 -0
  10. package/__tests__/refetch-loading-state.test.ts +158 -2
  11. package/__tests__/starting-sort-first-page.test.ts +285 -0
  12. package/dist/cjs/cache/ast.d.ts +2 -0
  13. package/dist/cjs/cache/ast.d.ts.map +1 -1
  14. package/dist/cjs/cache/ast.js +5 -2
  15. package/dist/cjs/cache/ast.js.map +1 -1
  16. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  17. package/dist/cjs/data-source-fetchers.js +10 -2
  18. package/dist/cjs/data-source-fetchers.js.map +1 -1
  19. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  20. package/dist/cjs/fetchers/teleport.js +1 -1
  21. package/dist/cjs/fetchers/teleport.js.map +1 -1
  22. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  23. package/dist/cjs/fetchers/utils/browser-row-policy.js +5 -1
  24. package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
  25. package/dist/cjs/index.d.ts.map +1 -1
  26. package/dist/cjs/index.js +14 -2
  27. package/dist/cjs/index.js.map +1 -1
  28. package/dist/cjs/infinite-scroll.d.ts +64 -2
  29. package/dist/cjs/infinite-scroll.d.ts.map +1 -1
  30. package/dist/cjs/infinite-scroll.js +102 -3
  31. package/dist/cjs/infinite-scroll.js.map +1 -1
  32. package/dist/cjs/loading-state.d.ts +23 -2
  33. package/dist/cjs/loading-state.d.ts.map +1 -1
  34. package/dist/cjs/loading-state.js +50 -15
  35. package/dist/cjs/loading-state.js.map +1 -1
  36. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  37. package/dist/cjs/pagination-plugin.js +200 -64
  38. package/dist/cjs/pagination-plugin.js.map +1 -1
  39. package/dist/cjs/sort-utils.d.ts +53 -1
  40. package/dist/cjs/sort-utils.d.ts.map +1 -1
  41. package/dist/cjs/sort-utils.js +105 -13
  42. package/dist/cjs/sort-utils.js.map +1 -1
  43. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  44. package/dist/cjs/utils.d.ts +3 -2
  45. package/dist/cjs/utils.d.ts.map +1 -1
  46. package/dist/cjs/utils.js +48 -18
  47. package/dist/cjs/utils.js.map +1 -1
  48. package/dist/esm/cache/ast.d.ts +2 -0
  49. package/dist/esm/cache/ast.d.ts.map +1 -1
  50. package/dist/esm/cache/ast.js +3 -1
  51. package/dist/esm/cache/ast.js.map +1 -1
  52. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  53. package/dist/esm/data-source-fetchers.js +10 -2
  54. package/dist/esm/data-source-fetchers.js.map +1 -1
  55. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  56. package/dist/esm/fetchers/teleport.js +1 -1
  57. package/dist/esm/fetchers/teleport.js.map +1 -1
  58. package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  59. package/dist/esm/fetchers/utils/browser-row-policy.js +6 -2
  60. package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
  61. package/dist/esm/index.d.ts.map +1 -1
  62. package/dist/esm/index.js +14 -2
  63. package/dist/esm/index.js.map +1 -1
  64. package/dist/esm/infinite-scroll.d.ts +64 -2
  65. package/dist/esm/infinite-scroll.d.ts.map +1 -1
  66. package/dist/esm/infinite-scroll.js +97 -2
  67. package/dist/esm/infinite-scroll.js.map +1 -1
  68. package/dist/esm/loading-state.d.ts +23 -2
  69. package/dist/esm/loading-state.d.ts.map +1 -1
  70. package/dist/esm/loading-state.js +50 -15
  71. package/dist/esm/loading-state.js.map +1 -1
  72. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  73. package/dist/esm/pagination-plugin.js +204 -68
  74. package/dist/esm/pagination-plugin.js.map +1 -1
  75. package/dist/esm/sort-utils.d.ts +53 -1
  76. package/dist/esm/sort-utils.d.ts.map +1 -1
  77. package/dist/esm/sort-utils.js +100 -12
  78. package/dist/esm/sort-utils.js.map +1 -1
  79. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  80. package/dist/esm/utils.d.ts +3 -2
  81. package/dist/esm/utils.d.ts.map +1 -1
  82. package/dist/esm/utils.js +49 -19
  83. package/dist/esm/utils.js.map +1 -1
  84. package/package.json +4 -4
  85. package/src/cache/ast.ts +4 -1
  86. package/src/data-source-fetchers.ts +33 -14
  87. package/src/fetchers/teleport.ts +8 -2
  88. package/src/fetchers/utils/browser-row-policy.ts +79 -5
  89. package/src/index.ts +8 -2
  90. package/src/infinite-scroll.ts +182 -4
  91. package/src/loading-state.ts +85 -13
  92. package/src/pagination-plugin.ts +369 -83
  93. package/src/sort-utils.ts +164 -21
  94. package/src/utils.ts +55 -18
@@ -47,24 +47,24 @@ import { ChunkType, FileType, } from '@teleporthq/teleport-types';
47
47
  import * as types from '@babel/types';
48
48
  import { parseExpression } from '@babel/parser';
49
49
  import { ASTStatementOrder, GenericUtils, StringUtils } from '@teleporthq/teleport-shared';
50
- import { ASTUtils, URLQueryWriter, URLSearchParamSync } from '@teleporthq/teleport-plugin-common';
50
+ import { ASTUtils, RouteUtils, URLQueryWriter, URLSearchParamSync, } from '@teleporthq/teleport-plugin-common';
51
51
  import { collectFilterLeafConditions, generateSafeFileName, pruneFilterTree } from './utils';
52
52
  import { generateDataSourceFetcherWithCore } from './data-source-fetchers';
53
53
  import { buildProductTransformOptions } from './transformations';
54
- import { appendSortsParam, extractDynamicSort, readFixedSort } from './sort-utils';
54
+ import { appendSortsParam, buildSortAtStartGuard, buildStartingSortsValue, readRepeaterSort, } from './sort-utils';
55
55
  import { appendFiltersParam, buildStaticFiltersStringifyCall, pushStateIdsAsDeps, pushPropIdsAsDeps, } from './filter-utils';
56
56
  import { appendItemsPathParam, extractItemsPath } from './items-path';
57
57
  import { resolveRepeaterPerPage } from './repeater-page-size';
58
58
  import { applyLoadingStateToDataProvider, buildLoadingStateDeclarations, getLoadingStateVars, } from './loading-state';
59
59
  import { mergeServerCacheOptions, resolveCacheFromRepeaterContent } from './cache/config';
60
- import { buildCacheHydrationEffect, buildCacheStoreThen, buildCachedFetchBody, buildCachedParamsDeclarations, registerCacheClientImports, } from './cache/ast';
60
+ import { buildCacheHydrationEffect, buildCacheStoreThen, buildCachedFetchBody, buildCachedParamsDeclarations, paramsVarFor, registerCacheClientImports, } from './cache/ast';
61
61
  import { convertControlToButton, dropInactivePaginationControls, findAllPaginationControls, findPaginationControl, PAGINATION_FOR_ATTR, readJSXAttributeString, setJSXExpressionAttribute, } from './pagination-controls';
62
62
  import { findRangeControls, wireRangeControl } from './range-control';
63
63
  import { findAllSearchInputsInJSX, pairSearchInputs, wireSearchInput } from './search-input';
64
64
  import { buildCountFetchEffect, buildCountSeqDeclaration } from './count-effect';
65
65
  import { findAllSearchClearButtonsInJSX, findSiblingSearchInput, wireSearchClearButton, } from './search-clear';
66
66
  import { buildPageTokensDeclaration, wireNumberedPagination, } from './numbered-pagination';
67
- import { buildAccumulatingRequestMark, buildAccumulatingResponseHandler, buildInfiniteScrollDeclarations, buildSentinelElement, buildSentinelObserverEffect, getInfiniteScrollVars, } from './infinite-scroll';
67
+ import { buildAccumulatingRequestMark, buildAccumulatingResponseHandler, buildAppendingRequestCheck, buildFirstPageDeclaration, buildFirstPageSeedEffect, buildInfiniteScrollDeclarations, buildSentinelElement, buildSentinelObserverEffect, buildServerRowsExpression, getInfiniteScrollVars, } from './infinite-scroll';
68
68
  import { buildFilterSortPageResetEffect, buildPageResetRefDeclaration, hasNoPageResetDeps, } from './page-reset';
69
69
  import { buildPageUrlRefDeclaration, buildPageUrlSyncEffect } from './page-url-sync';
70
70
  import { isLocalizedProject } from './content-localization';
@@ -275,7 +275,7 @@ function buildFilterDestinationExpression(destination) {
275
275
  return ASTUtils.convertFilterDestinationToExpression(destination);
276
276
  }
277
277
  // Scan UIDL to find all data source usages and build a registry
278
- function buildStateRegistry(uidlNode) {
278
+ function buildStateRegistry(uidlNode, stateDefinitions) {
279
279
  var usages = [];
280
280
  var byDataSourceId = new Map();
281
281
  var byArrayMapperRenderProp = new Map();
@@ -332,16 +332,19 @@ function buildStateRegistry(uidlNode) {
332
332
  // is skipped for a dynamic sort) — the pre-rendered HTML carried no
333
333
  // posts, and the browser fetched page 1 a second time. Only a sort
334
334
  // bound to state (the admin panel's sortable columns) is dynamic.
335
- var dynamicSort = void 0;
336
- if ((!sorts || sorts.length === 0) && content.sort) {
337
- var fixedSort = readFixedSort(content.sort, content.sortDirection);
338
- if (fixedSort) {
339
- sorts = [fixedSort];
340
- }
341
- else {
342
- dynamicSort = extractDynamicSort(content.sort, content.sortDirection);
343
- }
335
+ //
336
+ // A sort the visitor picks still starts somewhere — the store's sort
337
+ // dropdown at "Name A-Z". Known at build time, it orders the
338
+ // server-side first page, which the list then shows while the visitor
339
+ // leaves the sort alone. Read as a plain list's page fetch reads it.
340
+ var repeaterSort = !sorts || sorts.length === 0
341
+ ? readRepeaterSort(content.sort, content.sortDirection, stateDefinitions)
342
+ : {};
343
+ if (repeaterSort.fixed) {
344
+ sorts = [repeaterSort.fixed];
344
345
  }
346
+ var dynamicSort = repeaterSort.dynamic;
347
+ var startingSort = repeaterSort.starting;
345
348
  // Extract filters from parent's resource params, keeping the group tree
346
349
  // the inspector authored (see `readFilterTree`).
347
350
  var filters = [];
@@ -424,6 +427,7 @@ function buildStateRegistry(uidlNode) {
424
427
  queryColumns: queryColumns,
425
428
  sorts: sorts,
426
429
  dynamicSort: dynamicSort,
430
+ startingSort: startingSort,
427
431
  filters: filters,
428
432
  filterStateIds: filterStateIds,
429
433
  filterPropIds: filterPropIds,
@@ -780,7 +784,7 @@ function pushFilterSortPageResetEffect(stateDeclarations, effectStatements, usag
780
784
  // ==================== MAIN PLUGIN ====================
781
785
  export var createNextArrayMapperPaginationPlugin = function () {
782
786
  var paginationPlugin = function (structure) { return __awaiter(void 0, void 0, void 0, function () {
783
- var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, rangeControls, hasRangeControl, registry, getStaticPropsChunk, isPage, localized, stateDeclarations, effectStatements, needsUseRouter, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, usagesWithLoadingState, usageByProviderNode, cacheDeclarations, sharedOptions, cachedScopes, dataProvidersWithoutRepeaters, searchInputs, usageBySearchInput, _i, searchInputs_1, input, usage;
787
+ var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, rangeControls, hasRangeControl, registry, getStaticPropsChunk, isPage, localized, stateDeclarations, effectStatements, needsUseRouter, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, usagesWithLoadingState, usageByProviderNode, cacheDeclarations, firstPageDeclarations, sharedOptions, cachedScopes, dataProvidersWithoutRepeaters, searchInputs, usageBySearchInput, _i, searchInputs_1, input, usage;
784
788
  var _a, _b;
785
789
  return __generator(this, function (_c) {
786
790
  uidl = structure.uidl, chunks = structure.chunks, dependencies = structure.dependencies, options = structure.options;
@@ -806,7 +810,7 @@ export var createNextArrayMapperPaginationPlugin = function () {
806
810
  blockStatement = arrowFunction.body;
807
811
  rangeControls = findRangeControls(blockStatement);
808
812
  hasRangeControl = rangeControls.length > 0;
809
- registry = buildStateRegistry(uidl.node);
813
+ registry = buildStateRegistry(uidl.node, uidl.stateDefinitions);
810
814
  if (registry.usages.length === 0) {
811
815
  return [2 /*return*/, structure];
812
816
  }
@@ -1141,6 +1145,7 @@ export var createNextArrayMapperPaginationPlugin = function () {
1141
1145
  usagesWithLoadingState = new Set();
1142
1146
  usageByProviderNode = new Map();
1143
1147
  cacheDeclarations = [];
1148
+ firstPageDeclarations = isPage ? [] : undefined;
1144
1149
  dataProvidersWithRepeaters.forEach(function (dp) {
1145
1150
  var _a, _b;
1146
1151
  var nameAttr = dp.openingElement.attributes.find(function (attr) { return attr.type === 'JSXAttribute' && attr.name.name === 'name'; });
@@ -1161,10 +1166,10 @@ export var createNextArrayMapperPaginationPlugin = function () {
1161
1166
  var fileName = generateSafeFileName(usage.resourceDefinition.dataSourceType, usage.resourceDefinition.tableName, usage.resourceDefinition.dataSourceId);
1162
1167
  // Update DataProvider based on category
1163
1168
  if (usage.category === 'paginated+search') {
1164
- updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations, localized);
1169
+ updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations, localized, firstPageDeclarations);
1165
1170
  }
1166
1171
  else if (usage.category === 'paginated-only') {
1167
- updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations, localized);
1172
+ updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations, localized, firstPageDeclarations);
1168
1173
  }
1169
1174
  else if (usage.category === 'search-only') {
1170
1175
  updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations, localized);
@@ -1176,9 +1181,14 @@ export var createNextArrayMapperPaginationPlugin = function () {
1176
1181
  // change refetches, instead of leaving the previous rows on screen with
1177
1182
  // no feedback. Runs last so it sees the `fetchData` and
1178
1183
  // `persistDataDuringLoading` attributes the updaters above just wrote.
1184
+ // An appending list keeps its rows on screen while its next page loads;
1185
+ // only a list loading afresh falls through to the loading slot.
1179
1186
  var loadingVars = getLoadingStateVars(usage.index);
1180
- if (applyLoadingStateToDataProvider(dp, loadingVars)) {
1181
- loadingStateDeclarations.push.apply(loadingStateDeclarations, buildLoadingStateDeclarations(loadingVars));
1187
+ var appendingRequest = usage.infiniteScroll && usage.paginated
1188
+ ? buildAppendingRequestCheck(getInfiniteScrollVars(usage.index))
1189
+ : undefined;
1190
+ if (applyLoadingStateToDataProvider(dp, loadingVars, appendingRequest)) {
1191
+ loadingStateDeclarations.push.apply(loadingStateDeclarations, buildLoadingStateDeclarations(loadingVars, !!appendingRequest));
1182
1192
  // Recorded because the loading state is only declared for a provider
1183
1193
  // that HAS a loading slot; a Load More button wired later may only
1184
1194
  // reference `ds_N_isFetching` if it actually exists.
@@ -1223,6 +1233,11 @@ export var createNextArrayMapperPaginationPlugin = function () {
1223
1233
  ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, cacheDeclarations);
1224
1234
  registerCacheClientImports(dependencies, (_a = uidl.outputOptions) === null || _a === void 0 ? void 0 : _a.folderPath);
1225
1235
  }
1236
+ // After the cache declarations: a cached list's first page reads the
1237
+ // `ds_N_params` they hoisted.
1238
+ if (firstPageDeclarations === null || firstPageDeclarations === void 0 ? void 0 : firstPageDeclarations.length) {
1239
+ ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, firstPageDeclarations);
1240
+ }
1226
1241
  dataProvidersWithoutRepeaters = dataProviders.filter(function (dp) {
1227
1242
  var hasRepeater = findArrayMapperRenderPropInDataProvider(dp) !== undefined;
1228
1243
  return !hasRepeater;
@@ -1283,9 +1298,11 @@ export var createNextArrayMapperPaginationPlugin = function () {
1283
1298
  wireRangeControlFor(control, owner, getStateVarsForUsage(owner));
1284
1299
  }
1285
1300
  });
1286
- // STEP 6: Update getStaticProps if this is a page
1301
+ // STEP 6: Update getStaticProps if this is a page. Its rows are what the
1302
+ // page renders, so they are read as a visitor is served them — unless only
1303
+ // the store's trusted readers can open the page (`pageDataReadArguments`).
1287
1304
  if (isPage) {
1288
- updateGetStaticProps(chunks, registry, dependencies, (_b = uidl.outputOptions) === null || _b === void 0 ? void 0 : _b.folderPath, localized);
1305
+ updateGetStaticProps(chunks, registry, dependencies, (_b = uidl.outputOptions) === null || _b === void 0 ? void 0 : _b.folderPath, localized, function () { return RouteUtils.pageDataReadArguments(uidl, options.auth); });
1289
1306
  }
1290
1307
  return [2 /*return*/, structure];
1291
1308
  });
@@ -1544,34 +1561,97 @@ function findPaginationNodesWithOwners(blockStatement, usageByProviderNode) {
1544
1561
  *
1545
1562
  * An uncached mapper keeps the inline `params={useMemo(...)}` it has always had,
1546
1563
  * byte for byte. A cached one hoists that memo into a component const so the
1547
- * peek can depend on the same object identity instead of rebuilding it.
1564
+ * peek can depend on the same object identity instead of rebuilding it; so does
1565
+ * an appending list that starts from its page's rows (`hoistInto`), whose first
1566
+ * page is filed in the accumulator under those params.
1548
1567
  *
1549
- * Returns the identifier holding the peeked rows, or `undefined` when this
1550
- * mapper does not cache.
1568
+ * Returns the identifier holding the params when they were hoisted, and the one
1569
+ * holding the peeked rows when this mapper caches.
1551
1570
  */
1552
1571
  function pushParamsAttribute(
1553
1572
  // tslint:disable-next-line:no-any
1554
- dp, usage, paramsProps, memoDeps, cacheDeclarations) {
1573
+ dp, usage, paramsProps, memoDeps, cacheDeclarations, hoistInto) {
1555
1574
  var _a;
1556
1575
  var paramsMemo = types.callExpression(types.identifier('useMemo'), [
1557
1576
  types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
1558
1577
  types.arrayExpression(memoDeps),
1559
1578
  ]);
1560
- // A state-bound sort deliberately suppresses `initialData` (see the comment at
1561
- // the call site): the prefetch ran without sort params, so reusing anything on
1562
- // mount would mask the current sort. The cache peek is `initialData` by
1563
- // another name, so it has to be suppressed for exactly the same reason.
1579
+ // A state-bound sort gets no cache peek. The peek is `initialData` by another
1580
+ // name, and such a list takes the server's rows only while its sort is still
1581
+ // where the visitor starts (`buildStartingSortGuard`); the peek was never
1582
+ // extended to it.
1564
1583
  var cache = ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) && !usage.dynamicSort ? usage.cache : undefined;
1565
1584
  if (!cache) {
1566
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo)));
1567
- return undefined;
1585
+ if (!hoistInto) {
1586
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo)));
1587
+ return {};
1588
+ }
1589
+ var paramsIdentifier = paramsVarFor(usage.index);
1590
+ hoistInto.push(types.variableDeclaration('const', [
1591
+ types.variableDeclarator(types.identifier(paramsIdentifier), paramsMemo),
1592
+ ]));
1593
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.identifier(paramsIdentifier))));
1594
+ return { paramsIdentifier: paramsIdentifier };
1568
1595
  }
1569
1596
  var hoisted = buildCachedParamsDeclarations({ index: usage.index, paramsMemo: paramsMemo, cache: cache });
1570
1597
  cacheDeclarations.push.apply(cacheDeclarations, hoisted.declarations);
1571
1598
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.identifier(hoisted.paramsIdentifier))));
1572
- return hoisted.cachedIdentifier;
1599
+ return { paramsIdentifier: hoisted.paramsIdentifier, cachedIdentifier: hoisted.cachedIdentifier };
1600
+ }
1601
+ // Whether the rows `getStaticProps` fetched can be this list's first page at
1602
+ // all. A sort bound to state can only be served them when the build knows the
1603
+ // sort the visitor starts with (`startingSort`) and fetched by it; otherwise
1604
+ // the fetch has no sort, and its rows are not the list's first page.
1605
+ function prefetchFollowsSort(usage) {
1606
+ return !usage.dynamicSort || !!usage.startingSort;
1607
+ }
1608
+ // `sortBy === "name-asc"`: the visitor has not moved the sort from where it
1609
+ // starts, so the build-time rows are still its first page. `null` for a list
1610
+ // whose sort the visitor cannot pick, or one that reads no state.
1611
+ function buildStartingSortGuard(usage) {
1612
+ return usage.dynamicSort && usage.startingSort ? buildSortAtStartGuard(usage.startingSort) : null;
1613
+ }
1614
+ // `<condition> && <starting-sort guard>`, or the condition alone.
1615
+ function withStartingSortGuard(condition, usage) {
1616
+ var sortGuard = buildStartingSortGuard(usage);
1617
+ return sortGuard ? types.logicalExpression('&&', condition, sortGuard) : condition;
1618
+ }
1619
+ // An appending list on a page starts from the rows its `getStaticProps`
1620
+ // fetched, when they can be its first page (`prefetchFollowsSort`).
1621
+ function startsFromServerRows(usage) {
1622
+ return usage.infiniteScroll && usage.paginated && prefetchFollowsSort(usage);
1573
1623
  }
1574
- function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations, localized) {
1624
+ /**
1625
+ * `initialData={ds_N_firstPage}` on an appending list, beside the frozen first
1626
+ * page and the effect that files it as page 1 of the accumulator (see
1627
+ * `infinite-scroll.ts`).
1628
+ *
1629
+ * The rows stand in for page 1 only while they answer the list's query: no
1630
+ * search (`queryGuard`), the sort where the visitor starts, no filter set (the
1631
+ * guards the paged lists use), and at least one row — an empty build-time
1632
+ * answer, which a failed fetch also gives, leaves the browser to fetch page 1
1633
+ * as before.
1634
+ */
1635
+ function pushInfiniteScrollFirstPage(
1636
+ // tslint:disable-next-line:no-any
1637
+ dp, usage, vars, paramsIdentifier, declarations, queryGuard, instanceDeps) {
1638
+ var infiniteVars = getInfiniteScrollVars(usage.index);
1639
+ var serverPayload = function () {
1640
+ return types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true);
1641
+ };
1642
+ var hasServerRows = types.binaryExpression('>', types.optionalMemberExpression(buildServerRowsExpression(serverPayload(), usage.itemsPath), types.identifier('length'), false, true), types.numericLiteral(0));
1643
+ var startsFromServer = [
1644
+ queryGuard,
1645
+ buildStartingSortGuard(usage),
1646
+ buildNoUrlFilterGuard(usage),
1647
+ hasServerRows,
1648
+ ]
1649
+ .filter(function (guard) { return guard !== null; })
1650
+ .reduce(function (all, guard) { return types.logicalExpression('&&', all, guard); });
1651
+ declarations.push(buildFirstPageDeclaration(infiniteVars, startsFromServer, serverPayload(), instanceDeps), buildFirstPageSeedEffect(infiniteVars, paramsIdentifier, usage.perPage, usage.itemsPath));
1652
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.identifier(infiniteVars.firstPageVar))));
1653
+ }
1654
+ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations, localized, firstPageDeclarations) {
1575
1655
  var attrs = dp.openingElement.attributes;
1576
1656
  // Remove existing params, key, initialData, fetchData, persistDataDuringLoading
1577
1657
  dp.openingElement.attributes = attrs.filter(function (attr) {
@@ -1604,20 +1684,29 @@ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDe
1604
1684
  if (localized) {
1605
1685
  appendClientLocaleParam(paramsProps, memoDeps);
1606
1686
  }
1607
- var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1608
- // Add initialData. Skip the server-prefetched data entirely when a dynamic
1609
- // state-bound sort is active — the prefetch ran without sort parameters, so
1610
- // reusing it would mask the current sort state AND cause DataProvider to skip
1611
- // the first client fetch (its internal guard only skips when initialData is
1612
- // defined on mount). Without that skip, toggling sort correctly triggers a
1613
- // refetch via the useMemo params dependency chain.
1687
+ var seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined;
1688
+ var _a = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations, seedInto), paramsIdentifier = _a.paramsIdentifier, cachedInitialData = _a.cachedIdentifier;
1689
+ // Add initialData: the server-prefetched page 1 while it is the page on show
1690
+ // — page 1, no search, no filter set, and a sort the visitor picks still where
1691
+ // they start (the prefetch ran by that sort). A list whose starting sort the
1692
+ // build cannot know never takes it: that prefetch ran without a sort, so
1693
+ // reusing it would mask the current sort AND make DataProvider skip its first
1694
+ // client fetch (its internal guard only skips when initialData is defined on
1695
+ // mount). Any other sort is fetched fresh, through the useMemo params.
1614
1696
  //
1615
- // An appending list skips it for a second reason: `initialData` makes
1616
- // DataProvider skip its first fetch, so page 1 would render from the prefetch
1617
- // and never enter the accumulator — appending page 2 would then REPLACE the
1618
- // visible rows with page 2 alone.
1619
- if (!usage.dynamicSort && !usage.infiniteScroll) {
1620
- var initialDataCondition = wrapInitialDataWithUrlFilterGuard(types.logicalExpression('&&', types.binaryExpression('===', types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('page')), types.numericLiteral(1)), types.unaryExpression('!', types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('debouncedQuery')), true)), usage);
1697
+ // An appending list takes its own: `initialData` makes DataProvider skip its
1698
+ // first fetch, so the prefetched page 1 has to be filed in the accumulator
1699
+ // too, or appending page 2 would REPLACE the visible rows with page 2 alone.
1700
+ // The provider remounts per query (its `key`), so the first page is frozen
1701
+ // per query.
1702
+ if (seedInto && paramsIdentifier) {
1703
+ var appliedQuery = function () {
1704
+ return types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('debouncedQuery'));
1705
+ };
1706
+ pushInfiniteScrollFirstPage(dp, usage, vars, paramsIdentifier, seedInto, types.unaryExpression('!', appliedQuery(), true), [appliedQuery()]);
1707
+ }
1708
+ else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
1709
+ var initialDataCondition = wrapInitialDataWithUrlFilterGuard(withStartingSortGuard(types.logicalExpression('&&', types.binaryExpression('===', types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('page')), types.numericLiteral(1)), types.unaryExpression('!', types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('debouncedQuery')), true)), usage), usage);
1621
1710
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(initialDataCondition, types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true), cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')))));
1622
1711
  }
1623
1712
  // Add key. Sort is intentionally NOT part of the key — a key change would
@@ -1655,7 +1744,7 @@ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDe
1655
1744
  // Add persistDataDuringLoading
1656
1745
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1657
1746
  }
1658
- function updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations, localized) {
1747
+ function updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations, localized, firstPageDeclarations) {
1659
1748
  var attrs = dp.openingElement.attributes;
1660
1749
  dp.openingElement.attributes = attrs.filter(function (attr) {
1661
1750
  var _a;
@@ -1684,12 +1773,17 @@ function updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDec
1684
1773
  appendClientLocaleParam(paramsProps, memoDeps);
1685
1774
  }
1686
1775
  // Add params
1687
- var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1688
- // Add initialData. See paginated+search updater for why we skip prefetch
1689
- // reuse when a dynamic state-bound sort is active, and why an appending list
1690
- // skips it too.
1691
- if (!usage.dynamicSort && !usage.infiniteScroll) {
1692
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(wrapInitialDataWithUrlFilterGuard(types.binaryExpression('===', types.identifier(vars.pageStateVar), types.numericLiteral(1)), usage), types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true), cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')))));
1776
+ var seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined;
1777
+ var _a = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations, seedInto), paramsIdentifier = _a.paramsIdentifier, cachedInitialData = _a.cachedIdentifier;
1778
+ // Add initialData. See paginated+search updater for when a sort the visitor
1779
+ // picks may reuse the prefetch, and how an appending list takes its own. This
1780
+ // provider never remounts (no `key`), so its first page is frozen for the
1781
+ // page's lifetime.
1782
+ if (seedInto && paramsIdentifier) {
1783
+ pushInfiniteScrollFirstPage(dp, usage, vars, paramsIdentifier, seedInto, null, []);
1784
+ }
1785
+ else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
1786
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(wrapInitialDataWithUrlFilterGuard(withStartingSortGuard(types.binaryExpression('===', types.identifier(vars.pageStateVar), types.numericLiteral(1)), usage), usage), types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true), cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')))));
1693
1787
  }
1694
1788
  // Add key — sort is intentionally NOT included; see paginated+search updater,
1695
1789
  // which also explains why an appending list keys on nothing at all (there is
@@ -1739,11 +1833,11 @@ function updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclara
1739
1833
  if (localized) {
1740
1834
  appendClientLocaleParam(paramsProps, memoDeps);
1741
1835
  }
1742
- var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1743
- // Add initialData. See paginated+search updater for why we skip prefetch
1744
- // reuse when a dynamic state-bound sort is active.
1745
- if (!usage.dynamicSort) {
1746
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(wrapInitialDataWithUrlFilterGuard(types.unaryExpression('!', types.identifier(vars.debouncedSearchQueryVar), true), usage), types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true), cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')))));
1836
+ var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations).cachedIdentifier;
1837
+ // Add initialData. See paginated+search updater for when a sort the visitor
1838
+ // picks may reuse the prefetch.
1839
+ if (prefetchFollowsSort(usage)) {
1840
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(wrapInitialDataWithUrlFilterGuard(withStartingSortGuard(types.unaryExpression('!', types.identifier(vars.debouncedSearchQueryVar), true), usage), usage), types.optionalMemberExpression(types.identifier('props'), types.identifier(vars.propsPrefix), false, true), cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')))));
1747
1841
  }
1748
1842
  // Add key — sort is intentionally NOT included; see paginated+search updater.
1749
1843
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('key'), types.jsxExpressionContainer(types.templateLiteral([
@@ -1754,14 +1848,42 @@ function updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclara
1754
1848
  dp.openingElement.attributes.push(createFetchDataAttribute(fileName, ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined));
1755
1849
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1756
1850
  }
1851
+ /**
1852
+ * A plain list on a page is seeded with the rows its `getStaticProps` fetched
1853
+ * (`extractDataSourceIntoGetStaticProps`), and a seeded provider skips its first
1854
+ * fetch. With a sort the visitor picks, those rows are the list's only while the
1855
+ * sort is where a visitor starts — the build fetched them by it:
1856
+ *
1857
+ * initialData={sortBy === "name-asc" ? props.items_products : undefined}
1858
+ *
1859
+ * — and any other sort is fetched fresh, through the params it changes. When the
1860
+ * build cannot know where the sort starts (`prefetchFollowsSort`), it fetched
1861
+ * the rows unsorted, and the list is never seeded with them. A plain list has no
1862
+ * search, and is only seeded without a filter a visitor sets.
1863
+ */
1864
+ function seedPlainListWhileSortStarts(dp, usage) {
1865
+ if (!usage.dynamicSort) {
1866
+ return;
1867
+ }
1868
+ var attrs = dp.openingElement.attributes;
1869
+ var index = attrs.findIndex(function (attr) { var _a; return attr.type === 'JSXAttribute' && ((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name) === 'initialData'; });
1870
+ if (index === -1) {
1871
+ return;
1872
+ }
1873
+ if (!prefetchFollowsSort(usage)) {
1874
+ attrs.splice(index, 1);
1875
+ return;
1876
+ }
1877
+ var sortGuard = buildStartingSortGuard(usage);
1878
+ var seeded = attrs[index].value;
1879
+ if (!sortGuard || (seeded === null || seeded === void 0 ? void 0 : seeded.type) !== 'JSXExpressionContainer') {
1880
+ return;
1881
+ }
1882
+ attrs[index] = types.jsxAttribute(types.jsxIdentifier('initialData'), types.jsxExpressionContainer(types.conditionalExpression(sortGuard, seeded.expression, types.identifier('undefined'))));
1883
+ }
1757
1884
  function updateDataProviderForPlain(dp, fileName, usage, localized) {
1758
1885
  var _a, _b, _c, _d, _e, _f;
1759
- // A sort bound to state is the visitor's, which the server cannot know: rows
1760
- // pre-rendered in the table's order would stand in for it, since a seeded
1761
- // provider skips its first fetch — the paginated lists drop them the same way.
1762
- if (usage.dynamicSort) {
1763
- dp.openingElement.attributes = dp.openingElement.attributes.filter(function (attr) { var _a; return !(attr.type === 'JSXAttribute' && ((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name) === 'initialData'); });
1764
- }
1886
+ seedPlainListWhileSortStarts(dp, usage);
1765
1887
  var attrs = dp.openingElement.attributes;
1766
1888
  var clientCache = ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined;
1767
1889
  // Check if fetchData already exists
@@ -2215,7 +2337,15 @@ function ensureAPIRouteExists(extractedResources, usage, dataSources, transformO
2215
2337
  };
2216
2338
  }
2217
2339
  }
2218
- function updateGetStaticProps(chunks, registry, dependencies, folderPath, localized) {
2340
+ // `fetchCount(<params>, <read arguments>)`: the params may be left out only
2341
+ // when nothing follows them.
2342
+ var countReadArguments = function (countParams, readArguments) {
2343
+ return countParams.length > 0 || readArguments.length > 0
2344
+ ? __spreadArray([types.objectExpression(countParams)], readArguments, true) : [];
2345
+ };
2346
+ function updateGetStaticProps(chunks, registry, dependencies, folderPath, localized,
2347
+ // The arguments every read hands the module after its params (fresh nodes per call).
2348
+ readArguments) {
2219
2349
  var _a;
2220
2350
  var getStaticPropsChunk = chunks.find(function (c) { return c.name === 'getStaticProps'; });
2221
2351
  if (!getStaticPropsChunk || getStaticPropsChunk.type !== ChunkType.AST) {
@@ -2296,6 +2426,12 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2296
2426
  })),
2297
2427
  ])));
2298
2428
  }
2429
+ else if (usage.dynamicSort && usage.startingSort) {
2430
+ // A sort the visitor picks: page 1 by the sort they start with, sent
2431
+ // as the very string the browser sends for it, so page 2 continues
2432
+ // this page exactly (see `buildStartingSortsValue`).
2433
+ fetchParams.push(types.objectProperty(types.identifier('sorts'), buildStartingSortsValue(usage.dynamicSort, usage.startingSort)));
2434
+ }
2299
2435
  // Add filters if present
2300
2436
  // Filter out dynamic destinations for getStaticProps (they can't be resolved at build time)
2301
2437
  var staticFilters = staticFiltersOf(usage);
@@ -2311,7 +2447,7 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2311
2447
  var existingFetchIndex = arrayPattern.elements.findIndex(function (el) { return (el === null || el === void 0 ? void 0 : el.type) === 'Identifier' && el.name === vars.propsPrefix; });
2312
2448
  if (existingFetchIndex === -1) {
2313
2449
  arrayPattern.elements.push(types.identifier(vars.propsPrefix));
2314
- fetchesArray.elements.push(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchData')), [types.objectExpression(fetchParams)]), types.identifier('catch')), [
2450
+ fetchesArray.elements.push(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchData')), __spreadArray([types.objectExpression(fetchParams)], readArguments(), true)), types.identifier('catch')), [
2315
2451
  types.arrowFunctionExpression([types.identifier('error')], types.blockStatement([
2316
2452
  types.expressionStatement(types.callExpression(types.memberExpression(types.identifier('console'), types.identifier('error')), [
2317
2453
  types.stringLiteral("Error fetching ".concat(vars.propsPrefix, ":")),
@@ -2414,7 +2550,7 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2414
2550
  countParams.push(types.objectProperty(types.identifier('filters'), buildStaticFiltersStringifyCall(staticFiltersForCount)));
2415
2551
  }
2416
2552
  appendItemsPathParam(countParams, firstUsage.itemsPath, 'collectionPath');
2417
- fetchesArray.elements.push(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchCount')), countParams.length > 0 ? [types.objectExpression(countParams)] : []), types.identifier('catch')), [
2553
+ fetchesArray.elements.push(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchCount')), countReadArguments(countParams, readArguments())), types.identifier('catch')), [
2418
2554
  types.arrowFunctionExpression([types.identifier('error')], types.blockStatement([
2419
2555
  types.expressionStatement(types.callExpression(types.memberExpression(types.identifier('console'), types.identifier('error')), [
2420
2556
  types.stringLiteral("Error fetching ".concat(countVarName, ":")),