@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.
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 +3 -3
  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
  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
- columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
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 = JSON.parse(filters)
103
- Object.entries(parsedFilters).forEach(([key, value]) => {
104
- if (Array.isArray(value)) {
105
- const placeholders = value.map(() => \`$\${paramIndex++}\`)
106
- queryParams.push(...value)
107
- conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
108
- } else {
109
- conditions.push(\`\${key} = $\${paramIndex}\`)
110
- queryParams.push(value)
111
- paramIndex++
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
- if (sortBy) {
123
- sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
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
@@ -1,4 +1,8 @@
1
- import { generateDateFormatterCode } from '../utils'
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 columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
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[col]
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 = typeof filters === 'string' ? JSON.parse(filters) : filters
159
- data = data.filter((item) => {
160
- return Object.entries(parsedFilters).every(([key, value]) => {
161
- if (Array.isArray(value)) {
162
- return value.includes(item[key])
163
- }
164
- return item[key] === value
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
- // 3. Apply sorting
173
- if (sortBy && sortBy.trim()) {
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[sortBy]
176
- const bVal = b[sortBy]
238
+ const aVal = getNestedValue(a, sortBy)
239
+ const bVal = getNestedValue(b, sortBy)
177
240
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
178
- if (aVal < bVal) return -sortOrderValue
179
- if (aVal > bVal) return sortOrderValue
180
- return 0
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 { generateDateFormatterCode } from '../utils'
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 = JSON.parse(queryColumns)
45
+ const columns = safeJSONParse(queryColumns)
38
46
  filteredData = filteredData.filter((item) => {
39
47
  return columns.some((col) => {
40
- const value = item[col]
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 = JSON.parse(filters)
58
- filteredData = filteredData.filter((item) => {
59
- return Object.entries(parsedFilters).every(([key, value]) => {
60
- if (Array.isArray(value)) {
61
- return value.includes(item[key])
62
- }
63
- return item[key] === value
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 (sortBy) {
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[sortBy]
71
- const bVal = b[sortBy]
133
+ const aVal = getNestedValue(a, sortBy)
134
+ const bVal = getNestedValue(b, sortBy)
72
135
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
73
- if (aVal < bVal) return -sortOrderValue
74
- if (aVal > bVal) return sortOrderValue
75
- return 0
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