@teleporthq/teleport-plugin-next-data-source 0.43.55 → 0.43.59

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 (110) hide show
  1. package/__tests__/array-mapper-cache-wiring.test.ts +425 -0
  2. package/__tests__/data-source-cache-wrapping.test.ts +152 -0
  3. package/__tests__/ecommerce-product-allow-backorders.test.ts +156 -0
  4. package/__tests__/ecommerce-product-model.test.ts +179 -0
  5. package/__tests__/ecommerce-product-storefront-tax.test.ts +118 -0
  6. package/dist/cjs/cache/ast.d.ts +71 -0
  7. package/dist/cjs/cache/ast.d.ts.map +1 -0
  8. package/dist/cjs/cache/ast.js +229 -0
  9. package/dist/cjs/cache/ast.js.map +1 -0
  10. package/dist/cjs/cache/config.d.ts +22 -0
  11. package/dist/cjs/cache/config.d.ts.map +1 -0
  12. package/dist/cjs/cache/config.js +67 -0
  13. package/dist/cjs/cache/config.js.map +1 -0
  14. package/dist/cjs/cache/types.d.ts +31 -0
  15. package/dist/cjs/cache/types.d.ts.map +1 -0
  16. package/dist/cjs/cache/types.js +3 -0
  17. package/dist/cjs/cache/types.js.map +1 -0
  18. package/dist/cjs/data-source-fetchers.d.ts +5 -3
  19. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  20. package/dist/cjs/data-source-fetchers.js +51 -8
  21. package/dist/cjs/data-source-fetchers.js.map +1 -1
  22. package/dist/cjs/fetchers/teleport.d.ts +2 -2
  23. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  24. package/dist/cjs/fetchers/teleport.js +3 -2
  25. package/dist/cjs/fetchers/teleport.js.map +1 -1
  26. package/dist/cjs/index.d.ts +2 -0
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +14 -9
  29. package/dist/cjs/index.js.map +1 -1
  30. package/dist/cjs/loading-state.d.ts.map +1 -1
  31. package/dist/cjs/loading-state.js +74 -2
  32. package/dist/cjs/loading-state.js.map +1 -1
  33. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  34. package/dist/cjs/pagination-plugin.js +204 -64
  35. package/dist/cjs/pagination-plugin.js.map +1 -1
  36. package/dist/cjs/transformations/ecommerce-product.d.ts +27 -1
  37. package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
  38. package/dist/cjs/transformations/ecommerce-product.js +8 -3
  39. package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
  40. package/dist/cjs/transformations/index.d.ts +14 -2
  41. package/dist/cjs/transformations/index.d.ts.map +1 -1
  42. package/dist/cjs/transformations/index.js +37 -3
  43. package/dist/cjs/transformations/index.js.map +1 -1
  44. package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
  45. package/dist/cjs/transformations/shared-utils.js +1 -1
  46. package/dist/cjs/transformations/shared-utils.js.map +1 -1
  47. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  48. package/dist/cjs/utils.d.ts +4 -3
  49. package/dist/cjs/utils.d.ts.map +1 -1
  50. package/dist/cjs/utils.js +6 -4
  51. package/dist/cjs/utils.js.map +1 -1
  52. package/dist/esm/cache/ast.d.ts +71 -0
  53. package/dist/esm/cache/ast.d.ts.map +1 -0
  54. package/dist/esm/cache/ast.js +196 -0
  55. package/dist/esm/cache/ast.js.map +1 -0
  56. package/dist/esm/cache/config.d.ts +22 -0
  57. package/dist/esm/cache/config.d.ts.map +1 -0
  58. package/dist/esm/cache/config.js +62 -0
  59. package/dist/esm/cache/config.js.map +1 -0
  60. package/dist/esm/cache/types.d.ts +31 -0
  61. package/dist/esm/cache/types.d.ts.map +1 -0
  62. package/dist/esm/cache/types.js +2 -0
  63. package/dist/esm/cache/types.js.map +1 -0
  64. package/dist/esm/data-source-fetchers.d.ts +5 -3
  65. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  66. package/dist/esm/data-source-fetchers.js +51 -8
  67. package/dist/esm/data-source-fetchers.js.map +1 -1
  68. package/dist/esm/fetchers/teleport.d.ts +2 -2
  69. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  70. package/dist/esm/fetchers/teleport.js +3 -2
  71. package/dist/esm/fetchers/teleport.js.map +1 -1
  72. package/dist/esm/index.d.ts +2 -0
  73. package/dist/esm/index.d.ts.map +1 -1
  74. package/dist/esm/index.js +12 -8
  75. package/dist/esm/index.js.map +1 -1
  76. package/dist/esm/loading-state.d.ts.map +1 -1
  77. package/dist/esm/loading-state.js +74 -2
  78. package/dist/esm/loading-state.js.map +1 -1
  79. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  80. package/dist/esm/pagination-plugin.js +205 -65
  81. package/dist/esm/pagination-plugin.js.map +1 -1
  82. package/dist/esm/transformations/ecommerce-product.d.ts +27 -1
  83. package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
  84. package/dist/esm/transformations/ecommerce-product.js +8 -3
  85. package/dist/esm/transformations/ecommerce-product.js.map +1 -1
  86. package/dist/esm/transformations/index.d.ts +14 -2
  87. package/dist/esm/transformations/index.d.ts.map +1 -1
  88. package/dist/esm/transformations/index.js +36 -3
  89. package/dist/esm/transformations/index.js.map +1 -1
  90. package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
  91. package/dist/esm/transformations/shared-utils.js +1 -1
  92. package/dist/esm/transformations/shared-utils.js.map +1 -1
  93. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  94. package/dist/esm/utils.d.ts +4 -3
  95. package/dist/esm/utils.d.ts.map +1 -1
  96. package/dist/esm/utils.js +6 -4
  97. package/dist/esm/utils.js.map +1 -1
  98. package/package.json +5 -5
  99. package/src/cache/ast.ts +265 -0
  100. package/src/cache/config.ts +79 -0
  101. package/src/cache/types.ts +31 -0
  102. package/src/data-source-fetchers.ts +66 -12
  103. package/src/fetchers/teleport.ts +3 -3
  104. package/src/index.ts +10 -4
  105. package/src/loading-state.ts +65 -2
  106. package/src/pagination-plugin.ts +315 -116
  107. package/src/transformations/ecommerce-product.ts +161 -5
  108. package/src/transformations/index.ts +43 -4
  109. package/src/transformations/shared-utils.ts +14 -0
  110. package/src/utils.ts +10 -5
