@teleporthq/teleport-plugin-next-data-source 0.43.67 → 0.43.68
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-overlap-sql.test.ts +53 -0
- package/__tests__/blog-post-page-enrichment.test.ts +214 -0
- package/__tests__/browser-row-policy.test.ts +503 -0
- package/__tests__/data-source-cache-wrapping.test.ts +18 -2
- package/__tests__/ecommerce-product-options.test.ts +206 -0
- package/__tests__/ecommerce-product-pages.test.ts +82 -0
- package/__tests__/ecommerce-product-pre-options.golden.json +505 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +293 -0
- package/__tests__/ecommerce-product-subscriptions.test.ts +121 -0
- package/__tests__/fetchers.test.ts +2 -2
- package/__tests__/items-path.test.ts +150 -0
- package/__tests__/product-options-storefront.fixture.json +2510 -0
- package/__tests__/product-page-url-fixtures.json +25 -0
- package/__tests__/product-price-sort.ts +4 -2
- package/__tests__/raw-rows-mode.test.ts +111 -0
- package/__tests__/rest-api-response-cache.test.ts +434 -0
- package/__tests__/table-read-route-protected-tables.test.ts +493 -0
- package/dist/cjs/array-overlap-sql.d.ts +21 -0
- package/dist/cjs/array-overlap-sql.d.ts.map +1 -0
- package/dist/cjs/array-overlap-sql.js +25 -0
- package/dist/cjs/array-overlap-sql.js.map +1 -0
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -4
- package/dist/cjs/data-source-fetchers.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/postgresql.d.ts +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +8 -4
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts +8 -1
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
- package/dist/cjs/fetchers/raw-query.js +42 -4
- package/dist/cjs/fetchers/raw-query.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +7 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +8 -5
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js +66 -0
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js +12 -0
- package/dist/cjs/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js +64 -0
- package/dist/cjs/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/cjs/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js +34 -0
- package/dist/cjs/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/cjs/items-path.d.ts +10 -0
- package/dist/cjs/items-path.d.ts.map +1 -0
- package/dist/cjs/items-path.js +86 -0
- package/dist/cjs/items-path.js.map +1 -0
- package/dist/cjs/localized-columns.d.ts.map +1 -1
- package/dist/cjs/localized-columns.js +13 -0
- package/dist/cjs/localized-columns.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +17 -5
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js +25 -0
- package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/cjs/transformations/blog-comments.d.ts +30 -0
- package/dist/cjs/transformations/blog-comments.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-comments.js +34 -0
- package/dist/cjs/transformations/blog-comments.js.map +1 -0
- package/dist/cjs/transformations/blog-post.d.ts +2 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -1
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts +9 -1
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +28 -1
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/heading-anchors.d.ts +25 -0
- package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/cjs/transformations/heading-anchors.js +29 -0
- package/dist/cjs/transformations/heading-anchors.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +47 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +56 -18
- 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.map +1 -1
- package/dist/cjs/utils.js +1 -1
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/array-overlap-sql.d.ts +21 -0
- package/dist/esm/array-overlap-sql.d.ts.map +1 -0
- package/dist/esm/array-overlap-sql.js +21 -0
- package/dist/esm/array-overlap-sql.js.map +1 -0
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +11 -5
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +1 -1
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +8 -4
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts +8 -1
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
- package/dist/esm/fetchers/raw-query.js +42 -4
- package/dist/esm/fetchers/raw-query.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +7 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +8 -5
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +43 -0
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js +60 -0
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js +8 -0
- package/dist/esm/fetchers/utils/request-sql-guards.js.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts +40 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js +59 -0
- package/dist/esm/fetchers/utils/rest-api-response-cache.js.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts +23 -0
- package/dist/esm/fetchers/utils/table-access-guard.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/table-access-guard.js +29 -0
- package/dist/esm/fetchers/utils/table-access-guard.js.map +1 -0
- package/dist/esm/items-path.d.ts +10 -0
- package/dist/esm/items-path.d.ts.map +1 -0
- package/dist/esm/items-path.js +58 -0
- package/dist/esm/items-path.js.map +1 -0
- package/dist/esm/localized-columns.d.ts.map +1 -1
- package/dist/esm/localized-columns.js +13 -0
- package/dist/esm/localized-columns.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +17 -5
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/transformations/blog-adjacent-posts.d.ts +16 -0
- package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -0
- package/dist/esm/transformations/blog-adjacent-posts.js +21 -0
- package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -0
- package/dist/esm/transformations/blog-comments.d.ts +30 -0
- package/dist/esm/transformations/blog-comments.d.ts.map +1 -0
- package/dist/esm/transformations/blog-comments.js +30 -0
- package/dist/esm/transformations/blog-comments.js.map +1 -0
- package/dist/esm/transformations/blog-post.d.ts +2 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -1
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/ecommerce-product.d.ts +9 -1
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +29 -2
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/heading-anchors.d.ts +25 -0
- package/dist/esm/transformations/heading-anchors.d.ts.map +1 -0
- package/dist/esm/transformations/heading-anchors.js +25 -0
- package/dist/esm/transformations/heading-anchors.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +47 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +56 -18
- 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.map +1 -1
- package/dist/esm/utils.js +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/array-overlap-sql.ts +41 -0
- package/src/data-source-fetchers.ts +41 -5
- package/src/fetchers/javascript.ts +9 -1
- package/src/fetchers/postgresql.ts +37 -10
- package/src/fetchers/raw-query.ts +60 -5
- package/src/fetchers/rest-api.ts +27 -5
- package/src/fetchers/teleport.ts +37 -13
- package/src/fetchers/utils/browser-row-policy.ts +212 -0
- package/src/fetchers/utils/request-sql-guards.ts +25 -0
- package/src/fetchers/utils/rest-api-response-cache.ts +188 -0
- package/src/fetchers/utils/table-access-guard.ts +39 -0
- package/src/items-path.ts +74 -0
- package/src/localized-columns.ts +13 -0
- package/src/pagination-plugin.ts +29 -7
- package/src/transformations/blog-adjacent-posts.ts +74 -0
- package/src/transformations/blog-comments.ts +132 -0
- package/src/transformations/blog-post.ts +38 -5
- package/src/transformations/ecommerce-product.ts +568 -5
- package/src/transformations/heading-anchors.ts +85 -0
- package/src/transformations/index.ts +165 -22
- package/src/transformations/shared-utils.ts +6 -0
- package/src/utils.ts +26 -4
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The class of the `#` link added to a post's content headings.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ PAIRED with `BLOG_HEADING_ANCHOR_CLASS` in teleport-gui
|
|
5
|
+
* `features/blog/constants/blog-heading-anchors.ts`, whose stylesheet (shipped
|
|
6
|
+
* on the post page) is what keeps the link out of sight until the heading is
|
|
7
|
+
* hovered or focused.
|
|
8
|
+
*/
|
|
9
|
+
export const BLOG_HEADING_ANCHOR_CLASS = 'tq-heading-anchor'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generates `addHeadingAnchors(html)`: every `<h2>`–`<h6>` of a post's content
|
|
13
|
+
* gets an `id` (kept when it already has one, otherwise a slug of its text,
|
|
14
|
+
* unique within the post) and a trailing `#` link to itself, so a section can
|
|
15
|
+
* be linked to (`/blog/post#setup`) and the link works on the very first load —
|
|
16
|
+
* the ids are in the server-rendered HTML.
|
|
17
|
+
*
|
|
18
|
+
* The link repeats the heading a screen reader already announces, so it is
|
|
19
|
+
* hidden from assistive technology and left out of the tab order; the heading
|
|
20
|
+
* stays the landmark.
|
|
21
|
+
*
|
|
22
|
+
* Idempotent: a heading that already carries the link is left alone. `h1` is
|
|
23
|
+
* the post title's level and is never touched.
|
|
24
|
+
*/
|
|
25
|
+
export const generateHeadingAnchorsCode = (): string => `
|
|
26
|
+
var BLOG_HEADING_ANCHOR_CLASS = '${BLOG_HEADING_ANCHOR_CLASS}'
|
|
27
|
+
var BLOG_HEADING_ID_PATTERN = /\\sid\\s*=\\s*("([^"]*)"|'([^']*)')/i
|
|
28
|
+
|
|
29
|
+
function escapeHeadingAttribute(value) {
|
|
30
|
+
return String(value)
|
|
31
|
+
.replace(/&/g, '&')
|
|
32
|
+
.replace(/"/g, '"')
|
|
33
|
+
.replace(/</g, '<')
|
|
34
|
+
.replace(/>/g, '>')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function blogHeadingSlug(text, used) {
|
|
38
|
+
var base = String(text || '')
|
|
39
|
+
.normalize('NFKD')
|
|
40
|
+
.replace(/[\\u0300-\\u036f]/g, '')
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^\\p{L}\\p{N}]+/gu, '-')
|
|
43
|
+
.replace(/^-+|-+$/g, '')
|
|
44
|
+
if (!base) base = 'section'
|
|
45
|
+
var slug = base
|
|
46
|
+
var suffix = 2
|
|
47
|
+
while (used[slug]) {
|
|
48
|
+
slug = base + '-' + suffix
|
|
49
|
+
suffix++
|
|
50
|
+
}
|
|
51
|
+
used[slug] = true
|
|
52
|
+
return slug
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function addHeadingAnchors(html) {
|
|
56
|
+
if (typeof html !== 'string' || !/<h[2-6][\\s>]/i.test(html)) return html
|
|
57
|
+
// Every id the content already uses is taken, so a generated one never
|
|
58
|
+
// duplicates it.
|
|
59
|
+
var used = {}
|
|
60
|
+
var existingIds = new RegExp(BLOG_HEADING_ID_PATTERN.source, 'gi')
|
|
61
|
+
var existing
|
|
62
|
+
while ((existing = existingIds.exec(html)) !== null) {
|
|
63
|
+
used[decodeHtmlEntities(existing[2] || existing[3] || '')] = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return html.replace(/<h([2-6])(\\s[^>]*)?>([\\s\\S]*?)<\\/h\\1>/gi, function(match, level, rawAttrs, inner) {
|
|
67
|
+
if (inner.indexOf(BLOG_HEADING_ANCHOR_CLASS) !== -1) return match
|
|
68
|
+
var text = stripHtmlTags(inner)
|
|
69
|
+
if (!text) return match
|
|
70
|
+
var attrs = rawAttrs || ''
|
|
71
|
+
var idMatch = attrs.match(BLOG_HEADING_ID_PATTERN)
|
|
72
|
+
var id = idMatch ? decodeHtmlEntities(idMatch[2] || idMatch[3] || '') : ''
|
|
73
|
+
if (!id) {
|
|
74
|
+
id = blogHeadingSlug(text, used)
|
|
75
|
+
attrs = ' id="' + escapeHeadingAttribute(id) + '"' + attrs
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
'<h' + level + attrs + '>' + inner +
|
|
79
|
+
'<a class="' + BLOG_HEADING_ANCHOR_CLASS + '" href="#' + escapeHeadingAttribute(id) +
|
|
80
|
+
'" aria-hidden="true" tabindex="-1">#</a>' +
|
|
81
|
+
'</h' + level + '>'
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
`
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
GeneratorOptions,
|
|
3
|
+
UIDLAuthentication,
|
|
4
|
+
UIDLEcommerceCategory,
|
|
5
|
+
} from '@teleporthq/teleport-types'
|
|
6
|
+
import { StorefrontTax, TableAccess } from '@teleporthq/teleport-shared'
|
|
3
7
|
import { generateSharedTransformationCode } from './shared-utils'
|
|
4
8
|
import { generateBlogPostTransformationCode } from './blog-post'
|
|
5
9
|
import { generateCustomPageTransformationCode } from './custom-page'
|
|
@@ -25,6 +29,14 @@ export type { EcommerceProductTransformOptions }
|
|
|
25
29
|
export interface EntityTransformOptions extends EcommerceProductTransformOptions {
|
|
26
30
|
/** Blog post-category taxonomy — see `blogSettings.categories`. */
|
|
27
31
|
blogCategories?: UIDLEcommerceCategory[]
|
|
32
|
+
/** Post content headings get ids and `#` links — see `blogSettings.headingAnchors`. */
|
|
33
|
+
blogHeadingAnchors?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* A post details fetch carries the post's approved comments — see
|
|
36
|
+
* `blogSettings.comments`. Off, the comments table is never queried: a blog
|
|
37
|
+
* without comments has no such table.
|
|
38
|
+
*/
|
|
39
|
+
blogComments?: boolean
|
|
28
40
|
/**
|
|
29
41
|
* The languages the rows are stored in — absent for a single-language
|
|
30
42
|
* project. The transform resolves every translatable field against the
|
|
@@ -32,6 +44,12 @@ export interface EntityTransformOptions extends EcommerceProductTransformOptions
|
|
|
32
44
|
* the fetcher matches and sorts translatable columns the same way.
|
|
33
45
|
*/
|
|
34
46
|
localization?: ContentLocalization
|
|
47
|
+
/**
|
|
48
|
+
* Who may read a money table (gift cards, vouchers, their ledgers) from a
|
|
49
|
+
* browser: the roles the generated admin panel admits. See
|
|
50
|
+
* `TableAccess.resolveTrustedReaderRoles`.
|
|
51
|
+
*/
|
|
52
|
+
trustedReaderRoles?: string[]
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
/**
|
|
@@ -60,14 +78,32 @@ const resolveAllowBackorders = (
|
|
|
60
78
|
* "stock never blocks a purchase" flag — see `resolveAllowBackorders`.
|
|
61
79
|
*/
|
|
62
80
|
export const buildProductTransformOptions = (
|
|
63
|
-
options: Pick<
|
|
64
|
-
|
|
81
|
+
options: Pick<
|
|
82
|
+
GeneratorOptions,
|
|
83
|
+
'ecommerceSettings' | 'invoiceSettings' | 'blogSettings' | 'auth'
|
|
84
|
+
> &
|
|
85
|
+
LocalizedProjectOptions & {
|
|
86
|
+
/**
|
|
87
|
+
* The SAME project authentication under the name a `ProjectUIDL` gives
|
|
88
|
+
* it — the project plugins call this with the UIDL itself, where the
|
|
89
|
+
* field is `authentication`, while a component plugin calls it with
|
|
90
|
+
* `GeneratorOptions`, where it is `auth`. Reading only one of the two
|
|
91
|
+
* left every fetcher emitted by the project plugins with an empty
|
|
92
|
+
* trusted-reader list, i.e. a generated admin panel refused its own
|
|
93
|
+
* money tables.
|
|
94
|
+
*/
|
|
95
|
+
authentication?: UIDLAuthentication
|
|
96
|
+
}
|
|
65
97
|
): EntityTransformOptions => ({
|
|
66
98
|
categories: options.ecommerceSettings?.categories,
|
|
67
99
|
blogCategories: options.blogSettings?.categories,
|
|
100
|
+
blogHeadingAnchors: options.blogSettings?.headingAnchors === true,
|
|
101
|
+
blogComments: options.blogSettings?.comments === true,
|
|
68
102
|
storefrontTaxRate: StorefrontTax.resolveStorefrontTaxRate(options.invoiceSettings),
|
|
69
103
|
allowBackorders: resolveAllowBackorders(options.ecommerceSettings),
|
|
104
|
+
productPages: options.ecommerceSettings?.productPages,
|
|
70
105
|
localization: resolveContentLocalization(options),
|
|
106
|
+
trustedReaderRoles: TableAccess.resolveTrustedReaderRoles(options.auth || options.authentication),
|
|
71
107
|
})
|
|
72
108
|
|
|
73
109
|
export type TransformationType = 'blog-post' | 'ecommerce-product' | 'custom-page' | null
|
|
@@ -132,7 +168,13 @@ export const getTransformationCode = (
|
|
|
132
168
|
|
|
133
169
|
switch (type) {
|
|
134
170
|
case 'blog-post':
|
|
135
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
shared +
|
|
173
|
+
generateBlogPostTransformationCode({
|
|
174
|
+
categories: options.blogCategories,
|
|
175
|
+
headingAnchors: options.blogHeadingAnchors,
|
|
176
|
+
})
|
|
177
|
+
)
|
|
136
178
|
case 'ecommerce-product':
|
|
137
179
|
return shared + generateEcommerceProductTransformationCode(options)
|
|
138
180
|
case 'custom-page':
|
|
@@ -142,30 +184,99 @@ export const getTransformationCode = (
|
|
|
142
184
|
}
|
|
143
185
|
}
|
|
144
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The request parameter that asks a transformed table for its rows AS STORED.
|
|
189
|
+
*
|
|
190
|
+
* The product, blog-post and custom-page transforms build a storefront view
|
|
191
|
+
* model: camelCased keys, flags as `"true"`/`"false"` strings, derived fields,
|
|
192
|
+
* and several columns left out altogether (a product's `discounts`,
|
|
193
|
+
* `category_ids`, `download_limit`, …). A caller that WRITES the row back —
|
|
194
|
+
* the generated admin's edit forms — needs the columns themselves: fed the view
|
|
195
|
+
* model, its form showed a digital product as not digital, a gift card switch
|
|
196
|
+
* ticked on a product that is not one, no discounts, and every save wrote the
|
|
197
|
+
* missing columns back blank.
|
|
198
|
+
*
|
|
199
|
+
* ⛔ PAIRED with `ADMIN_RAW_ROWS_PARAM` in the GUI's
|
|
200
|
+
* `mappers/domain-to-uidl/feature-detail-page-options.ts`, which bakes it into
|
|
201
|
+
* the admin update pages' detail resource.
|
|
202
|
+
*/
|
|
203
|
+
export const RAW_ROWS_PARAM = 'rawRows'
|
|
204
|
+
|
|
145
205
|
/**
|
|
146
206
|
* Returns the JavaScript expression that transforms the data array.
|
|
147
207
|
* This expression assumes the data array variable is called `safeData`,
|
|
148
208
|
* that `getClient` is available for asset resolution, and that `req` is in scope.
|
|
149
|
-
* Returns null if no transformation is needed.
|
|
209
|
+
* Returns null if no transformation is needed. A request carrying
|
|
210
|
+
* `RAW_ROWS_PARAM` gets the stored rows instead (see `rawRecords`).
|
|
150
211
|
*/
|
|
151
212
|
export const getTransformExpression = (tableName: string): string | null => {
|
|
152
|
-
|
|
153
|
-
if (!type) {
|
|
213
|
+
if (!detectTransformationType(tableName)) {
|
|
154
214
|
return null
|
|
155
215
|
}
|
|
216
|
+
return '(__tqWantsRawRows(req.query) ? await rawRecords(safeData, getClient) : await transformRecords(safeData, getClient, req.query))'
|
|
217
|
+
}
|
|
156
218
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The raw-rows mode every transformed table's fetcher carries: the rows as
|
|
221
|
+
* stored, with one exception — a value that is an uploaded asset's ID, alone or
|
|
222
|
+
* inside a JSON list column, becomes the URL the transform would have shown, so
|
|
223
|
+
* an edit form's image previews keep working. Nothing else is renamed, derived
|
|
224
|
+
* or dropped. The response cache keys on the query, so the two modes never
|
|
225
|
+
* share an entry.
|
|
226
|
+
*/
|
|
227
|
+
const RAW_ROWS_CODE = `
|
|
228
|
+
function __tqWantsRawRows(query) {
|
|
229
|
+
var flag = query ? query.${RAW_ROWS_PARAM} : undefined
|
|
230
|
+
return flag === true || flag === 'true'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function rawRecords(records, getClientFn) {
|
|
234
|
+
if (!Array.isArray(records)) return []
|
|
235
|
+
var assetMap = {}
|
|
236
|
+
try {
|
|
237
|
+
assetMap = await getAssetMap(getClientFn)
|
|
238
|
+
} catch (e) {
|
|
239
|
+
// Asset resolution is best-effort; the stored values pass through as they are.
|
|
240
|
+
}
|
|
241
|
+
return records.map(function(record) { return __tqResolveRowAssets(record, assetMap || {}) })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function __tqResolveRowAssets(record, assetMap) {
|
|
245
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) return record
|
|
246
|
+
var out = {}
|
|
247
|
+
for (var key in record) {
|
|
248
|
+
if (!Object.prototype.hasOwnProperty.call(record, key)) continue
|
|
249
|
+
out[key] = __tqResolveAssetValue(record[key], assetMap)
|
|
166
250
|
}
|
|
251
|
+
return out
|
|
167
252
|
}
|
|
168
253
|
|
|
254
|
+
function __tqIsAssetId(value, assetMap) {
|
|
255
|
+
return typeof value === 'string' && value !== '' && Object.prototype.hasOwnProperty.call(assetMap, value)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function __tqResolveAssetValue(value, assetMap) {
|
|
259
|
+
if (typeof value !== 'string' || !value) return value
|
|
260
|
+
if (__tqIsAssetId(value, assetMap)) return resolveAssetUrl(value, assetMap)
|
|
261
|
+
var trimmed = value.trim()
|
|
262
|
+
if (trimmed.charAt(0) !== '[') return value
|
|
263
|
+
var list
|
|
264
|
+
try {
|
|
265
|
+
list = JSON.parse(trimmed)
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return value
|
|
268
|
+
}
|
|
269
|
+
if (!Array.isArray(list)) return value
|
|
270
|
+
var changed = false
|
|
271
|
+
var resolved = list.map(function(item) {
|
|
272
|
+
if (!__tqIsAssetId(item, assetMap)) return item
|
|
273
|
+
changed = true
|
|
274
|
+
return resolveAssetUrl(item, assetMap)
|
|
275
|
+
})
|
|
276
|
+
return changed ? JSON.stringify(resolved) : value
|
|
277
|
+
}
|
|
278
|
+
`
|
|
279
|
+
|
|
169
280
|
/**
|
|
170
281
|
* How many reviews a product page carries into its structured data.
|
|
171
282
|
*
|
|
@@ -189,7 +300,7 @@ export const REVIEWS_PER_PRODUCT = 5
|
|
|
189
300
|
*/
|
|
190
301
|
export const getTransformWrapperCode = (
|
|
191
302
|
tableName: string,
|
|
192
|
-
options: Pick<EntityTransformOptions, 'localization'> = {}
|
|
303
|
+
options: Pick<EntityTransformOptions, 'localization' | 'blogComments'> = {}
|
|
193
304
|
): string => {
|
|
194
305
|
const type = detectTransformationType(tableName)
|
|
195
306
|
if (!type) {
|
|
@@ -273,9 +384,11 @@ export const getTransformWrapperCode = (
|
|
|
273
384
|
}`
|
|
274
385
|
: ''
|
|
275
386
|
|
|
387
|
+
// `includeOptionDetail` uses the same single-record heuristic as the related
|
|
388
|
+
// items: only the details page carries a product's full option tree.
|
|
276
389
|
const variantOption =
|
|
277
390
|
type === 'ecommerce-product'
|
|
278
|
-
? ', variantsByProductId: variantsByProductId, ratingsByProductId: ratingsByProductId, reviewsByProductId: reviewsByProductId'
|
|
391
|
+
? ', variantsByProductId: variantsByProductId, ratingsByProductId: ratingsByProductId, reviewsByProductId: reviewsByProductId, includeOptionDetail: Array.isArray(records) && records.length === 1'
|
|
279
392
|
: ''
|
|
280
393
|
|
|
281
394
|
// Related items are resolved for a SINGLE-record fetch only — which is the
|
|
@@ -307,16 +420,46 @@ export const getTransformWrapperCode = (
|
|
|
307
420
|
: ''
|
|
308
421
|
const relatedOption = hasRelatedItems ? `, ${relatedMapVar}: ${relatedMapVar}` : ''
|
|
309
422
|
|
|
310
|
-
|
|
423
|
+
// A post page's neighbours and comments, on the same single-record heuristic
|
|
424
|
+
// as the related rail. The comments are only asked for when the blog takes
|
|
425
|
+
// them — see `EntityTransformOptions.blogComments`.
|
|
426
|
+
const blogPageEnrichment =
|
|
427
|
+
type === 'blog-post'
|
|
428
|
+
? `
|
|
429
|
+
var adjacentPostsById = null
|
|
430
|
+
var commentsByPostId = null
|
|
431
|
+
if (Array.isArray(records) && records.length === 1) {
|
|
432
|
+
try {
|
|
433
|
+
adjacentPostsById = await getAdjacentPostsMap(getClientFn, records)
|
|
434
|
+
} catch (e) {
|
|
435
|
+
// Best-effort; the post navigation stays hidden.
|
|
436
|
+
}${
|
|
437
|
+
options.blogComments
|
|
438
|
+
? `
|
|
439
|
+
try {
|
|
440
|
+
commentsByPostId = await getBlogCommentsMap(getClientFn, records)
|
|
441
|
+
} catch (e) {
|
|
442
|
+
// Best-effort; the post renders without comments.
|
|
443
|
+
}`
|
|
444
|
+
: ''
|
|
445
|
+
}
|
|
446
|
+
}`
|
|
447
|
+
: ''
|
|
448
|
+
const blogPageOption =
|
|
449
|
+
type === 'blog-post'
|
|
450
|
+
? ', adjacentPostsById: adjacentPostsById, commentsByPostId: commentsByPostId'
|
|
451
|
+
: ''
|
|
452
|
+
|
|
453
|
+
return `${RAW_ROWS_CODE}
|
|
311
454
|
async function transformRecords(records, getClientFn, reqQuery) {
|
|
312
455
|
var assetMap = {}
|
|
313
456
|
try {
|
|
314
457
|
assetMap = await getAssetMap(getClientFn)
|
|
315
458
|
} catch (e) {
|
|
316
459
|
// Asset resolution is best-effort; continue without it
|
|
317
|
-
}${relatedEnrichment}${variantEnrichment}
|
|
460
|
+
}${relatedEnrichment}${blogPageEnrichment}${variantEnrichment}
|
|
318
461
|
var currentLanguage = reqQuery && typeof reqQuery.${REQUEST_LOCALE_PARAM} === 'string' && reqQuery.${REQUEST_LOCALE_PARAM} ? reqQuery.${REQUEST_LOCALE_PARAM} : null
|
|
319
|
-
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: ${mainLanguageLiteral}${variantOption}${relatedOption} }
|
|
462
|
+
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: ${mainLanguageLiteral}${variantOption}${relatedOption}${blogPageOption} }
|
|
320
463
|
return ${transformFn}(records, options)
|
|
321
464
|
}
|
|
322
465
|
`
|
|
@@ -74,6 +74,12 @@ function getCurrencySymbol(currencyCode) {
|
|
|
74
74
|
|
|
75
75
|
function normalizeTimestamp(value) {
|
|
76
76
|
if (value == null) return Date.now()
|
|
77
|
+
// A row read straight off the driver (the related / adjacent lookups) carries
|
|
78
|
+
// its timestamps as Date objects.
|
|
79
|
+
if (value instanceof Date) {
|
|
80
|
+
var time = value.getTime()
|
|
81
|
+
return isNaN(time) ? Date.now() : time
|
|
82
|
+
}
|
|
77
83
|
if (typeof value === 'number') {
|
|
78
84
|
if (isNaN(value)) return Date.now()
|
|
79
85
|
return value < 100000000000 ? value * 1000 : value
|
package/src/utils.ts
CHANGED
|
@@ -628,8 +628,8 @@ export const generateSafeJSONParseCode = (): string => {
|
|
|
628
628
|
* collapsing to an empty result the caller cannot explain.
|
|
629
629
|
*/
|
|
630
630
|
export const generateCollectionPathHelperCode = (): string => {
|
|
631
|
-
return `const
|
|
632
|
-
if (!rawPath) return
|
|
631
|
+
return `const parsePayloadPath = (rawPath) => {
|
|
632
|
+
if (!rawPath) return null
|
|
633
633
|
let path = rawPath
|
|
634
634
|
if (typeof path === 'string') {
|
|
635
635
|
try {
|
|
@@ -638,17 +638,39 @@ export const generateCollectionPathHelperCode = (): string => {
|
|
|
638
638
|
path = path.split('.').filter(Boolean)
|
|
639
639
|
}
|
|
640
640
|
}
|
|
641
|
-
|
|
641
|
+
return Array.isArray(path) && path.length > 0 ? path : null
|
|
642
|
+
}
|
|
642
643
|
|
|
644
|
+
const readPayloadPath = (payload, path) => {
|
|
643
645
|
let current = payload
|
|
644
646
|
for (const segment of path) {
|
|
645
647
|
if (!current || typeof current !== 'object' || Array.isArray(current) || !(segment in current)) {
|
|
646
|
-
return
|
|
648
|
+
return undefined
|
|
647
649
|
}
|
|
648
650
|
current = current[segment]
|
|
649
651
|
}
|
|
652
|
+
return current
|
|
653
|
+
}
|
|
650
654
|
|
|
655
|
+
const resolveCollectionPath = (payload, rawPath) => {
|
|
656
|
+
const path = parsePayloadPath(rawPath)
|
|
657
|
+
if (!path) return payload
|
|
658
|
+
const current = readPayloadPath(payload, path)
|
|
651
659
|
return Array.isArray(current) ? current : payload
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// A list bound to an array INSIDE the payload (\`data?.results\`) still has to be
|
|
663
|
+
// searched, filtered, sorted and paged, but the page reads it back through the
|
|
664
|
+
// same path, so the wrapper must survive. Returns the inner array plus a way to
|
|
665
|
+
// put the processed array back, or null when the path does not lead to one.
|
|
666
|
+
const openItemsEnvelope = (payload, rawPath) => {
|
|
667
|
+
const path = parsePayloadPath(rawPath)
|
|
668
|
+
if (!path) return null
|
|
669
|
+
const items = readPayloadPath(payload, path)
|
|
670
|
+
if (!Array.isArray(items)) return null
|
|
671
|
+
const rebuild = (node, depth, next) =>
|
|
672
|
+
depth === path.length ? next : { ...node, [path[depth]]: rebuild(node[path[depth]], depth + 1, next) }
|
|
673
|
+
return { items, close: (next) => rebuild(payload, 0, next) }
|
|
652
674
|
}`
|
|
653
675
|
}
|
|
654
676
|
|