@teleporthq/teleport-plugin-next-data-source 0.42.9 → 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 +1 -1
  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 +22 -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 +1 -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 +2 -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 +1 -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 +2 -0
  68. package/dist/cjs/utils.d.ts.map +1 -1
  69. package/dist/cjs/utils.js +214 -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 +2 -2
  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 -4
  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 -2
  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 -3
  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 -2
  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 +2 -0
  136. package/dist/esm/utils.d.ts.map +1 -1
  137. package/dist/esm/utils.js +211 -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 +78 -30
  142. package/src/fetchers/clickhouse.ts +85 -18
  143. package/src/fetchers/csv-file.ts +254 -29
  144. package/src/fetchers/firestore.ts +62 -12
  145. package/src/fetchers/google-sheets.ts +147 -30
  146. package/src/fetchers/javascript.ts +102 -23
  147. package/src/fetchers/mariadb.ts +82 -25
  148. package/src/fetchers/mongodb.ts +153 -36
  149. package/src/fetchers/mysql.ts +83 -25
  150. package/src/fetchers/postgresql.ts +86 -26
  151. package/src/fetchers/redis.ts +40 -4
  152. package/src/fetchers/redshift.ts +84 -17
  153. package/src/fetchers/rest-api.ts +101 -24
  154. package/src/fetchers/static-collection.ts +96 -18
  155. package/src/fetchers/supabase.ts +175 -53
  156. package/src/fetchers/turso.ts +84 -17
  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 +344 -38
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference, generateDateFormatterCode } 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,99 @@ 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
+
70
167
  ${generateDateFormatterCode()}
71
168
 
