@teleporthq/teleport-plugin-next-data-source 0.43.54 → 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.
Files changed (102) 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-storefront-tax.test.ts +118 -0
  4. package/__tests__/ecommerce-product-variant-availability.test.ts +264 -0
  5. package/dist/cjs/cache/ast.d.ts +71 -0
  6. package/dist/cjs/cache/ast.d.ts.map +1 -0
  7. package/dist/cjs/cache/ast.js +229 -0
  8. package/dist/cjs/cache/ast.js.map +1 -0
  9. package/dist/cjs/cache/config.d.ts +22 -0
  10. package/dist/cjs/cache/config.d.ts.map +1 -0
  11. package/dist/cjs/cache/config.js +67 -0
  12. package/dist/cjs/cache/config.js.map +1 -0
  13. package/dist/cjs/cache/types.d.ts +31 -0
  14. package/dist/cjs/cache/types.d.ts.map +1 -0
  15. package/dist/cjs/cache/types.js +3 -0
  16. package/dist/cjs/cache/types.js.map +1 -0
  17. package/dist/cjs/data-source-fetchers.d.ts +5 -3
  18. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  19. package/dist/cjs/data-source-fetchers.js +51 -8
  20. package/dist/cjs/data-source-fetchers.js.map +1 -1
  21. package/dist/cjs/fetchers/teleport.d.ts +2 -2
  22. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  23. package/dist/cjs/fetchers/teleport.js +3 -2
  24. package/dist/cjs/fetchers/teleport.js.map +1 -1
  25. package/dist/cjs/index.d.ts +2 -0
  26. package/dist/cjs/index.d.ts.map +1 -1
  27. package/dist/cjs/index.js +14 -9
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/loading-state.d.ts.map +1 -1
  30. package/dist/cjs/loading-state.js +74 -2
  31. package/dist/cjs/loading-state.js.map +1 -1
  32. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  33. package/dist/cjs/pagination-plugin.js +204 -64
  34. package/dist/cjs/pagination-plugin.js.map +1 -1
  35. package/dist/cjs/transformations/ecommerce-product.d.ts +15 -1
  36. package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
  37. package/dist/cjs/transformations/ecommerce-product.js +7 -3
  38. package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
  39. package/dist/cjs/transformations/index.d.ts +13 -2
  40. package/dist/cjs/transformations/index.d.ts.map +1 -1
  41. package/dist/cjs/transformations/index.js +33 -5
  42. package/dist/cjs/transformations/index.js.map +1 -1
  43. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  44. package/dist/cjs/utils.d.ts +4 -3
  45. package/dist/cjs/utils.d.ts.map +1 -1
  46. package/dist/cjs/utils.js +6 -4
  47. package/dist/cjs/utils.js.map +1 -1
  48. package/dist/esm/cache/ast.d.ts +71 -0
  49. package/dist/esm/cache/ast.d.ts.map +1 -0
  50. package/dist/esm/cache/ast.js +196 -0
  51. package/dist/esm/cache/ast.js.map +1 -0
  52. package/dist/esm/cache/config.d.ts +22 -0
  53. package/dist/esm/cache/config.d.ts.map +1 -0
  54. package/dist/esm/cache/config.js +62 -0
  55. package/dist/esm/cache/config.js.map +1 -0
  56. package/dist/esm/cache/types.d.ts +31 -0
  57. package/dist/esm/cache/types.d.ts.map +1 -0
  58. package/dist/esm/cache/types.js +2 -0
  59. package/dist/esm/cache/types.js.map +1 -0
  60. package/dist/esm/data-source-fetchers.d.ts +5 -3
  61. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  62. package/dist/esm/data-source-fetchers.js +51 -8
  63. package/dist/esm/data-source-fetchers.js.map +1 -1
  64. package/dist/esm/fetchers/teleport.d.ts +2 -2
  65. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  66. package/dist/esm/fetchers/teleport.js +3 -2
  67. package/dist/esm/fetchers/teleport.js.map +1 -1
  68. package/dist/esm/index.d.ts +2 -0
  69. package/dist/esm/index.d.ts.map +1 -1
  70. package/dist/esm/index.js +12 -8
  71. package/dist/esm/index.js.map +1 -1
  72. package/dist/esm/loading-state.d.ts.map +1 -1
  73. package/dist/esm/loading-state.js +74 -2
  74. package/dist/esm/loading-state.js.map +1 -1
  75. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  76. package/dist/esm/pagination-plugin.js +205 -65
  77. package/dist/esm/pagination-plugin.js.map +1 -1
  78. package/dist/esm/transformations/ecommerce-product.d.ts +15 -1
  79. package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
  80. package/dist/esm/transformations/ecommerce-product.js +7 -3
  81. package/dist/esm/transformations/ecommerce-product.js.map +1 -1
  82. package/dist/esm/transformations/index.d.ts +13 -2
  83. package/dist/esm/transformations/index.d.ts.map +1 -1
  84. package/dist/esm/transformations/index.js +32 -5
  85. package/dist/esm/transformations/index.js.map +1 -1
  86. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  87. package/dist/esm/utils.d.ts +4 -3
  88. package/dist/esm/utils.d.ts.map +1 -1
  89. package/dist/esm/utils.js +6 -4
  90. package/dist/esm/utils.js.map +1 -1
  91. package/package.json +5 -5
  92. package/src/cache/ast.ts +265 -0
  93. package/src/cache/config.ts +79 -0
  94. package/src/cache/types.ts +31 -0
  95. package/src/data-source-fetchers.ts +66 -12
  96. package/src/fetchers/teleport.ts +3 -3
  97. package/src/index.ts +10 -4
  98. package/src/loading-state.ts +65 -2
  99. package/src/pagination-plugin.ts +315 -116
  100. package/src/transformations/ecommerce-product.ts +107 -11
  101. package/src/transformations/index.ts +48 -7
  102. package/src/utils.ts +10 -5
