@teleporthq/teleport-plugin-next-data-source 0.42.5 → 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/array-mapper-pagination.d.ts +5 -0
- package/dist/cjs/array-mapper-pagination.d.ts.map +1 -1
- package/dist/cjs/array-mapper-pagination.js.map +1 -1
- package/dist/cjs/array-mapper-registry.d.ts +84 -0
- package/dist/cjs/array-mapper-registry.d.ts.map +1 -0
- package/dist/cjs/array-mapper-registry.js +291 -0
- package/dist/cjs/array-mapper-registry.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +412 -19
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts +1 -3
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +895 -1405
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/array-mapper-pagination.d.ts +5 -0
- package/dist/esm/array-mapper-pagination.d.ts.map +1 -1
- package/dist/esm/array-mapper-pagination.js.map +1 -1
- package/dist/esm/array-mapper-registry.d.ts +84 -0
- package/dist/esm/array-mapper-registry.d.ts.map +1 -0
- package/dist/esm/array-mapper-registry.js +287 -0
- package/dist/esm/array-mapper-registry.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +402 -21
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts +1 -3
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +896 -1406
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/array-mapper-pagination.ts +5 -0
- package/src/array-mapper-registry.ts +408 -0
- package/src/index.ts +662 -5
- package/src/pagination-plugin.ts +1846 -2201
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
|
|
@@ -12,12 +17,307 @@ interface PaginationConfig {
|
|
|
12
17
|
perPageMap: Map<string, number>
|
|
13
18
|
searchConfigMap: Map<string, SearchConfig>
|
|
14
19
|
queryColumnsMap: Map<string, string[]>
|
|
20
|
+
limitMap: Map<string, number>
|
|
21
|
+
}
|
|
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
|
+
}
|
|
15
314
|
}
|
|
16
315
|
|
|
17
316
|
function extractPaginationConfigEarly(uidlNode: any, resources: any): PaginationConfig {
|
|
18
317
|
const perPageMap = new Map<string, number>()
|
|
19
318
|
const searchConfigMap = new Map<string, SearchConfig>()
|
|
20
319
|
const queryColumnsMap = new Map<string, string[]>()
|
|
320
|
+
const limitMap = new Map<string, number>()
|
|
21
321
|
|
|
22
322
|
const dataSourceToRenderProp = new Map<string, string>()
|
|
23
323
|
|
|
@@ -47,6 +347,19 @@ function extractPaginationConfigEarly(uidlNode: any, resources: any): Pagination
|
|
|
47
347
|
queryColumnsMap.set(renderProp, queryColumnsValue.content)
|
|
48
348
|
}
|
|
49
349
|
}
|
|
350
|
+
|
|
351
|
+
// Extract limit parameter from resource.params.limit for plain array mappers
|
|
352
|
+
if (node.content?.resource?.params?.limit) {
|
|
353
|
+
const limitValue = node.content.resource.params.limit
|
|
354
|
+
if (limitValue.type === 'static' && typeof limitValue.content === 'number') {
|
|
355
|
+
limitMap.set(renderProp, limitValue.content)
|
|
356
|
+
}
|
|
357
|
+
} else if (resources?.items?.[resourceId]?.params?.limit) {
|
|
358
|
+
const limitValue = resources.items[resourceId].params.limit
|
|
359
|
+
if (limitValue.type === 'static' && typeof limitValue.content === 'number') {
|
|
360
|
+
limitMap.set(renderProp, limitValue.content)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
50
363
|
}
|
|
51
364
|
|
|
52
365
|
if (node.type === 'cms-list-repeater') {
|
|
@@ -110,7 +423,7 @@ function extractPaginationConfigEarly(uidlNode: any, resources: any): Pagination
|
|
|
110
423
|
|
|
111
424
|
traverse(uidlNode)
|
|
112
425
|
|
|
113
|
-
return { perPageMap, searchConfigMap, queryColumnsMap }
|
|
426
|
+
return { perPageMap, searchConfigMap, queryColumnsMap, limitMap }
|
|
114
427
|
}
|
|
115
428
|
|
|
116
429
|
export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () => {
|
|
@@ -146,6 +459,7 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
146
459
|
perPageMap: new Map<string, number>(),
|
|
147
460
|
searchConfigMap: new Map<string, SearchConfig>(),
|
|
148
461
|
queryColumnsMap: new Map<string, string[]>(),
|
|
462
|
+
limitMap: new Map<string, number>(),
|
|
149
463
|
}
|
|
150
464
|
}
|
|
151
465
|
|
|
@@ -160,11 +474,18 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
160
474
|
pageConfig.queryColumnsMap.forEach((queryColumns, dataSourceId) => {
|
|
161
475
|
opts.paginationConfig.queryColumnsMap.set(dataSourceId, queryColumns)
|
|
162
476
|
})
|
|
477
|
+
pageConfig.limitMap.forEach((limit, dataSourceId) => {
|
|
478
|
+
opts.paginationConfig.limitMap.set(dataSourceId, limit)
|
|
479
|
+
})
|
|
163
480
|
|
|
164
481
|
let getStaticPropsChunk = chunks.find((chunk) => chunk.name === 'getStaticProps')
|
|
165
482
|
|
|
166
483
|
// Track which dataSourceId + tableName combinations have been processed
|
|
167
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
|
|
168
489
|
|
|
169
490
|
UIDLUtils.traverseNodes(uidl.node, (node) => {
|
|
170
491
|
// Data source nodes can be either:
|
|
@@ -238,6 +559,32 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
238
559
|
getStaticPropsChunk = result.chunk
|
|
239
560
|
// Mark this dataSource + table as processed
|
|
240
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
|
+
}
|
|
241
588
|
}
|
|
242
589
|
} else {
|
|
243
590
|
extractDataSourceIntoNextAPIFolder(
|
|
@@ -249,6 +596,172 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
249
596
|
}
|
|
250
597
|
})
|
|
251
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
|
+
|
|
252
765
|
const paginationPlugin = createNextArrayMapperPaginationPlugin()
|
|
253
766
|
return paginationPlugin(structure)
|
|
254
767
|
}
|
|
@@ -258,7 +771,7 @@ export const createNextPagesDataSourcePlugin: ComponentPluginFactory<{}> = () =>
|
|
|
258
771
|
|
|
259
772
|
export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = () => {
|
|
260
773
|
const nextComponentDataSourcePlugin: ComponentPlugin = async (structure) => {
|
|
261
|
-
const { uidl, chunks, options } = structure
|
|
774
|
+
const { uidl, chunks, options, dependencies } = structure
|
|
262
775
|
|
|
263
776
|
// Early return if no options or dataSources
|
|
264
777
|
if (!options || !options.dataSources) {
|
|
@@ -279,6 +792,7 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
279
792
|
perPageMap: new Map<string, number>(),
|
|
280
793
|
searchConfigMap: new Map<string, SearchConfig>(),
|
|
281
794
|
queryColumnsMap: new Map<string, string[]>(),
|
|
795
|
+
limitMap: new Map<string, number>(),
|
|
282
796
|
}
|
|
283
797
|
}
|
|
284
798
|
|
|
@@ -293,6 +807,9 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
293
807
|
componentConfig.queryColumnsMap.forEach((queryColumns, dataSourceId) => {
|
|
294
808
|
opts.paginationConfig.queryColumnsMap.set(dataSourceId, queryColumns)
|
|
295
809
|
})
|
|
810
|
+
componentConfig.limitMap.forEach((limit, dataSourceId) => {
|
|
811
|
+
opts.paginationConfig.limitMap.set(dataSourceId, limit)
|
|
812
|
+
})
|
|
296
813
|
|
|
297
814
|
const componentChunk = chunks.find((chunk) => chunk.name === 'jsx-component')
|
|
298
815
|
if (!componentChunk) {
|
|
@@ -304,6 +821,9 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
304
821
|
return structure
|
|
305
822
|
}
|
|
306
823
|
|
|
824
|
+
// Track the first data source info for wrapping dataSourceData expressions
|
|
825
|
+
let firstDataSourceInfo: DataSourceInfo | null = null
|
|
826
|
+
|
|
307
827
|
UIDLUtils.traverseNodes(uidl.node, (node) => {
|
|
308
828
|
// Data source nodes can be either:
|
|
309
829
|
// 1. Direct: node.type === 'data-source-item' or 'data-source-list'
|
|
@@ -330,6 +850,34 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
330
850
|
return
|
|
331
851
|
}
|
|
332
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
|
+
|
|
333
881
|
extractDataSourceIntoNextAPIFolder(
|
|
334
882
|
dataSourceNode,
|
|
335
883
|
dataSources,
|
|
@@ -338,6 +886,115 @@ export const createNextComponentDataSourcePlugin: ComponentPluginFactory<{}> = (
|
|
|
338
886
|
)
|
|
339
887
|
})
|
|
340
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
|
+
|
|
341
998
|
const paginationPlugin = createNextArrayMapperPaginationPlugin()
|
|
342
999
|
return paginationPlugin(structure)
|
|
343
1000
|
}
|