@@ -67,9 +67,12 @@ var teleport_shared_1 = require("@teleporthq/teleport-shared");
67
67
  var teleport_plugin_common_1 = require("@teleporthq/teleport-plugin-common");
68
68
  var utils_1 = require("./utils");
69
69
  var data_source_fetchers_1 = require("./data-source-fetchers");
70
+ var transformations_1 = require("./transformations");
70
71
  var sort_utils_1 = require("./sort-utils");
71
72
  var filter_utils_1 = require("./filter-utils");
72
73
  var loading_state_1 = require("./loading-state");
74
+ var config_1 = require("./cache/config");
75
+ var ast_1 = require("./cache/ast");
73
76
  // tslint:disable-next-line:no-any
74
77
  function parseSearchDefaultValue(raw) {
75
78
  if (!raw || typeof raw !== 'object') {
@@ -445,6 +448,7 @@ function buildStateRegistry(uidlNode) {
445
448
  filterStateIds: filterStateIds,
446
449
  filterPropIds: filterPropIds,
447
450
  filterUrlSearchParamKeys: filterUrlSearchParamKeys,
451
+ cache: (0, config_1.resolveCacheFromRepeaterContent)(content.cache, parentDataSource.resourceDef),
448
452
  category: 'plain',
449
453
  };
450
454
  // Determine category
@@ -578,9 +582,9 @@ function pushSearchUrlSyncEffects(effectStatements, usage, vars) {
578
582
  // ==================== MAIN PLUGIN ====================
579
583
  var createNextArrayMapperPaginationPlugin = function () {
580
584
  var paginationPlugin = function (structure) { return __awaiter(void 0, void 0, void 0, function () {
581
- var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, registry, getStaticPropsChunk, isPage, stateDeclarations, effectStatements, needsUseRouter, hasRouterDecl, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, dataProvidersWithoutRepeaters, searchInputs, searchEnabledUsages, paginationNodes, paginatedUsages;
582
- var _a;
583
- return __generator(this, function (_b) {
585
+ var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, registry, getStaticPropsChunk, isPage, stateDeclarations, effectStatements, needsUseRouter, hasRouterDecl, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, cacheDeclarations, sharedOptions, cachedScopes, dataProvidersWithoutRepeaters, searchInputs, searchEnabledUsages, paginationNodes, paginatedUsages;
586
+ var _a, _b;
587
+ return __generator(this, function (_c) {
584
588
  uidl = structure.uidl, chunks = structure.chunks, dependencies = structure.dependencies, options = structure.options;
585
589
  componentChunk = chunks.find(function (chunk) { return chunk.name === 'jsx-component'; });
586
590
  if (!componentChunk || componentChunk.type !== teleport_types_1.ChunkType.AST) {
@@ -942,8 +946,9 @@ var createNextArrayMapperPaginationPlugin = function () {
942
946
  });
943
947
  usageIndexByDataSourceId = new Map();
944
948
  loadingStateDeclarations = [];
949
+ cacheDeclarations = [];
945
950
  dataProvidersWithRepeaters.forEach(function (dp) {
946
- var _a, _b, _c;
951
+ var _a, _b;
947
952
  var nameAttr = dp.openingElement.attributes.find(function (attr) { return attr.type === 'JSXAttribute' && attr.name.name === 'name'; });
948
953
  if (!((_b = (_a = nameAttr === null || nameAttr === void 0 ? void 0 : nameAttr.value) === null || _a === void 0 ? void 0 : _a.expression) === null || _b === void 0 ? void 0 : _b.value)) {
949
954
  return;
@@ -961,13 +966,13 @@ var createNextArrayMapperPaginationPlugin = function () {
961
966
  var fileName = (0, utils_1.generateSafeFileName)(usage.resourceDefinition.dataSourceType, usage.resourceDefinition.tableName, usage.resourceDefinition.dataSourceId);
962
967
  // Update DataProvider based on category
963
968
  if (usage.category === 'paginated+search') {
964
- updateDataProviderForPaginatedSearch(dp, usage, vars, fileName);
969
+ updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations);
965
970
  }
966
971
  else if (usage.category === 'paginated-only') {
967
- updateDataProviderForPaginationOnly(dp, usage, vars, fileName);
972
+ updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations);
968
973
  }
969
974
  else if (usage.category === 'search-only') {
970
- updateDataProviderForSearchOnly(dp, usage, vars, fileName);
975
+ updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations);
971
976
  }
972
977
  else if (usage.category === 'plain') {
973
978
  updateDataProviderForPlain(dp, fileName, usage);
@@ -981,12 +986,37 @@ var createNextArrayMapperPaginationPlugin = function () {
981
986
  loadingStateDeclarations.push.apply(loadingStateDeclarations, (0, loading_state_1.buildLoadingStateDeclarations)(loadingVars));
982
987
  }
983
988
  // Create API route for all categories (including 'plain' for components)
984
- ensureAPIRouteExists(options.extractedResources, usage, options.dataSources, (_c = options.ecommerceSettings) === null || _c === void 0 ? void 0 : _c.categories);
989
+ ensureAPIRouteExists(options.extractedResources, usage, options.dataSources, (0, transformations_1.buildProductTransformOptions)(options));
985
990
  });
991
+ sharedOptions = options;
992
+ if (!sharedOptions.serverCacheByFile) {
993
+ sharedOptions.serverCacheByFile = new Map();
994
+ }
995
+ applyServerCacheToDataSourceModules(options.extractedResources, registry, options.dataSources, (0, transformations_1.buildProductTransformOptions)(options), sharedOptions.serverCacheByFile);
986
996
  // Declare the loading-state hooks alongside the other data-source state, at
987
997
  // the top of the component body. Unconditional and in a fixed order, so the
988
998
  // hook order stays stable across renders.
989
999
  loadingStateDeclarations.reverse().forEach(function (s) { return blockStatement.body.unshift(s); });
1000
+ cachedScopes = Array.from(new Set(registry.usages
1001
+ .filter(function (usage) { var _a; return (_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client; })
1002
+ .map(function (usage) { return usage.cache.scope; })));
1003
+ if (cacheDeclarations.length || cachedScopes.length) {
1004
+ // One effect per page covering every scope on it: flips the hydration
1005
+ // latch, then asks once whether any of this page's tables changed while
1006
+ // the visitor was away. Without it a browser serving everything from its
1007
+ // own cache would never learn about a write at all. It has to land
1008
+ // BEFORE the component's `return` — a plain `push` at this point in the
1009
+ // pipeline appends after it, where React never registers the hook.
1010
+ teleport_shared_1.ASTStatementOrder.insertStatementsBeforeReturn(blockStatement, [
1011
+ (0, ast_1.buildCacheHydrationEffect)(cachedScopes),
1012
+ ]);
1013
+ // `ds_N_params` / `ds_N_cached` read the `ds_N_state` hooks declared
1014
+ // above, so they are placed at the earliest point where everything they
1015
+ // reference is already in scope — never blindly at the top, which is
1016
+ // exactly the `Cannot access 'ds_0_state' before initialization` crash.
1017
+ teleport_shared_1.ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, cacheDeclarations);
1018
+ (0, ast_1.registerCacheClientImports)(dependencies, (_a = uidl.outputOptions) === null || _a === void 0 ? void 0 : _a.folderPath);
1019
+ }
990
1020
  dataProvidersWithoutRepeaters = dataProviders.filter(function (dp) {
991
1021
  var hasRepeater = findArrayMapperRenderPropInDataProvider(dp) !== undefined;
992
1022
  return !hasRepeater;
@@ -1018,7 +1048,7 @@ var createNextArrayMapperPaginationPlugin = function () {
1018
1048
  });
1019
1049
  // STEP 6: Update getStaticProps if this is a page
1020
1050
  if (isPage) {
1021
- updateGetStaticProps(chunks, registry, dependencies, (_a = uidl.outputOptions) === null || _a === void 0 ? void 0 : _a.folderPath);
1051
+ updateGetStaticProps(chunks, registry, dependencies, (_b = uidl.outputOptions) === null || _b === void 0 ? void 0 : _b.folderPath);
1022
1052
  }
1023
1053
  return [2 /*return*/, structure];
1024
1054
  });
@@ -1187,7 +1217,40 @@ function findAllPaginationNodesInJSX(blockStatement) {
1187
1217
  traverse(blockStatement);
1188
1218
  return results;
1189
1219
  }
1190
- function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName) {
1220
+ /**
1221
+ * Emits the provider's `params` and, for a cached mapper, the peek beside it.
1222
+ *
1223
+ * An uncached mapper keeps the inline `params={useMemo(...)}` it has always had,
1224
+ * byte for byte. A cached one hoists that memo into a component const so the
1225
+ * peek can depend on the same object identity instead of rebuilding it.
1226
+ *
1227
+ * Returns the identifier holding the peeked rows, or `undefined` when this
1228
+ * mapper does not cache.
1229
+ */
1230
+ function pushParamsAttribute(
1231
+ // tslint:disable-next-line:no-any
1232
+ dp, usage, paramsProps, memoDeps, cacheDeclarations) {
1233
+ var _a;
1234
+ var paramsMemo = types.callExpression(types.identifier('useMemo'), [
1235
+ types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
1236
+ types.arrayExpression(memoDeps),
1237
+ ]);
1238
+ // A state-bound sort deliberately suppresses `initialData` (see the comment at
1239
+ // the call site): the prefetch ran without sort params, so reusing anything on
1240
+ // mount would mask the current sort. The cache peek is `initialData` by
1241
+ // another name, so it has to be suppressed for exactly the same reason.
1242
+ var cache = ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) && !usage.dynamicSort ? usage.cache : undefined;
1243
+ if (!cache) {
1244
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo)));
1245
+ return undefined;
1246
+ }
1247
+ var hoisted = (0, ast_1.buildCachedParamsDeclarations)({ index: usage.index, paramsMemo: paramsMemo, cache: cache });
1248
+ cacheDeclarations.push.apply(cacheDeclarations, hoisted.declarations);
1249
+ dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.identifier(hoisted.paramsIdentifier))));
1250
+ return hoisted.cachedIdentifier;
1251
+ }
1252
+ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName, cacheDeclarations) {
1253
+ var _a;
1191
1254
  var attrs = dp.openingElement.attributes;
1192
1255
  // Remove existing params, key, initialData, fetchData, persistDataDuringLoading
1193
1256
  dp.openingElement.attributes = attrs.filter(function (attr) {
@@ -1216,10 +1279,7 @@ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName) {
1216
1279
  (0, filter_utils_1.pushStateIdsAsDeps)(memoDeps, seenDeps, usage.dynamicSort.depStateIds);
1217
1280
  }
1218
1281
  pushUrlSearchParamMemoDeps(memoDeps, usage);
1219
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.callExpression(types.identifier('useMemo'), [
1220
- types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
1221
- types.arrayExpression(memoDeps),
1222
- ]))));
1282
+ var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1223
1283
  // Add initialData. Skip the server-prefetched data entirely when a dynamic
1224
1284
  // state-bound sort is active — the prefetch ran without sort parameters, so
1225
1285
  // reusing it would mask the current sort state AND cause DataProvider to skip
@@ -1228,7 +1288,7 @@ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName) {
1228
1288
  // refetch via the useMemo params dependency chain.
1229
1289
  if (!usage.dynamicSort) {
1230
1290
  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);
1231
- 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), types.identifier('undefined')))));
1291
+ 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')))));
1232
1292
  }