@@ -1,4 +1,4 @@
1
- import { DataSourceType, UIDLDataSource, UIDLEcommerceCategory } from '@teleporthq/teleport-types'
1
+ import { DataSourceType, UIDLDataSource } from '@teleporthq/teleport-types'
2
2
  import {
3
3
  generatePostgreSQLFetcher,
4
4
  generateMySQLFetcher,
@@ -33,6 +33,9 @@ import {
33
33
  } from './fetchers'
34
34
  import { validateDatabaseConfig } from './validation'
35
35
  import { generateCountFetcher } from './count-fetchers'
36
+ import type { EcommerceProductTransformOptions } from './transformations'
37
+ import { DataCache } from '@teleporthq/teleport-shared'
38
+ import type { DataSourceServerCacheOptions } from './cache/types'
36
39
 
37
40
  export { generateRawQueryFetcher, parseQueryTemplateVariables }
38
41
 
@@ -81,7 +84,7 @@ export const getDataSourceDependencies = (
81
84
  export function generateDataSourceFetcher(
82
85
  dataSource: UIDLDataSource,
83
86
  tableName: string,
84
- categories?: UIDLEcommerceCategory[]
87
+ transformOptions: EcommerceProductTransformOptions = {}
85
88
  ): string {
86
89
  if (!dataSource || typeof dataSource !== 'object') {
87
90
  throw new Error('Invalid data source: data source must be a valid object')
@@ -237,7 +240,7 @@ export function generateDataSourceFetcher(
237
240
  if (!validation.isValid) {
238
241
  throw new Error(`Teleport config validation failed: ${validation.error}`)
239
242
  }
240
- return generateTeleportFetcher(config, tableName, categories)
243
+ return generateTeleportFetcher(config, tableName, transformOptions)
241
244
  }
242
245
 
243
246
  default:
@@ -258,9 +261,10 @@ export function generateDataSourceFetcherWithCore(
258
261
  dataSource: UIDLDataSource,
259
262
  tableName: string,
260
263
  isApiRoute: boolean = false,
261
- categories?: UIDLEcommerceCategory[]
264
+ transformOptions: EcommerceProductTransformOptions = {},
265
+ cacheOptions?: DataSourceServerCacheOptions
262
266
  ): string {
263
- const apiHandler = generateDataSourceFetcher(dataSource, tableName, categories)
267
+ const apiHandler = generateDataSourceFetcher(dataSource, tableName, transformOptions)
264
268
 
265
269
  // Extract the handler function body from the API route
266
270
  // The API route is structured as: export default async function handler(req, res) { ... }
@@ -285,14 +289,64 @@ export function generateDataSourceFetcherWithCore(
285
289
  // Combine and deduplicate imports
286
290
  const allImports = Array.from(new Set([...imports, ...countImports]))
287
291
 
292
+ // Caching is applied HERE rather than in the API route because four separate
293
+ // places emit an `api/<file>` route and a fifth path (`getStaticProps` →
294
+ // `fetchData` → `handler`) never goes through a route at all. Wrapping the
295
+ // module's exports is the one point that covers all five, and leaves every
296
+ // route emitter untouched.
297
+ //
298
+ // The import depth differs by destination: this module is written to
299
+ // `utils/data-sources/` normally, but the `isApiRoute` variant is inlined
300
+ // straight into `pages/api/`.
301
+ const cachePreamble = cacheOptions
302
+ ? `import { tqWithCache } from '${isApiRoute ? '../../' : '../'}tq-cache/server'`
303
+ : ''
304
+
305
+ const cacheWiring = cacheOptions
306
+ ? `
307
+ const __tqDataCache = ${JSON.stringify({
308
+ scope: cacheOptions.scope,
309
+ ttl: cacheOptions.ttlSeconds,
310
+ sMaxAge: cacheOptions.sMaxAge || 0,
311
+ swr: cacheOptions.staleWhileRevalidate || 0,
312
+ })}
313
+ const __tqCountCache = ${JSON.stringify({
314
+ scope: DataCache.countScopeFor(cacheOptions.scope),
315
+ // Its OWN keyspace, but the DATA scope's version row. A write bumps one
316
+ // row; if the count kept a separate version nothing would ever bump it,
317
+ // and the total would stay stale after every create and delete — the
318
+ // grid showing one more row than the pager counts.
319
+ versionScope: cacheOptions.scope,
320
+ ttl: cacheOptions.ttlSeconds,
321
+ sMaxAge: cacheOptions.sMaxAge || 0,
322
+ swr: cacheOptions.staleWhileRevalidate || 0,
323
+ })}
324
+
325
+ const cachedHandler = tqWithCache(handler, __tqDataCache)
326
+ const cachedGetCount = tqWithCache(getCount, __tqCountCache)
327
+ `
328
+ : ''
329
+
330
+ const handlerExport = cacheOptions ? 'cachedHandler' : 'handler'
331
+ const countExport = cacheOptions ? 'cachedGetCount' : 'getCount'
332
+
288
333
  // For API routes, export just the handler function
289
334
  // For utils files, export the full object with all functions
290
- const exports = isApiRoute
291
- ? 'export default handler'
292
- : `export { fetchData, fetchCount, handler, getCount }
335
+ //
336
+ // The uncached branch is spelled out verbatim rather than interpolated, so an
337
+ // uncached project keeps generating byte-identical output — `handler as
338
+ // handler` would be harmless at runtime and still a diff in every snapshot.
339
+ const exports = !cacheOptions
340
+ ? isApiRoute
341
+ ? 'export default handler'
342
+ : `export { fetchData, fetchCount, handler, getCount }
293
343
  export default { fetchData, fetchCount, handler, getCount }`
344
+ : isApiRoute
345
+ ? `export default ${handlerExport}`
346
+ : `export { fetchData, fetchCount, ${handlerExport} as handler, ${countExport} as getCount }
347
+ export default { fetchData, fetchCount, handler: ${handlerExport}, getCount: ${countExport} }`
294
348
 
295
- return `${allImports.join('\n')}
349
+ return `${[...allImports, cachePreamble].filter(Boolean).join('\n')}
296
350
 
297
351
  async function fetchData(params = {}) {
298
352
  const req = {
@@ -314,7 +368,7 @@ async function fetchData(params = {}) {
314
368
  },
315
369
  }
316
370
 
317
- await handler(req, res)
371
+ await ${handlerExport}(req, res)
318
372
 
319
373
  if (statusCode !== 200 || !result || !result.success) {
320
374
  throw new Error(result?.error || 'Failed to fetch data')
@@ -343,7 +397,7 @@ async function fetchCount(params = {}) {
343
397
  },
344
398
  }
345
399
 
346
- await getCount(req, res)
400
+ await ${countExport}(req, res)
347
401
 
348
402
  if (statusCode !== 200 || !result || !result.success) {
349
403
  throw new Error(result?.error || 'Failed to get count')
@@ -355,7 +409,7 @@ async function fetchCount(params = {}) {
355
409
  ${countFetcherWithoutImports}
356
410
 
357
411
  ${handlerWithoutImports}
358
-
412
+ ${cacheWiring}
359
413
  ${exports}
360
414
  `
361
415
  }
@@ -1,4 +1,3 @@
1
- import type { UIDLEcommerceCategory } from '@teleporthq/teleport-types'
2
1
  import {
3
2
  replaceSecretReference,
4
3
  generateDateFormatterCode,
@@ -9,6 +8,7 @@ import {
9
8
  getTransformationCode,
10
9
  getTransformExpression,
11
10
  getTransformWrapperCode,
11
+ type EcommerceProductTransformOptions,
12
12
  } from '../transformations'
13
13
 
14
14
  interface TeleportDBConfig {
@@ -52,7 +52,7 @@ export const validateTeleportConfig = (
52
52
  export const generateTeleportFetcher = (
53
53
  config: Record<string, unknown>,
54
54
  tableName: string,
55
- categories?: UIDLEcommerceCategory[]
55
+ transformOptions: EcommerceProductTransformOptions = {}
56
56
  ): string => {
57
57
  const dbConfig = config as TeleportDBConfig
58
58
  const schema = dbConfig.options?.schema
@@ -142,7 +142,7 @@ const getClient = () => {
142
142
  ${generateSafeJSONParseCode()}
143
143
 
144
144
  ${generateSearchEscapeHelpersCode()}
145
- ${getTransformationCode(tableName, categories)}
145
+ ${getTransformationCode(tableName, transformOptions)}
146
146
  ${getTransformWrapperCode(tableName)}
147
147
  const processFilters = (filters, conditions, queryParams, paramIndex) => {
148
148
  if (!filters) return paramIndex
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  hasUnresolvableDynamicParams,
15
15
  } from './utils'
16
16
  import { createNextArrayMapperPaginationPlugin } from './pagination-plugin'
17
+ import { buildProductTransformOptions } from './transformations'
17
18
  import { DATA_SOURCE_ISR_REVALIDATE_SECONDS } from './isr'
18
19
  import * as types from '@babel/types'
19
20
 
@@ -608,7 +609,7 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
608
609
  dependencies,
609
610
  dynamicRouteAttr,
610
611
  uidl.outputOptions?.folderPath,
611
- options.ecommerceSettings?.categories
612
+ buildProductTransformOptions(options)
612
613
  )
613
614
 
614
615
  if (result.success && result.chunk) {
@@ -646,7 +647,7 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
646
647
  dataSources,
647
648
  componentChunk,
648
649
  options.extractedResources,
649
- options.ecommerceSettings?.categories
650
+ buildProductTransformOptions(options)
650
651
  )
651
652
  }
652
653
  })
@@ -1268,7 +1269,7 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
1268
1269
  dataSources,
1269
1270
  componentChunk,
1270
1271
  options.extractedResources,
1271
- options.ecommerceSettings?.categories
1272
+ buildProductTransformOptions(options)
1272
1273
  )
1273
1274
  })
1274
1275
 
@@ -1354,7 +1355,7 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
1354
1355
  dataSource,
1355
1356
  firstDataSourceInfo.tableName,
1356
1357
  false,
1357
- options.ecommerceSettings?.categories
1358
+ buildProductTransformOptions(options)
1358
1359
  )
1359
1360
  options.extractedResources[`utils/${fileName}`] = {
1360
1361
  fileName,
@@ -1398,3 +1399,8 @@ export * from './utils'
1398
1399
  export * from './array-mapper-pagination'
1399
1400
  export * from './pagination-plugin'
1400
1401
  export * from './count-fetchers'
1402
+ // The product transform's build-time options (category taxonomy + storefront
1403
+ // tax rate) — every package that emits a data-source fetcher has to derive
1404
+ // them the same way.
1405
+ export { buildProductTransformOptions } from './transformations'
1406
+ export type { EcommerceProductTransformOptions } from './transformations'
@@ -1,4 +1,5 @@
1
1
  import * as types from '@babel/types'
2
+ import { findCachedFetchNetworkChain } from './cache/ast'
2
3
 
3
4
  /**
4
5
  * ------------------------------------------------------------------
@@ -137,17 +138,79 @@ export function applyLoadingStateToDataProvider(
137
138
  }
138
139
 
139
140
  const fetchArrow = findMemoizedFetchDataArrow(fetchDataAttr)
140
- if (!fetchArrow || fetchArrow.body.type !== 'CallExpression') {
141
+ if (!fetchArrow) {
141
142
  return false
142
143
  }
143
144
 
144
- fetchArrow.body = buildTrackedFetchBody(fetchArrow.body, vars)
145
+ if (fetchArrow.body.type === 'CallExpression') {
146
+ fetchArrow.body = buildTrackedFetchBody(fetchArrow.body, vars)
147
+ } else if (fetchArrow.body.type === 'BlockStatement') {
148
+ if (!injectTrackingIntoCachedBody(fetchArrow.body, vars)) {
149
+ return false
150
+ }
151
+ } else {
152
+ return false
153
+ }
145
154
 
146
155
  setPersistDataDuringLoading(attributes, vars)
147
156
 
148
157
  return true
149
158
  }
150
159
 
160
+ /**
161
+ * Adds the in-flight bookkeeping to a CACHED `fetchData` body.
162
+ *
163
+ * The body already returns early with `Promise.resolve(hit)` on a cache hit, so
164
+ * the tracking is attached to the network chain BELOW that — never to the early
165
+ * return. That is the whole point: a hit must not raise `isFetching`, because
166
+ * `persistDataDuringLoading={!isFetching}` would then drop the provider into its
167
+ * loading slot for a frame and produce exactly the skeleton flash the cache
168
+ * exists to remove.
169
+ *
170
+ * Returns `false` when the body is not the shape this module knows how to wrap,
171
+ * so the caller emits no state declarations rather than half-wiring it.
172
+ */
173
+ function injectTrackingIntoCachedBody(body: types.BlockStatement, vars: LoadingStateVars): boolean {
174
+ // Idempotent: a second pass over the same AST must not double-count.
175
+ if (containsIdentifier(body, vars.setIsFetchingVar)) {
176
+ return false
177
+ }
178
+
179
+ const networkChain = findCachedFetchNetworkChain(body)
180
+ if (!networkChain) {
181
+ return false
182
+ }
183
+
184
+ const tracked = buildTrackedFetchBody(networkChain.statement.argument as types.Expression, vars)
185
+ body.body.splice(networkChain.index, 1, ...tracked.body)
186
+
187
+ return true
188
+ }
189
+
190
+ // tslint:disable-next-line:no-any
191
+ function containsIdentifier(node: any, name: string): boolean {
192
+ if (!node || typeof node !== 'object') {
193
+ return false
194
+ }
195
+ if (node.type === 'Identifier' && node.name === name) {
196
+ return true
197
+ }
198
+ for (const key of Object.keys(node)) {
199
+ if (key === 'loc' || key === 'start' || key === 'end') {
200
+ continue
201
+ }
202
+ const value = node[key]
203
+ if (Array.isArray(value)) {
204
+ if (value.some((entry) => containsIdentifier(entry, name))) {
205
+ return true
206
+ }
207
+ } else if (value && typeof value === 'object' && containsIdentifier(value, name)) {
208
+ return true
209
+ }
210
+ }
211
+ return false
212
+ }
213
+
151
214
  // tslint:disable-next-line:no-any
152
215
  function findAttribute(attributes: any[], name: string): types.JSXAttribute | undefined {
153
216
  return attributes.find(