@teleporthq/teleport-plugin-next-data-source 0.42.9 → 0.42.11
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 +1 -1
- 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 +22 -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 +1 -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 +2 -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 +1 -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 +2 -0
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +214 -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 +2 -2
- 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 -4
- 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 -2
- 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 -3
- 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 -2
- 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 +2 -0
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +211 -45
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/data-source-fetchers.ts +29 -13
- package/src/fetchers/airtable.ts +78 -30
- package/src/fetchers/clickhouse.ts +85 -18
- package/src/fetchers/csv-file.ts +254 -29
- package/src/fetchers/firestore.ts +62 -12
- package/src/fetchers/google-sheets.ts +147 -30
- package/src/fetchers/javascript.ts +102 -23
- package/src/fetchers/mariadb.ts +82 -25
- package/src/fetchers/mongodb.ts +153 -36
- package/src/fetchers/mysql.ts +83 -25
- package/src/fetchers/postgresql.ts +86 -26
- package/src/fetchers/redis.ts +40 -4
- package/src/fetchers/redshift.ts +84 -17
- package/src/fetchers/rest-api.ts +101 -24
- package/src/fetchers/static-collection.ts +96 -18
- package/src/fetchers/supabase.ts +175 -53
- package/src/fetchers/turso.ts +84 -17
- 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 +344 -38
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
export const validateFirestoreConfig = (
|
|
4
8
|
config: Record<string, unknown>
|
|
@@ -67,31 +71,67 @@ const getFirestore = () => {
|
|
|
67
71
|
return firestore
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
${generateSafeJSONParseCode()}
|
|
75
|
+
|
|
70
76
|
${generateDateFormatterCode()}
|
|
71
77
|
|
|
72
78
|
export default async function handler(req, res) {
|
|
73
79
|
try {
|
|
74
80
|
const firestore = getFirestore()
|
|
75
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
|
|
81
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
76
82
|
|
|
77
83
|
let queryRef = firestore.collection('${tableName}')
|
|
78
84
|
|
|
79
85
|
if (filters) {
|
|
80
|
-
const parsedFilters =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
const parsedFilters = safeJSONParse(filters)
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(parsedFilters)) {
|
|
89
|
+
parsedFilters.forEach((filter) => {
|
|
90
|
+
if (!filter.source || filter.destination === undefined) return
|
|
91
|
+
|
|
92
|
+
const field = filter.source
|
|
93
|
+
const value = filter.destination
|
|
94
|
+
const operand = filter.operand || '='
|
|
95
|
+
|
|
96
|
+
// Map operands to Firestore operators
|
|
97
|
+
const operatorMap = {
|
|
98
|
+
'=': '==',
|
|
99
|
+
'!=': '!=',
|
|
100
|
+
'>': '>',
|
|
101
|
+
'<': '<',
|
|
102
|
+
'>=': '>=',
|
|
103
|
+
'<=': '<=',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
if (value.length === 0) return
|
|
108
|
+
if (operand === '!=') {
|
|
109
|
+
queryRef = queryRef.where(field, 'not-in', value)
|
|
110
|
+
} else {
|
|
111
|
+
queryRef = queryRef.where(field, 'in', value)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
const firestoreOp = operatorMap[operand] || '=='
|
|
115
|
+
queryRef = queryRef.where(field, firestoreOp, value)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
queryRef = queryRef.where(key, 'in', value)
|
|
122
|
+
} else {
|
|
123
|
+
queryRef = queryRef.where(key, '==', value)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
88
127
|
}
|
|
89
128
|
|
|
90
129
|
let usePostFiltering = false
|
|
91
130
|
|
|
92
131
|
if (query) {
|
|
93
132
|
if (queryColumns) {
|
|
94
|
-
const
|
|
133
|
+
const parsed = safeJSONParse(queryColumns)
|
|
134
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
95
135
|
for (const column of columns) {
|
|
96
136
|
queryRef = queryRef
|
|
97
137
|
.where(column, '>=', query)
|
|
@@ -104,7 +144,17 @@ export default async function handler(req, res) {
|
|
|
104
144
|
}
|
|
105
145
|
}
|
|
106
146
|
|
|
107
|
-
|
|
147
|
+
// Handle sorts - new array format
|
|
148
|
+
if (sorts) {
|
|
149
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
150
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
151
|
+
parsedSorts.forEach((sort) => {
|
|
152
|
+
if (!sort.field) return
|
|
153
|
+
const order = sort.order?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
154
|
+
queryRef = queryRef.orderBy(sort.field, order)
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
} else if (sortBy) {
|
|
108
158
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
109
159
|
queryRef = queryRef.orderBy(sortBy, sortOrderValue)
|
|
110
160
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { generateSortFilterHelperCode, generateSafeJSONParseCode } from '../utils'
|
|
2
|
+
import { generateHeaderDetectionCode } from './utils/header-detection'
|
|
3
|
+
|
|
1
4
|
export const validateGoogleSheetsConfig = (
|
|
2
5
|
config: Record<string, unknown>
|
|
3
6
|
): { isValid: boolean; error?: string } => {
|
|
@@ -33,18 +36,33 @@ interface GoogleSheetsConfig {
|
|
|
33
36
|
sheetName?: string
|
|
34
37
|
range?: string
|
|
35
38
|
maxRows?: number
|
|
39
|
+
autoDetectHeader?: boolean
|
|
40
|
+
firstRowIsHeader?: boolean
|
|
41
|
+
columns?: Array<{ id: string; label?: string; type?: string }>
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
export const generateGoogleSheetsFetcher = (config: Record<string, unknown>): string => {
|
|
39
45
|
const sheetsConfig = config as GoogleSheetsConfig
|
|
46
|
+
const autoDetectHeader = sheetsConfig.autoDetectHeader
|
|
47
|
+
const firstRowIsHeader = sheetsConfig.firstRowIsHeader
|
|
48
|
+
const configColumns = sheetsConfig.columns || []
|
|
49
|
+
|
|
40
50
|
return `import fetch from 'node-fetch'
|
|
41
51
|
|
|
52
|
+
${generateSafeJSONParseCode()}
|
|
53
|
+
|
|
54
|
+
${generateSortFilterHelperCode()}
|
|
55
|
+
|
|
56
|
+
${generateHeaderDetectionCode()}
|
|
57
|
+
|
|
42
58
|
export default async function handler(req, res) {
|
|
43
59
|
try {
|
|
44
60
|
const sheetUrl = ${JSON.stringify(sheetsConfig.sheetUrl)}
|
|
45
61
|
let sheetId = ${JSON.stringify(sheetsConfig.sheetId)}
|
|
46
62
|
const range = ${JSON.stringify(sheetsConfig.range || 'A1:Z1000')}
|
|
47
63
|
const maxRows = ${sheetsConfig.maxRows || 0}
|
|
64
|
+
const autoDetectHeader = ${autoDetectHeader !== undefined ? autoDetectHeader : true}
|
|
65
|
+
const firstRowIsHeader = ${firstRowIsHeader !== undefined ? firstRowIsHeader : true}
|
|
48
66
|
|
|
49
67
|
if (!sheetId && sheetUrl) {
|
|
50
68
|
const match = sheetUrl.match(/\\/d\\/([a-zA-Z0-9-_]+)/)
|
|
@@ -158,26 +176,40 @@ export default async function handler(req, res) {
|
|
|
158
176
|
const firstRow = rawRows[0]
|
|
159
177
|
const firstRowValues = firstRow.c.map((cell) => cell?.v ?? cell?.f ?? null)
|
|
160
178
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
const shouldAutoDetect = autoDetectHeader !== false
|
|
180
|
+
let hasHeaderRow = false
|
|
181
|
+
|
|
182
|
+
if (shouldAutoDetect) {
|
|
183
|
+
hasHeaderRow = detectHeaderRow(firstRowValues, rawRows)
|
|
184
|
+
} else {
|
|
185
|
+
hasHeaderRow = firstRowIsHeader !== false
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const configColumns = ${JSON.stringify(configColumns)}
|
|
189
|
+
const useConfigColumns = configColumns && configColumns.length > 0
|
|
164
190
|
|
|
165
191
|
let columns
|
|
166
192
|
let dataRows
|
|
167
193
|
|
|
168
194
|
if (hasHeaderRow && rawRows.length > 1) {
|
|
169
|
-
columns =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
columns = table.cols.map((col, index) => {
|
|
196
|
+
const configCol = useConfigColumns ? configColumns[index] : null
|
|
197
|
+
return {
|
|
198
|
+
id: configCol?.id || col.id || \`col_\${index}\`,
|
|
199
|
+
label: String(firstRowValues[index] || col.label || configCol?.label || \`Column \${index + 1}\`),
|
|
200
|
+
type: configCol?.type || col.type || 'string'
|
|
201
|
+
}
|
|
202
|
+
})
|
|
174
203
|
dataRows = rawRows.slice(1)
|
|
175
204
|
} else {
|
|
176
|
-
columns = table.cols.map((col, index) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
205
|
+
columns = table.cols.map((col, index) => {
|
|
206
|
+
const configCol = useConfigColumns ? configColumns[index] : null
|
|
207
|
+
return {
|
|
208
|
+
id: configCol?.id || col.id || \`col_\${index}\`,
|
|
209
|
+
label: configCol?.label || col.label || \`Column \${index + 1}\`,
|
|
210
|
+
type: configCol?.type || col.type || 'string'
|
|
211
|
+
}
|
|
212
|
+
})
|
|
181
213
|
dataRows = rawRows
|
|
182
214
|
}
|
|
183
215
|
|
|
@@ -212,7 +244,13 @@ export default async function handler(req, res) {
|
|
|
212
244
|
return filteredRow
|
|
213
245
|
})
|
|
214
246
|
|
|
215
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
|
|
247
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query
|
|
248
|
+
|
|
249
|
+
// Create label-to-ID mapping for filters and sorts
|
|
250
|
+
const labelToIdMap = {}
|
|
251
|
+
columnsWithData.forEach((col) => {
|
|
252
|
+
labelToIdMap[col.label] = col.id
|
|
253
|
+
})
|
|
216
254
|
|
|
217
255
|
let filteredData = [...filteredRows]
|
|
218
256
|
|
|
@@ -220,10 +258,12 @@ export default async function handler(req, res) {
|
|
|
220
258
|
const searchQuery = query.toLowerCase()
|
|
221
259
|
|
|
222
260
|
if (queryColumns) {
|
|
223
|
-
const searchColumns =
|
|
261
|
+
const searchColumns = safeJSONParse(queryColumns)
|
|
224
262
|
filteredData = filteredData.filter((item) => {
|
|
225
263
|
return searchColumns.some((col) => {
|
|
226
|
-
|
|
264
|
+
// Map label to column ID
|
|
265
|
+
const field = labelToIdMap[col] || col
|
|
266
|
+
const value = getNestedValue(item, field)
|
|
227
267
|
return value && String(value).toLowerCase().includes(searchQuery)
|
|
228
268
|
})
|
|
229
269
|
})
|
|
@@ -240,25 +280,102 @@ export default async function handler(req, res) {
|
|
|
240
280
|
}
|
|
241
281
|
|
|
242
282
|
if (filters) {
|
|
243
|
-
const parsedFilters =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
283
|
+
const parsedFilters = safeJSONParse(filters)
|
|
284
|
+
|
|
285
|
+
if (Array.isArray(parsedFilters)) {
|
|
286
|
+
filteredData = filteredData.filter((item) => {
|
|
287
|
+
return parsedFilters.every((filter) => {
|
|
288
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
289
|
+
|
|
290
|
+
// Map label to column ID
|
|
291
|
+
const field = labelToIdMap[filter.source] || filter.source
|
|
292
|
+
const value = getNestedValue(item, field)
|
|
293
|
+
const target = filter.destination
|
|
294
|
+
const operand = filter.operand || '='
|
|
295
|
+
|
|
296
|
+
if (Array.isArray(target)) {
|
|
297
|
+
if (operand === '!=') {
|
|
298
|
+
return !target.includes(value)
|
|
299
|
+
}
|
|
300
|
+
return target.includes(value)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return compareValues(value, target, operand)
|
|
304
|
+
})
|
|
250
305
|
})
|
|
251
|
-
}
|
|
306
|
+
} else {
|
|
307
|
+
filteredData = filteredData.filter((item) => {
|
|
308
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
309
|
+
// Map label to column ID
|
|
310
|
+
const field = labelToIdMap[key] || key
|
|
311
|
+
const itemValue = getNestedValue(item, field)
|
|
312
|
+
if (Array.isArray(value)) {
|
|
313
|
+
return value.includes(itemValue)
|
|
314
|
+
}
|
|
315
|
+
return compareValues(itemValue, value, '=')
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
}
|
|
252
319
|
}
|
|
253
320
|
|
|
254
|
-
if (
|
|
321
|
+
if (sorts) {
|
|
322
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
323
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
324
|
+
filteredData.sort((a, b) => {
|
|
325
|
+
for (const sort of parsedSorts) {
|
|
326
|
+
if (!sort.field) continue
|
|
327
|
+
// Map label to column ID
|
|
328
|
+
const field = labelToIdMap[sort.field] || sort.field
|
|
329
|
+
const aVal = getNestedValue(a, field)
|
|
330
|
+
const bVal = getNestedValue(b, field)
|
|
331
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
332
|
+
|
|
333
|
+
let comparison = 0
|
|
334
|
+
if (aVal === null || aVal === undefined) {
|
|
335
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
336
|
+
} else if (bVal === null || bVal === undefined) {
|
|
337
|
+
comparison = 1
|
|
338
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
339
|
+
comparison = aVal - bVal
|
|
340
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
341
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
342
|
+
} else {
|
|
343
|
+
const aStr = String(aVal)
|
|
344
|
+
const bStr = String(bVal)
|
|
345
|
+
if (aStr < bStr) comparison = -1
|
|
346
|
+
else if (aStr > bStr) comparison = 1
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
350
|
+
}
|
|
351
|
+
return 0
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
} else if (sortBy) {
|
|
255
355
|
filteredData.sort((a, b) => {
|
|
256
|
-
|
|
257
|
-
const
|
|
356
|
+
// Map label to column ID
|
|
357
|
+
const field = labelToIdMap[sortBy] || sortBy
|
|
358
|
+
const aVal = getNestedValue(a, field)
|
|
359
|
+
const bVal = getNestedValue(b, field)
|
|
258
360
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
361
|
+
|
|
362
|
+
let comparison = 0
|
|
363
|
+
if (aVal === null || aVal === undefined) {
|
|
364
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
365
|
+
} else if (bVal === null || bVal === undefined) {
|
|
366
|
+
comparison = 1
|
|
367
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
368
|
+
comparison = aVal - bVal
|
|
369
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
370
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
371
|
+
} else {
|
|
372
|
+
const aStr = String(aVal)
|
|
373
|
+
const bStr = String(bVal)
|
|
374
|
+
if (aStr < bStr) comparison = -1
|
|
375
|
+
else if (aStr > bStr) comparison = 1
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return comparison * sortOrderValue
|
|
262
379
|
})
|
|
263
380
|
}
|
|
264
381
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateDateFormatterCode,
|
|
3
|
+
generateSortFilterHelperCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
export const validateJavaScriptConfig = (
|
|
4
8
|
config: Record<string, unknown>
|
|
@@ -37,27 +41,31 @@ interface JavaScriptConfig {
|
|
|
37
41
|
|
|
38
42
|
export const generateJavaScriptFetcher = (config: Record<string, unknown>): string => {
|
|
39
43
|
const jsConfig = config as JavaScriptConfig
|
|
40
|
-
return `${
|
|
44
|
+
return `${generateSafeJSONParseCode()}
|
|
45
|
+
|
|
46
|
+
${generateDateFormatterCode()}
|
|
47
|
+
|
|
48
|
+
${generateSortFilterHelperCode()}
|
|
41
49
|
|
|
42
50
|
export default async function handler(req, res) {
|
|
43
51
|
try {
|
|
44
|
-
const { limit, offset, page, perPage, query, queryColumns, sortBy, sortOrder, filters } = req.query
|
|
52
|
+
const { limit, offset, page, perPage, query, queryColumns, sortBy, sortOrder, filters, sorts } = req.query
|
|
45
53
|
|
|
46
54
|
const code = ${JSON.stringify(jsConfig.code)}
|
|
47
55
|
const executeCode = new Function('return ' + code)
|
|
48
56
|
let data = executeCode()
|
|
49
57
|
|
|
50
58
|
if (Array.isArray(data)) {
|
|
51
|
-
// 1. Apply search filter
|
|
52
59
|
if (query && query.trim()) {
|
|
53
60
|
const searchQuery = query.toLowerCase()
|
|
54
61
|
|
|
55
62
|
if (queryColumns) {
|
|
56
63
|
try {
|
|
57
|
-
const
|
|
64
|
+
const parsed = safeJSONParse(queryColumns)
|
|
65
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
58
66
|
data = data.filter(item => {
|
|
59
67
|
return columns.some(col => {
|
|
60
|
-
const value = item
|
|
68
|
+
const value = getNestedValue(item, col)
|
|
61
69
|
if (value === null || value === undefined) return false
|
|
62
70
|
return String(value).toLowerCase().includes(searchQuery)
|
|
63
71
|
})
|
|
@@ -77,36 +85,107 @@ export default async function handler(req, res) {
|
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
87
|
|
|
80
|
-
// 2. Apply custom filters
|
|
81
88
|
if (filters) {
|
|
82
89
|
try {
|
|
83
|
-
const parsedFilters =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
const parsedFilters = safeJSONParse(filters)
|
|
91
|
+
|
|
92
|
+
if (Array.isArray(parsedFilters)) {
|
|
93
|
+
data = data.filter((item) => {
|
|
94
|
+
return parsedFilters.every((filter) => {
|
|
95
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
96
|
+
|
|
97
|
+
const field = filter.source
|
|
98
|
+
const value = getNestedValue(item, field)
|
|
99
|
+
const target = filter.destination
|
|
100
|
+
const operand = filter.operand || '='
|
|
101
|
+
|
|
102
|
+
if (Array.isArray(target)) {
|
|
103
|
+
if (operand === '!=') {
|
|
104
|
+
return !target.includes(value)
|
|
105
|
+
}
|
|
106
|
+
return target.includes(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return compareValues(value, target, operand)
|
|
110
|
+
})
|
|
90
111
|
})
|
|
91
|
-
}
|
|
112
|
+
} else {
|
|
113
|
+
data = data.filter((item) => {
|
|
114
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
115
|
+
const itemValue = getNestedValue(item, key)
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
return value.includes(itemValue)
|
|
118
|
+
}
|
|
119
|
+
return compareValues(itemValue, value, '=')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
}
|
|
92
123
|
} catch (err) {
|
|
93
124
|
console.error('Error parsing filters:', err)
|
|
94
125
|
}
|
|
95
126
|
}
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
if (sorts) {
|
|
129
|
+
try {
|
|
130
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
131
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
132
|
+
data.sort((a, b) => {
|
|
133
|
+
for (const sort of parsedSorts) {
|
|
134
|
+
if (!sort.field) continue
|
|
135
|
+
const aVal = getNestedValue(a, sort.field)
|
|
136
|
+
const bVal = getNestedValue(b, sort.field)
|
|
137
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
138
|
+
|
|
139
|
+
let comparison = 0
|
|
140
|
+
if (aVal === null || aVal === undefined) {
|
|
141
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
142
|
+
} else if (bVal === null || bVal === undefined) {
|
|
143
|
+
comparison = 1
|
|
144
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
145
|
+
comparison = aVal - bVal
|
|
146
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
147
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
148
|
+
} else {
|
|
149
|
+
const aStr = String(aVal)
|
|
150
|
+
const bStr = String(bVal)
|
|
151
|
+
if (aStr < bStr) comparison = -1
|
|
152
|
+
else if (aStr > bStr) comparison = 1
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
156
|
+
}
|
|
157
|
+
return 0
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error('Error parsing sorts:', err)
|
|
162
|
+
}
|
|
163
|
+
} else if (sortBy && sortBy.trim()) {
|
|
99
164
|
data.sort((a, b) => {
|
|
100
|
-
const aVal = a
|
|
101
|
-
const bVal = b
|
|
165
|
+
const aVal = getNestedValue(a, sortBy)
|
|
166
|
+
const bVal = getNestedValue(b, sortBy)
|
|
102
167
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
168
|
+
|
|
169
|
+
let comparison = 0
|
|
170
|
+
if (aVal === null || aVal === undefined) {
|
|
171
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
172
|
+
} else if (bVal === null || bVal === undefined) {
|
|
173
|
+
comparison = 1
|
|
174
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
175
|
+
comparison = aVal - bVal
|
|
176
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
177
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
178
|
+
} else {
|
|
179
|
+
const aStr = String(aVal)
|
|
180
|
+
const bStr = String(bVal)
|
|
181
|
+
if (aStr < bStr) comparison = -1
|
|
182
|
+
else if (aStr > bStr) comparison = 1
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return comparison * sortOrderValue
|
|
106
186
|
})
|
|
107
187
|
}
|
|
108
188
|
|
|
109
|
-
// 4. Apply pagination
|
|
110
189
|
const limitValue = limit || perPage
|
|
111
190
|
const pageValue = page ? Math.max(1, parseInt(page)) : undefined
|
|
112
191
|
const offsetValue = offset !== undefined ? Math.max(0, parseInt(offset)) : (pageValue && perPage ? (pageValue - 1) * Math.max(1, parseInt(perPage)) : 0)
|
package/src/fetchers/mariadb.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
interface MariaDBConfig {
|
|
4
8
|
host?: string
|
|
@@ -20,6 +24,60 @@ 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
|
+
|
|
23
81
|
${generateDateFormatterCode()}
|
|
24
82
|
|
|
25
83
|
export default async function handler(req, res) {
|
|
@@ -54,7 +112,7 @@ export default async function handler(req, res) {
|
|
|
54
112
|
}
|
|
55
113
|
})
|
|
56
114
|
|
|
57
|
-
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
|
|
58
116
|
|
|
59
117
|
const conditions = []
|
|
60
118
|
const queryParams = []
|
|
@@ -64,7 +122,7 @@ export default async function handler(req, res) {
|
|
|
64
122
|
|
|
65
123
|
if (queryColumns) {
|
|
66
124
|
// Use specified columns
|
|
67
|
-
columns =
|
|
125
|
+
columns = safeJSONParse(queryColumns)
|
|
68
126
|
} else {
|
|
69
127
|
// Fallback: Get all columns from information_schema
|
|
70
128
|
try {
|
|
@@ -88,19 +146,8 @@ export default async function handler(req, res) {
|
|
|
88
146
|
}
|
|
89
147
|
}
|
|
90
148
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
94
|
-
if (Array.isArray(value)) {
|
|
95
|
-
const placeholders = value.map(() => '?').join(', ')
|
|
96
|
-
queryParams.push(...value)
|
|
97
|
-
conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
|
|
98
|
-
} else {
|
|
99
|
-
conditions.push(\`\\\`\${key}\\\` = ?\`)
|
|
100
|
-
queryParams.push(value)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
}
|
|
149
|
+
// Apply filters using helper function
|
|
150
|
+
processFilters(filters, conditions, queryParams)
|
|
104
151
|
|
|
105
152
|
let sql = \`SELECT * FROM \\\`${tableName}\\\`\`
|
|
106
153
|
|
|
@@ -108,7 +155,21 @@ export default async function handler(req, res) {
|
|
|
108
155
|
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
109
156
|
}
|
|
110
157
|
|
|
111
|
-
|
|
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) {
|
|
112
173
|
sql += \` ORDER BY \\\`\${sortBy}\\\` \${sortOrder?.toUpperCase() || 'ASC'}\`
|
|
113
174
|
}
|
|
114
175
|
|
|
@@ -205,7 +266,8 @@ async function getCount(req, res) {
|
|
|
205
266
|
|
|
206
267
|
if (queryColumns) {
|
|
207
268
|
// Use specified columns
|
|
208
|
-
|
|
269
|
+
const parsed = safeJSONParse(queryColumns)
|
|
270
|
+
columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
209
271
|
} else {
|
|
210
272
|
// Fallback: Get all columns from information_schema
|
|
211
273
|
try {
|
|
@@ -229,13 +291,8 @@ async function getCount(req, res) {
|
|
|
229
291
|
}
|
|
230
292
|
}
|
|
231
293
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
for (const filter of parsedFilters) {
|
|
235
|
-
conditions.push(\`\${filter.column} \${filter.operator} ?\`)
|
|
236
|
-
queryParams.push(filter.value)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
294
|
+
// Apply filters using helper function
|
|
295
|
+
processFilters(filters, conditions, queryParams)
|
|
239
296
|
|
|
240
297
|
let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
|
|
241
298
|
if (conditions.length > 0) {
|