@teleporthq/teleport-plugin-next-data-source 0.43.55 → 0.43.57
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__/array-mapper-cache-wiring.test.ts +425 -0
- package/__tests__/data-source-cache-wrapping.test.ts +152 -0
- package/__tests__/ecommerce-product-storefront-tax.test.ts +118 -0
- package/dist/cjs/cache/ast.d.ts +71 -0
- package/dist/cjs/cache/ast.d.ts.map +1 -0
- package/dist/cjs/cache/ast.js +229 -0
- package/dist/cjs/cache/ast.js.map +1 -0
- package/dist/cjs/cache/config.d.ts +22 -0
- package/dist/cjs/cache/config.d.ts.map +1 -0
- package/dist/cjs/cache/config.js +67 -0
- package/dist/cjs/cache/config.js.map +1 -0
- package/dist/cjs/cache/types.d.ts +31 -0
- package/dist/cjs/cache/types.d.ts.map +1 -0
- package/dist/cjs/cache/types.js +3 -0
- package/dist/cjs/cache/types.js.map +1 -0
- package/dist/cjs/data-source-fetchers.d.ts +5 -3
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +51 -8
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts +2 -2
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +3 -2
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +14 -9
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +74 -2
- 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 +204 -64
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts +15 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +7 -3
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +13 -2
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +21 -3
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +4 -3
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +6 -4
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/cache/ast.d.ts +71 -0
- package/dist/esm/cache/ast.d.ts.map +1 -0
- package/dist/esm/cache/ast.js +196 -0
- package/dist/esm/cache/ast.js.map +1 -0
- package/dist/esm/cache/config.d.ts +22 -0
- package/dist/esm/cache/config.d.ts.map +1 -0
- package/dist/esm/cache/config.js +62 -0
- package/dist/esm/cache/config.js.map +1 -0
- package/dist/esm/cache/types.d.ts +31 -0
- package/dist/esm/cache/types.d.ts.map +1 -0
- package/dist/esm/cache/types.js +2 -0
- package/dist/esm/cache/types.js.map +1 -0
- package/dist/esm/data-source-fetchers.d.ts +5 -3
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +51 -8
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts +2 -2
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +3 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +12 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +74 -2
- 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 +205 -65
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/transformations/ecommerce-product.d.ts +15 -1
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +7 -3
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +13 -2
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +20 -3
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +4 -3
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +6 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/cache/ast.ts +265 -0
- package/src/cache/config.ts +79 -0
- package/src/cache/types.ts +31 -0
- package/src/data-source-fetchers.ts +66 -12
- package/src/fetchers/teleport.ts +3 -3
- package/src/index.ts +10 -4
- package/src/loading-state.ts +65 -2
- package/src/pagination-plugin.ts +315 -116
- package/src/transformations/ecommerce-product.ts +53 -2
- package/src/transformations/index.ts +25 -4
- package/src/utils.ts +10 -5
package/src/pagination-plugin.ts
CHANGED
|
@@ -3,14 +3,17 @@ import {
|
|
|
3
3
|
ComponentPluginFactory,
|
|
4
4
|
ChunkType,
|
|
5
5
|
FileType,
|
|
6
|
-
UIDLEcommerceCategory,
|
|
7
6
|
} from '@teleporthq/teleport-types'
|
|
8
7
|
import * as types from '@babel/types'
|
|
9
8
|
import { parseExpression } from '@babel/parser'
|
|
10
|
-
import { StringUtils } from '@teleporthq/teleport-shared'
|
|
9
|
+
import { ASTStatementOrder, StringUtils } from '@teleporthq/teleport-shared'
|
|
11
10
|
import { ASTUtils, URLSearchParamSync } from '@teleporthq/teleport-plugin-common'
|
|
12
11
|
import { generateSafeFileName } from './utils'
|
|
13
12
|
import { generateDataSourceFetcherWithCore } from './data-source-fetchers'
|
|
13
|
+
import {
|
|
14
|
+
buildProductTransformOptions,
|
|
15
|
+
type EcommerceProductTransformOptions,
|
|
16
|
+
} from './transformations'
|
|
14
17
|
import { appendSortsParam, DynamicSortAST, extractDynamicSort } from './sort-utils'
|
|
15
18
|
import { appendFiltersParam, pushStateIdsAsDeps, pushPropIdsAsDeps } from './filter-utils'
|
|
16
19
|
import {
|
|
@@ -18,6 +21,15 @@ import {
|
|
|
18
21
|
buildLoadingStateDeclarations,
|
|
19
22
|
getLoadingStateVars,
|
|
20
23
|
} from './loading-state'
|
|
24
|
+
import type { DataSourceServerCacheOptions, ResolvedDataSourceCache } from './cache/types'
|
|
25
|
+
import { mergeServerCacheOptions, resolveCacheFromRepeaterContent } from './cache/config'
|
|
26
|
+
import {
|
|
27
|
+
buildCacheHydrationEffect,
|
|
28
|
+
buildCacheStoreThen,
|
|
29
|
+
buildCachedFetchBody,
|
|
30
|
+
buildCachedParamsDeclarations,
|
|
31
|
+
registerCacheClientImports,
|
|
32
|
+
} from './cache/ast'
|
|
21
33
|
|
|
22
34
|
// ----- searchDefaultValue support -----
|
|
23
35
|
//
|
|
@@ -186,6 +198,10 @@ interface DataSourceUsage {
|
|
|
186
198
|
// client-side fetch refires when the buyer navigates between
|
|
187
199
|
// `?categoryFilter=Rings` and `?categoryFilter=Necklaces`.
|
|
188
200
|
filterUrlSearchParamKeys: string[]
|
|
201
|
+
// Resolved caching setup for this mapper, or `undefined` when it does not
|
|
202
|
+
// cache — in which case every emit site below must produce exactly what it
|
|
203
|
+
// produced before caching existed.
|
|
204
|
+
cache?: ResolvedDataSourceCache
|
|
189
205
|
// Computed category
|
|
190
206
|
category: 'paginated+search' | 'paginated-only' | 'search-only' | 'plain'
|
|
191
207
|
}
|
|
@@ -568,6 +584,7 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
568
584
|
filterStateIds,
|
|
569
585
|
filterPropIds,
|
|
570
586
|
filterUrlSearchParamKeys,
|
|
587
|
+
cache: resolveCacheFromRepeaterContent(content.cache, parentDataSource.resourceDef),
|
|
571
588
|
category: 'plain',
|
|
572
589
|
}
|
|
573
590
|
|
|
@@ -1508,6 +1525,7 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1508
1525
|
// provider can use one depends on its JSX (it needs a `renderLoading` slot),
|
|
1509
1526
|
// which is only known once the category updaters above have run.
|
|
1510
1527
|
const loadingStateDeclarations: types.Statement[] = []
|
|
1528
|
+
const cacheDeclarations: types.Statement[] = []
|
|
1511
1529
|
|
|
1512
1530
|
dataProvidersWithRepeaters.forEach((dp) => {
|
|
1513
1531
|
const nameAttr = dp.openingElement.attributes.find(
|
|
@@ -1539,11 +1557,11 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1539
1557
|
|
|
1540
1558
|
// Update DataProvider based on category
|
|
1541
1559
|
if (usage.category === 'paginated+search') {
|
|
1542
|
-
updateDataProviderForPaginatedSearch(dp, usage, vars, fileName)
|
|
1560
|
+
updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations)
|
|
1543
1561
|
} else if (usage.category === 'paginated-only') {
|
|
1544
|
-
updateDataProviderForPaginationOnly(dp, usage, vars, fileName)
|
|
1562
|
+
updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations)
|
|
1545
1563
|
} else if (usage.category === 'search-only') {
|
|
1546
|
-
updateDataProviderForSearchOnly(dp, usage, vars, fileName)
|
|
1564
|
+
updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations)
|
|
1547
1565
|
} else if (usage.category === 'plain') {
|
|
1548
1566
|
updateDataProviderForPlain(dp, fileName, usage)
|
|
1549
1567
|
}
|
|
@@ -1562,15 +1580,54 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1562
1580
|
options.extractedResources,
|
|
1563
1581
|
usage,
|
|
1564
1582
|
options.dataSources,
|
|
1565
|
-
options
|
|
1583
|
+
buildProductTransformOptions(options)
|
|
1566
1584
|
)
|
|
1567
1585
|
})
|
|
1568
1586
|
|
|
1587
|
+
// tslint:disable-next-line:no-any
|
|
1588
|
+
const sharedOptions = options as any
|
|
1589
|
+
if (!sharedOptions.serverCacheByFile) {
|
|
1590
|
+
sharedOptions.serverCacheByFile = new Map()
|
|
1591
|
+
}
|
|
1592
|
+
applyServerCacheToDataSourceModules(
|
|
1593
|
+
options.extractedResources,
|
|
1594
|
+
registry,
|
|
1595
|
+
options.dataSources,
|
|
1596
|
+
buildProductTransformOptions(options),
|
|
1597
|
+
sharedOptions.serverCacheByFile
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1569
1600
|
// Declare the loading-state hooks alongside the other data-source state, at
|
|
1570
1601
|
// the top of the component body. Unconditional and in a fixed order, so the
|
|
1571
1602
|
// hook order stays stable across renders.
|
|
1572
1603
|
loadingStateDeclarations.reverse().forEach((s) => blockStatement.body.unshift(s))
|
|
1573
1604
|
|
|
1605
|
+
const cachedScopes = Array.from(
|
|
1606
|
+
new Set(
|
|
1607
|
+
registry.usages
|
|
1608
|
+
.filter((usage) => usage.cache?.client)
|
|
1609
|
+
.map((usage) => (usage.cache as ResolvedDataSourceCache).scope)
|
|
1610
|
+
)
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
if (cacheDeclarations.length || cachedScopes.length) {
|
|
1614
|
+
// One effect per page covering every scope on it: flips the hydration
|
|
1615
|
+
// latch, then asks once whether any of this page's tables changed while
|
|
1616
|
+
// the visitor was away. Without it a browser serving everything from its
|
|
1617
|
+
// own cache would never learn about a write at all. It has to land
|
|
1618
|
+
// BEFORE the component's `return` — a plain `push` at this point in the
|
|
1619
|
+
// pipeline appends after it, where React never registers the hook.
|
|
1620
|
+
ASTStatementOrder.insertStatementsBeforeReturn(blockStatement, [
|
|
1621
|
+
buildCacheHydrationEffect(cachedScopes),
|
|
1622
|
+
])
|
|
1623
|
+
// `ds_N_params` / `ds_N_cached` read the `ds_N_state` hooks declared
|
|
1624
|
+
// above, so they are placed at the earliest point where everything they
|
|
1625
|
+
// reference is already in scope — never blindly at the top, which is
|
|
1626
|
+
// exactly the `Cannot access 'ds_0_state' before initialization` crash.
|
|
1627
|
+
ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, cacheDeclarations)
|
|
1628
|
+
registerCacheClientImports(dependencies, uidl.outputOptions?.folderPath)
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1574
1631
|
// STEP 3.5: Handle DataProviders WITHOUT repeaters (data-source-item type)
|
|
1575
1632
|
// These access single items like data[0].name and should not re-render on state changes
|
|
1576
1633
|
// We wrap their params in useMemo to prevent reference changes from triggering re-renders
|
|
@@ -1826,11 +1883,61 @@ function findAllPaginationNodesInJSX(
|
|
|
1826
1883
|
return results
|
|
1827
1884
|
}
|
|
1828
1885
|
|
|
1886
|
+
/**
|
|
1887
|
+
* Emits the provider's `params` and, for a cached mapper, the peek beside it.
|
|
1888
|
+
*
|
|
1889
|
+
* An uncached mapper keeps the inline `params={useMemo(...)}` it has always had,
|
|
1890
|
+
* byte for byte. A cached one hoists that memo into a component const so the
|
|
1891
|
+
* peek can depend on the same object identity instead of rebuilding it.
|
|
1892
|
+
*
|
|
1893
|
+
* Returns the identifier holding the peeked rows, or `undefined` when this
|
|
1894
|
+
* mapper does not cache.
|
|
1895
|
+
*/
|
|
1896
|
+
function pushParamsAttribute(
|
|
1897
|
+
// tslint:disable-next-line:no-any
|
|
1898
|
+
dp: any,
|
|
1899
|
+
usage: DataSourceUsage,
|
|
1900
|
+
paramsProps: types.ObjectProperty[],
|
|
1901
|
+
memoDeps: types.Expression[],
|
|
1902
|
+
cacheDeclarations: types.Statement[]
|
|
1903
|
+
): string | undefined {
|
|
1904
|
+
const paramsMemo = types.callExpression(types.identifier('useMemo'), [
|
|
1905
|
+
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
1906
|
+
types.arrayExpression(memoDeps),
|
|
1907
|
+
])
|
|
1908
|
+
|
|
1909
|
+
// A state-bound sort deliberately suppresses `initialData` (see the comment at
|
|
1910
|
+
// the call site): the prefetch ran without sort params, so reusing anything on
|
|
1911
|
+
// mount would mask the current sort. The cache peek is `initialData` by
|
|
1912
|
+
// another name, so it has to be suppressed for exactly the same reason.
|
|
1913
|
+
const cache = usage.cache?.client && !usage.dynamicSort ? usage.cache : undefined
|
|
1914
|
+
|
|
1915
|
+
if (!cache) {
|
|
1916
|
+
dp.openingElement.attributes.push(
|
|
1917
|
+
types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo))
|
|
1918
|
+
)
|
|
1919
|
+
return undefined
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const hoisted = buildCachedParamsDeclarations({ index: usage.index, paramsMemo, cache })
|
|
1923
|
+
cacheDeclarations.push(...hoisted.declarations)
|
|
1924
|
+
|
|
1925
|
+
dp.openingElement.attributes.push(
|
|
1926
|
+
types.jsxAttribute(
|
|
1927
|
+
types.jsxIdentifier('params'),
|
|
1928
|
+
types.jsxExpressionContainer(types.identifier(hoisted.paramsIdentifier))
|
|
1929
|
+
)
|
|
1930
|
+
)
|
|
1931
|
+
|
|
1932
|
+
return hoisted.cachedIdentifier
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1829
1935
|
function updateDataProviderForPaginatedSearch(
|
|
1830
1936
|
dp: any,
|
|
1831
1937
|
usage: DataSourceUsage,
|
|
1832
1938
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
1833
|
-
fileName: string
|
|
1939
|
+
fileName: string,
|
|
1940
|
+
cacheDeclarations: types.Statement[]
|
|
1834
1941
|
): void {
|
|
1835
1942
|
const attrs = dp.openingElement.attributes
|
|
1836
1943
|
|
|
@@ -1885,17 +1992,7 @@ function updateDataProviderForPaginatedSearch(
|
|
|
1885
1992
|
}
|
|
1886
1993
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
1887
1994
|
|
|
1888
|
-
dp
|
|
1889
|
-
types.jsxAttribute(
|
|
1890
|
-
types.jsxIdentifier('params'),
|
|
1891
|
-
types.jsxExpressionContainer(
|
|
1892
|
-
types.callExpression(types.identifier('useMemo'), [
|
|
1893
|
-
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
1894
|
-
types.arrayExpression(memoDeps),
|
|
1895
|
-
])
|
|
1896
|
-
)
|
|
1897
|
-
)
|
|
1898
|
-
)
|
|
1995
|
+
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
1899
1996
|
|
|
1900
1997
|
// Add initialData. Skip the server-prefetched data entirely when a dynamic
|
|
1901
1998
|
// state-bound sort is active — the prefetch ran without sort parameters, so
|
|
@@ -1935,7 +2032,7 @@ function updateDataProviderForPaginatedSearch(
|
|
|
1935
2032
|
false,
|
|
1936
2033
|
true
|
|
1937
2034
|
),
|
|
1938
|
-
types.identifier('undefined')
|
|
2035
|
+
cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')
|
|
1939
2036
|
)
|
|
1940
2037
|
)
|
|
1941
2038
|
)
|
|
@@ -1976,7 +2073,9 @@ function updateDataProviderForPaginatedSearch(
|
|
|
1976
2073
|
)
|
|
1977
2074
|
|
|
1978
2075
|
// Add fetchData
|
|
1979
|
-
dp.openingElement.attributes.push(
|
|
2076
|
+
dp.openingElement.attributes.push(
|
|
2077
|
+
createFetchDataAttribute(fileName, usage.cache?.client ? usage.cache : undefined)
|
|
2078
|
+
)
|
|
1980
2079
|
|
|
1981
2080
|
// Add persistDataDuringLoading
|
|
1982
2081
|
dp.openingElement.attributes.push(
|
|
@@ -1991,7 +2090,8 @@ function updateDataProviderForPaginationOnly(
|
|
|
1991
2090
|
dp: any,
|
|
1992
2091
|
usage: DataSourceUsage,
|
|
1993
2092
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
1994
|
-
fileName: string
|
|
2093
|
+
fileName: string,
|
|
2094
|
+
cacheDeclarations: types.Statement[]
|
|
1995
2095
|
): void {
|
|
1996
2096
|
const attrs = dp.openingElement.attributes
|
|
1997
2097
|
|
|
@@ -2025,17 +2125,7 @@ function updateDataProviderForPaginationOnly(
|
|
|
2025
2125
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2026
2126
|
|
|
2027
2127
|
// Add params
|
|
2028
|
-
dp
|
|
2029
|
-
types.jsxAttribute(
|
|
2030
|
-
types.jsxIdentifier('params'),
|
|
2031
|
-
types.jsxExpressionContainer(
|
|
2032
|
-
types.callExpression(types.identifier('useMemo'), [
|
|
2033
|
-
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
2034
|
-
types.arrayExpression(memoDeps),
|
|
2035
|
-
])
|
|
2036
|
-
)
|
|
2037
|
-
)
|
|
2038
|
-
)
|
|
2128
|
+
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
2039
2129
|
|
|
2040
2130
|
// Add initialData. See paginated+search updater for why we skip prefetch
|
|
2041
2131
|
// reuse when a dynamic state-bound sort is active.
|
|
@@ -2059,7 +2149,7 @@ function updateDataProviderForPaginationOnly(
|
|
|
2059
2149
|
false,
|
|
2060
2150
|
true
|
|
2061
2151
|
),
|
|
2062
|
-
types.identifier('undefined')
|
|
2152
|
+
cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')
|
|
2063
2153
|
)
|
|
2064
2154
|
)
|
|
2065
2155
|
)
|
|
@@ -2086,7 +2176,9 @@ function updateDataProviderForPaginationOnly(
|
|
|
2086
2176
|
)
|
|
2087
2177
|
|
|
2088
2178
|
// Add fetchData
|
|
2089
|
-
dp.openingElement.attributes.push(
|
|
2179
|
+
dp.openingElement.attributes.push(
|
|
2180
|
+
createFetchDataAttribute(fileName, usage.cache?.client ? usage.cache : undefined)
|
|
2181
|
+
)
|
|
2090
2182
|
|
|
2091
2183
|
dp.openingElement.attributes.push(
|
|
2092
2184
|
types.jsxAttribute(
|
|
@@ -2100,7 +2192,8 @@ function updateDataProviderForSearchOnly(
|
|
|
2100
2192
|
dp: any,
|
|
2101
2193
|
usage: DataSourceUsage,
|
|
2102
2194
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2103
|
-
fileName: string
|
|
2195
|
+
fileName: string,
|
|
2196
|
+
cacheDeclarations: types.Statement[]
|
|
2104
2197
|
): void {
|
|
2105
2198
|
const attrs = dp.openingElement.attributes
|
|
2106
2199
|
|
|
@@ -2143,17 +2236,7 @@ function updateDataProviderForSearchOnly(
|
|
|
2143
2236
|
}
|
|
2144
2237
|
pushUrlSearchParamMemoDeps(memoDeps, usage)
|
|
2145
2238
|
|
|
2146
|
-
dp
|
|
2147
|
-
types.jsxAttribute(
|
|
2148
|
-
types.jsxIdentifier('params'),
|
|
2149
|
-
types.jsxExpressionContainer(
|
|
2150
|
-
types.callExpression(types.identifier('useMemo'), [
|
|
2151
|
-
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
2152
|
-
types.arrayExpression(memoDeps),
|
|
2153
|
-
])
|
|
2154
|
-
)
|
|
2155
|
-
)
|
|
2156
|
-
)
|
|
2239
|
+
const cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations)
|
|
2157
2240
|
|
|
2158
2241
|
// Add initialData. See paginated+search updater for why we skip prefetch
|
|
2159
2242
|
// reuse when a dynamic state-bound sort is active.
|
|
@@ -2173,7 +2256,7 @@ function updateDataProviderForSearchOnly(
|
|
|
2173
2256
|
false,
|
|
2174
2257
|
true
|
|
2175
2258
|
),
|
|
2176
|
-
types.identifier('undefined')
|
|
2259
|
+
cachedInitialData ? types.identifier(cachedInitialData) : types.identifier('undefined')
|
|
2177
2260
|
)
|
|
2178
2261
|
)
|
|
2179
2262
|
)
|
|
@@ -2197,7 +2280,9 @@ function updateDataProviderForSearchOnly(
|
|
|
2197
2280
|
)
|
|
2198
2281
|
|
|
2199
2282
|
// Add fetchData
|
|
2200
|
-
dp.openingElement.attributes.push(
|
|
2283
|
+
dp.openingElement.attributes.push(
|
|
2284
|
+
createFetchDataAttribute(fileName, usage.cache?.client ? usage.cache : undefined)
|
|
2285
|
+
)
|
|
2201
2286
|
|
|
2202
2287
|
dp.openingElement.attributes.push(
|
|
2203
2288
|
types.jsxAttribute(
|
|
@@ -2210,13 +2295,22 @@ function updateDataProviderForSearchOnly(
|
|
|
2210
2295
|
function updateDataProviderForPlain(dp: any, fileName: string, usage: DataSourceUsage): void {
|
|
2211
2296
|
const attrs = dp.openingElement.attributes
|
|
2212
2297
|
|
|
2298
|
+
const clientCache = usage.cache?.client ? usage.cache : undefined
|
|
2299
|
+
|
|
2213
2300
|
// Check if fetchData already exists
|
|
2214
2301
|
const hasFetchData = attrs.some(
|
|
2215
2302
|
(attr: any) => attr.type === 'JSXAttribute' && attr.name?.name === 'fetchData'
|
|
2216
2303
|
)
|
|
2217
2304
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2305
|
+
if (clientCache) {
|
|
2306
|
+
// A plain provider may already carry the UNMEMOIZED fetcher `utils.ts`
|
|
2307
|
+
// emits. Caching has to replace it rather than skip: the memoized form is
|
|
2308
|
+
// what both the cache peek and the in-flight tracking are attached to.
|
|
2309
|
+
dp.openingElement.attributes = attrs.filter(
|
|
2310
|
+
(attr: any) => !(attr.type === 'JSXAttribute' && attr.name?.name === 'fetchData')
|
|
2311
|
+
)
|
|
2312
|
+
dp.openingElement.attributes.push(createFetchDataAttribute(fileName, clientCache))
|
|
2313
|
+
} else if (!hasFetchData) {
|
|
2220
2314
|
attrs.push(createFetchDataAttribute(fileName))
|
|
2221
2315
|
}
|
|
2222
2316
|
|
|
@@ -2334,73 +2428,75 @@ function stabilizeDataProviderWithoutRepeater(dp: any): void {
|
|
|
2334
2428
|
)
|
|
2335
2429
|
}
|
|
2336
2430
|
|
|
2337
|
-
function createFetchDataAttribute(
|
|
2431
|
+
function createFetchDataAttribute(
|
|
2432
|
+
fileName: string,
|
|
2433
|
+
cache?: ResolvedDataSourceCache
|
|
2434
|
+
): types.JSXAttribute {
|
|
2435
|
+
const fetchCall = types.callExpression(types.identifier('fetch'), [
|
|
2436
|
+
types.templateLiteral(
|
|
2437
|
+
[
|
|
2438
|
+
types.templateElement({
|
|
2439
|
+
raw: `/api/${fileName}?`,
|
|
2440
|
+
cooked: `/api/${fileName}?`,
|
|
2441
|
+
}),
|
|
2442
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
2443
|
+
],
|
|
2444
|
+
[types.newExpression(types.identifier('URLSearchParams'), [types.identifier('params')])]
|
|
2445
|
+
),
|
|
2446
|
+
types.objectExpression([
|
|
2447
|
+
types.objectProperty(
|
|
2448
|
+
types.identifier('headers'),
|
|
2449
|
+
types.objectExpression([
|
|
2450
|
+
types.objectProperty(
|
|
2451
|
+
types.stringLiteral('Content-Type'),
|
|
2452
|
+
types.stringLiteral('application/json')
|
|
2453
|
+
),
|
|
2454
|
+
])
|
|
2455
|
+
),
|
|
2456
|
+
]),
|
|
2457
|
+
])
|
|
2458
|
+
|
|
2459
|
+
const parsedJson = types.callExpression(
|
|
2460
|
+
types.memberExpression(fetchCall, types.identifier('then')),
|
|
2461
|
+
[
|
|
2462
|
+
types.arrowFunctionExpression(
|
|
2463
|
+
[types.identifier('res')],
|
|
2464
|
+
types.callExpression(
|
|
2465
|
+
types.memberExpression(types.identifier('res'), types.identifier('json')),
|
|
2466
|
+
[]
|
|
2467
|
+
)
|
|
2468
|
+
),
|
|
2469
|
+
]
|
|
2470
|
+
)
|
|
2471
|
+
|
|
2472
|
+
// The uncached tail is left exactly as it has always been emitted, so a
|
|
2473
|
+
// project that does not cache produces byte-identical output.
|
|
2474
|
+
const uncachedTail = types.arrowFunctionExpression(
|
|
2475
|
+
[types.identifier('response')],
|
|
2476
|
+
types.optionalMemberExpression(
|
|
2477
|
+
types.identifier('response'),
|
|
2478
|
+
types.identifier('data'),
|
|
2479
|
+
false,
|
|
2480
|
+
true
|
|
2481
|
+
)
|
|
2482
|
+
)
|
|
2483
|
+
|
|
2484
|
+
const networkChain = types.callExpression(
|
|
2485
|
+
types.memberExpression(parsedJson, types.identifier('then')),
|
|
2486
|
+
[cache ? buildCacheStoreThen(cache) : uncachedTail]
|
|
2487
|
+
)
|
|
2488
|
+
|
|
2489
|
+
const arrow = cache
|
|
2490
|
+
? types.arrowFunctionExpression(
|
|
2491
|
+
[types.identifier('params')],
|
|
2492
|
+
buildCachedFetchBody(networkChain, cache)
|
|
2493
|
+
)
|
|
2494
|
+
: types.arrowFunctionExpression([types.identifier('params')], networkChain)
|
|
2495
|
+
|
|
2338
2496
|
return types.jsxAttribute(
|
|
2339
2497
|
types.jsxIdentifier('fetchData'),
|
|
2340
2498
|
types.jsxExpressionContainer(
|
|
2341
|
-
types.callExpression(types.identifier('useCallback'), [
|
|
2342
|
-
types.arrowFunctionExpression(
|
|
2343
|
-
[types.identifier('params')],
|
|
2344
|
-
types.callExpression(
|
|
2345
|
-
types.memberExpression(
|
|
2346
|
-
types.callExpression(
|
|
2347
|
-
types.memberExpression(
|
|
2348
|
-
types.callExpression(types.identifier('fetch'), [
|
|
2349
|
-
types.templateLiteral(
|
|
2350
|
-
[
|
|
2351
|
-
types.templateElement({
|
|
2352
|
-
raw: `/api/${fileName}?`,
|
|
2353
|
-
cooked: `/api/${fileName}?`,
|
|
2354
|
-
}),
|
|
2355
|
-
types.templateElement({ raw: '', cooked: '' }),
|
|
2356
|
-
],
|
|
2357
|
-
[
|
|
2358
|
-
types.newExpression(types.identifier('URLSearchParams'), [
|
|
2359
|
-
types.identifier('params'),
|
|
2360
|
-
]),
|
|
2361
|
-
]
|
|
2362
|
-
),
|
|
2363
|
-
types.objectExpression([
|
|
2364
|
-
types.objectProperty(
|
|
2365
|
-
types.identifier('headers'),
|
|
2366
|
-
types.objectExpression([
|
|
2367
|
-
types.objectProperty(
|
|
2368
|
-
types.stringLiteral('Content-Type'),
|
|
2369
|
-
types.stringLiteral('application/json')
|
|
2370
|
-
),
|
|
2371
|
-
])
|
|
2372
|
-
),
|
|
2373
|
-
]),
|
|
2374
|
-
]),
|
|
2375
|
-
types.identifier('then')
|
|
2376
|
-
),
|
|
2377
|
-
[
|
|
2378
|
-
types.arrowFunctionExpression(
|
|
2379
|
-
[types.identifier('res')],
|
|
2380
|
-
types.callExpression(
|
|
2381
|
-
types.memberExpression(types.identifier('res'), types.identifier('json')),
|
|
2382
|
-
[]
|
|
2383
|
-
)
|
|
2384
|
-
),
|
|
2385
|
-
]
|
|
2386
|
-
),
|
|
2387
|
-
types.identifier('then')
|
|
2388
|
-
),
|
|
2389
|
-
[
|
|
2390
|
-
types.arrowFunctionExpression(
|
|
2391
|
-
[types.identifier('response')],
|
|
2392
|
-
types.optionalMemberExpression(
|
|
2393
|
-
types.identifier('response'),
|
|
2394
|
-
types.identifier('data'),
|
|
2395
|
-
false,
|
|
2396
|
-
true
|
|
2397
|
-
)
|
|
2398
|
-
),
|
|
2399
|
-
]
|
|
2400
|
-
)
|
|
2401
|
-
),
|
|
2402
|
-
types.arrayExpression([]),
|
|
2403
|
-
])
|
|
2499
|
+
types.callExpression(types.identifier('useCallback'), [arrow, types.arrayExpression([])])
|
|
2404
2500
|
)
|
|
2405
2501
|
)
|
|
2406
2502
|
}
|
|
@@ -2697,11 +2793,114 @@ function wirePaginationButtons(
|
|
|
2697
2793
|
}
|
|
2698
2794
|
}
|
|
2699
2795
|
|
|
2796
|
+
/**
|
|
2797
|
+
* Applies the SERVER cache to the generated data-source modules, once, after
|
|
2798
|
+
* every provider has been visited.
|
|
2799
|
+
*
|
|
2800
|
+
* A post-pass rather than a parameter to `ensureAPIRouteExists` because four
|
|
2801
|
+
* different places race to create `utils/data-sources/<file>.js` — the
|
|
2802
|
+
* getStaticProps path, the component path, the client-API path and this plugin —
|
|
2803
|
+
* and whichever wins would otherwise decide whether the file is cached. Since
|
|
2804
|
+
* the generator is deterministic, regenerating the winner with the cache options
|
|
2805
|
+
* is order-independent and cannot half-apply.
|
|
2806
|
+
*
|
|
2807
|
+
* The config is merged per FILE, not per mapper: one module serves every mapper
|
|
2808
|
+
* reading that `(type, table, dataSourceId)` triple, so two mappers on two pages
|
|
2809
|
+
* have to agree — and the fresher one wins (see `mergeServerCacheOptions`).
|
|
2810
|
+
*/
|
|
2811
|
+
function applyServerCacheToDataSourceModules(
|
|
2812
|
+
// tslint:disable-next-line:no-any
|
|
2813
|
+
extractedResources: any,
|
|
2814
|
+
registry: StateRegistry,
|
|
2815
|
+
// tslint:disable-next-line:no-any
|
|
2816
|
+
dataSources: Record<string, any>,
|
|
2817
|
+
transformOptions: EcommerceProductTransformOptions = {},
|
|
2818
|
+
// Accumulated across every page/component this generation has processed. The
|
|
2819
|
+
// module is shared project-wide, so merging only the CURRENT page's usages
|
|
2820
|
+
// would let page ORDER decide the TTL: a page asking for 300s processed after
|
|
2821
|
+
// one asking for 60s would widen the window the second page was configured
|
|
2822
|
+
// with. Carried on the shared options object so the merge stays global.
|
|
2823
|
+
accumulator?: Map<string, { usage: DataSourceUsage; entries: DataSourceServerCacheOptions[] }>
|
|
2824
|
+
): void {
|
|
2825
|
+
const byFileName =
|
|
2826
|
+
accumulator ??
|
|
2827
|
+
new Map<string, { usage: DataSourceUsage; entries: DataSourceServerCacheOptions[] }>()
|
|
2828
|
+
|
|
2829
|
+
registry.usages.forEach((usage) => {
|
|
2830
|
+
if (!usage.cache?.server) {
|
|
2831
|
+
return
|
|
2832
|
+
}
|
|
2833
|
+
const fileName = generateSafeFileName(
|
|
2834
|
+
usage.resourceDefinition.dataSourceType,
|
|
2835
|
+
usage.resourceDefinition.tableName,
|
|
2836
|
+
usage.resourceDefinition.dataSourceId
|
|
2837
|
+
)
|
|
2838
|
+
const bucket = byFileName.get(fileName) || { usage, entries: [] }
|
|
2839
|
+
bucket.entries.push({
|
|
2840
|
+
scope: usage.cache.scope,
|
|
2841
|
+
ttlSeconds: usage.cache.ttlSeconds,
|
|
2842
|
+
sMaxAge: usage.cache.sMaxAge,
|
|
2843
|
+
staleWhileRevalidate: usage.cache.staleWhileRevalidate,
|
|
2844
|
+
})
|
|
2845
|
+
byFileName.set(fileName, bucket)
|
|
2846
|
+
})
|
|
2847
|
+
|
|
2848
|
+
byFileName.forEach((bucket, fileName) => {
|
|
2849
|
+
const cacheOptions = mergeServerCacheOptions(bucket.entries)
|
|
2850
|
+
if (!cacheOptions) {
|
|
2851
|
+
return
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
const dataSource = dataSources[bucket.usage.resourceDefinition.dataSourceId]
|
|
2855
|
+
if (!dataSource) {
|
|
2856
|
+
return
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
const tableName = bucket.usage.resourceDefinition.tableName || 'data'
|
|
2860
|
+
const utilsKey = `utils/${fileName}`
|
|
2861
|
+
const apiKey = `api/${fileName}`
|
|
2862
|
+
|
|
2863
|
+
try {
|
|
2864
|
+
if (extractedResources[utilsKey]) {
|
|
2865
|
+
extractedResources[utilsKey].content = generateDataSourceFetcherWithCore(
|
|
2866
|
+
dataSource,
|
|
2867
|
+
tableName,
|
|
2868
|
+
false,
|
|
2869
|
+
transformOptions,
|
|
2870
|
+
cacheOptions
|
|
2871
|
+
)
|
|
2872
|
+
return
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// The variant that inlines the whole fetcher straight into `pages/api`
|
|
2876
|
+
// instead of re-exporting a utils module. Detected by content rather than
|
|
2877
|
+
// by which code path produced it, so a future fifth emitter is covered too.
|
|
2878
|
+
const apiEntry = extractedResources[apiKey]
|
|
2879
|
+
if (
|
|
2880
|
+
apiEntry &&
|
|
2881
|
+
typeof apiEntry.content === 'string' &&
|
|
2882
|
+
apiEntry.content.includes('async function handler')
|
|
2883
|
+
) {
|
|
2884
|
+
apiEntry.content = generateDataSourceFetcherWithCore(
|
|
2885
|
+
dataSource,
|
|
2886
|
+
tableName,
|
|
2887
|
+
true,
|
|
2888
|
+
transformOptions,
|
|
2889
|
+
cacheOptions
|
|
2890
|
+
)
|
|
2891
|
+
}
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
// Caching must never be the reason a project fails to generate; the
|
|
2894
|
+
// uncached module that is already in place stays exactly as it was.
|
|
2895
|
+
}
|
|
2896
|
+
})
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2700
2899
|
function ensureAPIRouteExists(
|
|
2701
2900
|
extractedResources: any,
|
|
2702
2901
|
usage: DataSourceUsage,
|
|
2703
2902
|
dataSources: Record<string, any>,
|
|
2704
|
-
|
|
2903
|
+
transformOptions: EcommerceProductTransformOptions = {}
|
|
2705
2904
|
): void {
|
|
2706
2905
|
// Generate file name for the API route
|
|
2707
2906
|
const fileName = generateSafeFileName(
|
|
@@ -2721,7 +2920,7 @@ function ensureAPIRouteExists(
|
|
|
2721
2920
|
dataSource,
|
|
2722
2921
|
usage.resourceDefinition.tableName || 'data',
|
|
2723
2922
|
false,
|
|
2724
|
-
|
|
2923
|
+
transformOptions
|
|
2725
2924
|
)
|
|
2726
2925
|
|
|
2727
2926
|
extractedResources[`utils/${fileName}`] = {
|