@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/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
@@ -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
  }