@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,239 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ export const validateMongoDBConfig = (
4
+ config: Record<string, unknown>
5
+ ): { isValid: boolean; error?: string } => {
6
+ if (!config || typeof config !== 'object') {
7
+ return { isValid: false, error: 'Config must be a valid object' }
8
+ }
9
+
10
+ // If connectionString is provided, validate it
11
+ if (config.connectionString) {
12
+ if (typeof config.connectionString !== 'string' || config.connectionString.trim() === '') {
13
+ return { isValid: false, error: 'Connection string must be a non-empty string' }
14
+ }
15
+
16
+ // Only validate format if it's not a secret reference that will be resolved at runtime
17
+ const connStr = config.connectionString as string
18
+ if (
19
+ !connStr.startsWith('teleporthq.secrets.') &&
20
+ !connStr.startsWith('mongodb://') &&
21
+ !connStr.startsWith('mongodb+srv://')
22
+ ) {
23
+ return { isValid: false, error: 'Invalid MongoDB connection string format' }
24
+ }
25
+
26
+ return { isValid: true }
27
+ }
28
+
29
+ // If no connectionString, host/port/database/etc will be used to build one
30
+ // Make host optional - if neither connectionString nor host is provided,
31
+ // the generator will need to handle it
32
+ if (config.host !== undefined && typeof config.host !== 'string') {
33
+ return { isValid: false, error: 'Host must be a string' }
34
+ }
35
+
36
+ if (!config.database || typeof config.database !== 'string') {
37
+ return { isValid: false, error: 'Database name is required' }
38
+ }
39
+
40
+ return { isValid: true }
41
+ }
42
+
43
+ interface MongoDBConfig {
44
+ connectionString?: string
45
+ host?: string
46
+ port?: number
47
+ username?: string
48
+ password?: string
49
+ database?: string
50
+ }
51
+
52
+ export const generateMongoDBFetcher = (
53
+ config: Record<string, unknown>,
54
+ tableName: string
55
+ ): string => {
56
+ const mongoConfig = config as MongoDBConfig
57
+ const hasUsername = mongoConfig?.username
58
+ const database = mongoConfig?.database
59
+
60
+ // Build connection string from parts if not provided
61
+ let connectionString = mongoConfig.connectionString
62
+ if (!connectionString) {
63
+ connectionString = `mongodb://${
64
+ hasUsername ? `${mongoConfig.username}:${mongoConfig.password}@` : ''
65
+ }${mongoConfig.host}:${mongoConfig.port || 27017}/${database}`
66
+ }
67
+
68
+ return `import { MongoClient, ObjectId } from 'mongodb'
69
+
70
+ export default async function handler(req, res) {
71
+ let client = null
72
+ try {
73
+ const url = ${replaceSecretReference(connectionString)}
74
+ client = new MongoClient(url, {
75
+ connectTimeoutMS: 30000,
76
+ serverSelectionTimeoutMS: 30000
77
+ })
78
+
79
+ await client.connect()
80
+ const db = client.db(${JSON.stringify(database)})
81
+ const collection = db.collection('${tableName}')
82
+
83
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
84
+
85
+ const filter = {}
86
+
87
+ if (query) {
88
+ let columns = []
89
+
90
+ if (queryColumns) {
91
+ // Use specified columns
92
+ columns = JSON.parse(queryColumns)
93
+ } else {
94
+ // Fallback: Get all field names from a sample document
95
+ try {
96
+ const sampleDoc = await db.collection(${JSON.stringify(tableName)}).findOne({})
97
+ if (sampleDoc) {
98
+ columns = Object.keys(sampleDoc).filter(key => key !== '_id')
99
+ }
100
+ } catch (schemaError) {
101
+ console.warn('Failed to fetch sample document for column names:', schemaError.message)
102
+ // Continue without search if we can't get columns
103
+ }
104
+ }
105
+
106
+ if (columns.length > 0) {
107
+ const orConditions = columns.map((col) => ({
108
+ [col]: { $regex: query, $options: 'i' }
109
+ }))
110
+ filter.$or = orConditions
111
+ }
112
+ }
113
+
114
+ if (filters) {
115
+ const parsedFilters = JSON.parse(filters)
116
+ Object.entries(parsedFilters).forEach(([key, value]) => {
117
+ if (key === '_id') {
118
+ if (Array.isArray(value)) {
119
+ filter[key] = {
120
+ $in: value.map((id) => (typeof id === 'string' ? new ObjectId(id) : id))
121
+ }
122
+ } else if (typeof value === 'string') {
123
+ filter[key] = new ObjectId(value)
124
+ } else {
125
+ filter[key] = value
126
+ }
127
+ } else if (Array.isArray(value)) {
128
+ filter[key] = { $in: value }
129
+ } else {
130
+ filter[key] = value
131
+ }
132
+ })
133
+ }
134
+
135
+ let cursor = collection.find(filter)
136
+
137
+ if (sortBy) {
138
+ const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
139
+ cursor = cursor.sort({ [sortBy]: sortOrderValue })
140
+ }
141
+
142
+ const limitValue = limit || perPage
143
+ const skipValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
144
+
145
+ if (skipValue !== undefined) {
146
+ cursor = cursor.skip(skipValue)
147
+ }
148
+
149
+ if (limitValue) {
150
+ cursor = cursor.limit(parseInt(limitValue))
151
+ }
152
+
153
+ const documents = await cursor.toArray()
154
+ const safeData = JSON.parse(JSON.stringify(documents))
155
+
156
+ return res.status(200).json({
157
+ success: true,
158
+ data: safeData,
159
+ timestamp: Date.now()
160
+ })
161
+ } catch (error) {
162
+ console.error('MongoDB fetch error:', error)
163
+ return res.status(500).json({
164
+ success: false,
165
+ error: error.message || 'Failed to fetch data',
166
+ timestamp: Date.now()
167
+ })
168
+ } finally {
169
+ if (client) {
170
+ await client.close()
171
+ }
172
+ }
173
+ }
174
+ `
175
+ }
176
+
177
+ // tslint:disable-next-line:variable-name
178
+ export const generateMongoDBCountFetcher = (_config: any, tableName: string): string => {
179
+ return `
180
+ async function getCount(req, res) {
181
+ const client = getClient()
182
+ const db = client.db()
183
+
184
+ try {
185
+ const { query, queryColumns, filters } = req.query
186
+ const collection = db.collection('${tableName}')
187
+ const filter = {}
188
+
189
+ if (query) {
190
+ let columns = []
191
+
192
+ if (queryColumns) {
193
+ // Use specified columns
194
+ columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
195
+ } else {
196
+ // Fallback: Get all field names from a sample document
197
+ try {
198
+ const sampleDoc = await collection.findOne({})
199
+ if (sampleDoc) {
200
+ columns = Object.keys(sampleDoc).filter(key => key !== '_id')
201
+ }
202
+ } catch (schemaError) {
203
+ console.warn('Failed to fetch sample document for column names:', schemaError.message)
204
+ // Continue without search if we can't get columns
205
+ }
206
+ }
207
+
208
+ if (columns.length > 0) {
209
+ filter.$or = columns.map(col => ({
210
+ [col]: { $regex: query, $options: 'i' }
211
+ }))
212
+ }
213
+ }
214
+
215
+ if (filters) {
216
+ const parsedFilters = JSON.parse(filters)
217
+ for (const f of parsedFilters) {
218
+ filter[f.column] = f.value
219
+ }
220
+ }
221
+
222
+ const count = await collection.countDocuments(filter)
223
+
224
+ return res.status(200).json({
225
+ success: true,
226
+ count: count,
227
+ timestamp: Date.now()
228
+ })
229
+ } catch (error) {
230
+ console.error('Error getting count:', error)
231
+ return res.status(500).json({
232
+ success: false,
233
+ error: error.message || 'Failed to get count',
234
+ timestamp: Date.now()
235
+ })
236
+ }
237
+ }
238
+ `
239
+ }
@@ -0,0 +1,237 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ interface MySQLConfig {
4
+ host?: string
5
+ port?: number
6
+ user?: string
7
+ username?: string
8
+ password?: string
9
+ database?: string
10
+ ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
11
+ sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
12
+ }
13
+
14
+ export const generateMySQLFetcher = (
15
+ config: Record<string, unknown>,
16
+ tableName: string
17
+ ): string => {
18
+ const mysqlConfig = config as MySQLConfig
19
+ const resolvedUser = mysqlConfig.user || mysqlConfig.username || null
20
+ const hasCustomSSLConfig = !!mysqlConfig.sslConfig
21
+ const defaultSSLEnabled = mysqlConfig.ssl !== false
22
+ const database = mysqlConfig.database
23
+
24
+ const sslConfigString = hasCustomSSLConfig
25
+ ? `{
26
+ ${mysqlConfig.sslConfig.ca ? `ca: ${replaceSecretReference(mysqlConfig.sslConfig.ca)},` : ''}
27
+ ${
28
+ mysqlConfig.sslConfig.cert
29
+ ? `cert: ${replaceSecretReference(mysqlConfig.sslConfig.cert)},`
30
+ : ''
31
+ }
32
+ ${
33
+ mysqlConfig.sslConfig.key
34
+ ? `key: ${replaceSecretReference(mysqlConfig.sslConfig.key)},`
35
+ : ''
36
+ }
37
+ rejectUnauthorized: ${
38
+ mysqlConfig.sslConfig.rejectUnauthorized !== undefined
39
+ ? mysqlConfig.sslConfig.rejectUnauthorized
40
+ : true
41
+ }
42
+ }`
43
+ : defaultSSLEnabled
44
+ ? `{ rejectUnauthorized: true }`
45
+ : 'false'
46
+
47
+ return `import mysql from 'mysql2/promise'
48
+
49
+ let pool = null
50
+
51
+ const getPool = () => {
52
+ if (pool) return pool
53
+
54
+ pool = mysql.createPool({
55
+ host: ${JSON.stringify(mysqlConfig.host)},
56
+ port: ${mysqlConfig.port || 3306},
57
+ user: ${resolvedUser !== null ? JSON.stringify(resolvedUser) : 'undefined'},
58
+ password: ${replaceSecretReference(mysqlConfig.password)},
59
+ database: ${JSON.stringify(mysqlConfig.database)},
60
+ ssl: ${sslConfigString}
61
+ })
62
+
63
+ return pool
64
+ }
65
+
66
+ export default async function handler(req, res) {
67
+ try {
68
+ const pool = getPool()
69
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
70
+
71
+ const conditions = []
72
+ const queryParams = []
73
+
74
+ if (query) {
75
+ let columns = []
76
+
77
+ if (queryColumns) {
78
+ // Use specified columns
79
+ columns = JSON.parse(queryColumns)
80
+ } else {
81
+ // Fallback: Get all columns from information_schema
82
+ try {
83
+ const [schemaRows] = await pool.promise().query(
84
+ \`SELECT COLUMN_NAME FROM information_schema.COLUMNS
85
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
86
+ ORDER BY ORDINAL_POSITION\`,
87
+ [${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
88
+ )
89
+ columns = schemaRows.map(row => row.COLUMN_NAME)
90
+ } catch (schemaError) {
91
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
92
+ // Continue without search if we can't get columns
93
+ }
94
+ }
95
+
96
+ if (columns.length > 0) {
97
+ const searchConditions = columns.map((col) => \`CAST(\${mysql.escapeId(col)} AS CHAR) LIKE ?\`)
98
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
99
+ conditions.push(\`(\${searchConditions.join(' OR ')})\`)
100
+ }
101
+ }
102
+
103
+ if (filters) {
104
+ const parsedFilters = JSON.parse(filters)
105
+ Object.entries(parsedFilters).forEach(([key, value]) => {
106
+ if (Array.isArray(value)) {
107
+ const placeholders = value.map(() => '?').join(', ')
108
+ queryParams.push(...value)
109
+ conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
110
+ } else {
111
+ conditions.push(\`\${mysql.escapeId(key)} = ?\`)
112
+ queryParams.push(value)
113
+ }
114
+ })
115
+ }
116
+
117
+ let sql = \`SELECT * FROM \${mysql.escapeId('${tableName}')}\`
118
+
119
+ if (conditions.length > 0) {
120
+ sql += \` WHERE \${conditions.join(' AND ')}\`
121
+ }
122
+
123
+ if (sortBy) {
124
+ sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${sortOrder?.toUpperCase() || 'ASC'}\`
125
+ }
126
+
127
+ const limitValue = limit || perPage
128
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
129
+
130
+ if (limitValue) {
131
+ sql += \` LIMIT \${limitValue}\`
132
+ }
133
+
134
+ if (offsetValue !== undefined) {
135
+ sql += \` OFFSET \${offsetValue}\`
136
+ }
137
+
138
+ const [rows] = await pool.query(sql, queryParams)
139
+ const rowArray = Array.isArray(rows) ? rows : []
140
+ const plainRows = rowArray.map((row) =>
141
+ row && typeof row.toJSON === 'function' ? row.toJSON() : row
142
+ )
143
+ const safeData = JSON.parse(JSON.stringify(plainRows))
144
+
145
+ return res.status(200).json({
146
+ success: true,
147
+ data: safeData,
148
+ timestamp: Date.now()
149
+ })
150
+ } catch (error) {
151
+ console.error('MySQL fetch error:', error)
152
+ return res.status(500).json({
153
+ success: false,
154
+ error: error.message || 'Failed to fetch data',
155
+ timestamp: Date.now()
156
+ })
157
+ }
158
+ }
159
+ `
160
+ }
161
+
162
+ export const generateMySQLCountFetcher = (
163
+ config: Record<string, unknown>,
164
+ tableName: string
165
+ ): string => {
166
+ const mysqlConfig = config as MySQLConfig
167
+
168
+ return `
169
+ async function getCount(req, res) {
170
+ const connection = getConnection()
171
+
172
+ try {
173
+ const { query, queryColumns, filters } = req.query
174
+ const conditions = []
175
+ const queryParams = []
176
+
177
+ if (query) {
178
+ let columns = []
179
+
180
+ if (queryColumns) {
181
+ // Use specified columns
182
+ columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
183
+ } else {
184
+ // Fallback: Get all columns from information_schema
185
+ try {
186
+ const [schemaRows] = await connection.execute(
187
+ \`SELECT COLUMN_NAME FROM information_schema.COLUMNS
188
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
189
+ ORDER BY ORDINAL_POSITION\`,
190
+ [${JSON.stringify(mysqlConfig.database)}, ${JSON.stringify(tableName)}]
191
+ )
192
+ columns = schemaRows.map(row => row.COLUMN_NAME)
193
+ } catch (schemaError) {
194
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
195
+ // Continue without search if we can't get columns
196
+ }
197
+ }
198
+
199
+ if (columns.length > 0) {
200
+ const searchConditions = columns.map(col => \`CAST(\${col} AS CHAR) LIKE ?\`).join(' OR ')
201
+ conditions.push(\`(\${searchConditions})\`)
202
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
203
+ }
204
+ }
205
+
206
+ if (filters) {
207
+ const parsedFilters = JSON.parse(filters)
208
+ for (const filter of parsedFilters) {
209
+ conditions.push(\`\${filter.column} \${filter.operator} ?\`)
210
+ queryParams.push(filter.value)
211
+ }
212
+ }
213
+
214
+ let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
215
+ if (conditions.length > 0) {
216
+ countSql += \` WHERE \${conditions.join(' AND ')}\`
217
+ }
218
+
219
+ const [rows] = await connection.execute(countSql, queryParams)
220
+ const count = rows[0].count
221
+
222
+ return res.status(200).json({
223
+ success: true,
224
+ count: count,
225
+ timestamp: Date.now()
226
+ })
227
+ } catch (error) {
228
+ console.error('Error getting count:', error)
229
+ return res.status(500).json({
230
+ success: false,
231
+ error: error.message || 'Failed to get count',
232
+ timestamp: Date.now()
233
+ })
234
+ }
235
+ }
236
+ `
237
+ }