@teleporthq/teleport-plugin-next-data-source 0.43.63 → 0.43.64
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__/filter-groups.test.ts +174 -0
- 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 +3 -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 +1 -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 +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- 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 +1 -1
- 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 +1 -1
- 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/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +1 -1
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +1 -1
- package/dist/cjs/fetchers/teleport.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/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +32 -0
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +116 -18
- package/dist/cjs/utils.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 +4 -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 +2 -2
- 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 +2 -2
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +2 -2
- 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 +2 -2
- 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 +2 -2
- 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/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +2 -2
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +2 -2
- package/dist/esm/fetchers/teleport.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/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +32 -0
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +112 -17
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/fetchers/airtable.ts +35 -36
- package/src/fetchers/clickhouse.ts +26 -36
- package/src/fetchers/csv-file.ts +13 -21
- package/src/fetchers/firestore.ts +41 -35
- package/src/fetchers/google-sheets.ts +15 -23
- package/src/fetchers/javascript.ts +11 -21
- package/src/fetchers/mariadb.ts +33 -44
- package/src/fetchers/mongodb.ts +66 -81
- package/src/fetchers/mysql.ts +33 -45
- package/src/fetchers/postgresql.ts +52 -62
- package/src/fetchers/redis.ts +6 -5
- package/src/fetchers/redshift.ts +29 -41
- package/src/fetchers/rest-api.ts +11 -21
- package/src/fetchers/supabase.ts +104 -41
- package/src/fetchers/teleport.ts +53 -64
- package/src/fetchers/turso.ts +27 -39
- package/src/utils.ts +188 -31
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
5
|
generateSearchEscapeHelpersCode,
|
|
6
|
+
generateFilterTreeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
import {
|
|
8
9
|
generateSortFallbackFieldHelper,
|
|
@@ -62,78 +63,67 @@ const getClient = () => {
|
|
|
62
63
|
|
|
63
64
|
${generateSafeJSONParseCode()}
|
|
64
65
|
|
|
66
|
+
${generateFilterTreeHelpersCode()}
|
|
67
|
+
|
|
65
68
|
${generateSearchEscapeHelpersCode()}
|
|
66
69
|
|
|
67
|
-
//
|
|
70
|
+
// Builds one SQL clause for the whole filter tree, so an OR group nested in
|
|
71
|
+
// the root AND keeps its meaning instead of being flattened into ANDs.
|
|
68
72
|
const processFilters = (filters, conditions, queryParams, paramIndex) => {
|
|
69
73
|
if (!filters) return paramIndex
|
|
70
74
|
|
|
71
|
-
const
|
|
75
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
76
|
+
if (!filterTree) return paramIndex
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (operand === 'array_overlap') {
|
|
84
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
85
|
-
queryParams.push(value.map((entry) => String(entry)))
|
|
86
|
-
paramIndex++
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
90
|
-
queryParams.push(...value)
|
|
91
|
-
if (operand === '!=') {
|
|
92
|
-
conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
|
|
93
|
-
} else {
|
|
94
|
-
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
if (operand === 'array_overlap') {
|
|
98
|
-
if (value === '' || value === null || value === undefined) return
|
|
99
|
-
// A single comma-joined string (the multi-select Category Filter's
|
|
100
|
-
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
101
|
-
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
102
|
-
if (overlapValues.length === 0) return
|
|
103
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
104
|
-
queryParams.push(overlapValues)
|
|
105
|
-
paramIndex++
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
if (value === null) {
|
|
109
|
-
if (operand === '=') {
|
|
110
|
-
conditions.push(\`\${field} IS NULL\`)
|
|
111
|
-
} else if (operand === '!=') {
|
|
112
|
-
conditions.push(\`\${field} IS NOT NULL\`)
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
116
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
117
|
-
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
118
|
-
queryParams.push(value)
|
|
119
|
-
paramIndex++
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
} else {
|
|
124
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
125
|
-
if (Array.isArray(value)) {
|
|
126
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
127
|
-
queryParams.push(...value)
|
|
128
|
-
conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
|
|
129
|
-
} else {
|
|
130
|
-
conditions.push(\`\${key} = $\${paramIndex}\`)
|
|
131
|
-
queryParams.push(value)
|
|
78
|
+
const buildCondition = (condition) => {
|
|
79
|
+
const field = condition.source
|
|
80
|
+
const value = condition.destination
|
|
81
|
+
const operand = condition.operand
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
if (value.length === 0) return null
|
|
85
|
+
if (operand === 'array_overlap') {
|
|
86
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
87
|
+
queryParams.push(value.map((entry) => String(entry)))
|
|
132
88
|
paramIndex++
|
|
89
|
+
return clause
|
|
133
90
|
}
|
|
134
|
-
|
|
91
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
92
|
+
queryParams.push(...value)
|
|
93
|
+
return operand === '!='
|
|
94
|
+
? \`\${field} NOT IN (\${placeholders.join(', ')})\`
|
|
95
|
+
: \`\${field} IN (\${placeholders.join(', ')})\`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (operand === 'array_overlap') {
|
|
99
|
+
if (value === '' || value === null || value === undefined) return null
|
|
100
|
+
// A single comma-joined string (the multi-select Category Filter's
|
|
101
|
+
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
102
|
+
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
103
|
+
if (overlapValues.length === 0) return null
|
|
104
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
105
|
+
queryParams.push(overlapValues)
|
|
106
|
+
paramIndex++
|
|
107
|
+
return clause
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (value === null) {
|
|
111
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
112
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
117
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
118
|
+
const clause = \`\${field} \${sqlOperator} $\${paramIndex}\`
|
|
119
|
+
queryParams.push(value)
|
|
120
|
+
paramIndex++
|
|
121
|
+
return clause
|
|
135
122
|
}
|
|
136
123
|
|
|
124
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
125
|
+
if (clause) conditions.push(clause)
|
|
126
|
+
|
|
137
127
|
return paramIndex
|
|
138
128
|
}
|
|
139
129
|
|
package/src/fetchers/redis.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
export const validateRedisConfig = (
|
|
@@ -69,6 +70,8 @@ export const generateRedisFetcher = (config: Record<string, unknown>): string =>
|
|
|
69
70
|
|
|
70
71
|
${generateSafeJSONParseCode()}
|
|
71
72
|
|
|
73
|
+
${generateFilterTreeHelpersCode()}
|
|
74
|
+
|
|
72
75
|
${generateDateFormatterCode()}
|
|
73
76
|
|
|
74
77
|
export default async function handler(req, res) {
|
|
@@ -88,14 +91,12 @@ export default async function handler(req, res) {
|
|
|
88
91
|
|
|
89
92
|
// Extract pattern from filters if available (new format)
|
|
90
93
|
if (filters) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
const patternFilter =
|
|
94
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
95
|
+
if (filterTree) {
|
|
96
|
+
const patternFilter = flattenFilterConditions(filterTree).find((f) => f.source === 'pattern')
|
|
94
97
|
if (patternFilter) {
|
|
95
98
|
pattern = patternFilter.destination || pattern
|
|
96
99
|
}
|
|
97
|
-
} else {
|
|
98
|
-
pattern = parsedFilters.pattern || pattern
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
package/src/fetchers/redshift.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
generateSearchEscapeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
|
|
@@ -56,6 +57,8 @@ const getClient = () => {
|
|
|
56
57
|
|
|
57
58
|
${generateSafeJSONParseCode()}
|
|
58
59
|
|
|
60
|
+
${generateFilterTreeHelpersCode()}
|
|
61
|
+
|
|
59
62
|
${generateSearchEscapeHelpersCode()}
|
|
60
63
|
|
|
61
64
|
${generateDateFormatterCode()}
|
|
@@ -119,54 +122,39 @@ export default async function handler(req, res) {
|
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
if (filters) {
|
|
122
|
-
const
|
|
125
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
123
126
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const value = filter.destination
|
|
130
|
-
const operand = filter.operand || '='
|
|
127
|
+
if (filterTree) {
|
|
128
|
+
const buildCondition = (condition) => {
|
|
129
|
+
const field = sanitizeIdentifier(condition.source)
|
|
130
|
+
const value = condition.destination
|
|
131
|
+
const operand = condition.operand
|
|
131
132
|
|
|
132
133
|
if (Array.isArray(value)) {
|
|
133
|
-
if (value.length === 0) return
|
|
134
|
+
if (value.length === 0) return null
|
|
134
135
|
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
135
136
|
queryParams.push(...value)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
if (value === null) {
|
|
143
|
-
if (operand === '=') {
|
|
144
|
-
conditions.push(\`\${field} IS NULL\`)
|
|
145
|
-
} else if (operand === '!=') {
|
|
146
|
-
conditions.push(\`\${field} IS NOT NULL\`)
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
150
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
151
|
-
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
152
|
-
queryParams.push(value)
|
|
153
|
-
paramIndex++
|
|
154
|
-
}
|
|
137
|
+
return operand === '!='
|
|
138
|
+
? \`\${field} NOT IN (\${placeholders.join(', ')})\`
|
|
139
|
+
: \`\${field} IN (\${placeholders.join(', ')})\`
|
|
155
140
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
162
|
-
queryParams.push(...value)
|
|
163
|
-
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
164
|
-
} else {
|
|
165
|
-
conditions.push(\`\${field} = $\${paramIndex}\`)
|
|
166
|
-
queryParams.push(value)
|
|
167
|
-
paramIndex++
|
|
141
|
+
|
|
142
|
+
if (value === null) {
|
|
143
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
144
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
145
|
+
return null
|
|
168
146
|
}
|
|
169
|
-
|
|
147
|
+
|
|
148
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
149
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
150
|
+
const clause = \`\${field} \${sqlOperator} $\${paramIndex}\`
|
|
151
|
+
queryParams.push(value)
|
|
152
|
+
paramIndex++
|
|
153
|
+
return clause
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
157
|
+
if (clause) conditions.push(clause)
|
|
170
158
|
}
|
|
171
159
|
}
|
|
172
160
|
|
package/src/fetchers/rest-api.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
generateDateFormatterCode,
|
|
3
3
|
generateSortFilterHelperCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
export const validateRESTAPIConfig = (
|
|
@@ -87,6 +88,8 @@ ${generateDateFormatterCode()}
|
|
|
87
88
|
|
|
88
89
|
${generateSortFilterHelperCode()}
|
|
89
90
|
|
|
91
|
+
${generateFilterTreeHelpersCode()}
|
|
92
|
+
|
|
90
93
|
export default async function handler(req, res) {
|
|
91
94
|
try {
|
|
92
95
|
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
@@ -160,17 +163,14 @@ export default async function handler(req, res) {
|
|
|
160
163
|
|
|
161
164
|
if (filters) {
|
|
162
165
|
try {
|
|
163
|
-
const
|
|
166
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
164
167
|
|
|
165
|
-
if (
|
|
166
|
-
data = data.filter((item) =>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const value = getNestedValue(item, field)
|
|
172
|
-
const target = filter.destination
|
|
173
|
-
const operand = filter.operand || '='
|
|
168
|
+
if (filterTree) {
|
|
169
|
+
data = data.filter((item) =>
|
|
170
|
+
evaluateFilterTree(filterTree, (condition) => {
|
|
171
|
+
const value = getNestedValue(item, condition.source)
|
|
172
|
+
const target = condition.destination
|
|
173
|
+
const operand = condition.operand
|
|
174
174
|
|
|
175
175
|
if (Array.isArray(target)) {
|
|
176
176
|
if (operand === '!=') {
|
|
@@ -181,17 +181,7 @@ export default async function handler(req, res) {
|
|
|
181
181
|
|
|
182
182
|
return compareValues(value, target, operand)
|
|
183
183
|
})
|
|
184
|
-
|
|
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
|
-
})
|
|
184
|
+
)
|
|
195
185
|
}
|
|
196
186
|
} catch (err) {
|
|
197
187
|
console.error('Error parsing filters:', err)
|
package/src/fetchers/supabase.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
5
|
generateSearchEscapeHelpersCode,
|
|
6
|
+
generateFilterTreeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
|
|
8
9
|
export const validateSupabaseConfig = (
|
|
@@ -127,6 +128,8 @@ const getClient = () => {
|
|
|
127
128
|
|
|
128
129
|
${generateSafeJSONParseCode()}
|
|
129
130
|
|
|
131
|
+
${generateFilterTreeHelpersCode()}
|
|
132
|
+
|
|
130
133
|
${generateSearchEscapeHelpersCode()}
|
|
131
134
|
|
|
132
135
|
// Helper function to process filter values
|
|
@@ -144,12 +147,12 @@ const applyFilters = (queryRef, filters) => {
|
|
|
144
147
|
const parsedFilters = safeJSONParse(filters)
|
|
145
148
|
|
|
146
149
|
if (Array.isArray(parsedFilters)) {
|
|
147
|
-
parsedFilters
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const field =
|
|
151
|
-
const value =
|
|
152
|
-
const operand =
|
|
150
|
+
const filterTree = normalizeFilterTree(parsedFilters)
|
|
151
|
+
|
|
152
|
+
const applyCondition = (condition) => {
|
|
153
|
+
const field = condition.source
|
|
154
|
+
const value = condition.destination
|
|
155
|
+
const operand = condition.operand
|
|
153
156
|
|
|
154
157
|
if (Array.isArray(value)) {
|
|
155
158
|
const processedValues = value.map(processFilterValue)
|
|
@@ -158,43 +161,103 @@ const applyFilters = (queryRef, filters) => {
|
|
|
158
161
|
} else {
|
|
159
162
|
queryRef = queryRef.in(field, processedValues)
|
|
160
163
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Map operand to Supabase methods
|
|
173
|
-
switch (operand) {
|
|
174
|
-
case '=':
|
|
175
|
-
queryRef = queryRef.eq(field, processedValue)
|
|
176
|
-
break
|
|
177
|
-
case '!=':
|
|
178
|
-
queryRef = queryRef.neq(field, processedValue)
|
|
179
|
-
break
|
|
180
|
-
case '>':
|
|
181
|
-
queryRef = queryRef.gt(field, processedValue)
|
|
182
|
-
break
|
|
183
|
-
case '>=':
|
|
184
|
-
queryRef = queryRef.gte(field, processedValue)
|
|
185
|
-
break
|
|
186
|
-
case '<':
|
|
187
|
-
queryRef = queryRef.lt(field, processedValue)
|
|
188
|
-
break
|
|
189
|
-
case '<=':
|
|
190
|
-
queryRef = queryRef.lte(field, processedValue)
|
|
191
|
-
break
|
|
192
|
-
default:
|
|
193
|
-
queryRef = queryRef.eq(field, processedValue)
|
|
194
|
-
}
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const processedValue = processFilterValue(value)
|
|
168
|
+
|
|
169
|
+
// Handle null values
|
|
170
|
+
if (processedValue === null) {
|
|
171
|
+
if (operand === '=') {
|
|
172
|
+
queryRef = queryRef.is(field, null)
|
|
173
|
+
} else if (operand === '!=') {
|
|
174
|
+
queryRef = queryRef.not(field, 'is', null)
|
|
195
175
|
}
|
|
176
|
+
return
|
|
196
177
|
}
|
|
197
|
-
|
|
178
|
+
|
|
179
|
+
// Map operand to Supabase methods
|
|
180
|
+
switch (operand) {
|
|
181
|
+
case '!=':
|
|
182
|
+
queryRef = queryRef.neq(field, processedValue)
|
|
183
|
+
break
|
|
184
|
+
case '>':
|
|
185
|
+
queryRef = queryRef.gt(field, processedValue)
|
|
186
|
+
break
|
|
187
|
+
case '>=':
|
|
188
|
+
queryRef = queryRef.gte(field, processedValue)
|
|
189
|
+
break
|
|
190
|
+
case '<':
|
|
191
|
+
queryRef = queryRef.lt(field, processedValue)
|
|
192
|
+
break
|
|
193
|
+
case '<=':
|
|
194
|
+
queryRef = queryRef.lte(field, processedValue)
|
|
195
|
+
break
|
|
196
|
+
default:
|
|
197
|
+
queryRef = queryRef.eq(field, processedValue)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// An OR group cannot be expressed by chaining (chained filters are ANDed),
|
|
202
|
+
// so it is emitted as one PostgREST or=(...) expression instead.
|
|
203
|
+
const POSTGREST_OPERANDS = { '=': 'eq', '!=': 'neq', '>': 'gt', '>=': 'gte', '<': 'lt', '<=': 'lte' }
|
|
204
|
+
|
|
205
|
+
const toExprValue = (value) => {
|
|
206
|
+
if (value === null || value === undefined) return 'null'
|
|
207
|
+
const asString = String(value)
|
|
208
|
+
return /[,.:()"\\s]/.test(asString) ? '"' + asString.replace(/"/g, '\\\\"') + '"' : asString
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const conditionToExpr = (condition) => {
|
|
212
|
+
const field = condition.source
|
|
213
|
+
const value = condition.destination
|
|
214
|
+
const operand = condition.operand
|
|
215
|
+
|
|
216
|
+
if (Array.isArray(value)) {
|
|
217
|
+
const list = '(' + value.map((entry) => toExprValue(processFilterValue(entry))).join(',') + ')'
|
|
218
|
+
return operand === '!=' ? field + '.not.in.' + list : field + '.in.' + list
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const processedValue = processFilterValue(value)
|
|
222
|
+
if (processedValue === null) {
|
|
223
|
+
return operand === '!=' ? field + '.not.is.null' : field + '.is.null'
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return field + '.' + (POSTGREST_OPERANDS[operand] || 'eq') + '.' + toExprValue(processedValue)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const nodeToExpr = (node) => {
|
|
230
|
+
if (node.type === 'group') {
|
|
231
|
+
const parts = node.children.map(nodeToExpr).filter(Boolean)
|
|
232
|
+
if (parts.length === 0) return null
|
|
233
|
+
if (parts.length === 1) return parts[0]
|
|
234
|
+
return (node.operator === 'or' ? 'or' : 'and') + '(' + parts.join(',') + ')'
|
|
235
|
+
}
|
|
236
|
+
return conditionToExpr(node)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const applyNode = (node) => {
|
|
240
|
+
if (node.type !== 'group') {
|
|
241
|
+
applyCondition(node)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
if (node.operator === 'and') {
|
|
245
|
+
node.children.forEach(applyNode)
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
if (node.children.length === 1) {
|
|
249
|
+
applyNode(node.children[0])
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
const parts = node.children.map(nodeToExpr).filter(Boolean)
|
|
253
|
+
if (parts.length > 0) {
|
|
254
|
+
queryRef = queryRef.or(parts.join(','))
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (filterTree) {
|
|
259
|
+
applyNode(filterTree)
|
|
260
|
+
}
|
|
198
261
|
} else {
|
|
199
262
|
// Old format: object with key-value pairs (backward compatibility)
|
|
200
263
|
Object.entries(parsedFilters).forEach(([key, value]) => {
|
package/src/fetchers/teleport.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateFilterTreeHelpersCode,
|
|
5
6
|
generateSearchEscapeHelpersCode,
|
|
6
7
|
} from '../utils'
|
|
7
8
|
import {
|
|
@@ -146,81 +147,69 @@ const getClient = () => {
|
|
|
146
147
|
|
|
147
148
|
${generateSafeJSONParseCode()}
|
|
148
149
|
|
|
150
|
+
${generateFilterTreeHelpersCode()}
|
|
151
|
+
|
|
149
152
|
${generateSearchEscapeHelpersCode()}
|
|
150
153
|
${getTransformationCode(tableName, transformOptions)}
|
|
151
154
|
${getTransformWrapperCode(tableName)}
|
|
152
155
|
const processFilters = (filters, conditions, queryParams, paramIndex) => {
|
|
153
156
|
if (!filters) return paramIndex
|
|
154
157
|
|
|
155
|
-
const
|
|
158
|
+
const filterTree = normalizeFilterTree(safeJSONParse(filters))
|
|
159
|
+
if (!filterTree) return paramIndex
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// form) avoids the '?' placeholder-token ambiguity some drivers hit.
|
|
171
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
172
|
-
queryParams.push(value.map((entry) => String(entry)))
|
|
173
|
-
paramIndex++
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
177
|
-
queryParams.push(...value)
|
|
178
|
-
if (operand === '!=') {
|
|
179
|
-
conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
|
|
180
|
-
} else {
|
|
181
|
-
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
if (operand === 'array_overlap') {
|
|
185
|
-
if (value === '' || value === null || value === undefined) return
|
|
186
|
-
// A single comma-joined string (the multi-select Category Filter's
|
|
187
|
-
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
188
|
-
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
189
|
-
if (overlapValues.length === 0) return
|
|
190
|
-
conditions.push(\`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`)
|
|
191
|
-
queryParams.push(overlapValues)
|
|
192
|
-
paramIndex++
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
if (value === null) {
|
|
196
|
-
if (operand === '=') {
|
|
197
|
-
conditions.push(\`\${field} IS NULL\`)
|
|
198
|
-
} else if (operand === '!=') {
|
|
199
|
-
conditions.push(\`\${field} IS NOT NULL\`)
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
203
|
-
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
204
|
-
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
205
|
-
queryParams.push(value)
|
|
206
|
-
paramIndex++
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
} else {
|
|
211
|
-
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
212
|
-
if (Array.isArray(value)) {
|
|
213
|
-
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
214
|
-
queryParams.push(...value)
|
|
215
|
-
conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
|
|
216
|
-
} else {
|
|
217
|
-
conditions.push(\`\${key} = $\${paramIndex}\`)
|
|
218
|
-
queryParams.push(value)
|
|
161
|
+
const buildCondition = (condition) => {
|
|
162
|
+
const field = condition.source
|
|
163
|
+
const value = condition.destination
|
|
164
|
+
const operand = condition.operand
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
if (value.length === 0) return null
|
|
168
|
+
if (operand === 'array_overlap') {
|
|
169
|
+
// Row column is a JSON array (e.g. category_ids). Match when it
|
|
170
|
+
// shares any element with the destination set. jsonb ?| (function
|
|
171
|
+
// form) avoids the '?' placeholder-token ambiguity some drivers hit.
|
|
172
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
173
|
+
queryParams.push(value.map((entry) => String(entry)))
|
|
219
174
|
paramIndex++
|
|
175
|
+
return clause
|
|
220
176
|
}
|
|
221
|
-
|
|
177
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
178
|
+
queryParams.push(...value)
|
|
179
|
+
return operand === '!='
|
|
180
|
+
? \`\${field} NOT IN (\${placeholders.join(', ')})\`
|
|
181
|
+
: \`\${field} IN (\${placeholders.join(', ')})\`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (operand === 'array_overlap') {
|
|
185
|
+
if (value === '' || value === null || value === undefined) return null
|
|
186
|
+
// A single comma-joined string (the multi-select Category Filter's
|
|
187
|
+
// ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.
|
|
188
|
+
const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)
|
|
189
|
+
if (overlapValues.length === 0) return null
|
|
190
|
+
const clause = \`jsonb_exists_any(NULLIF(\${field}, '')::jsonb, $\${paramIndex}::text[])\`
|
|
191
|
+
queryParams.push(overlapValues)
|
|
192
|
+
paramIndex++
|
|
193
|
+
return clause
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (value === null) {
|
|
197
|
+
if (operand === '=') return \`\${field} IS NULL\`
|
|
198
|
+
if (operand === '!=') return \`\${field} IS NOT NULL\`
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
203
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
204
|
+
const clause = \`\${field} \${sqlOperator} $\${paramIndex}\`
|
|
205
|
+
queryParams.push(value)
|
|
206
|
+
paramIndex++
|
|
207
|
+
return clause
|
|
222
208
|
}
|
|
223
209
|
|
|
210
|
+
const clause = buildFilterTreeClause(filterTree, buildCondition)
|
|
211
|
+
if (clause) conditions.push(clause)
|
|
212
|
+
|
|
224
213
|
return paramIndex
|
|
225
214
|
}
|
|
226
215
|
|