@teleporthq/teleport-plugin-next-data-source 0.43.62 → 0.43.64
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__/blog-post-categories.test.ts +131 -0
- package/__tests__/custom-page-transform.test.ts +119 -0
- package/__tests__/ecommerce-product-ratings.test.ts +155 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +147 -0
- package/__tests__/filter-groups.test.ts +174 -0
- package/__tests__/product-reviews-structured-data.test.ts +196 -0
- package/dist/cjs/data-source-fetchers.d.ts +3 -3
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
- package/dist/cjs/fetchers/csv-file.js +3 -3
- package/dist/cjs/fetchers/csv-file.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/cjs/fetchers/google-sheets.js +1 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +1 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +1 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redis.d.ts.map +1 -1
- package/dist/cjs/fetchers/redis.js +1 -1
- package/dist/cjs/fetchers/redis.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +2 -2
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +2 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +1 -1
- package/dist/cjs/fetchers/supabase.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 +1 -1
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/transformations/blog-post.d.ts +7 -1
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -2
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/category-taxonomy.d.ts +33 -0
- package/dist/cjs/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/cjs/transformations/category-taxonomy.js +42 -0
- package/dist/cjs/transformations/category-taxonomy.js.map +1 -0
- package/dist/cjs/transformations/custom-page.d.ts +18 -0
- package/dist/cjs/transformations/custom-page.d.ts.map +1 -0
- package/dist/cjs/transformations/custom-page.js +24 -0
- package/dist/cjs/transformations/custom-page.js.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +2 -35
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +23 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +38 -8
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
- package/dist/cjs/transformations/shared-utils.js +1 -1
- package/dist/cjs/transformations/shared-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +35 -3
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +116 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +3 -3
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +2 -2
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +2 -2
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
- package/dist/esm/fetchers/csv-file.js +4 -4
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +2 -2
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +2 -2
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +2 -2
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +2 -2
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +2 -2
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +2 -2
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +2 -2
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redis.d.ts.map +1 -1
- package/dist/esm/fetchers/redis.js +2 -2
- package/dist/esm/fetchers/redis.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +3 -3
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -3
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +2 -2
- package/dist/esm/fetchers/supabase.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 +2 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +2 -2
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +1 -1
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/transformations/blog-post.d.ts +7 -1
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -2
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/category-taxonomy.d.ts +33 -0
- package/dist/esm/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/esm/transformations/category-taxonomy.js +37 -0
- package/dist/esm/transformations/category-taxonomy.js.map +1 -0
- package/dist/esm/transformations/custom-page.d.ts +18 -0
- package/dist/esm/transformations/custom-page.d.ts.map +1 -0
- package/dist/esm/transformations/custom-page.js +20 -0
- package/dist/esm/transformations/custom-page.js.map +1 -0
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +3 -36
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +23 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +37 -7
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
- package/dist/esm/transformations/shared-utils.js +1 -1
- package/dist/esm/transformations/shared-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +35 -3
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +112 -17
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/data-source-fetchers.ts +3 -3
- package/src/fetchers/airtable.ts +35 -36
- package/src/fetchers/clickhouse.ts +26 -36
- package/src/fetchers/csv-file.ts +13 -21
- package/src/fetchers/firestore.ts +41 -35
- package/src/fetchers/google-sheets.ts +15 -23
- package/src/fetchers/javascript.ts +11 -21
- package/src/fetchers/mariadb.ts +33 -44
- package/src/fetchers/mongodb.ts +66 -81
- package/src/fetchers/mysql.ts +33 -45
- package/src/fetchers/postgresql.ts +52 -62
- package/src/fetchers/redis.ts +6 -5
- package/src/fetchers/redshift.ts +29 -41
- package/src/fetchers/rest-api.ts +11 -21
- package/src/fetchers/supabase.ts +104 -41
- package/src/fetchers/teleport.ts +55 -66
- package/src/fetchers/turso.ts +27 -39
- package/src/index.ts +1 -1
- package/src/pagination-plugin.ts +3 -6
- package/src/transformations/blog-post.ts +28 -9
- package/src/transformations/category-taxonomy.ts +107 -0
- package/src/transformations/custom-page.ts +83 -0
- package/src/transformations/ecommerce-product.ts +177 -78
- package/src/transformations/index.ts +85 -11
- package/src/transformations/shared-utils.ts +9 -0
- package/src/utils.ts +191 -34
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { GeneratorOptions } from '@teleporthq/teleport-types'
|
|
1
|
+
import type { GeneratorOptions, UIDLEcommerceCategory } from '@teleporthq/teleport-types'
|
|
2
2
|
import { StorefrontTax } from '@teleporthq/teleport-shared'
|
|
3
3
|
import { generateSharedTransformationCode } from './shared-utils'
|
|
4
4
|
import { generateBlogPostTransformationCode } from './blog-post'
|
|
5
|
+
import { generateCustomPageTransformationCode } from './custom-page'
|
|
5
6
|
import {
|
|
6
7
|
generateEcommerceProductTransformationCode,
|
|
7
8
|
type EcommerceProductTransformOptions,
|
|
@@ -9,6 +10,17 @@ import {
|
|
|
9
10
|
|
|
10
11
|
export type { EcommerceProductTransformOptions }
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Everything a generated fetcher bakes in, for EITHER entity. One options object
|
|
15
|
+
* because `getTransformationCode` is called with a table name and has to be able
|
|
16
|
+
* to serve whichever transform that resolves to — the product fields are ignored
|
|
17
|
+
* for a blog table and vice versa.
|
|
18
|
+
*/
|
|
19
|
+
export interface EntityTransformOptions extends EcommerceProductTransformOptions {
|
|
20
|
+
/** Blog post-category taxonomy — see `blogSettings.categories`. */
|
|
21
|
+
blogCategories?: UIDLEcommerceCategory[]
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
/**
|
|
13
25
|
* Stock never gates purchasability when the merchant disabled stock management
|
|
14
26
|
* entirely or opted into backorders. Mirrors the GUI rule in
|
|
@@ -35,14 +47,15 @@ const resolveAllowBackorders = (
|
|
|
35
47
|
* "stock never blocks a purchase" flag — see `resolveAllowBackorders`.
|
|
36
48
|
*/
|
|
37
49
|
export const buildProductTransformOptions = (
|
|
38
|
-
options: Pick<GeneratorOptions, 'ecommerceSettings' | 'invoiceSettings'>
|
|
39
|
-
):
|
|
50
|
+
options: Pick<GeneratorOptions, 'ecommerceSettings' | 'invoiceSettings' | 'blogSettings'>
|
|
51
|
+
): EntityTransformOptions => ({
|
|
40
52
|
categories: options.ecommerceSettings?.categories,
|
|
53
|
+
blogCategories: options.blogSettings?.categories,
|
|
41
54
|
storefrontTaxRate: StorefrontTax.resolveStorefrontTaxRate(options.invoiceSettings),
|
|
42
55
|
allowBackorders: resolveAllowBackorders(options.ecommerceSettings),
|
|
43
56
|
})
|
|
44
57
|
|
|
45
|
-
export type TransformationType = 'blog-post' | 'ecommerce-product' | null
|
|
58
|
+
export type TransformationType = 'blog-post' | 'ecommerce-product' | 'custom-page' | null
|
|
46
59
|
|
|
47
60
|
/**
|
|
48
61
|
* Strips a leading `schema.` qualifier from a table name so that only the
|
|
@@ -80,6 +93,9 @@ export const detectTransformationType = (tableName: string): TransformationType
|
|
|
80
93
|
if (bare === 'teleport_products') {
|
|
81
94
|
return 'ecommerce-product'
|
|
82
95
|
}
|
|
96
|
+
if (bare === 'teleport_pages') {
|
|
97
|
+
return 'custom-page'
|
|
98
|
+
}
|
|
83
99
|
return null
|
|
84
100
|
}
|
|
85
101
|
|
|
@@ -90,7 +106,7 @@ export const detectTransformationType = (tableName: string): TransformationType
|
|
|
90
106
|
*/
|
|
91
107
|
export const getTransformationCode = (
|
|
92
108
|
tableName: string,
|
|
93
|
-
options:
|
|
109
|
+
options: EntityTransformOptions = {}
|
|
94
110
|
): string => {
|
|
95
111
|
const type = detectTransformationType(tableName)
|
|
96
112
|
if (!type) {
|
|
@@ -101,9 +117,11 @@ export const getTransformationCode = (
|
|
|
101
117
|
|
|
102
118
|
switch (type) {
|
|
103
119
|
case 'blog-post':
|
|
104
|
-
return shared + generateBlogPostTransformationCode()
|
|
120
|
+
return shared + generateBlogPostTransformationCode({ categories: options.blogCategories })
|
|
105
121
|
case 'ecommerce-product':
|
|
106
122
|
return shared + generateEcommerceProductTransformationCode(options)
|
|
123
|
+
case 'custom-page':
|
|
124
|
+
return shared + generateCustomPageTransformationCode()
|
|
107
125
|
default:
|
|
108
126
|
return ''
|
|
109
127
|
}
|
|
@@ -126,11 +144,23 @@ export const getTransformExpression = (tableName: string): string | null => {
|
|
|
126
144
|
return 'await transformRecords(safeData, getClient, req.query)'
|
|
127
145
|
case 'ecommerce-product':
|
|
128
146
|
return 'await transformRecords(safeData, getClient, req.query)'
|
|
147
|
+
case 'custom-page':
|
|
148
|
+
return 'await transformRecords(safeData, getClient, req.query)'
|
|
129
149
|
default:
|
|
130
150
|
return null
|
|
131
151
|
}
|
|
132
152
|
}
|
|
133
153
|
|
|
154
|
+
/**
|
|
155
|
+
* How many reviews a product page carries into its structured data.
|
|
156
|
+
*
|
|
157
|
+
* They are inlined TWICE — once into the page's props, once into the JSON-LD
|
|
158
|
+
* script rendered from them — so this number is paid for in bytes on every
|
|
159
|
+
* request. Five is what a review snippet shows; the rest are on the page itself,
|
|
160
|
+
* fetched by the reviews section's own paginated query.
|
|
161
|
+
*/
|
|
162
|
+
export const REVIEWS_PER_PRODUCT = 5
|
|
163
|
+
|
|
134
164
|
/**
|
|
135
165
|
* Returns the transform wrapper function code that handles asset map loading
|
|
136
166
|
* and calls the appropriate transformer.
|
|
@@ -142,7 +172,12 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
142
172
|
return ''
|
|
143
173
|
}
|
|
144
174
|
|
|
145
|
-
const transformFn =
|
|
175
|
+
const transformFn =
|
|
176
|
+
type === 'blog-post'
|
|
177
|
+
? 'transformBlogPosts'
|
|
178
|
+
: type === 'custom-page'
|
|
179
|
+
? 'transformCustomPages'
|
|
180
|
+
: 'transformEcommerceProducts'
|
|
146
181
|
|
|
147
182
|
// Products additionally get their purchasable variant combinations attached in
|
|
148
183
|
// ONE batched query (keyed by product id). Blog posts have no such enrichment.
|
|
@@ -160,8 +195,11 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
160
195
|
type === 'ecommerce-product'
|
|
161
196
|
? `
|
|
162
197
|
var variantsByProductId = null
|
|
198
|
+
// Declared OUTSIDE the try because the ratings lookup below reuses it. \`var\`
|
|
199
|
+
// would hoist it anyway, but relying on that would make this block break
|
|
200
|
+
// silently the day someone modernises it to \`let\`.
|
|
201
|
+
var __variantPids = []
|
|
163
202
|
try {
|
|
164
|
-
var __variantPids = []
|
|
165
203
|
for (var __i = 0; __i < records.length; __i++) {
|
|
166
204
|
if (records[__i] && records[__i].id != null) __variantPids.push(records[__i].id)
|
|
167
205
|
}
|
|
@@ -178,11 +216,41 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
178
216
|
variantsByProductId = await getVariantsMap(getClientFn, __variantPids)
|
|
179
217
|
} catch (e) {
|
|
180
218
|
// Leaves the map null — "unknown", never "none".
|
|
219
|
+
}
|
|
220
|
+
// The aggregate star rating for the same id set, in ONE more query. Reuses
|
|
221
|
+
// __variantPids so a details page's related cards get their ratings from the
|
|
222
|
+
// same round trip rather than one query per card.
|
|
223
|
+
//
|
|
224
|
+
// Unlike the variants map this stays {} on failure: "no rating to show" is
|
|
225
|
+
// both the empty answer and the safe answer, so there is no unknown state for
|
|
226
|
+
// a caller to reason about.
|
|
227
|
+
var ratingsByProductId = {}
|
|
228
|
+
try {
|
|
229
|
+
ratingsByProductId = await getRatingsMap(getClientFn, __variantPids)
|
|
230
|
+
} catch (e) {
|
|
231
|
+
// Leaves the map empty — the star rows simply stay hidden.
|
|
232
|
+
}
|
|
233
|
+
// The individual reviews behind the stars, for the product page's JSON-LD.
|
|
234
|
+
//
|
|
235
|
+
// Gated on the SINGLE-RECORD heuristic, unlike the two lookups above. Those
|
|
236
|
+
// feed something every card on the page draws; this feeds structured data,
|
|
237
|
+
// which only a details page emits — and a listing of 24 products would
|
|
238
|
+
// otherwise fetch and inline 120 review rows that nothing on it renders or
|
|
239
|
+
// markup-references.
|
|
240
|
+
var reviewsByProductId = {}
|
|
241
|
+
if (Array.isArray(records) && records.length === 1) {
|
|
242
|
+
try {
|
|
243
|
+
reviewsByProductId = await getProductReviewsMap(getClientFn, __variantPids, ${REVIEWS_PER_PRODUCT})
|
|
244
|
+
} catch (e) {
|
|
245
|
+
// Best-effort; the product simply ships without review snippets.
|
|
246
|
+
}
|
|
181
247
|
}`
|
|
182
248
|
: ''
|
|
183
249
|
|
|
184
250
|
const variantOption =
|
|
185
|
-
type === 'ecommerce-product'
|
|
251
|
+
type === 'ecommerce-product'
|
|
252
|
+
? ', variantsByProductId: variantsByProductId, ratingsByProductId: ratingsByProductId, reviewsByProductId: reviewsByProductId'
|
|
253
|
+
: ''
|
|
186
254
|
|
|
187
255
|
// Related items are resolved for a SINGLE-record fetch only — which is the
|
|
188
256
|
// details page, the one surface that renders them (it looks the row up by
|
|
@@ -195,9 +263,13 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
195
263
|
// A one-product store's listing does pay for one extra query. That is the
|
|
196
264
|
// whole cost of the heuristic, and it beats plumbing a page-role flag through
|
|
197
265
|
// every fetcher.
|
|
266
|
+
// Custom pages have no related items and no variants: their transform only
|
|
267
|
+
// normalises the row's SEO fields.
|
|
268
|
+
const hasRelatedItems = type === 'blog-post' || type === 'ecommerce-product'
|
|
198
269
|
const relatedMapVar = type === 'blog-post' ? 'relatedPostsById' : 'relatedProductsById'
|
|
199
270
|
const relatedMapFn = type === 'blog-post' ? 'getRelatedPostsMap' : 'getRelatedProductsMap'
|
|
200
|
-
const relatedEnrichment =
|
|
271
|
+
const relatedEnrichment = hasRelatedItems
|
|
272
|
+
? `
|
|
201
273
|
var ${relatedMapVar} = null
|
|
202
274
|
if (Array.isArray(records) && records.length === 1) {
|
|
203
275
|
try {
|
|
@@ -206,6 +278,8 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
206
278
|
// Best-effort; the related rail stays hidden behind its empty gate.
|
|
207
279
|
}
|
|
208
280
|
}`
|
|
281
|
+
: ''
|
|
282
|
+
const relatedOption = hasRelatedItems ? `, ${relatedMapVar}: ${relatedMapVar}` : ''
|
|
209
283
|
|
|
210
284
|
return `
|
|
211
285
|
async function transformRecords(records, getClientFn, reqQuery) {
|
|
@@ -217,7 +291,7 @@ async function transformRecords(records, getClientFn, reqQuery) {
|
|
|
217
291
|
}${relatedEnrichment}${variantEnrichment}
|
|
218
292
|
var currentLanguage = (reqQuery && reqQuery.lang) || null
|
|
219
293
|
var mainLanguage = (reqQuery && reqQuery.mainLang) || null
|
|
220
|
-
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: mainLanguage${variantOption}
|
|
294
|
+
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: mainLanguage${variantOption}${relatedOption} }
|
|
221
295
|
return ${transformFn}(records, options)
|
|
222
296
|
}
|
|
223
297
|
`
|
|
@@ -248,6 +248,15 @@ function resolveMediaUrl(value, assetMap) {
|
|
|
248
248
|
return isResolvedMediaUrl(resolved) ? resolved : null
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// Trims a per-row SEO URL cell (blog posts, custom pages); blank/non-string
|
|
252
|
+
// values — including columns that don't exist yet on tables provisioned before
|
|
253
|
+
// the feature — become null.
|
|
254
|
+
function normalizeSeoUrlField(value) {
|
|
255
|
+
if (typeof value !== 'string') return null
|
|
256
|
+
var trimmed = value.trim()
|
|
257
|
+
return trimmed.length > 0 ? trimmed : null
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
function resolveAssetUrls(values, assetMap) {
|
|
252
261
|
if (!Array.isArray(values)) return []
|
|
253
262
|
return values
|
package/src/utils.ts
CHANGED
|
@@ -12,7 +12,7 @@ import * as types from '@babel/types'
|
|
|
12
12
|
import { ASTUtils } from '@teleporthq/teleport-plugin-common'
|
|
13
13
|
import { GenericUtils, StringUtils } from '@teleporthq/teleport-shared'
|
|
14
14
|
import { generateDataSourceFetcherWithCore } from './data-source-fetchers'
|
|
15
|
-
import type {
|
|
15
|
+
import type { EntityTransformOptions } from './transformations'
|
|
16
16
|
import { DATA_SOURCE_ISR_REVALIDATE_SECONDS } from './isr'
|
|
17
17
|
|
|
18
18
|
const VALID_DATA_SOURCE_TYPES: DataSourceType[] = [
|
|
@@ -192,7 +192,7 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
192
192
|
dataSources: Record<string, UIDLDataSource>,
|
|
193
193
|
componentChunk: ChunkDefinition,
|
|
194
194
|
extractedResources: GeneratorOptions['extractedResources'],
|
|
195
|
-
transformOptions:
|
|
195
|
+
transformOptions: EntityTransformOptions = {}
|
|
196
196
|
) => {
|
|
197
197
|
try {
|
|
198
198
|
// Validate node content structure
|
|
@@ -596,6 +596,146 @@ const dateReplacer = (key, value) => {
|
|
|
596
596
|
}`
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Every leaf condition of a filters array, unwrapping any `{ type: 'group' }`
|
|
601
|
+
* envelopes the inspector wrapped them in. Generator-side checks that ask
|
|
602
|
+
* "does any destination reference state / a route param?" must look through
|
|
603
|
+
* groups — asking `entry.destination` of a group silently answers "no", which
|
|
604
|
+
* is how a dynamic filter ends up baked into initialData at build time.
|
|
605
|
+
*/
|
|
606
|
+
export const collectFilterLeafConditions = (entries: unknown): unknown[] => {
|
|
607
|
+
if (!Array.isArray(entries)) {
|
|
608
|
+
return []
|
|
609
|
+
}
|
|
610
|
+
const leaves: unknown[] = []
|
|
611
|
+
const walk = (entry: unknown): void => {
|
|
612
|
+
if (!entry || typeof entry !== 'object') {
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
const node = entry as { type?: string; children?: unknown[] }
|
|
616
|
+
if (node.type === 'group') {
|
|
617
|
+
;(node.children || []).forEach(walk)
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
leaves.push(entry)
|
|
621
|
+
}
|
|
622
|
+
entries.forEach(walk)
|
|
623
|
+
return leaves
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Rebuilds a filters array keeping its group structure, dropping leaf
|
|
628
|
+
* conditions that `keepCondition` rejects and any group left empty by that.
|
|
629
|
+
* Used so the SSG emit can discard unresolvable dynamic destinations without
|
|
630
|
+
* flattening an `or` group into ANDs on the way.
|
|
631
|
+
*/
|
|
632
|
+
export const pruneFilterTree = (
|
|
633
|
+
entries: unknown[],
|
|
634
|
+
keepCondition: (condition: unknown) => boolean
|
|
635
|
+
): unknown[] => {
|
|
636
|
+
const prune = (entry: unknown): unknown | null => {
|
|
637
|
+
if (!entry || typeof entry !== 'object') {
|
|
638
|
+
return null
|
|
639
|
+
}
|
|
640
|
+
const node = entry as { type?: string; children?: unknown[] }
|
|
641
|
+
if (node.type === 'group') {
|
|
642
|
+
const children = (node.children || []).map(prune).filter(Boolean)
|
|
643
|
+
if (children.length === 0) {
|
|
644
|
+
return null
|
|
645
|
+
}
|
|
646
|
+
return { ...node, children }
|
|
647
|
+
}
|
|
648
|
+
return keepCondition(entry) ? entry : null
|
|
649
|
+
}
|
|
650
|
+
return entries.map(prune).filter(Boolean) as unknown[]
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Runtime helpers for the filter TREE the GUI authors.
|
|
655
|
+
*
|
|
656
|
+
* The inspector emits filters as a tree — a root `{ type: 'group', operator,
|
|
657
|
+
* children }` whose children are `{ type: 'condition', source, destination,
|
|
658
|
+
* operand }` leaves or further groups (an `or` group nested inside the root
|
|
659
|
+
* `and` is how "A and (B or C)" is expressed). Older projects, and params built
|
|
660
|
+
* by hand, still send a flat array of bare conditions or a plain `{ key: value }`
|
|
661
|
+
* map.
|
|
662
|
+
*
|
|
663
|
+
* Every fetcher used to hand-roll `parsedFilters.forEach(f => { if (!f.source)
|
|
664
|
+
* return ... })`, which silently DROPPED any group — a group has no `.source`,
|
|
665
|
+
* so the whole filter became a no-op and the page rendered unfiltered rows.
|
|
666
|
+
* These helpers exist so no fetcher has to know the shape: `normalizeFilterTree`
|
|
667
|
+
* turns all three input forms into one tree, and the walkers below consume it.
|
|
668
|
+
*/
|
|
669
|
+
export const generateFilterTreeHelpersCode = (): string => {
|
|
670
|
+
return `const normalizeFilterTree = (parsed) => {
|
|
671
|
+
if (parsed === null || parsed === undefined) return null
|
|
672
|
+
|
|
673
|
+
const toNode = (entry) => {
|
|
674
|
+
if (!entry || typeof entry !== 'object') return null
|
|
675
|
+
if (entry.type === 'group') {
|
|
676
|
+
const children = Array.isArray(entry.children)
|
|
677
|
+
? entry.children.map(toNode).filter(Boolean)
|
|
678
|
+
: []
|
|
679
|
+
if (children.length === 0) return null
|
|
680
|
+
return { type: 'group', operator: entry.operator === 'or' ? 'or' : 'and', children }
|
|
681
|
+
}
|
|
682
|
+
if (!entry.source || entry.destination === undefined) return null
|
|
683
|
+
return {
|
|
684
|
+
type: 'condition',
|
|
685
|
+
source: entry.source,
|
|
686
|
+
destination: entry.destination,
|
|
687
|
+
operand: entry.operand || '=',
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const children = Array.isArray(parsed)
|
|
692
|
+
? parsed.map(toNode).filter(Boolean)
|
|
693
|
+
: Object.entries(parsed).map(([source, destination]) =>
|
|
694
|
+
toNode({ source, destination, operand: '=' })
|
|
695
|
+
).filter(Boolean)
|
|
696
|
+
|
|
697
|
+
if (children.length === 0) return null
|
|
698
|
+
// A single root group is already the tree; wrapping it in another and-group
|
|
699
|
+
// would only add a redundant level.
|
|
700
|
+
if (children.length === 1 && children[0].type === 'group') return children[0]
|
|
701
|
+
return { type: 'group', operator: 'and', children }
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const evaluateFilterTree = (node, testCondition) => {
|
|
705
|
+
if (!node) return true
|
|
706
|
+
if (node.type === 'group') {
|
|
707
|
+
if (node.children.length === 0) return true
|
|
708
|
+
return node.operator === 'or'
|
|
709
|
+
? node.children.some((child) => evaluateFilterTree(child, testCondition))
|
|
710
|
+
: node.children.every((child) => evaluateFilterTree(child, testCondition))
|
|
711
|
+
}
|
|
712
|
+
return testCondition(node)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const buildFilterTreeClause = (node, buildCondition) => {
|
|
716
|
+
if (!node) return null
|
|
717
|
+
if (node.type === 'group') {
|
|
718
|
+
const parts = []
|
|
719
|
+
for (const child of node.children) {
|
|
720
|
+
const clause = buildFilterTreeClause(child, buildCondition)
|
|
721
|
+
if (clause) parts.push(clause)
|
|
722
|
+
}
|
|
723
|
+
if (parts.length === 0) return null
|
|
724
|
+
if (parts.length === 1) return parts[0]
|
|
725
|
+
return '(' + parts.join(node.operator === 'or' ? ' OR ' : ' AND ') + ')'
|
|
726
|
+
}
|
|
727
|
+
return buildCondition(node)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const flattenFilterConditions = (node) => {
|
|
731
|
+
if (!node) return []
|
|
732
|
+
if (node.type === 'group') {
|
|
733
|
+
return node.children.reduce((acc, child) => acc.concat(flattenFilterConditions(child)), [])
|
|
734
|
+
}
|
|
735
|
+
return [node]
|
|
736
|
+
}`
|
|
737
|
+
}
|
|
738
|
+
|
|
599
739
|
export const generateSortFilterHelperCode = (): string => {
|
|
600
740
|
return `function getNestedValue(obj, path) {
|
|
601
741
|
if (!obj || typeof obj !== 'object') return undefined
|
|
@@ -801,13 +941,14 @@ export const hasUnresolvableDynamicParams = (
|
|
|
801
941
|
}
|
|
802
942
|
|
|
803
943
|
if (param.type === 'static' && Array.isArray(param.content)) {
|
|
804
|
-
const
|
|
944
|
+
const leafConditions = collectFilterLeafConditions(param.content)
|
|
945
|
+
const hasDynamicDestinations = leafConditions.some(
|
|
805
946
|
(item: any) =>
|
|
806
947
|
item && typeof item === 'object' && ASTUtils.isUIDLDynamicReference(item?.destination)
|
|
807
948
|
)
|
|
808
949
|
|
|
809
950
|
if (hasDynamicDestinations && dynamicRouteAttr) {
|
|
810
|
-
const allResolvable =
|
|
951
|
+
const allResolvable = leafConditions.every((item: any) => {
|
|
811
952
|
if (!item || typeof item !== 'object') {
|
|
812
953
|
return true
|
|
813
954
|
}
|
|
@@ -901,7 +1042,7 @@ export const extractDataSourceIntoGetStaticProps = (
|
|
|
901
1042
|
dependencies: Record<string, any>,
|
|
902
1043
|
dynamicRouteAttr?: string,
|
|
903
1044
|
folderPath?: string[],
|
|
904
|
-
transformOptions:
|
|
1045
|
+
transformOptions: EntityTransformOptions = {}
|
|
905
1046
|
): { success: boolean; chunk?: any } => {
|
|
906
1047
|
try {
|
|
907
1048
|
// Validate node content
|
|
@@ -1165,9 +1306,9 @@ export const extractDataSourceIntoGetStaticProps = (
|
|
|
1165
1306
|
// tslint:disable-next-line:no-any
|
|
1166
1307
|
const resourceParamsForFilters = (node.content as any).resource?.params || {}
|
|
1167
1308
|
const nodeFilters = resourceParamsForFilters.filters?.content || []
|
|
1168
|
-
const hasDynamicFilterDestinations =
|
|
1169
|
-
|
|
1170
|
-
|
|
1309
|
+
const hasDynamicFilterDestinations = collectFilterLeafConditions(nodeFilters).some((f: any) =>
|
|
1310
|
+
ASTUtils.isUIDLDynamicReference(f?.destination)
|
|
1311
|
+
)
|
|
1171
1312
|
|
|
1172
1313
|
// Update all target JSX nodes with initialData (only those that don't already have it)
|
|
1173
1314
|
for (const jsxNode of nodesToUpdate) {
|
|
@@ -1291,9 +1432,12 @@ export const extractDataSourceIntoGetStaticProps = (
|
|
|
1291
1432
|
if (key === 'sorts' || key === 'filters' || key === 'queryColumns') {
|
|
1292
1433
|
let itemsToProcess = validItems
|
|
1293
1434
|
if (key === 'filters') {
|
|
1294
|
-
// Keep filter
|
|
1295
|
-
//
|
|
1296
|
-
|
|
1435
|
+
// Keep filter conditions that are either static or route-resolvable
|
|
1436
|
+
// (via dynamicRouteAttr) and drop the ones with unresolvable dynamic
|
|
1437
|
+
// destinations. Pruned through the tree so a condition nested in a
|
|
1438
|
+
// group is judged on its own destination, and an `or` group keeps
|
|
1439
|
+
// its shape on the way to the data source.
|
|
1440
|
+
itemsToProcess = pruneFilterTree(validItems, (item: any) => {
|
|
1297
1441
|
if (!ASTUtils.isUIDLDynamicReference(item?.destination)) {
|
|
1298
1442
|
return true
|
|
1299
1443
|
}
|
|
@@ -1310,36 +1454,49 @@ export const extractDataSourceIntoGetStaticProps = (
|
|
|
1310
1454
|
const hasRouteResolvableFilters =
|
|
1311
1455
|
key === 'filters' &&
|
|
1312
1456
|
dynamicRouteAttr &&
|
|
1313
|
-
itemsToProcess.some(
|
|
1457
|
+
collectFilterLeafConditions(itemsToProcess).some(
|
|
1314
1458
|
(item: any) =>
|
|
1315
1459
|
item &&
|
|
1316
1460
|
typeof item === 'object' &&
|
|
1317
1461
|
ASTUtils.isUIDLDynamicReference(item?.destination)
|
|
1318
1462
|
)
|
|
1319
1463
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
ASTUtils.isUIDLDynamicReference(item?.destination)
|
|
1335
|
-
) {
|
|
1336
|
-
return buildFilterObjectAST(item, dynamicRouteAttr)
|
|
1337
|
-
}
|
|
1338
|
-
return ASTUtils.objectToObjectExpression(item)
|
|
1339
|
-
}
|
|
1464
|
+
// Recurses through groups so a route-resolvable destination nested
|
|
1465
|
+
// inside one still gets its `context.params.*` AST instead of being
|
|
1466
|
+
// emitted as a raw UIDL dynamic-reference object.
|
|
1467
|
+
const buildFilterEntryAST = (item: any): types.Expression => {
|
|
1468
|
+
if (typeof item === 'string') {
|
|
1469
|
+
return types.stringLiteral(item)
|
|
1470
|
+
}
|
|
1471
|
+
if (typeof item === 'number') {
|
|
1472
|
+
return types.numericLiteral(item)
|
|
1473
|
+
}
|
|
1474
|
+
if (typeof item === 'boolean') {
|
|
1475
|
+
return types.booleanLiteral(item)
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof item !== 'object' || item === null) {
|
|
1340
1478
|
return types.nullLiteral()
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1479
|
+
}
|
|
1480
|
+
if (item.type === 'group' && Array.isArray(item.children)) {
|
|
1481
|
+
return types.objectExpression([
|
|
1482
|
+
types.objectProperty(types.identifier('type'), types.stringLiteral('group')),
|
|
1483
|
+
types.objectProperty(
|
|
1484
|
+
types.identifier('operator'),
|
|
1485
|
+
types.stringLiteral(item.operator === 'or' ? 'or' : 'and')
|
|
1486
|
+
),
|
|
1487
|
+
types.objectProperty(
|
|
1488
|
+
types.identifier('children'),
|
|
1489
|
+
types.arrayExpression(item.children.map(buildFilterEntryAST))
|
|
1490
|
+
),
|
|
1491
|
+
])
|
|
1492
|
+
}
|
|
1493
|
+
if (hasRouteResolvableFilters && ASTUtils.isUIDLDynamicReference(item?.destination)) {
|
|
1494
|
+
return buildFilterObjectAST(item, dynamicRouteAttr)
|
|
1495
|
+
}
|
|
1496
|
+
return ASTUtils.objectToObjectExpression(item)
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const arrayExpr = types.arrayExpression(itemsToProcess.map(buildFilterEntryAST))
|
|
1343
1500
|
// Wrap in JSON.stringify()
|
|
1344
1501
|
astValue = types.callExpression(
|
|
1345
1502
|
types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
|