@teleporthq/teleport-plugin-next-data-source 0.43.66 → 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-mapper-cache-wiring.test.ts +11 -0
- 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__/data-source-request-locale.test.ts +313 -0
- 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__/gated-data-provider.test.ts +88 -0
- package/__tests__/items-path.test.ts +150 -0
- package/__tests__/localized-columns.test.ts +217 -0
- package/__tests__/numbered-pagination.test.ts +15 -0
- package/__tests__/product-filter-fields.ts +230 -0
- package/__tests__/product-filter-sql.fixture.json +7 -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__/refetch-loading-state.test.ts +23 -0
- package/__tests__/rest-api-response-cache.test.ts +434 -0
- package/__tests__/search-clear-button.test.ts +122 -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/content-localization.d.ts +37 -0
- package/dist/cjs/content-localization.d.ts.map +1 -0
- package/dist/cjs/content-localization.js +29 -0
- package/dist/cjs/content-localization.js.map +1 -0
- package/dist/cjs/count-effect.d.ts +11 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +30 -2
- package/dist/cjs/count-effect.js.map +1 -1
- 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/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/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/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 +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +9 -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/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +1 -1
- 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 +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 +16 -6
- 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/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/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +60 -8
- package/dist/cjs/index.js.map +1 -1
- 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/loading-state.d.ts +16 -1
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +69 -3
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/localized-columns.d.ts +56 -0
- package/dist/cjs/localized-columns.d.ts.map +1 -0
- package/dist/cjs/localized-columns.js +137 -0
- package/dist/cjs/localized-columns.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +140 -50
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-filter-fields.d.ts +87 -0
- package/dist/cjs/product-filter-fields.d.ts.map +1 -0
- package/dist/cjs/product-filter-fields.js +161 -0
- package/dist/cjs/product-filter-fields.js.map +1 -0
- package/dist/cjs/request-locale.d.ts +49 -0
- package/dist/cjs/request-locale.d.ts.map +1 -0
- package/dist/cjs/request-locale.js +130 -0
- package/dist/cjs/request-locale.js.map +1 -0
- package/dist/cjs/search-clear.d.ts +31 -0
- package/dist/cjs/search-clear.d.ts.map +1 -0
- package/dist/cjs/search-clear.js +103 -0
- package/dist/cjs/search-clear.js.map +1 -0
- 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 +61 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +69 -19
- 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 +11 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +60 -16
- 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/content-localization.d.ts +37 -0
- package/dist/esm/content-localization.d.ts.map +1 -0
- package/dist/esm/content-localization.js +24 -0
- package/dist/esm/content-localization.js.map +1 -0
- package/dist/esm/count-effect.d.ts +11 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +28 -1
- package/dist/esm/count-effect.js.map +1 -1
- 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/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +1 -1
- 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 +1 -1
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +1 -1
- package/dist/esm/fetchers/firestore.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/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/esm/fetchers/mysql.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 +9 -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/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +1 -1
- 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 +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 +16 -6
- 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 +1 -1
- package/dist/esm/fetchers/turso.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/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +55 -7
- package/dist/esm/index.js.map +1 -1
- 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/loading-state.d.ts +16 -1
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +69 -3
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/localized-columns.d.ts +56 -0
- package/dist/esm/localized-columns.d.ts.map +1 -0
- package/dist/esm/localized-columns.js +132 -0
- package/dist/esm/localized-columns.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +141 -51
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-filter-fields.d.ts +87 -0
- package/dist/esm/product-filter-fields.d.ts.map +1 -0
- package/dist/esm/product-filter-fields.js +154 -0
- package/dist/esm/product-filter-fields.js.map +1 -0
- package/dist/esm/request-locale.d.ts +49 -0
- package/dist/esm/request-locale.d.ts.map +1 -0
- package/dist/esm/request-locale.js +98 -0
- package/dist/esm/request-locale.js.map +1 -0
- package/dist/esm/search-clear.d.ts +31 -0
- package/dist/esm/search-clear.d.ts.map +1 -0
- package/dist/esm/search-clear.js +74 -0
- package/dist/esm/search-clear.js.map +1 -0
- 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 +61 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +69 -19
- 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 +11 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +60 -16
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/array-overlap-sql.ts +41 -0
- package/src/content-localization.ts +53 -0
- package/src/count-effect.ts +53 -2
- package/src/data-source-fetchers.ts +41 -5
- package/src/fetchers/airtable.ts +4 -2
- package/src/fetchers/clickhouse.ts +4 -2
- package/src/fetchers/firestore.ts +4 -2
- package/src/fetchers/javascript.ts +9 -1
- package/src/fetchers/mariadb.ts +4 -2
- package/src/fetchers/mongodb.ts +4 -2
- package/src/fetchers/mysql.ts +4 -2
- package/src/fetchers/postgresql.ts +53 -12
- package/src/fetchers/raw-query.ts +60 -5
- package/src/fetchers/redshift.ts +4 -2
- package/src/fetchers/rest-api.ts +27 -5
- package/src/fetchers/teleport.ts +85 -22
- package/src/fetchers/turso.ts +4 -2
- 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/index.ts +79 -11
- package/src/items-path.ts +74 -0
- package/src/loading-state.ts +134 -3
- package/src/localized-columns.ts +146 -0
- package/src/pagination-plugin.ts +206 -60
- package/src/product-filter-fields.ts +173 -0
- package/src/request-locale.ts +113 -0
- package/src/search-clear.ts +102 -0
- 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 +191 -23
- package/src/transformations/shared-utils.ts +6 -0
- package/src/utils.ts +109 -19
|
@@ -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'
|
|
@@ -7,6 +11,12 @@ import {
|
|
|
7
11
|
generateEcommerceProductTransformationCode,
|
|
8
12
|
type EcommerceProductTransformOptions,
|
|
9
13
|
} from './ecommerce-product'
|
|
14
|
+
import { REQUEST_LOCALE_PARAM } from '../request-locale'
|
|
15
|
+
import {
|
|
16
|
+
resolveContentLocalization,
|
|
17
|
+
type ContentLocalization,
|
|
18
|
+
type LocalizedProjectOptions,
|
|
19
|
+
} from '../content-localization'
|
|
10
20
|
|
|
11
21
|
export type { EcommerceProductTransformOptions }
|
|
12
22
|
|
|
@@ -19,6 +29,27 @@ export type { EcommerceProductTransformOptions }
|
|
|
19
29
|
export interface EntityTransformOptions extends EcommerceProductTransformOptions {
|
|
20
30
|
/** Blog post-category taxonomy — see `blogSettings.categories`. */
|
|
21
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
|
|
40
|
+
/**
|
|
41
|
+
* The languages the rows are stored in — absent for a single-language
|
|
42
|
+
* project. The transform resolves every translatable field against the
|
|
43
|
+
* request's locale (`?locale=es` → `es_name`, falling back to `name`), and
|
|
44
|
+
* the fetcher matches and sorts translatable columns the same way.
|
|
45
|
+
*/
|
|
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[]
|
|
22
53
|
}
|
|
23
54
|
|
|
24
55
|
/**
|
|
@@ -47,12 +78,32 @@ const resolveAllowBackorders = (
|
|
|
47
78
|
* "stock never blocks a purchase" flag — see `resolveAllowBackorders`.
|
|
48
79
|
*/
|
|
49
80
|
export const buildProductTransformOptions = (
|
|
50
|
-
options: Pick<
|
|
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
|
+
}
|
|
51
97
|
): EntityTransformOptions => ({
|
|
52
98
|
categories: options.ecommerceSettings?.categories,
|
|
53
99
|
blogCategories: options.blogSettings?.categories,
|
|
100
|
+
blogHeadingAnchors: options.blogSettings?.headingAnchors === true,
|
|
101
|
+
blogComments: options.blogSettings?.comments === true,
|
|
54
102
|
storefrontTaxRate: StorefrontTax.resolveStorefrontTaxRate(options.invoiceSettings),
|
|
55
103
|
allowBackorders: resolveAllowBackorders(options.ecommerceSettings),
|
|
104
|
+
productPages: options.ecommerceSettings?.productPages,
|
|
105
|
+
localization: resolveContentLocalization(options),
|
|
106
|
+
trustedReaderRoles: TableAccess.resolveTrustedReaderRoles(options.auth || options.authentication),
|
|
56
107
|
})
|
|
57
108
|
|
|
58
109
|
export type TransformationType = 'blog-post' | 'ecommerce-product' | 'custom-page' | null
|
|
@@ -117,7 +168,13 @@ export const getTransformationCode = (
|
|
|
117
168
|
|
|
118
169
|
switch (type) {
|
|
119
170
|
case 'blog-post':
|
|
120
|
-
return
|
|
171
|
+
return (
|
|
172
|
+
shared +
|
|
173
|
+
generateBlogPostTransformationCode({
|
|
174
|
+
categories: options.blogCategories,
|
|
175
|
+
headingAnchors: options.blogHeadingAnchors,
|
|
176
|
+
})
|
|
177
|
+
)
|
|
121
178
|
case 'ecommerce-product':
|
|
122
179
|
return shared + generateEcommerceProductTransformationCode(options)
|
|
123
180
|
case 'custom-page':
|
|
@@ -127,30 +184,99 @@ export const getTransformationCode = (
|
|
|
127
184
|
}
|
|
128
185
|
}
|
|
129
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
|
+
|
|
130
205
|
/**
|
|
131
206
|
* Returns the JavaScript expression that transforms the data array.
|
|
132
207
|
* This expression assumes the data array variable is called `safeData`,
|
|
133
208
|
* that `getClient` is available for asset resolution, and that `req` is in scope.
|
|
134
|
-
* 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`).
|
|
135
211
|
*/
|
|
136
212
|
export const getTransformExpression = (tableName: string): string | null => {
|
|
137
|
-
|
|
138
|
-
if (!type) {
|
|
213
|
+
if (!detectTransformationType(tableName)) {
|
|
139
214
|
return null
|
|
140
215
|
}
|
|
216
|
+
return '(__tqWantsRawRows(req.query) ? await rawRecords(safeData, getClient) : await transformRecords(safeData, getClient, req.query))'
|
|
217
|
+
}
|
|
141
218
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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)
|
|
151
250
|
}
|
|
251
|
+
return out
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function __tqIsAssetId(value, assetMap) {
|
|
255
|
+
return typeof value === 'string' && value !== '' && Object.prototype.hasOwnProperty.call(assetMap, value)
|
|
152
256
|
}
|
|
153
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
|
+
|
|
154
280
|
/**
|
|
155
281
|
* How many reviews a product page carries into its structured data.
|
|
156
282
|
*
|
|
@@ -165,13 +291,24 @@ export const REVIEWS_PER_PRODUCT = 5
|
|
|
165
291
|
* Returns the transform wrapper function code that handles asset map loading
|
|
166
292
|
* and calls the appropriate transformer.
|
|
167
293
|
* Returns empty string if no transformation is needed.
|
|
294
|
+
*
|
|
295
|
+
* The request's `locale` (see `REQUEST_LOCALE_PARAM`) is what every
|
|
296
|
+
* translatable field resolves against; the MAIN locale it falls back to is a
|
|
297
|
+
* project constant and is baked in from `options.localization` rather than
|
|
298
|
+
* asked of every caller. Without a localization every row reads as before —
|
|
299
|
+
* the base columns.
|
|
168
300
|
*/
|
|
169
|
-
export const getTransformWrapperCode = (
|
|
301
|
+
export const getTransformWrapperCode = (
|
|
302
|
+
tableName: string,
|
|
303
|
+
options: Pick<EntityTransformOptions, 'localization' | 'blogComments'> = {}
|
|
304
|
+
): string => {
|
|
170
305
|
const type = detectTransformationType(tableName)
|
|
171
306
|
if (!type) {
|
|
172
307
|
return ''
|
|
173
308
|
}
|
|
174
309
|
|
|
310
|
+
const mainLanguageLiteral = JSON.stringify(options.localization?.mainLocale ?? null)
|
|
311
|
+
|
|
175
312
|
const transformFn =
|
|
176
313
|
type === 'blog-post'
|
|
177
314
|
? 'transformBlogPosts'
|
|
@@ -247,9 +384,11 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
247
384
|
}`
|
|
248
385
|
: ''
|
|
249
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.
|
|
250
389
|
const variantOption =
|
|
251
390
|
type === 'ecommerce-product'
|
|
252
|
-
? ', variantsByProductId: variantsByProductId, ratingsByProductId: ratingsByProductId, reviewsByProductId: reviewsByProductId'
|
|
391
|
+
? ', variantsByProductId: variantsByProductId, ratingsByProductId: ratingsByProductId, reviewsByProductId: reviewsByProductId, includeOptionDetail: Array.isArray(records) && records.length === 1'
|
|
253
392
|
: ''
|
|
254
393
|
|
|
255
394
|
// Related items are resolved for a SINGLE-record fetch only — which is the
|
|
@@ -281,17 +420,46 @@ export const getTransformWrapperCode = (tableName: string): string => {
|
|
|
281
420
|
: ''
|
|
282
421
|
const relatedOption = hasRelatedItems ? `, ${relatedMapVar}: ${relatedMapVar}` : ''
|
|
283
422
|
|
|
284
|
-
|
|
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}
|
|
285
454
|
async function transformRecords(records, getClientFn, reqQuery) {
|
|
286
455
|
var assetMap = {}
|
|
287
456
|
try {
|
|
288
457
|
assetMap = await getAssetMap(getClientFn)
|
|
289
458
|
} catch (e) {
|
|
290
459
|
// Asset resolution is best-effort; continue without it
|
|
291
|
-
}${relatedEnrichment}${variantEnrichment}
|
|
292
|
-
var currentLanguage =
|
|
293
|
-
var
|
|
294
|
-
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: mainLanguage${variantOption}${relatedOption} }
|
|
460
|
+
}${relatedEnrichment}${blogPageEnrichment}${variantEnrichment}
|
|
461
|
+
var currentLanguage = reqQuery && typeof reqQuery.${REQUEST_LOCALE_PARAM} === 'string' && reqQuery.${REQUEST_LOCALE_PARAM} ? reqQuery.${REQUEST_LOCALE_PARAM} : null
|
|
462
|
+
var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: ${mainLanguageLiteral}${variantOption}${relatedOption}${blogPageOption} }
|
|
295
463
|
return ${transformFn}(records, options)
|
|
296
464
|
}
|
|
297
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
|
@@ -14,6 +14,7 @@ import { GenericUtils, StringUtils } from '@teleporthq/teleport-shared'
|
|
|
14
14
|
import { generateDataSourceFetcherWithCore } from './data-source-fetchers'
|
|
15
15
|
import type { EntityTransformOptions } from './transformations'
|
|
16
16
|
import { DATA_SOURCE_ISR_REVALIDATE_SECONDS } from './isr'
|
|
17
|
+
import { buildServerLocaleParam, hasLocaleParam, buildClientLocaleParam } from './request-locale'
|
|
17
18
|
|
|
18
19
|
const VALID_DATA_SOURCE_TYPES: DataSourceType[] = [
|
|
19
20
|
'rest-api',
|
|
@@ -187,18 +188,28 @@ export const generateSafeFileName = (
|
|
|
187
188
|
return StringUtils.camelCaseToDashCase(baseName)
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Wires a `DataProvider` to a client-side fetch of its API route.
|
|
193
|
+
*
|
|
194
|
+
* In a localized project (`transformOptions.localization`) the fetch carries
|
|
195
|
+
* the visitor's locale: it is added to the provider's `params` — created when
|
|
196
|
+
* the node declares none — so it reaches the route AND keys the client cache.
|
|
197
|
+
* The caller puts `router` in scope when this answers `true`.
|
|
198
|
+
*
|
|
199
|
+
* @returns `true` when a provider was wired to a client fetch.
|
|
200
|
+
*/
|
|
190
201
|
export const extractDataSourceIntoNextAPIFolder = (
|
|
191
202
|
node: UIDLDataSourceItemNode | UIDLDataSourceListNode,
|
|
192
203
|
dataSources: Record<string, UIDLDataSource>,
|
|
193
204
|
componentChunk: ChunkDefinition,
|
|
194
205
|
extractedResources: GeneratorOptions['extractedResources'],
|
|
195
206
|
transformOptions: EntityTransformOptions = {}
|
|
196
|
-
) => {
|
|
207
|
+
): boolean => {
|
|
197
208
|
try {
|
|
198
209
|
// Validate node content structure
|
|
199
210
|
const contentValidation = validateNodeContent(node.content)
|
|
200
211
|
if (!contentValidation.isValid) {
|
|
201
|
-
return
|
|
212
|
+
return false
|
|
202
213
|
}
|
|
203
214
|
|
|
204
215
|
const { resourceDefinition } = node.content
|
|
@@ -206,31 +217,31 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
206
217
|
// Validate resource definition
|
|
207
218
|
const resourceValidation = validateResourceDefinition(resourceDefinition)
|
|
208
219
|
if (!resourceValidation.isValid) {
|
|
209
|
-
return
|
|
220
|
+
return false
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
const { dataSourceId, tableName, dataSourceType } = resourceDefinition
|
|
213
224
|
|
|
214
225
|
// Check if dataSources object exists
|
|
215
226
|
if (!dataSources || typeof dataSources !== 'object') {
|
|
216
|
-
return
|
|
227
|
+
return false
|
|
217
228
|
}
|
|
218
229
|
|
|
219
230
|
// Check if data source exists
|
|
220
231
|
const dataSource = dataSources[dataSourceId]
|
|
221
232
|
if (!dataSource) {
|
|
222
|
-
return
|
|
233
|
+
return false
|
|
223
234
|
}
|
|
224
235
|
|
|
225
236
|
// Validate data source configuration
|
|
226
237
|
const configValidation = validateDataSourceConfig(dataSource)
|
|
227
238
|
if (!configValidation.isValid) {
|
|
228
|
-
return
|
|
239
|
+
return false
|
|
229
240
|
}
|
|
230
241
|
|
|
231
242
|
// Check if component chunk has meta and nodesLookup
|
|
232
243
|
if (!componentChunk.meta || !componentChunk.meta.nodesLookup) {
|
|
233
|
-
return
|
|
244
|
+
return false
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
// Generate safe file name
|
|
@@ -238,7 +249,7 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
238
249
|
|
|
239
250
|
// Check if file name is valid
|
|
240
251
|
if (!fileName || fileName === 'unknown') {
|
|
241
|
-
return
|
|
252
|
+
return false
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
// Find JSX node by searching through nodesLookup
|
|
@@ -322,12 +333,12 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
322
333
|
}
|
|
323
334
|
|
|
324
335
|
if (!jsxNode || jsxNode.type !== 'JSXElement') {
|
|
325
|
-
return
|
|
336
|
+
return false
|
|
326
337
|
}
|
|
327
338
|
|
|
328
339
|
// Ensure opening element and attributes exist
|
|
329
340
|
if (!jsxNode.openingElement || !Array.isArray(jsxNode.openingElement.attributes)) {
|
|
330
|
-
return
|
|
341
|
+
return false
|
|
331
342
|
}
|
|
332
343
|
|
|
333
344
|
// Check if this node has already been processed (has fetchData attribute)
|
|
@@ -336,12 +347,16 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
336
347
|
)
|
|
337
348
|
|
|
338
349
|
if (existingFetchData) {
|
|
339
|
-
return
|
|
350
|
+
return false
|
|
340
351
|
}
|
|
341
352
|
|
|
342
353
|
// Check if there are resource params
|
|
343
354
|
const resourceParams = node.content.resource?.params || {}
|
|
344
|
-
const
|
|
355
|
+
const localized = transformOptions.localization !== undefined
|
|
356
|
+
if (localized) {
|
|
357
|
+
addClientLocaleToParams(jsxNode)
|
|
358
|
+
}
|
|
359
|
+
const hasParams = localized || Object.keys(resourceParams).length > 0
|
|
345
360
|
|
|
346
361
|
// Build resource path - use template literal if params exist
|
|
347
362
|
let resourcePath: types.StringLiteral | types.TemplateLiteral
|
|
@@ -423,7 +438,7 @@ export const extractDataSourceIntoNextAPIFolder = (
|
|
|
423
438
|
|
|
424
439
|
// Ensure extracted resources object exists
|
|
425
440
|
if (!extractedResources || typeof extractedResources !== 'object') {
|
|
426
|
-
return
|
|
441
|
+
return true
|
|
427
442
|
}
|
|
428
443
|
|
|
429
444
|
// Check if a utils file already exists for this data source (from getStaticProps)
|
|
@@ -440,7 +455,7 @@ export default dataSourceModule.handler
|
|
|
440
455
|
path: ['pages', 'api'],
|
|
441
456
|
content: apiRouteCode,
|
|
442
457
|
}
|
|
443
|
-
return
|
|
458
|
+
return true
|
|
444
459
|
}
|
|
445
460
|
|
|
446
461
|
// Generate fetcher code for API route (exports just the handler)
|
|
@@ -453,7 +468,7 @@ export default dataSourceModule.handler
|
|
|
453
468
|
transformOptions
|
|
454
469
|
)
|
|
455
470
|
} catch (error) {
|
|
456
|
-
return
|
|
471
|
+
return true
|
|
457
472
|
}
|
|
458
473
|
|
|
459
474
|
extractedResources[`api/${fileName}`] = {
|
|
@@ -462,11 +477,43 @@ export default dataSourceModule.handler
|
|
|
462
477
|
path: ['pages', 'api'],
|
|
463
478
|
content: fetcherCode,
|
|
464
479
|
}
|
|
480
|
+
return true
|
|
465
481
|
} catch (error) {
|
|
466
482
|
// Catch any unexpected errors to prevent plugin from crashing
|
|
483
|
+
return false
|
|
467
484
|
}
|
|
468
485
|
}
|
|
469
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Puts `locale: router?.locale` into a provider's `params` object literal,
|
|
489
|
+
* creating the attribute when the node declared no params at all.
|
|
490
|
+
*/
|
|
491
|
+
const addClientLocaleToParams = (jsxNode: types.JSXElement): void => {
|
|
492
|
+
const attrs = jsxNode.openingElement.attributes
|
|
493
|
+
let paramsAttr = attrs.find(
|
|
494
|
+
(attr): attr is types.JSXAttribute =>
|
|
495
|
+
attr.type === 'JSXAttribute' &&
|
|
496
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
497
|
+
attr.name.name === 'params'
|
|
498
|
+
)
|
|
499
|
+
if (!paramsAttr) {
|
|
500
|
+
paramsAttr = types.jsxAttribute(
|
|
501
|
+
types.jsxIdentifier('params'),
|
|
502
|
+
types.jsxExpressionContainer(types.objectExpression([]))
|
|
503
|
+
)
|
|
504
|
+
attrs.push(paramsAttr)
|
|
505
|
+
}
|
|
506
|
+
const value = paramsAttr.value
|
|
507
|
+
if (
|
|
508
|
+
value?.type !== 'JSXExpressionContainer' ||
|
|
509
|
+
value.expression.type !== 'ObjectExpression' ||
|
|
510
|
+
hasLocaleParam(value.expression.properties)
|
|
511
|
+
) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
value.expression.properties.push(buildClientLocaleParam())
|
|
515
|
+
}
|
|
516
|
+
|
|
470
517
|
export const isEmbeddedDataSource = (dataSourceType: string): boolean => {
|
|
471
518
|
return ['javascript', 'csv-file'].includes(dataSourceType)
|
|
472
519
|
}
|
|
@@ -581,8 +628,8 @@ export const generateSafeJSONParseCode = (): string => {
|
|
|
581
628
|
* collapsing to an empty result the caller cannot explain.
|
|
582
629
|
*/
|
|
583
630
|
export const generateCollectionPathHelperCode = (): string => {
|
|
584
|
-
return `const
|
|
585
|
-
if (!rawPath) return
|
|
631
|
+
return `const parsePayloadPath = (rawPath) => {
|
|
632
|
+
if (!rawPath) return null
|
|
586
633
|
let path = rawPath
|
|
587
634
|
if (typeof path === 'string') {
|
|
588
635
|
try {
|
|
@@ -591,17 +638,39 @@ export const generateCollectionPathHelperCode = (): string => {
|
|
|
591
638
|
path = path.split('.').filter(Boolean)
|
|
592
639
|
}
|
|
593
640
|
}
|
|
594
|
-
|
|
641
|
+
return Array.isArray(path) && path.length > 0 ? path : null
|
|
642
|
+
}
|
|
595
643
|
|
|
644
|
+
const readPayloadPath = (payload, path) => {
|
|
596
645
|
let current = payload
|
|
597
646
|
for (const segment of path) {
|
|
598
647
|
if (!current || typeof current !== 'object' || Array.isArray(current) || !(segment in current)) {
|
|
599
|
-
return
|
|
648
|
+
return undefined
|
|
600
649
|
}
|
|
601
650
|
current = current[segment]
|
|
602
651
|
}
|
|
652
|
+
return current
|
|
653
|
+
}
|
|
603
654
|
|
|
655
|
+
const resolveCollectionPath = (payload, rawPath) => {
|
|
656
|
+
const path = parsePayloadPath(rawPath)
|
|
657
|
+
if (!path) return payload
|
|
658
|
+
const current = readPayloadPath(payload, path)
|
|
604
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) }
|
|
605
674
|
}`
|
|
606
675
|
}
|
|
607
676
|
|
|
@@ -766,6 +835,22 @@ const buildFilterTreeClause = (node, buildCondition) => {
|
|
|
766
835
|
return buildCondition(node)
|
|
767
836
|
}
|
|
768
837
|
|
|
838
|
+
// The generic multi-value operand. The editor sends the destination either as
|
|
839
|
+
// an array or as one comma-joined string (a URL-bound state such as
|
|
840
|
+
// "acme,globex"); both become an array, and "in" becomes "=" so every
|
|
841
|
+
// fetcher's existing array branch answers with IN (...). An empty set means
|
|
842
|
+
// "no filter", never "match nothing".
|
|
843
|
+
const normalizeInOperand = (operand, value) => {
|
|
844
|
+
if (operand !== 'in') return { operand, value }
|
|
845
|
+
const entries = Array.isArray(value) ? value : [value]
|
|
846
|
+
const values = entries
|
|
847
|
+
.flatMap((entry) => (typeof entry === 'string' ? entry.split(',') : [entry]))
|
|
848
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : entry))
|
|
849
|
+
.filter((entry) => entry !== '' && entry !== null && entry !== undefined)
|
|
850
|
+
if (values.length === 0) return null
|
|
851
|
+
return { operand: '=', value: values }
|
|
852
|
+
}
|
|
853
|
+
|
|
769
854
|
const flattenFilterConditions = (node) => {
|
|
770
855
|
if (!node) return []
|
|
771
856
|
if (node.type === 'group') {
|
|
@@ -1588,6 +1673,11 @@ export const extractDataSourceIntoGetStaticProps = (
|
|
|
1588
1673
|
}
|
|
1589
1674
|
})
|
|
1590
1675
|
|
|
1676
|
+
// getStaticProps runs once per language: the prefetch is for that language.
|
|
1677
|
+
if (transformOptions.localization !== undefined) {
|
|
1678
|
+
paramsProperties.push(buildServerLocaleParam())
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1591
1681
|
const fetchCallExpression = types.callExpression(
|
|
1592
1682
|
types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchData')),
|
|
1593
1683
|
[types.objectExpression(paramsProperties)]
|