@teleporthq/teleport-plugin-next-data-source 0.44.0 → 0.44.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/__tests__/_helpers/first-page-runtime.ts +184 -0
- package/__tests__/_helpers/pagination-widget-fixture.ts +67 -1
- package/__tests__/browser-row-policy.test.ts +202 -2
- package/__tests__/data-source-request-locale.test.ts +4 -3
- package/__tests__/fetchers.test.ts +6 -2
- package/__tests__/infinite-scroll.test.ts +210 -5
- package/__tests__/page-data-visitor-read.test.ts +196 -0
- package/__tests__/plain-list-sort.test.ts +1 -1
- package/__tests__/plain-list-starting-sort.test.ts +323 -0
- package/__tests__/refetch-loading-state.test.ts +158 -2
- package/__tests__/starting-sort-first-page.test.ts +285 -0
- package/dist/cjs/cache/ast.d.ts +2 -0
- package/dist/cjs/cache/ast.d.ts.map +1 -1
- package/dist/cjs/cache/ast.js +5 -2
- package/dist/cjs/cache/ast.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -2
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +1 -1
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +5 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +14 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +64 -2
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +102 -3
- package/dist/cjs/infinite-scroll.js.map +1 -1
- package/dist/cjs/loading-state.d.ts +23 -2
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +50 -15
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +200 -64
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/sort-utils.d.ts +53 -1
- package/dist/cjs/sort-utils.d.ts.map +1 -1
- package/dist/cjs/sort-utils.js +105 -13
- package/dist/cjs/sort-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -2
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +48 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/cache/ast.d.ts +2 -0
- package/dist/esm/cache/ast.d.ts.map +1 -1
- package/dist/esm/cache/ast.js +3 -1
- package/dist/esm/cache/ast.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +10 -2
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +1 -1
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +6 -2
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +14 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +64 -2
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +97 -2
- package/dist/esm/infinite-scroll.js.map +1 -1
- package/dist/esm/loading-state.d.ts +23 -2
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +50 -15
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +204 -68
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/sort-utils.d.ts +53 -1
- package/dist/esm/sort-utils.d.ts.map +1 -1
- package/dist/esm/sort-utils.js +100 -12
- package/dist/esm/sort-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -2
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +49 -19
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/cache/ast.ts +4 -1
- package/src/data-source-fetchers.ts +33 -14
- package/src/fetchers/teleport.ts +8 -2
- package/src/fetchers/utils/browser-row-policy.ts +79 -5
- package/src/index.ts +8 -2
- package/src/infinite-scroll.ts +182 -4
- package/src/loading-state.ts +85 -13
- package/src/pagination-plugin.ts +369 -83
- package/src/sort-utils.ts +164 -21
- package/src/utils.ts +55 -18
package/src/pagination-plugin.ts
CHANGED
|
@@ -3,15 +3,28 @@ import {
|
|
|
3
3
|
ComponentPluginFactory,
|
|
4
4
|
ChunkType,
|
|
5
5
|
FileType,
|
|
6
|
+
UIDLStateDefinition,
|
|
6
7
|
} from '@teleporthq/teleport-types'
|
|
7
8
|
import * as types from '@babel/types'
|
|
8
9
|
import { parseExpression } from '@babel/parser'
|
|
9
10
|
import { ASTStatementOrder, GenericUtils, StringUtils } from '@teleporthq/teleport-shared'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ASTUtils,
|
|
13
|
+
RouteUtils,
|
|
14
|
+
URLQueryWriter,
|
|
15
|
+
URLSearchParamSync,
|
|
16
|
+
} from '@teleporthq/teleport-plugin-common'
|
|
11
17
|
import { collectFilterLeafConditions, generateSafeFileName, pruneFilterTree } from './utils'
|
|
12
18
|
import { generateDataSourceFetcherWithCore } from './data-source-fetchers'
|
|
13
19
|
import { buildProductTransformOptions, type EntityTransformOptions } from './transformations'
|
|
14
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
appendSortsParam,
|
|
22
|
+
buildSortAtStartGuard,
|
|
23
|
+
buildStartingSortsValue,
|
|
24
|
+
DynamicSortAST,
|
|
25
|
+
readRepeaterSort,
|
|
26
|
+
type SortStateDefault,
|
|
27
|
+
} from './sort-utils'
|
|
15
28
|
import {
|
|
16
29
|
appendFiltersParam,
|
|
17
30
|
buildStaticFiltersStringifyCall,
|
|
@@ -32,6 +45,7 @@ import {
|
|
|
32
45
|
buildCacheStoreThen,
|
|
33
46
|
buildCachedFetchBody,
|
|
34
47
|
buildCachedParamsDeclarations,
|
|
48
|
+
paramsVarFor,
|
|
35
49
|
registerCacheClientImports,
|
|
36
50
|
} from './cache/ast'
|
|
37
51
|
import {
|
|
@@ -60,9 +74,13 @@ import {
|
|
|
60
74
|
import {
|
|
61
75
|
buildAccumulatingRequestMark,
|
|
62
76
|
buildAccumulatingResponseHandler,
|
|
77
|
+
buildAppendingRequestCheck,
|
|
78
|
+
buildFirstPageDeclaration,
|
|
79
|
+
buildFirstPageSeedEffect,
|
|
63
80
|
buildInfiniteScrollDeclarations,
|
|
64
81
|
buildSentinelElement,
|
|
65
82
|
buildSentinelObserverEffect,
|
|
83
|
+
buildServerRowsExpression,
|
|
66
84
|
getInfiniteScrollVars,
|
|
67
85
|
} from './infinite-scroll'
|
|
68
86
|
import {
|
|
@@ -197,6 +215,12 @@ interface DataSourceUsage {
|
|
|
197
215
|
// Dynamic single-column sort bound to component state (set when legacy sorts empty
|
|
198
216
|
// and cms-list-repeater declares sort/sortDirection fields)
|
|
199
217
|
dynamicSort?: DynamicSortAST
|
|
218
|
+
// With `dynamicSort`: the states it reads, at the values a visitor starts
|
|
219
|
+
// with (`readSortStateDefaults`). The build fetches the first page by that
|
|
220
|
+
// sort, and the list takes those rows while the sort is still there. Absent
|
|
221
|
+
// when the build cannot know the starting sort: the list then fetches its
|
|
222
|
+
// first page in the browser.
|
|
223
|
+
startingSort?: SortStateDefault[]
|
|
200
224
|
// Filters from resource params, as the TREE the inspector authored: condition
|
|
201
225
|
// leaves and `{ type: 'group', operator, children }` entries (see
|
|
202
226
|
// `readFilterTree`). Anything that needs the leaves walks the tree with
|
|
@@ -456,7 +480,10 @@ function buildFilterDestinationExpression(destination: unknown): types.Expressio
|
|
|
456
480
|
}
|
|
457
481
|
|
|
458
482
|
// Scan UIDL to find all data source usages and build a registry
|
|
459
|
-
function buildStateRegistry(
|
|
483
|
+
function buildStateRegistry(
|
|
484
|
+
uidlNode: any,
|
|
485
|
+
stateDefinitions?: Record<string, UIDLStateDefinition>
|
|
486
|
+
): StateRegistry {
|
|
460
487
|
const usages: DataSourceUsage[] = []
|
|
461
488
|
const byDataSourceId = new Map<string, DataSourceUsage[]>()
|
|
462
489
|
const byArrayMapperRenderProp = new Map<string, DataSourceUsage>()
|
|
@@ -525,15 +552,20 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
525
552
|
// is skipped for a dynamic sort) — the pre-rendered HTML carried no
|
|
526
553
|
// posts, and the browser fetched page 1 a second time. Only a sort
|
|
527
554
|
// bound to state (the admin panel's sortable columns) is dynamic.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
555
|
+
//
|
|
556
|
+
// A sort the visitor picks still starts somewhere — the store's sort
|
|
557
|
+
// dropdown at "Name A-Z". Known at build time, it orders the
|
|
558
|
+
// server-side first page, which the list then shows while the visitor
|
|
559
|
+
// leaves the sort alone. Read as a plain list's page fetch reads it.
|
|
560
|
+
const repeaterSort =
|
|
561
|
+
!sorts || sorts.length === 0
|
|
562
|
+
? readRepeaterSort(content.sort, content.sortDirection, stateDefinitions)
|
|
563
|
+
: {}
|
|
564
|
+
if (repeaterSort.fixed) {
|
|
565
|
+
sorts = [repeaterSort.fixed]
|
|
536
566
|
}
|
|
567
|
+
const dynamicSort = repeaterSort.dynamic
|
|
568
|
+
const startingSort = repeaterSort.starting
|
|
537
569
|
|
|
538
570
|
// Extract filters from parent's resource params, keeping the group tree
|
|
539
571
|
// the inspector authored (see `readFilterTree`).
|
|
@@ -628,6 +660,7 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
628
660
|
queryColumns,
|
|
629
661
|
sorts,
|
|
630
662
|
dynamicSort,
|
|
663
|
+
startingSort,
|
|
631
664
|
filters,
|
|
632
665
|
filterStateIds,
|
|
633
666
|
filterPropIds,
|
|
@@ -1220,7 +1253,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1220
1253
|
const hasRangeControl = rangeControls.length > 0
|
|
1221
1254
|
|
|
1222
1255
|
// STEP 1: Build state registry from UIDL
|
|
1223
|
-
const registry = buildStateRegistry(uidl.node)
|
|
1256
|
+
const registry = buildStateRegistry(uidl.node, uidl.stateDefinitions)
|
|
1224
1257
|
|
|
1225
1258
|
if (registry.usages.length === 0) {
|
|
1226
1259
|
return structure
|
|
@@ -1847,6 +1880,9 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1847
1880
|
// widgets can be matched to their owner rather than counted off in order.
|
|
1848
1881
|
const usageByProviderNode = new Map<any, DataSourceUsage>()
|
|
1849
1882
|
const cacheDeclarations: types.Statement[] = []
|
|
1883
|
+
// Only a page has rows of its own to start an appending list from: its
|
|
1884
|
+
// `getStaticProps` fetched page 1 (`updateGetStaticProps`).
|
|
1885
|
+
const firstPageDeclarations: types.Statement[] | undefined = isPage ? [] : undefined
|
|
1850
1886
|
|
|
1851
1887
|
dataProvidersWithRepeaters.forEach((dp) => {
|
|
1852
1888
|
const nameAttr = dp.openingElement.attributes.find(
|
|
@@ -1885,10 +1921,19 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1885
1921
|
vars,
|
|
1886
1922
|
fileName,
|
|
1887
1923
|
cacheDeclarations,
|
|
1888
|
-
localized
|
|
1924
|
+
localized,
|
|
1925
|
+
firstPageDeclarations
|
|
1889
1926
|
)
|
|
1890
1927
|
} else if (usage.category === 'paginated-only') {
|
|
1891
|
-
updateDataProviderForPaginationOnly(
|
|
1928
|
+
updateDataProviderForPaginationOnly(
|
|
1929
|
+
dp,
|
|
1930
|
+
usage,
|
|
1931
|
+
vars,
|
|
1932
|
+
fileName,
|
|
1933
|
+
cacheDeclarations,
|
|
1934
|
+
localized,
|
|
1935
|
+
firstPageDeclarations
|
|
1936
|
+
)
|
|
1892
1937
|
} else if (usage.category === 'search-only') {
|
|
1893
1938
|
updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations, localized)
|
|
1894
1939
|
} else if (usage.category === 'plain') {
|
|
@@ -1899,9 +1944,17 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1899
1944
|
// change refetches, instead of leaving the previous rows on screen with
|
|
1900
1945
|
// no feedback. Runs last so it sees the `fetchData` and
|
|
1901
1946
|
// `persistDataDuringLoading` attributes the updaters above just wrote.
|
|
1947
|
+
// An appending list keeps its rows on screen while its next page loads;
|
|
1948
|
+
// only a list loading afresh falls through to the loading slot.
|
|
1902
1949
|
const loadingVars = getLoadingStateVars(usage.index)
|
|
1903
|
-
|
|
1904
|
-
|
|
1950
|
+
const appendingRequest =
|
|
1951
|
+
usage.infiniteScroll && usage.paginated
|
|
1952
|
+
? buildAppendingRequestCheck(getInfiniteScrollVars(usage.index))
|
|
1953
|
+
: undefined
|
|
1954
|
+
if (applyLoadingStateToDataProvider(dp, loadingVars, appendingRequest)) {
|
|
1955
|
+
loadingStateDeclarations.push(
|
|
1956
|
+
...buildLoadingStateDeclarations(loadingVars, !!appendingRequest)
|
|
1957
|
+
)
|
|
1905
1958
|
// Recorded because the loading state is only declared for a provider
|
|
1906
1959
|
// that HAS a loading slot; a Load More button wired later may only
|
|
1907
1960
|
// reference `ds_N_isFetching` if it actually exists.
|
|
@@ -1969,6 +2022,12 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1969
2022
|
registerCacheClientImports(dependencies, uidl.outputOptions?.folderPath)
|
|
1970
2023
|
}
|
|
1971
2024
|
|
|
2025
|
+
// After the cache declarations: a cached list's first page reads the
|
|
2026
|
+
// `ds_N_params` they hoisted.
|
|
2027
|
+
if (firstPageDeclarations?.length) {
|
|
2028
|
+
ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, firstPageDeclarations)
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1972
2031
|
// STEP 3.5: Handle DataProviders WITHOUT repeaters (data-source-item type)
|
|
1973
2032
|
// These access single items like data[0].name and should not re-render on state changes
|
|
1974
2033
|
// We wrap their params in useMemo to prevent reference changes from triggering re-renders
|
|
@@ -2049,14 +2108,17 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
2049
2108
|
}
|
|
2050
2109
|
})
|
|
2051
2110
|
|
|
2052
|
-
// STEP 6: Update getStaticProps if this is a page
|
|
2111
|
+
// STEP 6: Update getStaticProps if this is a page. Its rows are what the
|
|
2112
|
+
// page renders, so they are read as a visitor is served them — unless only
|
|
2113
|
+
// the store's trusted readers can open the page (`pageDataReadArguments`).
|
|
2053
2114
|
if (isPage) {
|
|
2054
2115
|
updateGetStaticProps(
|
|
2055
2116
|
chunks,
|
|
2056
2117
|
registry,
|
|
2057
2118
|
dependencies,
|
|
2058
2119
|
uidl.outputOptions?.folderPath,
|
|
2059
|
-
localized
|
|
2120
|
+
localized,
|
|
2121
|
+
() => RouteUtils.pageDataReadArguments(uidl, options.auth)
|
|
2060
2122
|
)
|
|
2061
2123
|
}
|
|
2062
2124
|
|
|
@@ -2353,10 +2415,12 @@ function findPaginationNodesWithOwners(
|
|
|
2353
2415
|
*
|
|
2354
2416
|
* An uncached mapper keeps the inline `params={useMemo(...)}` it has always had,
|
|
2355
2417
|
* byte for byte. A cached one hoists that memo into a component const so the
|
|
2356
|
-
* peek can depend on the same object identity instead of rebuilding it
|
|
2418
|
+
* peek can depend on the same object identity instead of rebuilding it; so does
|
|
2419
|
+
* an appending list that starts from its page's rows (`hoistInto`), whose first
|
|
2420
|
+
* page is filed in the accumulator under those params.
|
|
2357
2421
|
*
|
|
2358
|
-
* Returns the identifier holding the
|
|
2359
|
-
*
|
|
2422
|
+
* Returns the identifier holding the params when they were hoisted, and the one
|
|
2423
|
+
* holding the peeked rows when this mapper caches.
|
|
2360
2424
|
*/
|
|
2361
2425
|
function pushParamsAttribute(
|
|
2362
2426
|
// tslint:disable-next-line:no-any
|
|
@@ -2364,24 +2428,40 @@ function pushParamsAttribute(
|
|
|
2364
2428
|
usage: DataSourceUsage,
|
|
2365
2429
|
paramsProps: types.ObjectProperty[],
|
|
2366
2430
|
memoDeps: types.Expression[],
|
|
2367
|
-
cacheDeclarations: types.Statement[]
|
|
2368
|
-
|
|
2431
|
+
cacheDeclarations: types.Statement[],
|
|
2432
|
+
hoistInto?: types.Statement[]
|
|
2433
|
+
): { paramsIdentifier?: string; cachedIdentifier?: string } {
|
|
2369
2434
|
const paramsMemo = types.callExpression(types.identifier('useMemo'), [
|
|
2370
2435
|
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
2371
2436
|
types.arrayExpression(memoDeps),
|
|
2372
2437
|
])
|
|
2373
2438
|
|
|
2374
|
-
// A state-bound sort
|
|
2375
|
-
//
|
|
2376
|
-
//
|
|
2377
|
-
//
|
|
2439
|
+
// A state-bound sort gets no cache peek. The peek is `initialData` by another
|
|
2440
|
+
// name, and such a list takes the server's rows only while its sort is still
|
|
2441
|
+
// where the visitor starts (`buildStartingSortGuard`); the peek was never
|
|
2442
|
+
// extended to it.
|
|
2378
2443
|
const cache = usage.cache?.client && !usage.dynamicSort ? usage.cache : undefined
|
|
2379
2444
|
|
|
2380
2445
|
if (!cache) {
|
|
2446
|
+
if (!hoistInto) {
|
|
2447
|
+
dp.openingElement.attributes.push(
|
|
2448
|
+
types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo))
|
|
2449
|
+
)
|
|
2450
|
+
return {}
|
|
2451
|
+
}
|
|
2452
|
+
const paramsIdentifier = paramsVarFor(usage.index)
|
|
2453
|
+
hoistInto.push(
|
|
2454
|
+
types.variableDeclaration('const', [
|
|
2455
|
+
types.variableDeclarator(types.identifier(paramsIdentifier), paramsMemo),
|
|
2456
|
+
])
|
|
2457
|
+
)
|
|
2381
2458
|
dp.openingElement.attributes.push(
|
|
2382
|
-
types.jsxAttribute(
|
|
2459
|
+
types.jsxAttribute(
|
|
2460
|
+
types.jsxIdentifier('params'),
|
|
2461
|
+
types.jsxExpressionContainer(types.identifier(paramsIdentifier))
|
|
2462
|
+
)
|
|
2383
2463
|
)
|
|
2384
|
-
return
|
|
2464
|
+
return { paramsIdentifier }
|
|
2385
2465
|
}
|
|
2386
2466
|
|
|
2387
2467
|
const hoisted = buildCachedParamsDeclarations({ index: usage.index, paramsMemo, cache })
|
|
@@ -2394,7 +2474,97 @@ function pushParamsAttribute(
|
|
|
2394
2474
|
)
|
|
2395
2475
|
)
|
|
2396
2476
|
|
|
2397
|
-
return hoisted.cachedIdentifier
|
|
2477
|
+
return { paramsIdentifier: hoisted.paramsIdentifier, cachedIdentifier: hoisted.cachedIdentifier }
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// Whether the rows `getStaticProps` fetched can be this list's first page at
|
|
2481
|
+
// all. A sort bound to state can only be served them when the build knows the
|
|
2482
|
+
// sort the visitor starts with (`startingSort`) and fetched by it; otherwise
|
|
2483
|
+
// the fetch has no sort, and its rows are not the list's first page.
|
|
2484
|
+
function prefetchFollowsSort(usage: DataSourceUsage): boolean {
|
|
2485
|
+
return !usage.dynamicSort || !!usage.startingSort
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// `sortBy === "name-asc"`: the visitor has not moved the sort from where it
|
|
2489
|
+
// starts, so the build-time rows are still its first page. `null` for a list
|
|
2490
|
+
// whose sort the visitor cannot pick, or one that reads no state.
|
|
2491
|
+
function buildStartingSortGuard(usage: DataSourceUsage): types.Expression | null {
|
|
2492
|
+
return usage.dynamicSort && usage.startingSort ? buildSortAtStartGuard(usage.startingSort) : null
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// `<condition> && <starting-sort guard>`, or the condition alone.
|
|
2496
|
+
function withStartingSortGuard(
|
|
2497
|
+
condition: types.Expression,
|
|
2498
|
+
usage: DataSourceUsage
|
|
2499
|
+
): types.Expression {
|
|
2500
|
+
const sortGuard = buildStartingSortGuard(usage)
|
|
2501
|
+
return sortGuard ? types.logicalExpression('&&', condition, sortGuard) : condition
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// An appending list on a page starts from the rows its `getStaticProps`
|
|
2505
|
+
// fetched, when they can be its first page (`prefetchFollowsSort`).
|
|
2506
|
+
function startsFromServerRows(usage: DataSourceUsage): boolean {
|
|
2507
|
+
return usage.infiniteScroll && usage.paginated && prefetchFollowsSort(usage)
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
/**
|
|
2511
|
+
* `initialData={ds_N_firstPage}` on an appending list, beside the frozen first
|
|
2512
|
+
* page and the effect that files it as page 1 of the accumulator (see
|
|
2513
|
+
* `infinite-scroll.ts`).
|
|
2514
|
+
*
|
|
2515
|
+
* The rows stand in for page 1 only while they answer the list's query: no
|
|
2516
|
+
* search (`queryGuard`), the sort where the visitor starts, no filter set (the
|
|
2517
|
+
* guards the paged lists use), and at least one row — an empty build-time
|
|
2518
|
+
* answer, which a failed fetch also gives, leaves the browser to fetch page 1
|
|
2519
|
+
* as before.
|
|
2520
|
+
*/
|
|
2521
|
+
function pushInfiniteScrollFirstPage(
|
|
2522
|
+
// tslint:disable-next-line:no-any
|
|
2523
|
+
dp: any,
|
|
2524
|
+
usage: DataSourceUsage,
|
|
2525
|
+
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2526
|
+
paramsIdentifier: string,
|
|
2527
|
+
declarations: types.Statement[],
|
|
2528
|
+
queryGuard: types.Expression | null,
|
|
2529
|
+
instanceDeps: types.Expression[]
|
|
2530
|
+
): void {
|
|
2531
|
+
const infiniteVars = getInfiniteScrollVars(usage.index)
|
|
2532
|
+
const serverPayload = (): types.Expression =>
|
|
2533
|
+
types.optionalMemberExpression(
|
|
2534
|
+
types.identifier('props'),
|
|
2535
|
+
types.identifier(vars.propsPrefix),
|
|
2536
|
+
false,
|
|
2537
|
+
true
|
|
2538
|
+
)
|
|
2539
|
+
const hasServerRows = types.binaryExpression(
|
|
2540
|
+
'>',
|
|
2541
|
+
types.optionalMemberExpression(
|
|
2542
|
+
buildServerRowsExpression(serverPayload(), usage.itemsPath),
|
|
2543
|
+
types.identifier('length'),
|
|
2544
|
+
false,
|
|
2545
|
+
true
|
|
2546
|
+
),
|
|
2547
|
+
types.numericLiteral(0)
|
|
2548
|
+
)
|
|
2549
|
+
const startsFromServer = [
|
|
2550
|
+
queryGuard,
|
|
2551
|
+
buildStartingSortGuard(usage),
|
|
2552
|
+
buildNoUrlFilterGuard(usage),
|
|
2553
|
+
hasServerRows,
|
|
2554
|
+
]
|
|
2555
|
+
.filter((guard): guard is types.Expression => guard !== null)
|
|
2556
|
+
.reduce((all, guard) => types.logicalExpression('&&', all, guard))
|
|
2557
|
+
|
|
2558
|
+
declarations.push(
|
|
2559
|
+
buildFirstPageDeclaration(infiniteVars, startsFromServer, serverPayload(), instanceDeps),
|
|
2560
|
+
buildFirstPageSeedEffect(infiniteVars, paramsIdentifier, usage.perPage, usage.itemsPath)
|
|
2561
|
+
)
|
|
2562
|
+
dp.openingElement.attributes.push(
|
|
2563
|
+
types.jsxAttribute(
|
|
2564
|
+
types.jsxIdentifier('initialData'),
|
|
2565
|
+
types.jsxExpressionContainer(types.identifier(infiniteVars.firstPageVar))
|
|
2566
|
+
)
|
|
2567
|
+
)
|
|
2398
2568
|
}
|
|
2399
2569
|
|
|
2400
2570
|
function updateDataProviderForPaginatedSearch(
|
|
@@ -2403,7 +2573,8 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2403
2573
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2404
2574
|
fileName: string,
|
|
2405
2575
|
cacheDeclarations: types.Statement[],
|
|
2406
|
-
localized: boolean
|
|
2576
|
+
localized: boolean,
|
|
2577
|
+
firstPageDeclarations?: types.Statement[]
|
|
2407
2578
|
): void {
|
|
2408
2579
|
const attrs = dp.openingElement.attributes
|
|
2409
2580
|
|
|
@@ -2462,36 +2633,67 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2462
2633
|
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2463
2634
|
}
|
|
2464
2635
|
|
|
2465
|
-
const
|
|
2636
|
+
const seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined
|
|
2637
|
+
const { paramsIdentifier, cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2638
|
+
dp,
|
|
2639
|
+
usage,
|
|
2640
|
+
paramsProps,
|
|
2641
|
+
memoDeps,
|
|
2642
|
+
cacheDeclarations,
|
|
2643
|
+
seedInto
|
|
2644
|
+
)
|
|
2466
2645
|
|
|
2467
|
-
// Add initialData
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
//
|
|
2471
|
-
//
|
|
2472
|
-
//
|
|
2646
|
+
// Add initialData: the server-prefetched page 1 while it is the page on show
|
|
2647
|
+
// — page 1, no search, no filter set, and a sort the visitor picks still where
|
|
2648
|
+
// they start (the prefetch ran by that sort). A list whose starting sort the
|
|
2649
|
+
// build cannot know never takes it: that prefetch ran without a sort, so
|
|
2650
|
+
// reusing it would mask the current sort AND make DataProvider skip its first
|
|
2651
|
+
// client fetch (its internal guard only skips when initialData is defined on
|
|
2652
|
+
// mount). Any other sort is fetched fresh, through the useMemo params.
|
|
2473
2653
|
//
|
|
2474
|
-
// An appending list
|
|
2475
|
-
//
|
|
2476
|
-
//
|
|
2477
|
-
//
|
|
2478
|
-
|
|
2654
|
+
// An appending list takes its own: `initialData` makes DataProvider skip its
|
|
2655
|
+
// first fetch, so the prefetched page 1 has to be filed in the accumulator
|
|
2656
|
+
// too, or appending page 2 would REPLACE the visible rows with page 2 alone.
|
|
2657
|
+
// The provider remounts per query (its `key`), so the first page is frozen
|
|
2658
|
+
// per query.
|
|
2659
|
+
if (seedInto && paramsIdentifier) {
|
|
2660
|
+
const appliedQuery = (): types.MemberExpression =>
|
|
2661
|
+
types.memberExpression(
|
|
2662
|
+
types.identifier(vars.combinedStateVar),
|
|
2663
|
+
types.identifier('debouncedQuery')
|
|
2664
|
+
)
|
|
2665
|
+
pushInfiniteScrollFirstPage(
|
|
2666
|
+
dp,
|
|
2667
|
+
usage,
|
|
2668
|
+
vars,
|
|
2669
|
+
paramsIdentifier,
|
|
2670
|
+
seedInto,
|
|
2671
|
+
types.unaryExpression('!', appliedQuery(), true),
|
|
2672
|
+
[appliedQuery()]
|
|
2673
|
+
)
|
|
2674
|
+
} else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
|
|
2479
2675
|
const initialDataCondition = wrapInitialDataWithUrlFilterGuard(
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
types.identifier(vars.combinedStateVar),
|
|
2491
|
-
types.identifier('debouncedQuery')
|
|
2676
|
+
withStartingSortGuard(
|
|
2677
|
+
types.logicalExpression(
|
|
2678
|
+
'&&',
|
|
2679
|
+
types.binaryExpression(
|
|
2680
|
+
'===',
|
|
2681
|
+
types.memberExpression(
|
|
2682
|
+
types.identifier(vars.combinedStateVar),
|
|
2683
|
+
types.identifier('page')
|
|
2684
|
+
),
|
|
2685
|
+
types.numericLiteral(1)
|
|
2492
2686
|
),
|
|
2493
|
-
|
|
2494
|
-
|
|
2687
|
+
types.unaryExpression(
|
|
2688
|
+
'!',
|
|
2689
|
+
types.memberExpression(
|
|
2690
|
+
types.identifier(vars.combinedStateVar),
|
|
2691
|
+
types.identifier('debouncedQuery')
|
|
2692
|
+
),
|
|
2693
|
+
true
|
|
2694
|
+
)
|
|
2695
|
+
),
|
|
2696
|
+
usage
|
|
2495
2697
|
),
|
|
2496
2698
|
usage
|
|
2497
2699
|
)
|
|
@@ -2585,7 +2787,8 @@ function updateDataProviderForPaginationOnly(
|
|
|
2585
2787
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2586
2788
|
fileName: string,
|
|
2587
2789
|
cacheDeclarations: types.Statement[],
|
|
2588
|
-
localized: boolean
|
|
2790
|
+
localized: boolean,
|
|
2791
|
+
firstPageDeclarations?: types.Statement[]
|
|
2589
2792
|
): void {
|
|
2590
2793
|
const attrs = dp.openingElement.attributes
|
|
2591
2794
|
|
|
@@ -2623,22 +2826,36 @@ function updateDataProviderForPaginationOnly(
|
|
|
2623
2826
|
}
|
|
2624
2827
|
|
|
2625
2828
|
// Add params
|
|
2626
|
-
const
|
|
2829
|
+
const seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined
|
|
2830
|
+
const { paramsIdentifier, cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2831
|
+
dp,
|
|
2832
|
+
usage,
|
|
2833
|
+
paramsProps,
|
|
2834
|
+
memoDeps,
|
|
2835
|
+
cacheDeclarations,
|
|
2836
|
+
seedInto
|
|
2837
|
+
)
|
|
2627
2838
|
|
|
2628
|
-
// Add initialData. See paginated+search updater for
|
|
2629
|
-
//
|
|
2630
|
-
//
|
|
2631
|
-
|
|
2839
|
+
// Add initialData. See paginated+search updater for when a sort the visitor
|
|
2840
|
+
// picks may reuse the prefetch, and how an appending list takes its own. This
|
|
2841
|
+
// provider never remounts (no `key`), so its first page is frozen for the
|
|
2842
|
+
// page's lifetime.
|
|
2843
|
+
if (seedInto && paramsIdentifier) {
|
|
2844
|
+
pushInfiniteScrollFirstPage(dp, usage, vars, paramsIdentifier, seedInto, null, [])
|
|
2845
|
+
} else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
|
|
2632
2846
|
dp.openingElement.attributes.push(
|
|
2633
2847
|
types.jsxAttribute(
|
|
2634
2848
|
types.jsxIdentifier('initialData'),
|
|
2635
2849
|
types.jsxExpressionContainer(
|
|
2636
2850
|
types.conditionalExpression(
|
|
2637
2851
|
wrapInitialDataWithUrlFilterGuard(
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2852
|
+
withStartingSortGuard(
|
|
2853
|
+
types.binaryExpression(
|
|
2854
|
+
'===',
|
|
2855
|
+
types.identifier(vars.pageStateVar),
|
|
2856
|
+
types.numericLiteral(1)
|
|
2857
|
+
),
|
|
2858
|
+
usage
|
|
2642
2859
|
),
|
|
2643
2860
|
usage
|
|
2644
2861
|
),
|
|
@@ -2743,18 +2960,27 @@ function updateDataProviderForSearchOnly(
|
|
|
2743
2960
|
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2744
2961
|
}
|
|
2745
2962
|
|
|
2746
|
-
const cachedInitialData = pushParamsAttribute(
|
|
2963
|
+
const { cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2964
|
+
dp,
|
|
2965
|
+
usage,
|
|
2966
|
+
paramsProps,
|
|
2967
|
+
memoDeps,
|
|
2968
|
+
cacheDeclarations
|
|
2969
|
+
)
|
|
2747
2970
|
|
|
2748
|
-
// Add initialData. See paginated+search updater for
|
|
2749
|
-
//
|
|
2750
|
-
if (
|
|
2971
|
+
// Add initialData. See paginated+search updater for when a sort the visitor
|
|
2972
|
+
// picks may reuse the prefetch.
|
|
2973
|
+
if (prefetchFollowsSort(usage)) {
|
|
2751
2974
|
dp.openingElement.attributes.push(
|
|
2752
2975
|
types.jsxAttribute(
|
|
2753
2976
|
types.jsxIdentifier('initialData'),
|
|
2754
2977
|
types.jsxExpressionContainer(
|
|
2755
2978
|
types.conditionalExpression(
|
|
2756
2979
|
wrapInitialDataWithUrlFilterGuard(
|
|
2757
|
-
|
|
2980
|
+
withStartingSortGuard(
|
|
2981
|
+
types.unaryExpression('!', types.identifier(vars.debouncedSearchQueryVar), true),
|
|
2982
|
+
usage
|
|
2983
|
+
),
|
|
2758
2984
|
usage
|
|
2759
2985
|
),
|
|
2760
2986
|
types.optionalMemberExpression(
|
|
@@ -2799,20 +3025,58 @@ function updateDataProviderForSearchOnly(
|
|
|
2799
3025
|
)
|
|
2800
3026
|
}
|
|
2801
3027
|
|
|
3028
|
+
/**
|
|
3029
|
+
* A plain list on a page is seeded with the rows its `getStaticProps` fetched
|
|
3030
|
+
* (`extractDataSourceIntoGetStaticProps`), and a seeded provider skips its first
|
|
3031
|
+
* fetch. With a sort the visitor picks, those rows are the list's only while the
|
|
3032
|
+
* sort is where a visitor starts — the build fetched them by it:
|
|
3033
|
+
*
|
|
3034
|
+
* initialData={sortBy === "name-asc" ? props.items_products : undefined}
|
|
3035
|
+
*
|
|
3036
|
+
* — and any other sort is fetched fresh, through the params it changes. When the
|
|
3037
|
+
* build cannot know where the sort starts (`prefetchFollowsSort`), it fetched
|
|
3038
|
+
* the rows unsorted, and the list is never seeded with them. A plain list has no
|
|
3039
|
+
* search, and is only seeded without a filter a visitor sets.
|
|
3040
|
+
*/
|
|
3041
|
+
function seedPlainListWhileSortStarts(dp: any, usage: DataSourceUsage): void {
|
|
3042
|
+
if (!usage.dynamicSort) {
|
|
3043
|
+
return
|
|
3044
|
+
}
|
|
3045
|
+
const attrs = dp.openingElement.attributes
|
|
3046
|
+
const index = attrs.findIndex(
|
|
3047
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name?.name === 'initialData'
|
|
3048
|
+
)
|
|
3049
|
+
if (index === -1) {
|
|
3050
|
+
return
|
|
3051
|
+
}
|
|
3052
|
+
if (!prefetchFollowsSort(usage)) {
|
|
3053
|
+
attrs.splice(index, 1)
|
|
3054
|
+
return
|
|
3055
|
+
}
|
|
3056
|
+
const sortGuard = buildStartingSortGuard(usage)
|
|
3057
|
+
const seeded = (attrs[index] as types.JSXAttribute).value
|
|
3058
|
+
if (!sortGuard || seeded?.type !== 'JSXExpressionContainer') {
|
|
3059
|
+
return
|
|
3060
|
+
}
|
|
3061
|
+
attrs[index] = types.jsxAttribute(
|
|
3062
|
+
types.jsxIdentifier('initialData'),
|
|
3063
|
+
types.jsxExpressionContainer(
|
|
3064
|
+
types.conditionalExpression(
|
|
3065
|
+
sortGuard,
|
|
3066
|
+
seeded.expression as types.Expression,
|
|
3067
|
+
types.identifier('undefined')
|
|
3068
|
+
)
|
|
3069
|
+
)
|
|
3070
|
+
)
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2802
3073
|
function updateDataProviderForPlain(
|
|
2803
3074
|
dp: any,
|
|
2804
3075
|
fileName: string,
|
|
2805
3076
|
usage: DataSourceUsage,
|
|
2806
3077
|
localized: boolean
|
|
2807
3078
|
): void {
|
|
2808
|
-
|
|
2809
|
-
// pre-rendered in the table's order would stand in for it, since a seeded
|
|
2810
|
-
// provider skips its first fetch — the paginated lists drop them the same way.
|
|
2811
|
-
if (usage.dynamicSort) {
|
|
2812
|
-
dp.openingElement.attributes = dp.openingElement.attributes.filter(
|
|
2813
|
-
(attr: any) => !(attr.type === 'JSXAttribute' && attr.name?.name === 'initialData')
|
|
2814
|
-
)
|
|
2815
|
-
}
|
|
3079
|
+
seedPlainListWhileSortStarts(dp, usage)
|
|
2816
3080
|
|
|
2817
3081
|
const attrs = dp.openingElement.attributes
|
|
2818
3082
|
|
|
@@ -3627,12 +3891,24 @@ export default dataSourceModule.getCount
|
|
|
3627
3891
|
}
|
|
3628
3892
|
}
|
|
3629
3893
|
|
|
3894
|
+
// `fetchCount(<params>, <read arguments>)`: the params may be left out only
|
|
3895
|
+
// when nothing follows them.
|
|
3896
|
+
const countReadArguments = (
|
|
3897
|
+
countParams: types.ObjectProperty[],
|
|
3898
|
+
readArguments: types.Expression[]
|
|
3899
|
+
): types.Expression[] =>
|
|
3900
|
+
countParams.length > 0 || readArguments.length > 0
|
|
3901
|
+
? [types.objectExpression(countParams), ...readArguments]
|
|
3902
|
+
: []
|
|
3903
|
+
|
|
3630
3904
|
function updateGetStaticProps(
|
|
3631
3905
|
chunks: any[],
|
|
3632
3906
|
registry: StateRegistry,
|
|
3633
3907
|
dependencies: Record<string, any>,
|
|
3634
3908
|
folderPath: string[] | undefined,
|
|
3635
|
-
localized: boolean
|
|
3909
|
+
localized: boolean,
|
|
3910
|
+
// The arguments every read hands the module after its params (fresh nodes per call).
|
|
3911
|
+
readArguments: () => types.Expression[]
|
|
3636
3912
|
): void {
|
|
3637
3913
|
const getStaticPropsChunk = chunks.find((c) => c.name === 'getStaticProps')
|
|
3638
3914
|
if (!getStaticPropsChunk || getStaticPropsChunk.type !== ChunkType.AST) {
|
|
@@ -3764,6 +4040,16 @@ function updateGetStaticProps(
|
|
|
3764
4040
|
)
|
|
3765
4041
|
)
|
|
3766
4042
|
)
|
|
4043
|
+
} else if (usage.dynamicSort && usage.startingSort) {
|
|
4044
|
+
// A sort the visitor picks: page 1 by the sort they start with, sent
|
|
4045
|
+
// as the very string the browser sends for it, so page 2 continues
|
|
4046
|
+
// this page exactly (see `buildStartingSortsValue`).
|
|
4047
|
+
fetchParams.push(
|
|
4048
|
+
types.objectProperty(
|
|
4049
|
+
types.identifier('sorts'),
|
|
4050
|
+
buildStartingSortsValue(usage.dynamicSort, usage.startingSort)
|
|
4051
|
+
)
|
|
4052
|
+
)
|
|
3767
4053
|
}
|
|
3768
4054
|
|
|
3769
4055
|
// Add filters if present
|
|
@@ -3801,7 +4087,7 @@ function updateGetStaticProps(
|
|
|
3801
4087
|
types.identifier(fetcherImportName),
|
|
3802
4088
|
types.identifier('fetchData')
|
|
3803
4089
|
),
|
|
3804
|
-
[types.objectExpression(fetchParams)]
|
|
4090
|
+
[types.objectExpression(fetchParams), ...readArguments()]
|
|
3805
4091
|
),
|
|
3806
4092
|
types.identifier('catch')
|
|
3807
4093
|
),
|
|
@@ -3990,7 +4276,7 @@ function updateGetStaticProps(
|
|
|
3990
4276
|
types.identifier(fetcherImportName),
|
|
3991
4277
|
types.identifier('fetchCount')
|
|
3992
4278
|
),
|
|
3993
|
-
countParams
|
|
4279
|
+
countReadArguments(countParams, readArguments())
|
|
3994
4280
|
),
|
|
3995
4281
|
types.identifier('catch')
|
|
3996
4282
|
),
|