@teleporthq/teleport-plugin-next-data-source 0.40.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/ARRAY_MAPPER_PAGINATION.md +1128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +40 -0
  4. package/SEARCH_IMPLEMENTATION_SUMMARY.md +983 -0
  5. package/__tests__/fetchers.test.ts +545 -0
  6. package/__tests__/integration.test.ts +561 -0
  7. package/__tests__/mocks.ts +241 -0
  8. package/__tests__/pagination.test.ts +31 -0
  9. package/__tests__/plugin.test.ts +577 -0
  10. package/__tests__/utils.test.ts +430 -0
  11. package/__tests__/validation.test.ts +348 -0
  12. package/dist/cjs/array-mapper-pagination.d.ts +32 -0
  13. package/dist/cjs/array-mapper-pagination.d.ts.map +1 -0
  14. package/dist/cjs/array-mapper-pagination.js +77 -0
  15. package/dist/cjs/array-mapper-pagination.js.map +1 -0
  16. package/dist/cjs/count-fetchers.d.ts +12 -0
  17. package/dist/cjs/count-fetchers.d.ts.map +1 -0
  18. package/dist/cjs/count-fetchers.js +46 -0
  19. package/dist/cjs/count-fetchers.js.map +1 -0
  20. package/dist/cjs/data-source-fetchers.d.ts +14 -0
  21. package/dist/cjs/data-source-fetchers.d.ts.map +1 -0
  22. package/dist/cjs/data-source-fetchers.js +185 -0
  23. package/dist/cjs/data-source-fetchers.js.map +1 -0
  24. package/dist/cjs/fetchers/airtable.d.ts +6 -0
  25. package/dist/cjs/fetchers/airtable.d.ts.map +1 -0
  26. package/dist/cjs/fetchers/airtable.js +27 -0
  27. package/dist/cjs/fetchers/airtable.js.map +1 -0
  28. package/dist/cjs/fetchers/clickhouse.d.ts +6 -0
  29. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -0
  30. package/dist/cjs/fetchers/clickhouse.js +29 -0
  31. package/dist/cjs/fetchers/clickhouse.js.map +1 -0
  32. package/dist/cjs/fetchers/csv-file.d.ts +7 -0
  33. package/dist/cjs/fetchers/csv-file.d.ts.map +1 -0
  34. package/dist/cjs/fetchers/csv-file.js +36 -0
  35. package/dist/cjs/fetchers/csv-file.js.map +1 -0
  36. package/dist/cjs/fetchers/firestore.d.ts +6 -0
  37. package/dist/cjs/fetchers/firestore.d.ts.map +1 -0
  38. package/dist/cjs/fetchers/firestore.js +35 -0
  39. package/dist/cjs/fetchers/firestore.js.map +1 -0
  40. package/dist/cjs/fetchers/google-sheets.d.ts +6 -0
  41. package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -0
  42. package/dist/cjs/fetchers/google-sheets.js +30 -0
  43. package/dist/cjs/fetchers/google-sheets.js.map +1 -0
  44. package/dist/cjs/fetchers/index.d.ts +17 -0
  45. package/dist/cjs/fetchers/index.d.ts.map +1 -0
  46. package/dist/cjs/fetchers/index.js +56 -0
  47. package/dist/cjs/fetchers/index.js.map +1 -0
  48. package/dist/cjs/fetchers/javascript.d.ts +7 -0
  49. package/dist/cjs/fetchers/javascript.d.ts.map +1 -0
  50. package/dist/cjs/fetchers/javascript.js +40 -0
  51. package/dist/cjs/fetchers/javascript.js.map +1 -0
  52. package/dist/cjs/fetchers/mariadb.d.ts +3 -0
  53. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -0
  54. package/dist/cjs/fetchers/mariadb.js +23 -0
  55. package/dist/cjs/fetchers/mariadb.js.map +1 -0
  56. package/dist/cjs/fetchers/mongodb.d.ts +7 -0
  57. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -0
  58. package/dist/cjs/fetchers/mongodb.js +52 -0
  59. package/dist/cjs/fetchers/mongodb.js.map +1 -0
  60. package/dist/cjs/fetchers/mysql.d.ts +3 -0
  61. package/dist/cjs/fetchers/mysql.d.ts.map +1 -0
  62. package/dist/cjs/fetchers/mysql.js +30 -0
  63. package/dist/cjs/fetchers/mysql.js.map +1 -0
  64. package/dist/cjs/fetchers/postgresql.d.ts +3 -0
  65. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -0
  66. package/dist/cjs/fetchers/postgresql.js +25 -0
  67. package/dist/cjs/fetchers/postgresql.js.map +1 -0
  68. package/dist/cjs/fetchers/redis.d.ts +6 -0
  69. package/dist/cjs/fetchers/redis.d.ts.map +1 -0
  70. package/dist/cjs/fetchers/redis.js +46 -0
  71. package/dist/cjs/fetchers/redis.js.map +1 -0
  72. package/dist/cjs/fetchers/redshift.d.ts +2 -0
  73. package/dist/cjs/fetchers/redshift.d.ts.map +1 -0
  74. package/dist/cjs/fetchers/redshift.js +24 -0
  75. package/dist/cjs/fetchers/redshift.js.map +1 -0
  76. package/dist/cjs/fetchers/rest-api.d.ts +6 -0
  77. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -0
  78. package/dist/cjs/fetchers/rest-api.js +58 -0
  79. package/dist/cjs/fetchers/rest-api.js.map +1 -0
  80. package/dist/cjs/fetchers/static-collection.d.ts +7 -0
  81. package/dist/cjs/fetchers/static-collection.d.ts.map +1 -0
  82. package/dist/cjs/fetchers/static-collection.js +24 -0
  83. package/dist/cjs/fetchers/static-collection.js.map +1 -0
  84. package/dist/cjs/fetchers/supabase.d.ts +7 -0
  85. package/dist/cjs/fetchers/supabase.d.ts.map +1 -0
  86. package/dist/cjs/fetchers/supabase.js +42 -0
  87. package/dist/cjs/fetchers/supabase.js.map +1 -0
  88. package/dist/cjs/fetchers/turso.d.ts +6 -0
  89. package/dist/cjs/fetchers/turso.d.ts.map +1 -0
  90. package/dist/cjs/fetchers/turso.js +25 -0
  91. package/dist/cjs/fetchers/turso.js.map +1 -0
  92. package/dist/cjs/index.d.ts +9 -0
  93. package/dist/cjs/index.d.ts.map +1 -0
  94. package/dist/cjs/index.js +325 -0
  95. package/dist/cjs/index.js.map +1 -0
  96. package/dist/cjs/pagination-plugin.d.ts +5 -0
  97. package/dist/cjs/pagination-plugin.d.ts.map +1 -0
  98. package/dist/cjs/pagination-plugin.js +1484 -0
  99. package/dist/cjs/pagination-plugin.js.map +1 -0
  100. package/dist/cjs/pagination-with-count.d.ts +6 -0
  101. package/dist/cjs/pagination-with-count.d.ts.map +1 -0
  102. package/dist/cjs/pagination-with-count.js +63 -0
  103. package/dist/cjs/pagination-with-count.js.map +1 -0
  104. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  105. package/dist/cjs/utils.d.ts +31 -0
  106. package/dist/cjs/utils.d.ts.map +1 -0
  107. package/dist/cjs/utils.js +763 -0
  108. package/dist/cjs/utils.js.map +1 -0
  109. package/dist/cjs/validation.d.ts +5 -0
  110. package/dist/cjs/validation.d.ts.map +1 -0
  111. package/dist/cjs/validation.js +29 -0
  112. package/dist/cjs/validation.js.map +1 -0
  113. package/dist/esm/array-mapper-pagination.d.ts +32 -0
  114. package/dist/esm/array-mapper-pagination.d.ts.map +1 -0
  115. package/dist/esm/array-mapper-pagination.js +72 -0
  116. package/dist/esm/array-mapper-pagination.js.map +1 -0
  117. package/dist/esm/count-fetchers.d.ts +12 -0
  118. package/dist/esm/count-fetchers.d.ts.map +1 -0
  119. package/dist/esm/count-fetchers.js +35 -0
  120. package/dist/esm/count-fetchers.js.map +1 -0
  121. package/dist/esm/data-source-fetchers.d.ts +14 -0
  122. package/dist/esm/data-source-fetchers.d.ts.map +1 -0
  123. package/dist/esm/data-source-fetchers.js +179 -0
  124. package/dist/esm/data-source-fetchers.js.map +1 -0
  125. package/dist/esm/fetchers/airtable.d.ts +6 -0
  126. package/dist/esm/fetchers/airtable.d.ts.map +1 -0
  127. package/dist/esm/fetchers/airtable.js +22 -0
  128. package/dist/esm/fetchers/airtable.js.map +1 -0
  129. package/dist/esm/fetchers/clickhouse.d.ts +6 -0
  130. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -0
  131. package/dist/esm/fetchers/clickhouse.js +24 -0
  132. package/dist/esm/fetchers/clickhouse.js.map +1 -0
  133. package/dist/esm/fetchers/csv-file.d.ts +7 -0
  134. package/dist/esm/fetchers/csv-file.d.ts.map +1 -0
  135. package/dist/esm/fetchers/csv-file.js +30 -0
  136. package/dist/esm/fetchers/csv-file.js.map +1 -0
  137. package/dist/esm/fetchers/firestore.d.ts +6 -0
  138. package/dist/esm/fetchers/firestore.d.ts.map +1 -0
  139. package/dist/esm/fetchers/firestore.js +30 -0
  140. package/dist/esm/fetchers/firestore.js.map +1 -0
  141. package/dist/esm/fetchers/google-sheets.d.ts +6 -0
  142. package/dist/esm/fetchers/google-sheets.d.ts.map +1 -0
  143. package/dist/esm/fetchers/google-sheets.js +25 -0
  144. package/dist/esm/fetchers/google-sheets.js.map +1 -0
  145. package/dist/esm/fetchers/index.d.ts +17 -0
  146. package/dist/esm/fetchers/index.d.ts.map +1 -0
  147. package/dist/esm/fetchers/index.js +17 -0
  148. package/dist/esm/fetchers/index.js.map +1 -0
  149. package/dist/esm/fetchers/javascript.d.ts +7 -0
  150. package/dist/esm/fetchers/javascript.d.ts.map +1 -0
  151. package/dist/esm/fetchers/javascript.js +34 -0
  152. package/dist/esm/fetchers/javascript.js.map +1 -0
  153. package/dist/esm/fetchers/mariadb.d.ts +3 -0
  154. package/dist/esm/fetchers/mariadb.d.ts.map +1 -0
  155. package/dist/esm/fetchers/mariadb.js +18 -0
  156. package/dist/esm/fetchers/mariadb.js.map +1 -0
  157. package/dist/esm/fetchers/mongodb.d.ts +7 -0
  158. package/dist/esm/fetchers/mongodb.d.ts.map +1 -0
  159. package/dist/esm/fetchers/mongodb.js +46 -0
  160. package/dist/esm/fetchers/mongodb.js.map +1 -0
  161. package/dist/esm/fetchers/mysql.d.ts +3 -0
  162. package/dist/esm/fetchers/mysql.d.ts.map +1 -0
  163. package/dist/esm/fetchers/mysql.js +25 -0
  164. package/dist/esm/fetchers/mysql.js.map +1 -0
  165. package/dist/esm/fetchers/postgresql.d.ts +3 -0
  166. package/dist/esm/fetchers/postgresql.d.ts.map +1 -0
  167. package/dist/esm/fetchers/postgresql.js +20 -0
  168. package/dist/esm/fetchers/postgresql.js.map +1 -0
  169. package/dist/esm/fetchers/redis.d.ts +6 -0
  170. package/dist/esm/fetchers/redis.d.ts.map +1 -0
  171. package/dist/esm/fetchers/redis.js +41 -0
  172. package/dist/esm/fetchers/redis.js.map +1 -0
  173. package/dist/esm/fetchers/redshift.d.ts +2 -0
  174. package/dist/esm/fetchers/redshift.d.ts.map +1 -0
  175. package/dist/esm/fetchers/redshift.js +20 -0
  176. package/dist/esm/fetchers/redshift.js.map +1 -0
  177. package/dist/esm/fetchers/rest-api.d.ts +6 -0
  178. package/dist/esm/fetchers/rest-api.d.ts.map +1 -0
  179. package/dist/esm/fetchers/rest-api.js +53 -0
  180. package/dist/esm/fetchers/rest-api.js.map +1 -0
  181. package/dist/esm/fetchers/static-collection.d.ts +7 -0
  182. package/dist/esm/fetchers/static-collection.d.ts.map +1 -0
  183. package/dist/esm/fetchers/static-collection.js +18 -0
  184. package/dist/esm/fetchers/static-collection.js.map +1 -0
  185. package/dist/esm/fetchers/supabase.d.ts +7 -0
  186. package/dist/esm/fetchers/supabase.d.ts.map +1 -0
  187. package/dist/esm/fetchers/supabase.js +36 -0
  188. package/dist/esm/fetchers/supabase.js.map +1 -0
  189. package/dist/esm/fetchers/turso.d.ts +6 -0
  190. package/dist/esm/fetchers/turso.d.ts.map +1 -0
  191. package/dist/esm/fetchers/turso.js +20 -0
  192. package/dist/esm/fetchers/turso.js.map +1 -0
  193. package/dist/esm/index.d.ts +9 -0
  194. package/dist/esm/index.d.ts.map +1 -0
  195. package/dist/esm/index.js +306 -0
  196. package/dist/esm/index.js.map +1 -0
  197. package/dist/esm/pagination-plugin.d.ts +5 -0
  198. package/dist/esm/pagination-plugin.d.ts.map +1 -0
  199. package/dist/esm/pagination-plugin.js +1457 -0
  200. package/dist/esm/pagination-plugin.js.map +1 -0
  201. package/dist/esm/pagination-with-count.d.ts +6 -0
  202. package/dist/esm/pagination-with-count.d.ts.map +1 -0
  203. package/dist/esm/pagination-with-count.js +34 -0
  204. package/dist/esm/pagination-with-count.js.map +1 -0
  205. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  206. package/dist/esm/utils.d.ts +31 -0
  207. package/dist/esm/utils.d.ts.map +1 -0
  208. package/dist/esm/utils.js +722 -0
  209. package/dist/esm/utils.js.map +1 -0
  210. package/dist/esm/validation.d.ts +5 -0
  211. package/dist/esm/validation.d.ts.map +1 -0
  212. package/dist/esm/validation.js +25 -0
  213. package/dist/esm/validation.js.map +1 -0
  214. package/package.json +33 -0
  215. package/src/array-mapper-pagination.ts +113 -0
  216. package/src/count-fetchers.ts +99 -0
  217. package/src/data-source-fetchers.ts +313 -0
  218. package/src/fetchers/airtable.ts +153 -0
  219. package/src/fetchers/clickhouse.ts +127 -0
  220. package/src/fetchers/csv-file.ts +163 -0
  221. package/src/fetchers/firestore.ts +138 -0
  222. package/src/fetchers/google-sheets.ts +189 -0
  223. package/src/fetchers/index.ts +32 -0
  224. package/src/fetchers/javascript.ts +150 -0
  225. package/src/fetchers/mariadb.ts +230 -0
  226. package/src/fetchers/mongodb.ts +239 -0
  227. package/src/fetchers/mysql.ts +237 -0
  228. package/src/fetchers/postgresql.ts +247 -0
  229. package/src/fetchers/redis.ts +152 -0
  230. package/src/fetchers/redshift.ts +138 -0
  231. package/src/fetchers/rest-api.ts +148 -0
  232. package/src/fetchers/static-collection.ts +149 -0
  233. package/src/fetchers/supabase.ts +246 -0
  234. package/src/fetchers/turso.ts +131 -0
  235. package/src/index.ts +352 -0
  236. package/src/pagination-plugin.ts +2335 -0
  237. package/src/pagination-with-count.ts +89 -0
  238. package/src/utils.ts +1013 -0
  239. package/src/validation.ts +32 -0
  240. package/tsconfig.json +9 -0
