@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
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,8 @@ const getClient = () => {
|
|
|
49
53
|
})
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
${generateSafeJSONParseCode()}
|
|
57
|
+
|
|
52
58
|
${generateDateFormatterCode()}
|
|
53
59
|
|
|
54
60
|
export default async function handler(req, res) {
|
|
@@ -58,7 +64,7 @@ export default async function handler(req, res) {
|
|
|
58
64
|
await client.connect()
|
|
59
65
|
${schema ? `await client.query('SET search_path TO ${schema}')` : ''}
|
|
60
66
|
|
|
61
|
-
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
|
|
62
68
|
|
|
63
69
|
const conditions = []
|
|
64
70
|
const queryParams = []
|
|
@@ -68,7 +74,8 @@ export default async function handler(req, res) {
|
|
|
68
74
|
let columns = []
|
|
69
75
|
|
|
70
76
|
if (queryColumns) {
|
|
71
|
-
|
|
77
|
+
const parsed = safeJSONParse(queryColumns)
|
|
78
|
+
columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
72
79
|
} else {
|
|
73
80
|
// Fallback: Get all columns from information_schema
|
|
74
81
|
try {
|
|
@@ -98,19 +105,65 @@ export default async function handler(req, res) {
|
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
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
|
+
|
|
101
117
|
if (filters) {
|
|
102
|
-
const parsedFilters =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
}
|
|
114
167
|
}
|
|
115
168
|
|
|
116
169
|
let sql = \`SELECT * FROM ${tableName}\`
|
|
@@ -119,8 +172,22 @@ export default async function handler(req, res) {
|
|
|
119
172
|
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
120
173
|
}
|
|
121
174
|
|
|
122
|
-
|
|
123
|
-
|
|
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'}\`
|
|
124
191
|
}
|
|
125
192
|
|
|
126
193
|
const limitValue = limit || perPage
|
package/src/fetchers/rest-api.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateDateFormatterCode,
|
|
3
|
+
generateSortFilterHelperCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
export const validateRESTAPIConfig = (
|
|
4
8
|
config: Record<string, unknown>
|
|
@@ -77,11 +81,15 @@ export const generateRESTAPIFetcher = (config: Record<string, unknown>): string
|
|
|
77
81
|
|
|
78
82
|
return `import fetch from 'node-fetch'
|
|
79
83
|
|
|
84
|
+
${generateSafeJSONParseCode()}
|
|
85
|
+
|
|
80
86
|
${generateDateFormatterCode()}
|
|
81
87
|
|
|
88
|
+
${generateSortFilterHelperCode()}
|
|
89
|
+
|
|
82
90
|
export default async function handler(req, res) {
|
|
83
91
|
try {
|
|
84
|
-
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
|
|
85
93
|
|
|
86
94
|
const url = ${JSON.stringify(restConfig.url)}
|
|
87
95
|
const method = ${JSON.stringify(restConfig.method || 'GET')}
|
|
@@ -120,18 +128,17 @@ export default async function handler(req, res) {
|
|
|
120
128
|
|
|
121
129
|
let data = await response.json()
|
|
122
130
|
|
|
123
|
-
// Apply filtering, sorting, and pagination if data is an array
|
|
124
131
|
if (Array.isArray(data)) {
|
|
125
|
-
// 1. Apply search filter
|
|
126
132
|
if (query && query.trim()) {
|
|
127
133
|
const searchQuery = query.toLowerCase()
|
|
128
134
|
|
|
129
135
|
if (queryColumns) {
|
|
130
136
|
try {
|
|
131
|
-
const
|
|
137
|
+
const parsed = safeJSONParse(queryColumns)
|
|
138
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
132
139
|
data = data.filter((item) => {
|
|
133
140
|
return columns.some((col) => {
|
|
134
|
-
const value = item
|
|
141
|
+
const value = getNestedValue(item, col)
|
|
135
142
|
if (value === null || value === undefined) return false
|
|
136
143
|
return String(value).toLowerCase().includes(searchQuery)
|
|
137
144
|
})
|
|
@@ -140,7 +147,6 @@ export default async function handler(req, res) {
|
|
|
140
147
|
console.error('Error parsing queryColumns:', err)
|
|
141
148
|
}
|
|
142
149
|
} else {
|
|
143
|
-
// Search across all fields
|
|
144
150
|
data = data.filter((item) => {
|
|
145
151
|
try {
|
|
146
152
|
const stringified = JSON.stringify(item).toLowerCase()
|
|
@@ -152,36 +158,107 @@ export default async function handler(req, res) {
|
|
|
152
158
|
}
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
// 2. Apply custom filters
|
|
156
161
|
if (filters) {
|
|
157
162
|
try {
|
|
158
|
-
const parsedFilters =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
})
|
|
165
184
|
})
|
|
166
|
-
}
|
|
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
|
+
}
|
|
167
196
|
} catch (err) {
|
|
168
197
|
console.error('Error parsing filters:', err)
|
|
169
198
|
}
|
|
170
199
|
}
|
|
171
200
|
|
|
172
|
-
|
|
173
|
-
|
|
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()) {
|
|
174
237
|
data.sort((a, b) => {
|
|
175
|
-
const aVal = a
|
|
176
|
-
const bVal = b
|
|
238
|
+
const aVal = getNestedValue(a, sortBy)
|
|
239
|
+
const bVal = getNestedValue(b, sortBy)
|
|
177
240
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
181
259
|
})
|
|
182
260
|
}
|
|
183
261
|
|
|
184
|
-
// 4. Apply pagination
|
|
185
262
|
const limitValue = limit || perPage
|
|
186
263
|
const pageValue = page ? Math.max(1, parseInt(page)) : undefined
|
|
187
264
|
const offsetValue = offset !== undefined ? Math.max(0, parseInt(offset)) : (pageValue && perPage ? (pageValue - 1) * Math.max(1, parseInt(perPage)) : 0)
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateDateFormatterCode,
|
|
3
|
+
generateSortFilterHelperCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
} from '../utils'
|
|
2
6
|
|
|
3
7
|
export const validateStaticCollectionConfig = (
|
|
4
8
|
config: Record<string, unknown>
|
|
@@ -22,11 +26,15 @@ export const generateStaticCollectionFetcher = (config: Record<string, unknown>)
|
|
|
22
26
|
const staticConfig = config as StaticCollectionConfig
|
|
23
27
|
return `const data = ${JSON.stringify(staticConfig.data || [])}
|
|
24
28
|
|
|
29
|
+
${generateSafeJSONParseCode()}
|
|
30
|
+
|
|
25
31
|
${generateDateFormatterCode()}
|
|
26
32
|
|
|
33
|
+
${generateSortFilterHelperCode()}
|
|
34
|
+
|
|
27
35
|
export default async function handler(req, res) {
|
|
28
36
|
try {
|
|
29
|
-
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
|
|
37
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query
|
|
30
38
|
|
|
31
39
|
let filteredData = [...data]
|
|
32
40
|
|
|
@@ -34,10 +42,10 @@ export default async function handler(req, res) {
|
|
|
34
42
|
const searchQuery = query.toLowerCase()
|
|
35
43
|
|
|
36
44
|
if (queryColumns) {
|
|
37
|
-
const columns =
|
|
45
|
+
const columns = safeJSONParse(queryColumns)
|
|
38
46
|
filteredData = filteredData.filter((item) => {
|
|
39
47
|
return columns.some((col) => {
|
|
40
|
-
const value = item
|
|
48
|
+
const value = getNestedValue(item, col)
|
|
41
49
|
return value && String(value).toLowerCase().includes(searchQuery)
|
|
42
50
|
})
|
|
43
51
|
})
|
|
@@ -54,25 +62,95 @@ export default async function handler(req, res) {
|
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
if (filters) {
|
|
57
|
-
const parsedFilters =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
const parsedFilters = safeJSONParse(filters)
|
|
66
|
+
|
|
67
|
+
if (Array.isArray(parsedFilters)) {
|
|
68
|
+
filteredData = filteredData.filter((item) => {
|
|
69
|
+
return parsedFilters.every((filter) => {
|
|
70
|
+
if (!filter.source || filter.destination === undefined) return true
|
|
71
|
+
|
|
72
|
+
const field = filter.source
|
|
73
|
+
const value = getNestedValue(item, field)
|
|
74
|
+
const target = filter.destination
|
|
75
|
+
const operand = filter.operand || '='
|
|
76
|
+
|
|
77
|
+
if (Array.isArray(target)) {
|
|
78
|
+
if (operand === '!=') {
|
|
79
|
+
return !target.includes(value)
|
|
80
|
+
}
|
|
81
|
+
return target.includes(value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return compareValues(value, target, operand)
|
|
85
|
+
})
|
|
64
86
|
})
|
|
65
|
-
}
|
|
87
|
+
} else {
|
|
88
|
+
filteredData = filteredData.filter((item) => {
|
|
89
|
+
return Object.entries(parsedFilters).every(([key, value]) => {
|
|
90
|
+
const itemValue = getNestedValue(item, key)
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
return value.includes(itemValue)
|
|
93
|
+
}
|
|
94
|
+
return compareValues(itemValue, value, '=')
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
}
|
|
66
98
|
}
|
|
67
99
|
|
|
68
|
-
if (
|
|
100
|
+
if (sorts) {
|
|
101
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
102
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
103
|
+
filteredData.sort((a, b) => {
|
|
104
|
+
for (const sort of parsedSorts) {
|
|
105
|
+
if (!sort.field) continue
|
|
106
|
+
const aVal = getNestedValue(a, sort.field)
|
|
107
|
+
const bVal = getNestedValue(b, sort.field)
|
|
108
|
+
const sortOrderValue = sort.order?.toLowerCase() === 'desc' ? -1 : 1
|
|
109
|
+
|
|
110
|
+
let comparison = 0
|
|
111
|
+
if (aVal === null || aVal === undefined) {
|
|
112
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
113
|
+
} else if (bVal === null || bVal === undefined) {
|
|
114
|
+
comparison = 1
|
|
115
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
116
|
+
comparison = aVal - bVal
|
|
117
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
118
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
119
|
+
} else {
|
|
120
|
+
const aStr = String(aVal)
|
|
121
|
+
const bStr = String(bVal)
|
|
122
|
+
if (aStr < bStr) comparison = -1
|
|
123
|
+
else if (aStr > bStr) comparison = 1
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (comparison !== 0) return comparison * sortOrderValue
|
|
127
|
+
}
|
|
128
|
+
return 0
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
} else if (sortBy) {
|
|
69
132
|
filteredData.sort((a, b) => {
|
|
70
|
-
const aVal = a
|
|
71
|
-
const bVal = b
|
|
133
|
+
const aVal = getNestedValue(a, sortBy)
|
|
134
|
+
const bVal = getNestedValue(b, sortBy)
|
|
72
135
|
const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
136
|
+
|
|
137
|
+
let comparison = 0
|
|
138
|
+
if (aVal === null || aVal === undefined) {
|
|
139
|
+
comparison = bVal === null || bVal === undefined ? 0 : -1
|
|
140
|
+
} else if (bVal === null || bVal === undefined) {
|
|
141
|
+
comparison = 1
|
|
142
|
+
} else if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
143
|
+
comparison = aVal - bVal
|
|
144
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
145
|
+
comparison = aVal.getTime() - bVal.getTime()
|
|
146
|
+
} else {
|
|
147
|
+
const aStr = String(aVal)
|
|
148
|
+
const bStr = String(bVal)
|
|
149
|
+
if (aStr < bStr) comparison = -1
|
|
150
|
+
else if (aStr > bStr) comparison = 1
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return comparison * sortOrderValue
|
|
76
154
|
})
|
|
77
155
|
}
|
|
78
156
|
|