@teleporthq/teleport-plugin-next-data-source 0.42.34 → 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
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { replaceSecretReference } from '../utils'
|
|
2
|
+
|
|
3
|
+
interface PostgreSQLConfig {
|
|
4
|
+
host?: string
|
|
5
|
+
port?: number
|
|
6
|
+
user?: string
|
|
7
|
+
username?: string
|
|
8
|
+
password?: string
|
|
9
|
+
database?: string
|
|
10
|
+
ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
11
|
+
sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FORBIDDEN_KEYWORDS = ['CREATE', 'ALTER', 'DROP', 'TRUNCATE', 'RENAME', 'GRANT', 'REVOKE']
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates an API route handler that executes a parameterized raw SQL query.
|
|
18
|
+
* Supports {{Current User.*}} substitution via query params.
|
|
19
|
+
*
|
|
20
|
+
* @param config - PostgreSQL connection config
|
|
21
|
+
* @param query - Raw SQL query string with {{Current User.*}} patterns already replaced to $N placeholders
|
|
22
|
+
* @param paramFields - Array of query param field names that map to $1, $2, etc. in order
|
|
23
|
+
*/
|
|
24
|
+
export const generateRawQueryFetcher = (
|
|
25
|
+
config: Record<string, unknown>,
|
|
26
|
+
query: string,
|
|
27
|
+
paramFields: string[]
|
|
28
|
+
): string => {
|
|
29
|
+
const pgConfig = config as PostgreSQLConfig
|
|
30
|
+
|
|
31
|
+
const paramDestructure =
|
|
32
|
+
paramFields.length > 0 ? `const { ${paramFields.join(', ')} } = req.query` : ''
|
|
33
|
+
|
|
34
|
+
const paramValidation =
|
|
35
|
+
paramFields.length > 0
|
|
36
|
+
? `
|
|
37
|
+
if (${paramFields.map((f) => `!${f}`).join(' || ')}) {
|
|
38
|
+
return res.status(200).json({ success: true, data: [], timestamp: Date.now() })
|
|
39
|
+
}`
|
|
40
|
+
: ''
|
|
41
|
+
|
|
42
|
+
const paramArray = paramFields.length > 0 ? `[${paramFields.join(', ')}]` : '[]'
|
|
43
|
+
|
|
44
|
+
const sslValue =
|
|
45
|
+
pgConfig.ssl === false
|
|
46
|
+
? 'false'
|
|
47
|
+
: pgConfig.ssl === true || pgConfig.sslConfig
|
|
48
|
+
? pgConfig.sslConfig
|
|
49
|
+
? `{
|
|
50
|
+
${pgConfig.sslConfig.ca ? `ca: ${replaceSecretReference(pgConfig.sslConfig.ca)},` : ''}
|
|
51
|
+
${pgConfig.sslConfig.cert ? `cert: ${replaceSecretReference(pgConfig.sslConfig.cert)},` : ''}
|
|
52
|
+
${pgConfig.sslConfig.key ? `key: ${replaceSecretReference(pgConfig.sslConfig.key)},` : ''}
|
|
53
|
+
rejectUnauthorized: false
|
|
54
|
+
}`
|
|
55
|
+
: '{ rejectUnauthorized: false }'
|
|
56
|
+
: 'false'
|
|
57
|
+
|
|
58
|
+
return `import { Client } from 'pg'
|
|
59
|
+
|
|
60
|
+
const FORBIDDEN_KEYWORDS = ${JSON.stringify(FORBIDDEN_KEYWORDS)}
|
|
61
|
+
|
|
62
|
+
const getClient = () => {
|
|
63
|
+
const connStr = process.env.TELEPORT_DB_CONNECTION_STRING
|
|
64
|
+
if (connStr) {
|
|
65
|
+
const sslEnv = process.env.TELEPORT_DB_SSL
|
|
66
|
+
const sslOpt = sslEnv === 'false' ? false : sslEnv === 'true' ? { rejectUnauthorized: false } : undefined
|
|
67
|
+
return new Client(Object.assign({ connectionString: connStr }, sslOpt !== undefined ? { ssl: sslOpt } : {}))
|
|
68
|
+
}
|
|
69
|
+
return new Client({
|
|
70
|
+
host: process.env.TELEPORT_DB_HOST || ${JSON.stringify(pgConfig.host ?? null)},
|
|
71
|
+
port: parseInt(process.env.TELEPORT_DB_PORT || '${pgConfig.port || 5432}', 10),
|
|
72
|
+
user: process.env.TELEPORT_DB_USER || ${JSON.stringify(
|
|
73
|
+
pgConfig.user ?? pgConfig.username ?? null
|
|
74
|
+
)},
|
|
75
|
+
password: process.env.TELEPORT_DB_PASSWORD || ${
|
|
76
|
+
replaceSecretReference(pgConfig.password) !== 'undefined'
|
|
77
|
+
? replaceSecretReference(pgConfig.password)
|
|
78
|
+
: 'null'
|
|
79
|
+
},
|
|
80
|
+
database: process.env.TELEPORT_DB_NAME || ${JSON.stringify(pgConfig.database ?? null)},
|
|
81
|
+
ssl: ${sslValue}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const validateQuery = (query) => {
|
|
86
|
+
const trimmed = query.trim().toUpperCase()
|
|
87
|
+
for (const keyword of FORBIDDEN_KEYWORDS) {
|
|
88
|
+
if (trimmed.startsWith(keyword)) {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default async function handler(req, res) {
|
|
96
|
+
try {
|
|
97
|
+
${paramDestructure}
|
|
98
|
+
${paramValidation}
|
|
99
|
+
|
|
100
|
+
const query = ${JSON.stringify(query)}
|
|
101
|
+
|
|
102
|
+
if (!validateQuery(query)) {
|
|
103
|
+
return res.status(400).json({
|
|
104
|
+
success: false,
|
|
105
|
+
error: 'Only SELECT queries are allowed',
|
|
106
|
+
timestamp: Date.now()
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const client = getClient()
|
|
111
|
+
await client.connect()
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await client.query(query, ${paramArray})
|
|
115
|
+
|
|
116
|
+
return res.status(200).json({
|
|
117
|
+
success: true,
|
|
118
|
+
data: result.rows,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
})
|
|
121
|
+
} finally {
|
|
122
|
+
await client.end()
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Raw query fetch error:', error)
|
|
126
|
+
return res.status(500).json({
|
|
127
|
+
success: false,
|
|
128
|
+
error: error.message || 'Failed to fetch data',
|
|
129
|
+
timestamp: Date.now()
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parses a SQL query string for {{Current User.*}} template patterns.
|
|
138
|
+
* Returns the parameterized query and an ordered list of field names.
|
|
139
|
+
*/
|
|
140
|
+
export const parseQueryTemplateVariables = (
|
|
141
|
+
query: string
|
|
142
|
+
): { parameterizedQuery: string; paramFields: string[] } => {
|
|
143
|
+
const captureRe = /\{\{Current User\.(\w+)\}\}/g
|
|
144
|
+
const paramFields: string[] = []
|
|
145
|
+
|
|
146
|
+
// First pass: collect unique fields in order
|
|
147
|
+
const tempRe = new RegExp(captureRe.source, 'g')
|
|
148
|
+
let match = tempRe.exec(query)
|
|
149
|
+
while (match !== null) {
|
|
150
|
+
const field = match[1]
|
|
151
|
+
if (!paramFields.includes(field)) {
|
|
152
|
+
paramFields.push(field)
|
|
153
|
+
}
|
|
154
|
+
match = tempRe.exec(query)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Second pass: replace patterns with $N placeholders
|
|
158
|
+
let parameterizedQuery = query
|
|
159
|
+
for (let i = 0; i < paramFields.length; i++) {
|
|
160
|
+
const field = paramFields[i]
|
|
161
|
+
// Replace both quoted ('{{...}}') and unquoted ({{...}}) variants
|
|
162
|
+
const quotedPattern = `'{{Current User.${field}}}'`
|
|
163
|
+
const unquotedPattern = `{{Current User.${field}}}`
|
|
164
|
+
|
|
165
|
+
if (parameterizedQuery.includes(quotedPattern)) {
|
|
166
|
+
parameterizedQuery = parameterizedQuery.split(quotedPattern).join(`$${i + 1}`)
|
|
167
|
+
} else {
|
|
168
|
+
parameterizedQuery = parameterizedQuery.split(unquotedPattern).join(`$${i + 1}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Convert field names to camelCase query param names
|
|
173
|
+
const queryParamNames = paramFields.map(
|
|
174
|
+
(f) => `currentUser${f.charAt(0).toUpperCase() + f.slice(1)}`
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return { parameterizedQuery, paramFields: queryParamNames }
|
|
178
|
+
}
|
package/src/fetchers/redis.ts
CHANGED
|
@@ -145,7 +145,7 @@ export default async function handler(req, res) {
|
|
|
145
145
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
146
146
|
const primarySort = parsedSorts[0]
|
|
147
147
|
if (primarySort.field) {
|
|
148
|
-
const sortOrderValue = primarySort.order
|
|
148
|
+
const sortOrderValue = (primarySort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
149
149
|
results.sort((a, b) => {
|
|
150
150
|
const aVal = a[primarySort.field]
|
|
151
151
|
const bVal = b[primarySort.field]
|
|
@@ -156,7 +156,7 @@ export default async function handler(req, res) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
} else if (sortBy) {
|
|
159
|
-
const sortOrderValue = sortOrder
|
|
159
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
160
160
|
results.sort((a, b) => {
|
|
161
161
|
const aVal = a[sortBy]
|
|
162
162
|
const bVal = b[sortBy]
|
package/src/fetchers/redshift.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 RedshiftConfig {
|
|
@@ -55,6 +56,8 @@ const getClient = () => {
|
|
|
55
56
|
|
|
56
57
|
${generateSafeJSONParseCode()}
|
|
57
58
|
|
|
59
|
+
${generateSearchEscapeHelpersCode()}
|
|
60
|
+
|
|
58
61
|
${generateDateFormatterCode()}
|
|
59
62
|
|
|
60
63
|
export default async function handler(req, res) {
|
|
@@ -95,13 +98,14 @@ export default async function handler(req, res) {
|
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
if (columns.length > 0) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
const pattern = '%' + escapeLikePattern(query) + '%'
|
|
102
|
+
const placeholder = '$' + paramIndex
|
|
103
|
+
paramIndex++
|
|
104
|
+
queryParams.push(pattern)
|
|
105
|
+
const searchConditions = columns.map(
|
|
106
|
+
(col) => '"' + sanitizeSearchIdentifier(col) + '"::text ILIKE ' + placeholder + " ESCAPE '|'"
|
|
107
|
+
)
|
|
108
|
+
conditions.push('(' + searchConditions.join(' OR ') + ')')
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -178,16 +182,16 @@ export default async function handler(req, res) {
|
|
|
178
182
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
179
183
|
const orderClauses = parsedSorts.map((sort) => {
|
|
180
184
|
if (!sort.field) return null
|
|
181
|
-
const order = sort.order
|
|
185
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
182
186
|
return \`\${sanitizeIdentifier(sort.field)} \${order}\`
|
|
183
187
|
}).filter(Boolean)
|
|
184
|
-
|
|
188
|
+
|
|
185
189
|
if (orderClauses.length > 0) {
|
|
186
190
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
} else if (sortBy) {
|
|
190
|
-
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${sortOrder
|
|
194
|
+
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
const limitValue = limit || perPage
|
package/src/fetchers/rest-api.ts
CHANGED
|
@@ -207,7 +207,7 @@ export default async function handler(req, res) {
|
|
|
207
207
|
if (!sort.field) continue
|
|
208
208
|
const aVal = getNestedValue(a, sort.field)
|
|
209
209
|
const bVal = getNestedValue(b, sort.field)
|
|
210
|
-
const sortOrderValue = sort.order
|
|
210
|
+
const sortOrderValue = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
211
211
|
|
|
212
212
|
let comparison = 0
|
|
213
213
|
if (aVal === null || aVal === undefined) {
|
|
@@ -237,7 +237,7 @@ export default async function handler(req, res) {
|
|
|
237
237
|
data.sort((a, b) => {
|
|
238
238
|
const aVal = getNestedValue(a, sortBy)
|
|
239
239
|
const bVal = getNestedValue(b, sortBy)
|
|
240
|
-
const sortOrderValue = sortOrder
|
|
240
|
+
const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1
|
|
241
241
|
|
|
242
242
|
let comparison = 0
|
|
243
243
|
if (aVal === null || aVal === undefined) {
|
package/src/fetchers/supabase.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
|
export const validateSupabaseConfig = (
|
|
@@ -49,7 +50,66 @@ export const generateSupabaseFetcher = (
|
|
|
49
50
|
const supabaseConfig = config as SupabaseConfig
|
|
50
51
|
const supabaseUrl = supabaseConfig.supabaseUrl
|
|
51
52
|
const apiKey = supabaseConfig.serviceRoleKey || supabaseConfig.publicApiKey
|
|
53
|
+
/*
|
|
54
|
+
DO $$
|
|
55
|
+
DECLARE
|
|
56
|
+
first_account_id UUID;
|
|
57
|
+
first_team_id UUID;
|
|
58
|
+
BEGIN
|
|
59
|
+
-- Step 1: Select the first account's id
|
|
60
|
+
SELECT id INTO first_account_id
|
|
61
|
+
FROM accounts
|
|
62
|
+
ORDER BY id
|
|
63
|
+
LIMIT 1;
|
|
52
64
|
|
|
65
|
+
-- Step 2: Insert into subscriptions
|
|
66
|
+
INSERT INTO subscriptions (
|
|
67
|
+
"stripeSubscriptionId",
|
|
68
|
+
"accountId",
|
|
69
|
+
"stripeCurrentPeriodStart",
|
|
70
|
+
"stripeCurrentPeriodEnd",
|
|
71
|
+
"createdAt",
|
|
72
|
+
"updatedAt",
|
|
73
|
+
"percentOff"
|
|
74
|
+
) VALUES (
|
|
75
|
+
'abc',
|
|
76
|
+
first_account_id,
|
|
77
|
+
'2025-08-13 00:00:00+00'::timestamptz,
|
|
78
|
+
'2035-09-13 00:00:00+00'::timestamptz,
|
|
79
|
+
'2025-08-13 00:00:00+00'::timestamptz,
|
|
80
|
+
'2025-08-13 00:00:00+00'::timestamptz,
|
|
81
|
+
100
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
-- Step 3: Insert into subscription-items
|
|
85
|
+
INSERT INTO "subscription-items" (
|
|
86
|
+
"stripeSubscriptionItemId",
|
|
87
|
+
"stripePriceId",
|
|
88
|
+
"stripeSubscriptionId",
|
|
89
|
+
"createdAt",
|
|
90
|
+
"updatedAt"
|
|
91
|
+
) VALUES (
|
|
92
|
+
'abc',
|
|
93
|
+
'price_1KO3BuGd5V11xo4EFF8fKiVR',
|
|
94
|
+
'abc',
|
|
95
|
+
'2025-08-13 00:00:00+00'::timestamptz,
|
|
96
|
+
'2025-08-13 00:00:00+00'::timestamptz
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
-- Step 4: Get first team id and update its subscriptionItemId
|
|
100
|
+
SELECT id INTO first_team_id
|
|
101
|
+
FROM teams
|
|
102
|
+
ORDER BY id
|
|
103
|
+
LIMIT 1;
|
|
104
|
+
|
|
105
|
+
UPDATE teams
|
|
106
|
+
SET "subscriptionItemId" = 'abc'
|
|
107
|
+
WHERE id = first_team_id;
|
|
108
|
+
|
|
109
|
+
RAISE NOTICE 'Script completed successfully. Account ID: %, Team ID: %', first_account_id, first_team_id;
|
|
110
|
+
END $$;
|
|
111
|
+
|
|
112
|
+
*/
|
|
53
113
|
return `import { createClient } from '@supabase/supabase-js'
|
|
54
114
|
|
|
55
115
|
let client = null
|
|
@@ -67,6 +127,8 @@ const getClient = () => {
|
|
|
67
127
|
|
|
68
128
|
${generateSafeJSONParseCode()}
|
|
69
129
|
|
|
130
|
+
${generateSearchEscapeHelpersCode()}
|
|
131
|
+
|
|
70
132
|
// Helper function to process filter values
|
|
71
133
|
const processFilterValue = (value) => {
|
|
72
134
|
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
@@ -183,8 +245,10 @@ export default async function handler(req, res) {
|
|
|
183
245
|
let columns = []
|
|
184
246
|
|
|
185
247
|
if (queryColumns) {
|
|
186
|
-
// Use specified columns
|
|
187
|
-
|
|
248
|
+
// Use specified columns. Wrap non-arrays so a single column
|
|
249
|
+
// passed as a bare string doesn't get iterated as chars.
|
|
250
|
+
const parsed = safeJSONParse(queryColumns)
|
|
251
|
+
columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
188
252
|
} else {
|
|
189
253
|
// Fallback: Get text-searchable columns from a sample row
|
|
190
254
|
try {
|
|
@@ -220,14 +284,26 @@ export default async function handler(req, res) {
|
|
|
220
284
|
}
|
|
221
285
|
|
|
222
286
|
if (columns.length > 0) {
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
287
|
+
// PostgREST .or() DSL separates conditions with comma and uses
|
|
288
|
+
// dot to split field / operator / value, so we wrap the pattern
|
|
289
|
+
// in double quotes and backslash-escape any embedded quotes or
|
|
290
|
+
// backslashes in the user's search term. Columns go through the
|
|
291
|
+
// shared identifier sanitizer to reject injection. Note:
|
|
292
|
+
// PostgREST .ilike. does not expose a LIKE ESCAPE clause, so
|
|
293
|
+
// raw % / _ in the user input still act as wildcards in this
|
|
294
|
+
// backend; the canvas preview path escapes them explicitly.
|
|
295
|
+
const rawPattern = "%" + String(query) + "%"
|
|
296
|
+
const escapedForPostgrest = rawPattern
|
|
297
|
+
.replace(/\\\\/g, "\\\\\\\\")
|
|
298
|
+
.replace(/"/g, '\\\\"')
|
|
299
|
+
const searchPattern = '"' + escapedForPostgrest + '"'
|
|
300
|
+
const orConditions = columns
|
|
301
|
+
.map((col) => sanitizeSearchIdentifier(col) + ".ilike." + searchPattern)
|
|
302
|
+
.join(",")
|
|
227
303
|
queryRef = queryRef.or(orConditions)
|
|
228
304
|
}
|
|
229
305
|
}
|
|
230
|
-
|
|
306
|
+
|
|
231
307
|
// Apply filters using helper function
|
|
232
308
|
queryRef = applyFilters(queryRef, filters)
|
|
233
309
|
|
|
@@ -237,14 +313,14 @@ export default async function handler(req, res) {
|
|
|
237
313
|
if (Array.isArray(parsedSorts)) {
|
|
238
314
|
parsedSorts.forEach((sort) => {
|
|
239
315
|
if (sort.field) {
|
|
240
|
-
queryRef = queryRef.order(sort.field, {
|
|
241
|
-
ascending: sort.order
|
|
316
|
+
queryRef = queryRef.order(sort.field, {
|
|
317
|
+
ascending: !(sort.order || '').toLowerCase().startsWith('desc')
|
|
242
318
|
})
|
|
243
319
|
}
|
|
244
320
|
})
|
|
245
321
|
}
|
|
246
322
|
} else if (sortBy) {
|
|
247
|
-
queryRef = queryRef.order(sortBy, { ascending: sortOrder
|
|
323
|
+
queryRef = queryRef.order(sortBy, { ascending: !(sortOrder || '').toLowerCase().startsWith('desc') })
|
|
248
324
|
}
|
|
249
325
|
|
|
250
326
|
const limitValue = limit || perPage
|
|
@@ -337,10 +413,17 @@ async function getCount(req, res) {
|
|
|
337
413
|
}
|
|
338
414
|
|
|
339
415
|
if (columns.length > 0) {
|
|
340
|
-
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
const
|
|
416
|
+
// Mirror the fetch handler: sanitize identifiers, wrap the
|
|
417
|
+
// pattern in double quotes for PostgREST .or() DSL, and escape
|
|
418
|
+
// backslashes / quotes in the user's search term.
|
|
419
|
+
const rawPattern = "%" + String(query) + "%"
|
|
420
|
+
const escapedForPostgrest = rawPattern
|
|
421
|
+
.replace(/\\\\/g, "\\\\\\\\")
|
|
422
|
+
.replace(/"/g, '\\\\"')
|
|
423
|
+
const searchPattern = '"' + escapedForPostgrest + '"'
|
|
424
|
+
const orConditions = columns
|
|
425
|
+
.map((col) => sanitizeSearchIdentifier(col) + ".ilike." + searchPattern)
|
|
426
|
+
.join(",")
|
|
344
427
|
countQuery = countQuery.or(orConditions)
|
|
345
428
|
}
|
|
346
429
|
}
|