@@ -0,0 +1,247 @@
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
+ options?: { schema?: string }
13
+ }
14
+
15
+ export const generatePostgreSQLFetcher = (
16
+ config: Record<string, unknown>,
17
+ tableName: string
18
+ ): string => {
19
+ const pgConfig = config as PostgreSQLConfig
20
+ const schema = pgConfig.options?.schema
21
+
22
+ return `import { Pool } from 'pg'
23
+
24
+ let pool = null
25
+
26
+ const getPool = () => {
27
+ if (pool) return pool
28
+
29
+ pool = new Pool({
30
+ host: ${JSON.stringify(pgConfig.host)},
31
+ port: ${pgConfig.port || 5432},
32
+ user: ${JSON.stringify(pgConfig.user || pgConfig.username)},
33
+ password: ${replaceSecretReference(pgConfig.password)},
34
+ database: ${JSON.stringify(pgConfig.database)},
35
+ ssl: ${
36
+ pgConfig.ssl === false
37
+ ? 'false'
38
+ : pgConfig.sslConfig
39
+ ? `{
40
+ ${pgConfig.sslConfig.ca ? `ca: ${replaceSecretReference(pgConfig.sslConfig.ca)},` : ''}
41
+ ${pgConfig.sslConfig.cert ? `cert: ${replaceSecretReference(pgConfig.sslConfig.cert)},` : ''}
42
+ ${pgConfig.sslConfig.key ? `key: ${replaceSecretReference(pgConfig.sslConfig.key)},` : ''}
43
+ rejectUnauthorized: false
44
+ }`
45
+ : '{ rejectUnauthorized: false }'
46
+ }
47
+ })
48
+
49
+ return pool
50
+ }
51
+
52
+ export default async function handler(req, res) {
53
+ try {
54
+ const pool = getPool()
55
+ ${schema ? `await pool.query('SET search_path TO ${schema}')` : ''}
56
+
57
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
58
+
59
+ const conditions = []
60
+ const queryParams = []
61
+ let paramIndex = 1
62
+
63
+ if (query) {
64
+ let columns = []
65
+
66
+ if (queryColumns) {
67
+ // Use specified columns
68
+ columns = JSON.parse(queryColumns)
69
+ } else {
70
+ // Fallback: Get all columns from information_schema
71
+ try {
72
+ const schemaQuery = \`
73
+ SELECT column_name
74
+ FROM information_schema.columns
75
+ WHERE table_name = $1
76
+ ${schema ? `AND table_schema = $2` : ''}
77
+ ORDER BY ordinal_position
78
+ \`
79
+ const schemaParams = schema
80
+ ? [${JSON.stringify(tableName)}, ${JSON.stringify(schema)}]
81
+ : [${JSON.stringify(tableName)}]
82
+
83
+ const schemaResult = await pool.query(schemaQuery, schemaParams)
84
+ columns = schemaResult.rows.map(row => row.column_name)
85
+ } catch (schemaError) {
86
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
87
+ // Continue without search if we can't get columns
88
+ }
89
+ }
90
+
91
+ if (columns.length > 0) {
92
+ const searchConditions = columns.map((col) => {
93
+ const condition = \`\${col}::text ILIKE $\${paramIndex}\`
94
+ paramIndex++
95
+ return condition
96
+ })
97
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
98
+ conditions.push(\`(\${searchConditions.join(' OR ')})\`)
99
+ }
100
+ }
101
+
102
+ if (filters) {
103
+ const parsedFilters = JSON.parse(filters)
104
+ Object.entries(parsedFilters).forEach(([key, value]) => {
105
+ if (Array.isArray(value)) {
106
+ const placeholders = value.map(() => \`$\${paramIndex++}\`)
107
+ queryParams.push(...value)
108
+ conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
109
+ } else {
110
+ conditions.push(\`\${key} = $\${paramIndex}\`)
111
+ queryParams.push(value)
112
+ paramIndex++
113
+ }
114
+ })
115
+ }
116
+
117
+ let sql = \`SELECT * FROM ${tableName}\`
118
+
119
+ if (conditions.length > 0) {
120
+ sql += \` WHERE \${conditions.join(' AND ')}\`
121
+ }
122
+
123
+ if (sortBy) {
124
+ sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
125
+ }
126
+
127
+ const limitValue = limit || perPage
128
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
129
+
130
+ if (limitValue) {
131
+ sql += \` LIMIT \${limitValue}\`
132
+ }
133
+
134
+ if (offsetValue !== undefined) {
135
+ sql += \` OFFSET \${offsetValue}\`
136
+ }
137
+
138
+ const result = await pool.query(sql, queryParams)
139
+ const rows = Array.isArray(result?.rows) ? result.rows : []
140
+ const plainRows = rows.map((row) =>
141
+ row && typeof row.toJSON === 'function' ? row.toJSON() : row
142
+ )
143
+ const safeData = JSON.parse(JSON.stringify(plainRows))
144
+
145
+ return res.status(200).json({
146
+ success: true,
147
+ data: safeData,
148
+ timestamp: Date.now()
149
+ })
150
+ } catch (error) {
151
+ console.error('PostgreSQL fetch error:', error)
152
+ return res.status(500).json({
153
+ success: false,
154
+ error: error.message || 'Failed to fetch data',
155
+ timestamp: Date.now()
156
+ })
157
+ }
158
+ }
159
+ `
160
+ }
161
+
162
+ export const generatePostgreSQLCountFetcher = (
163
+ config: Record<string, unknown>,
164
+ tableName: string
165
+ ): string => {
166
+ const pgConfig = config as PostgreSQLConfig
167
+ const hasSchema = !!pgConfig.options?.schema
168
+
169
+ return `
170
+ async function getCount(req, res) {
171
+ const pool = getPool()
172
+
173
+ try {
174
+ const { query, queryColumns, filters } = req.query
175
+ const conditions = []
176
+ const queryParams = []
177
+ let paramIndex = 1
178
+
179
+ if (query) {
180
+ let columns = []
181
+
182
+ if (queryColumns) {
183
+ // Use specified columns
184
+ columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
185
+ } else {
186
+ // Fallback: Get all columns from information_schema
187
+ try {
188
+ const schemaQuery = \`
189
+ SELECT column_name
190
+ FROM information_schema.columns
191
+ WHERE table_name = $1
192
+ ${hasSchema ? `AND table_schema = $2` : ''}
193
+ ORDER BY ordinal_position
194
+ \`
195
+ const schemaParams = ${
196
+ hasSchema
197
+ ? `[${JSON.stringify(tableName)}, ${JSON.stringify(pgConfig.options!.schema)}]`
198
+ : `[${JSON.stringify(tableName)}]`
199
+ }
200
+
201
+ const schemaResult = await pool.query(schemaQuery, schemaParams)
202
+ columns = schemaResult.rows.map(row => row.column_name)
203
+ } catch (schemaError) {
204
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
205
+ // Continue without search if we can't get columns
206
+ }
207
+ }
208
+
209
+ if (columns.length > 0) {
210
+ const searchConditions = columns.map(col => \`\${col}::text ILIKE $\${paramIndex++}\`).join(' OR ')
211
+ conditions.push(\`(\${searchConditions})\`)
212
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
213
+ }
214
+ }
215
+
216
+ if (filters) {
217
+ const parsedFilters = JSON.parse(filters)
218
+ for (const filter of parsedFilters) {
219
+ conditions.push(\`\${filter.column} \${filter.operator} $\${paramIndex++}\`)
220
+ queryParams.push(filter.value)
221
+ }
222
+ }
223
+
224
+ let countSql = \`SELECT COUNT(*) FROM ${tableName}\`
225
+ if (conditions.length > 0) {
226
+ countSql += \` WHERE \${conditions.join(' AND ')}\`
227
+ }
228
+
229
+ const result = await pool.query(countSql, queryParams)
230
+ const count = parseInt(result.rows[0].count, 10)
231
+
232
+ return res.status(200).json({
233
+ success: true,
234
+ count: count,
235
+ timestamp: Date.now()
236
+ })
237
+ } catch (error) {
238
+ console.error('Error getting count:', error)
239
+ return res.status(500).json({
240
+ success: false,
241
+ error: error.message || 'Failed to get count',
242
+ timestamp: Date.now()
243
+ })
244
+ }
245
+ }
246
+ `
247
+ }
@@ -0,0 +1,152 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ export const validateRedisConfig = (
4
+ config: Record<string, unknown>
5
+ ): { isValid: boolean; error?: string } => {
6
+ if (!config || typeof config !== 'object') {
7
+ return { isValid: false, error: 'Config must be a valid object' }
8
+ }
9
+
10
+ // If connectionString is provided, validate it
11
+ if (config.connectionString) {
12
+ if (typeof config.connectionString !== 'string' || config.connectionString.trim() === '') {
13
+ return { isValid: false, error: 'Connection string must be a non-empty string' }
14
+ }
15
+
16
+ // Only validate format if it's not a secret reference that will be resolved at runtime
17
+ const connStr = config.connectionString as string
18
+ if (
19
+ !connStr.startsWith('teleporthq.secrets.') &&
20
+ !connStr.startsWith('redis://') &&
21
+ !connStr.startsWith('rediss://')
22
+ ) {
23
+ return { isValid: false, error: 'Invalid Redis connection string format' }
24
+ }
25
+
26
+ return { isValid: true }
27
+ }
28
+
29
+ // If no connectionString, host/port/etc will be used to build one
30
+ if (!config.host || typeof config.host !== 'string') {
31
+ return { isValid: false, error: 'Redis host is required when connectionString is not provided' }
32
+ }
33
+
34
+ return { isValid: true }
35
+ }
36
+
37
+ interface RedisConfig {
38
+ connectionString?: string
39
+ host?: string
40
+ port?: number
41
+ database?: number
42
+ password?: string
43
+ username?: string
44
+ selectedTables?: Record<string, unknown>
45
+ }
46
+
47
+ export const generateRedisFetcher = (config: Record<string, unknown>): string => {
48
+ const redisConfig = config as RedisConfig
49
+ const host = redisConfig.host
50
+ const port = redisConfig.port
51
+ const username = redisConfig.username
52
+ const password = redisConfig.password
53
+ const database = redisConfig.database
54
+ const hasUsername = username
55
+
56
+ // Build connection string from parts if not provided
57
+ let connectionString = redisConfig.connectionString
58
+ if (!connectionString) {
59
+ connectionString = `redis://${hasUsername ? `${username}:${password}@` : ''}${host}:${
60
+ port || 6379
61
+ }`
62
+ }
63
+
64
+ return `import { createClient } from 'redis'
65
+
66
+ export default async function handler(req, res) {
67
+ let client = null
68
+ try {
69
+ client = createClient({
70
+ url: ${replaceSecretReference(connectionString)}${
71
+ database ? `,\n database: ${database}` : ''
72
+ }
73
+ })
74
+
75
+ await client.connect()
76
+
77
+ const { query, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
78
+
79
+ const pattern = (filters && JSON.parse(filters).pattern) || query || '*'
80
+ const keys = await client.keys(pattern)
81
+
82
+ const limitValue = limit || perPage || 100
83
+ const skipValue = offset !== undefined ? parseInt(offset) : ((parseInt(page) || 1) - 1) * parseInt(limitValue)
84
+ const paginatedKeys = keys.slice(skipValue, skipValue + parseInt(limitValue))
85
+
86
+ const results = []
87
+ for (const key of paginatedKeys) {
88
+ const type = await client.type(key)
89
+ const ttl = await client.ttl(key)
90
+ let value
91
+
92
+ switch (type) {
93
+ case 'string':
94
+ value = await client.get(key)
95
+ break
96
+ case 'list':
97
+ value = await client.lRange(key, 0, -1)
98
+ break
99
+ case 'set':
100
+ value = await client.sMembers(key)
101
+ break
102
+ case 'zset':
103
+ value = await client.zRange(key, 0, -1)
104
+ break
105
+ case 'hash':
106
+ value = await client.hGetAll(key)
107
+ break
108
+ default:
109
+ value = null
110
+ }
111
+
112
+ results.push({
113
+ key,
114
+ type,
115
+ value,
116
+ ttl: ttl === -1 ? null : ttl
117
+ })
118
+ }
119
+
120
+ if (sortBy) {
121
+ const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
122
+ results.sort((a, b) => {
123
+ const aVal = a[sortBy]
124
+ const bVal = b[sortBy]
125
+ if (aVal < bVal) return -sortOrderValue
126
+ if (aVal > bVal) return sortOrderValue
127
+ return 0
128
+ })
129
+ }
130
+
131
+ const safeData = JSON.parse(JSON.stringify(results))
132
+
133
+ return res.status(200).json({
134
+ success: true,
135
+ data: safeData,
136
+ timestamp: Date.now()
137
+ })
138
+ } catch (error) {
139
+ console.error('Redis fetch error:', error)
140
+ return res.status(500).json({
141
+ success: false,
142
+ error: error.message || 'Failed to fetch data',
143
+ timestamp: Date.now()
144
+ })
145
+ } finally {
146
+ if (client) {
147
+ await client.quit()
148
+ }
149
+ }
150
+ }
151
+ `
152
+ }
@@ -0,0 +1,138 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ interface RedshiftConfig {
4
+ host?: string
5
+ port?: number
6
+ user?: string
7
+ password?: string
8
+ database?: string
9
+ ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
10
+ sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
11
+ options?: { schema?: string }
12
+ }
13
+
14
+ export const generateRedshiftFetcher = (
15
+ config: Record<string, unknown>,
16
+ tableName: string
17
+ ): string => {
18
+ const redshiftConfig = config as RedshiftConfig
19
+ const host = redshiftConfig.host
20
+ const port = redshiftConfig.port
21
+ const user = redshiftConfig.user
22
+ const password = redshiftConfig.password
23
+ const database = redshiftConfig.database
24
+ const ssl = redshiftConfig.ssl
25
+ const sslConfig = redshiftConfig.sslConfig
26
+ const schema = redshiftConfig.options?.schema
27
+
28
+ return `import { Pool } from 'pg'
29
+
30
+ let pool = null
31
+
32
+ const getPool = () => {
33
+ if (pool) return pool
34
+
35
+ pool = new Pool({
36
+ host: ${JSON.stringify(host)},
37
+ port: ${port || 5439},
38
+ user: ${JSON.stringify(user)},
39
+ password: ${replaceSecretReference(password)},
40
+ database: ${JSON.stringify(database)},
41
+ ssl: ${
42
+ ssl === false
43
+ ? '{ rejectUnauthorized: false }'
44
+ : sslConfig
45
+ ? `{
46
+ ${sslConfig.ca ? `ca: ${replaceSecretReference(sslConfig.ca)},` : ''}
47
+ ${sslConfig.cert ? `cert: ${replaceSecretReference(sslConfig.cert)},` : ''}
48
+ ${sslConfig.key ? `key: ${replaceSecretReference(sslConfig.key)},` : ''}
49
+ rejectUnauthorized: ${sslConfig.rejectUnauthorized !== false}
50
+ }`
51
+ : '{ rejectUnauthorized: false }' // Default to SSL with no cert verification for Redshift
52
+ }
53
+ })
54
+
55
+ return pool
56
+ }
57
+
58
+ export default async function handler(req, res) {
59
+ try {
60
+ const pool = getPool()
61
+ ${schema ? `await pool.query('SET search_path TO ${schema}')` : ''}
62
+
63
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
64
+
65
+ const conditions = []
66
+ const queryParams = []
67
+ let paramIndex = 1
68
+
69
+ if (query && queryColumns) {
70
+ const columns = JSON.parse(queryColumns)
71
+ const searchConditions = columns.map((col) => {
72
+ const condition = \`\${col}::text ILIKE $\${paramIndex}\`
73
+ paramIndex++
74
+ return condition
75
+ })
76
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
77
+ conditions.push(\`(\${searchConditions.join(' OR ')})\`)
78
+ }
79
+
80
+ if (filters) {
81
+ const parsedFilters = JSON.parse(filters)
82
+ Object.entries(parsedFilters).forEach(([key, value]) => {
83
+ if (Array.isArray(value)) {
84
+ const placeholders = value.map(() => \`$\${paramIndex++}\`)
85
+ queryParams.push(...value)
86
+ conditions.push(\`\${key} IN (\${placeholders.join(', ')})\`)
87
+ } else {
88
+ conditions.push(\`\${key} = $\${paramIndex}\`)
89
+ queryParams.push(value)
90
+ paramIndex++
91
+ }
92
+ })
93
+ }
94
+
95
+ let sql = \`SELECT * FROM ${tableName}\`
96
+
97
+ if (conditions.length > 0) {
98
+ sql += \` WHERE \${conditions.join(' AND ')}\`
99
+ }
100
+
101
+ if (sortBy) {
102
+ sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
103
+ }
104
+
105
+ const limitValue = limit || perPage
106
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
107
+
108
+ if (limitValue) {
109
+ sql += \` LIMIT \${limitValue}\`
110
+ }
111
+
112
+ if (offsetValue !== undefined) {
113
+ sql += \` OFFSET \${offsetValue}\`
114
+ }
115
+
116
+ const result = await pool.query(sql, queryParams)
117
+ const rows = Array.isArray(result?.rows) ? result.rows : []
118
+ const plainRows = rows.map((row) =>
119
+ row && typeof row.toJSON === 'function' ? row.toJSON() : row
120
+ )
121
+ const safeData = JSON.parse(JSON.stringify(plainRows))
122
+
123
+ return res.status(200).json({
124
+ success: true,
125
+ data: safeData,
126
+ timestamp: Date.now()
127
+ })
128
+ } catch (error) {
129
+ console.error('Redshift fetch error:', error)
130
+ return res.status(500).json({
131
+ success: false,
132
+ error: error.message || 'Failed to fetch data',
133
+ timestamp: Date.now()
134
+ })
135
+ }
136
+ }
137
+ `
138
+ }
@@ -0,0 +1,148 @@
1
+ export const validateRESTAPIConfig = (
2
+ config: Record<string, unknown>
3
+ ): { isValid: boolean; error?: string } => {
4
+ if (!config || typeof config !== 'object') {
5
+ return { isValid: false, error: 'Config must be a valid object' }
6
+ }
7
+
8
+ if (!config.url || typeof config.url !== 'string' || config.url.trim() === '') {
9
+ return { isValid: false, error: 'URL is required' }
10
+ }
11
+
12
+ try {
13
+ const url = new URL(config.url)
14
+ if (!['http:', 'https:'].includes(url.protocol)) {
15
+ return { isValid: false, error: 'URL must use HTTP or HTTPS protocol' }
16
+ }
17
+ } catch {
18
+ return { isValid: false, error: 'Invalid URL format' }
19
+ }
20
+
21
+ if (config.method) {
22
+ const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
23
+ if (typeof config.method !== 'string' || !validMethods.includes(config.method.toUpperCase())) {
24
+ return { isValid: false, error: 'Invalid HTTP method' }
25
+ }
26
+ }
27
+
28
+ return { isValid: true }
29
+ }
30
+
31
+ interface Authorization {
32
+ type?: string
33
+ credentials?: {
34
+ apiKey?: string
35
+ token?: string
36
+ username?: string
37
+ password?: string
38
+ clientSecret?: string
39
+ }
40
+ }
41
+
42
+ const generateAuthCode = (authorization: Authorization): string => {
43
+ if (!authorization || authorization.type === 'none') {
44
+ return ''
45
+ }
46
+
47
+ const { type, credentials } = authorization
48
+
49
+ switch (type) {
50
+ case 'api-key':
51
+ return `headers['Authorization'] = 'ApiKey ${credentials.apiKey}'`
52
+ case 'bearer-token':
53
+ case 'jwt-bearer':
54
+ return `headers['Authorization'] = 'Bearer ${credentials.token}'`
55
+ case 'basic-auth':
56
+ return `headers['Authorization'] = 'Basic ' + Buffer.from('${credentials.username}:${credentials.password}').toString('base64')`
57
+ case 'oauth2':
58
+ return `headers['Authorization'] = 'Bearer ${credentials.clientSecret}'`
59
+ default:
60
+ return ''
61
+ }
62
+ }
63
+
64
+ interface RESTAPIConfig {
65
+ url?: string
66
+ method?: string
67
+ headers?: Record<string, string>
68
+ authorization?: Authorization
69
+ bodyType?: string
70
+ }
71
+
72
+ export const generateRESTAPIFetcher = (config: Record<string, unknown>): string => {
73
+ const restConfig = config as RESTAPIConfig
74
+ const authCode = generateAuthCode(restConfig.authorization || {})
75
+
76
+ return `import fetch from 'node-fetch'
77
+
78
+ export default async function handler(req, res) {
79
+ try {
80
+ const { offset, limit } = req.query
81
+
82
+ const url = ${JSON.stringify(restConfig.url)}
83
+ const method = ${JSON.stringify(restConfig.method || 'GET')}
84
+
85
+ const headers = ${JSON.stringify(restConfig.headers || {})}
86
+ ${authCode}
87
+
88
+ const options = {
89
+ method,
90
+ headers
91
+ }
92
+
93
+ ${
94
+ restConfig.method === 'POST' || restConfig.method === 'PUT' || restConfig.method === 'PATCH'
95
+ ? `
96
+ if (req.body) {
97
+ options.body = ${
98
+ restConfig.bodyType === 'json' || !restConfig.bodyType
99
+ ? 'JSON.stringify(req.body)'
100
+ : 'req.body'
101
+ }
102
+ }
103
+ `
104
+ : ''
105
+ }
106
+
107
+ const response = await fetch(url, options)
108
+
109
+ if (!response.ok) {
110
+ return res.status(response.status).json({
111
+ success: false,
112
+ error: \`HTTP \${response.status}: \${response.statusText}\`,
113
+ timestamp: Date.now()
114
+ })
115
+ }
116
+
117
+ let data = await response.json()
118
+
119
+ // Apply offset and limit if data is an array and parameters are provided
120
+ if (Array.isArray(data)) {
121
+ const offsetValue = offset !== undefined ? parseInt(offset) : 0
122
+ const limitValue = limit !== undefined ? parseInt(limit) : undefined
123
+
124
+ if (limitValue !== undefined) {
125
+ data = data.slice(offsetValue, offsetValue + limitValue)
126
+ } else if (offsetValue > 0) {
127
+ data = data.slice(offsetValue)
128
+ }
129
+ }
130
+
131
+ const safeData = JSON.parse(JSON.stringify(data))
132
+
133
+ return res.status(200).json({
134
+ success: true,
135
+ data: safeData,
136
+ timestamp: Date.now()
137
+ })
138
+ } catch (error) {
139
+ console.error('REST API fetch error:', error)
140
+ return res.status(500).json({
141
+ success: false,
142
+ error: error.message || 'Failed to fetch data',
143
+ timestamp: Date.now()
144
+ })
145
+ }
146
+ }
147
+ `
148
+ }