@teleporthq/teleport-plugin-next-data-source 0.42.35 → 0.43.0
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__/ecommerce-product-out-of-stock.test.ts +112 -0
- package/__tests__/fetchers.test.ts +0 -42
- package/__tests__/filter-utils.test.ts +149 -0
- package/__tests__/mocks.ts +0 -12
- package/__tests__/utils.test.ts +0 -2
- package/dist/cjs/array-mapper-registry.d.ts +2 -0
- package/dist/cjs/array-mapper-registry.d.ts.map +1 -1
- package/dist/cjs/array-mapper-registry.js +9 -1
- package/dist/cjs/array-mapper-registry.js.map +1 -1
- package/dist/cjs/count-fetchers.d.ts +2 -2
- package/dist/cjs/count-fetchers.d.ts.map +1 -1
- package/dist/cjs/count-fetchers.js +5 -5
- package/dist/cjs/count-fetchers.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts +2 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +11 -9
- 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.js +1 -1
- package/dist/cjs/fetchers/csv-file.js.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.js +1 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/index.d.ts +2 -1
- package/dist/cjs/fetchers/index.d.ts.map +1 -1
- package/dist/cjs/fetchers/index.js +8 -5
- package/dist/cjs/fetchers/index.js.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.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 +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 +2 -2
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts +18 -0
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -0
- package/dist/cjs/fetchers/raw-query.js +70 -0
- package/dist/cjs/fetchers/raw-query.js.map +1 -0
- 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.js +1 -1
- 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 +62 -2
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts +7 -0
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -0
- package/dist/cjs/fetchers/teleport.js +63 -0
- package/dist/cjs/fetchers/teleport.js.map +1 -0
- 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/filter-utils.d.ts +13 -0
- package/dist/cjs/filter-utils.d.ts.map +1 -0
- package/dist/cjs/filter-utils.js +95 -0
- package/dist/cjs/filter-utils.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +112 -9
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +389 -128
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/sort-utils.d.ts +10 -0
- package/dist/cjs/sort-utils.d.ts.map +1 -0
- package/dist/cjs/sort-utils.js +141 -0
- package/dist/cjs/sort-utils.js.map +1 -0
- package/dist/cjs/transformations/blog-post.d.ts +7 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -0
- package/dist/cjs/transformations/blog-post.js +13 -0
- package/dist/cjs/transformations/blog-post.js.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts +7 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.js +13 -0
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +26 -0
- package/dist/cjs/transformations/index.d.ts.map +1 -0
- package/dist/cjs/transformations/index.js +81 -0
- package/dist/cjs/transformations/index.js.map +1 -0
- package/dist/cjs/transformations/shared-utils.d.ts +7 -0
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -0
- package/dist/cjs/transformations/shared-utils.js +13 -0
- package/dist/cjs/transformations/shared-utils.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +30 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +173 -10
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/array-mapper-registry.d.ts +2 -0
- package/dist/esm/array-mapper-registry.d.ts.map +1 -1
- package/dist/esm/array-mapper-registry.js +9 -1
- package/dist/esm/array-mapper-registry.js.map +1 -1
- package/dist/esm/count-fetchers.d.ts +2 -2
- package/dist/esm/count-fetchers.d.ts.map +1 -1
- package/dist/esm/count-fetchers.js +4 -4
- package/dist/esm/count-fetchers.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +2 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +10 -9
- 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 +1 -1
- 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 +1 -1
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/csv-file.js +1 -1
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/firestore.js +1 -1
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +1 -1
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/index.d.ts +2 -1
- package/dist/esm/fetchers/index.d.ts.map +1 -1
- package/dist/esm/fetchers/index.js +2 -1
- package/dist/esm/fetchers/index.js.map +1 -1
- package/dist/esm/fetchers/javascript.js +1 -1
- 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.js +1 -1
- 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 +3 -3
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts +18 -0
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -0
- package/dist/esm/fetchers/raw-query.js +65 -0
- package/dist/esm/fetchers/raw-query.js.map +1 -0
- package/dist/esm/fetchers/redis.js +1 -1
- 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.js +1 -1
- 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 +63 -3
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts +7 -0
- package/dist/esm/fetchers/teleport.d.ts.map +1 -0
- package/dist/esm/fetchers/teleport.js +57 -0
- package/dist/esm/fetchers/teleport.js.map +1 -0
- 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/filter-utils.d.ts +13 -0
- package/dist/esm/filter-utils.d.ts.map +1 -0
- package/dist/esm/filter-utils.js +66 -0
- package/dist/esm/filter-utils.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +113 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +389 -128
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/sort-utils.d.ts +10 -0
- package/dist/esm/sort-utils.d.ts.map +1 -0
- package/dist/esm/sort-utils.js +113 -0
- package/dist/esm/sort-utils.js.map +1 -0
- package/dist/esm/transformations/blog-post.d.ts +7 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -0
- package/dist/esm/transformations/blog-post.js +9 -0
- package/dist/esm/transformations/blog-post.js.map +1 -0
- package/dist/esm/transformations/ecommerce-product.d.ts +7 -0
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -0
- package/dist/esm/transformations/ecommerce-product.js +9 -0
- package/dist/esm/transformations/ecommerce-product.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +26 -0
- package/dist/esm/transformations/index.d.ts.map +1 -0
- package/dist/esm/transformations/index.js +74 -0
- package/dist/esm/transformations/index.js.map +1 -0
- package/dist/esm/transformations/shared-utils.d.ts +7 -0
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -0
- package/dist/esm/transformations/shared-utils.js +9 -0
- package/dist/esm/transformations/shared-utils.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +30 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +170 -9
- package/dist/esm/utils.js.map +1 -1
- package/package.json +6 -5
- package/src/array-mapper-registry.ts +13 -0
- package/src/count-fetchers.ts +5 -5
- package/src/data-source-fetchers.ts +15 -11
- package/src/fetchers/airtable.ts +54 -8
- package/src/fetchers/clickhouse.ts +25 -19
- package/src/fetchers/csv-file.ts +2 -2
- package/src/fetchers/firestore.ts +2 -2
- package/src/fetchers/google-sheets.ts +2 -2
- package/src/fetchers/index.ts +6 -5
- package/src/fetchers/javascript.ts +2 -2
- package/src/fetchers/mariadb.ts +27 -12
- package/src/fetchers/mongodb.ts +2 -2
- package/src/fetchers/mysql.ts +27 -12
- package/src/fetchers/postgresql.ts +31 -18
- package/src/fetchers/raw-query.ts +178 -0
- package/src/fetchers/redis.ts +2 -2
- package/src/fetchers/redshift.ts +14 -10
- package/src/fetchers/rest-api.ts +2 -2
- package/src/fetchers/supabase.ts +97 -14
- package/src/fetchers/teleport.ts +485 -0
- package/src/fetchers/turso.ts +15 -7
- package/src/filter-utils.ts +111 -0
- package/src/index.ts +146 -6
- package/src/pagination-plugin.ts +547 -308
- package/src/sort-utils.ts +150 -0
- package/src/transformations/blog-post.ts +128 -0
- package/src/transformations/ecommerce-product.ts +173 -0
- package/src/transformations/index.ts +97 -0
- package/src/transformations/shared-utils.ts +271 -0
- package/src/utils.ts +227 -11
- package/dist/cjs/fetchers/static-collection.d.ts +0 -7
- package/dist/cjs/fetchers/static-collection.d.ts.map +0 -1
- package/dist/cjs/fetchers/static-collection.js +0 -25
- package/dist/cjs/fetchers/static-collection.js.map +0 -1
- package/dist/esm/fetchers/static-collection.d.ts +0 -7
- package/dist/esm/fetchers/static-collection.d.ts.map +0 -1
- package/dist/esm/fetchers/static-collection.js +0 -19
- package/dist/esm/fetchers/static-collection.js.map +0 -1
- package/src/fetchers/static-collection.ts +0 -231
package/src/fetchers/airtable.ts
CHANGED
|
@@ -42,12 +42,42 @@ ${generateSafeJSONParseCode()}
|
|
|
42
42
|
|
|
43
43
|
${generateDateFormatterCode()}
|
|
44
44
|
|
|
45
|
+
// Escape a string literal for Airtable formula grammar. Strings use
|
|
46
|
+
// single quotes; embedded single-quote must be backslash-escaped.
|
|
47
|
+
// Backslash itself also escapes.
|
|
48
|
+
const escapeAirtableString = (s) => {
|
|
49
|
+
return "'" + String(s).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, "\\\\'") + "'"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Escape an Airtable field reference name for use inside curly braces.
|
|
53
|
+
// Only close-brace and backslash are special inside the braces.
|
|
54
|
+
const escapeAirtableFieldRef = (name) => {
|
|
55
|
+
return String(name).replace(/\\\\/g, '\\\\\\\\').replace(/}/g, '\\\\}')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const buildAirtableSearchFormula = (rawQuery, rawQueryColumns) => {
|
|
59
|
+
if (typeof rawQuery !== 'string' || rawQuery.trim() === '') return ''
|
|
60
|
+
let cols = []
|
|
61
|
+
if (rawQueryColumns) {
|
|
62
|
+
const parsed = safeJSONParse(rawQueryColumns)
|
|
63
|
+
cols = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
64
|
+
}
|
|
65
|
+
const validCols = cols.filter((c) => typeof c === 'string' && c.length > 0)
|
|
66
|
+
if (validCols.length === 0) return ''
|
|
67
|
+
const literal = escapeAirtableString(rawQuery)
|
|
68
|
+
const parts = validCols.map(
|
|
69
|
+
(col) => 'SEARCH(' + literal + ', {' + escapeAirtableFieldRef(col) + '})'
|
|
70
|
+
)
|
|
71
|
+
return parts.length === 1 ? parts[0] : 'OR(' + parts.join(',') + ')'
|
|
72
|
+
}
|
|
73
|
+
|
|
45
74
|
export default async function handler(req, res) {
|
|
46
75
|
try {
|
|
47
|
-
const { query, view, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query
|
|
48
|
-
|
|
76
|
+
const { query, queryColumns, view, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query
|
|
77
|
+
|
|
49
78
|
const queryParams = new URLSearchParams()
|
|
50
|
-
|
|
79
|
+
const formulaParts = []
|
|
80
|
+
|
|
51
81
|
if (view) {
|
|
52
82
|
queryParams.append('view', view)
|
|
53
83
|
}
|
|
@@ -59,12 +89,12 @@ export default async function handler(req, res) {
|
|
|
59
89
|
parsedSorts.forEach((sort, index) => {
|
|
60
90
|
if (!sort.field) return
|
|
61
91
|
queryParams.append(\`sort[\${index}][field]\`, sort.field)
|
|
62
|
-
queryParams.append(\`sort[\${index}][direction]\`, sort.order
|
|
92
|
+
queryParams.append(\`sort[\${index}][direction]\`, (sort.order || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')
|
|
63
93
|
})
|
|
64
94
|
}
|
|
65
95
|
} else if (sortBy) {
|
|
66
96
|
queryParams.append('sort[0][field]', sortBy)
|
|
67
|
-
queryParams.append('sort[0][direction]', sortOrder || 'asc')
|
|
97
|
+
queryParams.append('sort[0][direction]', (sortOrder || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')
|
|
68
98
|
}
|
|
69
99
|
|
|
70
100
|
const perPageValue = limit || perPage || 100
|
|
@@ -114,7 +144,7 @@ export default async function handler(req, res) {
|
|
|
114
144
|
|
|
115
145
|
if (conditions.length > 0) {
|
|
116
146
|
const filterFormula = conditions.length > 1 ? \`AND(\${conditions.join(',')})\` : conditions[0]
|
|
117
|
-
|
|
147
|
+
formulaParts.push(filterFormula)
|
|
118
148
|
}
|
|
119
149
|
} else {
|
|
120
150
|
const conditions = Object.entries(parsedFilters).map(([field, value]) => {
|
|
@@ -127,13 +157,29 @@ export default async function handler(req, res) {
|
|
|
127
157
|
return \`{\${field}}=\${formatAirtableValue(value)}\`
|
|
128
158
|
}
|
|
129
159
|
})
|
|
130
|
-
|
|
160
|
+
|
|
131
161
|
const filterFormula = conditions.length > 1 ? \`AND(\${conditions.join(',')})\` : conditions[0]
|
|
132
162
|
if (filterFormula) {
|
|
133
|
-
|
|
163
|
+
formulaParts.push(filterFormula)
|
|
134
164
|
}
|
|
135
165
|
}
|
|
136
166
|
}
|
|
167
|
+
|
|
168
|
+
// Apply the { query, queryColumns } search contract via the
|
|
169
|
+
// Airtable SEARCH() function. Search-term string literal escaping
|
|
170
|
+
// protects against single-quote injection; field-ref escaping
|
|
171
|
+
// protects against close-brace or backslash breaking out of the
|
|
172
|
+
// {field} reference.
|
|
173
|
+
const searchFormula = buildAirtableSearchFormula(query, queryColumns)
|
|
174
|
+
if (searchFormula) {
|
|
175
|
+
formulaParts.push(searchFormula)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (formulaParts.length === 1) {
|
|
179
|
+
queryParams.append('filterByFormula', formulaParts[0])
|
|
180
|
+
} else if (formulaParts.length > 1) {
|
|
181
|
+
queryParams.append('filterByFormula', 'AND(' + formulaParts.join(',') + ')')
|
|
182
|
+
}
|
|
137
183
|
|
|
138
184
|
let url = \`https://api.airtable.com/v0/${baseId}/\${encodeURIComponent('${tableName}')}\`
|
|
139
185
|
if (queryParams.toString()) {
|
|
@@ -68,28 +68,13 @@ export default async function handler(req, res) {
|
|
|
68
68
|
|
|
69
69
|
const conditions = []
|
|
70
70
|
|
|
71
|
-
if (query) {
|
|
72
|
-
if (queryColumns) {
|
|
73
|
-
const parsed = safeJSONParse(queryColumns)
|
|
74
|
-
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
75
|
-
const searchConditions = columns.map(
|
|
76
|
-
(col) => \`positionCaseInsensitive(toString(\${col}), '\${query}') > 0\`
|
|
77
|
-
)
|
|
78
|
-
conditions.push(\`(\${searchConditions.join(' OR ')})\`)
|
|
79
|
-
} else {
|
|
80
|
-
// Note: Without queryColumns, ClickHouse can't search all columns efficiently
|
|
81
|
-
// Users should provide queryColumns for optimal search performance
|
|
82
|
-
console.warn('Search query provided without queryColumns - search may not work as expected')
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
71
|
const formatClickHouseValue = (value) => {
|
|
87
72
|
if (value === null || value === undefined) return 'NULL'
|
|
88
73
|
if (typeof value === 'string') return \`'\${value.replace(/'/g, "\\\\'")}'\`
|
|
89
74
|
if (typeof value === 'boolean') return value ? '1' : '0'
|
|
90
75
|
return String(value)
|
|
91
76
|
}
|
|
92
|
-
|
|
77
|
+
|
|
93
78
|
// Helper to sanitize identifier (prevent SQL injection in column names)
|
|
94
79
|
const sanitizeIdentifier = (name) => {
|
|
95
80
|
// Only allow alphanumeric and underscore
|
|
@@ -98,6 +83,27 @@ export default async function handler(req, res) {
|
|
|
98
83
|
}
|
|
99
84
|
return \`\\\`\${name}\\\`\`
|
|
100
85
|
}
|
|
86
|
+
|
|
87
|
+
if (query) {
|
|
88
|
+
if (queryColumns) {
|
|
89
|
+
const parsed = safeJSONParse(queryColumns)
|
|
90
|
+
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
91
|
+
// Escape the search term into a ClickHouse string literal so
|
|
92
|
+
// single quotes and backslashes can't break out of the literal
|
|
93
|
+
// or execute injected SQL. Column identifiers are sanitized
|
|
94
|
+
// against the identifier regex above.
|
|
95
|
+
const queryLiteral = formatClickHouseValue(String(query))
|
|
96
|
+
const searchConditions = columns.map(
|
|
97
|
+
(col) =>
|
|
98
|
+
\`positionCaseInsensitiveUTF8(toString(\${sanitizeIdentifier(col)}), \${queryLiteral}) > 0\`
|
|
99
|
+
)
|
|
100
|
+
conditions.push(\`(\${searchConditions.join(' OR ')})\`)
|
|
101
|
+
} else {
|
|
102
|
+
// Note: Without queryColumns, ClickHouse can't search all columns efficiently
|
|
103
|
+
// Users should provide queryColumns for optimal search performance
|
|
104
|
+
console.warn('Search query provided without queryColumns - search may not work as expected')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
101
107
|
|
|
102
108
|
if (filters) {
|
|
103
109
|
const parsedFilters = safeJSONParse(filters)
|
|
@@ -157,16 +163,16 @@ export default async function handler(req, res) {
|
|
|
157
163
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
158
164
|
const orderClauses = parsedSorts.map((sort) => {
|
|
159
165
|
if (!sort.field) return null
|
|
160
|
-
const order = sort.order
|
|
166
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
161
167
|
return \`\${sanitizeIdentifier(sort.field)} \${order}\`
|
|
162
168
|
}).filter(Boolean)
|
|
163
|
-
|
|
169
|
+
|
|
164
170
|
if (orderClauses.length > 0) {
|
|
165
171
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
} else if (sortBy) {
|
|
169
|
-
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${sortOrder
|
|
175
|
+
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
const limitValue = limit || perPage
|
package/src/fetchers/csv-file.ts
CHANGED
|
@@ -131,7 +131,7 @@ const generateHandlerBody = (): string => {
|
|
|
131
131
|
const field = labelToIdMap[sort.field] || sort.field
|
|
132
132
|
const aVal = getNestedValue(a, field)
|
|
133
133
|
const bVal = getNestedValue(b, field)
|
|
134
|
-
const sortOrderValue = sort.order
|
|
134
|
+
const sortOrderValue = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
135
135
|
|
|
136
136
|
let comparison = 0
|
|
137
137
|
if (aVal === null || aVal === undefined) {
|
|
@@ -159,7 +159,7 @@ const generateHandlerBody = (): string => {
|
|
|
159
159
|
filteredData.sort((a, b) => {
|
|
160
160
|
const aVal = getNestedValue(a, field)
|
|
161
161
|
const bVal = getNestedValue(b, field)
|
|
162
|
-
const sortOrderValue = sortOrder
|
|
162
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
163
163
|
|
|
164
164
|
let comparison = 0
|
|
165
165
|
if (aVal === null || aVal === undefined) {
|
|
@@ -150,12 +150,12 @@ export default async function handler(req, res) {
|
|
|
150
150
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
151
151
|
parsedSorts.forEach((sort) => {
|
|
152
152
|
if (!sort.field) return
|
|
153
|
-
const order = sort.order
|
|
153
|
+
const order = (sort.order || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc'
|
|
154
154
|
queryRef = queryRef.orderBy(sort.field, order)
|
|
155
155
|
})
|
|
156
156
|
}
|
|
157
157
|
} else if (sortBy) {
|
|
158
|
-
const sortOrderValue = sortOrder
|
|
158
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc'
|
|
159
159
|
queryRef = queryRef.orderBy(sortBy, sortOrderValue)
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -328,7 +328,7 @@ export default async function handler(req, res) {
|
|
|
328
328
|
const field = labelToIdMap[sort.field] || sort.field
|
|
329
329
|
const aVal = getNestedValue(a, field)
|
|
330
330
|
const bVal = getNestedValue(b, field)
|
|
331
|
-
const sortOrderValue = sort.order
|
|
331
|
+
const sortOrderValue = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
332
332
|
|
|
333
333
|
let comparison = 0
|
|
334
334
|
if (aVal === null || aVal === undefined) {
|
|
@@ -357,7 +357,7 @@ export default async function handler(req, res) {
|
|
|
357
357
|
const field = labelToIdMap[sortBy] || sortBy
|
|
358
358
|
const aVal = getNestedValue(a, field)
|
|
359
359
|
const bVal = getNestedValue(b, field)
|
|
360
|
-
const sortOrderValue = sortOrder
|
|
360
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
361
361
|
|
|
362
362
|
let comparison = 0
|
|
363
363
|
if (aVal === null || aVal === undefined) {
|
package/src/fetchers/index.ts
CHANGED
|
@@ -24,9 +24,10 @@ export {
|
|
|
24
24
|
validateJavaScriptConfig,
|
|
25
25
|
} from './javascript'
|
|
26
26
|
export { generateCSVFileFetcher, generateCSVCountFetcher, validateCSVConfig } from './csv-file'
|
|
27
|
-
export {
|
|
28
|
-
generateStaticCollectionFetcher,
|
|
29
|
-
generateStaticCollectionCountFetcher,
|
|
30
|
-
validateStaticCollectionConfig,
|
|
31
|
-
} from './static-collection'
|
|
32
27
|
export { generateGoogleSheetsFetcher, validateGoogleSheetsConfig } from './google-sheets'
|
|
28
|
+
export {
|
|
29
|
+
generateTeleportFetcher,
|
|
30
|
+
generateTeleportCountFetcher,
|
|
31
|
+
validateTeleportConfig,
|
|
32
|
+
} from './teleport'
|
|
33
|
+
export { generateRawQueryFetcher, parseQueryTemplateVariables } from './raw-query'
|
|
@@ -134,7 +134,7 @@ export default async function handler(req, res) {
|
|
|
134
134
|
if (!sort.field) continue
|
|
135
135
|
const aVal = getNestedValue(a, sort.field)
|
|
136
136
|
const bVal = getNestedValue(b, sort.field)
|
|
137
|
-
const sortOrderValue = sort.order
|
|
137
|
+
const sortOrderValue = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
138
138
|
|
|
139
139
|
let comparison = 0
|
|
140
140
|
if (aVal === null || aVal === undefined) {
|
|
@@ -164,7 +164,7 @@ export default async function handler(req, res) {
|
|
|
164
164
|
data.sort((a, b) => {
|
|
165
165
|
const aVal = getNestedValue(a, sortBy)
|
|
166
166
|
const bVal = getNestedValue(b, sortBy)
|
|
167
|
-
const sortOrderValue = sortOrder
|
|
167
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
168
168
|
|
|
169
169
|
let comparison = 0
|
|
170
170
|
if (aVal === null || aVal === undefined) {
|
package/src/fetchers/mariadb.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateSearchEscapeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
interface MariaDBConfig {
|
|
@@ -26,6 +27,8 @@ export const generateMariaDBFetcher = (
|
|
|
26
27
|
|
|
27
28
|
${generateSafeJSONParseCode()}
|
|
28
29
|
|
|
30
|
+
${generateSearchEscapeHelpersCode()}
|
|
31
|
+
|
|
29
32
|
// Helper function to process filters and build conditions
|
|
30
33
|
const processFilters = (filters, conditions, queryParams) => {
|
|
31
34
|
if (!filters) return
|
|
@@ -121,8 +124,10 @@ export default async function handler(req, res) {
|
|
|
121
124
|
let columns = []
|
|
122
125
|
|
|
123
126
|
if (queryColumns) {
|
|
124
|
-
// Use specified columns
|
|
125
|
-
|
|
127
|
+
// Use specified columns. Wrap non-arrays so a single column
|
|
128
|
+
// passed as a bare string doesn't get iterated as chars.
|
|
129
|
+
const parsed = safeJSONParse(queryColumns)
|
|
130
|
+
columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
126
131
|
} else {
|
|
127
132
|
// Fallback: Get all columns from information_schema
|
|
128
133
|
try {
|
|
@@ -140,12 +145,16 @@ export default async function handler(req, res) {
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
if (columns.length > 0) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
const pattern = "%" + escapeLikePattern(query) + "%"
|
|
149
|
+
const searchConditions = columns.map(
|
|
150
|
+
(col) =>
|
|
151
|
+
"LOWER(CAST(\`" + sanitizeSearchIdentifier(col) + "\` AS CHAR)) LIKE LOWER(?) ESCAPE '|'"
|
|
152
|
+
)
|
|
153
|
+
columns.forEach(() => queryParams.push(pattern))
|
|
154
|
+
conditions.push("(" + searchConditions.join(" OR ") + ")")
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
|
-
|
|
157
|
+
|
|
149
158
|
// Apply filters using helper function
|
|
150
159
|
processFilters(filters, conditions, queryParams)
|
|
151
160
|
|
|
@@ -161,16 +170,16 @@ export default async function handler(req, res) {
|
|
|
161
170
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
162
171
|
const orderClauses = parsedSorts.map((sort) => {
|
|
163
172
|
if (!sort.field) return null
|
|
164
|
-
const order = sort.order
|
|
173
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
165
174
|
return \`\\\`\${sort.field}\\\` \${order}\`
|
|
166
175
|
}).filter(Boolean)
|
|
167
|
-
|
|
176
|
+
|
|
168
177
|
if (orderClauses.length > 0) {
|
|
169
178
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
} else if (sortBy) {
|
|
173
|
-
sql += \` ORDER BY \\\`\${sortBy}\\\` \${sortOrder
|
|
182
|
+
sql += \` ORDER BY \\\`\${sortBy}\\\` \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
const limitValue = limit || perPage
|
|
@@ -285,9 +294,15 @@ async function getCount(req, res) {
|
|
|
285
294
|
}
|
|
286
295
|
|
|
287
296
|
if (columns.length > 0) {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
297
|
+
const pattern = "%" + escapeLikePattern(query) + "%"
|
|
298
|
+
const searchConditions = columns
|
|
299
|
+
.map(
|
|
300
|
+
(col) =>
|
|
301
|
+
"LOWER(CAST(\`" + sanitizeSearchIdentifier(col) + "\` AS CHAR)) LIKE LOWER(?) ESCAPE '|'"
|
|
302
|
+
)
|
|
303
|
+
.join(" OR ")
|
|
304
|
+
conditions.push("(" + searchConditions + ")")
|
|
305
|
+
columns.forEach(() => queryParams.push(pattern))
|
|
291
306
|
}
|
|
292
307
|
}
|
|
293
308
|
|
package/src/fetchers/mongodb.ts
CHANGED
|
@@ -222,7 +222,7 @@ export default async function handler(req, res) {
|
|
|
222
222
|
const sortObject = {}
|
|
223
223
|
parsedSorts.forEach((sort) => {
|
|
224
224
|
if (sort.field) {
|
|
225
|
-
sortObject[sort.field] = sort.order
|
|
225
|
+
sortObject[sort.field] = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
226
226
|
}
|
|
227
227
|
})
|
|
228
228
|
if (Object.keys(sortObject).length > 0) {
|
|
@@ -230,7 +230,7 @@ export default async function handler(req, res) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
} else if (sortBy) {
|
|
233
|
-
const sortOrderValue = sortOrder
|
|
233
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
234
234
|
cursor = cursor.sort({ [sortBy]: sortOrderValue })
|
|
235
235
|
}
|
|
236
236
|
|
package/src/fetchers/mysql.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateSearchEscapeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
interface MySQLConfig {
|
|
@@ -63,6 +64,8 @@ const getConnection = () => {
|
|
|
63
64
|
|
|
64
65
|
${generateSafeJSONParseCode()}
|
|
65
66
|
|
|
67
|
+
${generateSearchEscapeHelpersCode()}
|
|
68
|
+
|
|
66
69
|
// Helper function to process filters and build conditions
|
|
67
70
|
const processFilters = (filters, conditions, queryParams) => {
|
|
68
71
|
if (!filters) return
|
|
@@ -131,8 +134,10 @@ export default async function handler(req, res) {
|
|
|
131
134
|
let columns = []
|
|
132
135
|
|
|
133
136
|
if (queryColumns) {
|
|
134
|
-
// Use specified columns
|
|
135
|
-
|
|
137
|
+
// Use specified columns. Wrap non-arrays so a single column
|
|
138
|
+
// passed as a bare string doesn't get iterated as chars.
|
|
139
|
+
const parsed = safeJSONParse(queryColumns)
|
|
140
|
+
columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
136
141
|
} else {
|
|
137
142
|
// Fallback: Get all columns from information_schema
|
|
138
143
|
try {
|
|
@@ -150,12 +155,16 @@ export default async function handler(req, res) {
|
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
if (columns.length > 0) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
const pattern = "%" + escapeLikePattern(query) + "%"
|
|
159
|
+
const searchConditions = columns.map(
|
|
160
|
+
(col) =>
|
|
161
|
+
"LOWER(CAST(" + mysql.escapeId(sanitizeSearchIdentifier(col)) + " AS CHAR)) LIKE LOWER(?) ESCAPE '|'"
|
|
162
|
+
)
|
|
163
|
+
columns.forEach(() => queryParams.push(pattern))
|
|
164
|
+
conditions.push("(" + searchConditions.join(" OR ") + ")")
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
|
-
|
|
167
|
+
|
|
159
168
|
// Apply filters using helper function
|
|
160
169
|
processFilters(filters, conditions, queryParams)
|
|
161
170
|
|
|
@@ -171,16 +180,16 @@ export default async function handler(req, res) {
|
|
|
171
180
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
172
181
|
const orderClauses = parsedSorts.map((sort) => {
|
|
173
182
|
if (!sort.field) return null
|
|
174
|
-
const order = sort.order
|
|
183
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
175
184
|
return \`\${mysql.escapeId(sort.field)} \${order}\`
|
|
176
185
|
}).filter(Boolean)
|
|
177
|
-
|
|
186
|
+
|
|
178
187
|
if (orderClauses.length > 0) {
|
|
179
188
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
180
189
|
}
|
|
181
190
|
}
|
|
182
191
|
} else if (sortBy) {
|
|
183
|
-
sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${sortOrder
|
|
192
|
+
sql += \` ORDER BY \${mysql.escapeId(sortBy)} \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
const limitValue = limit || perPage
|
|
@@ -265,9 +274,15 @@ async function getCount(req, res) {
|
|
|
265
274
|
}
|
|
266
275
|
|
|
267
276
|
if (columns.length > 0) {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
const pattern = "%" + escapeLikePattern(query) + "%"
|
|
278
|
+
const searchConditions = columns
|
|
279
|
+
.map(
|
|
280
|
+
(col) =>
|
|
281
|
+
"LOWER(CAST(" + mysql.escapeId(sanitizeSearchIdentifier(col)) + " AS CHAR)) LIKE LOWER(?) ESCAPE '|'"
|
|
282
|
+
)
|
|
283
|
+
.join(" OR ")
|
|
284
|
+
conditions.push("(" + searchConditions + ")")
|
|
285
|
+
columns.forEach(() => queryParams.push(pattern))
|
|
271
286
|
}
|
|
272
287
|
}
|
|
273
288
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
replaceSecretReference,
|
|
3
3
|
generateDateFormatterCode,
|
|
4
4
|
generateSafeJSONParseCode,
|
|
5
|
+
generateSearchEscapeHelpersCode,
|
|
5
6
|
} from '../utils'
|
|
6
7
|
|
|
7
8
|
interface PostgreSQLConfig {
|
|
@@ -56,6 +57,8 @@ const getClient = () => {
|
|
|
56
57
|
|
|
57
58
|
${generateSafeJSONParseCode()}
|
|
58
59
|
|
|
60
|
+
${generateSearchEscapeHelpersCode()}
|
|
61
|
+
|
|
59
62
|
// Helper function to process filters and build conditions
|
|
60
63
|
const processFilters = (filters, conditions, queryParams, paramIndex) => {
|
|
61
64
|
if (!filters) return paramIndex
|
|
@@ -131,14 +134,16 @@ export default async function handler(req, res) {
|
|
|
131
134
|
let columns = []
|
|
132
135
|
|
|
133
136
|
if (queryColumns) {
|
|
134
|
-
// Use specified columns
|
|
135
|
-
|
|
137
|
+
// Use specified columns. Wrap non-arrays so that a single
|
|
138
|
+
// column passed as a bare string doesn't get iterated as chars.
|
|
139
|
+
const parsed = safeJSONParse(queryColumns)
|
|
140
|
+
columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
136
141
|
} else {
|
|
137
142
|
// Fallback: Get all columns from information_schema
|
|
138
143
|
try {
|
|
139
144
|
const schemaQuery = \`
|
|
140
|
-
SELECT column_name
|
|
141
|
-
FROM information_schema.columns
|
|
145
|
+
SELECT column_name
|
|
146
|
+
FROM information_schema.columns
|
|
142
147
|
WHERE table_name = $1
|
|
143
148
|
${schema ? `AND table_schema = $2` : ''}
|
|
144
149
|
ORDER BY ordinal_position
|
|
@@ -156,16 +161,17 @@ export default async function handler(req, res) {
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
if (columns.length > 0) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const pattern = '%' + escapeLikePattern(query) + '%'
|
|
165
|
+
const placeholder = '$' + paramIndex
|
|
166
|
+
paramIndex++
|
|
167
|
+
queryParams.push(pattern)
|
|
168
|
+
const searchConditions = columns.map(
|
|
169
|
+
(col) => '"' + sanitizeSearchIdentifier(col) + '"::text ILIKE ' + placeholder + " ESCAPE '|'"
|
|
170
|
+
)
|
|
171
|
+
conditions.push('(' + searchConditions.join(' OR ') + ')')
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
|
-
|
|
174
|
+
|
|
169
175
|
// Apply filters using helper function
|
|
170
176
|
paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
|
|
171
177
|
|
|
@@ -181,16 +187,16 @@ export default async function handler(req, res) {
|
|
|
181
187
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
182
188
|
const orderClauses = parsedSorts.map((sort) => {
|
|
183
189
|
if (!sort.field) return null
|
|
184
|
-
const order = sort.order
|
|
190
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
185
191
|
return \`\${sort.field} \${order}\`
|
|
186
192
|
}).filter(Boolean)
|
|
187
|
-
|
|
193
|
+
|
|
188
194
|
if (orderClauses.length > 0) {
|
|
189
195
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
190
196
|
}
|
|
191
197
|
}
|
|
192
198
|
} else if (sortBy) {
|
|
193
|
-
sql += \` ORDER BY \${sortBy} \${sortOrder
|
|
199
|
+
sql += \` ORDER BY \${sortBy} \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
const limitValue = limit || perPage
|
|
@@ -286,9 +292,16 @@ async function getCount(req, res) {
|
|
|
286
292
|
}
|
|
287
293
|
|
|
288
294
|
if (columns.length > 0) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
const pattern = '%' + escapeLikePattern(query) + '%'
|
|
296
|
+
const placeholder = '$' + paramIndex
|
|
297
|
+
paramIndex++
|
|
298
|
+
queryParams.push(pattern)
|
|
299
|
+
const searchConditions = columns
|
|
300
|
+
.map(
|
|
301
|
+
(col) => '"' + sanitizeSearchIdentifier(col) + '"::text ILIKE ' + placeholder + " ESCAPE '|'"
|
|
302
|
+
)
|
|
303
|
+
.join(' OR ')
|
|
304
|
+
conditions.push('(' + searchConditions + ')')
|
|
292
305
|
}
|
|
293
306
|
}
|
|
294
307
|
|