@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.
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 +2 -2
  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 +23 -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 +2 -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 +3 -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 +2 -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 +3 -0
  68. package/dist/cjs/utils.d.ts.map +1 -1
  69. package/dist/cjs/utils.js +218 -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 +3 -3
  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 -3
  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 -1
  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 -2
  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 -1
  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 +3 -0
  136. package/dist/esm/utils.d.ts.map +1 -1
  137. package/dist/esm/utils.js +214 -45
  138. package/dist/esm/utils.js.map +1 -1
  139. package/package.json +2 -2
  140. package/src/data-source-fetchers.ts +29 -13
  141. package/src/fetchers/airtable.ts +81 -31
  142. package/src/fetchers/clickhouse.ts +88 -19
  143. package/src/fetchers/csv-file.ts +256 -27
  144. package/src/fetchers/firestore.ts +65 -13
  145. package/src/fetchers/google-sheets.ts +243 -26
  146. package/src/fetchers/javascript.ts +106 -23
  147. package/src/fetchers/mariadb.ts +85 -26
  148. package/src/fetchers/mongodb.ts +156 -37
  149. package/src/fetchers/mysql.ts +86 -26
  150. package/src/fetchers/postgresql.ts +89 -27
  151. package/src/fetchers/redis.ts +43 -5
  152. package/src/fetchers/redshift.ts +87 -18
  153. package/src/fetchers/rest-api.ts +105 -24
  154. package/src/fetchers/static-collection.ts +100 -18
  155. package/src/fetchers/supabase.ts +178 -54
  156. package/src/fetchers/turso.ts +87 -18
  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 +374 -38
@@ -1,4 +1,8 @@
1
- import { replaceSecretReference } from '../utils'
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,29 +71,67 @@ const getFirestore = () => {
67
71
  return firestore
68
72
  }
69
73
 
74
+ ${generateSafeJSONParseCode()}
75
+
76
+ ${generateDateFormatterCode()}
77
+
70
78
  export default async function handler(req, res) {
71
79
  try {
72
80
  const firestore = getFirestore()
73
- 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
74
82
 
75
83
  let queryRef = firestore.collection('${tableName}')
76
84
 
77
85
  if (filters) {
78
- const parsedFilters = JSON.parse(filters)
79
- Object.entries(parsedFilters).forEach(([key, value]) => {
80
- if (Array.isArray(value)) {
81
- queryRef = queryRef.where(key, 'in', value)
82
- } else {
83
- queryRef = queryRef.where(key, '==', value)
84
- }
85
- })
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
+ }
86
127
  }
87
128
 
88
129
  let usePostFiltering = false
89
130
 
90
131
  if (query) {
91
132
  if (queryColumns) {
92
- const columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
133
+ const parsed = safeJSONParse(queryColumns)
134
+ const columns = Array.isArray(parsed) ? parsed : [parsed]
93
135
  for (const column of columns) {
94
136
  queryRef = queryRef
95
137
  .where(column, '>=', query)
@@ -102,7 +144,17 @@ export default async function handler(req, res) {
102
144
  }
103
145
  }
104
146
 
105
- if (sortBy) {
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) {
106
158
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? 'desc' : 'asc'
107
159
  queryRef = queryRef.orderBy(sortBy, sortOrderValue)
108
160
  }
@@ -150,7 +202,7 @@ export default async function handler(req, res) {
150
202
  }
151
203
  }
152
204
 
153
- const safeData = JSON.parse(JSON.stringify(documents))
205
+ const safeData = JSON.parse(JSON.stringify(documents, dateReplacer))
154
206
 
155
207
  return res.status(200).json({
156
208
  success: true,
@@ -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-_]+)/)
@@ -59,8 +77,18 @@ export default async function handler(req, res) {
59
77
  })
60
78
  }
61
79
 