1233
1293
  // Add key. Sort is intentionally NOT part of the key — a key change would
1234
1294
  // remount the DataProvider, and a fresh mount re-arms the internal
@@ -1247,11 +1307,12 @@ function updateDataProviderForPaginatedSearch(dp, usage, vars, fileName) {
1247
1307
  types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('debouncedQuery')),
1248
1308
  ]))));
1249
1309
  // Add fetchData
1250
- dp.openingElement.attributes.push(createFetchDataAttribute(fileName));
1310
+ dp.openingElement.attributes.push(createFetchDataAttribute(fileName, ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined));
1251
1311
  // Add persistDataDuringLoading
1252
1312
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1253
1313
  }
1254
- function updateDataProviderForPaginationOnly(dp, usage, vars, fileName) {
1314
+ function updateDataProviderForPaginationOnly(dp, usage, vars, fileName, cacheDeclarations) {
1315
+ var _a;
1255
1316
  var attrs = dp.openingElement.attributes;
1256
1317
  dp.openingElement.attributes = attrs.filter(function (attr) {
1257
1318
  var _a;
@@ -1276,14 +1337,11 @@ function updateDataProviderForPaginationOnly(dp, usage, vars, fileName) {
1276
1337
  }
1277
1338
  pushUrlSearchParamMemoDeps(memoDeps, usage);
1278
1339
  // Add params
1279
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.callExpression(types.identifier('useMemo'), [
1280
- types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
1281
- types.arrayExpression(memoDeps),
1282
- ]))));
1340
+ var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1283
1341
  // Add initialData. See paginated+search updater for why we skip prefetch
1284
1342
  // reuse when a dynamic state-bound sort is active.
1285
1343
  if (!usage.dynamicSort) {
1286
- 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), types.identifier('undefined')))));
1344
+ 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')))));
1287
1345
  }
