@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,189 @@
1
+ export const validateGoogleSheetsConfig = (
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.sheetId && !config.sheetUrl) {
9
+ return { isValid: false, error: 'Google Sheets ID or URL is required' }
10
+ }
11
+
12
+ if (config.sheetId && typeof config.sheetId !== 'string') {
13
+ return { isValid: false, error: 'Sheet ID must be a string' }
14
+ }
15
+
16
+ if (config.sheetUrl) {
17
+ if (typeof config.sheetUrl !== 'string') {
18
+ return { isValid: false, error: 'Sheet URL must be a string' }
19
+ }
20
+
21
+ if (!config.sheetUrl.includes('docs.google.com/spreadsheets')) {
22
+ return { isValid: false, error: 'Invalid Google Sheets URL format' }
23
+ }
24
+ }
25
+
26
+ return { isValid: true }
27
+ }
28
+
29
+ interface GoogleSheetsConfig {
30
+ sheetId?: string
31
+ sheetUrl?: string
32
+ apiKey?: string
33
+ sheetName?: string
34
+ range?: string
35
+ maxRows?: number
36
+ }
37
+
38
+ export const generateGoogleSheetsFetcher = (config: Record<string, unknown>): string => {
39
+ const sheetsConfig = config as GoogleSheetsConfig
40
+ return `import fetch from 'node-fetch'
41
+
42
+ export default async function handler(req, res) {
43
+ try {
44
+ const sheetUrl = ${JSON.stringify(sheetsConfig.sheetUrl)}
45
+ let sheetId = ${JSON.stringify(sheetsConfig.sheetId)}
46
+ const range = ${JSON.stringify(sheetsConfig.range || 'A1:Z1000')}
47
+ const maxRows = ${sheetsConfig.maxRows || 0}
48
+
49
+ if (!sheetId && sheetUrl) {
50
+ const match = sheetUrl.match(/\\/d\\/([a-zA-Z0-9-_]+)/)
51
+ sheetId = match ? match[1] : undefined
52
+ }
53
+
54
+ if (!sheetId) {
55
+ return res.status(400).json({
56
+ success: false,
57
+ error: 'Invalid Google Sheets URL or Sheet ID',
58
+ timestamp: Date.now()
59
+ })
60
+ }
61
+
62
+ let url = \`https://docs.google.com/spreadsheets/d/\${sheetId}/gviz/tq?tqx=out:json&range=\${range}\`
63
+
64
+ if (maxRows && maxRows > 0) {
65
+ url += \`&tq=limit \${maxRows}\`
66
+ }
67
+
68
+ const response = await fetch(url)
69
+
70
+ if (!response.ok) {
71
+ return res.status(response.status).json({
72
+ success: false,
73
+ error: \`HTTP \${response.status}: \${response.statusText}\`,
74
+ timestamp: Date.now()
75
+ })
76
+ }
77
+
78
+ const text = await response.text()
79
+ const jsonMatch = text.match(/google\\.visualization\\.Query\\.setResponse\\((.*)\\);/)
80
+
81
+ if (!jsonMatch) {
82
+ return res.status(500).json({
83
+ success: false,
84
+ error: 'Unable to parse Google Sheets response',
85
+ timestamp: Date.now()
86
+ })
87
+ }
88
+
89
+ const data = JSON.parse(jsonMatch[1])
90
+
91
+ if (data.status === 'error') {
92
+ return res.status(500).json({
93
+ success: false,
94
+ error: data.errors?.[0]?.detailed_message || 'Failed to fetch Google Sheets data',
95
+ timestamp: Date.now()
96
+ })
97
+ }
98
+
99
+ const table = data.table
100
+ const columns = table.cols.map((col, index) => ({
101
+ id: col.id || \`col_\${index}\`,
102
+ label: col.label || \`Column \${index + 1}\`,
103
+ type: col.type || 'string'
104
+ }))
105
+
106
+ const rows = table.rows.map((row) => {
107
+ const rowData = {}
108
+ row.c.forEach((cell, index) => {
109
+ const columnId = columns[index].id
110
+ rowData[columnId] = cell?.v ?? null
111
+ })
112
+ return rowData
113
+ })
114
+
115
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset: offsetParam } = req.query
116
+
117
+ let filteredData = [...rows]
118
+
119
+ if (query) {
120
+ const searchQuery = query.toLowerCase()
121
+
122
+ if (queryColumns) {
123
+ const searchColumns = JSON.parse(queryColumns)
124
+ filteredData = filteredData.filter((item) => {
125
+ return searchColumns.some((col) => {
126
+ const value = item[col]
127
+ return value && String(value).toLowerCase().includes(searchQuery)
128
+ })
129
+ })
130
+ } else {
131
+ filteredData = filteredData.filter((item) => {
132
+ try {
133
+ const stringified = JSON.stringify(item).toLowerCase()
134
+ return stringified.includes(searchQuery)
135
+ } catch {
136
+ return false
137
+ }
138
+ })
139
+ }
140
+ }
141
+
142
+ if (filters) {
143
+ const parsedFilters = JSON.parse(filters)
144
+ filteredData = filteredData.filter((item) => {
145
+ return Object.entries(parsedFilters).every(([key, value]) => {
146
+ if (Array.isArray(value)) {
147
+ return value.includes(item[key])
148
+ }
149
+ return item[key] === value
150
+ })
151
+ })
152
+ }
153
+
154
+ if (sortBy) {
155
+ filteredData.sort((a, b) => {
156
+ const aVal = a[sortBy]
157
+ const bVal = b[sortBy]
158
+ const sortOrderValue = sortOrder?.toLowerCase() === 'desc' ? -1 : 1
159
+ if (aVal < bVal) return -sortOrderValue
160
+ if (aVal > bVal) return sortOrderValue
161
+ return 0
162
+ })
163
+ }
164
+
165
+ const limitValue = limit || perPage
166
+ const offsetValue = offsetParam !== undefined ? parseInt(offsetParam) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
167
+
168
+ if (limitValue) {
169
+ filteredData = filteredData.slice(offsetValue, offsetValue + parseInt(limitValue))
170
+ }
171
+
172
+ const safeData = JSON.parse(JSON.stringify(filteredData))
173
+
174
+ return res.status(200).json({
175
+ success: true,
176
+ data: safeData,
177
+ timestamp: Date.now()
178
+ })
179
+ } catch (error) {
180
+ console.error('Google Sheets fetch error:', error)
181
+ return res.status(500).json({
182
+ success: false,
183
+ error: error.message || 'Failed to fetch data',
184
+ timestamp: Date.now()
185
+ })
186
+ }
187
+ }
188
+ `
189
+ }
@@ -0,0 +1,32 @@
1
+ export { generatePostgreSQLFetcher, generatePostgreSQLCountFetcher } from './postgresql'
2
+ export { generateMySQLFetcher, generateMySQLCountFetcher } from './mysql'
3
+ export { generateMariaDBFetcher, generateMariaDBCountFetcher } from './mariadb'
4
+ export { generateRedshiftFetcher } from './redshift'
5
+ export {
6
+ generateMongoDBFetcher,
7
+ generateMongoDBCountFetcher,
8
+ validateMongoDBConfig,
9
+ } from './mongodb'
10
+ export { generateRedisFetcher, validateRedisConfig } from './redis'
11
+ export { generateFirestoreFetcher, validateFirestoreConfig } from './firestore'
12
+ export { generateClickHouseFetcher, validateClickHouseConfig } from './clickhouse'
13
+ export { generateAirtableFetcher, validateAirtableConfig } from './airtable'
14
+ export {
15
+ generateSupabaseFetcher,
16
+ generateSupabaseCountFetcher,
17
+ validateSupabaseConfig,
18
+ } from './supabase'
19
+ export { generateTursoFetcher, validateTursoConfig } from './turso'
20
+ export { generateRESTAPIFetcher, validateRESTAPIConfig } from './rest-api'
21
+ export {
22
+ generateJavaScriptFetcher,
23
+ generateJavaScriptCountFetcher,
24
+ validateJavaScriptConfig,
25
+ } from './javascript'
26
+ export { generateCSVFileFetcher, generateCSVCountFetcher, validateCSVConfig } from './csv-file'
27
+ export {
28
+ generateStaticCollectionFetcher,
29
+ generateStaticCollectionCountFetcher,
30
+ validateStaticCollectionConfig,
31
+ } from './static-collection'
32
+ export { generateGoogleSheetsFetcher, validateGoogleSheetsConfig } from './google-sheets'
@@ -0,0 +1,150 @@
1
+ export const validateJavaScriptConfig = (
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.code || typeof config.code !== 'string' || config.code.trim() === '') {
9
+ return { isValid: false, error: 'JavaScript code is required' }
10
+ }
11
+
12
+ const dangerousPatterns = [
13
+ /require\s*\(/i,
14
+ /import\s+/i,
15
+ /eval\s*\(/i,
16
+ /Function\s*\(/i,
17
+ /process\./i,
18
+ /global\./i,
19
+ /\.exec\s*\(/i,
20
+ ]
21
+
22
+ for (const pattern of dangerousPatterns) {
23
+ if (pattern.test(config.code)) {
24
+ console.warn('[Data Source] Warning: JavaScript code contains potentially dangerous patterns')
25
+ break
26
+ }
27
+ }
28
+
29
+ return { isValid: true }
30
+ }
31
+
32
+ interface JavaScriptConfig {
33
+ code?: string
34
+ }
35
+
36
+ export const generateJavaScriptFetcher = (config: Record<string, unknown>): string => {
37
+ const jsConfig = config as JavaScriptConfig
38
+ return `export default async function handler(req, res) {
39
+ try {
40
+ const { limit, offset, page, perPage, query, queryColumns } = req.query
41
+
42
+ const code = ${JSON.stringify(jsConfig.code)}
43
+ const executeCode = new Function('return ' + code)
44
+ let data = executeCode()
45
+
46
+ if (Array.isArray(data)) {
47
+ if (query) {
48
+ const searchQuery = query.toLowerCase()
49
+
50
+ if (queryColumns) {
51
+ // Search specific columns
52
+ const columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
53
+ data = data.filter(item => {
54
+ return columns.some(col => {
55
+ const value = item[col]
56
+ if (value === null || value === undefined) return false
57
+ return String(value).toLowerCase().includes(searchQuery)
58
+ })
59
+ })
60
+ } else {
61
+ // Search across all fields by stringifying the entire record
62
+ data = data.filter(item => {
63
+ try {
64
+ const stringified = JSON.stringify(item).toLowerCase()
65
+ return stringified.includes(searchQuery)
66
+ } catch {
67
+ return false
68
+ }
69
+ })
70
+ }
71
+ }
72
+
73
+ const limitValue = limit || perPage
74
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
75
+
76
+ if (limitValue) {
77
+ data = data.slice(offsetValue, offsetValue + parseInt(limitValue))
78
+ } else if (offsetValue > 0) {
79
+ data = data.slice(offsetValue)
80
+ }
81
+ }
82
+
83
+ const safeData = JSON.parse(JSON.stringify(data))
84
+
85
+ return res.status(200).json({
86
+ success: true,
87
+ data: safeData,
88
+ timestamp: Date.now()
89
+ })
90
+ } catch (error) {
91
+ console.error('JavaScript execution error:', error)
92
+ return res.status(500).json({
93
+ success: false,
94
+ error: error.message || 'Failed to execute code',
95
+ timestamp: Date.now()
96
+ })
97
+ }
98
+ }
99
+ `
100
+ }
101
+
102
+ // tslint:disable-next-line:variable-name
103
+ export const generateJavaScriptCountFetcher = (_config: any): string => {
104
+ return `
105
+ async function getCount(req, res) {
106
+ try {
107
+ const { query, queryColumns } = req.query
108
+ const fakeReq = { query: { query, queryColumns }, method: 'GET' }
109
+ let result = null
110
+ let statusCode = 200
111
+
112
+ const fakeRes = {
113
+ status: (code) => {
114
+ statusCode = code
115
+ return fakeRes
116
+ },
117
+ json: (data) => {
118
+ result = data
119
+ return fakeRes
120
+ },
121
+ }
122
+
123
+ await handler(fakeReq, fakeRes)
124
+
125
+ if (statusCode !== 200 || !result || !result.success) {
126
+ return res.status(500).json({
127
+ success: false,
128
+ error: 'Failed to get data for counting',
129
+ timestamp: Date.now()
130
+ })
131
+ }
132
+
133
+ const count = Array.isArray(result.data) ? result.data.length : 0
134
+
135
+ return res.status(200).json({
136
+ success: true,
137
+ count: count,
138
+ timestamp: Date.now()
139
+ })
140
+ } catch (error) {
141
+ console.error('Error getting count:', error)
142
+ return res.status(500).json({
143
+ success: false,
144
+ error: error.message || 'Failed to get count',
145
+ timestamp: Date.now()
146
+ })
147
+ }
148
+ }
149
+ `
150
+ }
@@ -0,0 +1,230 @@
1
+ import { replaceSecretReference } from '../utils'
2
+
3
+ interface MariaDBConfig {
4
+ host?: string
5
+ port?: number
6
+ user?: string
7
+ username?: string
8
+ password?: string
9
+ database?: string
10
+ ssl?: boolean | { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
11
+ sslConfig?: { ca?: string; cert?: string; key?: string; rejectUnauthorized?: boolean }
12
+ }
13
+
14
+ export const generateMariaDBFetcher = (
15
+ config: Record<string, unknown>,
16
+ tableName: string
17
+ ): string => {
18
+ const mariaConfig = config as MariaDBConfig
19
+ const database = mariaConfig.database
20
+
21
+ return `import mariadb from 'mariadb'
22
+
23
+ export default async function handler(req, res) {
24
+ let pool = null
25
+ try {
26
+ pool = mariadb.createPool({
27
+ host: ${JSON.stringify(mariaConfig.host)},
28
+ port: ${mariaConfig.port || 3306},
29
+ user: ${JSON.stringify(mariaConfig.user)},
30
+ password: ${replaceSecretReference(mariaConfig.password)},
31
+ database: ${JSON.stringify(mariaConfig.database)},
32
+ ssl: ${mariaConfig.ssl || false}${
33
+ mariaConfig.sslConfig
34
+ ? `,
35
+ sslConfig: {
36
+ ${
37
+ mariaConfig.sslConfig.ca ? `ca: ${replaceSecretReference(mariaConfig.sslConfig.ca)},` : ''
38
+ }
39
+ ${
40
+ mariaConfig.sslConfig.cert
41
+ ? `cert: ${replaceSecretReference(mariaConfig.sslConfig.cert)},`
42
+ : ''
43
+ }
44
+ ${
45
+ mariaConfig.sslConfig.key
46
+ ? `key: ${replaceSecretReference(mariaConfig.sslConfig.key)},`
47
+ : ''
48
+ }
49
+ rejectUnauthorized: ${mariaConfig.sslConfig.rejectUnauthorized !== false}
50
+ }`
51
+ : ''
52
+ }
53
+ })
54
+
55
+ const connection = await pool.getConnection()
56
+ const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, offset } = req.query
57
+
58
+ const conditions = []
59
+ const queryParams = []
60
+
61
+ if (query) {
62
+ let columns = []
63
+
64
+ if (queryColumns) {
65
+ // Use specified columns
66
+ columns = JSON.parse(queryColumns)
67
+ } else {
68
+ // Fallback: Get all columns from information_schema
69
+ try {
70
+ const schemaRows = await connection.query(
71
+ \`SELECT COLUMN_NAME FROM information_schema.COLUMNS
72
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
73
+ ORDER BY ORDINAL_POSITION\`,
74
+ [${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
75
+ )
76
+ columns = schemaRows.map(row => row.COLUMN_NAME)
77
+ } catch (schemaError) {
78
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
79
+ // Continue without search if we can't get columns
80
+ }
81
+ }
82
+
83
+ if (columns.length > 0) {
84
+ const searchConditions = columns.map((col) => \`CAST(\\\`\${col}\\\` AS CHAR) LIKE ?\`)
85
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
86
+ conditions.push(\`(\${searchConditions.join(' OR ')})\`)
87
+ }
88
+ }
89
+
90
+ if (filters) {
91
+ const parsedFilters = JSON.parse(filters)
92
+ Object.entries(parsedFilters).forEach(([key, value]) => {
93
+ if (Array.isArray(value)) {
94
+ const placeholders = value.map(() => '?').join(', ')
95
+ queryParams.push(...value)
96
+ conditions.push(\`\\\`\${key}\\\` IN (\${placeholders})\`)
97
+ } else {
98
+ conditions.push(\`\\\`\${key}\\\` = ?\`)
99
+ queryParams.push(value)
100
+ }
101
+ })
102
+ }
103
+
104
+ let sql = \`SELECT * FROM \\\`${tableName}\\\`\`
105
+
106
+ if (conditions.length > 0) {
107
+ sql += \` WHERE \${conditions.join(' AND ')}\`
108
+ }
109
+
110
+ if (sortBy) {
111
+ sql += \` ORDER BY \\\`\${sortBy}\\\` \${sortOrder?.toUpperCase() || 'ASC'}\`
112
+ }
113
+
114
+ const limitValue = limit || perPage
115
+ const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)
116
+
117
+ if (limitValue) {
118
+ sql += \` LIMIT \${limitValue}\`
119
+ }
120
+
121
+ if (offsetValue !== undefined) {
122
+ sql += \` OFFSET \${offsetValue}\`
123
+ }
124
+
125
+ const rows = await connection.query(sql, queryParams)
126
+ const rowArray = Array.isArray(rows) ? rows : []
127
+ const plainRows = rowArray.map((row) =>
128
+ row && typeof row.toJSON === 'function' ? row.toJSON() : row
129
+ )
130
+ const safeData = JSON.parse(JSON.stringify(plainRows))
131
+ connection.release()
132
+
133
+ return res.status(200).json({
134
+ success: true,
135
+ data: safeData,
136
+ timestamp: Date.now()
137
+ })
138
+ } catch (error) {
139
+ console.error('MariaDB 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 (pool) {
147
+ await pool.end()
148
+ }
149
+ }
150
+ }
151
+ `
152
+ }
153
+
154
+ export const generateMariaDBCountFetcher = (
155
+ config: Record<string, unknown>,
156
+ tableName: string
157
+ ): string => {
158
+ const mariaConfig = config as MariaDBConfig
159
+ const database = mariaConfig.database
160
+
161
+ return `
162
+ async function getCount(req, res) {
163
+ const connection = getConnection()
164
+
165
+ try {
166
+ const { query, queryColumns, filters } = req.query
167
+ const conditions = []
168
+ const queryParams = []
169
+
170
+ if (query) {
171
+ let columns = []
172
+
173
+ if (queryColumns) {
174
+ // Use specified columns
175
+ columns = typeof queryColumns === 'string' ? JSON.parse(queryColumns) : (Array.isArray(queryColumns) ? queryColumns : [queryColumns])
176
+ } else {
177
+ // Fallback: Get all columns from information_schema
178
+ try {
179
+ const schemaRows = await connection.query(
180
+ \`SELECT COLUMN_NAME FROM information_schema.COLUMNS
181
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
182
+ ORDER BY ORDINAL_POSITION\`,
183
+ [${JSON.stringify(database)}, ${JSON.stringify(tableName)}]
184
+ )
185
+ columns = schemaRows.map(row => row.COLUMN_NAME)
186
+ } catch (schemaError) {
187
+ console.warn('Failed to fetch column names from information_schema:', schemaError.message)
188
+ // Continue without search if we can't get columns
189
+ }
190
+ }
191
+
192
+ if (columns.length > 0) {
193
+ const searchConditions = columns.map(col => \`CAST(\${col} AS CHAR) LIKE ?\`).join(' OR ')
194
+ conditions.push(\`(\${searchConditions})\`)
195
+ columns.forEach(() => queryParams.push(\`%\${query}%\`))
196
+ }
197
+ }
198
+
199
+ if (filters) {
200
+ const parsedFilters = JSON.parse(filters)
201
+ for (const filter of parsedFilters) {
202
+ conditions.push(\`\${filter.column} \${filter.operator} ?\`)
203
+ queryParams.push(filter.value)
204
+ }
205
+ }
206
+
207
+ let countSql = \`SELECT COUNT(*) as count FROM ${tableName}\`
208
+ if (conditions.length > 0) {
209
+ countSql += \` WHERE \${conditions.join(' AND ')}\`
210
+ }
211
+
212
+ const [rows] = await connection.execute(countSql, queryParams)
213
+ const count = rows[0].count
214
+
215
+ return res.status(200).json({
216
+ success: true,
217
+ count: count,
218
+ timestamp: Date.now()
219
+ })
220
+ } catch (error) {
221
+ console.error('Error getting count:', error)
222
+ return res.status(500).json({
223
+ success: false,
224
+ error: error.message || 'Failed to get count',
225
+ timestamp: Date.now()
226
+ })
227
+ }
228
+ }
229
+ `
230
+ }