@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.
- package/__tests__/csv-header-detection.test.ts +212 -0
- package/__tests__/validation.test.ts +33 -2
- package/dist/cjs/data-source-fetchers.d.ts +2 -2
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +30 -7
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +2 -2
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
- package/dist/cjs/fetchers/csv-file.js +23 -3
- package/dist/cjs/fetchers/csv-file.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/cjs/fetchers/google-sheets.js +6 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +2 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +3 -3
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +11 -3
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +2 -2
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +3 -3
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redis.d.ts.map +1 -1
- package/dist/cjs/fetchers/redis.js +1 -1
- package/dist/cjs/fetchers/redis.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +2 -2
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +3 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/static-collection.d.ts.map +1 -1
- package/dist/cjs/fetchers/static-collection.js +2 -1
- package/dist/cjs/fetchers/static-collection.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +2 -2
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/fetchers/utils/header-detection.d.ts +2 -0
- package/dist/cjs/fetchers/utils/header-detection.d.ts.map +1 -0
- package/dist/cjs/fetchers/utils/header-detection.js +8 -0
- package/dist/cjs/fetchers/utils/header-detection.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +168 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +320 -65
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +218 -46
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +2 -2
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +29 -6
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +3 -3
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +2 -2
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
- package/dist/esm/fetchers/csv-file.js +23 -3
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +2 -2
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +6 -1
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +2 -1
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +4 -4
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +12 -4
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +3 -3
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +4 -4
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redis.d.ts.map +1 -1
- package/dist/esm/fetchers/redis.js +2 -2
- package/dist/esm/fetchers/redis.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +3 -3
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/static-collection.d.ts.map +1 -1
- package/dist/esm/fetchers/static-collection.js +2 -1
- package/dist/esm/fetchers/static-collection.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +3 -3
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +2 -2
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/fetchers/utils/header-detection.d.ts +2 -0
- package/dist/esm/fetchers/utils/header-detection.d.ts.map +1 -0
- package/dist/esm/fetchers/utils/header-detection.js +4 -0
- package/dist/esm/fetchers/utils/header-detection.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +169 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +320 -65
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +214 -45
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/data-source-fetchers.ts +29 -13
- package/src/fetchers/airtable.ts +81 -31
- package/src/fetchers/clickhouse.ts +88 -19
- package/src/fetchers/csv-file.ts +256 -27
- package/src/fetchers/firestore.ts +65 -13
- package/src/fetchers/google-sheets.ts +243 -26
- package/src/fetchers/javascript.ts +106 -23
- package/src/fetchers/mariadb.ts +85 -26
- package/src/fetchers/mongodb.ts +156 -37
- package/src/fetchers/mysql.ts +86 -26
- package/src/fetchers/postgresql.ts +89 -27
- package/src/fetchers/redis.ts +43 -5
- package/src/fetchers/redshift.ts +87 -18
- package/src/fetchers/rest-api.ts +105 -24
- package/src/fetchers/static-collection.ts +100 -18
- package/src/fetchers/supabase.ts +178 -54
- package/src/fetchers/turso.ts +87 -18
- package/src/fetchers/utils/header-detection.ts +200 -0
- package/src/index.ts +248 -2
- package/src/pagination-plugin.ts +708 -191
- package/src/utils.ts +374 -38
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
interface PostgreSQLConfig {
|
|
4
8
|
host?: string
|
|
@@ -43,6 +47,66 @@ 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
|
+
|
|
108
|
+
${generateDateFormatterCode()}
|
|
109
|
+
|
|
46
110
|
export default async function handler(req, res) {
|
|
47
111
|
const client = getClient()
|
|
48
112
|
|
|
@@ -50,7 +114,7 @@ export default async function handler(req, res) {
|
|
|
50
114
|
await client.connect()
|
|
51
115
|
${schema ? `await client.query('SET search_path TO ${schema}')` : ''}
|
|
52
116
|
|
|
53
|
-
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
|
|
54
118
|
|
|
55
119
|
const conditions = []
|
|
56
120
|
const queryParams = []
|
|
@@ -61,7 +125,7 @@ export default async function handler(req, res) {
|
|
|
61
125
|
|
|
62
126
|
if (queryColumns) {
|
|
63
127
|
// Use specified columns
|
|
64
|
-
columns =
|
|
128
|
+
columns = safeJSONParse(queryColumns)
|
|
65
129
|
} else {
|
|
66
130
|
// Fallback: Get all columns from information_schema
|
|
67
131
|
try {
|
|
@@ -95,20 +159,8 @@ export default async function handler(req, res) {
|
|
|
95
159
|
}
|
|
96
160
|
}
|
|
97
161
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
101
|
-
if (Array.isArray(value)) {
|
|
102
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
103
|
-
queryParams.push(...value)
|
|
104
|
-
conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
|
|
105
|
-
} else {
|
|
106
|
-
conditions.push(\`\${key} = $\${paramIndex}\`)
|
|
107
|
-
queryParams.push(value)
|
|
108
|
-
paramIndex++
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
}
|
|
162
|
+
// Apply filters using helper function
|
|
163
|
+
paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
|
|
112
164
|
|
|
113
165
|
let sql = \`SELECT * FROM ${tableName}\`
|
|
114
166
|
|
|
@@ -116,7 +168,21 @@ export default async function handler(req, res) {
|
|
|
116
168
|
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
117
169
|
}
|
|
118
170
|
|
|
119
|
-
|
|
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) {
|
|
120
186
|
sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
121
187
|
}
|
|
122
188
|
|
|
@@ -136,7 +202,7 @@ export default async function handler(req, res) {
|
|
|
136
202
|
const plainRows = rows.map((row) =>
|
|
137
203
|
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
138
204
|
)
|
|
139
|
-
const safeData = JSON.parse(JSON.stringify(plainRows))
|
|
205
|
+
const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))
|
|
140
206
|
|
|
141
207
|
return res.status(200).json({
|
|
142
208
|
success: true,
|
|
@@ -186,7 +252,8 @@ async function getCount(req, res) {
|
|
|
186
252
|
|
|
187
253
|
if (queryColumns) {
|
|
188
254
|
// Use specified columns
|
|
189
|
-
|
|
255
|
+
const parsed = safeJSONParse(queryColumns)
|
|
256
|
+
columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
190
257
|
} else {
|
|
191
258
|
// Fallback: Get all columns from information_schema
|
|
192
259
|
try {
|
|
@@ -218,13 +285,8 @@ async function getCount(req, res) {
|
|
|
218
285
|
}
|
|
219
286
|
}
|
|
220
287
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
for (const filter of parsedFilters) {
|
|
224
|
-
conditions.push(\`\${filter.column} \${filter.operator} $\${paramIndex++}\`)
|
|
225
|
-
queryParams.push(filter.value)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
288
|
+
// Apply filters using helper function
|
|
289
|
+
paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
|
|
228
290
|
|
|
229
291
|
let countSql = \`SELECT COUNT(*) FROM ${tableName}\`
|
|
230
292
|
if (conditions.length > 0) {
|
package/src/fetchers/redis.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
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,10 @@ export const generateRedisFetcher = (config: Record<string, unknown>): string =>
|
|
|
63
67
|
|
|
64
68
|
return `import { createClient } from 'redis'
|
|
65
69
|
|
|
70
|
+
${generateSafeJSONParseCode()}
|
|
71
|
+
|
|
72
|
+
${generateDateFormatterCode()}
|
|
73
|
+
|
|
66
74
|
export default async function handler(req, res) {
|
|
67
75
|
let client = null
|
|
68
76
|
try {
|
|
@@ -74,9 +82,23 @@ export default async function handler(req, res) {
|
|
|
74
82
|
|
|
75
83
|
await client.connect()
|
|
76
84
|
|
|
77
|
-
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
|
+
}
|
|
78
101
|
|
|
79
|
-
const pattern = (filters && JSON.parse(filters).pattern) || query || '*'
|
|
80
102
|
const keys = await client.keys(pattern)
|
|
81
103
|
|
|
82
104
|
const limitValue = limit || perPage || 100
|
|
@@ -117,7 +139,23 @@ export default async function handler(req, res) {
|
|
|
117
139
|
})
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
|
|
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) {
|
|
121
159
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
122
160
|
results.sort((a, b) => {
|
|
123
161
|
const aVal = a[sortBy]
|
|
@@ -128,7 +166,7 @@ export default async function handler(req, res) {
|
|
|
128
166
|
})
|
|
129
167
|
}
|
|
130
168
|
|
|
131
|
-
const safeData = JSON.parse(JSON.stringify(results))
|
|
169
|
+
const safeData = JSON.parse(JSON.stringify(results, dateReplacer))
|
|
132
170
|
|
|
133
171
|
return res.status(200).json({
|
|
134
172
|
success: true,
|
package/src/fetchers/redshift.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
interface RedshiftConfig {
|
|
4
8
|
host?: string
|
|
@@ -49,6 +53,10 @@ const getClient = () => {
|
|
|
49
53
|
})
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
${generateSafeJSONParseCode()}
|
|
57
|
+
|
|
58
|
+
${generateDateFormatterCode()}
|
|
59
|
+
|
|
52
60
|
export default async function handler(req, res) {
|
|
53
61
|
const client = getClient()
|
|
54
62
|
|
|
@@ -56,7 +64,7 @@ export default async function handler(req, res) {
|
|
|
56
64
|
await client.connect()
|
|
57
65
|
${schema ? `await client.query('SET search_path TO ${schema}')` : ''}
|
|
58
66
|
|
|
59
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
67
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
60
68
|
|
|
61
69
|
const conditions = []
|
|
62
70
|
const queryParams = []
|
|
@@ -66,7 +74,8 @@ export default async function handler(req, res) {
|
|
|
66
74
|
let columns = []
|
|
67
75
|
|
|
68
76
|
if (queryColumns) {
|
|
69
|
-
|
|
77
|
+
const parsed = safeJSONParse(queryColumns)
|
|
78
|
+
columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
70
79
|
} else {
|
|
71
80
|
// Fallback: Get all columns from information_schema
|
|
72
81
|
try {
|
|
@@ -96,19 +105,65 @@ export default async function handler(req, res) {
|
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
// Helper to sanitize identifier (prevent SQL injection in column names)
|
|
109
|
+
const sanitizeIdentifier = (name) => {
|
|
110
|
+
// Only allow alphanumeric, underscore, and dot (for schema.table)
|
|
111
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
112
|
+
throw new Error(\`Invalid identifier: \${name}\`)
|
|
113
|
+
}
|
|
114
|
+
return \`"\${name}"\`
|
|
115
|
+
}
|
|
116
|
+
|
|
99
117
|
if (filters) {
|
|
100
|
-
const parsedFilters =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
const parsedFilters = safeJSONParse(filters)
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(parsedFilters)) {
|
|
121
|
+
parsedFilters.forEach((filter) => {
|
|
122
|
+
if (!filter.source || filter.destination === undefined) return
|
|
123
|
+
|
|
124
|
+
const field = sanitizeIdentifier(filter.source)
|
|
125
|
+
const value = filter.destination
|
|
126
|
+
const operand = filter.operand || '='
|
|
127
|
+
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
if (value.length === 0) return
|
|
130
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
131
|
+
queryParams.push(...value)
|
|
132
|
+
if (operand === '!=') {
|
|
133
|
+
conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
|
|
134
|
+
} else {
|
|
135
|
+
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
if (value === null) {
|
|
139
|
+
if (operand === '=') {
|
|
140
|
+
conditions.push(\`\${field} IS NULL\`)
|
|
141
|
+
} else if (operand === '!=') {
|
|
142
|
+
conditions.push(\`\${field} IS NOT NULL\`)
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
146
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
147
|
+
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
148
|
+
queryParams.push(value)
|
|
149
|
+
paramIndex++
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
} else {
|
|
154
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
155
|
+
const field = sanitizeIdentifier(key)
|
|
156
|
+
if (Array.isArray(value)) {
|
|
157
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
158
|
+
queryParams.push(...value)
|
|
159
|
+
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
160
|
+
} else {
|
|
161
|
+
conditions.push(\`\${field} = $\${paramIndex}\`)
|
|
162
|
+
queryParams.push(value)
|
|
163
|
+
paramIndex++
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
112
167
|
}
|
|
113
168
|
|
|
114
169
|
let sql = \`SELECT * FROM ${tableName}\`
|
|
@@ -117,8 +172,22 @@ export default async function handler(req, res) {
|
|
|
117
172
|
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
118
173
|
}
|
|
119
174
|
|
|
120
|
-
|
|
121
|
-
|
|
175
|
+
// Handle sorts - new array format
|
|
176
|
+
if (sorts) {
|
|
177
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
178
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
179
|
+
const orderClauses = parsedSorts.map((sort) => {
|
|
180
|
+
if (!sort.field) return null
|
|
181
|
+
const order = sort.order?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
|
|
182
|
+
return \`\${sanitizeIdentifier(sort.field)} \${order}\`
|
|
183
|
+
}).filter(Boolean)
|
|
184
|
+
|
|
185
|
+
if (orderClauses.length > 0) {
|
|
186
|
+
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} else if (sortBy) {
|
|
190
|
+
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
122
191
|
}
|
|
123
192
|
|
|
124
193
|
const limitValue = limit || perPage
|
|
@@ -137,7 +206,7 @@ export default async function handler(req, res) {
|
|
|
137
206
|
const plainRows = rows.map((row) =>
|
|
138
207
|
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
139
208
|
)
|
|
140
|
-
const safeData = JSON.parse(JSON.stringify(plainRows))
|
|
209
|
+
const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))
|
|
141
210
|
|
|
142
211
|
return res.status(200).json({
|
|
143
212
|
success: true,
|
package/src/fetchers/rest-api.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateDateFormatterCode,
|
|
3
|
+
generateSortFilterHelperCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
6
|
+
|
|
1
7
|
export const validateRESTAPIConfig = (
|
|
2
8
|
config: Record<string, unknown>
|
|
3
9
|
): { isValid: boolean; error?: string } => {
|
|
@@ -75,9 +81,15 @@ export const generateRESTAPIFetcher = (config: Record<string, unknown>): string
|
|
|
75
81
|
|
|
76
82
|
return `import fetch from 'node-fetch'
|
|
77
83
|
|
|
84
|
+
${generateSafeJSONParseCode()}
|
|
85
|
+
|
|
86
|
+
${generateDateFormatterCode()}
|
|
87
|
+
|
|
88
|
+
${generateSortFilterHelperCode()}
|
|
89
|
+
|
|
78
90
|
export default async function handler(req, res) {
|
|
79
91
|
try {
|
|
80
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
92
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
81
93
|
|
|
82
94
|
const url = ${JSON.stringify(restConfig.url)}
|
|
83
95
|
const method = ${JSON.stringify(restConfig.method || 'GET')}
|
|
@@ -116,18 +128,17 @@ export default async function handler(req, res) {
|
|
|
116
128
|
|
|
117
129
|
let data = await response.json()
|
|
118
130
|
|
|
119
|
-
// Apply filtering, sorting, and pagination if data is an array
|
|
120
131
|
if (Array.isArray(data)) {
|
|
121
|
-
// 1. Apply search filter
|
|
122
132
|
if (query && query.trim()) {
|
|
123
133
|
const searchQuery = query.toLowerCase()
|
|
124
134
|
|
|
125
135
|
if (queryColumns) {
|
|
126
136
|
try {
|
|
127
|
-
const
|
|
137
|
+
const parsed = safeJSONParse(queryColumns)
|
|
138
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
128
139
|
data = data.filter((item) => {
|
|
129
140
|
return columns.some((col) => {
|
|
130
|
-
const value = item
|
|
141
|
+
const value = getNestedValue(item, col)
|
|
131
142
|
if (value === null || value === undefined) return false
|
|
132
143
|
return String(value).toLowerCase().includes(searchQuery)
|
|
133
144
|
})
|
|
@@ -136,7 +147,6 @@ export default async function handler(req, res) {
|
|
|
136
147
|
console.error('Error parsing queryColumns:', err)
|
|
137
148
|
}
|
|
138
149
|
} else {
|
|
139
|
-
// Search across all fields
|
|
140
150
|
data = data.filter((item) => {
|
|
141
151
|
try {
|
|
142
152
|
const stringified = JSON.stringify(item).toLowerCase()
|
|
@@ -148,36 +158,107 @@ export default async function handler(req, res) {
|
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
// 2. Apply custom filters
|
|
152
161
|
if (filters) {
|
|
153
162
|
try {
|
|
154
|
-
const parsedFilters =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
const parsedFilters = safeJSONParse(filters)
|
|
164
|
+
|
|
165
|
+
if (Array.isArray(parsedFilters)) {
|
|
166
|
+
data = data.filter((item) => {
|
|
167
|
+
return parsedFilters.every((filter) => {
|
|
168
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
169
|
+
|
|
170
|
+
const field = filter.source
|
|
171
|
+
const value = getNestedValue(item, field)
|
|
172
|
+
const target = filter.destination
|
|
173
|
+
const operand = filter.operand || '='
|
|
174
|
+
|
|
175
|
+
if (Array.isArray(target)) {
|
|
176
|
+
if (operand === '!=') {
|
|
177
|
+
return !target.includes(value)
|
|
178
|
+
}
|
|
179
|
+
return target.includes(value)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return compareValues(value, target, operand)
|
|
183
|
+
})
|
|
161
184
|
})
|
|
162
|
-
}
|
|
185
|
+
} else {
|
|
186
|
+
data = data.filter((item) => {
|
|
187
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
188
|
+
const itemValue = getNestedValue(item, key)
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
return value.includes(itemValue)
|
|
191
|
+
}
|
|
192
|
+
return compareValues(itemValue, value, '=')
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
}
|
|
163
196
|
} catch (err) {
|
|
164
197
|
console.error('Error parsing filters:', err)
|
|
165
198
|
}
|
|
166
199
|
}
|
|
167
200
|
|
|
168
|
-
|
|
169
|
-
|
|
201
|
+
if (sorts) {
|
|
202
|
+
try {
|
|
203
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
204
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
205
|
+
data.sort((a, b) => {
|
|
206
|
+
for (const sort of parsedSorts) {
|
|
207
|
+
if (!sort.field) continue
|
|
208
|
+
const aVal = getNestedValue(a, sort.field)
|
|
209
|
+
const bVal = getNestedValue(b, sort.field)
|
|
210
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
211
|
+
|
|
212
|
+
let comparison = 0
|
|
213
|
+
if (aVal === null || aVal === undefined) {
|
|
214
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
215
|
+
} else if (bVal === null || bVal === undefined) {
|
|
216
|
+
comparison = 1
|
|
217
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
218
|
+
comparison = aVal - bVal
|
|
219
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
220
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
221
|
+
} else {
|
|
222
|
+
const aStr = String(aVal)
|
|
223
|
+
const bStr = String(bVal)
|
|
224
|
+
if (aStr < bStr) comparison = -1
|
|
225
|
+
else if (aStr > bStr) comparison = 1
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
229
|
+
}
|
|
230
|
+
return 0
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error('Error parsing sorts:', err)
|
|
235
|
+
}
|
|
236
|
+
} else if (sortBy && sortBy.trim()) {
|
|
170
237
|
data.sort((a, b) => {
|
|
171
|
-
const aVal = a
|
|
172
|
-
const bVal = b
|
|
238
|
+
const aVal = getNestedValue(a, sortBy)
|
|
239
|
+
const bVal = getNestedValue(b, sortBy)
|
|
173
240
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
241
|
+
|
|
242
|
+
let comparison = 0
|
|
243
|
+
if (aVal === null || aVal === undefined) {
|
|
244
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
245
|
+
} else if (bVal === null || bVal === undefined) {
|
|
246
|
+
comparison = 1
|
|
247
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
248
|
+
comparison = aVal - bVal
|
|
249
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
250
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
251
|
+
} else {
|
|
252
|
+
const aStr = String(aVal)
|
|
253
|
+
const bStr = String(bVal)
|
|
254
|
+
if (aStr < bStr) comparison = -1
|
|
255
|
+
else if (aStr > bStr) comparison = 1
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return comparison * sortOrderValue
|
|
177
259
|
})
|
|
178
260
|
}
|
|
179
261
|
|
|
180
|
-
// 4. Apply pagination
|
|
181
262
|
const limitValue = limit || perPage
|
|
182
263
|
const pageValue = page ? Math.max(1, parseInt(page)) : undefined
|
|
183
264
|
const offsetValue = offset !== undefined ? Math.max(0, parseInt(offset)) : (pageValue && perPage ? (pageValue - 1) * Math.max(1, parseInt(perPage)) : 0)
|
|
@@ -190,7 +271,7 @@ export default async function handler(req, res) {
|
|
|
190
271
|
}
|
|
191
272
|
}
|
|
192
273
|
|
|
193
|
-
const safeData = JSON.parse(JSON.stringify(data))
|
|
274
|
+
const safeData = JSON.parse(JSON.stringify(data, dateReplacer))
|
|
194
275
|
|
|
195
276
|
return res.status(200).json({
|
|
196
277
|
success: true,
|