@teleporthq/teleport-plugin-next-data-source 0.42.6 → 0.42.7
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/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +376 -5
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +366 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +638 -4
package/src/index.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ComponentPlugin, ComponentPluginFactory } from '@teleporthq/teleport-types'
|
|
2
|
-
import { UIDLUtils } from '@teleporthq/teleport-shared'
|
|
3
|
-
import {
|
|
1
|
+
import { ComponentPlugin, ComponentPluginFactory, FileType } from '@teleporthq/teleport-types'
|
|
2
|
+
import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
|
|
3
|
+
import {
|
|
4
|
+
extractDataSourceIntoNextAPIFolder,
|
|
5
|
+
extractDataSourceIntoGetStaticProps,
|
|
6
|
+
sanitizeFileName,
|
|
7
|
+
} from './utils'
|
|
4
8
|
import { createNextArrayMapperPaginationPlugin } from './pagination-plugin'
|
|
9
|
+
import * as types from '@babel/types'
|
|
5
10
|
|
|
6
11
|
interface SearchConfig {
|
|
7
12
|
searchEnabled: boolean
|
|
@@ -15,6 +20,299 @@ interface PaginationConfig {
|
|
|
15
20
|
limitMap: Map<string, number>
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
interface DataSourceInfo {
|
|
24
|
+
dataSourceId: string
|
|
25
|
+
tableName: string
|
|
26
|
+
dataSourceType: string
|
|
27
|
+
basePropKey: string
|
|
28
|
+
fileName: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface WrappedDataProviderInfo {
|
|
32
|
+
propKey: string
|
|
33
|
+
dataSourceInfo: DataSourceInfo
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface WrapContext {
|
|
37
|
+
counter: number
|
|
38
|
+
wrappedProviders: WrappedDataProviderInfo[]
|
|
39
|
+
isPage: boolean
|
|
40
|
+
fileName: string
|
|
41
|
+
existingPropKeys: Set<string>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Prefix used to clearly differentiate wrapped data source expression props
|
|
45
|
+
// from user-defined or standard renderPropIdentifier values
|
|
46
|
+
const WRAPPED_DS_EXPR_PREFIX = '__dsExpr_'
|
|
47
|
+
|
|
48
|
+
function containsDataSourceDataReference(astNode: any): boolean {
|
|
49
|
+
if (!astNode || typeof astNode !== 'object') {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (astNode.type === 'Identifier' && astNode.name === 'dataSourceData') {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const key of Object.keys(astNode)) {
|
|
58
|
+
const value = astNode[key]
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const item of value) {
|
|
61
|
+
if (containsDataSourceDataReference(item)) {
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
66
|
+
if (containsDataSourceDataReference(value)) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function replaceDataSourceDataWithRenderProp(astNode: any, renderPropName: string): void {
|
|
76
|
+
if (!astNode || typeof astNode !== 'object') {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (astNode.type === 'Identifier' && astNode.name === 'dataSourceData') {
|
|
81
|
+
astNode.name = renderPropName
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const key of Object.keys(astNode)) {
|
|
85
|
+
const value = astNode[key]
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
value.forEach((item) => replaceDataSourceDataWithRenderProp(item, renderPropName))
|
|
88
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
89
|
+
replaceDataSourceDataWithRenderProp(value, renderPropName)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wrapElementInDataProvider(
|
|
95
|
+
elementNode: types.JSXElement | types.JSXFragment,
|
|
96
|
+
dataSourceInfo: DataSourceInfo,
|
|
97
|
+
context: WrapContext
|
|
98
|
+
): types.JSXElement {
|
|
99
|
+
context.counter++
|
|
100
|
+
|
|
101
|
+
// Generate a unique prop key with clear prefix to avoid collision with user-defined names
|
|
102
|
+
// Format: __dsExpr_{basePropKey}_{counter}
|
|
103
|
+
// The double underscore prefix is a convention for internal/generated identifiers
|
|
104
|
+
let uniquePropKey = `${WRAPPED_DS_EXPR_PREFIX}${dataSourceInfo.basePropKey}_${context.counter}`
|
|
105
|
+
|
|
106
|
+
// Ensure uniqueness by checking against existing prop keys
|
|
107
|
+
while (context.existingPropKeys.has(uniquePropKey)) {
|
|
108
|
+
context.counter++
|
|
109
|
+
uniquePropKey = `${WRAPPED_DS_EXPR_PREFIX}${dataSourceInfo.basePropKey}_${context.counter}`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Mark this key as used
|
|
113
|
+
context.existingPropKeys.add(uniquePropKey)
|
|
114
|
+
|
|
115
|
+
replaceDataSourceDataWithRenderProp(elementNode, uniquePropKey)
|
|
116
|
+
|
|
117
|
+
context.wrappedProviders.push({
|
|
118
|
+
propKey: uniquePropKey,
|
|
119
|
+
dataSourceInfo,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const dataProviderNode = types.jsxElement(
|
|
123
|
+
types.jsxOpeningElement(types.jsxIdentifier('DataProvider'), [], false),
|
|
124
|
+
types.jsxClosingElement(types.jsxIdentifier('DataProvider')),
|
|
125
|
+
[],
|
|
126
|
+
false
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
dataProviderNode.openingElement.attributes.push(
|
|
130
|
+
types.jsxAttribute(
|
|
131
|
+
types.jsxIdentifier('resourceDefinition'),
|
|
132
|
+
types.jsxExpressionContainer(
|
|
133
|
+
types.objectExpression([
|
|
134
|
+
types.objectProperty(
|
|
135
|
+
types.stringLiteral('type'),
|
|
136
|
+
types.stringLiteral('external-data-source')
|
|
137
|
+
),
|
|
138
|
+
types.objectProperty(
|
|
139
|
+
types.stringLiteral('dataSourceId'),
|
|
140
|
+
types.stringLiteral(dataSourceInfo.dataSourceId)
|
|
141
|
+
),
|
|
142
|
+
types.objectProperty(
|
|
143
|
+
types.stringLiteral('tableName'),
|
|
144
|
+
types.stringLiteral(dataSourceInfo.tableName)
|
|
145
|
+
),
|
|
146
|
+
types.objectProperty(
|
|
147
|
+
types.stringLiteral('dataSourceType'),
|
|
148
|
+
types.stringLiteral(dataSourceInfo.dataSourceType)
|
|
149
|
+
),
|
|
150
|
+
])
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
dataProviderNode.openingElement.attributes.push(
|
|
156
|
+
types.jsxAttribute(
|
|
157
|
+
types.jsxIdentifier('name'),
|
|
158
|
+
types.jsxExpressionContainer(types.stringLiteral(uniquePropKey))
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const renderSuccessFunction = types.arrowFunctionExpression(
|
|
163
|
+
[types.identifier(uniquePropKey)],
|
|
164
|
+
elementNode
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
dataProviderNode.openingElement.attributes.push(
|
|
168
|
+
types.jsxAttribute(
|
|
169
|
+
types.jsxIdentifier('renderSuccess'),
|
|
170
|
+
types.jsxExpressionContainer(renderSuccessFunction)
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (context.isPage) {
|
|
175
|
+
dataProviderNode.openingElement.attributes.push(
|
|
176
|
+
types.jsxAttribute(
|
|
177
|
+
types.jsxIdentifier('initialData'),
|
|
178
|
+
types.jsxExpressionContainer(
|
|
179
|
+
types.memberExpression(types.identifier('props'), types.identifier(uniquePropKey))
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
dataProviderNode.openingElement.attributes.push(
|
|
185
|
+
types.jsxAttribute(
|
|
186
|
+
types.jsxIdentifier('persistDataDuringLoading'),
|
|
187
|
+
types.jsxExpressionContainer(types.booleanLiteral(true))
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
} else {
|
|
191
|
+
const fetchDataAST = types.arrowFunctionExpression(
|
|
192
|
+
[],
|
|
193
|
+
types.callExpression(
|
|
194
|
+
types.memberExpression(
|
|
195
|
+
types.callExpression(types.identifier('fetch'), [
|
|
196
|
+
types.stringLiteral(`/api/${dataSourceInfo.fileName}`),
|
|
197
|
+
types.objectExpression([
|
|
198
|
+
types.objectProperty(
|
|
199
|
+
types.identifier('headers'),
|
|
200
|
+
types.objectExpression([
|
|
201
|
+
types.objectProperty(
|
|
202
|
+
types.stringLiteral('Content-Type'),
|
|
203
|
+
types.stringLiteral('application/json')
|
|
204
|
+
),
|
|
205
|
+
])
|
|
206
|
+
),
|
|
207
|
+
]),
|
|
208
|
+
]),
|
|
209
|
+
types.identifier('then')
|
|
210
|
+
),
|
|
211
|
+
[
|
|
212
|
+
types.arrowFunctionExpression(
|
|
213
|
+
[types.identifier('res')],
|
|
214
|
+
types.callExpression(
|
|
215
|
+
types.memberExpression(
|
|
216
|
+
types.callExpression(
|
|
217
|
+
types.memberExpression(types.identifier('res'), types.identifier('json')),
|
|
218
|
+
[]
|
|
219
|
+
),
|
|
220
|
+
types.identifier('then')
|
|
221
|
+
),
|
|
222
|
+
[
|
|
223
|
+
types.arrowFunctionExpression(
|
|
224
|
+
[types.identifier('response')],
|
|
225
|
+
types.optionalMemberExpression(
|
|
226
|
+
types.identifier('response'),
|
|
227
|
+
types.identifier('data'),
|
|
228
|
+
false,
|
|
229
|
+
true
|
|
230
|
+
)
|
|
231
|
+
),
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
),
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
dataProviderNode.openingElement.attributes.push(
|
|
240
|
+
types.jsxAttribute(
|
|
241
|
+
types.jsxIdentifier('fetchData'),
|
|
242
|
+
types.jsxExpressionContainer(fetchDataAST)
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
dataProviderNode.openingElement.attributes.push(
|
|
247
|
+
types.jsxAttribute(
|
|
248
|
+
types.jsxIdentifier('persistDataDuringLoading'),
|
|
249
|
+
types.jsxExpressionContainer(types.booleanLiteral(true))
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return dataProviderNode
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function wrapDataSourceExpressionsInAttributes(
|
|
258
|
+
astNode: any,
|
|
259
|
+
dataSourceInfo: DataSourceInfo | null,
|
|
260
|
+
context: WrapContext,
|
|
261
|
+
dependencies: Record<string, any>
|
|
262
|
+
): void {
|
|
263
|
+
if (!astNode || typeof astNode !== 'object' || !dataSourceInfo) {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (astNode.type === 'JSXElement' && astNode.openingElement?.attributes) {
|
|
268
|
+
const attrs = astNode.openingElement.attributes as types.JSXAttribute[]
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
271
|
+
const attr = attrs[i]
|
|
272
|
+
if (
|
|
273
|
+
attr.type === 'JSXAttribute' &&
|
|
274
|
+
attr.value &&
|
|
275
|
+
attr.value.type === 'JSXExpressionContainer'
|
|
276
|
+
) {
|
|
277
|
+
const expr = attr.value.expression
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
(expr.type === 'JSXElement' || expr.type === 'JSXFragment') &&
|
|
281
|
+
containsDataSourceDataReference(expr)
|
|
282
|
+
) {
|
|
283
|
+
dependencies.DataProvider = {
|
|
284
|
+
type: 'package',
|
|
285
|
+
path: '@teleporthq/react-components',
|
|
286
|
+
version: 'latest',
|
|
287
|
+
meta: {
|
|
288
|
+
namedImport: true,
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const wrappedElement = wrapElementInDataProvider(
|
|
293
|
+
expr as types.JSXElement | types.JSXFragment,
|
|
294
|
+
dataSourceInfo,
|
|
295
|
+
context
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
attr.value = types.jsxExpressionContainer(wrappedElement)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const key of Object.keys(astNode)) {
|
|
305
|
+
const value = astNode[key]
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
value.forEach((item) =>
|
|
308
|
+
wrapDataSourceExpressionsInAttributes(item, dataSourceInfo, context, dependencies)
|
|
309
|
+
)
|
|
310
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
311
|
+
wrapDataSourceExpressionsInAttributes(value, dataSourceInfo, context, dependencies)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
18
316
|
function extractPaginationConfigEarly(uidlNode: any, resources: any): PaginationConfig {
|
|
19
317
|
const perPageMap = new Map<string, number>()
|
|
20
318
|
const searchConfigMap = new Map<string, SearchConfig>()
|
|
@@ -184,6 +482,10 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
184
482
|
|
|
185
483
|
// Track which dataSourceId + tableName combinations have been processed
|
|
186
484
|
const processedDataSources = new Set<string>()
|
|
485
|
+
// Track the first data source info for wrapping dataSourceData expressions
|
|
486
|
+
let firstDataSourceInfo: DataSourceInfo | null = null
|
|
487
|
+
// Track fetcher import name for wrapped providers
|
|
488
|
+
let fetcherImportName: string | null = null
|
|
187
489
|
|
|
188
490
|
UIDLUtils.traverseNodes(uidl.node, (node) => {
|
|
189
491
|
// Data source nodes can be either:
|
|
@@ -257,6 +559,32 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
257
559
|
getStaticPropsChunk = result.chunk
|
|
258
560
|
// Mark this dataSource + table as processed
|
|
259
561
|
processedDataSources.add(dataSourceKey)
|
|
562
|
+
|
|
563
|
+
// Track the first data source info for wrapping dataSourceData expressions
|
|
564
|
+
if (!firstDataSourceInfo) {
|
|
565
|
+
const dataSource = dataSources[resourceDef.dataSourceId]
|
|
566
|
+
if (dataSource) {
|
|
567
|
+
const sanitizedDsName = StringUtils.dashCaseToCamelCase(
|
|
568
|
+
sanitizeFileName(dataSource.name || resourceDef.dataSourceId)
|
|
569
|
+
)
|
|
570
|
+
const sanitizedTableName = StringUtils.dashCaseToCamelCase(
|
|
571
|
+
sanitizeFileName(resourceDef.tableName || 'data')
|
|
572
|
+
)
|
|
573
|
+
const fileName = StringUtils.camelCaseToDashCase(
|
|
574
|
+
`${sanitizeFileName(resourceDef.dataSourceType)}-${sanitizeFileName(
|
|
575
|
+
resourceDef.tableName || 'data'
|
|
576
|
+
)}-${sanitizeFileName(resourceDef.dataSourceId).substring(0, 8)}`
|
|
577
|
+
)
|
|
578
|
+
firstDataSourceInfo = {
|
|
579
|
+
dataSourceId: resourceDef.dataSourceId,
|
|
580
|
+
tableName: resourceDef.tableName || 'data',
|
|
581
|
+
dataSourceType: resourceDef.dataSourceType,
|
|
582
|
+
basePropKey: `${sanitizedDsName}_${sanitizedTableName}_data`,
|
|
583
|
+
fileName,
|
|
584
|
+
}
|
|
585
|
+
fetcherImportName = StringUtils.dashCaseToCamelCase(fileName)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
260
588
|
}
|
|
261
589
|
} else {
|
|
262
590
|
extractDataSourceIntoNextAPIFolder(
|
|
@@ -268,6 +596,172 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
268
596
|
}
|
|
269
597
|
})
|
|
270
598
|
|
|
599
|
+
// After processing all data source nodes, wrap any element props containing dataSourceData
|
|
600
|
+
// expressions in a DataProvider
|
|
601
|
+
if (firstDataSourceInfo && componentChunk.content) {
|
|
602
|
+
// Collect existing prop keys from getStaticProps to avoid collisions
|
|
603
|
+
const existingPropKeys = new Set<string>()
|
|
604
|
+
|
|
605
|
+
if (getStaticPropsChunk) {
|
|
606
|
+
try {
|
|
607
|
+
const funcDecl = (getStaticPropsChunk.content as types.ExportNamedDeclaration)
|
|
608
|
+
.declaration as types.FunctionDeclaration
|
|
609
|
+
const tryStmt = funcDecl.body.body.find(
|
|
610
|
+
(s) => s.type === 'TryStatement'
|
|
611
|
+
) as types.TryStatement
|
|
612
|
+
if (tryStmt) {
|
|
613
|
+
const retStmt = tryStmt.block.body.find(
|
|
614
|
+
(s) => s.type === 'ReturnStatement'
|
|
615
|
+
) as types.ReturnStatement
|
|
616
|
+
if (retStmt) {
|
|
617
|
+
const propsProp = (retStmt.argument as types.ObjectExpression).properties.find(
|
|
618
|
+
(p) => ((p as types.ObjectProperty).key as types.Identifier).name === 'props'
|
|
619
|
+
) as types.ObjectProperty
|
|
620
|
+
if (propsProp) {
|
|
621
|
+
const propsVal = propsProp.value as types.ObjectExpression
|
|
622
|
+
for (const prop of propsVal.properties) {
|
|
623
|
+
if (prop.type === 'ObjectProperty') {
|
|
624
|
+
const keyName =
|
|
625
|
+
(prop.key as types.Identifier).name ||
|
|
626
|
+
((prop.key as types.StringLiteral).value as string)
|
|
627
|
+
if (keyName) {
|
|
628
|
+
existingPropKeys.add(keyName)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
// Ignore errors in parsing existing props
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const wrapContext: WrapContext = {
|
|
641
|
+
counter: 0,
|
|
642
|
+
wrappedProviders: [],
|
|
643
|
+
isPage: true,
|
|
644
|
+
fileName: firstDataSourceInfo.fileName,
|
|
645
|
+
existingPropKeys,
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
wrapDataSourceExpressionsInAttributes(
|
|
649
|
+
componentChunk.content,
|
|
650
|
+
firstDataSourceInfo,
|
|
651
|
+
wrapContext,
|
|
652
|
+
dependencies
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
// Add wrapped providers to getStaticProps
|
|
656
|
+
if (wrapContext.wrappedProviders.length > 0 && getStaticPropsChunk && fetcherImportName) {
|
|
657
|
+
const functionDeclaration = (getStaticPropsChunk.content as types.ExportNamedDeclaration)
|
|
658
|
+
.declaration as types.FunctionDeclaration
|
|
659
|
+
const functionBody = functionDeclaration.body.body
|
|
660
|
+
const tryBlock = functionBody.find(
|
|
661
|
+
(subNode) => subNode.type === 'TryStatement'
|
|
662
|
+
) as types.TryStatement
|
|
663
|
+
|
|
664
|
+
if (tryBlock) {
|
|
665
|
+
const returnStatement = tryBlock.block.body.find(
|
|
666
|
+
(subNode) => subNode.type === 'ReturnStatement'
|
|
667
|
+
) as types.ReturnStatement
|
|
668
|
+
|
|
669
|
+
if (returnStatement) {
|
|
670
|
+
const propsObject = (
|
|
671
|
+
returnStatement.argument as types.ObjectExpression
|
|
672
|
+
).properties.find(
|
|
673
|
+
(property) =>
|
|
674
|
+
((property as types.ObjectProperty).key as types.Identifier).name === 'props'
|
|
675
|
+
) as types.ObjectProperty
|
|
676
|
+
|
|
677
|
+
const propsValue = propsObject.value as types.ObjectExpression
|
|
678
|
+
|
|
679
|
+
// Get the existing parallel fetch metadata
|
|
680
|
+
const meta = (getStaticPropsChunk.meta?.parallelFetchData as any) || {
|
|
681
|
+
names: [],
|
|
682
|
+
expressions: [],
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
for (const wrapped of wrapContext.wrappedProviders) {
|
|
686
|
+
// Add to parallel fetch
|
|
687
|
+
const fetchCallExpression = types.callExpression(
|
|
688
|
+
types.memberExpression(
|
|
689
|
+
types.identifier(fetcherImportName),
|
|
690
|
+
types.identifier('fetchData')
|
|
691
|
+
),
|
|
692
|
+
[types.objectExpression([])]
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
const safeFetchExpression = types.callExpression(
|
|
696
|
+
types.memberExpression(fetchCallExpression, types.identifier('catch')),
|
|
697
|
+
[
|
|
698
|
+
types.arrowFunctionExpression(
|
|
699
|
+
[types.identifier('error')],
|
|
700
|
+
types.blockStatement([
|
|
701
|
+
types.expressionStatement(
|
|
702
|
+
types.callExpression(
|
|
703
|
+
types.memberExpression(
|
|
704
|
+
types.identifier('console'),
|
|
705
|
+
types.identifier('error')
|
|
706
|
+
),
|
|
707
|
+
[
|
|
708
|
+
types.stringLiteral(`Error fetching ${wrapped.propKey}:`),
|
|
709
|
+
types.identifier('error'),
|
|
710
|
+
]
|
|
711
|
+
)
|
|
712
|
+
),
|
|
713
|
+
types.returnStatement(types.arrayExpression([])),
|
|
714
|
+
])
|
|
715
|
+
),
|
|
716
|
+
]
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
meta.names.push(wrapped.propKey)
|
|
720
|
+
meta.expressions.push(safeFetchExpression)
|
|
721
|
+
|
|
722
|
+
// Add prop to return object
|
|
723
|
+
propsValue.properties.push(
|
|
724
|
+
types.objectProperty(
|
|
725
|
+
types.identifier(wrapped.propKey),
|
|
726
|
+
types.identifier(wrapped.propKey),
|
|
727
|
+
false,
|
|
728
|
+
true
|
|
729
|
+
)
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Update the parallel fetch statement
|
|
734
|
+
if (meta.declaration) {
|
|
735
|
+
const existingIndex = tryBlock.block.body.indexOf(meta.declaration)
|
|
736
|
+
if (existingIndex !== -1) {
|
|
737
|
+
tryBlock.block.body.splice(existingIndex, 1)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const promiseAllCall = types.awaitExpression(
|
|
742
|
+
types.callExpression(
|
|
743
|
+
types.memberExpression(types.identifier('Promise'), types.identifier('all')),
|
|
744
|
+
[types.arrayExpression(meta.expressions)]
|
|
745
|
+
)
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
const arrayPattern = types.arrayPattern(
|
|
749
|
+
meta.names.map((name: string) => types.identifier(name))
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
meta.declaration = types.variableDeclaration('const', [
|
|
753
|
+
types.variableDeclarator(arrayPattern, promiseAllCall),
|
|
754
|
+
])
|
|
755
|
+
|
|
756
|
+
tryBlock.block.body.unshift(meta.declaration)
|
|
757
|
+
|
|
758
|
+
getStaticPropsChunk.meta = getStaticPropsChunk.meta || {}
|
|
759
|
+
getStaticPropsChunk.meta.parallelFetchData = meta
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
271
765
|
const paginationPlugin = createNextArrayMapperPaginationPlugin()
|
|
272
766
|
return paginationPlugin(structure)
|
|
273
767
|
}
|
|
@@ -277,7 +771,7 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
277
771
|
|
|
278
772
|
export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = () => {
|
|
279
773
|
const nextComponentDataSourcePlugin: ComponentPlugin = async (structure) => {
|
|
280
|
-
const { uidl, chunks, options } = structure
|
|
774
|
+
const { uidl, chunks, options, dependencies } = structure
|
|
281
775
|
|
|
282
776
|
// Early return if no options or dataSources
|
|
283
777
|
if (!options || !options.dataSources) {
|
|
@@ -327,6 +821,9 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
327
821
|
return structure
|
|
328
822
|
}
|
|
329
823
|
|
|
824
|
+
// Track the first data source info for wrapping dataSourceData expressions
|
|
825
|
+
let firstDataSourceInfo: DataSourceInfo | null = null
|
|
826
|
+
|
|
330
827
|
UIDLUtils.traverseNodes(uidl.node, (node) => {
|
|
331
828
|
// Data source nodes can be either:
|
|
332
829
|
// 1. Direct: node.type === 'data-source-item' or 'data-source-list'
|
|
@@ -353,6 +850,34 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
353
850
|
return
|
|
354
851
|
}
|
|
355
852
|
|
|
853
|
+
// Track the first data source info for wrapping dataSourceData expressions
|
|
854
|
+
if (!firstDataSourceInfo) {
|
|
855
|
+
const resourceDef = dataSourceNode.content?.resourceDefinition
|
|
856
|
+
if (resourceDef) {
|
|
857
|
+
const dataSource = dataSources[resourceDef.dataSourceId]
|
|
858
|
+
if (dataSource) {
|
|
859
|
+
const sanitizedDsName = StringUtils.dashCaseToCamelCase(
|
|
860
|
+
sanitizeFileName(dataSource.name || resourceDef.dataSourceId)
|
|
861
|
+
)
|
|
862
|
+
const sanitizedTableName = StringUtils.dashCaseToCamelCase(
|
|
863
|
+
sanitizeFileName(resourceDef.tableName || 'data')
|
|
864
|
+
)
|
|
865
|
+
const fileName = StringUtils.camelCaseToDashCase(
|
|
866
|
+
`${sanitizeFileName(resourceDef.dataSourceType)}-${sanitizeFileName(
|
|
867
|
+
resourceDef.tableName || 'data'
|
|
868
|
+
)}-${sanitizeFileName(resourceDef.dataSourceId).substring(0, 8)}`
|
|
869
|
+
)
|
|
870
|
+
firstDataSourceInfo = {
|
|
871
|
+
dataSourceId: resourceDef.dataSourceId,
|
|
872
|
+
tableName: resourceDef.tableName || 'data',
|
|
873
|
+
dataSourceType: resourceDef.dataSourceType,
|
|
874
|
+
basePropKey: `${sanitizedDsName}_${sanitizedTableName}_data`,
|
|
875
|
+
fileName,
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
356
881
|
extractDataSourceIntoNextAPIFolder(
|
|
357
882
|
dataSourceNode,
|
|
358
883
|
dataSources,
|
|
@@ -361,6 +886,115 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
361
886
|
)
|
|
362
887
|
})
|
|
363
888
|
|
|
889
|
+
// If no data source nodes were found but there are dataSources available,
|
|
890
|
+
// use the first available dataSource to create firstDataSourceInfo
|
|
891
|
+
// This handles the case where a component has element props with dataSourceData expressions
|
|
892
|
+
// but no explicit data source nodes
|
|
893
|
+
if (!firstDataSourceInfo && Object.keys(dataSources).length > 0) {
|
|
894
|
+
const firstDataSourceId = Object.keys(dataSources)[0]
|
|
895
|
+
const dataSource = dataSources[firstDataSourceId]
|
|
896
|
+
if (dataSource) {
|
|
897
|
+
// Check if there's already an existing utils module for this data source
|
|
898
|
+
// by looking for any file that matches the pattern {type}-*-{dsIdPrefix}
|
|
899
|
+
const dsIdPrefix = sanitizeFileName(firstDataSourceId).substring(0, 8)
|
|
900
|
+
const dsType = sanitizeFileName(dataSource.type)
|
|
901
|
+
let existingFileName: string | null = null
|
|
902
|
+
let existingTableName: string | null = null
|
|
903
|
+
|
|
904
|
+
for (const key of Object.keys(options.extractedResources)) {
|
|
905
|
+
if (key.startsWith('utils/') && key.includes(dsIdPrefix) && key.includes(dsType)) {
|
|
906
|
+
// Extract the file name from the key (e.g., 'utils/cockroachdb-users-0e678dd9' -> 'cockroachdb-users-0e678dd9')
|
|
907
|
+
existingFileName = key.replace('utils/', '').replace('.js', '')
|
|
908
|
+
// Extract table name from filename (e.g., 'cockroachdb-users-0e678dd9' -> 'users')
|
|
909
|
+
const parts = existingFileName.split('-')
|
|
910
|
+
if (parts.length >= 3) {
|
|
911
|
+
// Table name is between type and dsIdPrefix
|
|
912
|
+
existingTableName = parts.slice(1, -1).join('-')
|
|
913
|
+
}
|
|
914
|
+
break
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const tableName = existingTableName || 'data'
|
|
919
|
+
const sanitizedDsName = StringUtils.dashCaseToCamelCase(
|
|
920
|
+
sanitizeFileName(dataSource.name || firstDataSourceId)
|
|
921
|
+
)
|
|
922
|
+
const sanitizedTableName = StringUtils.dashCaseToCamelCase(sanitizeFileName(tableName))
|
|
923
|
+
const fileName =
|
|
924
|
+
existingFileName ||
|
|
925
|
+
StringUtils.camelCaseToDashCase(`${dsType}-${sanitizeFileName(tableName)}-${dsIdPrefix}`)
|
|
926
|
+
firstDataSourceInfo = {
|
|
927
|
+
dataSourceId: firstDataSourceId,
|
|
928
|
+
tableName,
|
|
929
|
+
dataSourceType: dataSource.type,
|
|
930
|
+
basePropKey: `${sanitizedDsName}_${sanitizedTableName}_data`,
|
|
931
|
+
fileName,
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// After processing all data source nodes, wrap any element props containing dataSourceData
|
|
937
|
+
// expressions in a DataProvider (for components, use fetchData instead of initialData)
|
|
938
|
+
if (firstDataSourceInfo && componentChunk.content) {
|
|
939
|
+
// For components, we don't have getStaticProps, so start with empty set
|
|
940
|
+
// The existingPropKeys will track keys within this component to avoid duplicates
|
|
941
|
+
const existingPropKeys = new Set<string>()
|
|
942
|
+
|
|
943
|
+
const wrapContext: WrapContext = {
|
|
944
|
+
counter: 0,
|
|
945
|
+
wrappedProviders: [],
|
|
946
|
+
isPage: false, // Components don't have getStaticProps
|
|
947
|
+
fileName: firstDataSourceInfo.fileName,
|
|
948
|
+
existingPropKeys,
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
wrapDataSourceExpressionsInAttributes(
|
|
952
|
+
componentChunk.content,
|
|
953
|
+
firstDataSourceInfo,
|
|
954
|
+
wrapContext,
|
|
955
|
+
dependencies
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
// For components, ensure API file is created for wrapped providers
|
|
959
|
+
if (wrapContext.wrappedProviders.length > 0) {
|
|
960
|
+
const dataSource = dataSources[firstDataSourceInfo.dataSourceId]
|
|
961
|
+
const fileName = firstDataSourceInfo.fileName
|
|
962
|
+
|
|
963
|
+
// First, ensure the utils data source module exists
|
|
964
|
+
if (dataSource && !options.extractedResources[`utils/${fileName}`]) {
|
|
965
|
+
const { generateDataSourceFetcherWithCore } = require('./data-source-fetchers')
|
|
966
|
+
try {
|
|
967
|
+
const fetcherCode = generateDataSourceFetcherWithCore(
|
|
968
|
+
dataSource,
|
|
969
|
+
firstDataSourceInfo.tableName
|
|
970
|
+
)
|
|
971
|
+
options.extractedResources[`utils/${fileName}`] = {
|
|
972
|
+
fileName,
|
|
973
|
+
fileType: FileType.JS,
|
|
974
|
+
path: ['utils', 'data-sources'],
|
|
975
|
+
content: fetcherCode,
|
|
976
|
+
}
|
|
977
|
+
} catch (error) {
|
|
978
|
+
// Silently fail
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Then, create the API route that imports from utils and exports the handler
|
|
983
|
+
if (!options.extractedResources[`api/${fileName}`]) {
|
|
984
|
+
const apiRouteCode = `import dataSourceModule from '../../utils/data-sources/${fileName}'
|
|
985
|
+
|
|
986
|
+
export default dataSourceModule.handler
|
|
987
|
+
`
|
|
988
|
+
options.extractedResources[`api/${fileName}`] = {
|
|
989
|
+
fileName,
|
|
990
|
+
fileType: FileType.JS,
|
|
991
|
+
path: ['pages', 'api'],
|
|
992
|
+
content: apiRouteCode,
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
364
998
|
const paginationPlugin = createNextArrayMapperPaginationPlugin()
|
|
365
999
|
return paginationPlugin(structure)
|
|
366
1000
|
}
|