80
+ let gid = undefined
81
+ if (sheetUrl) {
82
+ const gidMatch = sheetUrl.match(/[#&]gid=([0-9]+)/)
83
+ gid = gidMatch ? gidMatch[1] : undefined
84
+ }
85
+
62
86
  let url = \`https://docs.google.com/spreadsheets/d/\${sheetId}/gviz/tq?tqx=out:json&range=\${range}\`
63
87
 
88
+ if (gid) {
89
+ url += \`&gid=\${gid}\`
90
+ }
91
+
64
92
  if (maxRows && maxRows > 0) {
65
93
  url += \`&tq=limit \${maxRows}\`
66
94
  }
@@ -96,34 +124,146 @@ export default async function handler(req, res) {
96
124
  })
97
125
  }
98
126
 
127
+ const formatDateValue = (date) => {
128
+ const options = {
129
+ year: 'numeric',
130
+ month: 'short',
131
+ day: 'numeric',
132
+ }
133
+
134
+ const timeOptions = {
135
+ hour: '2-digit',
136
+ minute: '2-digit',
137
+ }
138
+
139
+ const hasTime = date.getHours() !== 0 || date.getMinutes() !== 0 || date.getSeconds() !== 0
140
+
141
+ if (hasTime) {
142
+ return date.toLocaleString('en-US', { ...options, ...timeOptions })
143
+ }
144
+
145
+ return date.toLocaleDateString('en-US', options)
146
+ }
147
+
148
+ const parseGoogleSheetsValue = (value) => {
149
+ if (typeof value === 'string') {
150
+ const dateMatch = value.match(/^Date\\((\\d+),(\\d+),(\\d+)(?:,(\\d+),(\\d+),(\\d+))?\\)$/)
151
+ if (dateMatch) {
152
+ const year = parseInt(dateMatch[1], 10)
153
+ const month = parseInt(dateMatch[2], 10)
154
+ const day = parseInt(dateMatch[3], 10)
155
+ const hour = dateMatch[4] ? parseInt(dateMatch[4], 10) : 0
156
+ const minute = dateMatch[5] ? parseInt(dateMatch[5], 10) : 0
157
+ const second = dateMatch[6] ? parseInt(dateMatch[6], 10) : 0
158
+ const date = new Date(year, month, day, hour, minute, second)
159
+ return formatDateValue(date)
160
+ }
161
+ }
162
+ return value
163
+ }
164
+
99
165
  const table = data.table
100
- const columns = table.cols.map((col, index) => ({
101
- id: col.id || \`col_\${index}\`,
102
- label: col.label || \`Column \${index + 1}\`,
103
- type: col.type || 'string'
104
- }))
166
+ const rawRows = table.rows || []
167
+
168
+ if (rawRows.length === 0) {
169
+ return res.status(200).json({
170
+ success: true,
171
+ data: [],
172
+ timestamp: Date.now()
173
+ })
174
+ }
175
+
176
+ const firstRow = rawRows[0]
177
+ const firstRowValues = firstRow.c.map((cell) => cell?.v ?? cell?.f ?? null)
178
+
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
+ }
105
187
 
106
- const rows = table.rows.map((row) => {
188
+ const configColumns = ${JSON.stringify(configColumns)}
189
+ const useConfigColumns = configColumns && configColumns.length > 0
190
+
191
+ let columns
192
+ let dataRows
193
+
194
+ if (hasHeaderRow && rawRows.length > 1) {
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
+ })
203
+ dataRows = rawRows.slice(1)
204
+ } else {
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
+ })
213
+ dataRows = rawRows
214
+ }
215
+
216
+ if (maxRows && dataRows.length > maxRows) {
217
+ dataRows = dataRows.slice(0, maxRows)
218
+ }
219
+
220
+ const rows = dataRows.map((row) => {
107
221
  const rowData = {}
108
222
  row.c.forEach((cell, index) => {
109
- const columnId = columns[index].id
110
- rowData[columnId] = cell?.v ?? null
223
+ const columnId = columns[index]?.id || \`col_\${index}\`
224
+ const rawValue = cell?.v ?? cell?.f ?? null
225
+ rowData[columnId] = parseGoogleSheetsValue(rawValue)
111
226
  })
112
227
  return rowData
113
228
  })
114
229
 
115
- const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
230
+ const columnsWithData = columns.filter((col, index) => {
231
+ const hasHeaderData = col.label && col.label !== \`Column \${index + 1}\`
232
+ const hasDataInColumn = rows.some((row) => {
233
+ const value = row[col.id]
234
+ return value !== null && value !== undefined && value !== ''
235
+ })
236
+ return hasHeaderData || hasDataInColumn
237
+ })
116
238
 
117
- let filteredData = [...rows]
239
+ const filteredRows = rows.map((row) => {
240
+ const filteredRow = {}
241
+ columnsWithData.forEach((col) => {
242
+ filteredRow[col.id] = row[col.id]
243
+ })
244
+ return filteredRow
245
+ })
246
+
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
+ })
254
+
255
+ let filteredData = [...filteredRows]
118
256
 
119
257
  if (query) {
120
258
  const searchQuery = query.toLowerCase()
121
259
 
122
260
  if (queryColumns) {
123
- const searchColumns = JSON.parse(queryColumns)
261
+ const searchColumns = safeJSONParse(queryColumns)
124
262
  filteredData = filteredData.filter((item) => {
125
263
  return searchColumns.some((col) => {
126
- const value = item[col]
264
+ // Map label to column ID
265
+ const field = labelToIdMap[col] || col
266
+ const value = getNestedValue(item, field)
127
267
  return value && String(value).toLowerCase().includes(searchQuery)
128
268
  })
129
269
  })
@@ -140,25 +280,102 @@ export default async function handler(req, res) {
140
280
  }
141
281
 
142
282
  if (filters) {
143
- const parsedFilters = JSON.parse(filters)
144
- filteredData = filteredData.filter((item) => {
145
- return Object.entries(parsedFilters).every(([key, value]) => {
146
- if (Array.isArray(value)) {
147
- return value.includes(item[key])
148
- }
149
- return item[key] === value
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
+ })
150
305
  })
151
- })
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
+ }
152
319
  }
153
320
 
154
- if (sortBy) {
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) {
155
355
  filteredData.sort((a, b) => {
156
- const aVal = a[sortBy]
157
- const bVal = b[sortBy]
356
+ // Map label to column ID
357
+ const field = labelToIdMap[sortBy] || sortBy
358
+ const aVal = getNestedValue(a, field)
359
+ const bVal = getNestedValue(b, field)
158
360
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
159
- if (aVal < bVal) return -sortOrderValue
160
- if (aVal > bVal) return sortOrderValue
161
- return 0
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
162
379
  })
163
380
  }
164
381
 
@@ -1,3 +1,9 @@
1
+ import {
2
+ generateDateFormatterCode,
3
+ generateSortFilterHelperCode,
4
+ generateSafeJSONParseCode,
5
+ } from '../utils'
6
+
1
7
  export const validateJavaScriptConfig = (
2
8
  config: Record<string, unknown>
3
9
  ): { isValid: boolean; error?: string } => {
@@ -35,25 +41,31 @@ interface JavaScriptConfig {
35
41
 
36
42
  export const generateJavaScriptFetcher = (config: Record<string, unknown>): string => {
37
43
  const jsConfig = config as JavaScriptConfig
38
- return `export default async function handler(req, res) {
44
+ return `${generateSafeJSONParseCode()}
45
+
46
+ ${generateDateFormatterCode()}
47
+
48
+ ${generateSortFilterHelperCode()}
49
+
50
+ export default async function handler(req, res) {
39
51
  try {
40
- 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
41
53
 
42
54
  const code = ${JSON.stringify(jsConfig.code)}
43
55
  const executeCode = new Function('return ' + code)
44
56
  let data = executeCode()
45
57
 
46
58
  if (Array.isArray(data)) {
47
- // 1. Apply search filter
48
59
  if (query && query.trim()) {
49
60
  const searchQuery = query.toLowerCase()
50
61
 
51
62
  if (queryColumns) {
52
63
  try {
53
- const columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
64
+ const parsed = safeJSONParse(queryColumns)
65
+ const columns = Array.isArray(parsed) ? parsed : [parsed]
54
66
  data = data.filter(item => {
55
67
  return columns.some(col => {
56
- const value = item[col]
68
+ const value = getNestedValue(item, col)
57
69
  if (value === null || value === undefined) return false
58
70
  return String(value).toLowerCase().includes(searchQuery)
59
71
  })
@@ -73,36 +85,107 @@ export const generateJavaScriptFetcher = (config: Record<string, unknown>): stri
73
85
  }
74
86
  }
75
87
 
76
- // 2. Apply custom filters
77
88
  if (filters) {
78
89
  try {
79
- const parsedFilters = typeof filters === 'string' ? JSON.parse(filters) : filters
80
- data = data.filter((item) => {
81
- return Object.entries(parsedFilters).every(([key, value]) => {
82
- if (Array.isArray(value)) {
83
- return value.includes(item[key])
84
- }
85
- return item[key] === value
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
+ })
86
111
  })
87
- })
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
+ }
88
123
  } catch (err) {
89
124
  console.error('Error parsing filters:', err)
90
125
  }
91
126
  }
92
127
 
93
- // 3. Apply sorting
94
- if (sortBy && sortBy.trim()) {
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()) {
95
164
  data.sort((a, b) => {
96
- const aVal = a[sortBy]
97
- const bVal = b[sortBy]
165
+ const aVal = getNestedValue(a, sortBy)
166
+ const bVal = getNestedValue(b, sortBy)
98
167
  const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
99
- if (aVal < bVal) return -sortOrderValue
100
- if (aVal > bVal) return sortOrderValue
101
- return 0
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
102
186
  })
103
187
  }
104
188
 
105
- // 4. Apply pagination
106
189
  const limitValue = limit || perPage
107
190
  const pageValue = page ? Math.max(1, parseInt(page)) : undefined
108
191
  const offsetValue = offset !== undefined ? Math.max(0, parseInt(offset)) : (pageValue && perPage ? (pageValue - 1) * Math.max(1, parseInt(perPage)) : 0)
@@ -115,7 +198,7 @@ export const generateJavaScriptFetcher = (config: Record<string, unknown>): stri
115
198
  }
116
199
  }
117
200
 
118
- const safeData = JSON.parse(JSON.stringify(data))
201
+ const safeData = JSON.parse(JSON.stringify(data, dateReplacer))
119
202
 
120
203
  return res.status(200).json({
121
204
  success: true,