@teleporthq/teleport-plugin-next-data-source 0.40.15

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 (240) hide show
  1. package/ARRAY_MAPPER_PAGINATION.md +1128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +40 -0
  4. package/SEARCH_IMPLEMENTATION_SUMMARY.md +983 -0
  5. package/__tests__/fetchers.test.ts +545 -0
  6. package/__tests__/integration.test.ts +561 -0
  7. package/__tests__/mocks.ts +241 -0
  8. package/__tests__/pagination.test.ts +31 -0
  9. package/__tests__/plugin.test.ts +577 -0
  10. package/__tests__/utils.test.ts +430 -0
  11. package/__tests__/validation.test.ts +348 -0
  12. package/dist/cjs/array-mapper-pagination.d.ts +32 -0
  13. package/dist/cjs/array-mapper-pagination.d.ts.map +1 -0
  14. package/dist/cjs/array-mapper-pagination.js +77 -0
  15. package/dist/cjs/array-mapper-pagination.js.map +1 -0
  16. package/dist/cjs/count-fetchers.d.ts +12 -0
  17. package/dist/cjs/count-fetchers.d.ts.map +1 -0
  18. package/dist/cjs/count-fetchers.js +46 -0
  19. package/dist/cjs/count-fetchers.js.map +1 -0
  20. package/dist/cjs/data-source-fetchers.d.ts +14 -0
  21. package/dist/cjs/data-source-fetchers.d.ts.map +1 -0
  22. package/dist/cjs/data-source-fetchers.js +185 -0
  23. package/dist/cjs/data-source-fetchers.js.map +1 -0
  24. package/dist/cjs/fetchers/airtable.d.ts +6 -0
  25. package/dist/cjs/fetchers/airtable.d.ts.map +1 -0
  26. package/dist/cjs/fetchers/airtable.js +27 -0
  27. package/dist/cjs/fetchers/airtable.js.map +1 -0
  28. package/dist/cjs/fetchers/clickhouse.d.ts +6 -0
  29. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -0
  30. package/dist/cjs/fetchers/clickhouse.js +29 -0
  31. package/dist/cjs/fetchers/clickhouse.js.map +1 -0
  32. package/dist/cjs/fetchers/csv-file.d.ts +7 -0
  33. package/dist/cjs/fetchers/csv-file.d.ts.map +1 -0
  34. package/dist/cjs/fetchers/csv-file.js +36 -0
  35. package/dist/cjs/fetchers/csv-file.js.map +1 -0
  36. package/dist/cjs/fetchers/firestore.d.ts +6 -0
  37. package/dist/cjs/fetchers/firestore.d.ts.map +1 -0
  38. package/dist/cjs/fetchers/firestore.js +35 -0
  39. package/dist/cjs/fetchers/firestore.js.map +1 -0
  40. package/dist/cjs/fetchers/google-sheets.d.ts +6 -0
  41. package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -0
  42. package/dist/cjs/fetchers/google-sheets.js +30 -0
  43. package/dist/cjs/fetchers/google-sheets.js.map +1 -0
  44. package/dist/cjs/fetchers/index.d.ts +17 -0
  45. package/dist/cjs/fetchers/index.d.ts.map +1 -0
  46. package/dist/cjs/fetchers/index.js +56 -0
  47. package/dist/cjs/fetchers/index.js.map +1 -0
  48. package/dist/cjs/fetchers/javascript.d.ts +7 -0
  49. package/dist/cjs/fetchers/javascript.d.ts.map +1 -0
  50. package/dist/cjs/fetchers/javascript.js +40 -0
  51. package/dist/cjs/fetchers/javascript.js.map +1 -0
  52. package/dist/cjs/fetchers/mariadb.d.ts +3 -0
  53. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -0
  54. package/dist/cjs/fetchers/mariadb.js +23 -0
  55. package/dist/cjs/fetchers/mariadb.js.map +1 -0
  56. package/dist/cjs/fetchers/mongodb.d.ts +7 -0
  57. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -0
  58. package/dist/cjs/fetchers/mongodb.js +52 -0
  59. package/dist/cjs/fetchers/mongodb.js.map +1 -0
  60. package/dist/cjs/fetchers/mysql.d.ts +3 -0
  61. package/dist/cjs/fetchers/mysql.d.ts.map +1 -0
  62. package/dist/cjs/fetchers/mysql.js +30 -0
  63. package/dist/cjs/fetchers/mysql.js.map +1 -0
  64. package/dist/cjs/fetchers/postgresql.d.ts +3 -0
  65. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -0
  66. package/dist/cjs/fetchers/postgresql.js +25 -0
  67. package/dist/cjs/fetchers/postgresql.js.map +1 -0
  68. package/dist/cjs/fetchers/redis.d.ts +6 -0
  69. package/dist/cjs/fetchers/redis.d.ts.map +1 -0
  70. package/dist/cjs/fetchers/redis.js +46 -0
  71. package/dist/cjs/fetchers/redis.js.map +1 -0
  72. package/dist/cjs/fetchers/redshift.d.ts +2 -0
  73. package/dist/cjs/fetchers/redshift.d.ts.map +1 -0
  74. package/dist/cjs/fetchers/redshift.js +24 -0
  75. package/dist/cjs/fetchers/redshift.js.map +1 -0
  76. package/dist/cjs/fetchers/rest-api.d.ts +6 -0
  77. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -0
  78. package/dist/cjs/fetchers/rest-api.js +58 -0
  79. package/dist/cjs/fetchers/rest-api.js.map +1 -0
  80. package/dist/cjs/fetchers/static-collection.d.ts +7 -0
  81. package/dist/cjs/fetchers/static-collection.d.ts.map +1 -0
  82. package/dist/cjs/fetchers/static-collection.js +24 -0
  83. package/dist/cjs/fetchers/static-collection.js.map +1 -0
  84. package/dist/cjs/fetchers/supabase.d.ts +7 -0
  85. package/dist/cjs/fetchers/supabase.d.ts.map +1 -0
  86. package/dist/cjs/fetchers/supabase.js +42 -0
  87. package/dist/cjs/fetchers/supabase.js.map +1 -0
  88. package/dist/cjs/fetchers/turso.d.ts +6 -0
  89. package/dist/cjs/fetchers/turso.d.ts.map +1 -0
  90. package/dist/cjs/fetchers/turso.js +25 -0
  91. package/dist/cjs/fetchers/turso.js.map +1 -0
  92. package/dist/cjs/index.d.ts +9 -0
  93. package/dist/cjs/index.d.ts.map +1 -0
  94. package/dist/cjs/index.js +325 -0
  95. package/dist/cjs/index.js.map +1 -0
  96. package/dist/cjs/pagination-plugin.d.ts +5 -0
  97. package/dist/cjs/pagination-plugin.d.ts.map +1 -0
  98. package/dist/cjs/pagination-plugin.js +1484 -0
  99. package/dist/cjs/pagination-plugin.js.map +1 -0
  100. package/dist/cjs/pagination-with-count.d.ts +6 -0
  101. package/dist/cjs/pagination-with-count.d.ts.map +1 -0
  102. package/dist/cjs/pagination-with-count.js +63 -0
  103. package/dist/cjs/pagination-with-count.js.map +1 -0
  104. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  105. package/dist/cjs/utils.d.ts +31 -0
  106. package/dist/cjs/utils.d.ts.map +1 -0
  107. package/dist/cjs/utils.js +763 -0
  108. package/dist/cjs/utils.js.map +1 -0
  109. package/dist/cjs/validation.d.ts +5 -0
  110. package/dist/cjs/validation.d.ts.map +1 -0
  111. package/dist/cjs/validation.js +29 -0
  112. package/dist/cjs/validation.js.map +1 -0
  113. package/dist/esm/array-mapper-pagination.d.ts +32 -0
  114. package/dist/esm/array-mapper-pagination.d.ts.map +1 -0
  115. package/dist/esm/array-mapper-pagination.js +72 -0
  116. package/dist/esm/array-mapper-pagination.js.map +1 -0
  117. package/dist/esm/count-fetchers.d.ts +12 -0
  118. package/dist/esm/count-fetchers.d.ts.map +1 -0
  119. package/dist/esm/count-fetchers.js +35 -0
  120. package/dist/esm/count-fetchers.js.map +1 -0
  121. package/dist/esm/data-source-fetchers.d.ts +14 -0
  122. package/dist/esm/data-source-fetchers.d.ts.map +1 -0
  123. package/dist/esm/data-source-fetchers.js +179 -0
  124. package/dist/esm/data-source-fetchers.js.map +1 -0
  125. package/dist/esm/fetchers/airtable.d.ts +6 -0
  126. package/dist/esm/fetchers/airtable.d.ts.map +1 -0
  127. package/dist/esm/fetchers/airtable.js +22 -0
  128. package/dist/esm/fetchers/airtable.js.map +1 -0
  129. package/dist/esm/fetchers/clickhouse.d.ts +6 -0
  130. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -0
  131. package/dist/esm/fetchers/clickhouse.js +24 -0
  132. package/dist/esm/fetchers/clickhouse.js.map +1 -0
  133. package/dist/esm/fetchers/csv-file.d.ts +7 -0
  134. package/dist/esm/fetchers/csv-file.d.ts.map +1 -0
  135. package/dist/esm/fetchers/csv-file.js +30 -0
  136. package/dist/esm/fetchers/csv-file.js.map +1 -0
  137. package/dist/esm/fetchers/firestore.d.ts +6 -0
  138. package/dist/esm/fetchers/firestore.d.ts.map +1 -0
  139. package/dist/esm/fetchers/firestore.js +30 -0
  140. package/dist/esm/fetchers/firestore.js.map +1 -0
  141. package/dist/esm/fetchers/google-sheets.d.ts +6 -0
  142. package/dist/esm/fetchers/google-sheets.d.ts.map +1 -0
  143. package/dist/esm/fetchers/google-sheets.js +25 -0
  144. package/dist/esm/fetchers/google-sheets.js.map +1 -0
  145. package/dist/esm/fetchers/index.d.ts +17 -0
  146. package/dist/esm/fetchers/index.d.ts.map +1 -0
  147. package/dist/esm/fetchers/index.js +17 -0
  148. package/dist/esm/fetchers/index.js.map +1 -0
  149. package/dist/esm/fetchers/javascript.d.ts +7 -0
  150. package/dist/esm/fetchers/javascript.d.ts.map +1 -0
  151. package/dist/esm/fetchers/javascript.js +34 -0
  152. package/dist/esm/fetchers/javascript.js.map +1 -0
  153. package/dist/esm/fetchers/mariadb.d.ts +3 -0
  154. package/dist/esm/fetchers/mariadb.d.ts.map +1 -0
  155. package/dist/esm/fetchers/mariadb.js +18 -0
  156. package/dist/esm/fetchers/mariadb.js.map +1 -0
  157. package/dist/esm/fetchers/mongodb.d.ts +7 -0
  158. package/dist/esm/fetchers/mongodb.d.ts.map +1 -0
  159. package/dist/esm/fetchers/mongodb.js +46 -0
  160. package/dist/esm/fetchers/mongodb.js.map +1 -0
  161. package/dist/esm/fetchers/mysql.d.ts +3 -0
  162. package/dist/esm/fetchers/mysql.d.ts.map +1 -0
  163. package/dist/esm/fetchers/mysql.js +25 -0
  164. package/dist/esm/fetchers/mysql.js.map +1 -0
  165. package/dist/esm/fetchers/postgresql.d.ts +3 -0
  166. package/dist/esm/fetchers/postgresql.d.ts.map +1 -0
  167. package/dist/esm/fetchers/postgresql.js +20 -0
  168. package/dist/esm/fetchers/postgresql.js.map +1 -0
  169. package/dist/esm/fetchers/redis.d.ts +6 -0
  170. package/dist/esm/fetchers/redis.d.ts.map +1 -0
  171. package/dist/esm/fetchers/redis.js +41 -0
  172. package/dist/esm/fetchers/redis.js.map +1 -0
  173. package/dist/esm/fetchers/redshift.d.ts +2 -0
  174. package/dist/esm/fetchers/redshift.d.ts.map +1 -0
  175. package/dist/esm/fetchers/redshift.js +20 -0
  176. package/dist/esm/fetchers/redshift.js.map +1 -0
  177. package/dist/esm/fetchers/rest-api.d.ts +6 -0
  178. package/dist/esm/fetchers/rest-api.d.ts.map +1 -0
  179. package/dist/esm/fetchers/rest-api.js +53 -0
  180. package/dist/esm/fetchers/rest-api.js.map +1 -0
  181. package/dist/esm/fetchers/static-collection.d.ts +7 -0
  182. package/dist/esm/fetchers/static-collection.d.ts.map +1 -0
  183. package/dist/esm/fetchers/static-collection.js +18 -0
  184. package/dist/esm/fetchers/static-collection.js.map +1 -0
  185. package/dist/esm/fetchers/supabase.d.ts +7 -0
  186. package/dist/esm/fetchers/supabase.d.ts.map +1 -0
  187. package/dist/esm/fetchers/supabase.js +36 -0
  188. package/dist/esm/fetchers/supabase.js.map +1 -0
  189. package/dist/esm/fetchers/turso.d.ts +6 -0
  190. package/dist/esm/fetchers/turso.d.ts.map +1 -0
  191. package/dist/esm/fetchers/turso.js +20 -0
  192. package/dist/esm/fetchers/turso.js.map +1 -0
  193. package/dist/esm/index.d.ts +9 -0
  194. package/dist/esm/index.d.ts.map +1 -0
  195. package/dist/esm/index.js +306 -0
  196. package/dist/esm/index.js.map +1 -0
  197. package/dist/esm/pagination-plugin.d.ts +5 -0
  198. package/dist/esm/pagination-plugin.d.ts.map +1 -0
  199. package/dist/esm/pagination-plugin.js +1457 -0
  200. package/dist/esm/pagination-plugin.js.map +1 -0
  201. package/dist/esm/pagination-with-count.d.ts +6 -0
  202. package/dist/esm/pagination-with-count.d.ts.map +1 -0
  203. package/dist/esm/pagination-with-count.js +34 -0
  204. package/dist/esm/pagination-with-count.js.map +1 -0
  205. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  206. package/dist/esm/utils.d.ts +31 -0
  207. package/dist/esm/utils.d.ts.map +1 -0
  208. package/dist/esm/utils.js +722 -0
  209. package/dist/esm/utils.js.map +1 -0
  210. package/dist/esm/validation.d.ts +5 -0
  211. package/dist/esm/validation.d.ts.map +1 -0
  212. package/dist/esm/validation.js +25 -0
  213. package/dist/esm/validation.js.map +1 -0
  214. package/package.json +33 -0
  215. package/src/array-mapper-pagination.ts +113 -0
  216. package/src/count-fetchers.ts +99 -0
  217. package/src/data-source-fetchers.ts +313 -0
  218. package/src/fetchers/airtable.ts +153 -0
  219. package/src/fetchers/clickhouse.ts +127 -0
  220. package/src/fetchers/csv-file.ts +163 -0
  221. package/src/fetchers/firestore.ts +138 -0
  222. package/src/fetchers/google-sheets.ts +189 -0
  223. package/src/fetchers/index.ts +32 -0
  224. package/src/fetchers/javascript.ts +150 -0
  225. package/src/fetchers/mariadb.ts +230 -0
  226. package/src/fetchers/mongodb.ts +239 -0
  227. package/src/fetchers/mysql.ts +237 -0
  228. package/src/fetchers/postgresql.ts +247 -0
  229. package/src/fetchers/redis.ts +152 -0
  230. package/src/fetchers/redshift.ts +138 -0
  231. package/src/fetchers/rest-api.ts +148 -0
  232. package/src/fetchers/static-collection.ts +149 -0
  233. package/src/fetchers/supabase.ts +246 -0
  234. package/src/fetchers/turso.ts +131 -0
  235. package/src/index.ts +352 -0
  236. package/src/pagination-plugin.ts +2335 -0
  237. package/src/pagination-with-count.ts +89 -0
  238. package/src/utils.ts +1013 -0
  239. package/src/validation.ts +32 -0
  240. package/tsconfig.json +9 -0
