@teleporthq/teleport-plugin-next-data-source 0.42.8 → 0.42.10

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 (160) hide show
  1. package/__tests__/csv-header-detection.test.ts +212 -0
  2. package/__tests__/validation.test.ts +33 -2
  3. package/dist/cjs/data-source-fetchers.d.ts +2 -2
  4. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  5. package/dist/cjs/data-source-fetchers.js +30 -7
  6. package/dist/cjs/data-source-fetchers.js.map +1 -1
  7. package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
  8. package/dist/cjs/fetchers/airtable.js +2 -2
  9. package/dist/cjs/fetchers/airtable.js.map +1 -1
  10. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
  11. package/dist/cjs/fetchers/clickhouse.js +1 -1
  12. package/dist/cjs/fetchers/clickhouse.js.map +1 -1
  13. package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
  14. package/dist/cjs/fetchers/csv-file.js +23 -3
  15. package/dist/cjs/fetchers/csv-file.js.map +1 -1
  16. package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
  17. package/dist/cjs/fetchers/firestore.js +1 -1
  18. package/dist/cjs/fetchers/firestore.js.map +1 -1
  19. package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
  20. package/dist/cjs/fetchers/google-sheets.js +6 -1
  21. package/dist/cjs/fetchers/google-sheets.js.map +1 -1
  22. package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
  23. package/dist/cjs/fetchers/javascript.js +2 -1
  24. package/dist/cjs/fetchers/javascript.js.map +1 -1
  25. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
  26. package/dist/cjs/fetchers/mariadb.js +3 -3
  27. package/dist/cjs/fetchers/mariadb.js.map +1 -1
  28. package/dist/cjs/fetchers/mongodb.d.ts +1 -1
  29. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
  30. package/dist/cjs/fetchers/mongodb.js +11 -3
  31. package/dist/cjs/fetchers/mongodb.js.map +1 -1
  32. package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
  33. package/dist/cjs/fetchers/mysql.js +2 -2
  34. package/dist/cjs/fetchers/mysql.js.map +1 -1
  35. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  36. package/dist/cjs/fetchers/postgresql.js +3 -3
  37. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  38. package/dist/cjs/fetchers/redis.d.ts.map +1 -1
  39. package/dist/cjs/fetchers/redis.js +1 -1
  40. package/dist/cjs/fetchers/redis.js.map +1 -1
  41. package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
  42. package/dist/cjs/fetchers/redshift.js +2 -2
  43. package/dist/cjs/fetchers/redshift.js.map +1 -1
  44. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
  45. package/dist/cjs/fetchers/rest-api.js +3 -2
  46. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  47. package/dist/cjs/fetchers/static-collection.d.ts.map +1 -1
  48. package/dist/cjs/fetchers/static-collection.js +2 -1
  49. package/dist/cjs/fetchers/static-collection.js.map +1 -1
  50. package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
  51. package/dist/cjs/fetchers/supabase.js +2 -2
  52. package/dist/cjs/fetchers/supabase.js.map +1 -1
  53. package/dist/cjs/fetchers/turso.d.ts.map +1 -1
  54. package/dist/cjs/fetchers/turso.js +1 -1
  55. package/dist/cjs/fetchers/turso.js.map +1 -1
  56. package/dist/cjs/fetchers/utils/header-detection.d.ts +2 -0
  57. package/dist/cjs/fetchers/utils/header-detection.d.ts.map +1 -0
  58. package/dist/cjs/fetchers/utils/header-detection.js +8 -0
  59. package/dist/cjs/fetchers/utils/header-detection.js.map +1 -0
  60. package/dist/cjs/index.d.ts.map +1 -1
  61. package/dist/cjs/index.js +168 -4
  62. package/dist/cjs/index.js.map +1 -1
  63. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  64. package/dist/cjs/pagination-plugin.js +320 -65
  65. package/dist/cjs/pagination-plugin.js.map +1 -1
  66. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  67. package/dist/cjs/utils.d.ts +3 -0
  68. package/dist/cjs/utils.d.ts.map +1 -1
  69. package/dist/cjs/utils.js +218 -46
  70. package/dist/cjs/utils.js.map +1 -1
  71. package/dist/esm/data-source-fetchers.d.ts +2 -2
  72. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  73. package/dist/esm/data-source-fetchers.js +29 -6
  74. package/dist/esm/data-source-fetchers.js.map +1 -1
  75. package/dist/esm/fetchers/airtable.d.ts.map +1 -1
  76. package/dist/esm/fetchers/airtable.js +3 -3
  77. package/dist/esm/fetchers/airtable.js.map +1 -1
  78. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
  79. package/dist/esm/fetchers/clickhouse.js +2 -2
  80. package/dist/esm/fetchers/clickhouse.js.map +1 -1
  81. package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
  82. package/dist/esm/fetchers/csv-file.js +23 -3
  83. package/dist/esm/fetchers/csv-file.js.map +1 -1
  84. package/dist/esm/fetchers/firestore.d.ts.map +1 -1
  85. package/dist/esm/fetchers/firestore.js +2 -2
  86. package/dist/esm/fetchers/firestore.js.map +1 -1
  87. package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
  88. package/dist/esm/fetchers/google-sheets.js +6 -1
  89. package/dist/esm/fetchers/google-sheets.js.map +1 -1
  90. package/dist/esm/fetchers/javascript.d.ts.map +1 -1
  91. package/dist/esm/fetchers/javascript.js +2 -1
  92. package/dist/esm/fetchers/javascript.js.map +1 -1
  93. package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
  94. package/dist/esm/fetchers/mariadb.js +4 -4
  95. package/dist/esm/fetchers/mariadb.js.map +1 -1
  96. package/dist/esm/fetchers/mongodb.d.ts +1 -1
  97. package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
  98. package/dist/esm/fetchers/mongodb.js +12 -4
  99. package/dist/esm/fetchers/mongodb.js.map +1 -1
  100. package/dist/esm/fetchers/mysql.d.ts.map +1 -1
  101. package/dist/esm/fetchers/mysql.js +3 -3
  102. package/dist/esm/fetchers/mysql.js.map +1 -1
  103. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  104. package/dist/esm/fetchers/postgresql.js +4 -4
  105. package/dist/esm/fetchers/postgresql.js.map +1 -1
  106. package/dist/esm/fetchers/redis.d.ts.map +1 -1
  107. package/dist/esm/fetchers/redis.js +2 -2
  108. package/dist/esm/fetchers/redis.js.map +1 -1
  109. package/dist/esm/fetchers/redshift.d.ts.map +1 -1
  110. package/dist/esm/fetchers/redshift.js +3 -3
  111. package/dist/esm/fetchers/redshift.js.map +1 -1
  112. package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
  113. package/dist/esm/fetchers/rest-api.js +3 -2
  114. package/dist/esm/fetchers/rest-api.js.map +1 -1
  115. package/dist/esm/fetchers/static-collection.d.ts.map +1 -1
  116. package/dist/esm/fetchers/static-collection.js +2 -1
  117. package/dist/esm/fetchers/static-collection.js.map +1 -1
  118. package/dist/esm/fetchers/supabase.d.ts.map +1 -1
  119. package/dist/esm/fetchers/supabase.js +3 -3
  120. package/dist/esm/fetchers/supabase.js.map +1 -1
  121. package/dist/esm/fetchers/turso.d.ts.map +1 -1
  122. package/dist/esm/fetchers/turso.js +2 -2
  123. package/dist/esm/fetchers/turso.js.map +1 -1
  124. package/dist/esm/fetchers/utils/header-detection.d.ts +2 -0
  125. package/dist/esm/fetchers/utils/header-detection.d.ts.map +1 -0
  126. package/dist/esm/fetchers/utils/header-detection.js +4 -0
  127. package/dist/esm/fetchers/utils/header-detection.js.map +1 -0
  128. package/dist/esm/index.d.ts.map +1 -1
  129. package/dist/esm/index.js +169 -5
  130. package/dist/esm/index.js.map +1 -1
  131. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  132. package/dist/esm/pagination-plugin.js +320 -65
  133. package/dist/esm/pagination-plugin.js.map +1 -1
  134. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  135. package/dist/esm/utils.d.ts +3 -0
  136. package/dist/esm/utils.d.ts.map +1 -1
  137. package/dist/esm/utils.js +214 -45
  138. package/dist/esm/utils.js.map +1 -1
  139. package/package.json +2 -2
  140. package/src/data-source-fetchers.ts +29 -13
  141. package/src/fetchers/airtable.ts +81 -31
  142. package/src/fetchers/clickhouse.ts +88 -19
  143. package/src/fetchers/csv-file.ts +256 -27
  144. package/src/fetchers/firestore.ts +65 -13
  145. package/src/fetchers/google-sheets.ts +243 -26
  146. package/src/fetchers/javascript.ts +106 -23
  147. package/src/fetchers/mariadb.ts +85 -26
  148. package/src/fetchers/mongodb.ts +156 -37
  149. package/src/fetchers/mysql.ts +86 -26
  150. package/src/fetchers/postgresql.ts +89 -27
  151. package/src/fetchers/redis.ts +43 -5
  152. package/src/fetchers/redshift.ts +87 -18
  153. package/src/fetchers/rest-api.ts +105 -24
  154. package/src/fetchers/static-collection.ts +100 -18
  155. package/src/fetchers/supabase.ts +178 -54
  156. package/src/fetchers/turso.ts +87 -18
  157. package/src/fetchers/utils/header-detection.ts +200 -0
  158. package/src/index.ts +248 -2
  159. package/src/pagination-plugin.ts +708 -191
  160. package/src/utils.ts +374 -38
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  interface MariaDBConfig {
4
8
  host?: string
@@ -20,6 +24,62 @@ export const generateMariaDBFetcher = (
20
24
 
21
25
  return `import mariadb from 'mariadb'
22
26
 
27
+ ${generateSafeJSONParseCode()}
28
+
29
+ // Helper function to process filters and build conditions
30
+ const processFilters = (filters, conditions, queryParams) => {
31
+ if (!filters) return
32
+
33
+ const parsedFilters = safeJSONParse(filters)
34
+
35
+ if (Array.isArray(parsedFilters)) {
36
+ parsedFilters.forEach((filter) => {
37
+ if (!filter.source || filter.destination === undefined) return
38
+
39
+ const field = \`\\\`\${filter.source}\\\`\`
40
+ const value = filter.destination
41
+ const operand = filter.operand || '='
42
+
43
+ if (Array.isArray(value)) {
44
+ if (value.length === 0) return
45
+ const placeholders = value.map(() => '?').join(', ')
46
+ queryParams.push(...value)
47
+ if (operand === '!=') {
48
+ conditions.push(\`\${field} NOT IN (\${placeholders})\`)
49
+ } else {
50
+ conditions.push(\`\${field} IN (\${placeholders})\`)
51
+ }
52
+ } else {
53
+ if (value === null) {
54
+ if (operand === '=') {
55
+ conditions.push(\`\${field} IS NULL\`)
56
+ } else if (operand === '!=') {
57
+ conditions.push(\`\${field} IS NOT NULL\`)
58
+ }
59
+ } else {
60
+ const validOps = ['=', '!=', '>', '<', '>=', '<=']
61
+ const sqlOperator = validOps.includes(operand) ? operand : '='
62
+ conditions.push(\`\${field} \${sqlOperator} ?\`)
63
+ queryParams.push(value)
64
+ }
65
+ }
66
+ })
67
+ } else {
68
+ Object.entries(parsedFilters).forEach(([key, value]) => {
69
+ if (Array.isArray(value)) {
70
+ const placeholders = value.map(() => '?').join(', ')
71
+ queryParams.push(...value)
72
+ conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
73
+ } else {
74
+ conditions.push(\`\\\`\${key}\\\` = ?\`)
75
+ queryParams.push(value)
76
+ }
77
+ })
78
+ }
79
+ }
80
+
81
+ ${generateDateFormatterCode()}
82
+
23
83
  export default async function handler(req, res) {
24
84
  let connection = null
25
85
  try {
@@ -52,7 +112,7 @@ export default async function handler(req, res) {
52
112
  }
53
113
  })
54
114
 
55
- const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
115
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
56
116
 
57
117
  const conditions = []
58
118
  const queryParams = []
@@ -62,7 +122,7 @@ export default async function handler(req, res) {
62
122
 
63
123
  if (queryColumns) {
64
124
  // Use specified columns
65
- columns = JSON.parse(queryColumns)
125
+ columns = safeJSONParse(queryColumns)
66
126
  } else {
67
127
  // Fallback: Get all columns from information_schema
68
128
  try {
@@ -86,19 +146,8 @@ export default async function handler(req, res) {
86
146
  }
87
147
  }
88
148
 
89
- if (filters) {
90
- const parsedFilters = JSON.parse(filters)
91
- Object.entries(parsedFilters).forEach(([key, value]) => {
92
- if (Array.isArray(value)) {
93
- const placeholders = value.map(() => '?').join(', ')
94
- queryParams.push(...value)
95
- conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
96
- } else {
97
- conditions.push(\`\\\`\${key}\\\` = ?\`)
98
- queryParams.push(value)
99
- }
100
- })
101
- }
149
+ // Apply filters using helper function
150
+ processFilters(filters, conditions, queryParams)
102
151
 
103
152
  let sql = \`SELECT * FROM \\\`${tableName}\\\`\`
104
153
 
@@ -106,7 +155,21 @@ export default async function handler(req, res) {
106
155
  sql += \` WHERE \${conditions.join(' AND ')}\`
107
156
  }
108
157
 
109
- if (sortBy) {
158
+ // Handle sorts - new array format
159
+ if (sorts) {
160
+ const parsedSorts = safeJSONParse(sorts)
161
+ if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
162
+ const orderClauses = parsedSorts.map((sort) => {
163
+ if (!sort.field) return null
164
+ const order = sort.order?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
165
+ return \`\\\`\${sort.field}\\\` \${order}\`
166
+ }).filter(Boolean)
167
+
168
+ if (orderClauses.length > 0) {
169
+ sql += \` ORDER BY \${orderClauses.join(', ')}\`
170
+ }
171
+ }
172
+ } else if (sortBy) {
110
173
  sql += \` ORDER BY \\\`\${sortBy}\\\` \${sortOrder?.toUpperCase() || 'ASC'}\`
111
174
  }
112
175
 
@@ -126,7 +189,7 @@ export default async function handler(req, res) {
126
189
  const plainRows = rowArray.map((row) =>
127
190
  row && typeof row.toJSON === 'function' ? row.toJSON() : row
128
191
  )
129
- const safeData = JSON.parse(JSON.stringify(plainRows))
192
+ const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))
130
193
 
131
194
  return res.status(200).json({
132
195
  success: true,
@@ -203,7 +266,8 @@ async function getCount(req, res) {
203
266
 
204
267
  if (queryColumns) {
205
268
  // Use specified columns
206
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
269
+ const parsed = safeJSONParse(queryColumns)
270
+ columns = Array.isArray(parsed) ? parsed : [parsed]
207
271
  } else {
208
272
  // Fallback: Get all columns from information_schema
209
273
  try {
@@ -227,13 +291,8 @@ async function getCount(req, res) {
227
291
  }
228
292
  }
229
293
 
230
- if (filters) {
231
- const parsedFilters = JSON.parse(filters)
232
- for (const filter of parsedFilters) {
233
- conditions.push(\`\${filter.column} \${filter.operator} ?\`)
234
- queryParams.push(filter.value)
235
- }
236
- }
294
+ // Apply filters using helper function
295
+ processFilters(filters, conditions, queryParams)
237
296
 
238
297
  let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
239
298
  if (conditions.length > 0) {
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  export const validateMongoDBConfig = (
4
8
  config: Record<string, unknown>
@@ -67,6 +71,101 @@ export const generateMongoDBFetcher = (
67
71
 
68
72
  return `import { MongoClient, ObjectId } from 'mongodb'
69
73
 
74
+ ${generateSafeJSONParseCode()}
75
+
76
+ // Helper function to process filters
77
+ const processFilters = (filters, filter) => {
78
+ if (!filters) return
79
+
80
+ const parsedFilters = safeJSONParse(filters)
81
+
82
+ if (Array.isArray(parsedFilters)) {
83
+ parsedFilters.forEach((filterItem) => {
84
+ if (!filterItem.source || filterItem.destination === undefined) return
85
+
86
+ const field = filterItem.source
87
+ const value = filterItem.destination
88
+ const operand = filterItem.operand || '='
89
+
90
+ // Handle _id specially
91
+ const processValue = (v) => {
92
+ if (field === '_id' && typeof v === 'string') {
93
+ try {
94
+ return new ObjectId(v)
95
+ } catch (e) {
96
+ return v
97
+ }
98
+ }
99
+ return v
100
+ }
101
+
102
+ if (Array.isArray(value)) {
103
+ const processedValues = value.map(processValue)
104
+ if (operand === '!=') {
105
+ filter[field] = { $nin: processedValues }
106
+ } else {
107
+ filter[field] = { $in: processedValues }
108
+ }
109
+ } else {
110
+ const processedValue = processValue(value)
111
+
112
+ // Handle null values
113
+ if (processedValue === null) {
114
+ if (operand === '=') {
115
+ filter[field] = null
116
+ } else if (operand === '!=') {
117
+ filter[field] = { $ne: null }
118
+ }
119
+ } else {
120
+ // Map operand to MongoDB operators
121
+ switch (operand) {
122
+ case '=':
123
+ filter[field] = processedValue
124
+ break
125
+ case '!=':
126
+ filter[field] = { $ne: processedValue }
127
+ break
128
+ case '>':
129
+ filter[field] = { $gt: processedValue }
130
+ break
131
+ case '>=':
132
+ filter[field] = { $gte: processedValue }
133
+ break
134
+ case '<':
135
+ filter[field] = { $lt: processedValue }
136
+ break
137
+ case '<=':
138
+ filter[field] = { $lte: processedValue }
139
+ break
140
+ default:
141
+ filter[field] = processedValue
142
+ }
143
+ }
144
+ }
145
+ })
146
+ } else {
147
+ Object.entries(parsedFilters).forEach(([key, value]) => {
148
+ if (key === '_id') {
149
+ if (Array.isArray(value)) {
150
+ filter[key] = {
151
+ $in: value.map((id) => (typeof id === 'string' ? new ObjectId(id) : id))
152
+ }
153
+ } else if (typeof value === 'string') {
154
+ filter[key] = new ObjectId(value)
155
+ } else {
156
+ filter[key] = value
157
+ }
158
+ } else if (Array.isArray(value)) {
159
+ filter[key] = { $in: value }
160
+ } else {
161
+ filter[key] = value
162
+ }
163
+ })
164
+ }
165
+ }
166
+
167
+ ${generateDateFormatterCode()}
168
+
70
169
  export default async function handler(req, res) {
71
170
  let client = null
72
171
  try {
@@ -80,7 +179,7 @@ export default async function handler(req, res) {
80
179
  const db = client.db(${JSON.stringify(database)})
81
180
  const collection = db.collection('${tableName}')
82
181
 
83
- const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
182
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
84
183
 
85
184
  const filter = {}
86
185
 
@@ -89,7 +188,7 @@ export default async function handler(req, res) {
89
188
 
90
189
  if (queryColumns) {
91
190
  // Use specified columns
92
- columns = JSON.parse(queryColumns)
191
+ columns = safeJSONParse(queryColumns)
93
192
  } else {
94
193
  // Fallback: Get all field names from a sample document
95
194
  try {
@@ -111,30 +210,26 @@ export default async function handler(req, res) {
111
210
  }
112
211
  }
113
212
 
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
- }
213
+ // Apply filters using helper function
214
+ processFilters(filters, filter)
134
215
 
135
216
  let cursor = collection.find(filter)
136
217
 
137
- if (sortBy) {
218
+ // Handle sorts - new array format
219
+ if (sorts) {
220
+ const parsedSorts = safeJSONParse(sorts)
221
+ if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
222
+ const sortObject = {}
223
+ parsedSorts.forEach((sort) => {
224
+ if (sort.field) {
225
+ sortObject[sort.field] = sort.order?.toLowerCase() === 'desc' ? -1 : 1
226
+ }
227
+ })
228
+ if (Object.keys(sortObject).length > 0) {
229
+ cursor = cursor.sort(sortObject)
230
+ }
231
+ }
232
+ } else if (sortBy) {
138
233
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
139
234
  cursor = cursor.sort({ [sortBy]: sortOrderValue })
140
235
  }
@@ -151,7 +246,7 @@ export default async function handler(req, res) {
151
246
  }
152
247
 
153
248
  const documents = await cursor.toArray()
154
- const safeData = JSON.parse(JSON.stringify(documents))
249
+ const safeData = JSON.parse(JSON.stringify(documents, dateReplacer))
155
250
 
156
251
  return res.status(200).json({
157
252
  success: true,
@@ -179,15 +274,34 @@ export default async function handler(req, res) {
179
274
  }
180
275
 
181
276
  // tslint:disable-next-line:variable-name
182
- export const generateMongoDBCountFetcher = (_config: any, tableName: string): string => {
277
+ export const generateMongoDBCountFetcher = (config: any, tableName: string): string => {
278
+ const mongoConfig = config as MongoDBConfig
279
+ const hasUsername = mongoConfig?.username
280
+ const database = mongoConfig?.database
281
+
282
+ // Build connection string from parts if not provided
283
+ let connectionString = mongoConfig.connectionString
284
+ if (!connectionString) {
285
+ connectionString = `mongodb://${
286
+ hasUsername ? `${mongoConfig.username}:${mongoConfig.password}@` : ''
287
+ }${mongoConfig.host}:${mongoConfig.port || 27017}/${database}`
288
+ }
289
+
183
290
  return `
184
291
  async function getCount(req, res) {
185
- const client = getClient()
186
- const db = client.db()
187
-
292
+ let client = null
188
293
  try {
189
- const { query, queryColumns, filters } = req.query
294
+ const url = ${replaceSecretReference(connectionString)}
295
+ client = new MongoClient(url, {
296
+ connectTimeoutMS: 30000,
297
+ serverSelectionTimeoutMS: 30000
298
+ })
299
+
300
+ await client.connect()
301
+ const db = client.db(${JSON.stringify(database)})
190
302
  const collection = db.collection('${tableName}')
303
+
304
+ const { query, queryColumns, filters } = req.query
191
305
  const filter = {}
192
306
 
193
307
  if (query) {
@@ -195,7 +309,8 @@ async function getCount(req, res) {
195
309
 
196
310
  if (queryColumns) {
197
311
  // Use specified columns
198
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
312
+ const parsed = safeJSONParse(queryColumns)
313
+ columns = Array.isArray(parsed) ? parsed : [parsed]
199
314
  } else {
200
315
  // Fallback: Get all field names from a sample document
201
316
  try {
@@ -216,12 +331,8 @@ async function getCount(req, res) {
216
331
  }
217
332
  }
218
333
 
219
- if (filters) {
220
- const parsedFilters = JSON.parse(filters)
221
- for (const f of parsedFilters) {
222
- filter[f.column] = f.value
223
- }
224
- }
334
+ // Apply filters using helper function
335
+ processFilters(filters, filter)
225
336
 
226
337
  const count = await collection.countDocuments(filter)
227
338
 
@@ -237,6 +348,14 @@ async function getCount(req, res) {
237
348
  error: error.message || 'Failed to get count',
238
349
  timestamp: Date.now()
239
350
  })
351
+ } finally {
352
+ if (client) {
353
+ try {
354
+ await client.close()
355
+ } catch (error) {
356
+ console.error('Error closing MongoDB client:', error)
357
+ }
358
+ }
240
359
  }
241
360
  }
242
361
  `
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  interface MySQLConfig {
4
8
  host?: string
@@ -57,11 +61,68 @@ const getConnection = () => {
57
61
  })
58
62
  }
59
63
 
64
+ ${generateSafeJSONParseCode()}
65
+
66
+ // Helper function to process filters and build conditions
67
+ const processFilters = (filters, conditions, queryParams) => {
68
+ if (!filters) return
69
+
70
+ const parsedFilters = safeJSONParse(filters)
71
+
72
+ if (Array.isArray(parsedFilters)) {
73
+ parsedFilters.forEach((filter) => {
74
+ if (!filter.source || filter.destination === undefined) return
75
+
76
+ const field = mysql.escapeId(filter.source)
77
+ const value = filter.destination
78
+ const operand = filter.operand || '='
79
+
80
+ if (Array.isArray(value)) {
81
+ if (value.length === 0) return
82
+ const placeholders = value.map(() => '?').join(', ')
83
+ queryParams.push(...value)
84
+ if (operand === '!=') {
85
+ conditions.push(\`\${field} NOT IN (\${placeholders})\`)
86
+ } else {
87
+ conditions.push(\`\${field} IN (\${placeholders})\`)
88
+ }
89
+ } else {
90
+ if (value === null) {
91
+ if (operand === '=') {
92
+ conditions.push(\`\${field} IS NULL\`)
93
+ } else if (operand === '!=') {
94
+ conditions.push(\`\${field} IS NOT NULL\`)
95
+ }
96
+ } else {
97
+ // Validate operator to prevent SQL injection
98
+ const validOps = ['=', '!=', '>', '<', '>=', '<=']
99
+ const sqlOperator = validOps.includes(operand) ? operand : '='
100
+ conditions.push(\`\${field} \${sqlOperator} ?\`)
101
+ queryParams.push(value)
102
+ }
103
+ }
104
+ })
105
+ } else {
106
+ Object.entries(parsedFilters).forEach(([key, value]) => {
107
+ if (Array.isArray(value)) {
108
+ const placeholders = value.map(() => '?').join(', ')
109
+ queryParams.push(...value)
110
+ conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
111
+ } else {
112
+ conditions.push(\`\${mysql.escapeId(key)} = ?\`)
113
+ queryParams.push(value)
114
+ }
115
+ })
116
+ }
117
+ }
118
+
119
+ ${generateDateFormatterCode()}
120
+
60
121
  export default async function handler(req, res) {
61
122
  const connection = await getConnection()
62
123
 
63
124
  try {
64
- const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
125
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
65
126
 
66
127
  const conditions = []
67
128
  const queryParams = []
@@ -71,7 +132,7 @@ export default async function handler(req, res) {
71
132
 
72
133
  if (queryColumns) {
73
134
  // Use specified columns
74
- columns = JSON.parse(queryColumns)
135
+ columns = safeJSONParse(queryColumns)
75
136
  } else {
76
137
  // Fallback: Get all columns from information_schema
77
138
  try {
@@ -95,19 +156,8 @@ export default async function handler(req, res) {
95
156
  }
96
157
  }
97
158
 
98
- if (filters) {
99
- const parsedFilters = JSON.parse(filters)
100
- Object.entries(parsedFilters).forEach(([key, value]) => {
101
- if (Array.isArray(value)) {
102
- const placeholders = value.map(() => '?').join(', ')
103
- queryParams.push(...value)
104
- conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
105
- } else {
106
- conditions.push(\`\${mysql.escapeId(key)} = ?\`)
107
- queryParams.push(value)
108
- }
109
- })
110
- }
159
+ // Apply filters using helper function
160
+ processFilters(filters, conditions, queryParams)
111
161
 
112
162
  let sql = \`SELECT * FROM \${mysql.escapeId('${tableName}')}\`
113
163
 
@@ -115,7 +165,21 @@ export default async function handler(req, res) {
115
165
  sql += \` WHERE \${conditions.join(' AND ')}\`
116
166
  }
117
167
 
118
- if (sortBy) {
168
+ // Handle sorts - new array format
169
+ if (sorts) {
170
+ const parsedSorts = safeJSONParse(sorts)
171
+ if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
172
+ const orderClauses = parsedSorts.map((sort) => {
173
+ if (!sort.field) return null
174
+ const order = sort.order?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
175
+ return \`\${mysql.escapeId(sort.field)} \${order}\`
176
+ }).filter(Boolean)
177
+
178
+ if (orderClauses.length > 0) {
179
+ sql += \` ORDER BY \${orderClauses.join(', ')}\`
180
+ }
181
+ }
182
+ } else if (sortBy) {
119
183
  sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${sortOrder?.toUpperCase() || 'ASC'}\`
120
184
  }
121
185
 
@@ -135,7 +199,7 @@ export default async function handler(req, res) {
135
199
  const plainRows = rowArray.map((row) =>
136
200
  row && typeof row.toJSON === 'function' ? row.toJSON() : row
137
201
  )
138
- const safeData = JSON.parse(JSON.stringify(plainRows))
202
+ const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))
139
203
 
140
204
  return res.status(200).json({
141
205
  success: true,
@@ -182,7 +246,8 @@ async function getCount(req, res) {
182
246
 
183
247
  if (queryColumns) {
184
248
  // Use specified columns
185
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
249
+ const parsed = safeJSONParse(queryColumns)
250
+ columns = Array.isArray(parsed) ? parsed : [parsed]
186
251
  } else {
187
252
  // Fallback: Get all columns from information_schema
188
253
  try {
@@ -206,13 +271,8 @@ async function getCount(req, res) {
206
271
  }
207
272
  }
208
273
 
209
- if (filters) {
210
- const parsedFilters = JSON.parse(filters)
211
- for (const filter of parsedFilters) {
212
- conditions.push(\`\${filter.column} \${filter.operator} ?\`)
213
- queryParams.push(filter.value)
214
- }
215
- }
274
+ // Apply filters using helper function
275
+ processFilters(filters, conditions, queryParams)
216
276
 
217
277
  let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
218
278
  if (conditions.length > 0) {