@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.
Files changed (35) hide show
  1. package/dist/cjs/array-mapper-pagination.d.ts +5 -0
  2. package/dist/cjs/array-mapper-pagination.d.ts.map +1 -1
  3. package/dist/cjs/array-mapper-pagination.js.map +1 -1
  4. package/dist/cjs/array-mapper-registry.d.ts +84 -0
  5. package/dist/cjs/array-mapper-registry.d.ts.map +1 -0
  6. package/dist/cjs/array-mapper-registry.js +291 -0
  7. package/dist/cjs/array-mapper-registry.js.map +1 -0
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +412 -19
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/pagination-plugin.d.ts +1 -3
  12. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  13. package/dist/cjs/pagination-plugin.js +895 -1405
  14. package/dist/cjs/pagination-plugin.js.map +1 -1
  15. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  16. package/dist/esm/array-mapper-pagination.d.ts +5 -0
  17. package/dist/esm/array-mapper-pagination.d.ts.map +1 -1
  18. package/dist/esm/array-mapper-pagination.js.map +1 -1
  19. package/dist/esm/array-mapper-registry.d.ts +84 -0
  20. package/dist/esm/array-mapper-registry.d.ts.map +1 -0
  21. package/dist/esm/array-mapper-registry.js +287 -0
  22. package/dist/esm/array-mapper-registry.js.map +1 -0
  23. package/dist/esm/index.d.ts.map +1 -1
  24. package/dist/esm/index.js +402 -21
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/esm/pagination-plugin.d.ts +1 -3
  27. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  28. package/dist/esm/pagination-plugin.js +896 -1406
  29. package/dist/esm/pagination-plugin.js.map +1 -1
  30. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +2 -2
  32. package/src/array-mapper-pagination.ts +5 -0
  33. package/src/array-mapper-registry.ts +408 -0
  34. package/src/index.ts +662 -5
  35. 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 { extractDataSourceIntoNextAPIFolder, extractDataSourceIntoGetStaticProps } from './utils'
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
  }