@teleporthq/teleport-plugin-next-data-source 0.43.62 → 0.43.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/blog-post-categories.test.ts +131 -0
- package/__tests__/custom-page-transform.test.ts +119 -0
- package/__tests__/ecommerce-product-ratings.test.ts +155 -0
- package/__tests__/ecommerce-product-storefront-contract.test.ts +147 -0
- package/__tests__/filter-groups.test.ts +174 -0
- package/__tests__/product-reviews-structured-data.test.ts +196 -0
- package/dist/cjs/data-source-fetchers.d.ts +3 -3
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
- package/dist/cjs/fetchers/airtable.js +1 -1
- package/dist/cjs/fetchers/airtable.js.map +1 -1
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/cjs/fetchers/clickhouse.js +1 -1
- package/dist/cjs/fetchers/clickhouse.js.map +1 -1
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
- package/dist/cjs/fetchers/csv-file.js +3 -3
- package/dist/cjs/fetchers/csv-file.js.map +1 -1
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
- package/dist/cjs/fetchers/firestore.js +1 -1
- package/dist/cjs/fetchers/firestore.js.map +1 -1
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/cjs/fetchers/google-sheets.js +1 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +1 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +1 -1
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mongodb.js +1 -1
- package/dist/cjs/fetchers/mongodb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +1 -1
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +1 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redis.d.ts.map +1 -1
- package/dist/cjs/fetchers/redis.js +1 -1
- package/dist/cjs/fetchers/redis.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +2 -2
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +2 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +1 -1
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts +2 -2
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +1 -1
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +1 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/transformations/blog-post.d.ts +7 -1
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -2
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/category-taxonomy.d.ts +33 -0
- package/dist/cjs/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/cjs/transformations/category-taxonomy.js +42 -0
- package/dist/cjs/transformations/category-taxonomy.js.map +1 -0
- package/dist/cjs/transformations/custom-page.d.ts +18 -0
- package/dist/cjs/transformations/custom-page.d.ts.map +1 -0
- package/dist/cjs/transformations/custom-page.js +24 -0
- package/dist/cjs/transformations/custom-page.js.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +2 -35
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +23 -4
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +38 -8
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/transformations/shared-utils.d.ts.map +1 -1
- package/dist/cjs/transformations/shared-utils.js +1 -1
- package/dist/cjs/transformations/shared-utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +35 -3
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +116 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts +3 -3
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/airtable.d.ts.map +1 -1
- package/dist/esm/fetchers/airtable.js +2 -2
- package/dist/esm/fetchers/airtable.js.map +1 -1
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
- package/dist/esm/fetchers/clickhouse.js +2 -2
- package/dist/esm/fetchers/clickhouse.js.map +1 -1
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
- package/dist/esm/fetchers/csv-file.js +4 -4
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/firestore.d.ts.map +1 -1
- package/dist/esm/fetchers/firestore.js +2 -2
- package/dist/esm/fetchers/firestore.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +2 -2
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +2 -2
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +2 -2
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
- package/dist/esm/fetchers/mongodb.js +2 -2
- package/dist/esm/fetchers/mongodb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +2 -2
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +2 -2
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redis.d.ts.map +1 -1
- package/dist/esm/fetchers/redis.js +2 -2
- package/dist/esm/fetchers/redis.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +3 -3
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -3
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +2 -2
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts +2 -2
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +2 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +2 -2
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +1 -1
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/transformations/blog-post.d.ts +7 -1
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -2
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/category-taxonomy.d.ts +33 -0
- package/dist/esm/transformations/category-taxonomy.d.ts.map +1 -0
- package/dist/esm/transformations/category-taxonomy.js +37 -0
- package/dist/esm/transformations/category-taxonomy.js.map +1 -0
- package/dist/esm/transformations/custom-page.d.ts +18 -0
- package/dist/esm/transformations/custom-page.d.ts.map +1 -0
- package/dist/esm/transformations/custom-page.js +20 -0
- package/dist/esm/transformations/custom-page.js.map +1 -0
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +3 -36
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +23 -4
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +37 -7
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/transformations/shared-utils.d.ts.map +1 -1
- package/dist/esm/transformations/shared-utils.js +1 -1
- package/dist/esm/transformations/shared-utils.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +35 -3
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +112 -17
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/data-source-fetchers.ts +3 -3
- package/src/fetchers/airtable.ts +35 -36
- package/src/fetchers/clickhouse.ts +26 -36
- package/src/fetchers/csv-file.ts +13 -21
- package/src/fetchers/firestore.ts +41 -35
- package/src/fetchers/google-sheets.ts +15 -23
- package/src/fetchers/javascript.ts +11 -21
- package/src/fetchers/mariadb.ts +33 -44
- package/src/fetchers/mongodb.ts +66 -81
- package/src/fetchers/mysql.ts +33 -45
- package/src/fetchers/postgresql.ts +52 -62
- package/src/fetchers/redis.ts +6 -5
- package/src/fetchers/redshift.ts +29 -41
- package/src/fetchers/rest-api.ts +11 -21
- package/src/fetchers/supabase.ts +104 -41
- package/src/fetchers/teleport.ts +55 -66
- package/src/fetchers/turso.ts +27 -39
- package/src/index.ts +1 -1
- package/src/pagination-plugin.ts +3 -6
- package/src/transformations/blog-post.ts +28 -9
- package/src/transformations/category-taxonomy.ts +107 -0
- package/src/transformations/custom-page.ts +83 -0
- package/src/transformations/ecommerce-product.ts +177 -78
- package/src/transformations/index.ts +85 -11
- package/src/transformations/shared-utils.ts +9 -0
- package/src/utils.ts +191 -34
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as vm from 'vm'
|
|
2
|
+
import { generateCSVFileFetcher } from '../src/fetchers/csv-file'
|
|
3
|
+
import { generateDataSourceFetcher } from '../src/data-source-fetchers'
|
|
4
|
+
import {
|
|
5
|
+
collectFilterLeafConditions,
|
|
6
|
+
pruneFilterTree,
|
|
7
|
+
generateFilterTreeHelpersCode,
|
|
8
|
+
} from '../src/utils'
|
|
9
|
+
|
|
10
|
+
const CSV = [
|
|
11
|
+
'Index,Title,Category',
|
|
12
|
+
'1,Alpha,Commercial',
|
|
13
|
+
'2,Beta,Narrative',
|
|
14
|
+
'3,Gamma,Commercial',
|
|
15
|
+
'4,Delta,Music Video',
|
|
16
|
+
].join('\n')
|
|
17
|
+
|
|
18
|
+
const condition = (source: string, destination: string, operand = '=') => ({
|
|
19
|
+
type: 'condition',
|
|
20
|
+
source,
|
|
21
|
+
destination,
|
|
22
|
+
operand,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const group = (operator: 'and' | 'or', children: unknown[]) => ({
|
|
26
|
+
type: 'group',
|
|
27
|
+
operator,
|
|
28
|
+
children,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Loads the generated CSV API route and calls it the way Next.js would.
|
|
32
|
+
const runCsvHandler = async (filters?: unknown): Promise<Array<Record<string, string>>> => {
|
|
33
|
+
const code = generateCSVFileFetcher({
|
|
34
|
+
fileContent: CSV,
|
|
35
|
+
autoDetectHeader: true,
|
|
36
|
+
firstRowIsHeader: true,
|
|
37
|
+
})
|
|
38
|
+
const moduleExports: { default?: (req: unknown, res: unknown) => Promise<void> } = {}
|
|
39
|
+
const context = vm.createContext({ exports: moduleExports, console })
|
|
40
|
+
vm.runInContext(
|
|
41
|
+
code.replace(
|
|
42
|
+
'export default async function handler',
|
|
43
|
+
'exports.default = async function handler'
|
|
44
|
+
),
|
|
45
|
+
context
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
let payload: { data: Array<Record<string, string>> } | null = null
|
|
49
|
+
const res = {
|
|
50
|
+
status: () => res,
|
|
51
|
+
json: (data: never) => {
|
|
52
|
+
payload = data
|
|
53
|
+
return res
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
await moduleExports.default!(
|
|
57
|
+
{ query: filters === undefined ? {} : { filters: JSON.stringify(filters) }, method: 'GET' },
|
|
58
|
+
res
|
|
59
|
+
)
|
|
60
|
+
return payload!.data
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const titles = (rows: Array<Record<string, string>>) => rows.map((row) => row.col_1).sort()
|
|
64
|
+
|
|
65
|
+
describe('filter groups reach the data source', () => {
|
|
66
|
+
it('applies a condition wrapped in the inspector and-group', async () => {
|
|
67
|
+
// The regression: a group has no `.source`, so the old flat-only runtime
|
|
68
|
+
// skipped it and returned every row unfiltered.
|
|
69
|
+
expect(
|
|
70
|
+
titles(await runCsvHandler([group('and', [condition('Category', 'Commercial')])]))
|
|
71
|
+
).toEqual(['Alpha', 'Gamma'])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('leaves rows unfiltered only when there is genuinely no filter', async () => {
|
|
75
|
+
expect(await runCsvHandler()).toHaveLength(4)
|
|
76
|
+
expect(await runCsvHandler([])).toHaveLength(4)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('honours an or-group instead of flattening it into ands', async () => {
|
|
80
|
+
const rows = await runCsvHandler([
|
|
81
|
+
group('or', [condition('Category', 'Commercial'), condition('Category', 'Narrative')]),
|
|
82
|
+
])
|
|
83
|
+
expect(titles(rows)).toEqual(['Alpha', 'Beta', 'Gamma'])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('honours an or-group nested inside the root and-group', async () => {
|
|
87
|
+
const rows = await runCsvHandler([
|
|
88
|
+
group('and', [
|
|
89
|
+
condition('Title', 'Alpha', '!='),
|
|
90
|
+
group('or', [condition('Category', 'Commercial'), condition('Category', 'Narrative')]),
|
|
91
|
+
]),
|
|
92
|
+
])
|
|
93
|
+
expect(titles(rows)).toEqual(['Beta', 'Gamma'])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('still accepts the legacy flat and object filter shapes', async () => {
|
|
97
|
+
expect(titles(await runCsvHandler([condition('Category', 'Commercial')]))).toEqual([
|
|
98
|
+
'Alpha',
|
|
99
|
+
'Gamma',
|
|
100
|
+
])
|
|
101
|
+
expect(titles(await runCsvHandler({ Category: 'Commercial' } as never))).toEqual([
|
|
102
|
+
'Alpha',
|
|
103
|
+
'Gamma',
|
|
104
|
+
])
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('every data source emits the filter-tree runtime', () => {
|
|
109
|
+
const dataSources: Array<[string, Record<string, unknown>]> = [
|
|
110
|
+
['postgresql', { host: 'h', port: 5432, database: 'd', user: 'u', password: 'p' }],
|
|
111
|
+
['mysql', { host: 'h', port: 3306, database: 'd', user: 'u', password: 'p' }],
|
|
112
|
+
['mariadb', { host: 'h', port: 3306, database: 'd', user: 'u', password: 'p' }],
|
|
113
|
+
['amazon-redshift', { host: 'h', port: 5439, database: 'd', user: 'u', password: 'p' }],
|
|
114
|
+
['clickhouse', { url: 'http://h', username: 'u', password: 'p', database: 'd' }],
|
|
115
|
+
['turso', { databaseUrl: 'libsql://x', token: 't' }],
|
|
116
|
+
['mongodb', { connectionString: 'mongodb://h', database: 'd' }],
|
|
117
|
+
['firestore', { serviceAccount: 'teleporthq.secrets.FIRESTORE_SERVICE_ACCOUNT' }],
|
|
118
|
+
['supabase', { supabaseUrl: 'https://x.supabase.co', serviceRoleKey: 'k' }],
|
|
119
|
+
['airtable', { personalAccessToken: 'k', baseId: 'b' }],
|
|
120
|
+
['redis', { host: 'h', port: 6379 }],
|
|
121
|
+
['rest-api', { url: 'https://example.com/items' }],
|
|
122
|
+
['javascript', { code: 'return []' }],
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
it.each(dataSources)('%s understands filter groups', (type, config) => {
|
|
126
|
+
const code = generateDataSourceFetcher({ type, config } as never, 'items')
|
|
127
|
+
expect(code).toContain('normalizeFilterTree')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('normalizes groups, legacy conditions and object maps into one tree', () => {
|
|
131
|
+
const context: { normalizeFilterTree?: (parsed: unknown) => unknown } = {}
|
|
132
|
+
vm.runInContext(
|
|
133
|
+
generateFilterTreeHelpersCode().replace(/^const /gm, 'this.'),
|
|
134
|
+
vm.createContext(context)
|
|
135
|
+
)
|
|
136
|
+
const normalize = context.normalizeFilterTree!
|
|
137
|
+
|
|
138
|
+
expect(normalize([group('and', [condition('Category', 'Commercial')])])).toEqual(
|
|
139
|
+
group('and', [condition('Category', 'Commercial')])
|
|
140
|
+
)
|
|
141
|
+
expect(normalize([{ source: 'Category', destination: 'Commercial' }])).toEqual(
|
|
142
|
+
group('and', [condition('Category', 'Commercial')])
|
|
143
|
+
)
|
|
144
|
+
expect(normalize({ Category: 'Commercial' })).toEqual(
|
|
145
|
+
group('and', [condition('Category', 'Commercial')])
|
|
146
|
+
)
|
|
147
|
+
expect(normalize([])).toBeNull()
|
|
148
|
+
expect(normalize([group('and', [])])).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('generator-side tree walkers', () => {
|
|
153
|
+
const tree = [
|
|
154
|
+
group('and', [
|
|
155
|
+
condition('Category', 'Commercial'),
|
|
156
|
+
group('or', [condition('Year', '2024'), condition('Year', '2025')]),
|
|
157
|
+
]),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
it('finds conditions nested inside groups', () => {
|
|
161
|
+
// The initialData gate and the dynamic-destination checks read destinations
|
|
162
|
+
// through this; asking a group for `.destination` silently answers "static".
|
|
163
|
+
expect(collectFilterLeafConditions(tree)).toHaveLength(3)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('prunes rejected conditions without collapsing the group structure', () => {
|
|
167
|
+
const pruned = pruneFilterTree(tree, (c) => (c as { source: string }).source !== 'Year')
|
|
168
|
+
expect(pruned).toEqual([group('and', [condition('Category', 'Commercial')])])
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('drops a group left empty by pruning', () => {
|
|
172
|
+
expect(pruneFilterTree(tree, () => false)).toEqual([])
|
|
173
|
+
})
|
|
174
|
+
})
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { generateEcommerceProductTransformationCode } from '../src/transformations/ecommerce-product'
|
|
2
|
+
import { generateSharedTransformationCode } from '../src/transformations/shared-utils'
|
|
3
|
+
import { REVIEWS_PER_PRODUCT, getTransformWrapperCode } from '../src/transformations'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The individual reviews a product page puts into its JSON-LD.
|
|
7
|
+
*
|
|
8
|
+
* Two separate claims are tested, because they fail in different ways:
|
|
9
|
+
*
|
|
10
|
+
* 1. **The transform exposes them.** A missing `reviews` field is invisible —
|
|
11
|
+
* the JSON-LD's `reviewList` leaf simply drops the property and the page
|
|
12
|
+
* ships with stars but no snippets, looking entirely healthy.
|
|
13
|
+
* 2. **They are only fetched for a SINGLE product.** A listing of 24 products
|
|
14
|
+
* would otherwise run a review query and inline 120 rows into a page that
|
|
15
|
+
* emits no Product markup to put them in. The `records.length === 1`
|
|
16
|
+
* heuristic is the same one the related-items lookup already uses.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const buildProduct = (): ((record: unknown, options?: unknown) => Record<string, unknown>) => {
|
|
20
|
+
const code =
|
|
21
|
+
generateSharedTransformationCode() + '\n' + generateEcommerceProductTransformationCode({})
|
|
22
|
+
return new Function(code + '\nreturn buildEcommerceProduct;')() as (
|
|
23
|
+
record: unknown,
|
|
24
|
+
options?: unknown
|
|
25
|
+
) => Record<string, unknown>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PRODUCT = { id: 'p1', name: 'P', slug: 'p', price: '10.00', currency: 'USD' }
|
|
29
|
+
|
|
30
|
+
const REVIEWS = [
|
|
31
|
+
{ author: 'Sam', rating: 5, body: 'Perfect', datePublished: '2026-01-02T00:00:00.000Z' },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
describe('product transform — reviews', () => {
|
|
35
|
+
it('passes the fetched reviews through untouched', () => {
|
|
36
|
+
const product = buildProduct()(PRODUCT, { reviewsByProductId: { p1: REVIEWS } })
|
|
37
|
+
expect(product.reviews).toEqual(REVIEWS)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// A listing, an older baked transform, a store with reviews disabled: all
|
|
41
|
+
// three arrive here as "no map", and all three have to mean an empty list
|
|
42
|
+
// rather than an undefined the JSON-LD leaf would have to guard again.
|
|
43
|
+
it('is an empty array when nothing was fetched', () => {
|
|
44
|
+
expect(buildProduct()(PRODUCT, {}).reviews).toEqual([])
|
|
45
|
+
expect(buildProduct()(PRODUCT, { reviewsByProductId: {} }).reviews).toEqual([])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('never borrows another product reviews', () => {
|
|
49
|
+
const product = buildProduct()(PRODUCT, { reviewsByProductId: { other: REVIEWS } })
|
|
50
|
+
expect(product.reviews).toEqual([])
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('transform wrapper — when reviews are fetched', () => {
|
|
55
|
+
const wrapper = getTransformWrapperCode('teleport_products')
|
|
56
|
+
|
|
57
|
+
it('fetches them only for a single-record request', () => {
|
|
58
|
+
expect(wrapper).toContain('getProductReviewsMap')
|
|
59
|
+
// The same guard the related-items lookup uses.
|
|
60
|
+
expect(wrapper).toMatch(/records\.length === 1[\s\S]*getProductReviewsMap/)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('caps how many it inlines', () => {
|
|
64
|
+
expect(wrapper).toContain(
|
|
65
|
+
`getProductReviewsMap(getClientFn, __variantPids, ${REVIEWS_PER_PRODUCT})`
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('hands them to the transform', () => {
|
|
70
|
+
expect(wrapper).toContain('reviewsByProductId: reviewsByProductId')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('is not emitted for a blog listing', () => {
|
|
74
|
+
expect(getTransformWrapperCode('teleport_blog_posts')).not.toContain('getProductReviewsMap')
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('the reviews query', () => {
|
|
79
|
+
const source = generateEcommerceProductTransformationCode({})
|
|
80
|
+
|
|
81
|
+
// 'approved' is a fixed literal and must stay one: a pending review is one the
|
|
82
|
+
// merchant has not agreed to publish, and a rejected one is a review they
|
|
83
|
+
// actively removed. Neither belongs in the shop's structured data.
|
|
84
|
+
it('reads approved reviews only', () => {
|
|
85
|
+
expect(source).toContain("status = 'approved'")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Google requires `author.name` on a review snippet; an anonymous row would
|
|
89
|
+
// produce a Review object it rejects.
|
|
90
|
+
it('skips rows with no reviewer name and no rating', () => {
|
|
91
|
+
expect(source).toContain('rating IS NOT NULL')
|
|
92
|
+
expect(source).toContain('reviewer_name IS NOT NULL')
|
|
93
|
+
expect(source).toContain("btrim(reviewer_name) <> ''")
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns the newest first', () => {
|
|
97
|
+
expect(source).toContain('ORDER BY created_at DESC')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The emitted fetcher, RUN against a fake client.
|
|
103
|
+
*
|
|
104
|
+
* The row → review mapping is the part the JSON-LD depends on key-for-key, and
|
|
105
|
+
* it is the part no source-text assertion can prove: a renamed field produces a
|
|
106
|
+
* `Review` object missing a required property, and the page still builds.
|
|
107
|
+
*/
|
|
108
|
+
describe('getProductReviewsMap', () => {
|
|
109
|
+
const loadFetcher = (
|
|
110
|
+
rows: Array<Record<string, unknown>>
|
|
111
|
+
): ((
|
|
112
|
+
getClientFn: () => unknown,
|
|
113
|
+
productIds: unknown,
|
|
114
|
+
perProduct?: number
|
|
115
|
+
) => Promise<Record<string, unknown[]>>) => {
|
|
116
|
+
const code =
|
|
117
|
+
generateSharedTransformationCode() + '\n' + generateEcommerceProductTransformationCode({})
|
|
118
|
+
const fn = new Function(code + '\nreturn getProductReviewsMap;')() as (
|
|
119
|
+
getClientFn: () => unknown,
|
|
120
|
+
productIds: unknown,
|
|
121
|
+
perProduct?: number
|
|
122
|
+
) => Promise<Record<string, unknown[]>>
|
|
123
|
+
const client = {
|
|
124
|
+
connect: async () => undefined,
|
|
125
|
+
query: async () => ({ rows }),
|
|
126
|
+
end: async () => undefined,
|
|
127
|
+
}
|
|
128
|
+
return (_getClientFn, productIds, perProduct) => fn(() => client, productIds, perProduct)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
it('shapes a row into exactly what the JSON-LD leaf reads', async () => {
|
|
132
|
+
const fetch = loadFetcher([
|
|
133
|
+
{
|
|
134
|
+
product_id: 'p1',
|
|
135
|
+
reviewer_name: ' Sam ',
|
|
136
|
+
rating: '5',
|
|
137
|
+
content: 'Perfect',
|
|
138
|
+
created_at: '2026-01-02T00:00:00.000Z',
|
|
139
|
+
},
|
|
140
|
+
])
|
|
141
|
+
const map = await fetch(() => undefined, ['p1'])
|
|
142
|
+
expect(map.p1).toEqual([
|
|
143
|
+
{
|
|
144
|
+
author: 'Sam',
|
|
145
|
+
rating: 5,
|
|
146
|
+
body: 'Perfect',
|
|
147
|
+
datePublished: '2026-01-02T00:00:00.000Z',
|
|
148
|
+
},
|
|
149
|
+
])
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('caps each product at the requested number', async () => {
|
|
153
|
+
const rows = Array.from({ length: 9 }, (_, index) => ({
|
|
154
|
+
product_id: 'p1',
|
|
155
|
+
reviewer_name: 'R' + index,
|
|
156
|
+
rating: 4,
|
|
157
|
+
content: 'x',
|
|
158
|
+
created_at: '2026-01-02T00:00:00.000Z',
|
|
159
|
+
}))
|
|
160
|
+
const map = await loadFetcher(rows)(() => undefined, ['p1'], 3)
|
|
161
|
+
expect(map.p1).toHaveLength(3)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('tolerates a null review body and a null date', async () => {
|
|
165
|
+
const map = await loadFetcher([
|
|
166
|
+
{ product_id: 'p1', reviewer_name: 'Sam', rating: 4, content: null, created_at: null },
|
|
167
|
+
])(() => undefined, ['p1'])
|
|
168
|
+
expect(map.p1[0]).toMatchObject({ body: '', datePublished: null })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('returns an empty map for no ids', async () => {
|
|
172
|
+
expect(await loadFetcher([])(() => undefined, [])).toEqual({})
|
|
173
|
+
expect(await loadFetcher([])(() => undefined, null)).toEqual({})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// The reviews table may not exist at all — reviews are an opt-in feature.
|
|
177
|
+
it('returns an empty map when the query throws', async () => {
|
|
178
|
+
const code =
|
|
179
|
+
generateSharedTransformationCode() + '\n' + generateEcommerceProductTransformationCode({})
|
|
180
|
+
const fn = new Function(code + '\nreturn getProductReviewsMap;')() as (
|
|
181
|
+
getClientFn: () => unknown,
|
|
182
|
+
productIds: unknown
|
|
183
|
+
) => Promise<Record<string, unknown[]>>
|
|
184
|
+
const map = await fn(
|
|
185
|
+
() => ({
|
|
186
|
+
connect: async () => undefined,
|
|
187
|
+
query: async () => {
|
|
188
|
+
throw new Error('relation does not exist')
|
|
189
|
+
},
|
|
190
|
+
end: async () => undefined,
|
|
191
|
+
}),
|
|
192
|
+
['p1']
|
|
193
|
+
)
|
|
194
|
+
expect(map).toEqual({})
|
|
195
|
+
})
|
|
196
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DataSourceType, UIDLDataSource } from '@teleporthq/teleport-types';
|
|
2
2
|
import { generateRawQueryFetcher, parseQueryTemplateVariables } from './fetchers';
|
|
3
|
-
import type {
|
|
3
|
+
import type { EntityTransformOptions } from './transformations';
|
|
4
4
|
import type { DataSourceServerCacheOptions } from './cache/types';
|
|
5
5
|
export { generateRawQueryFetcher, parseQueryTemplateVariables };
|
|
6
6
|
interface DataSourceFetcherDependencies {
|
|
@@ -9,10 +9,10 @@ interface DataSourceFetcherDependencies {
|
|
|
9
9
|
}
|
|
10
10
|
export declare const isSupabaseConnectionStringFallback: (config: Record<string, unknown>) => boolean;
|
|
11
11
|
export declare const getDataSourceDependencies: (type: DataSourceType, config?: Record<string, unknown>) => DataSourceFetcherDependencies;
|
|
12
|
-
export declare function generateDataSourceFetcher(dataSource: UIDLDataSource, tableName: string, transformOptions?:
|
|
12
|
+
export declare function generateDataSourceFetcher(dataSource: UIDLDataSource, tableName: string, transformOptions?: EntityTransformOptions): string;
|
|
13
13
|
/**
|
|
14
14
|
* Generates a fetcher with both a core fetchData function (for server-side use in getStaticProps)
|
|
15
15
|
* and an API handler (for client-side use)
|
|
16
16
|
*/
|
|
17
|
-
export declare function generateDataSourceFetcherWithCore(dataSource: UIDLDataSource, tableName: string, isApiRoute?: boolean, transformOptions?:
|
|
17
|
+
export declare function generateDataSourceFetcherWithCore(dataSource: UIDLDataSource, tableName: string, isApiRoute?: boolean, transformOptions?: EntityTransformOptions, cacheOptions?: DataSourceServerCacheOptions): string;
|
|
18
18
|
//# sourceMappingURL=data-source-fetchers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-source-fetchers.d.ts","sourceRoot":"","sources":["../../src/data-source-fetchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3E,OAAO,EA6BL,uBAAuB,EACvB,2BAA2B,EAC5B,MAAM,YAAY,CAAA;AAGnB,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"data-source-fetchers.d.ts","sourceRoot":"","sources":["../../src/data-source-fetchers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3E,OAAO,EA6BL,uBAAuB,EACvB,2BAA2B,EAC5B,MAAM,YAAY,CAAA;AAGnB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE/D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AAEjE,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,CAAA;AAE/D,UAAU,6BAA6B;IACrC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,eAAO,MAAM,kCAAkC,WAAY,OAAO,MAAM,EAAE,OAAO,CAAC,KAAG,OAEpF,CAAA;AAED,eAAO,MAAM,yBAAyB,SAC9B,cAAc,WACX,OAAO,MAAM,EAAE,OAAO,CAAC,KAC/B,6BA4BF,CAAA;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,MAAM,EACjB,gBAAgB,GAAE,sBAA2B,GAC5C,MAAM,CAsKR;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,OAAe,EAC3B,gBAAgB,GAAE,sBAA2B,EAC7C,YAAY,CAAC,EAAE,4BAA4B,GAC1C,MAAM,CAqJR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-source-fetchers.js","sourceRoot":"","sources":["../../src/data-source-fetchers.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,uCA+BmB;AAOV,wGATP,kCAAuB,OASO;AAAE,4GARhC,sCAA2B,OAQgC;AAN7D,2CAAqD;AACrD,mDAAuD;AAEvD,+DAAuD;AAUhD,IAAM,kCAAkC,GAAG,UAAC,MAA+B;IAChF,OAAO,CAAC,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,gBAAgB,CAAA,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,WAAW,CAAA,CAAA;AAC3D,CAAC,CAAA;AAFY,QAAA,kCAAkC,sCAE9C;AAEM,IAAM,yBAAyB,GAAG,UACvC,IAAoB,EACpB,MAAgC;IAEhC,yDAAyD;IACzD,IAAI,IAAI,KAAK,UAAU,IAAI,MAAM,IAAI,IAAA,0CAAkC,EAAC,MAAM,CAAC,EAAE;QAC/E,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAA;KACpD;IAED,IAAM,aAAa,GAA0D;QAC3E,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE;QAC/D,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACxD,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACvD,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QAC1D,iBAAiB,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QAC/D,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QAC1D,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACzD,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACtD,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACtD,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACnE,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,oBAAoB,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACxE,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE;QAC7D,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,uBAAuB,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QACzE,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;QAC/D,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE;QACpD,eAAe,EAAE,EAAE,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE;QACpE,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE;QACpD,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE;KACvD,CAAA;IAED,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;AAC5B,CAAC,CAAA;AA/BY,QAAA,yBAAyB,6BA+BrC;AAED,SAAgB,yBAAyB,CACvC,UAA0B,EAC1B,SAAiB,EACjB,
|
|
1
|
+
{"version":3,"file":"data-source-fetchers.js","sourceRoot":"","sources":["../../src/data-source-fetchers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airtable.d.ts","sourceRoot":"","sources":["../../../src/fetchers/airtable.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"airtable.d.ts","sourceRoot":"","sources":["../../../src/fetchers/airtable.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,sBAAsB,WACzB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAcpC,CAAA;AAQD,eAAO,MAAM,uBAAuB,WAC1B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MAqNF,CAAA"}
|
|
@@ -19,7 +19,7 @@ var generateAirtableFetcher = function (config, tableName) {
|
|
|
19
19
|
var airtableConfig = config;
|
|
20
20
|
var baseId = airtableConfig.baseId;
|
|
21
21
|
var personalAccessToken = airtableConfig.personalAccessToken;
|
|
22
|
-
return "import fetch from 'node-fetch'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n// Escape a string literal for Airtable formula grammar. Strings use\n// single quotes; embedded single-quote must be backslash-escaped.\n// Backslash itself also escapes.\nconst escapeAirtableString = (s) => {\n return \"'\" + String(s).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\") + \"'\"\n}\n\n// Escape an Airtable field reference name for use inside curly braces.\n// Only close-brace and backslash are special inside the braces.\nconst escapeAirtableFieldRef = (name) => {\n return String(name).replace(/\\\\/g, '\\\\\\\\').replace(/}/g, '\\\\}')\n}\n\nconst buildAirtableSearchFormula = (rawQuery, rawQueryColumns) => {\n if (typeof rawQuery !== 'string' || rawQuery.trim() === '') return ''\n let cols = []\n if (rawQueryColumns) {\n const parsed = safeJSONParse(rawQueryColumns)\n cols = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n }\n const validCols = cols.filter((c) => typeof c === 'string' && c.length > 0)\n if (validCols.length === 0) return ''\n const literal = escapeAirtableString(rawQuery)\n const parts = validCols.map(\n (col) => 'SEARCH(' + literal + ', {' + escapeAirtableFieldRef(col) + '})'\n )\n return parts.length === 1 ? parts[0] : 'OR(' + parts.join(',') + ')'\n}\n\nexport default async function handler(req, res) {\n try {\n const { query, queryColumns, view, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query\n\n const queryParams = new URLSearchParams()\n const formulaParts = []\n\n if (view) {\n queryParams.append('view', view)\n }\n \n // Handle sorts - new array format\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n parsedSorts.forEach((sort, index) => {\n if (!sort.field) return\n queryParams.append(`sort[${index}][field]`, sort.field)\n queryParams.append(`sort[${index}][direction]`, (sort.order || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')\n })\n }\n } else if (sortBy) {\n queryParams.append('sort[0][field]', sortBy)\n queryParams.append('sort[0][direction]', (sortOrder || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')\n }\n \n const perPageValue = limit || perPage || 100\n queryParams.append('pageSize', Math.min(parseInt(perPageValue), 100).toString())\n \n const formatAirtableValue = (value) => {\n if (typeof value === 'string') {\n return `'${value.replace(/'/g, \"\\\\'\")}'`\n } else if (typeof value === 'number') {\n return String(value)\n } else if (typeof value === 'boolean') {\n return value ? 'TRUE()' : 'FALSE()'\n }\n return `'${String(value)}'`\n }\n \n if (filters) {\n const
|
|
22
|
+
return "import fetch from 'node-fetch'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n// Escape a string literal for Airtable formula grammar. Strings use\n// single quotes; embedded single-quote must be backslash-escaped.\n// Backslash itself also escapes.\nconst escapeAirtableString = (s) => {\n return \"'\" + String(s).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\") + \"'\"\n}\n\n// Escape an Airtable field reference name for use inside curly braces.\n// Only close-brace and backslash are special inside the braces.\nconst escapeAirtableFieldRef = (name) => {\n return String(name).replace(/\\\\/g, '\\\\\\\\').replace(/}/g, '\\\\}')\n}\n\nconst buildAirtableSearchFormula = (rawQuery, rawQueryColumns) => {\n if (typeof rawQuery !== 'string' || rawQuery.trim() === '') return ''\n let cols = []\n if (rawQueryColumns) {\n const parsed = safeJSONParse(rawQueryColumns)\n cols = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n }\n const validCols = cols.filter((c) => typeof c === 'string' && c.length > 0)\n if (validCols.length === 0) return ''\n const literal = escapeAirtableString(rawQuery)\n const parts = validCols.map(\n (col) => 'SEARCH(' + literal + ', {' + escapeAirtableFieldRef(col) + '})'\n )\n return parts.length === 1 ? parts[0] : 'OR(' + parts.join(',') + ')'\n}\n\nexport default async function handler(req, res) {\n try {\n const { query, queryColumns, view, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query\n\n const queryParams = new URLSearchParams()\n const formulaParts = []\n\n if (view) {\n queryParams.append('view', view)\n }\n \n // Handle sorts - new array format\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n parsedSorts.forEach((sort, index) => {\n if (!sort.field) return\n queryParams.append(`sort[${index}][field]`, sort.field)\n queryParams.append(`sort[${index}][direction]`, (sort.order || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')\n })\n }\n } else if (sortBy) {\n queryParams.append('sort[0][field]', sortBy)\n queryParams.append('sort[0][direction]', (sortOrder || '').toLowerCase().startsWith('desc') ? 'desc' : 'asc')\n }\n \n const perPageValue = limit || perPage || 100\n queryParams.append('pageSize', Math.min(parseInt(perPageValue), 100).toString())\n \n const formatAirtableValue = (value) => {\n if (typeof value === 'string') {\n return `'${value.replace(/'/g, \"\\\\'\")}'`\n } else if (typeof value === 'number') {\n return String(value)\n } else if (typeof value === 'boolean') {\n return value ? 'TRUE()' : 'FALSE()'\n }\n return `'${String(value)}'`\n }\n \n if (filters) {\n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n \n if (filterTree) {\n const buildCondition = (condition) => {\n const field = condition.source\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n const arrayConditions = value.map((v) => `{${field}}=${formatAirtableValue(v)}`)\n return arrayConditions.length > 1\n ? `OR(${arrayConditions.join(',')})`\n : arrayConditions[0]\n }\n \n const operatorMap = {\n '=': '=',\n '!=': '!=',\n '>': '>',\n '<': '<',\n '>=': '>=',\n '<=': '<=',\n }\n const airtableOp = operatorMap[operand] || '='\n return `{${field}}${airtableOp}${formatAirtableValue(value)}`\n }\n \n // Airtable spells its logic as AND(a,b) / OR(a,b) rather than infix, so\n // the tree is walked here instead of with buildFilterTreeClause.\n const buildNode = (node) => {\n if (node.type === 'group') {\n const parts = node.children.map(buildNode).filter(Boolean)\n if (parts.length === 0) return null\n if (parts.length === 1) return parts[0]\n return node.operator === 'or'\n ? `OR(${parts.join(',')})`\n : `AND(${parts.join(',')})`\n }\n return buildCondition(node)\n }\n \n const filterFormula = buildNode(filterTree)\n if (filterFormula) {\n formulaParts.push(filterFormula)\n }\n }\n }\n\n // Apply the { query, queryColumns } search contract via the\n // Airtable SEARCH() function. Search-term string literal escaping\n // protects against single-quote injection; field-ref escaping\n // protects against close-brace or backslash breaking out of the\n // {field} reference.\n const searchFormula = buildAirtableSearchFormula(query, queryColumns)\n if (searchFormula) {\n formulaParts.push(searchFormula)\n }\n\n if (formulaParts.length === 1) {\n queryParams.append('filterByFormula', formulaParts[0])\n } else if (formulaParts.length > 1) {\n queryParams.append('filterByFormula', 'AND(' + formulaParts.join(',') + ')')\n }\n \n let url = `https://api.airtable.com/v0/").concat(baseId, "/${encodeURIComponent('").concat(tableName, "')}`\n if (queryParams.toString()) {\n url += `?${queryParams.toString()}`\n }\n \n const allRecords = []\n let airtableOffset\n const skipValue = offsetParam !== undefined ? parseInt(offsetParam) : (page ? (parseInt(page) - 1) * parseInt(perPageValue) : 0)\n const totalRecordsNeeded = skipValue + parseInt(perPageValue)\n \n do {\n const fetchUrl = airtableOffset ? `${url}&offset=${airtableOffset}` : url\n const response = await fetch(fetchUrl, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ").concat((0, utils_1.replaceSecretReference)(personalAccessToken, {
|
|
23
23
|
templateLiteral: true,
|
|
24
24
|
}), "`,\n 'Content-Type': 'application/json'\n }\n })\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}))\n return res.status(response.status).json({\n success: false,\n error: errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n timestamp: Date.now()\n })\n }\n \n const data = await response.json()\n allRecords.push(...data.records)\n airtableOffset = data.offset\n \n if (allRecords.length >= totalRecordsNeeded || !airtableOffset) {\n break\n }\n } while (airtableOffset)\n \n const paginatedRecords = allRecords.slice(skipValue, skipValue + parseInt(perPageValue))\n \n const formattedRecords = paginatedRecords.map((record) => ({\n id: record.id,\n ...record.fields,\n createdTime: record.createdTime\n }))\n \n const safeData = JSON.parse(JSON.stringify(formattedRecords, dateReplacer))\n \n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Airtable fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n }\n}\n");
|
|
25
25
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"airtable.js","sourceRoot":"","sources":["../../../src/fetchers/airtable.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"airtable.js","sourceRoot":"","sources":["../../../src/fetchers/airtable.ts"],"names":[],"mappings":";;;AAAA,kCAKiB;AAEV,IAAM,sBAAsB,GAAG,UACpC,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;KAClE;IAED,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACtF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAA;KACjE;IAED,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,OAAO,MAAM,CAAC,mBAAmB,KAAK,QAAQ,EAAE;QACjF,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,4CAA4C,EAAE,CAAA;KAC/E;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC,CAAA;AAhBY,QAAA,sBAAsB,0BAgBlC;AAQM,IAAM,uBAAuB,GAAG,UACrC,MAA+B,EAC/B,SAAiB;IAEjB,IAAM,cAAc,GAAG,MAAwB,CAAA;IAC/C,IAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAA;IACpC,IAAM,mBAAmB,GAAG,cAAc,CAAC,mBAAmB,CAAA;IAE9D,OAAO,4CAEP,IAAA,iCAAyB,GAAE,iBAE3B,IAAA,qCAA6B,GAAE,iBAE/B,IAAA,iCAAyB,GAAE,irKAyIiB,MAAM,oCAA2B,SAAS,wkBAepD,IAAA,8BAAsB,EAAC,mBAAmB,EAAE;QACpE,eAAe,EAAE,IAAI;KACtB,CAAC,+1CA+CX,CAAA;AACD,CAAC,CAAA;AAxNY,QAAA,uBAAuB,2BAwNnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../../src/fetchers/clickhouse.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../../src/fetchers/clickhouse.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,wBAAwB,WAC3B,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAkBpC,CAAA;AAQD,eAAO,MAAM,yBAAyB,WAC5B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA+JF,CAAA"}
|
|
@@ -23,7 +23,7 @@ var generateClickHouseFetcher = function (config, tableName) {
|
|
|
23
23
|
var url = clickConfig.url;
|
|
24
24
|
var username = clickConfig.username;
|
|
25
25
|
var password = clickConfig.password;
|
|
26
|
-
return "import { createClient } from '@clickhouse/client'\n\nlet client = null\n\nconst getClient = () => {\n if (client) return client\n \n client = createClient({\n url: ".concat(JSON.stringify(url), ",\n username: ").concat(JSON.stringify(username), ",\n password: ").concat((0, utils_1.replaceSecretReference)(password), "\n })\n \n return client\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n try {\n const client = getClient()\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n const conditions = []\n \n const formatClickHouseValue = (value) => {\n if (value === null || value === undefined) return 'NULL'\n if (typeof value === 'string') return `'${value.replace(/'/g, \"\\\\'\")}'`\n if (typeof value === 'boolean') return value ? '1' : '0'\n return String(value)\n }\n\n // Helper to sanitize identifier (prevent SQL injection in column names)\n const sanitizeIdentifier = (name) => {\n // Only allow alphanumeric and underscore\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new Error(`Invalid identifier: ${name}`)\n }\n return `\\`${name}\\``\n }\n\n if (query) {\n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n const columns = Array.isArray(parsed) ? parsed : [parsed]\n // Escape the search term into a ClickHouse string literal so\n // single quotes and backslashes can't break out of the literal\n // or execute injected SQL. Column identifiers are sanitized\n // against the identifier regex above.\n const queryLiteral = formatClickHouseValue(String(query))\n const searchConditions = columns.map(\n (col) =>\n `positionCaseInsensitiveUTF8(toString(${sanitizeIdentifier(col)}), ${queryLiteral}) > 0`\n )\n conditions.push(`(${searchConditions.join(' OR ')})`)\n } else {\n // Note: Without queryColumns, ClickHouse can't search all columns efficiently\n // Users should provide queryColumns for optimal search performance\n console.warn('Search query provided without queryColumns - search may not work as expected')\n }\n }\n \n if (filters) {\n const
|
|
26
|
+
return "import { createClient } from '@clickhouse/client'\n\nlet client = null\n\nconst getClient = () => {\n if (client) return client\n \n client = createClient({\n url: ".concat(JSON.stringify(url), ",\n username: ").concat(JSON.stringify(username), ",\n password: ").concat((0, utils_1.replaceSecretReference)(password), "\n })\n \n return client\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n try {\n const client = getClient()\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n const conditions = []\n \n const formatClickHouseValue = (value) => {\n if (value === null || value === undefined) return 'NULL'\n if (typeof value === 'string') return `'${value.replace(/'/g, \"\\\\'\")}'`\n if (typeof value === 'boolean') return value ? '1' : '0'\n return String(value)\n }\n\n // Helper to sanitize identifier (prevent SQL injection in column names)\n const sanitizeIdentifier = (name) => {\n // Only allow alphanumeric and underscore\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new Error(`Invalid identifier: ${name}`)\n }\n return `\\`${name}\\``\n }\n\n if (query) {\n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n const columns = Array.isArray(parsed) ? parsed : [parsed]\n // Escape the search term into a ClickHouse string literal so\n // single quotes and backslashes can't break out of the literal\n // or execute injected SQL. Column identifiers are sanitized\n // against the identifier regex above.\n const queryLiteral = formatClickHouseValue(String(query))\n const searchConditions = columns.map(\n (col) =>\n `positionCaseInsensitiveUTF8(toString(${sanitizeIdentifier(col)}), ${queryLiteral}) > 0`\n )\n conditions.push(`(${searchConditions.join(' OR ')})`)\n } else {\n // Note: Without queryColumns, ClickHouse can't search all columns efficiently\n // Users should provide queryColumns for optimal search performance\n console.warn('Search query provided without queryColumns - search may not work as expected')\n }\n }\n \n if (filters) {\n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n \n if (filterTree) {\n const buildCondition = (condition) => {\n const field = sanitizeIdentifier(condition.source)\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n const formattedValues = value.map(formatClickHouseValue).join(', ')\n return operand === '!='\n ? `${field} NOT IN (${formattedValues})`\n : `${field} IN (${formattedValues})`\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n return `${field} ${sqlOperator} ${formatClickHouseValue(value)}`\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\n }\n }\n \n let sql = `SELECT * FROM ").concat(tableName, "`\n \n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`\n }\n \n // Handle sorts - new array format\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n const orderClauses = parsedSorts.map((sort) => {\n if (!sort.field) return null\n const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n return `${sanitizeIdentifier(sort.field)} ${order}`\n }).filter(Boolean)\n\n if (orderClauses.length > 0) {\n sql += ` ORDER BY ${orderClauses.join(', ')}`\n }\n }\n } else if (sortBy) {\n sql += ` ORDER BY ${sanitizeIdentifier(sortBy)} ${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}`\n }\n \n const limitValue = limit || perPage\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n \n if (limitValue) {\n sql += ` LIMIT ${limitValue}`\n }\n \n if (offsetValue !== undefined) {\n sql += ` OFFSET ${offsetValue}`\n }\n \n const result = await client.query({ query: sql })\n const resultResponse = await result.json()\n const safeData = JSON.parse(JSON.stringify(resultResponse.data, dateReplacer))\n\n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('ClickHouse fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n }\n}\n");
|
|
27
27
|
};
|
|
28
28
|
exports.generateClickHouseFetcher = generateClickHouseFetcher;
|
|
29
29
|
//# sourceMappingURL=clickhouse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../../src/fetchers/clickhouse.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"clickhouse.js","sourceRoot":"","sources":["../../../src/fetchers/clickhouse.ts"],"names":[],"mappings":";;;AAAA,kCAKiB;AAEV,IAAM,wBAAwB,GAAG,UACtC,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;KAClE;IAED,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE;QACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;KAC/D;IAED,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE;QAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAA;KACpE;IAED,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE;QAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAA;KACpE;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC,CAAA;AApBY,QAAA,wBAAwB,4BAoBpC;AAQM,IAAM,yBAAyB,GAAG,UACvC,MAA+B,EAC/B,SAAiB;IAEjB,IAAM,WAAW,GAAG,MAA0B,CAAA;IAC9C,IAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAA;IAC3B,IAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAA;IACrC,IAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAA;IAErC,OAAO,oLAQE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,8BACd,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,8BACxB,IAAA,8BAAsB,EAAC,QAAQ,CAAC,+CAM9C,IAAA,iCAAyB,GAAE,iBAE3B,IAAA,qCAA6B,GAAE,iBAE/B,IAAA,iCAAyB,GAAE,6oGA+EG,SAAS,6sDAqDxC,CAAA;AACD,CAAC,CAAA;AAlKY,QAAA,yBAAyB,6BAkKrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csv-file.d.ts","sourceRoot":"","sources":["../../../src/fetchers/csv-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"csv-file.d.ts","sourceRoot":"","sources":["../../../src/fetchers/csv-file.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,iBAAiB,WACpB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CA8BpC,CAAA;AA4JD,eAAO,MAAM,sBAAsB,WAAY,OAAO,MAAM,EAAE,OAAO,CAAC,KAAG,MAyIxE,CAAA;AAGD,eAAO,MAAM,uBAAuB,YAAa,GAAG,KAAG,MA+CtD,CAAA"}
|
|
@@ -31,7 +31,7 @@ var validateCSVConfig = function (config) {
|
|
|
31
31
|
};
|
|
32
32
|
exports.validateCSVConfig = validateCSVConfig;
|
|
33
33
|
var generateHandlerBody = function () {
|
|
34
|
-
return "\n try {\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query\n \n const labelToIdMap = {}\n columns.forEach((col) => {\n if (col.label && col.id) {\n labelToIdMap[col.label] = col.id\n }\n })\n \n let filteredData = [...data]\n \n if (query) {\n const searchQuery = query.toLowerCase()\n \n if (queryColumns) {\n const searchColumns = safeJSONParse(queryColumns)\n filteredData = filteredData.filter((item) => {\n return searchColumns.some((col) => {\n const field = labelToIdMap[col] || col\n const value = getNestedValue(item, field)\n return value && String(value).toLowerCase().includes(searchQuery)\n })\n })\n } else {\n filteredData = filteredData.filter((item) => {\n try {\n const stringified = JSON.stringify(item).toLowerCase()\n return stringified.includes(searchQuery)\n } catch {\n return false\n }\n })\n }\n }\n \n if (filters) {\n const
|
|
34
|
+
return "\n try {\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset: offsetParam } = req.query\n \n const labelToIdMap = {}\n columns.forEach((col) => {\n if (col.label && col.id) {\n labelToIdMap[col.label] = col.id\n }\n })\n \n let filteredData = [...data]\n \n if (query) {\n const searchQuery = query.toLowerCase()\n \n if (queryColumns) {\n const searchColumns = safeJSONParse(queryColumns)\n filteredData = filteredData.filter((item) => {\n return searchColumns.some((col) => {\n const field = labelToIdMap[col] || col\n const value = getNestedValue(item, field)\n return value && String(value).toLowerCase().includes(searchQuery)\n })\n })\n } else {\n filteredData = filteredData.filter((item) => {\n try {\n const stringified = JSON.stringify(item).toLowerCase()\n return stringified.includes(searchQuery)\n } catch {\n return false\n }\n })\n }\n }\n \n if (filters) {\n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n \n if (filterTree) {\n filteredData = filteredData.filter((item) =>\n evaluateFilterTree(filterTree, (condition) => {\n const field = labelToIdMap[condition.source] || condition.source\n const value = getNestedValue(item, field)\n const target = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(target)) {\n if (operand === '!=') {\n return !target.includes(value)\n }\n return target.includes(value)\n }\n \n return compareValues(value, target, operand)\n })\n )\n }\n }\n \n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n filteredData.sort((a, b) => {\n for (const sort of parsedSorts) {\n if (!sort.field) continue\n const field = labelToIdMap[sort.field] || sort.field\n const aVal = getNestedValue(a, field)\n const bVal = getNestedValue(b, field)\n const sortOrderValue = (sort.order || '').toLowerCase().startsWith('desc') ? -1 : 1\n \n let comparison = 0\n if (aVal === null || aVal === undefined) {\n comparison = bVal === null || bVal === undefined ? 0 : -1\n } else if (bVal === null || bVal === undefined) {\n comparison = 1\n } else if (typeof aVal === 'number' && typeof bVal === 'number') {\n comparison = aVal - bVal\n } else if (aVal instanceof Date && bVal instanceof Date) {\n comparison = aVal.getTime() - bVal.getTime()\n } else {\n const aStr = String(aVal)\n const bStr = String(bVal)\n if (aStr < bStr) comparison = -1\n else if (aStr > bStr) comparison = 1\n }\n \n if (comparison !== 0) return comparison * sortOrderValue\n }\n return 0\n })\n }\n } else if (sortBy) {\n const field = labelToIdMap[sortBy] || sortBy\n filteredData.sort((a, b) => {\n const aVal = getNestedValue(a, field)\n const bVal = getNestedValue(b, field)\n const sortOrderValue = (sortOrder || '').toLowerCase().startsWith('desc') ? -1 : 1\n \n let comparison = 0\n if (aVal === null || aVal === undefined) {\n comparison = bVal === null || bVal === undefined ? 0 : -1\n } else if (bVal === null || bVal === undefined) {\n comparison = 1\n } else if (typeof aVal === 'number' && typeof bVal === 'number') {\n comparison = aVal - bVal\n } else if (aVal instanceof Date && bVal instanceof Date) {\n comparison = aVal.getTime() - bVal.getTime()\n } else {\n const aStr = String(aVal)\n const bStr = String(bVal)\n if (aStr < bStr) comparison = -1\n else if (aStr > bStr) comparison = 1\n }\n \n return comparison * sortOrderValue\n })\n }\n \n const limitValue = limit || perPage\n const offsetValue = offsetParam !== undefined ? parseInt(offsetParam) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)\n \n if (limitValue) {\n filteredData = filteredData.slice(offsetValue, offsetValue + parseInt(limitValue))\n }\n \n const safeData = JSON.parse(JSON.stringify(filteredData, dateReplacer))\n \n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('CSV fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n }\n";
|
|
35
35
|
};
|
|
36
36
|
var generateCSVFileFetcher = function (config) {
|
|
37
37
|
var csvConfig = config;
|
|
@@ -41,10 +41,10 @@ var generateCSVFileFetcher = function (config) {
|
|
|
41
41
|
var configColumns = csvConfig.columns || [];
|
|
42
42
|
if (csvConfig.fileContent) {
|
|
43
43
|
var fileContent = csvConfig.fileContent;
|
|
44
|
-
return "const csvContent = ".concat(JSON.stringify(fileContent), "\nconst autoDetectHeader = ").concat(autoDetectHeader, "\nconst firstRowIsHeader = ").concat(firstRowIsHeader, "\nconst configColumns = ").concat(JSON.stringify(configColumns), "\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\n").concat((0, header_detection_1.generateHeaderDetectionCode)(), "\n\nfunction parseCSVLine(line) {\n const result = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i]\n const nextChar = line[i + 1]\n\n if (char === '\"') {\n if (inQuotes && nextChar === '\"') {\n current += '\"'\n i++\n } else {\n inQuotes = !inQuotes\n }\n } else if (char === ',' && !inQuotes) {\n result.push(current)\n current = ''\n } else {\n current += char\n }\n }\n\n result.push(current)\n return result\n}\n\nfunction parseCSVContent(content, autoDetect, firstRowHeader, columns) {\n const lines = content.split('\\n').filter((line) => line.trim())\n \n if (lines.length === 0) {\n return { columns: [], rows: [] }\n }\n \n const allRowValues = lines.map((line) => parseCSVLine(line))\n const firstRowValues = allRowValues[0]\n \n if (firstRowValues.length === 0) {\n return { columns: [], rows: [] }\n }\n \n const shouldAutoDetect = autoDetect !== false\n let hasHeaderRow = false\n \n if (shouldAutoDetect) {\n hasHeaderRow = detectHeaderRow(firstRowValues, allRowValues)\n } else {\n hasHeaderRow = firstRowHeader !== false\n }\n \n const useConfigColumns = columns && columns.length > 0\n let parsedColumns\n let dataStartIndex\n \n if (hasHeaderRow && allRowValues.length > 1) {\n parsedColumns = firstRowValues.map((header, index) => {\n const configCol = useConfigColumns ? columns[index] : null\n return {\n id: configCol?.id || `col_${index}`,\n label: String(header || configCol?.label || `Column ${index + 1}`),\n type: configCol?.type || 'string'\n }\n })\n dataStartIndex = 1\n } else {\n parsedColumns = firstRowValues.map((_, index) => {\n const configCol = useConfigColumns ? columns[index] : null\n return {\n id: configCol?.id || `col_${index}`,\n label: configCol?.label || `Column ${index + 1}`,\n type: configCol?.type || 'string'\n }\n })\n dataStartIndex = 0\n }\n \n const rows = []\n for (let i = dataStartIndex; i < allRowValues.length; i++) {\n const values = allRowValues[i]\n const row = {}\n parsedColumns.forEach((col, index) => {\n const value = values[index]?.trim() || null\n row[col.id] = value\n })\n rows.push(row)\n }\n \n return { columns: parsedColumns, rows }\n}\n\nconst { columns, rows: data } = parseCSVContent(csvContent, autoDetectHeader, firstRowIsHeader, configColumns)\n\nexport default async function handler(req, res) {").concat(generateHandlerBody(), "\n}\n");
|
|
44
|
+
return "const csvContent = ".concat(JSON.stringify(fileContent), "\nconst autoDetectHeader = ").concat(autoDetectHeader, "\nconst firstRowIsHeader = ").concat(firstRowIsHeader, "\nconst configColumns = ").concat(JSON.stringify(configColumns), "\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, header_detection_1.generateHeaderDetectionCode)(), "\n\nfunction parseCSVLine(line) {\n const result = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i]\n const nextChar = line[i + 1]\n\n if (char === '\"') {\n if (inQuotes && nextChar === '\"') {\n current += '\"'\n i++\n } else {\n inQuotes = !inQuotes\n }\n } else if (char === ',' && !inQuotes) {\n result.push(current)\n current = ''\n } else {\n current += char\n }\n }\n\n result.push(current)\n return result\n}\n\nfunction parseCSVContent(content, autoDetect, firstRowHeader, columns) {\n const lines = content.split('\\n').filter((line) => line.trim())\n \n if (lines.length === 0) {\n return { columns: [], rows: [] }\n }\n \n const allRowValues = lines.map((line) => parseCSVLine(line))\n const firstRowValues = allRowValues[0]\n \n if (firstRowValues.length === 0) {\n return { columns: [], rows: [] }\n }\n \n const shouldAutoDetect = autoDetect !== false\n let hasHeaderRow = false\n \n if (shouldAutoDetect) {\n hasHeaderRow = detectHeaderRow(firstRowValues, allRowValues)\n } else {\n hasHeaderRow = firstRowHeader !== false\n }\n \n const useConfigColumns = columns && columns.length > 0\n let parsedColumns\n let dataStartIndex\n \n if (hasHeaderRow && allRowValues.length > 1) {\n parsedColumns = firstRowValues.map((header, index) => {\n const configCol = useConfigColumns ? columns[index] : null\n return {\n id: configCol?.id || `col_${index}`,\n label: String(header || configCol?.label || `Column ${index + 1}`),\n type: configCol?.type || 'string'\n }\n })\n dataStartIndex = 1\n } else {\n parsedColumns = firstRowValues.map((_, index) => {\n const configCol = useConfigColumns ? columns[index] : null\n return {\n id: configCol?.id || `col_${index}`,\n label: configCol?.label || `Column ${index + 1}`,\n type: configCol?.type || 'string'\n }\n })\n dataStartIndex = 0\n }\n \n const rows = []\n for (let i = dataStartIndex; i < allRowValues.length; i++) {\n const values = allRowValues[i]\n const row = {}\n parsedColumns.forEach((col, index) => {\n const value = values[index]?.trim() || null\n row[col.id] = value\n })\n rows.push(row)\n }\n \n return { columns: parsedColumns, rows }\n}\n\nconst { columns, rows: data } = parseCSVContent(csvContent, autoDetectHeader, firstRowIsHeader, configColumns)\n\nexport default async function handler(req, res) {").concat(generateHandlerBody(), "\n}\n");
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
|
-
return "const data = ".concat(JSON.stringify(csvConfig.parsedData || []), "\nconst columns = ").concat(JSON.stringify(configColumns), "\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\nexport default async function handler(req, res) {").concat(generateHandlerBody(), "\n}\n");
|
|
47
|
+
return "const data = ".concat(JSON.stringify(csvConfig.parsedData || []), "\nconst columns = ").concat(JSON.stringify(configColumns), "\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\nexport default async function handler(req, res) {").concat(generateHandlerBody(), "\n}\n");
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
exports.generateCSVFileFetcher = generateCSVFileFetcher;
|