@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
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import {
|
|
2
|
+
replaceSecretReference,
|
|
3
|
+
generateDateFormatterCode,
|
|
4
|
+
generateSafeJSONParseCode,
|
|
5
|
+
generateSearchEscapeHelpersCode,
|
|
6
|
+
} from '../utils'
|
|
7
|
+
import {
|
|
8
|
+
getTransformationCode,
|
|
9
|
+
getTransformExpression,
|
|
10
|
+
getTransformWrapperCode,
|
|
11
|
+
} from '../transformations'
|
|
12
|
+
|
|
13
|
+
interface TeleportDBConfig {
|
|
14
|
+
host?: string
|
|
15
|
+
port?: number | string
|
|
16
|
+
user?: string
|
|
17
|
+
username?: string
|
|
18
|
+
password?: string
|
|
19
|
+
database?: string
|
|
20
|
+
ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
21
|
+
sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
|
|
22
|
+
options?: { schema?: string }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DEFAULT_ENV_KEYS = {
|
|
26
|
+
host: 'TELEPORT_DB_HOST',
|
|
27
|
+
port: 'TELEPORT_DB_PORT',
|
|
28
|
+
user: 'TELEPORT_DB_USER',
|
|
29
|
+
password: 'TELEPORT_DB_PASSWORD',
|
|
30
|
+
database: 'TELEPORT_DB_NAME',
|
|
31
|
+
ssl: 'TELEPORT_DB_SSL',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const resolveEnvReference = (value: unknown, defaultEnvKey: string): string => {
|
|
35
|
+
if (typeof value === 'string' && value.startsWith('teleporthq.secrets.')) {
|
|
36
|
+
return replaceSecretReference(value)
|
|
37
|
+
}
|
|
38
|
+
return `process.env.${defaultEnvKey}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const validateTeleportConfig = (
|
|
42
|
+
config: Record<string, unknown>
|
|
43
|
+
): { isValid: boolean; error?: string } => {
|
|
44
|
+
if (!config || typeof config !== 'object') {
|
|
45
|
+
return { isValid: false, error: 'Config must be a valid object' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { isValid: true }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const generateTeleportFetcher = (
|
|
52
|
+
config: Record<string, unknown>,
|
|
53
|
+
tableName: string
|
|
54
|
+
): string => {
|
|
55
|
+
const dbConfig = config as TeleportDBConfig
|
|
56
|
+
const schema = dbConfig.options?.schema
|
|
57
|
+
|
|
58
|
+
const hostRef = resolveEnvReference(dbConfig.host, DEFAULT_ENV_KEYS.host)
|
|
59
|
+
const portRef = resolveEnvReference(dbConfig.port, DEFAULT_ENV_KEYS.port)
|
|
60
|
+
const userRef = resolveEnvReference(dbConfig.user || dbConfig.username, DEFAULT_ENV_KEYS.user)
|
|
61
|
+
const passwordRef = resolveEnvReference(dbConfig.password, DEFAULT_ENV_KEYS.password)
|
|
62
|
+
const databaseRef = resolveEnvReference(dbConfig.database, DEFAULT_ENV_KEYS.database)
|
|
63
|
+
|
|
64
|
+
const sslCode =
|
|
65
|
+
dbConfig.ssl === false
|
|
66
|
+
? 'false'
|
|
67
|
+
: dbConfig.sslConfig
|
|
68
|
+
? `{
|
|
69
|
+
${
|
|
70
|
+
dbConfig.sslConfig.ca
|
|
71
|
+
? `ca: ${resolveEnvReference(dbConfig.sslConfig.ca, 'TELEPORT_DB_SSL_CA')},`
|
|
72
|
+
: ''
|
|
73
|
+
}
|
|
74
|
+
${
|
|
75
|
+
dbConfig.sslConfig.cert
|
|
76
|
+
? `cert: ${resolveEnvReference(dbConfig.sslConfig.cert, 'TELEPORT_DB_SSL_CERT')},`
|
|
77
|
+
: ''
|
|
78
|
+
}
|
|
79
|
+
${
|
|
80
|
+
dbConfig.sslConfig.key
|
|
81
|
+
? `key: ${resolveEnvReference(dbConfig.sslConfig.key, 'TELEPORT_DB_SSL_KEY')},`
|
|
82
|
+
: ''
|
|
83
|
+
}
|
|
84
|
+
rejectUnauthorized: false
|
|
85
|
+
}`
|
|
86
|
+
: `(process.env.${DEFAULT_ENV_KEYS.ssl} === 'false' ? false : process.env.${DEFAULT_ENV_KEYS.ssl} === 'true' ? { rejectUnauthorized: false } : undefined)`
|
|
87
|
+
|
|
88
|
+
return `import { Client } from 'pg'
|
|
89
|
+
|
|
90
|
+
function normalizePostgresConnectionString(connectionString) {
|
|
91
|
+
if (!connectionString || typeof connectionString !== 'string') return connectionString;
|
|
92
|
+
if (/^postgresql:\\/(?!\\/)/i.test(connectionString)) {
|
|
93
|
+
return connectionString.replace(/^postgresql:\\//i, 'postgresql://');
|
|
94
|
+
}
|
|
95
|
+
return connectionString;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function stripSslQueryParamsFromConnectionString(connectionString) {
|
|
99
|
+
if (!connectionString || typeof connectionString !== 'string') return connectionString;
|
|
100
|
+
try {
|
|
101
|
+
var u = new URL(connectionString.replace(/^postgresql:/i, 'postgres:'));
|
|
102
|
+
u.searchParams.delete('sslmode');
|
|
103
|
+
u.searchParams.delete('ssl');
|
|
104
|
+
u.searchParams.delete('sslrootcert');
|
|
105
|
+
u.searchParams.delete('sslcert');
|
|
106
|
+
u.searchParams.delete('sslkey');
|
|
107
|
+
return u.toString().replace(/^postgres:/i, 'postgresql:');
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return connectionString;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const getClient = () => {
|
|
114
|
+
var ssl = ${sslCode};
|
|
115
|
+
var connStr = process.env.TELEPORT_DB_CONNECTION_STRING;
|
|
116
|
+
if (connStr) {
|
|
117
|
+
connStr = normalizePostgresConnectionString(connStr);
|
|
118
|
+
}
|
|
119
|
+
if (ssl === false && connStr) {
|
|
120
|
+
connStr = stripSslQueryParamsFromConnectionString(connStr);
|
|
121
|
+
}
|
|
122
|
+
if (connStr) {
|
|
123
|
+
return new Client(Object.assign(
|
|
124
|
+
{ connectionString: connStr },
|
|
125
|
+
ssl !== undefined ? { ssl: ssl } : {}
|
|
126
|
+
));
|
|
127
|
+
}
|
|
128
|
+
return new Client(Object.assign(
|
|
129
|
+
{
|
|
130
|
+
host: ${hostRef},
|
|
131
|
+
port: parseInt(${portRef} || '5432', 10),
|
|
132
|
+
user: ${userRef},
|
|
133
|
+
password: ${passwordRef},
|
|
134
|
+
database: ${databaseRef},
|
|
135
|
+
},
|
|
136
|
+
ssl !== undefined ? { ssl: ssl } : {}
|
|
137
|
+
));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
${generateSafeJSONParseCode()}
|
|
141
|
+
|
|
142
|
+
${generateSearchEscapeHelpersCode()}
|
|
143
|
+
${getTransformationCode(tableName)}
|
|
144
|
+
${getTransformWrapperCode(tableName)}
|
|
145
|
+
const processFilters = (filters, conditions, queryParams, paramIndex) => {
|
|
146
|
+
if (!filters) return paramIndex
|
|
147
|
+
|
|
148
|
+
const parsedFilters = safeJSONParse(filters)
|
|
149
|
+
|
|
150
|
+
if (Array.isArray(parsedFilters)) {
|
|
151
|
+
parsedFilters.forEach((filter) => {
|
|
152
|
+
if (!filter.source || filter.destination === undefined) return
|
|
153
|
+
|
|
154
|
+
const field = filter.source
|
|
155
|
+
const value = filter.destination
|
|
156
|
+
const operand = filter.operand || '='
|
|
157
|
+
|
|
158
|
+
if (Array.isArray(value)) {
|
|
159
|
+
if (value.length === 0) return
|
|
160
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
161
|
+
queryParams.push(...value)
|
|
162
|
+
if (operand === '!=') {
|
|
163
|
+
conditions.push(\`\${field} NOT IN (\${placeholders.join(', ')})\`)
|
|
164
|
+
} else {
|
|
165
|
+
conditions.push(\`\${field} IN (\${placeholders.join(', ')})\`)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
if (value === null) {
|
|
169
|
+
if (operand === '=') {
|
|
170
|
+
conditions.push(\`\${field} IS NULL\`)
|
|
171
|
+
} else if (operand === '!=') {
|
|
172
|
+
conditions.push(\`\${field} IS NOT NULL\`)
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
const validOps = ['=', '!=', '>', '<', '>=', '<=']
|
|
176
|
+
const sqlOperator = validOps.includes(operand) ? operand : '='
|
|
177
|
+
conditions.push(\`\${field} \${sqlOperator} $\${paramIndex}\`)
|
|
178
|
+
queryParams.push(value)
|
|
179
|
+
paramIndex++
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
} else {
|
|
184
|
+
Object.entries(parsedFilters).forEach(([key, value]) => {
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
const placeholders = value.map(() => \`$\${paramIndex++}\`)
|
|
187
|
+
queryParams.push(...value)
|
|
188
|
+
conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
|
|
189
|
+
} else {
|
|
190
|
+
conditions.push(\`\${key} = $\${paramIndex}\`)
|
|
191
|
+
queryParams.push(value)
|
|
192
|
+
paramIndex++
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return paramIndex
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
${generateDateFormatterCode()}
|
|
201
|
+
|
|
202
|
+
// Matches DDL / dangerous statements the raw-query branch should refuse.
|
|
203
|
+
// Keep this list conservative — anything destructive or schema-changing is
|
|
204
|
+
// out of scope for a client-triggered fetch. SELECT and CTEs (WITH ...) are
|
|
205
|
+
// the only shapes consumers legitimately need.
|
|
206
|
+
const BLOCKED_RAW_QUERY_PATTERNS = [
|
|
207
|
+
/\\bcreate\\s+(?:temporary\\s+|temp\\s+|unlogged\\s+|global\\s+|local\\s+)?table\\b/i,
|
|
208
|
+
/\\bcreate\\s+(?:unique\\s+)?index\\b/i,
|
|
209
|
+
/\\bcreate\\s+(?:or\\s+replace\\s+)?(?:materialized\\s+)?view\\b/i,
|
|
210
|
+
/\\bcreate\\s+(?:or\\s+replace\\s+)?trigger\\b/i,
|
|
211
|
+
/\\bcreate\\s+(?:or\\s+replace\\s+)?(?:aggregate\\s+)?function\\b/i,
|
|
212
|
+
/\\bcreate\\s+(?:or\\s+replace\\s+)?procedure\\b/i,
|
|
213
|
+
/\\bcreate\\s+(?:database|schema|sequence|extension|type|role|user)\\b/i,
|
|
214
|
+
/\\bdrop\\s+(?:table|view|index|schema|database|sequence|trigger|function|procedure|role|user|extension|type|materialized)\\b/i,
|
|
215
|
+
/\\balter\\s+(?:table|view|index|schema|database|sequence|role|user|system)\\b/i,
|
|
216
|
+
/\\btruncate\\b/i,
|
|
217
|
+
/\\bgrant\\b/i,
|
|
218
|
+
/\\brevoke\\b/i,
|
|
219
|
+
/\\binsert\\b/i,
|
|
220
|
+
/\\bupdate\\b/i,
|
|
221
|
+
/\\bdelete\\b/i,
|
|
222
|
+
/\\bcopy\\b/i,
|
|
223
|
+
/\\bvacuum\\b/i,
|
|
224
|
+
/\\breindex\\b/i,
|
|
225
|
+
/\\bcluster\\b/i,
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
function assertRawQuerySafe(rawQuery) {
|
|
229
|
+
if (typeof rawQuery !== 'string' || rawQuery.length === 0) {
|
|
230
|
+
throw new Error('rawQuery must be a non-empty string')
|
|
231
|
+
}
|
|
232
|
+
// Reject multi-statement payloads — only single SELECT / WITH statements.
|
|
233
|
+
// A trailing semicolon is tolerated but any content after it fails.
|
|
234
|
+
var trimmed = rawQuery.trim().replace(/;\\s*$/, '')
|
|
235
|
+
if (trimmed.indexOf(';') !== -1) {
|
|
236
|
+
throw new Error('rawQuery must contain exactly one statement')
|
|
237
|
+
}
|
|
238
|
+
for (var i = 0; i < BLOCKED_RAW_QUERY_PATTERNS.length; i++) {
|
|
239
|
+
if (BLOCKED_RAW_QUERY_PATTERNS[i].test(trimmed)) {
|
|
240
|
+
throw new Error('rawQuery contains a blocked statement')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default async function handler(req, res) {
|
|
246
|
+
const client = getClient()
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await client.connect()
|
|
250
|
+
${schema ? `await client.query('SET search_path TO ${schema}')` : ''}
|
|
251
|
+
|
|
252
|
+
// If the caller supplied a rawQuery, run it verbatim (after a safety
|
|
253
|
+
// guard). The schema-driven branch below builds \`SELECT * FROM ${tableName}\`
|
|
254
|
+
// with optional filters — it's the default read path and ignores
|
|
255
|
+
// rawQuery. Page-load workflows and other consumers that need a JOIN
|
|
256
|
+
// or a user-scoped filter pass their fully-rendered SQL here via
|
|
257
|
+
// \`fetchData({ rawQuery })\` and expect it to execute verbatim.
|
|
258
|
+
if (req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0) {
|
|
259
|
+
assertRawQuerySafe(req.query.rawQuery)
|
|
260
|
+
const rawResult = await client.query(req.query.rawQuery)
|
|
261
|
+
const rawRows = Array.isArray(rawResult?.rows) ? rawResult.rows : []
|
|
262
|
+
const rawPlain = rawRows.map((row) =>
|
|
263
|
+
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
264
|
+
)
|
|
265
|
+
const rawSafe = JSON.parse(JSON.stringify(rawPlain, dateReplacer))
|
|
266
|
+
return res.status(200).json({
|
|
267
|
+
success: true,
|
|
268
|
+
data: rawSafe,
|
|
269
|
+
timestamp: Date.now()
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query
|
|
274
|
+
|
|
275
|
+
const conditions = []
|
|
276
|
+
const queryParams = []
|
|
277
|
+
let paramIndex = 1
|
|
278
|
+
|
|
279
|
+
if (query) {
|
|
280
|
+
let columns = []
|
|
281
|
+
|
|
282
|
+
if (queryColumns) {
|
|
283
|
+
const parsed = safeJSONParse(queryColumns)
|
|
284
|
+
columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])
|
|
285
|
+
} else {
|
|
286
|
+
try {
|
|
287
|
+
const schemaQuery = \`
|
|
288
|
+
SELECT column_name
|
|
289
|
+
FROM information_schema.columns
|
|
290
|
+
WHERE table_name = $1
|
|
291
|
+
${schema ? `AND table_schema = $2` : ''}
|
|
292
|
+
ORDER BY ordinal_position
|
|
293
|
+
\`
|
|
294
|
+
const schemaParams = ${
|
|
295
|
+
schema
|
|
296
|
+
? `[${JSON.stringify(tableName)}, ${JSON.stringify(schema)}]`
|
|
297
|
+
: `[${JSON.stringify(tableName)}]`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const schemaResult = await client.query(schemaQuery, schemaParams)
|
|
301
|
+
columns = schemaResult.rows.map(row => row.column_name)
|
|
302
|
+
} catch (schemaError) {
|
|
303
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (columns.length > 0) {
|
|
308
|
+
const pattern = '%' + escapeLikePattern(query) + '%'
|
|
309
|
+
const placeholder = '$' + paramIndex
|
|
310
|
+
paramIndex++
|
|
311
|
+
queryParams.push(pattern)
|
|
312
|
+
const searchConditions = columns.map(
|
|
313
|
+
(col) => '"' + sanitizeSearchIdentifier(col) + '"::text ILIKE ' + placeholder + " ESCAPE '|'"
|
|
314
|
+
)
|
|
315
|
+
conditions.push('(' + searchConditions.join(' OR ') + ')')
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
|
|
320
|
+
|
|
321
|
+
let sql = \`SELECT * FROM ${tableName}\`
|
|
322
|
+
|
|
323
|
+
if (conditions.length > 0) {
|
|
324
|
+
sql += \` WHERE \${conditions.join(' AND ')}\`
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (sorts) {
|
|
328
|
+
const parsedSorts = safeJSONParse(sorts)
|
|
329
|
+
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
330
|
+
const orderClauses = parsedSorts.map((sort) => {
|
|
331
|
+
if (!sort.field) return null
|
|
332
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
333
|
+
return \`\${sort.field} \${order}\`
|
|
334
|
+
}).filter(Boolean)
|
|
335
|
+
|
|
336
|
+
if (orderClauses.length > 0) {
|
|
337
|
+
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} else if (sortBy) {
|
|
341
|
+
sql += \` ORDER BY \${sortBy} \${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}\`
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const limitValue = limit || perPage
|
|
345
|
+
const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
|
|
346
|
+
|
|
347
|
+
if (limitValue) {
|
|
348
|
+
sql += \` LIMIT \${limitValue}\`
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (offsetValue !== undefined) {
|
|
352
|
+
sql += \` OFFSET \${offsetValue}\`
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const result = await client.query(sql, queryParams)
|
|
356
|
+
const rows = Array.isArray(result?.rows) ? result.rows : []
|
|
357
|
+
const plainRows = rows.map((row) =>
|
|
358
|
+
row && typeof row.toJSON === 'function' ? row.toJSON() : row
|
|
359
|
+
)
|
|
360
|
+
const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))
|
|
361
|
+
${
|
|
362
|
+
getTransformExpression(tableName)
|
|
363
|
+
? `const transformedData = ${getTransformExpression(tableName)}`
|
|
364
|
+
: ''
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return res.status(200).json({
|
|
368
|
+
success: true,
|
|
369
|
+
data: ${getTransformExpression(tableName) ? 'transformedData' : 'safeData'},
|
|
370
|
+
timestamp: Date.now()
|
|
371
|
+
})
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error('Teleport DB fetch error:', error)
|
|
374
|
+
return res.status(500).json({
|
|
375
|
+
success: false,
|
|
376
|
+
error: error.message || 'Failed to fetch data',
|
|
377
|
+
timestamp: Date.now()
|
|
378
|
+
})
|
|
379
|
+
} finally {
|
|
380
|
+
if (client) {
|
|
381
|
+
try {
|
|
382
|
+
await client.end()
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error('Error closing database client:', error)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
`
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export const generateTeleportCountFetcher = (
|
|
393
|
+
config: Record<string, unknown>,
|
|
394
|
+
tableName: string
|
|
395
|
+
): string => {
|
|
396
|
+
const dbConfig = config as TeleportDBConfig
|
|
397
|
+
const hasSchema = !!dbConfig.options?.schema
|
|
398
|
+
|
|
399
|
+
return `
|
|
400
|
+
async function getCount(req, res) {
|
|
401
|
+
const client = getClient()
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
await client.connect()
|
|
405
|
+
const { query, queryColumns, filters } = req.query
|
|
406
|
+
const conditions = []
|
|
407
|
+
const queryParams = []
|
|
408
|
+
let paramIndex = 1
|
|
409
|
+
|
|
410
|
+
if (query) {
|
|
411
|
+
let columns = []
|
|
412
|
+
|
|
413
|
+
if (queryColumns) {
|
|
414
|
+
const parsed = safeJSONParse(queryColumns)
|
|
415
|
+
columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
416
|
+
} else {
|
|
417
|
+
try {
|
|
418
|
+
const schemaQuery = \`
|
|
419
|
+
SELECT column_name
|
|
420
|
+
FROM information_schema.columns
|
|
421
|
+
WHERE table_name = $1
|
|
422
|
+
${hasSchema ? `AND table_schema = $2` : ''}
|
|
423
|
+
ORDER BY ordinal_position
|
|
424
|
+
\`
|
|
425
|
+
const schemaParams = ${
|
|
426
|
+
hasSchema
|
|
427
|
+
? `[${JSON.stringify(tableName)}, ${JSON.stringify(dbConfig.options!.schema)}]`
|
|
428
|
+
: `[${JSON.stringify(tableName)}]`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const schemaResult = await client.query(schemaQuery, schemaParams)
|
|
432
|
+
columns = schemaResult.rows.map(row => row.column_name)
|
|
433
|
+
} catch (schemaError) {
|
|
434
|
+
console.warn('Failed to fetch column names from information_schema:', schemaError.message)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (columns.length > 0) {
|
|
439
|
+
const pattern = '%' + escapeLikePattern(query) + '%'
|
|
440
|
+
const placeholder = '$' + paramIndex
|
|
441
|
+
paramIndex++
|
|
442
|
+
queryParams.push(pattern)
|
|
443
|
+
const searchConditions = columns
|
|
444
|
+
.map(
|
|
445
|
+
(col) => '"' + sanitizeSearchIdentifier(col) + '"::text ILIKE ' + placeholder + " ESCAPE '|'"
|
|
446
|
+
)
|
|
447
|
+
.join(' OR ')
|
|
448
|
+
conditions.push('(' + searchConditions + ')')
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
paramIndex = processFilters(filters, conditions, queryParams, paramIndex)
|
|
453
|
+
|
|
454
|
+
let countSql = \`SELECT COUNT(*) FROM ${tableName}\`
|
|
455
|
+
if (conditions.length > 0) {
|
|
456
|
+
countSql += \` WHERE \${conditions.join(' AND ')}\`
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const result = await client.query(countSql, queryParams)
|
|
460
|
+
const count = parseInt(result.rows[0].count, 10)
|
|
461
|
+
|
|
462
|
+
return res.status(200).json({
|
|
463
|
+
success: true,
|
|
464
|
+
count: count,
|
|
465
|
+
timestamp: Date.now()
|
|
466
|
+
})
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error('Error getting count:', error)
|
|
469
|
+
return res.status(500).json({
|
|
470
|
+
success: false,
|
|
471
|
+
error: error.message || 'Failed to get count',
|
|
472
|
+
timestamp: Date.now()
|
|
473
|
+
})
|
|
474
|
+
} finally {
|
|
475
|
+
if (client) {
|
|
476
|
+
try {
|
|
477
|
+
await client.end()
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error('Error closing database client:', error)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
`
|
|
485
|
+
}
|
package/src/fetchers/turso.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 validateTursoConfig = (
|
|
@@ -41,6 +42,8 @@ export const generateTursoFetcher = (
|
|
|
41
42
|
|
|
42
43
|
${generateSafeJSONParseCode()}
|
|
43
44
|
|
|
45
|
+
${generateSearchEscapeHelpersCode()}
|
|
46
|
+
|
|
44
47
|
${generateDateFormatterCode()}
|
|
45
48
|
|
|
46
49
|
export default async function handler(req, res) {
|
|
@@ -62,11 +65,16 @@ export default async function handler(req, res) {
|
|
|
62
65
|
if (queryColumns) {
|
|
63
66
|
const parsed = safeJSONParse(queryColumns)
|
|
64
67
|
const columns = Array.isArray(parsed) ? parsed : [parsed]
|
|
65
|
-
// Cast columns to TEXT
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
// Cast columns to TEXT and LOWER both sides so the match is
|
|
69
|
+
// case-insensitive regardless of SQLite collation.
|
|
70
|
+
const pattern = "%" + escapeLikePattern(query) + "%"
|
|
71
|
+
const searchConditions = columns.map(
|
|
72
|
+
(col) =>
|
|
73
|
+
'LOWER(CAST("' + sanitizeSearchIdentifier(col) + '" AS TEXT)) LIKE LOWER(?) ESCAPE ' + "'|'"
|
|
74
|
+
)
|
|
75
|
+
whereClauses.push("(" + searchConditions.join(" OR ") + ")")
|
|
68
76
|
columns.forEach(() => {
|
|
69
|
-
queryParams.push(
|
|
77
|
+
queryParams.push(pattern)
|
|
70
78
|
})
|
|
71
79
|
} else {
|
|
72
80
|
// Store query for post-filtering if columns not specified
|
|
@@ -143,16 +151,16 @@ export default async function handler(req, res) {
|
|
|
143
151
|
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
|
|
144
152
|
const orderClauses = parsedSorts.map((sort) => {
|
|
145
153
|
if (!sort.field) return null
|
|
146
|
-
const order = sort.order
|
|
154
|
+
const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
147
155
|
return \`\${sanitizeIdentifier(sort.field)} \${order}\`
|
|
148
156
|
}).filter(Boolean)
|
|
149
|
-
|
|
157
|
+
|
|
150
158
|
if (orderClauses.length > 0) {
|
|
151
159
|
sql += \` ORDER BY \${orderClauses.join(', ')}\`
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
} else if (sortBy) {
|
|
155
|
-
const sortOrderValue = sortOrder
|
|
163
|
+
const sortOrderValue = (sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'
|
|
156
164
|
sql += \` ORDER BY \${sanitizeIdentifier(sortBy)} \${sortOrderValue}\`
|
|
157
165
|
}
|
|
158
166
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
|
|
3
|
+
// Build a single filter entry: { source: '<src>', destination: <destExpr>, operand: '<op>' }
|
|
4
|
+
const buildFilterEntry = (
|
|
5
|
+
source: string,
|
|
6
|
+
destinationExpr: types.Expression,
|
|
7
|
+
operand: string
|
|
8
|
+
): types.ObjectExpression =>
|
|
9
|
+
types.objectExpression([
|
|
10
|
+
types.objectProperty(types.identifier('source'), types.stringLiteral(source)),
|
|
11
|
+
types.objectProperty(types.identifier('destination'), destinationExpr),
|
|
12
|
+
types.objectProperty(types.identifier('operand'), types.stringLiteral(operand)),
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
// Predicate `(__f) => __f.destination !== '' && __f.destination !== null && __f.destination !== undefined`
|
|
16
|
+
// Used at runtime to drop filter conditions whose destination resolves to an
|
|
17
|
+
// empty value. Keeps the page generic: state-bound filters (e.g.
|
|
18
|
+
// `selectedCategory === ''` after picking "All Categories") and url-bound
|
|
19
|
+
// filters (e.g. no `?categoryFilter=` in URL → `router?.query?.categoryFilter`
|
|
20
|
+
// is `undefined`) both fall away cleanly instead of being sent to the API as
|
|
21
|
+
// `destination: ''`, which the SQL backend interprets as a literal empty
|
|
22
|
+
// string and returns zero rows.
|
|
23
|
+
const buildNonEmptyDestinationPredicate = (): types.ArrowFunctionExpression => {
|
|
24
|
+
const f = types.identifier('__f')
|
|
25
|
+
const dest = types.memberExpression(f, types.identifier('destination'))
|
|
26
|
+
return types.arrowFunctionExpression(
|
|
27
|
+
[f],
|
|
28
|
+
types.logicalExpression(
|
|
29
|
+
'&&',
|
|
30
|
+
types.logicalExpression(
|
|
31
|
+
'&&',
|
|
32
|
+
types.binaryExpression('!==', dest, types.stringLiteral('')),
|
|
33
|
+
types.binaryExpression('!==', dest, types.nullLiteral())
|
|
34
|
+
),
|
|
35
|
+
types.binaryExpression('!==', dest, types.identifier('undefined'))
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Build the AST for:
|
|
41
|
+
// JSON.stringify(
|
|
42
|
+
// [ {source, destination, operand}, ... ]
|
|
43
|
+
// .filter(__f => __f.destination !== '' && __f.destination !== null && __f.destination !== undefined)
|
|
44
|
+
// )
|
|
45
|
+
//
|
|
46
|
+
// The `.filter(...)` step is unconditional even when every entry is a static
|
|
47
|
+
// (always-truthy) destination — the cost is negligible at runtime and it
|
|
48
|
+
// keeps the generated code uniform, so future regressions where a new filter
|
|
49
|
+
// type is added but its empty-value behavior is forgotten don't silently
|
|
50
|
+
// re-introduce empty-string filters.
|
|
51
|
+
export const buildFiltersStringifyCall = (
|
|
52
|
+
filters: Array<{ source?: string; operand?: string; destination: unknown }>,
|
|
53
|
+
buildDestinationExpression: (destination: unknown) => types.Expression
|
|
54
|
+
): types.CallExpression => {
|
|
55
|
+
const entries = filters.map((filter) =>
|
|
56
|
+
buildFilterEntry(
|
|
57
|
+
filter.source || '',
|
|
58
|
+
buildDestinationExpression(filter.destination),
|
|
59
|
+
filter.operand || ''
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const filteredArray = types.callExpression(
|
|
64
|
+
types.memberExpression(types.arrayExpression(entries), types.identifier('filter')),
|
|
65
|
+
[buildNonEmptyDestinationPredicate()]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return types.callExpression(
|
|
69
|
+
types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
|
|
70
|
+
[filteredArray]
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Convenience: push `filters: <call>` onto a paramsProps array.
|
|
75
|
+
export const appendFiltersParam = (
|
|
76
|
+
paramsProps: types.ObjectProperty[],
|
|
77
|
+
filters: Array<{ source?: string; operand?: string; destination: unknown }> | undefined,
|
|
78
|
+
buildDestinationExpression: (destination: unknown) => types.Expression
|
|
79
|
+
): void => {
|
|
80
|
+
if (!filters || filters.length === 0) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
paramsProps.push(
|
|
84
|
+
types.objectProperty(
|
|
85
|
+
types.identifier('filters'),
|
|
86
|
+
buildFiltersStringifyCall(filters, buildDestinationExpression)
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Appends each id in `stateIds` as a bare `Identifier` onto `deps`, skipping
|
|
92
|
+
// any id already tracked in `seen`. Mutates both `deps` and `seen`.
|
|
93
|
+
//
|
|
94
|
+
// Used by every count-fetch and params-`useMemo` builder in the pagination
|
|
95
|
+
// plugin to wire state-bound filter destinations into React's deps array.
|
|
96
|
+
// Centralising the loop here means a new dep-source (sort, future filter
|
|
97
|
+
// shapes) can hook into the same dedup-by-name set without re-implementing
|
|
98
|
+
// it at four call sites.
|
|
99
|
+
export const pushStateIdsAsDeps = (
|
|
100
|
+
deps: types.Expression[],
|
|
101
|
+
seen: Set<string>,
|
|
102
|
+
stateIds: string[]
|
|
103
|
+
): void => {
|
|
104
|
+
for (const id of stateIds) {
|
|
105
|
+
if (seen.has(id)) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
seen.add(id)
|
|
109
|
+
deps.push(types.identifier(id))
|
|
110
|
+
}
|
|
111
|
+
}
|