1288
1346
  // Add key — sort is intentionally NOT included; see paginated+search updater.
1289
1347
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('key'), types.jsxExpressionContainer(types.templateLiteral([
@@ -1294,10 +1352,11 @@ function updateDataProviderForPaginationOnly(dp, usage, vars, fileName) {
1294
1352
  types.templateElement({ raw: '', cooked: '' }),
1295
1353
  ], [types.identifier(vars.pageStateVar)]))));
1296
1354
  // Add fetchData
1297
- dp.openingElement.attributes.push(createFetchDataAttribute(fileName));
1355
+ dp.openingElement.attributes.push(createFetchDataAttribute(fileName, ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined));
1298
1356
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1299
1357
  }
1300
- function updateDataProviderForSearchOnly(dp, usage, vars, fileName) {
1358
+ function updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations) {
1359
+ var _a;
1301
1360
  var attrs = dp.openingElement.attributes;
1302
1361
  dp.openingElement.attributes = attrs.filter(function (attr) {
1303
1362
  var _a;
@@ -1323,14 +1382,11 @@ function updateDataProviderForSearchOnly(dp, usage, vars, fileName) {
1323
1382
  (0, filter_utils_1.pushStateIdsAsDeps)(memoDeps, seenDeps, usage.dynamicSort.depStateIds);
1324
1383
  }
1325
1384
  pushUrlSearchParamMemoDeps(memoDeps, usage);
1326
- dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.callExpression(types.identifier('useMemo'), [
1327
- types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
1328
- types.arrayExpression(memoDeps),
1329
- ]))));
1385
+ var cachedInitialData = pushParamsAttribute(dp, usage, paramsProps, memoDeps, cacheDeclarations);
1330
1386
  // Add initialData. See paginated+search updater for why we skip prefetch