72
169
  export default async function handler(req, res) {
@@ -82,7 +179,7 @@ export default async function handler(req, res) {
82
179
  const db = client.db(${JSON.stringify(database)})
83
180
  const collection = db.collection('${tableName}')
84
181
 
85
- 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
86
183
 
87
184
  const filter = {}
88
185
 
@@ -91,7 +188,7 @@ export default async function handler(req, res) {
91
188
 
92
189
  if (queryColumns) {
93
190
  // Use specified columns
94
- columns = JSON.parse(queryColumns)
191
+ columns = safeJSONParse(queryColumns)
95
192
  } else {
96
193
  // Fallback: Get all field names from a sample document
97
194
  try {
@@ -113,30 +210,26 @@ export default async function handler(req, res) {
113
210
  }
114
211
  }
115
212
 
116
- if (filters) {
117
- const parsedFilters = JSON.parse(filters)
118
- Object.entries(parsedFilters).forEach(([key, value]) => {
119
- if (key === '_id') {
120
- if (Array.isArray(value)) {
121
- filter[key] = {
122
- $in: value.map((id) => (typeof id === 'string' ? new ObjectId(id) : id))
123
- }
124
- } else if (typeof value === 'string') {
125
- filter[key] = new ObjectId(value)
126
- } else {
127
- filter[key] = value
128
- }
129
- } else if (Array.isArray(value)) {
130
- filter[key] = { $in: value }
131
- } else {
132
- filter[key] = value
133
- }
134
- })
135
- }
213
+ // Apply filters using helper function
214
+ processFilters(filters, filter)
136
215
 
137
216
  let cursor = collection.find(filter)
138
217
 
139
- 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) {
140
233
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
141
234
  cursor = cursor.sort({ [sortBy]: sortOrderValue })
142
235
  }
@@ -181,15 +274,34 @@ export default async function handler(req, res) {
181
274
  }
182
275
 
183
276
  // tslint:disable-next-line:variable-name
184
- 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
+
185
290
  return `
186
291
  async function getCount(req, res) {
187
- const client = getClient()
188
- const db = client.db()
189
-
292
+ let client = null
190
293
  try {
191
- 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)})
192
302
  const collection = db.collection('${tableName}')
303
+
304
+ const { query, queryColumns, filters } = req.query
193
305
  const filter = {}
194
306
 
195
307
  if (query) {
@@ -197,7 +309,8 @@ async function getCount(req, res) {
197
309
 
198
310
  if (queryColumns) {
199
311
  // Use specified columns
200
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
312
+ const parsed = safeJSONParse(queryColumns)
313
+ columns = Array.isArray(parsed) ? parsed : [parsed]
201
314
  } else {
202
315
  // Fallback: Get all field names from a sample document
203
316
  try {
@@ -218,12 +331,8 @@ async function getCount(req, res) {
218
331
  }
219
332
  }
220
333
 
221
- if (filters) {
222
- const parsedFilters = JSON.parse(filters)
223
- for (const f of parsedFilters) {
224
- filter[f.column] = f.value
225
- }
226
- }
334
+ // Apply filters using helper function
335
+ processFilters(filters, filter)
227
336
 
228
337
  const count = await collection.countDocuments(filter)
229
338
 
@@ -239,6 +348,14 @@ async function getCount(req, res) {
239
348
  error: error.message || 'Failed to get count',
240
349
  timestamp: Date.now()
241
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
+ }
242
359
  }
243
360
  }
244
361
  `
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference, generateDateFormatterCode } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  interface MySQLConfig {
4
8
  host?: string
@@ -57,13 +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
+
60
119
  ${generateDateFormatterCode()}
61
120
 
62
121
  export default async function handler(req, res) {
63
122
  const connection = await getConnection()
64
123
 
65
124
  try {
66
- 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
67
126
 
68
127
  const conditions = []
69
128
  const queryParams = []
@@ -73,7 +132,7 @@ export default async function handler(req, res) {
73
132
 
74
133
  if (queryColumns) {
75
134
  // Use specified columns
76
- columns = JSON.parse(queryColumns)
135
+ columns = safeJSONParse(queryColumns)
77
136
  } else {
78
137
  // Fallback: Get all columns from information_schema
79
138
  try {
@@ -97,19 +156,8 @@ export default async function handler(req, res) {
97
156
  }
98
157
  }
99
158
 
100
- if (filters) {
101
- const parsedFilters = JSON.parse(filters)
102
- Object.entries(parsedFilters).forEach(([key, value]) => {
103
- if (Array.isArray(value)) {
104
- const placeholders = value.map(() => '?').join(', ')
105
- queryParams.push(...value)
106
- conditions.push(\`\${mysql.escapeId(key)} IN (\${placeholders})\`)
107
- } else {
108
- conditions.push(\`\${mysql.escapeId(key)} = ?\`)
109
- queryParams.push(value)
110
- }
111
- })
112
- }
159
+ // Apply filters using helper function
160
+ processFilters(filters, conditions, queryParams)
113
161
 
114
162
  let sql = \`SELECT * FROM \${mysql.escapeId('${tableName}')}\`
115
163
 
@@ -117,7 +165,21 @@ export default async function handler(req, res) {
117
165
  sql += \` WHERE \${conditions.join(' AND ')}\`
118
166
  }
119
167
 
120
- 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) {
121
183
  sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${sortOrder?.toUpperCase() || 'ASC'}\`
122
184
  }
123
185
 
@@ -184,7 +246,8 @@ async function getCount(req, res) {
184
246
 
185
247
  if (queryColumns) {
186
248
  // Use specified columns
187
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
249
+ const parsed = safeJSONParse(queryColumns)
250
+ columns = Array.isArray(parsed) ? parsed : [parsed]
188
251
  } else {
189
252
  // Fallback: Get all columns from information_schema
190
253
  try {
@@ -208,13 +271,8 @@ async function getCount(req, res) {
208
271
  }
209
272
  }
210
273
 
211
- if (filters) {
212
- const parsedFilters = JSON.parse(filters)
213
- for (const filter of parsedFilters) {
214
- conditions.push(\`\${filter.column} \${filter.operator} ?\`)
215
- queryParams.push(filter.value)
216
- }
217
- }
274
+ // Apply filters using helper function
275
+ processFilters(filters, conditions, queryParams)
218
276
 
219
277
  let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
220
278
  if (conditions.length > 0) {
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference, generateDateFormatterCode } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  interface PostgreSQLConfig {
4
8
  host?: string
@@ -43,6 +47,64 @@ const getClient = () => {
43
47
  })
44
48
  }
45
49
 
50
+ ${generateSafeJSONParseCode()}
51
+
52
+ // Helper function to process filters and build conditions
53
+ const processFilters = (filters, conditions, queryParams, paramIndex) => {
54
+ if (!filters) return paramIndex
55
+
56
+ const parsedFilters = safeJSONParse(filters)
57
+
58
+ if (Array.isArray(parsedFilters)) {
59
+ parsedFilters.forEach((filter) => {
60
+ if (!filter.source || filter.destination === undefined) return
61
+
62
+ const field = filter.source
63
+ const value = filter.destination
64
+ const operand = filter.operand || '='
65
+
66
+ if (Array.isArray(value)) {
67
+ if (value.length === 0) return
68
+ const placeholders = value.map(() => \`$\${paramIndex++}\`)
69
+ queryParams.push(...value)
70
+ if (operand === '!=') {
71
+ conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
72
+ } else {
73
+ conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
74
+ }
75
+ } else {
76
+ if (value === null) {
77
+ if (operand === '=') {
78
+ conditions.push(\`\${field} IS NULL\`)
79
+ } else if (operand === '!=') {
80
+ conditions.push(\`\${field} IS NOT NULL\`)
81
+ }
82
+ } else {
83
+ const validOps = ['=', '!=', '>', '<', '>=', '<=']
84
+ const sqlOperator = validOps.includes(operand) ? operand : '='
85
+ conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
86
+ queryParams.push(value)
87
+ paramIndex++
88
+ }
89
+ }
90
+ })
91
+ } else {
92
+ Object.entries(parsedFilters).forEach(([key, value]) => {
93
+ if (Array.isArray(value)) {
94
+ const placeholders = value.map(() => \`$\${paramIndex++}\`)
95
+ queryParams.push(...value)
96
+ conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
97
+ } else {
98
+ conditions.push(\`\${key} = $\${paramIndex}\`)
99
+ queryParams.push(value)
100
+ paramIndex++
101
+ }
102
+ })
103
+ }
104
+
105
+ return paramIndex
106
+ }
107
+
46
108
  ${generateDateFormatterCode()}
47
109
 
48
110
  export default async function handler(req, res) {
@@ -52,7 +114,7 @@ export default async function handler(req, res) {
52
114
  await client.connect()
53
115
  ${schema ? `await client.query('SET search_path TO ${schema}')` : ''}
54
116
 
55
- const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
117
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
56
118
 
57
119
  const conditions = []
58
120
  const queryParams = []
@@ -63,7 +125,7 @@ export default async function handler(req, res) {
63
125
 
64
126
  if (queryColumns) {
65
127
  // Use specified columns
66
- columns = JSON.parse(queryColumns)
128
+ columns = safeJSONParse(queryColumns)
67
129
  } else {
68
130
  // Fallback: Get all columns from information_schema
69
131
  try {
@@ -97,20 +159,8 @@ export default async function handler(req, res) {
97
159
  }
98
160
  }
99
161
 
100
- if (filters) {
101
- const parsedFilters = JSON.parse(filters)
102
- Object.entries(parsedFilters).forEach(([key, value]) => {
103
- if (Array.isArray(value)) {
104
- const placeholders = value.map(() => \`$\${paramIndex++}\`)
105
- queryParams.push(...value)
106
- conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
107
- } else {
108
- conditions.push(\`\${key} = $\${paramIndex}\`)
109
- queryParams.push(value)
110
- paramIndex++
111
- }
112
- })
113
- }
162
+ // Apply filters using helper function
163
+ paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
114
164
 
115
165
  let sql = \`SELECT * FROM ${tableName}\`
116
166
 
@@ -118,7 +168,21 @@ export default async function handler(req, res) {
118
168
  sql += \` WHERE \${conditions.join(' AND ')}\`
119
169
  }
120
170
 
121
- if (sortBy) {
171
+ // Handle sorts - new array format
172
+ if (sorts) {
173
+ const parsedSorts = safeJSONParse(sorts)
174
+ if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
175
+ const orderClauses = parsedSorts.map((sort) => {
176
+ if (!sort.field) return null
177
+ const order = sort.order?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
178
+ return \`\${sort.field} \${order}\`
179
+ }).filter(Boolean)
180
+
181
+ if (orderClauses.length > 0) {
182
+ sql += \` ORDER BY \${orderClauses.join(', ')}\`
183
+ }
184
+ }
185
+ } else if (sortBy) {
122
186
  sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
123
187
  }
124
188
 
@@ -188,7 +252,8 @@ async function getCount(req, res) {
188
252
 
189
253
  if (queryColumns) {
190
254
  // Use specified columns
191
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
255
+ const parsed = safeJSONParse(queryColumns)
256
+ columns = Array.isArray(parsed) ? parsed : [parsed]
192
257
  } else {
193
258
  // Fallback: Get all columns from information_schema
194
259
  try {
@@ -220,13 +285,8 @@ async function getCount(req, res) {
220
285
  }
221
286
  }
222
287
 
223
- if (filters) {
224
- const parsedFilters = JSON.parse(filters)
225
- for (const filter of parsedFilters) {
226
- conditions.push(\`\${filter.column} \${filter.operator} $\${paramIndex++}\`)
227
- queryParams.push(filter.value)
228
- }
229
- }
288
+ // Apply filters using helper function
289
+ paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
230
290
 
231
291
  let countSql = \`SELECT COUNT(*) FROM ${tableName}\`
232
292
  if (conditions.length > 0) {
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference, generateDateFormatterCode } from '../utils'
1
+ import {
2
+ replaceSecretReference,
3
+ generateDateFormatterCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
2
6
 
3
7
  export const validateRedisConfig = (
4
8
  config: Record<string, unknown>
@@ -63,6 +67,8 @@ export const generateRedisFetcher = (config: Record<string, unknown>): string =>
63
67
 
64
68
  return `import { createClient } from 'redis'
65
69
 
70
+ ${generateSafeJSONParseCode()}
71
+
66
72
  ${generateDateFormatterCode()}
67
73
 
68
74
  export default async function handler(req, res) {
@@ -76,9 +82,23 @@ export default async function handler(req, res) {
76
82
 
77
83
  await client.connect()
78
84
 
79
- const { query, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
85
+ const { query, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
86
+
87
+ let pattern = query || '*'
88
+
89
+ // Extract pattern from filters if available (new format)
90
+ if (filters) {
91
+ const parsedFilters = safeJSONParse(filters)
92
+ if (Array.isArray(parsedFilters)) {
93
+ const patternFilter = parsedFilters.find(f => f.source === 'pattern')
94
+ if (patternFilter) {
95
+ pattern = patternFilter.destination || pattern
96
+ }
97
+ } else {
98
+ pattern = parsedFilters.pattern || pattern
99
+ }
100
+ }
80
101
 
81
- const pattern = (filters && JSON.parse(filters).pattern) || query || '*'
82
102
  const keys = await client.keys(pattern)
83
103
 
84
104
  const limitValue = limit || perPage || 100
@@ -119,7 +139,23 @@ export default async function handler(req, res) {
119
139
  })
120
140
  }
121
141
 
122
- if (sortBy) {
142
+ // Handle sorts - new array format
143
+ if (sorts) {
144
+ const parsedSorts = safeJSONParse(sorts)
145
+ if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
146
+ const primarySort = parsedSorts[0]
147
+ if (primarySort.field) {
148
+ const sortOrderValue = primarySort.order?.toLowerCase() === 'desc' ? -1 : 1
149
+ results.sort((a, b) => {
150
+ const aVal = a[primarySort.field]
151
+ const bVal = b[primarySort.field]
152
+ if (aVal < bVal) return -sortOrderValue
153
+ if (aVal > bVal) return sortOrderValue
154
+ return 0
155
+ })
156
+ }
157
+ }
158
+ } else if (sortBy) {
123
159
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
124
160
  results.sort((a, b) => {
125
161
  const aVal = a[sortBy]