@teleporthq/teleport-plugin-next-data-source 0.42.34 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/__tests__/ecommerce-product-out-of-stock.test.ts +112 -0
  2. package/__tests__/fetchers.test.ts +0 -42
  3. package/__tests__/filter-utils.test.ts +149 -0
  4. package/__tests__/mocks.ts +0 -12
  5. package/__tests__/utils.test.ts +0 -2
  6. package/dist/cjs/array-mapper-registry.d.ts +2 -0
  7. package/dist/cjs/array-mapper-registry.d.ts.map +1 -1
  8. package/dist/cjs/array-mapper-registry.js +9 -1
  9. package/dist/cjs/array-mapper-registry.js.map +1 -1
  10. package/dist/cjs/count-fetchers.d.ts +2 -2
  11. package/dist/cjs/count-fetchers.d.ts.map +1 -1
  12. package/dist/cjs/count-fetchers.js +5 -5
  13. package/dist/cjs/count-fetchers.js.map +1 -1
  14. package/dist/cjs/data-source-fetchers.d.ts +2 -1
  15. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  16. package/dist/cjs/data-source-fetchers.js +11 -9
  17. package/dist/cjs/data-source-fetchers.js.map +1 -1
  18. package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
  19. package/dist/cjs/fetchers/airtable.js +1 -1
  20. package/dist/cjs/fetchers/airtable.js.map +1 -1
  21. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
  22. package/dist/cjs/fetchers/clickhouse.js +1 -1
  23. package/dist/cjs/fetchers/clickhouse.js.map +1 -1
  24. package/dist/cjs/fetchers/csv-file.js +1 -1
  25. package/dist/cjs/fetchers/csv-file.js.map +1 -1
  26. package/dist/cjs/fetchers/firestore.js +1 -1
  27. package/dist/cjs/fetchers/firestore.js.map +1 -1
  28. package/dist/cjs/fetchers/google-sheets.js +1 -1
  29. package/dist/cjs/fetchers/google-sheets.js.map +1 -1
  30. package/dist/cjs/fetchers/index.d.ts +2 -1
  31. package/dist/cjs/fetchers/index.d.ts.map +1 -1
  32. package/dist/cjs/fetchers/index.js +8 -5
  33. package/dist/cjs/fetchers/index.js.map +1 -1
  34. package/dist/cjs/fetchers/javascript.js +1 -1
  35. package/dist/cjs/fetchers/javascript.js.map +1 -1
  36. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
  37. package/dist/cjs/fetchers/mariadb.js +3 -3
  38. package/dist/cjs/fetchers/mariadb.js.map +1 -1
  39. package/dist/cjs/fetchers/mongodb.js +1 -1
  40. package/dist/cjs/fetchers/mongodb.js.map +1 -1
  41. package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
  42. package/dist/cjs/fetchers/mysql.js +2 -2
  43. package/dist/cjs/fetchers/mysql.js.map +1 -1
  44. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  45. package/dist/cjs/fetchers/postgresql.js +2 -2
  46. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  47. package/dist/cjs/fetchers/raw-query.d.ts +18 -0
  48. package/dist/cjs/fetchers/raw-query.d.ts.map +1 -0
  49. package/dist/cjs/fetchers/raw-query.js +70 -0
  50. package/dist/cjs/fetchers/raw-query.js.map +1 -0
  51. package/dist/cjs/fetchers/redis.js +1 -1
  52. package/dist/cjs/fetchers/redis.js.map +1 -1
  53. package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
  54. package/dist/cjs/fetchers/redshift.js +2 -2
  55. package/dist/cjs/fetchers/redshift.js.map +1 -1
  56. package/dist/cjs/fetchers/rest-api.js +1 -1
  57. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  58. package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
  59. package/dist/cjs/fetchers/supabase.js +62 -2
  60. package/dist/cjs/fetchers/supabase.js.map +1 -1
  61. package/dist/cjs/fetchers/teleport.d.ts +7 -0
  62. package/dist/cjs/fetchers/teleport.d.ts.map +1 -0
  63. package/dist/cjs/fetchers/teleport.js +63 -0
  64. package/dist/cjs/fetchers/teleport.js.map +1 -0
  65. package/dist/cjs/fetchers/turso.d.ts.map +1 -1
  66. package/dist/cjs/fetchers/turso.js +1 -1
  67. package/dist/cjs/fetchers/turso.js.map +1 -1
  68. package/dist/cjs/filter-utils.d.ts +13 -0
  69. package/dist/cjs/filter-utils.d.ts.map +1 -0
  70. package/dist/cjs/filter-utils.js +95 -0
  71. package/dist/cjs/filter-utils.js.map +1 -0
  72. package/dist/cjs/index.d.ts.map +1 -1
  73. package/dist/cjs/index.js +112 -9
  74. package/dist/cjs/index.js.map +1 -1
  75. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  76. package/dist/cjs/pagination-plugin.js +389 -128
  77. package/dist/cjs/pagination-plugin.js.map +1 -1
  78. package/dist/cjs/sort-utils.d.ts +10 -0
  79. package/dist/cjs/sort-utils.d.ts.map +1 -0
  80. package/dist/cjs/sort-utils.js +141 -0
  81. package/dist/cjs/sort-utils.js.map +1 -0
  82. package/dist/cjs/transformations/blog-post.d.ts +7 -0
  83. package/dist/cjs/transformations/blog-post.d.ts.map +1 -0
  84. package/dist/cjs/transformations/blog-post.js +13 -0
  85. package/dist/cjs/transformations/blog-post.js.map +1 -0
  86. package/dist/cjs/transformations/ecommerce-product.d.ts +7 -0
  87. package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -0
  88. package/dist/cjs/transformations/ecommerce-product.js +13 -0
  89. package/dist/cjs/transformations/ecommerce-product.js.map +1 -0
  90. package/dist/cjs/transformations/index.d.ts +26 -0
  91. package/dist/cjs/transformations/index.d.ts.map +1 -0
  92. package/dist/cjs/transformations/index.js +81 -0
  93. package/dist/cjs/transformations/index.js.map +1 -0
  94. package/dist/cjs/transformations/shared-utils.d.ts +7 -0
  95. package/dist/cjs/transformations/shared-utils.d.ts.map +1 -0
  96. package/dist/cjs/transformations/shared-utils.js +13 -0
  97. package/dist/cjs/transformations/shared-utils.js.map +1 -0
  98. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  99. package/dist/cjs/utils.d.ts +30 -1
  100. package/dist/cjs/utils.d.ts.map +1 -1
  101. package/dist/cjs/utils.js +173 -10
  102. package/dist/cjs/utils.js.map +1 -1
  103. package/dist/esm/array-mapper-registry.d.ts +2 -0
  104. package/dist/esm/array-mapper-registry.d.ts.map +1 -1
  105. package/dist/esm/array-mapper-registry.js +9 -1
  106. package/dist/esm/array-mapper-registry.js.map +1 -1
  107. package/dist/esm/count-fetchers.d.ts +2 -2
  108. package/dist/esm/count-fetchers.d.ts.map +1 -1
  109. package/dist/esm/count-fetchers.js +4 -4
  110. package/dist/esm/count-fetchers.js.map +1 -1
  111. package/dist/esm/data-source-fetchers.d.ts +2 -1
  112. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  113. package/dist/esm/data-source-fetchers.js +10 -9
  114. package/dist/esm/data-source-fetchers.js.map +1 -1
  115. package/dist/esm/fetchers/airtable.d.ts.map +1 -1
  116. package/dist/esm/fetchers/airtable.js +1 -1
  117. package/dist/esm/fetchers/airtable.js.map +1 -1
  118. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
  119. package/dist/esm/fetchers/clickhouse.js +1 -1
  120. package/dist/esm/fetchers/clickhouse.js.map +1 -1
  121. package/dist/esm/fetchers/csv-file.js +1 -1
  122. package/dist/esm/fetchers/csv-file.js.map +1 -1
  123. package/dist/esm/fetchers/firestore.js +1 -1
  124. package/dist/esm/fetchers/firestore.js.map +1 -1
  125. package/dist/esm/fetchers/google-sheets.js +1 -1
  126. package/dist/esm/fetchers/google-sheets.js.map +1 -1
  127. package/dist/esm/fetchers/index.d.ts +2 -1
  128. package/dist/esm/fetchers/index.d.ts.map +1 -1
  129. package/dist/esm/fetchers/index.js +2 -1
  130. package/dist/esm/fetchers/index.js.map +1 -1
  131. package/dist/esm/fetchers/javascript.js +1 -1
  132. package/dist/esm/fetchers/javascript.js.map +1 -1
  133. package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
  134. package/dist/esm/fetchers/mariadb.js +4 -4
  135. package/dist/esm/fetchers/mariadb.js.map +1 -1
  136. package/dist/esm/fetchers/mongodb.js +1 -1
  137. package/dist/esm/fetchers/mongodb.js.map +1 -1
  138. package/dist/esm/fetchers/mysql.d.ts.map +1 -1
  139. package/dist/esm/fetchers/mysql.js +3 -3
  140. package/dist/esm/fetchers/mysql.js.map +1 -1
  141. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  142. package/dist/esm/fetchers/postgresql.js +3 -3
  143. package/dist/esm/fetchers/postgresql.js.map +1 -1
  144. package/dist/esm/fetchers/raw-query.d.ts +18 -0
  145. package/dist/esm/fetchers/raw-query.d.ts.map +1 -0
  146. package/dist/esm/fetchers/raw-query.js +65 -0
  147. package/dist/esm/fetchers/raw-query.js.map +1 -0
  148. package/dist/esm/fetchers/redis.js +1 -1
  149. package/dist/esm/fetchers/redis.js.map +1 -1
  150. package/dist/esm/fetchers/redshift.d.ts.map +1 -1
  151. package/dist/esm/fetchers/redshift.js +3 -3
  152. package/dist/esm/fetchers/redshift.js.map +1 -1
  153. package/dist/esm/fetchers/rest-api.js +1 -1
  154. package/dist/esm/fetchers/rest-api.js.map +1 -1
  155. package/dist/esm/fetchers/supabase.d.ts.map +1 -1
  156. package/dist/esm/fetchers/supabase.js +63 -3
  157. package/dist/esm/fetchers/supabase.js.map +1 -1
  158. package/dist/esm/fetchers/teleport.d.ts +7 -0
  159. package/dist/esm/fetchers/teleport.d.ts.map +1 -0
  160. package/dist/esm/fetchers/teleport.js +57 -0
  161. package/dist/esm/fetchers/teleport.js.map +1 -0
  162. package/dist/esm/fetchers/turso.d.ts.map +1 -1
  163. package/dist/esm/fetchers/turso.js +2 -2
  164. package/dist/esm/fetchers/turso.js.map +1 -1
  165. package/dist/esm/filter-utils.d.ts +13 -0
  166. package/dist/esm/filter-utils.d.ts.map +1 -0
  167. package/dist/esm/filter-utils.js +66 -0
  168. package/dist/esm/filter-utils.js.map +1 -0
  169. package/dist/esm/index.d.ts.map +1 -1
  170. package/dist/esm/index.js +113 -10
  171. package/dist/esm/index.js.map +1 -1
  172. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  173. package/dist/esm/pagination-plugin.js +389 -128
  174. package/dist/esm/pagination-plugin.js.map +1 -1
  175. package/dist/esm/sort-utils.d.ts +10 -0
  176. package/dist/esm/sort-utils.d.ts.map +1 -0
  177. package/dist/esm/sort-utils.js +113 -0
  178. package/dist/esm/sort-utils.js.map +1 -0
  179. package/dist/esm/transformations/blog-post.d.ts +7 -0
  180. package/dist/esm/transformations/blog-post.d.ts.map +1 -0
  181. package/dist/esm/transformations/blog-post.js +9 -0
  182. package/dist/esm/transformations/blog-post.js.map +1 -0
  183. package/dist/esm/transformations/ecommerce-product.d.ts +7 -0
  184. package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -0
  185. package/dist/esm/transformations/ecommerce-product.js +9 -0
  186. package/dist/esm/transformations/ecommerce-product.js.map +1 -0
  187. package/dist/esm/transformations/index.d.ts +26 -0
  188. package/dist/esm/transformations/index.d.ts.map +1 -0
  189. package/dist/esm/transformations/index.js +74 -0
  190. package/dist/esm/transformations/index.js.map +1 -0
  191. package/dist/esm/transformations/shared-utils.d.ts +7 -0
  192. package/dist/esm/transformations/shared-utils.d.ts.map +1 -0
  193. package/dist/esm/transformations/shared-utils.js +9 -0
  194. package/dist/esm/transformations/shared-utils.js.map +1 -0
  195. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  196. package/dist/esm/utils.d.ts +30 -1
  197. package/dist/esm/utils.d.ts.map +1 -1
  198. package/dist/esm/utils.js +170 -9
  199. package/dist/esm/utils.js.map +1 -1
  200. package/package.json +6 -5
  201. package/src/array-mapper-registry.ts +13 -0
  202. package/src/count-fetchers.ts +5 -5
  203. package/src/data-source-fetchers.ts +15 -11
  204. package/src/fetchers/airtable.ts +54 -8
  205. package/src/fetchers/clickhouse.ts +25 -19
  206. package/src/fetchers/csv-file.ts +2 -2
  207. package/src/fetchers/firestore.ts +2 -2
  208. package/src/fetchers/google-sheets.ts +2 -2
  209. package/src/fetchers/index.ts +6 -5
  210. package/src/fetchers/javascript.ts +2 -2
  211. package/src/fetchers/mariadb.ts +27 -12
  212. package/src/fetchers/mongodb.ts +2 -2
  213. package/src/fetchers/mysql.ts +27 -12
  214. package/src/fetchers/postgresql.ts +31 -18
  215. package/src/fetchers/raw-query.ts +178 -0
  216. package/src/fetchers/redis.ts +2 -2
  217. package/src/fetchers/redshift.ts +14 -10
  218. package/src/fetchers/rest-api.ts +2 -2
  219. package/src/fetchers/supabase.ts +97 -14
  220. package/src/fetchers/teleport.ts +485 -0
  221. package/src/fetchers/turso.ts +15 -7
  222. package/src/filter-utils.ts +111 -0
  223. package/src/index.ts +146 -6
  224. package/src/pagination-plugin.ts +547 -308
  225. package/src/sort-utils.ts +150 -0
  226. package/src/transformations/blog-post.ts +128 -0
  227. package/src/transformations/ecommerce-product.ts +173 -0
  228. package/src/transformations/index.ts +97 -0
  229. package/src/transformations/shared-utils.ts +271 -0
  230. package/src/utils.ts +227 -11
  231. package/dist/cjs/fetchers/static-collection.d.ts +0 -7
  232. package/dist/cjs/fetchers/static-collection.d.ts.map +0 -1
  233. package/dist/cjs/fetchers/static-collection.js +0 -25
  234. package/dist/cjs/fetchers/static-collection.js.map +0 -1
  235. package/dist/esm/fetchers/static-collection.d.ts +0 -7
  236. package/dist/esm/fetchers/static-collection.d.ts.map +0 -1
  237. package/dist/esm/fetchers/static-collection.js +0 -19
  238. package/dist/esm/fetchers/static-collection.js.map +0 -1
  239. package/src/fetchers/static-collection.ts +0 -231