1331
1387
  // reuse when a dynamic state-bound sort is active.
1332
1388
  if (!usage.dynamicSort) {
1333
- 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), types.identifier('undefined')))));
1389
+ 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')))));
1334
1390
  }
1335
1391
  // Add key — sort is intentionally NOT included; see paginated+search updater.
1336
1392
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('key'), types.jsxExpressionContainer(types.templateLiteral([
@@ -1338,16 +1394,23 @@ function updateDataProviderForSearchOnly(dp, usage, vars, fileName) {
1338
1394
  types.templateElement({ raw: '', cooked: '' }),
1339
1395
  ], [types.identifier(vars.debouncedSearchQueryVar)]))));
1340
1396
  // Add fetchData
1341
- dp.openingElement.attributes.push(createFetchDataAttribute(fileName));
1397
+ dp.openingElement.attributes.push(createFetchDataAttribute(fileName, ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined));
1342
1398
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1343
1399
  }
1344
1400
  function updateDataProviderForPlain(dp, fileName, usage) {
1345
- var _a, _b, _c;
1401
+ var _a, _b, _c, _d;
1346
1402
  var attrs = dp.openingElement.attributes;
1403
+ var clientCache = ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined;
1347
1404
  // Check if fetchData already exists
1348
1405
  var hasFetchData = attrs.some(function (attr) { var _a; return attr.type === 'JSXAttribute' && ((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name) === 'fetchData'; });
1349
- // If no fetchData, add it
1350
- if (!hasFetchData) {
1406
+ if (clientCache) {
1407
+ // A plain provider may already carry the UNMEMOIZED fetcher `utils.ts`
1408
+ // emits. Caching has to replace it rather than skip: the memoized form is
1409
+ // what both the cache peek and the in-flight tracking are attached to.
1410
+ dp.openingElement.attributes = attrs.filter(function (attr) { var _a; return !(attr.type === 'JSXAttribute' && ((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name) === 'fetchData'); });
1411
+ dp.openingElement.attributes.push(createFetchDataAttribute(fileName, clientCache));
1412
+ }
1413
+ else if (!hasFetchData) {
1351
1414
  attrs.push(createFetchDataAttribute(fileName));
1352
1415
  }
1353
1416
  // Check if persistDataDuringLoading already exists
@@ -1363,14 +1426,14 @@ function updateDataProviderForPlain(dp, fileName, usage) {
1363
1426
  }
1364
1427
  var paramsAttr = attrs[paramsAttrIndex];
1365
1428
  // Check if params is already wrapped in useMemo
1366
- if (((_a = paramsAttr.value) === null || _a === void 0 ? void 0 : _a.type) === 'JSXExpressionContainer' &&
1429
+ if (((_b = paramsAttr.value) === null || _b === void 0 ? void 0 : _b.type) === 'JSXExpressionContainer' &&
1367
1430
  paramsAttr.value.expression.type === 'CallExpression' &&
1368
- ((_b = paramsAttr.value.expression.callee) === null || _b === void 0 ? void 0 : _b.name) === 'useMemo') {
1431
+ ((_c = paramsAttr.value.expression.callee) === null || _c === void 0 ? void 0 : _c.name) === 'useMemo') {
1369
1432
  return;
1370
1433
  }
1371
1434
  // Get the current params value expression
1372
1435
  var paramsExpression = null;
1373
- if (((_c = paramsAttr.value) === null || _c === void 0 ? void 0 : _c.type) === 'JSXExpressionContainer') {
1436
+ if (((_d = paramsAttr.value) === null || _d === void 0 ? void 0 : _d.type) === 'JSXExpressionContainer') {
1374
1437
  paramsExpression = paramsAttr.value.expression;
1375
1438
  }
1376
1439
  if (!paramsExpression) {
@@ -1422,32 +1485,32 @@ function stabilizeDataProviderWithoutRepeater(dp) {
1422
1485
  // Replace the params attribute
1423
1486
  attrs[paramsAttrIndex] = types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(memoizedParams));
1424
1487
  }
1425
- function createFetchDataAttribute(fileName) {
1426
- return types.jsxAttribute(types.jsxIdentifier('fetchData'), types.jsxExpressionContainer(types.callExpression(types.identifier('useCallback'), [
1427
- types.arrowFunctionExpression([types.identifier('params')], types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.callExpression(types.identifier('fetch'), [
1428
- types.templateLiteral([
1429
- types.templateElement({
1430
- raw: "/api/".concat(fileName, "?"),
1431
- cooked: "/api/".concat(fileName, "?"),
1432
- }),
1433
- types.templateElement({ raw: '', cooked: '' }),
1434
- ], [
1435
- types.newExpression(types.identifier('URLSearchParams'), [
1436
- types.identifier('params'),
1437
- ]),
1438
- ]),
1439
- types.objectExpression([
1440
- types.objectProperty(types.identifier('headers'), types.objectExpression([
1441
- types.objectProperty(types.stringLiteral('Content-Type'), types.stringLiteral('application/json')),
1442
- ])),
1443
- ]),
1444
- ]), types.identifier('then')), [
1445
- types.arrowFunctionExpression([types.identifier('res')], types.callExpression(types.memberExpression(types.identifier('res'), types.identifier('json')), [])),
1446
- ]), types.identifier('then')), [
1447
- types.arrowFunctionExpression([types.identifier('response')], types.optionalMemberExpression(types.identifier('response'), types.identifier('data'), false, true)),
1448
- ])),
1449
- types.arrayExpression([]),
1450
- ])));
1488
+ function createFetchDataAttribute(fileName, cache) {
1489
+ var fetchCall = types.callExpression(types.identifier('fetch'), [
1490
+ types.templateLiteral([
1491
+ types.templateElement({
1492
+ raw: "/api/".concat(fileName, "?"),
1493
+ cooked: "/api/".concat(fileName, "?"),
1494
+ }),
1495
+ types.templateElement({ raw: '', cooked: '' }),
1496
+ ], [types.newExpression(types.identifier('URLSearchParams'), [types.identifier('params')])]),
1497
+ types.objectExpression([
1498
+ types.objectProperty(types.identifier('headers'), types.objectExpression([
1499
+ types.objectProperty(types.stringLiteral('Content-Type'), types.stringLiteral('application/json')),
1500
+ ])),
1501
+ ]),
1502
+ ]);
1503
+ var parsedJson = types.callExpression(types.memberExpression(fetchCall, types.identifier('then')), [
1504
+ types.arrowFunctionExpression([types.identifier('res')], types.callExpression(types.memberExpression(types.identifier('res'), types.identifier('json')), [])),
1505
+ ]);
1506
+ // The uncached tail is left exactly as it has always been emitted, so a
1507
+ // project that does not cache produces byte-identical output.
1508
+ var uncachedTail = types.arrowFunctionExpression([types.identifier('response')], types.optionalMemberExpression(types.identifier('response'), types.identifier('data'), false, true));
1509
+ var networkChain = types.callExpression(types.memberExpression(parsedJson, types.identifier('then')), [cache ? (0, ast_1.buildCacheStoreThen)(cache) : uncachedTail]);
1510
+ var arrow = cache
1511
+ ? types.arrowFunctionExpression([types.identifier('params')], (0, ast_1.buildCachedFetchBody)(networkChain, cache))
1512
+ : types.arrowFunctionExpression([types.identifier('params')], networkChain);
1513
+ return types.jsxAttribute(types.jsxIdentifier('fetchData'), types.jsxExpressionContainer(types.callExpression(types.identifier('useCallback'), [arrow, types.arrayExpression([])])));
1451
1514
  }
1452
1515
  function wireSearchInput(inputNode, vars) {
1453
1516
  // Remove existing onChange and value
@@ -1554,7 +1617,84 @@ function wirePaginationButtons(paginationNode, usage, vars) {
1554
1617
  }
1555
1618
  }
1556
1619
  }
1557
- function ensureAPIRouteExists(extractedResources, usage, dataSources, categories) {
1620
+ /**
1621
+ * Applies the SERVER cache to the generated data-source modules, once, after
1622
+ * every provider has been visited.
1623
+ *
1624
+ * A post-pass rather than a parameter to `ensureAPIRouteExists` because four
1625
+ * different places race to create `utils/data-sources/<file>.js` — the
1626
+ * getStaticProps path, the component path, the client-API path and this plugin —
1627
+ * and whichever wins would otherwise decide whether the file is cached. Since
1628
+ * the generator is deterministic, regenerating the winner with the cache options
1629
+ * is order-independent and cannot half-apply.
1630
+ *
1631
+ * The config is merged per FILE, not per mapper: one module serves every mapper
1632
+ * reading that `(type, table, dataSourceId)` triple, so two mappers on two pages
1633
+ * have to agree — and the fresher one wins (see `mergeServerCacheOptions`).
1634
+ */
1635
+ function applyServerCacheToDataSourceModules(
1636
+ // tslint:disable-next-line:no-any
1637
+ extractedResources, registry,
1638
+ // tslint:disable-next-line:no-any
1639
+ dataSources, transformOptions,
1640
+ // Accumulated across every page/component this generation has processed. The
1641
+ // module is shared project-wide, so merging only the CURRENT page's usages
1642
+ // would let page ORDER decide the TTL: a page asking for 300s processed after
1643
+ // one asking for 60s would widen the window the second page was configured
1644
+ // with. Carried on the shared options object so the merge stays global.
1645
+ accumulator) {
1646
+ if (transformOptions === void 0) { transformOptions = {}; }
1647
+ var byFileName = accumulator !== null && accumulator !== void 0 ? accumulator : new Map();
1648
+ registry.usages.forEach(function (usage) {
1649
+ var _a;
1650
+ if (!((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.server)) {
1651
+ return;
1652
+ }
1653
+ var fileName = (0, utils_1.generateSafeFileName)(usage.resourceDefinition.dataSourceType, usage.resourceDefinition.tableName, usage.resourceDefinition.dataSourceId);
1654
+ var bucket = byFileName.get(fileName) || { usage: usage, entries: [] };
1655
+ bucket.entries.push({
1656
+ scope: usage.cache.scope,
1657
+ ttlSeconds: usage.cache.ttlSeconds,
1658
+ sMaxAge: usage.cache.sMaxAge,
1659
+ staleWhileRevalidate: usage.cache.staleWhileRevalidate,
1660
+ });
1661
+ byFileName.set(fileName, bucket);
1662
+ });
1663
+ byFileName.forEach(function (bucket, fileName) {
1664
+ var cacheOptions = (0, config_1.mergeServerCacheOptions)(bucket.entries);
1665
+ if (!cacheOptions) {
1666
+ return;
1667
+ }
1668
+ var dataSource = dataSources[bucket.usage.resourceDefinition.dataSourceId];
1669
+ if (!dataSource) {
1670
+ return;
1671
+ }
1672
+ var tableName = bucket.usage.resourceDefinition.tableName || 'data';
1673
+ var utilsKey = "utils/".concat(fileName);
1674
+ var apiKey = "api/".concat(fileName);
1675
+ try {
1676
+ if (extractedResources[utilsKey]) {
1677
+ extractedResources[utilsKey].content = (0, data_source_fetchers_1.generateDataSourceFetcherWithCore)(dataSource, tableName, false, transformOptions, cacheOptions);
1678
+ return;
1679
+ }
1680
+ // The variant that inlines the whole fetcher straight into `pages/api`
1681
+ // instead of re-exporting a utils module. Detected by content rather than
1682
+ // by which code path produced it, so a future fifth emitter is covered too.
1683
+ var apiEntry = extractedResources[apiKey];
1684
+ if (apiEntry &&
1685
+ typeof apiEntry.content === 'string' &&
1686
+ apiEntry.content.includes('async function handler')) {
1687
+ apiEntry.content = (0, data_source_fetchers_1.generateDataSourceFetcherWithCore)(dataSource, tableName, true, transformOptions, cacheOptions);
1688
+ }
1689
+ }
1690
+ catch (error) {
1691
+ // Caching must never be the reason a project fails to generate; the
1692
+ // uncached module that is already in place stays exactly as it was.
1693
+ }
1694
+ });
1695
+ }
1696
+ function ensureAPIRouteExists(extractedResources, usage, dataSources, transformOptions) {
1697
+ if (transformOptions === void 0) { transformOptions = {}; }
1558
1698
  // Generate file name for the API route
1559
1699
  var fileName = (0, utils_1.generateSafeFileName)(usage.resourceDefinition.dataSourceType, usage.resourceDefinition.tableName, usage.resourceDefinition.dataSourceId);
1560
1700
  // Check if the utils data source file exists - if not, create it
@@ -1564,7 +1704,7 @@ function ensureAPIRouteExists(extractedResources, usage, dataSources, categories
1564
1704
  if (dataSource) {
1565
1705
  try {
1566
1706
  // Generate the utils file with fetchData, fetchCount, handler, and getCount
1567
- var fetcherCode = (0, data_source_fetchers_1.generateDataSourceFetcherWithCore)(dataSource, usage.resourceDefinition.tableName || 'data', false, categories);
1707
+ var fetcherCode = (0, data_source_fetchers_1.generateDataSourceFetcherWithCore)(dataSource, usage.resourceDefinition.tableName || 'data', false, transformOptions);
1568
1708
  extractedResources["utils/".concat(fileName)] = {
1569
1709
  fileName: fileName,
1570
1710
  fileType: teleport_types_1.FileType.JS,