@@ -0,0 +1,561 @@
1
+ import {
2
+ extractDataSourceIntoNextAPIFolder,
3
+ extractDataSourceIntoGetStaticProps,
4
+ } from '../src/utils'
5
+ import * as types from '@babel/types'
6
+ import { FileType } from '@teleporthq/teleport-types'
7
+ import {
8
+ createPostgreSQLDataSource,
9
+ createJavaScriptDataSource,
10
+ createDataSourceNode,
11
+ createComponentChunk,
12
+ createMockJSXElementWithResourceDef,
13
+ } from './mocks'
14
+
15
+ describe('extractDataSourceIntoNextAPIFolder', () => {
16
+ it('creates API route file with correct structure', () => {
17
+ const dataSource = createPostgreSQLDataSource('ds-test')
18
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
19
+
20
+ const jsxElement = createMockJSXElementWithResourceDef('ds-test', 'users')
21
+ const componentChunk = createComponentChunk()
22
+ componentChunk.meta!.nodesLookup = { 'ds-test': jsxElement }
23
+ componentChunk.content = jsxElement
24
+
25
+ const extractedResources: Record<string, any> = {}
26
+
27
+ extractDataSourceIntoNextAPIFolder(
28
+ node,
29
+ { 'ds-test': dataSource },
30
+ componentChunk,
31
+ extractedResources
32
+ )
33
+
34
+ const apiFiles = Object.keys(extractedResources).filter((key) => key.startsWith('api/'))
35
+ expect(apiFiles.length).toBeGreaterThan(0)
36
+
37
+ const apiFile = extractedResources[apiFiles[0]]
38
+ expect(apiFile.fileType).toBe(FileType.JS)
39
+ expect(apiFile.path).toContain('api')
40
+ expect(apiFile.content).toContain('export default')
41
+ })
42
+
43
+ it('adds fetchData attribute to JSX element', () => {
44
+ const dataSource = createPostgreSQLDataSource('ds-test')
45
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
46
+
47
+ const jsxElement = createMockJSXElementWithResourceDef('ds-test', 'users')
48
+ const componentChunk = createComponentChunk()
49
+ componentChunk.meta!.nodesLookup = { 'ds-test': jsxElement }
50
+ componentChunk.content = jsxElement
51
+
52
+ const extractedResources: Record<string, any> = {}
53
+
54
+ extractDataSourceIntoNextAPIFolder(
55
+ node,
56
+ { 'ds-test': dataSource },
57
+ componentChunk,
58
+ extractedResources
59
+ )
60
+
61
+ const fetchDataAttr = jsxElement.openingElement.attributes.find(
62
+ (attr) => (attr as types.JSXAttribute).name?.name === 'fetchData'
63
+ )
64
+
65
+ expect(fetchDataAttr).toBeDefined()
66
+ })
67
+
68
+ it('handles resource params in fetch URL', () => {
69
+ const dataSource = createPostgreSQLDataSource('ds-test')
70
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql', true)
71
+
72
+ const jsxElement = createMockJSXElementWithResourceDef('ds-test', 'users')
73
+ const componentChunk = createComponentChunk()
74
+ componentChunk.meta!.nodesLookup = { 'ds-test': jsxElement }
75
+ componentChunk.content = jsxElement
76
+
77
+ const extractedResources: Record<string, any> = {}
78
+
79
+ extractDataSourceIntoNextAPIFolder(
80
+ node,
81
+ { 'ds-test': dataSource },
82
+ componentChunk,
83
+ extractedResources
84
+ )
85
+
86
+ const fetchDataAttr = jsxElement.openingElement.attributes.find(
87
+ (attr) => (attr as types.JSXAttribute).name?.name === 'fetchData'
88
+ ) as types.JSXAttribute
89
+
90
+ expect(fetchDataAttr).toBeDefined()
91
+ const arrowFunc = (fetchDataAttr.value as types.JSXExpressionContainer)
92
+ .expression as types.ArrowFunctionExpression
93
+ expect(arrowFunc.params.length).toBeGreaterThan(0)
94
+ })
95
+
96
+ it('re-uses existing utils file for API route', () => {
97
+ const dataSource = createPostgreSQLDataSource('ds-test')
98
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
99
+
100
+ const jsxElement = createMockJSXElementWithResourceDef('ds-test', 'users')
101
+ const componentChunk = createComponentChunk()
102
+ componentChunk.meta!.nodesLookup = { 'ds-test': jsxElement }
103
+ componentChunk.content = jsxElement
104
+
105
+ const extractedResources = {
106
+ 'utils/postgresql-users-ds-test': {
107
+ fileName: 'postgresql-users-ds-test',
108
+ fileType: FileType.JS,
109
+ path: ['utils', 'data-sources'],
110
+ content: 'existing content',
111
+ },
112
+ }
113
+
114
+ extractDataSourceIntoNextAPIFolder(
115
+ node,
116
+ { 'ds-test': dataSource },
117
+ componentChunk,
118
+ extractedResources
119
+ )
120
+
121
+ const apiFile = Object.values(extractedResources).find((file: any) =>
122
+ file.path?.includes('api')
123
+ ) as any
124
+
125
+ expect(apiFile).toBeDefined()
126
+ expect(apiFile.content).toContain('import dataSourceModule')
127
+ })
128
+
129
+ it('handles invalid node content gracefully', () => {
130
+ const dataSource = createPostgreSQLDataSource('ds-test')
131
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
132
+ delete (node.content as any).resourceDefinition
133
+
134
+ const componentChunk = createComponentChunk()
135
+ const extractedResources: Record<string, any> = {}
136
+
137
+ expect(() => {
138
+ extractDataSourceIntoNextAPIFolder(
139
+ node,
140
+ { 'ds-test': dataSource },
141
+ componentChunk,
142
+ extractedResources
143
+ )
144
+ }).not.toThrow()
145
+
146
+ expect(Object.keys(extractedResources)).toHaveLength(0)
147
+ })
148
+
149
+ it('handles missing data source gracefully', () => {
150
+ const node = createDataSourceNode('ds-missing', 'users', 'postgresql')
151
+
152
+ const jsxElement = createMockJSXElementWithResourceDef('ds-missing', 'users')
153
+ const componentChunk = createComponentChunk()
154
+ componentChunk.meta!.nodesLookup = { 'ds-missing': jsxElement }
155
+ componentChunk.content = jsxElement
156
+
157
+ const extractedResources: Record<string, any> = {}
158
+
159
+ expect(() => {
160
+ extractDataSourceIntoNextAPIFolder(node, {}, componentChunk, extractedResources)
161
+ }).not.toThrow()
162
+
163
+ expect(Object.keys(extractedResources)).toHaveLength(0)
164
+ })
165
+
166
+ it('handles JSX element not found gracefully', () => {
167
+ const dataSource = createPostgreSQLDataSource('ds-test')
168
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
169
+
170
+ const componentChunk = createComponentChunk()
171
+ componentChunk.meta!.nodesLookup = {}
172
+
173
+ const extractedResources: Record<string, any> = {}
174
+
175
+ expect(() => {
176
+ extractDataSourceIntoNextAPIFolder(
177
+ node,
178
+ { 'ds-test': dataSource },
179
+ componentChunk,
180
+ extractedResources
181
+ )
182
+ }).not.toThrow()
183
+ })
184
+
185
+ it('skips nodes that already have fetchData attribute', () => {
186
+ const dataSource = createPostgreSQLDataSource('ds-test')
187
+ const node = createDataSourceNode('ds-test', 'users', 'postgresql')
188
+
189
+ const jsxElement = createMockJSXElementWithResourceDef('ds-test', 'users')
190
+ jsxElement.openingElement.attributes.push(
191
+ types.jsxAttribute(
192
+ types.jsxIdentifier('fetchData'),
193
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], types.blockStatement([])))
194
+ )
195
+ )
196
+
197
+ const componentChunk = createComponentChunk()
198
+ componentChunk.meta!.nodesLookup = { 'ds-test': jsxElement }
199
+ componentChunk.content = jsxElement
200
+
201
+ const extractedResources: Record<string, any> = {}
202
+ const initialAttrCount = jsxElement.openingElement.attributes.length
203
+
204
+ extractDataSourceIntoNextAPIFolder(
205
+ node,
206
+ { 'ds-test': dataSource },
207
+ componentChunk,
208
+ extractedResources
209
+ )
210
+
211
+ expect(jsxElement.openingElement.attributes.length).toBe(initialAttrCount)
212
+ expect(Object.keys(extractedResources)).toHaveLength(0)
213
+ })
214
+ })
215
+
216
+ describe('extractDataSourceIntoGetStaticProps', () => {
217
+ it('creates utils file and getStaticProps chunk', () => {
218
+ const dataSource = createJavaScriptDataSource('ds-js')
219
+ const node = createDataSourceNode('ds-js', 'data', 'javascript', false)
220
+
221
+ const jsxElement = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
222
+ const componentChunk = createComponentChunk()
223
+ componentChunk.meta!.nodesLookup = { 'ds-js': jsxElement }
224
+ // Wrap in AST structure so traverseAST can find it
225
+ componentChunk.content = types.arrowFunctionExpression(
226
+ [],
227
+ types.blockStatement([types.returnStatement(jsxElement)])
228
+ )
229
+
230
+ const extractedResources: Record<string, any> = {}
231
+ const chunks: any[] = []
232
+ const dependencies: Record<string, any> = {}
233
+
234
+ const result = extractDataSourceIntoGetStaticProps(
235
+ node,
236
+ { 'ds-js': dataSource },
237
+ componentChunk,
238
+ null,
239
+ chunks,
240
+ extractedResources,
241
+ dependencies
242
+ )
243
+
244
+ expect(result.success).toBe(true)
245
+ expect(result.chunk).toBeDefined()
246
+
247
+ const utilsFiles = Object.keys(extractedResources).filter((key) => key.startsWith('utils/'))
248
+ expect(utilsFiles.length).toBeGreaterThan(0)
249
+
250
+ expect(Object.keys(dependencies).length).toBeGreaterThan(0)
251
+ })
252
+
253
+ it('adds initialData to all matching JSX elements', () => {
254
+ const dataSource = createJavaScriptDataSource('ds-js')
255
+ const node = createDataSourceNode('ds-js', 'data', 'javascript', false)
256
+
257
+ const jsxElement1 = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
258
+ const jsxElement2 = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
259
+
260
+ const componentChunk = createComponentChunk()
261
+ componentChunk.content = types.jsxFragment(
262
+ types.jsxOpeningFragment(),
263
+ types.jsxClosingFragment(),
264
+ [jsxElement1, jsxElement2]
265
+ )
266
+
267
+ const extractedResources: Record<string, any> = {}
268
+ const chunks: any[] = []
269
+ const dependencies: Record<string, any> = {}
270
+
271
+ extractDataSourceIntoGetStaticProps(
272
+ node,
273
+ { 'ds-js': dataSource },
274
+ componentChunk,
275
+ null,
276
+ chunks,
277
+ extractedResources,
278
+ dependencies
279
+ )
280
+
281
+ const hasInitialData1 = jsxElement1.openingElement.attributes.some(
282
+ (attr) => (attr as types.JSXAttribute).name?.name === 'initialData'
283
+ )
284
+ const hasInitialData2 = jsxElement2.openingElement.attributes.some(
285
+ (attr) => (attr as types.JSXAttribute).name?.name === 'initialData'
286
+ )
287
+
288
+ expect(hasInitialData1).toBe(true)
289
+ expect(hasInitialData2).toBe(true)
290
+ })
291
+
292
+ it('updates existing getStaticProps chunk', () => {
293
+ const dataSource = createJavaScriptDataSource('ds-js')
294
+ const node = createDataSourceNode('ds-js', 'data', 'javascript', false)
295
+
296
+ const jsxElement = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
297
+ const componentChunk = createComponentChunk()
298
+ componentChunk.meta!.nodesLookup = { 'ds-js': jsxElement }
299
+ // Wrap in AST structure so traverseAST can find it
300
+ componentChunk.content = types.arrowFunctionExpression(
301
+ [],
302
+ types.blockStatement([types.returnStatement(jsxElement)])
303
+ )
304
+
305
+ const tryBlock = types.tryStatement(
306
+ types.blockStatement([
307
+ types.returnStatement(
308
+ types.objectExpression([
309
+ types.objectProperty(types.identifier('props'), types.objectExpression([])),
310
+ types.objectProperty(types.identifier('revalidate'), types.numericLiteral(1)),
311
+ ])
312
+ ),
313
+ ]),
314
+ types.catchClause(
315
+ types.identifier('error'),
316
+ types.blockStatement([
317
+ types.returnStatement(
318
+ types.objectExpression([
319
+ types.objectProperty(types.identifier('props'), types.objectExpression([])),
320
+ ])
321
+ ),
322
+ ])
323
+ )
324
+ )
325
+
326
+ const existingChunk = {
327
+ name: 'getStaticProps',
328
+ type: 'ast' as const,
329
+ fileType: FileType.JS,
330
+ content: types.exportNamedDeclaration(
331
+ types.functionDeclaration(
332
+ types.identifier('getStaticProps'),
333
+ [types.identifier('context')],
334
+ types.blockStatement([tryBlock]),
335
+ false,
336
+ true
337
+ )
338
+ ),
339
+ linkAfter: ['jsx-component'],
340
+ meta: {},
341
+ }
342
+
343
+ const extractedResources: Record<string, any> = {}
344
+ const chunks: any[] = []
345
+ const dependencies: Record<string, any> = {}
346
+
347
+ const result = extractDataSourceIntoGetStaticProps(
348
+ node,
349
+ { 'ds-js': dataSource },
350
+ componentChunk,
351
+ existingChunk,
352
+ chunks,
353
+ extractedResources,
354
+ dependencies
355
+ )
356
+
357
+ expect(result.success).toBe(true)
358
+ expect(result.chunk).toBe(existingChunk)
359
+ })
360
+
361
+ it('handles parallel fetch with multiple data sources', () => {
362
+ const dataSource1 = createJavaScriptDataSource('ds-js-1')
363
+ const dataSource2 = createJavaScriptDataSource('ds-js-2')
364
+
365
+ const node1 = createDataSourceNode('ds-js-1', 'data1', 'javascript', false)
366
+ const node2 = createDataSourceNode('ds-js-2', 'data2', 'javascript', false)
367
+
368
+ const jsxElement1 = createMockJSXElementWithResourceDef('ds-js-1', 'data1', 'javascript')
369
+ const jsxElement2 = createMockJSXElementWithResourceDef('ds-js-2', 'data2', 'javascript')
370
+
371
+ const componentChunk = createComponentChunk()
372
+ componentChunk.content = types.jsxFragment(
373
+ types.jsxOpeningFragment(),
374
+ types.jsxClosingFragment(),
375
+ [jsxElement1, jsxElement2]
376
+ )
377
+
378
+ const extractedResources: Record<string, any> = {}
379
+ const chunks: any[] = []
380
+ const dependencies: Record<string, any> = {}
381
+
382
+ extractDataSourceIntoGetStaticProps(
383
+ node1,
384
+ { 'ds-js-1': dataSource1, 'ds-js-2': dataSource2 },
385
+ componentChunk,
386
+ null,
387
+ chunks,
388
+ extractedResources,
389
+ dependencies
390
+ )
391
+
392
+ const getStaticPropsChunk = chunks.find((c) => c.name === 'getStaticProps')
393
+
394
+ extractDataSourceIntoGetStaticProps(
395
+ node2,
396
+ { 'ds-js-1': dataSource1, 'ds-js-2': dataSource2 },
397
+ componentChunk,
398
+ getStaticPropsChunk,
399
+ chunks,
400
+ extractedResources,
401
+ dependencies
402
+ )
403
+
404
+ expect(getStaticPropsChunk.meta.parallelFetchData).toBeDefined()
405
+ expect(getStaticPropsChunk.meta.parallelFetchData.names.length).toBeGreaterThanOrEqual(2)
406
+ })
407
+
408
+ it('renames children to renderSuccess for SSR', () => {
409
+ const dataSource = createJavaScriptDataSource('ds-js')
410
+ const node = createDataSourceNode('ds-js', 'data', 'javascript', false)
411
+
412
+ const jsxElement = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
413
+ const initialAttrCount = jsxElement.openingElement.attributes.length
414
+ jsxElement.openingElement.attributes.push(
415
+ types.jsxAttribute(
416
+ types.jsxIdentifier('children'),
417
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], types.blockStatement([])))
418
+ )
419
+ )
420
+
421
+ const componentChunk = createComponentChunk()
422
+ componentChunk.meta!.nodesLookup = { 'ds-js': jsxElement }
423
+ // Wrap in AST structure so traverseAST can find it
424
+ componentChunk.content = types.arrowFunctionExpression(
425
+ [],
426
+ types.blockStatement([types.returnStatement(jsxElement)])
427
+ )
428
+
429
+ const extractedResources: Record<string, any> = {}
430
+ const chunks: any[] = []
431
+ const dependencies: Record<string, any> = {}
432
+
433
+ extractDataSourceIntoGetStaticProps(
434
+ node,
435
+ { 'ds-js': dataSource },
436
+ componentChunk,
437
+ null,
438
+ chunks,
439
+ extractedResources,
440
+ dependencies
441
+ )
442
+
443
+ const hasChildren = jsxElement.openingElement.attributes.some(
444
+ (attr) => (attr as types.JSXAttribute).name?.name === 'children'
445
+ )
446
+ const hasRenderSuccess = jsxElement.openingElement.attributes.some(
447
+ (attr) => (attr as types.JSXAttribute).name?.name === 'renderSuccess'
448
+ )
449
+
450
+ // Should have renamed children to renderSuccess
451
+ expect(hasChildren).toBe(false)
452
+ expect(hasRenderSuccess).toBe(true)
453
+ // Should have added initialData and persistDataDuringLoading
454
+ expect(jsxElement.openingElement.attributes.length).toBeGreaterThan(initialAttrCount)
455
+ })
456
+
457
+ it('handles invalid resource definition gracefully', () => {
458
+ const dataSource = createJavaScriptDataSource('ds-js')
459
+ const node = createDataSourceNode('ds-js', '', 'javascript', false)
460
+ delete (node.content as any).resourceDefinition
461
+
462
+ const componentChunk = createComponentChunk()
463
+ const extractedResources: Record<string, any> = {}
464
+ const chunks: any[] = []
465
+ const dependencies: Record<string, any> = {}
466
+
467
+ const result = extractDataSourceIntoGetStaticProps(
468
+ node,
469
+ { 'ds-js': dataSource },
470
+ componentChunk,
471
+ null,
472
+ chunks,
473
+ extractedResources,
474
+ dependencies
475
+ )
476
+
477
+ expect(result.success).toBe(false)
478
+ })
479
+
480
+ it('handles missing data source gracefully', () => {
481
+ const node = createDataSourceNode('ds-missing', '', 'javascript', false)
482
+
483
+ const jsxElement = createMockJSXElementWithResourceDef('ds-missing', '', 'javascript')
484
+ const componentChunk = createComponentChunk()
485
+ componentChunk.meta!.nodesLookup = { 'ds-missing': jsxElement }
486
+ componentChunk.content = jsxElement
487
+
488
+ const extractedResources: Record<string, any> = {}
489
+ const chunks: any[] = []
490
+ const dependencies: Record<string, any> = {}
491
+
492
+ const result = extractDataSourceIntoGetStaticProps(
493
+ node,
494
+ {},
495
+ componentChunk,
496
+ null,
497
+ chunks,
498
+ extractedResources,
499
+ dependencies
500
+ )
501
+
502
+ expect(result.success).toBe(false)
503
+ })
504
+
505
+ it('does not duplicate prop in return object', () => {
506
+ const dataSource = createJavaScriptDataSource('ds-js')
507
+ const node = createDataSourceNode('ds-js', 'data', 'javascript', false)
508
+
509
+ const jsxElement = createMockJSXElementWithResourceDef('ds-js', 'data', 'javascript')
510
+ const componentChunk = createComponentChunk()
511
+ componentChunk.content = jsxElement
512
+
513
+ const extractedResources: Record<string, any> = {}
514
+ const chunks: any[] = []
515
+ const dependencies: Record<string, any> = {}
516
+
517
+ extractDataSourceIntoGetStaticProps(
518
+ node,
519
+ { 'ds-js': dataSource },
520
+ componentChunk,
521
+ null,
522
+ chunks,
523
+ extractedResources,
524
+ dependencies
525
+ )
526
+
527
+ const getStaticPropsChunk = chunks.find((c) => c.name === 'getStaticProps')
528
+
529
+ extractDataSourceIntoGetStaticProps(
530
+ node,
531
+ { 'ds-js': dataSource },
532
+ componentChunk,
533
+ getStaticPropsChunk,
534
+ chunks,
535
+ extractedResources,
536
+ dependencies
537
+ )
538
+
539
+ const declaration = (getStaticPropsChunk.content as types.ExportNamedDeclaration)
540
+ .declaration as types.FunctionDeclaration
541
+ const tryBlock = declaration.body.body[0] as types.TryStatement
542
+ const returnStmt = tryBlock.block.body.find(
543
+ (stmt) => stmt.type === 'ReturnStatement'
544
+ ) as types.ReturnStatement
545
+ const returnObj = returnStmt.argument as types.ObjectExpression
546
+ const propsObj = (
547
+ returnObj.properties.find(
548
+ (p) =>
549
+ (p as types.ObjectProperty).key.type === 'Identifier' &&
550
+ ((p as types.ObjectProperty).key as types.Identifier).name === 'props'
551
+ ) as types.ObjectProperty
552
+ ).value as types.ObjectExpression
553
+
554
+ const propNames = propsObj.properties.map(
555
+ (p) => ((p as types.ObjectProperty).key as types.Identifier).name
556
+ )
557
+ const uniquePropNames = new Set(propNames)
558
+
559
+ expect(propNames.length).toBe(uniquePropNames.size)
560
+ })
561
+ })