@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,153 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ export const validateAirtableConfig = (
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 (!config.baseId || typeof config.baseId !== 'string' || config.baseId.trim() === '') {
11
+ return { isValid: false, error: 'Airtable base ID is required' }
12
+ }
13
+
14
+ if (!config.personalAccessToken || typeof config.personalAccessToken !== 'string') {
15
+ return { isValid: false, error: 'Airtable personal access token is required' }
16
+ }
17
+
18
+ return { isValid: true }
19
+ }
20
+
21
+ interface AirtableConfig {
22
+ baseId?: string
23
+ personalAccessToken?: string
24
+ selectedTables?: Record<string, unknown>
25
+ }
26
+
27
+ export const generateAirtableFetcher = (
28
+ config: Record<string, unknown>,
29
+ tableName: string
30
+ ): string => {
31
+ const airtableConfig = config as AirtableConfig
32
+ const baseId = airtableConfig.baseId
33
+ const personalAccessToken = airtableConfig.personalAccessToken
34
+
35
+ return `import fetch from 'node-fetch'
36
+
37
+ export default async function handler(req, res) {
38
+ try {
39
+ const { query, view, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
40
+
41
+ const queryParams = new URLSearchParams()
42
+
43
+ if (view) {
44
+ queryParams.append('view', view)
45
+ }
46
+
47
+ if (sortBy) {
48
+ queryParams.append('sort[0][field]', sortBy)
49
+ queryParams.append('sort[0][direction]', sortOrder || 'asc')
50
+ }
51
+
52
+ const perPageValue = limit || perPage || 100
53
+ queryParams.append('pageSize', Math.min(parseInt(perPageValue), 100).toString())
54
+
55
+ if (filters) {
56
+ const parsedFilters = JSON.parse(filters)
57
+ const conditions = Object.entries(parsedFilters).map(([field, value]) => {
58
+ if (Array.isArray(value)) {
59
+ const arrayConditions = value.map((v) => {
60
+ if (typeof v === 'string') {
61
+ return \`{\${field}}='\${v.replace(/'/g, "\\\\'")}'\`
62
+ } else if (typeof v === 'number') {
63
+ return \`{\${field}}=\${v}\`
64
+ } else if (typeof v === 'boolean') {
65
+ return \`{\${field}}=\${v ? 'TRUE()' : 'FALSE()'}\`
66
+ }
67
+ return \`{\${field}}='\${String(v)}'\`
68
+ })
69
+ return arrayConditions.length > 1
70
+ ? \`OR(\${arrayConditions.join(',')})\`
71
+ : arrayConditions[0]
72
+ } else if (typeof value === 'string') {
73
+ return \`{\${field}}='\${value.replace(/'/g, "\\\\'")}'\`
74
+ } else if (typeof value === 'number') {
75
+ return \`{\${field}}=\${value}\`
76
+ } else if (typeof value === 'boolean') {
77
+ return \`{\${field}}=\${value ? 'TRUE()' : 'FALSE()'}\`
78
+ }
79
+ return \`{\${field}}='\${String(value)}'\`
80
+ })
81
+
82
+ const filterFormula = conditions.length > 1 ? \`AND(\${conditions.join(',')})\` : conditions[0]
83
+ if (filterFormula) {
84
+ queryParams.append('filterByFormula', filterFormula)
85
+ }
86
+ }
87
+
88
+ let url = \`https://api.airtable.com/v0/${baseId}/\${encodeURIComponent('${tableName}')}\`
89
+ if (queryParams.toString()) {
90
+ url += \`?\${queryParams.toString()}\`
91
+ }
92
+
93
+ const allRecords = []
94
+ let airtableOffset
95
+ const skipValue = offsetParam !== undefined ? parseInt(offsetParam) : (page ? (parseInt(page) - 1) * parseInt(perPageValue) : 0)
96
+ const totalRecordsNeeded = skipValue + parseInt(perPageValue)
97
+
98
+ do {
99
+ const fetchUrl = airtableOffset ? \`\${url}&offset=\${airtableOffset}\` : url
100
+ const response = await fetch(fetchUrl, {
101
+ method: 'GET',
102
+ headers: {
103
+ Authorization: \`Bearer ${replaceSecretReference(personalAccessToken, {
104
+ templateLiteral: true,
105
+ })}\`,
106
+ 'Content-Type': 'application/json'
107
+ }
108
+ })
109
+
110
+ if (!response.ok) {
111
+ const errorData = await response.json().catch(() => ({}))
112
+ return res.status(response.status).json({
113
+ success: false,
114
+ error: errorData.error?.message || \`HTTP \${response.status}: \${response.statusText}\`,
115
+ timestamp: Date.now()
116
+ })
117
+ }
118
+
119
+ const data = await response.json()
120
+ allRecords.push(...data.records)
121
+ airtableOffset = data.offset
122
+
123
+ if (allRecords.length >= totalRecordsNeeded || !airtableOffset) {
124
+ break
125
+ }
126
+ } while (airtableOffset)
127
+
128
+ const paginatedRecords = allRecords.slice(skipValue, skipValue + parseInt(perPageValue))
129
+
130
+ const formattedRecords = paginatedRecords.map((record) => ({
131
+ id: record.id,
132
+ ...record.fields,
133
+ createdTime: record.createdTime
134
+ }))
135
+
136
+ const safeData = JSON.parse(JSON.stringify(formattedRecords))
137
+
138
+ return res.status(200).json({
139
+ success: true,
140
+ data: safeData,
141
+ timestamp: Date.now()
142
+ })
143
+ } catch (error) {
144
+ console.error('Airtable fetch error:', error)
145
+ return res.status(500).json({
146
+ success: false,
147
+ error: error.message || 'Failed to fetch data',
148
+ timestamp: Date.now()
149
+ })
150
+ }
151
+ }
152
+ `
153
+ }
@@ -0,0 +1,127 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ export const validateClickHouseConfig = (
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 (!config.url || typeof config.url !== 'string') {
11
+ return { isValid: false, error: 'ClickHouse URL is required' }
12
+ }
13
+
14
+ if (!config.username || typeof config.username !== 'string') {
15
+ return { isValid: false, error: 'ClickHouse username is required' }
16
+ }
17
+
18
+ if (!config.password || typeof config.password !== 'string') {
19
+ return { isValid: false, error: 'ClickHouse password is required' }
20
+ }
21
+
22
+ return { isValid: true }
23
+ }
24
+
25
+ interface ClickHouseConfig {
26
+ url?: string
27
+ username?: string
28
+ password?: string
29
+ }
30
+
31
+ export const generateClickHouseFetcher = (
32
+ config: Record<string, unknown>,
33
+ tableName: string
34
+ ): string => {
35
+ const clickConfig = config as ClickHouseConfig
36
+ const url = clickConfig.url
37
+ const username = clickConfig.username
38
+ const password = clickConfig.password
39
+
40
+ return `import { createClient } from '@clickhouse/client'
41
+
42
+ let client = null
43
+
44
+ const getClient = () => {
45
+ if (client) return client
46
+
47
+ client = createClient({
48
+ url: ${JSON.stringify(url)},
49
+ username: ${JSON.stringify(username)},
50
+ password: ${replaceSecretReference(password)}
51
+ })
52
+
53
+ return client
54
+ }
55
+
56
+ export default async function handler(req, res) {
57
+ try {
58
+ const client = getClient()
59
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
60
+
61
+ const conditions = []
62
+
63
+ if (query && queryColumns) {
64
+ const columns = JSON.parse(queryColumns)
65
+ const searchConditions = columns.map(
66
+ (col) => \`positionCaseInsensitive(toString(\${col}), '\${query}') > 0\`
67
+ )
68
+ conditions.push(\`(\${searchConditions.join(' OR ')})\`)
69
+ }
70
+
71
+ if (filters) {
72
+ const parsedFilters = JSON.parse(filters)
73
+ Object.entries(parsedFilters).forEach(([key, value]) => {
74
+ if (Array.isArray(value)) {
75
+ const formattedValues = value
76
+ .map((v) => (typeof v === 'string' ? \`'\${v}'\` : v))
77
+ .join(', ')
78
+ conditions.push(\`\${key} IN (\${formattedValues})\`)
79
+ } else if (typeof value === 'string') {
80
+ conditions.push(\`\${key} = '\${value}'\`)
81
+ } else {
82
+ conditions.push(\`\${key} = \${value}\`)
83
+ }
84
+ })
85
+ }
86
+
87
+ let sql = \`SELECT * FROM ${tableName}\`
88
+
89
+ if (conditions.length > 0) {
90
+ sql += \` WHERE \${conditions.join(' AND ')}\`
91
+ }
92
+
93
+ if (sortBy) {
94
+ sql += \` ORDER BY \${sortBy} \${sortOrder?.toUpperCase() || 'ASC'}\`
95
+ }
96
+
97
+ const limitValue = limit || perPage
98
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
99
+
100
+ if (limitValue) {
101
+ sql += \` LIMIT \${limitValue}\`
102
+ }
103
+
104
+ if (offsetValue !== undefined) {
105
+ sql += \` OFFSET \${offsetValue}\`
106
+ }
107
+
108
+ const result = await client.query({ query: sql })
109
+ const resultResponse = await result.json()
110
+ const safeData = JSON.parse(JSON.stringify(resultResponse.data))
111
+
112
+ return res.status(200).json({
113
+ success: true,
114
+ data: safeData,
115
+ timestamp: Date.now()
116
+ })
117
+ } catch (error) {
118
+ console.error('ClickHouse fetch error:', error)
119
+ return res.status(500).json({
120
+ success: false,
121
+ error: error.message || 'Failed to fetch data',
122
+ timestamp: Date.now()
123
+ })
124
+ }
125
+ }
126
+ `
127
+ }
@@ -0,0 +1,163 @@
1
+ export const validateCSVConfig = (
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.parsedData || !Array.isArray(config.parsedData)) {
9
+ return { isValid: false, error: 'Parsed data must be an array' }
10
+ }
11
+
12
+ // Columns are optional - if not provided, we'll infer them from parsedData
13
+ if (config.columns !== undefined) {
14
+ if (!Array.isArray(config.columns)) {
15
+ return { isValid: false, error: 'Columns definition must be an array' }
16
+ }
17
+
18
+ for (const column of config.columns) {
19
+ if (!column || typeof column !== 'object' || !column.id || typeof column.id !== 'string') {
20
+ return { isValid: false, error: 'Each column must have a valid id' }
21
+ }
22
+ }
23
+ }
24
+
25
+ return { isValid: true }
26
+ }
27
+
28
+ interface CSVFileConfig {
29
+ parsedData?: unknown[]
30
+ columns?: Array<{ id: string; [key: string]: unknown }>
31
+ }
32
+
33
+ export const generateCSVFileFetcher = (config: Record<string, unknown>): string => {
34
+ const csvConfig = config as CSVFileConfig
35
+ return `const data = ${JSON.stringify(csvConfig.parsedData || [])}
36
+
37
+ export default async function handler(req, res) {
38
+ try {
39
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
40
+
41
+ let filteredData = [...data]
42
+
43
+ if (query) {
44
+ const searchQuery = query.toLowerCase()
45
+
46
+ if (queryColumns) {
47
+ const columns = JSON.parse(queryColumns)
48
+ filteredData = filteredData.filter((item) => {
49
+ return columns.some((col) => {
50
+ const value = item[col]
51
+ return value && String(value).toLowerCase().includes(searchQuery)
52
+ })
53
+ })
54
+ } else {
55
+ filteredData = filteredData.filter((item) => {
56
+ try {
57
+ const stringified = JSON.stringify(item).toLowerCase()
58
+ return stringified.includes(searchQuery)
59
+ } catch {
60
+ return false
61
+ }
62
+ })
63
+ }
64
+ }
65
+
66
+ if (filters) {
67
+ const parsedFilters = JSON.parse(filters)
68
+ filteredData = filteredData.filter((item) => {
69
+ return Object.entries(parsedFilters).every(([key, value]) => {
70
+ if (Array.isArray(value)) {
71
+ return value.includes(item[key])
72
+ }
73
+ return item[key] === value
74
+ })
75
+ })
76
+ }
77
+
78
+ if (sortBy) {
79
+ filteredData.sort((a, b) => {
80
+ const aVal = a[sortBy]
81
+ const bVal = b[sortBy]
82
+ const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
83
+ if (aVal < bVal) return -sortOrderValue
84
+ if (aVal > bVal) return sortOrderValue
85
+ return 0
86
+ })
87
+ }
88
+
89
+ const limitValue = limit || perPage
90
+ const offsetValue = offsetParam !== undefined ? parseInt(offsetParam) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
91
+
92
+ if (limitValue) {
93
+ filteredData = filteredData.slice(offsetValue, offsetValue + parseInt(limitValue))
94
+ }
95
+
96
+ const safeData = JSON.parse(JSON.stringify(filteredData))
97
+
98
+ return res.status(200).json({
99
+ success: true,
100
+ data: safeData,
101
+ timestamp: Date.now()
102
+ })
103
+ } catch (error) {
104
+ console.error('CSV fetch error:', error)
105
+ return res.status(500).json({
106
+ success: false,
107
+ error: error.message || 'Failed to fetch data',
108
+ timestamp: Date.now()
109
+ })
110
+ }
111
+ }
112
+ `
113
+ }
114
+
115
+ // tslint:disable-next-line:variable-name
116
+ export const generateCSVCountFetcher = (_config: any): string => {
117
+ return `
118
+ async function getCount(req, res) {
119
+ try {
120
+ const { query, queryColumns, filters } = req.query
121
+ const fakeReq = { query: { query, queryColumns, filters }, method: 'GET' }
122
+ let result = null
123
+ let statusCode = 200
124
+
125
+ const fakeRes = {
126
+ status: (code) => {
127
+ statusCode = code
128
+ return fakeRes
129
+ },
130
+ json: (data) => {
131
+ result = data
132
+ return fakeRes
133
+ },
134
+ }
135
+
136
+ await handler(fakeReq, fakeRes)
137
+
138
+ if (statusCode !== 200 || !result || !result.success) {
139
+ return res.status(500).json({
140
+ success: false,
141
+ error: 'Failed to get data for counting',
142
+ timestamp: Date.now()
143
+ })
144
+ }
145
+
146
+ const count = Array.isArray(result.data) ? result.data.length : 0
147
+
148
+ return res.status(200).json({
149
+ success: true,
150
+ count: count,
151
+ timestamp: Date.now()
152
+ })
153
+ } catch (error) {
154
+ console.error('Error getting count:', error)
155
+ return res.status(500).json({
156
+ success: false,
157
+ error: error.message || 'Failed to get count',
158
+ timestamp: Date.now()
159
+ })
160
+ }
161
+ }
162
+ `
163
+ }
@@ -0,0 +1,138 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ export const validateFirestoreConfig = (
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 (!config.serviceAccount || typeof config.serviceAccount !== 'string') {
11
+ return { isValid: false, error: 'Firestore service account JSON is required' }
12
+ }
13
+
14
+ const serviceAccount = config.serviceAccount as string
15
+
16
+ // If serviceAccount is a secret reference, we assume the runtime env var will contain valid JSON
17
+ // Example: "teleporthq.secrets.DATA_SOURCE_FIRESTORE_SERVICE_ACCOUNT"
18
+ if (!serviceAccount.startsWith('teleporthq.secrets.')) {
19
+ try {
20
+ const parsed = JSON.parse(serviceAccount)
21
+ if (!parsed.project_id || !parsed.private_key || !parsed.client_email) {
22
+ return { isValid: false, error: 'Invalid Firestore service account JSON structure' }
23
+ }
24
+ } catch {
25
+ return { isValid: false, error: 'Service account must be valid JSON' }
26
+ }
27
+ }
28
+
29
+ return { isValid: true }
30
+ }
31
+
32
+ interface FirestoreConfig {
33
+ serviceAccount?: string
34
+ selectedTables?: Record<string, unknown>
35
+ }
36
+
37
+ export const generateFirestoreFetcher = (
38
+ config: Record<string, unknown>,
39
+ tableName: string
40
+ ): string => {
41
+ const firestoreConfig = config as FirestoreConfig
42
+ const serviceAccount = firestoreConfig.serviceAccount
43
+
44
+ return `import * as admin from 'firebase-admin'
45
+
46
+ let firestore = null
47
+
48
+ const getFirestore = () => {
49
+ if (firestore) return firestore
50
+
51
+ const rawServiceAccount = ${replaceSecretReference(serviceAccount)}
52
+ let serviceAccount
53
+
54
+ try {
55
+ serviceAccount = JSON.parse(rawServiceAccount)
56
+ } catch (error) {
57
+ throw new Error('Invalid Firestore service account JSON: ' + error.message)
58
+ }
59
+
60
+ if (!admin.apps.length) {
61
+ admin.initializeApp({
62
+ credential: admin.credential.cert(serviceAccount)
63
+ })
64
+ }
65
+
66
+ firestore = admin.firestore()
67
+ return firestore
68
+ }
69
+
70
+ export default async function handler(req, res) {
71
+ try {
72
+ const firestore = getFirestore()
73
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
74
+
75
+ let queryRef = firestore.collection('${tableName}')
76
+
77
+ if (filters) {
78
+ const parsedFilters = JSON.parse(filters)
79
+ Object.entries(parsedFilters).forEach(([key, value]) => {
80
+ if (Array.isArray(value)) {
81
+ queryRef = queryRef.where(key, 'in', value)
82
+ } else {
83
+ queryRef = queryRef.where(key, '==', value)
84
+ }
85
+ })
86
+ }
87
+
88
+ if (query && queryColumns) {
89
+ const columns = JSON.parse(queryColumns)
90
+ for (const column of columns) {
91
+ queryRef = queryRef
92
+ .where(column, '>=', query)
93
+ .where(column, '<=', query + '\\uf8ff')
94
+ }
95
+ }
96
+
97
+ if (sortBy) {
98
+ const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? 'desc' : 'asc'
99
+ queryRef = queryRef.orderBy(sortBy, sortOrderValue)
100
+ }
101
+
102
+ const limitValue = limit || perPage
103
+ if (limitValue) {
104
+ queryRef = queryRef.limit(parseInt(limitValue))
105
+ }
106
+
107
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage && parseInt(page) > 1 ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
108
+ if (offsetValue !== undefined) {
109
+ queryRef = queryRef.offset(offsetValue)
110
+ }
111
+
112
+ const snapshot = await queryRef.get()
113
+ const documents = []
114
+ snapshot.forEach((doc) => {
115
+ documents.push({
116
+ id: doc.id,
117
+ ...doc.data()
118
+ })
119
+ })
120
+
121
+ const safeData = JSON.parse(JSON.stringify(documents))
122
+
123
+ return res.status(200).json({
124
+ success: true,
125
+ data: safeData,
126
+ timestamp: Date.now()
127
+ })
128
+ } catch (error) {
129
+ console.error('Firestore 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
+ }