@@ -0,0 +1,150 @@
1
+ import * as types from '@babel/types'
2
+ import { ASTUtils } from '@teleporthq/teleport-plugin-common'
3
+ import { UIDLExpressionValue, UIDLStaticValue } from '@teleporthq/teleport-types'
4
+
5
+ export interface DynamicSortAST {
6
+ field: types.Expression
7
+ order: types.Expression
8
+ depStateIds: string[]
9
+ }
10
+
11
+ const SKIP_IDENTIFIERS = new Set([
12
+ 'undefined',
13
+ 'NaN',
14
+ 'Infinity',
15
+ 'globalThis',
16
+ 'window',
17
+ 'document',
18
+ 'console',
19
+ ])
20
+
21
+ const collectIdentifiers = (node: types.Node | null | undefined, acc: Set<string>): void => {
22
+ if (!node || typeof node !== 'object') {
23
+ return
24
+ }
25
+
26
+ if (node.type === 'Identifier') {
27
+ if (!SKIP_IDENTIFIERS.has(node.name)) {
28
+ acc.add(node.name)
29
+ }
30
+ return
31
+ }
32
+
33
+ if (node.type === 'MemberExpression') {
34
+ collectIdentifiers(node.object, acc)
35
+ if (node.computed) {
36
+ collectIdentifiers(node.property, acc)
37
+ }
38
+ return
39
+ }
40
+
41
+ // tslint:disable-next-line:no-any
42
+ for (const key of Object.keys(node as any)) {
43
+ // tslint:disable-next-line:no-any
44
+ const child = (node as any)[key]
45
+ if (Array.isArray(child)) {
46
+ child.forEach((c) => collectIdentifiers(c, acc))
47
+ } else if (child && typeof child === 'object' && typeof child.type === 'string') {
48
+ collectIdentifiers(child, acc)
49
+ }
50
+ }
51
+ }
52
+
53
+ const toExpressionAndDeps = (
54
+ value: UIDLStaticValue | UIDLExpressionValue
55
+ ): { expr: types.Expression; deps: string[] } => {
56
+ if (value.type === 'static') {
57
+ return { expr: types.stringLiteral(String(value.content ?? '')), deps: [] }
58
+ }
59
+
60
+ const parsed = ASTUtils.getExpressionFromUIDLExpressionNode(value)
61
+ const deps = new Set<string>()
62
+ collectIdentifiers(parsed, deps)
63
+ return { expr: parsed, deps: Array.from(deps) }
64
+ }
65
+
66
+ export const extractDynamicSort = (
67
+ sort: UIDLStaticValue | UIDLExpressionValue | undefined,
68
+ sortDirection: UIDLStaticValue | UIDLExpressionValue | undefined
69
+ ): DynamicSortAST | undefined => {
70
+ if (!sort) {
71
+ return undefined
72
+ }
73
+
74
+ const field = toExpressionAndDeps(sort)
75
+
76
+ const order: { expr: types.Expression; deps: string[] } = sortDirection
77
+ ? toExpressionAndDeps(sortDirection)
78
+ : { expr: types.stringLiteral('asc'), deps: [] }
79
+
80
+ const depSet = new Set<string>([...field.deps, ...order.deps])
81
+
82
+ return {
83
+ field: field.expr,
84
+ order: order.expr,
85
+ depStateIds: Array.from(depSet),
86
+ }
87
+ }
88
+
89
+ // Build the AST for `sorts: JSON.stringify([{ field: <fieldExpr>, order: <orderExpr> }])`
90
+ const buildDynamicSortsProperty = (dynamicSort: DynamicSortAST): types.ObjectProperty => {
91
+ return types.objectProperty(
92
+ types.identifier('sorts'),
93
+ types.callExpression(
94
+ types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
95
+ [
96
+ types.arrayExpression([
97
+ types.objectExpression([
98
+ types.objectProperty(types.identifier('field'), types.cloneNode(dynamicSort.field)),
99
+ types.objectProperty(types.identifier('order'), types.cloneNode(dynamicSort.order)),
100
+ ]),
101
+ ]),
102
+ ]
103
+ )
104
+ )
105
+ }
106
+
107
+ // Build the AST for the legacy static sorts array form.
108
+ // tslint:disable-next-line:no-any
109
+ const buildLegacySortsProperty = (sorts: any[]): types.ObjectProperty => {
110
+ return types.objectProperty(
111
+ types.identifier('sorts'),
112
+ types.callExpression(
113
+ types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
114
+ [
115
+ types.arrayExpression(
116
+ // tslint:disable-next-line:no-any
117
+ sorts.map((sort: any) =>
118
+ types.objectExpression([
119
+ types.objectProperty(
120
+ types.identifier('field'),
121
+ types.stringLiteral(sort.field || '')
122
+ ),
123
+ types.objectProperty(
124
+ types.identifier('order'),
125
+ types.stringLiteral(sort.order || '')
126
+ ),
127
+ ])
128
+ )
129
+ ),
130
+ ]
131
+ )
132
+ )
133
+ }
134
+
135
+ // Push the `sorts` param to paramsProps. Legacy static-array form wins when present.
136
+ export const appendSortsParam = (
137
+ paramsProps: types.ObjectProperty[],
138
+ // tslint:disable-next-line:no-any
139
+ legacySorts: any[] | undefined,
140
+ dynamicSort: DynamicSortAST | undefined
141
+ ): void => {
142
+ if (legacySorts && legacySorts.length > 0) {
143
+ paramsProps.push(buildLegacySortsProperty(legacySorts))
144
+ return
145
+ }
146
+
147
+ if (dynamicSort) {
148
+ paramsProps.push(buildDynamicSortsProperty(dynamicSort))
149
+ }
150
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Generates JavaScript code for blog post data transformation.
3
+ * Transforms raw snake_case database records into the camelCase shape
4
+ * that UIDL components expect.
5
+ */
6
+ export const generateBlogPostTransformationCode = (): string => {
7
+ return `
8
+ // ============================================================
9
+ // Blog Post Transformation
10
+ // ============================================================
11
+
12
+ function buildBlogPost(record, options) {
13
+ if (!record || typeof record !== 'object') return record
14
+ options = options || {}
15
+ var currentLang = options.currentLanguage || null
16
+ var mainLang = options.mainLanguage || null
17
+ var assetMap = options.assetMap || {}
18
+
19
+ var id = record.id !== undefined && record.id !== null ? record.id : null
20
+
21
+ // i18n-resolved text fields
22
+ var title = resolveI18nField(record, 'title', 'title', currentLang, mainLang) || ''
23
+ var slug = resolveI18nField(record, 'slug', 'slug', currentLang, mainLang) || ''
24
+ var content = resolveI18nField(record, 'content', 'content', currentLang, mainLang) || ''
25
+ var excerpt = resolveI18nField(record, 'excerpt', 'excerpt', currentLang, mainLang) || ''
26
+ var category = resolveI18nField(record, 'category', 'category', currentLang, mainLang) || null
27
+ var metaTitle = resolveI18nField(record, 'meta_title', 'metaTitle', currentLang, mainLang) || null
28
+ var metaDescription = resolveI18nField(record, 'meta_description', 'metaDescription', currentLang, mainLang) || null
29
+ var featuredImageAlt = resolveI18nField(record, 'featured_image_alt', 'featuredImageAlt', currentLang, mainLang) || null
30
+
31
+ // Status
32
+ var status = record.status || 'draft'
33
+
34
+ // Asset fields - resolve IDs to URLs
35
+ var rawFeaturedImage = pickFirst(record.featured_image_url, record.featuredImageUrl)
36
+ var TELEPORT_DEFAULT_FEATURED_IMG = 'https://play.teleporthq.io/static/svg/default-img.svg'
37
+ var featuredImageUrl = resolveAssetUrl(rawFeaturedImage, assetMap)
38
+ if (
39
+ !featuredImageUrl ||
40
+ rawFeaturedImage === TELEPORT_DEFAULT_FEATURED_IMG ||
41
+ featuredImageUrl === TELEPORT_DEFAULT_FEATURED_IMG
42
+ ) {
43
+ featuredImageUrl =
44
+ 'data:image/svg+xml;charset=utf-8,' +
45
+ encodeURIComponent(
46
+ '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="240" viewBox="0 0 400 240"><rect fill="#e5e7eb" width="400" height="240"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#9ca3af" font-family="system-ui,sans-serif" font-size="13">No image</text></svg>'
47
+ )
48
+ }
49
+
50
+ var rawAuthorAvatar = pickFirst(record.author_avatar_url, record.authorAvatarUrl)
51
+ var authorAvatarUrl = resolveAssetUrl(rawAuthorAvatar, assetMap)
52
+
53
+ // Simple pass-through fields
54
+ var authorName = pickFirst(record.author_name, record.authorName)
55
+ var authorEmail = pickFirst(record.author_email, record.authorEmail)
56
+ var readingTimeMinutes = safeNumber(pickFirst(record.reading_time_minutes, record.readingTimeMinutes), null)
57
+
58
+ // Tags - i18n-resolved then parsed as JSON array
59
+ var rawTags = resolveI18nField(record, 'tags', 'tags', currentLang, mainLang)
60
+ var tags = parseJsonArray(rawTags)
61
+
62
+ // Gallery images - parse JSON array then resolve each asset URL
63
+ var rawGalleryImages = pickFirst(record.gallery_images, record.galleryImages)
64
+ var parsedGalleryImages = parseJsonArray(rawGalleryImages)
65
+ var galleryImages = resolveAssetUrls(parsedGalleryImages, assetMap)
66
+
67
+ // Computed: allImages = [featuredImageUrl, ...galleryImages] (skip null featured)
68
+ var allImages = []
69
+ if (featuredImageUrl) allImages.push(featuredImageUrl)
70
+ allImages = allImages.concat(galleryImages)
71
+
72
+ // Boolean fields
73
+ var isFeatured = coerceBoolean(pickFirst(record.is_featured, record.isFeatured), false)
74
+ var allowComments = record.allow_comments !== undefined
75
+ ? coerceBoolean(record.allow_comments, true)
76
+ : record.allowComments !== undefined
77
+ ? coerceBoolean(record.allowComments, true)
78
+ : true
79
+
80
+ // Timestamps
81
+ var rawPublishedAt = pickFirst(record.published_at, record.publishedAt)
82
+ var publishedAt = rawPublishedAt != null ? normalizeTimestamp(rawPublishedAt) : null
83
+
84
+ var rawCreatedAt = pickFirst(record.created_at, record.createdAt, record.created)
85
+ var createdAt = normalizeTimestamp(rawCreatedAt)
86
+
87
+ var rawUpdatedAt = pickFirst(record.updated_at, record.updatedAt, record.updated)
88
+ var updatedAt = rawUpdatedAt != null ? normalizeTimestamp(rawUpdatedAt) : createdAt
89
+
90
+ // Computed: created = Unix seconds from createdAt milliseconds
91
+ var created = Math.floor(createdAt / 1000)
92
+
93
+ return {
94
+ id: id,
95
+ title: title,
96
+ slug: slug,
97
+ content: content,
98
+ excerpt: excerpt,
99
+ status: status,
100
+ category: category,
101
+ tags: tags,
102
+ featuredImageUrl: featuredImageUrl,
103
+ featuredImageAlt: featuredImageAlt,
104
+ galleryImages: galleryImages,
105
+ allImages: allImages,
106
+ authorName: authorName,
107
+ authorEmail: authorEmail,
108
+ author_name: authorName,
109
+ author_email: authorEmail,
110
+ authorAvatarUrl: authorAvatarUrl,
111
+ metaTitle: metaTitle,
112
+ metaDescription: metaDescription,
113
+ readingTimeMinutes: readingTimeMinutes,
114
+ isFeatured: isFeatured,
115
+ allowComments: allowComments,
116
+ publishedAt: publishedAt,
117
+ createdAt: createdAt,
118
+ updatedAt: updatedAt,
119
+ created: created,
120
+ }
121
+ }
122
+
123
+ function transformBlogPosts(records, options) {
124
+ if (!Array.isArray(records)) return []
125
+ return records.map(function(record) { return buildBlogPost(record, options) })
126
+ }
127
+ `
128
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Generates JavaScript code for e-commerce product data transformation.
3
+ * Transforms raw snake_case database records into the camelCase shape
4
+ * that UIDL components expect.
5
+ */
6
+ export const generateEcommerceProductTransformationCode = (): string => {
7
+ return `
8
+ // ============================================================
9
+ // E-Commerce Product Transformation
10
+ // ============================================================
11
+
12
+ function buildEcommerceProduct(record, options) {
13
+ if (!record || typeof record !== 'object') return record
14
+ options = options || {}
15
+ var currentLang = options.currentLanguage || null
16
+ var mainLang = options.mainLanguage || null
17
+ var assetMap = options.assetMap || {}
18
+
19
+ var id = record.id !== undefined && record.id !== null ? record.id : null
20
+
21
+ // i18n-resolved text fields
22
+ var name = resolveI18nField(record, 'name', 'name', currentLang, mainLang) || ''
23
+ var slug = resolveI18nField(record, 'slug', 'slug', currentLang, mainLang) || ''
24
+ var description = resolveI18nField(record, 'description', 'description', currentLang, mainLang) || ''
25
+ var category = resolveI18nField(record, 'category', 'category', currentLang, mainLang) || null
26
+ var tagsRaw = resolveI18nField(record, 'tags', 'tags', currentLang, mainLang)
27
+ var imageAlt = resolveI18nField(record, 'image_alt', 'imageAlt', currentLang, mainLang) || null
28
+
29
+ // Computed: plain-text description (HTML stripped)
30
+ var descriptionText = stripHtmlTags(description)
31
+
32
+ // Numeric fields (with NaN protection)
33
+ var price = safeNumber(record.price, 0)
34
+ var quantity = safeNumber(record.quantity, null)
35
+
36
+ // Currency
37
+ var currency = (record.currency || 'USD').toUpperCase()
38
+ var rawCurrencySymbol = pickFirst(record.currency_symbol, record.currencySymbol)
39
+ var currencySymbol = rawCurrencySymbol || getCurrencySymbol(currency)
40
+
41
+ // Status and active (normalize case — DB / forms may send "Active", "Inactive", etc.)
42
+ var statusRaw = record.status
43
+ if (statusRaw == null || statusRaw === '') {
44
+ statusRaw = record.active ? 'active' : 'inactive'
45
+ }
46
+ var status =
47
+ typeof statusRaw === 'string'
48
+ ? statusRaw.trim().toLowerCase()
49
+ : String(statusRaw).toLowerCase()
50
+ var active = status === 'active'
51
+
52
+ // Payment fields
53
+ var paymentType = pickFirst(record.payment_type, record.paymentType) || 'one_time'
54
+ var recurringInterval = pickFirst(record.recurring_interval, record.recurringInterval) || null
55
+ var rawRecurringCount = pickFirst(record.recurring_interval_count, record.recurringIntervalCount)
56
+ var recurringIntervalCount = safeNumber(rawRecurringCount, null)
57
+
58
+ // Physical product fields
59
+ var sku = record.sku || null
60
+ var weight = safeNumber(record.weight, null)
61
+ var weightUnit = pickFirst(record.weight_unit, record.weightUnit) || null
62
+ var dimensions = record.dimensions || null
63
+
64
+ // Tags - parse as JSON array (handles JSON strings, arrays, comma-separated)
65
+ var tags = parseJsonArray(tagsRaw)
66
+
67
+ // Provider fields
68
+ var providerType = pickFirst(record.provider_type, record.providerType) || null
69
+ var providerProductId = pickFirst(record.provider_product_id, record.providerProductId) || null
70
+ var defaultPrice = pickFirst(record.default_price, record.defaultPrice) || null
71
+
72
+ // Metadata
73
+ var metadata = parseJsonObject(record.metadata)
74
+
75
+ // -------------------------------------------------------
76
+ // Image resolution (complex multi-step process)
77
+ // -------------------------------------------------------
78
+
79
+ // Step 1: Get raw image values
80
+ var rawImageUrl = pickFirst(record.image_url, record.mainImage, record.imageUrl)
81
+ var rawGalleryImages = pickFirst(record.gallery_images, record.galleryImages)
82
+ var legacyImages = Array.isArray(record.images)
83
+ ? record.images.filter(function(v) { return typeof v === 'string' })
84
+ : []
85
+
86
+ // Step 2: Parse gallery images from JSON
87
+ var parsedGalleryImages = parseJsonArray(rawGalleryImages)
88
+
89
+ // Step 3: Build combined raw images array
90
+ var allRawImages = []
91
+ if (rawImageUrl) allRawImages.push(rawImageUrl)
92
+ allRawImages = allRawImages.concat(parsedGalleryImages).concat(legacyImages)
93
+
94
+ // Step 4: Resolve all asset URLs first, then deduplicate
95
+ var resolvedAll = resolveAssetUrls(allRawImages, assetMap)
96
+ var resolvedImages = deduplicateStrings(resolvedAll)
97
+
98
+ // Step 5: Determine mainImage
99
+ var mainImage = rawImageUrl
100
+ ? resolveAssetUrl(rawImageUrl, assetMap)
101
+ : (resolvedImages.length > 0 ? resolvedImages[0] : null)
102
+
103
+ // Step 6: galleryImages = resolvedImages without mainImage
104
+ var galleryImages = mainImage
105
+ ? resolvedImages.filter(function(url) { return url !== mainImage })
106
+ : resolvedImages.slice()
107
+
108
+ // Step 7: images = full resolved deduplicated array
109
+ var images = resolvedImages
110
+
111
+ // Timestamps
112
+ var rawCreatedAt = pickFirst(record.created_at, record.createdAt, record.created)
113
+ var createdAt = normalizeTimestamp(rawCreatedAt)
114
+
115
+ var rawUpdatedAt = pickFirst(record.updated_at, record.updatedAt, record.updated)
116
+ var updatedAt = rawUpdatedAt != null ? normalizeTimestamp(rawUpdatedAt) : createdAt
117
+
118
+ // Computed: created = Unix seconds
119
+ var created = Math.floor(createdAt / 1000)
120
+
121
+ // Computed: outOfStock (emitted as a STRING — 'true' / 'false' — so it
122
+ // matches the strict equality check the AI generates on product
123
+ // cards/details pages: \`ecommerceProduct?.outOfStock === 'true'\`.
124
+ // A boolean here would silently fail that comparison (boolean !==
125
+ // string), leaving the Add to Cart button visible on out-of-stock
126
+ // products. NULL/NaN quantity → 'false' (treated as "unlimited
127
+ // stock", same as the upstream cart-availability rewriter).
128
+ var outOfStock = (quantity !== null && !isNaN(quantity) && quantity <= 0) ? 'true' : 'false'
129
+
130
+ return {
131
+ id: id,
132
+ name: name,
133
+ slug: slug,
134
+ description: description,
135
+ descriptionText: descriptionText,
136
+ price: price,
137
+ currency: currency,
138
+ currencySymbol: currencySymbol,
139
+ status: status,
140
+ active: active,
141
+ quantity: quantity,
142
+ paymentType: paymentType,
143
+ recurringInterval: recurringInterval,
144
+ recurringIntervalCount: recurringIntervalCount,
145
+ sku: sku,
146
+ weight: weight,
147
+ weightUnit: weightUnit,
148
+ dimensions: dimensions,
149
+ category: category,
150
+ tags: tags,
151
+ imageAlt: imageAlt,
152
+ providerType: providerType,
153
+ providerProductId: providerProductId,
154
+ default_price: defaultPrice,
155
+ metadata: metadata,
156
+ mainImage: mainImage,
157
+ imageUrl: mainImage,
158
+ image_url: mainImage,
159
+ galleryImages: galleryImages,
160
+ images: images,
161
+ createdAt: createdAt,
162
+ updatedAt: updatedAt,
163
+ created: created,
164
+ outOfStock: outOfStock,
165
+ }
166
+ }
167
+
168
+ function transformEcommerceProducts(records, options) {
169
+ if (!Array.isArray(records)) return []
170
+ return records.map(function(record) { return buildEcommerceProduct(record, options) })
171
+ }
172
+ `
173
+ }
@@ -0,0 +1,97 @@
1
+ import { generateSharedTransformationCode } from './shared-utils'
2
+ import { generateBlogPostTransformationCode } from './blog-post'
3
+ import { generateEcommerceProductTransformationCode } from './ecommerce-product'
4
+
5
+ export type TransformationType = 'blog-post' | 'ecommerce-product' | null
6
+
7
+ /**
8
+ * Detects which transformation type to apply based on the table name.
9
+ * Returns null if no transformation is needed.
10
+ */
11
+ export const detectTransformationType = (tableName: string): TransformationType => {
12
+ if (!tableName) {
13
+ return null
14
+ }
15
+ const lower = tableName.toLowerCase()
16
+ if (lower.includes('blog_posts') || lower.includes('blog-posts')) {
17
+ return 'blog-post'
18
+ }
19
+ if (lower.includes('products')) {
20
+ return 'ecommerce-product'
21
+ }
22
+ return null
23
+ }
24
+
25
+ /**
26
+ * Returns the full transformation code (shared utils + specific transformer)
27
+ * to be included in the generated data source fetcher file.
28
+ * Returns empty string if no transformation is needed for this table.
29
+ */
30
+ export const getTransformationCode = (tableName: string): string => {
31
+ const type = detectTransformationType(tableName)
32
+ if (!type) {
33
+ return ''
34
+ }
35
+
36
+ const shared = generateSharedTransformationCode()
37
+
38
+ switch (type) {
39
+ case 'blog-post':
40
+ return shared + generateBlogPostTransformationCode()
41
+ case 'ecommerce-product':
42
+ return shared + generateEcommerceProductTransformationCode()
43
+ default:
44
+ return ''
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Returns the JavaScript expression that transforms the data array.
50
+ * This expression assumes the data array variable is called `safeData`,
51
+ * that `getClient` is available for asset resolution, and that `req` is in scope.
52
+ * Returns null if no transformation is needed.
53
+ */
54
+ export const getTransformExpression = (tableName: string): string | null => {
55
+ const type = detectTransformationType(tableName)
56
+ if (!type) {
57
+ return null
58
+ }
59
+
60
+ switch (type) {
61
+ case 'blog-post':
62
+ return 'await transformRecords(safeData, getClient, req.query)'
63
+ case 'ecommerce-product':
64
+ return 'await transformRecords(safeData, getClient, req.query)'
65
+ default:
66
+ return null
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Returns the transform wrapper function code that handles asset map loading
72
+ * and calls the appropriate transformer.
73
+ * Returns empty string if no transformation is needed.
74
+ */
75
+ export const getTransformWrapperCode = (tableName: string): string => {
76
+ const type = detectTransformationType(tableName)
77
+ if (!type) {
78
+ return ''
79
+ }
80
+
81
+ const transformFn = type === 'blog-post' ? 'transformBlogPosts' : 'transformEcommerceProducts'
82
+
83
+ return `
84
+ async function transformRecords(records, getClientFn, reqQuery) {
85
+ var assetMap = {}
86
+ try {
87
+ assetMap = await getAssetMap(getClientFn)
88
+ } catch (e) {
89
+ // Asset resolution is best-effort; continue without it
90
+ }
91
+ var currentLanguage = (reqQuery && reqQuery.lang) || null
92
+ var mainLanguage = (reqQuery && reqQuery.mainLang) || null
93
+ var options = { assetMap: assetMap, currentLanguage: currentLanguage, mainLanguage: mainLanguage }
94
+ return ${transformFn}(records, options)
95
+ }
96
+ `
97
+ }