@teleporthq/teleport-plugin-next-data-source 0.43.59 → 0.43.60
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__/_helpers/pagination-widget-fixture.ts +230 -0
- package/__tests__/ecommerce-product-discounts.test.ts +196 -0
- package/__tests__/infinite-scroll.test.ts +143 -0
- package/__tests__/numbered-pagination.test.ts +113 -0
- package/__tests__/page-reset-on-filter-sort.test.ts +182 -0
- package/__tests__/page-url-sync.test.ts +208 -0
- package/__tests__/product-price-sort.ts +313 -0
- package/__tests__/search-url-sync.test.ts +7 -3
- package/dist/cjs/array-mapper-pagination.d.ts +9 -0
- package/dist/cjs/array-mapper-pagination.d.ts.map +1 -1
- package/dist/cjs/array-mapper-pagination.js.map +1 -1
- package/dist/cjs/count-effect.d.ts +42 -0
- package/dist/cjs/count-effect.d.ts.map +1 -0
- package/dist/cjs/count-effect.js +81 -0
- package/dist/cjs/count-effect.js.map +1 -0
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +2 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +3 -2
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +65 -0
- package/dist/cjs/infinite-scroll.d.ts.map +1 -0
- package/dist/cjs/infinite-scroll.js +190 -0
- package/dist/cjs/infinite-scroll.js.map +1 -0
- package/dist/cjs/numbered-pagination.d.ts +42 -0
- package/dist/cjs/numbered-pagination.d.ts.map +1 -0
- package/dist/cjs/numbered-pagination.js +167 -0
- package/dist/cjs/numbered-pagination.js.map +1 -0
- package/dist/cjs/page-reset.d.ts +42 -0
- package/dist/cjs/page-reset.d.ts.map +1 -0
- package/dist/cjs/page-reset.js +116 -0
- package/dist/cjs/page-reset.js.map +1 -0
- package/dist/cjs/page-url-sync.d.ts +42 -0
- package/dist/cjs/page-url-sync.d.ts.map +1 -0
- package/dist/cjs/page-url-sync.js +180 -0
- package/dist/cjs/page-url-sync.js.map +1 -0
- package/dist/cjs/pagination-controls.d.ts +55 -0
- package/dist/cjs/pagination-controls.d.ts.map +1 -0
- package/dist/cjs/pagination-controls.js +159 -0
- package/dist/cjs/pagination-controls.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +506 -160
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-price-sort.d.ts +116 -0
- package/dist/cjs/product-price-sort.d.ts.map +1 -0
- package/dist/cjs/product-price-sort.js +169 -0
- package/dist/cjs/product-price-sort.js.map +1 -0
- package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/cjs/transformations/ecommerce-product.js +1 -1
- package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/array-mapper-pagination.d.ts +9 -0
- package/dist/esm/array-mapper-pagination.d.ts.map +1 -1
- package/dist/esm/array-mapper-pagination.js.map +1 -1
- package/dist/esm/count-effect.d.ts +42 -0
- package/dist/esm/count-effect.d.ts.map +1 -0
- package/dist/esm/count-effect.js +53 -0
- package/dist/esm/count-effect.js.map +1 -0
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +2 -1
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +3 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +65 -0
- package/dist/esm/infinite-scroll.d.ts.map +1 -0
- package/dist/esm/infinite-scroll.js +159 -0
- package/dist/esm/infinite-scroll.js.map +1 -0
- package/dist/esm/numbered-pagination.d.ts +42 -0
- package/dist/esm/numbered-pagination.d.ts.map +1 -0
- package/dist/esm/numbered-pagination.js +138 -0
- package/dist/esm/numbered-pagination.js.map +1 -0
- package/dist/esm/page-reset.d.ts +42 -0
- package/dist/esm/page-reset.d.ts.map +1 -0
- package/dist/esm/page-reset.js +87 -0
- package/dist/esm/page-reset.js.map +1 -0
- package/dist/esm/page-url-sync.d.ts +42 -0
- package/dist/esm/page-url-sync.d.ts.map +1 -0
- package/dist/esm/page-url-sync.js +152 -0
- package/dist/esm/page-url-sync.js.map +1 -0
- package/dist/esm/pagination-controls.d.ts +55 -0
- package/dist/esm/pagination-controls.d.ts.map +1 -0
- package/dist/esm/pagination-controls.js +126 -0
- package/dist/esm/pagination-controls.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +507 -161
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-price-sort.d.ts +116 -0
- package/dist/esm/product-price-sort.d.ts.map +1 -0
- package/dist/esm/product-price-sort.js +160 -0
- package/dist/esm/product-price-sort.js.map +1 -0
- package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
- package/dist/esm/transformations/ecommerce-product.js +2 -2
- package/dist/esm/transformations/ecommerce-product.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/array-mapper-pagination.ts +10 -0
- package/src/count-effect.ts +137 -0
- package/src/fetchers/postgresql.ts +52 -13
- package/src/fetchers/teleport.ts +53 -13
- package/src/infinite-scroll.ts +401 -0
- package/src/numbered-pagination.ts +292 -0
- package/src/page-reset.ts +162 -0
- package/src/page-url-sync.ts +241 -0
- package/src/pagination-controls.ts +176 -0
- package/src/pagination-plugin.ts +755 -346
- package/src/product-price-sort.ts +204 -0
- package/src/transformations/ecommerce-product.ts +33 -7
- package/dist/cjs/array-mapper-registry.d.ts +0 -85
- package/dist/cjs/array-mapper-registry.d.ts.map +0 -1
- package/dist/cjs/array-mapper-registry.js +0 -296
- package/dist/cjs/array-mapper-registry.js.map +0 -1
- package/dist/cjs/pagination-with-count.d.ts +0 -6
- package/dist/cjs/pagination-with-count.d.ts.map +0 -1
- package/dist/cjs/pagination-with-count.js +0 -63
- package/dist/cjs/pagination-with-count.js.map +0 -1
- package/dist/esm/array-mapper-registry.d.ts +0 -85
- package/dist/esm/array-mapper-registry.d.ts.map +0 -1
- package/dist/esm/array-mapper-registry.js +0 -292
- package/dist/esm/array-mapper-registry.js.map +0 -1
- package/dist/esm/pagination-with-count.d.ts +0 -6
- package/dist/esm/pagination-with-count.d.ts.map +0 -1
- package/dist/esm/pagination-with-count.js +0 -34
- package/dist/esm/pagination-with-count.js.map +0 -1
- package/src/array-mapper-registry.ts +0 -416
- package/src/pagination-with-count.ts +0 -89
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import {
|
|
3
|
+
PRODUCT_DISCOUNT_SORT_FIELD,
|
|
4
|
+
buildProductDiscountPercentageSql,
|
|
5
|
+
buildProductEffectivePriceSql,
|
|
6
|
+
generateSortFallbackFieldHelper,
|
|
7
|
+
generateSortFieldSqlHelper,
|
|
8
|
+
generateSortTiebreakSql,
|
|
9
|
+
supportsProductPriceSort,
|
|
10
|
+
} from '../src/product-price-sort'
|
|
11
|
+
import { generatePostgreSQLFetcher } from '../src/fetchers/postgresql'
|
|
12
|
+
import { generateTeleportFetcher } from '../src/fetchers/teleport'
|
|
13
|
+
import { generateDataSourceFetcher } from '../src/data-source-fetchers'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ordering a products list by what customers actually pay.
|
|
17
|
+
*
|
|
18
|
+
* The sort happens in SQL, before any row reaches JavaScript, and the list is
|
|
19
|
+
* paginated — so re-sorting the fetched page would only reorder 20 of N
|
|
20
|
+
* products. The database therefore has to compute the discounted price itself.
|
|
21
|
+
*
|
|
22
|
+
* These tests cannot execute the SQL (no Postgres here), so they pin the two
|
|
23
|
+
* things that are checkable: the emitted route still parses, and the expression
|
|
24
|
+
* says what the discount rule says.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const PG_CONFIG = { connectionString: 'postgres://user:pw@host:5432/db' }
|
|
28
|
+
|
|
29
|
+
describe('generateSortFieldSqlHelper', () => {
|
|
30
|
+
it('rewrites price only for the products table', () => {
|
|
31
|
+
expect(supportsProductPriceSort('teleport_products')).toBe(true)
|
|
32
|
+
expect(supportsProductPriceSort('teleport_orders')).toBe(false)
|
|
33
|
+
|
|
34
|
+
expect(generateSortFieldSqlHelper('teleport_products')).toContain('if (field === "price")')
|
|
35
|
+
// Every other table gets the identity, so a fetcher can embed it
|
|
36
|
+
// unconditionally and no unrelated sort can change.
|
|
37
|
+
const other = generateSortFieldSqlHelper('teleport_orders')
|
|
38
|
+
expect(other).not.toContain('if (field ===')
|
|
39
|
+
expect(other).toContain('return field')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('emits the SQL as a double-quoted literal, carrying no template syntax', () => {
|
|
43
|
+
// The helper is interpolated into a fetcher's template literal. A backtick
|
|
44
|
+
// or a `${` inside it would close or interpolate that template — which is
|
|
45
|
+
// exactly how this first went wrong.
|
|
46
|
+
const helper = generateSortFieldSqlHelper('teleport_products')
|
|
47
|
+
expect(helper).not.toContain('`')
|
|
48
|
+
expect(helper).not.toContain('${')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('is a syntactically valid JS function', () => {
|
|
52
|
+
expect(() =>
|
|
53
|
+
parse(generateSortFieldSqlHelper('teleport_products'), { sourceType: 'script' })
|
|
54
|
+
).not.toThrow()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('the effective-price expression', () => {
|
|
59
|
+
const sql = buildProductEffectivePriceSql()
|
|
60
|
+
|
|
61
|
+
it('falls back to the list price when nothing is live', () => {
|
|
62
|
+
expect(sql.startsWith('COALESCE((')).toBe(true)
|
|
63
|
+
expect(sql.trimEnd().endsWith('), "price"::numeric)')).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('applies the same rule as the discount module', () => {
|
|
67
|
+
// Percentage off the list price, capped at 100; fixed subtracted; floored
|
|
68
|
+
// at zero and rounded to the cent — `computeDiscountedPrice`'s contract.
|
|
69
|
+
expect(sql).toContain(`"price"::numeric * (1 - LEAST(entry.amount, 100) / 100)`)
|
|
70
|
+
expect(sql).toContain(`"price"::numeric - entry.amount`)
|
|
71
|
+
expect(sql).toContain('ROUND(GREATEST(0,')
|
|
72
|
+
expect(sql).toContain(', 2)')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('takes the FIRST live entry in array order, like resolveActiveDiscount', () => {
|
|
76
|
+
expect(sql).toContain('WITH ORDINALITY')
|
|
77
|
+
expect(sql).toContain('ORDER BY entry.ord')
|
|
78
|
+
expect(sql).toContain('LIMIT 1')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('honours the half-open window', () => {
|
|
82
|
+
// `[startsAt, endsAt)` — inclusive start, exclusive end, so back-to-back
|
|
83
|
+
// discounts hand over with no overlap.
|
|
84
|
+
expect(sql).toContain('entry.starts_at <=')
|
|
85
|
+
expect(sql).toContain('entry.ends_at >')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('ignores entries the parser would drop', () => {
|
|
89
|
+
expect(sql).toContain('entry.amount > 0')
|
|
90
|
+
expect(sql).toContain(`entry.kind IN ('percentage', 'fixed')`)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('never casts anything that could raise', () => {
|
|
94
|
+
// It runs inside ORDER BY for the whole table: one unparseable row would
|
|
95
|
+
// empty the products page.
|
|
96
|
+
// JSON is cast only when the text already looks like an array…
|
|
97
|
+
expect(sql).toContain(`btrim("discounts") NOT LIKE '[%'`)
|
|
98
|
+
expect(sql).toContain(`'[]'::jsonb`)
|
|
99
|
+
// …the value only when it is a bare decimal…
|
|
100
|
+
expect(sql).toContain(`~ '^[0-9]+(\\.[0-9]+)?$'`)
|
|
101
|
+
// …and the window bounds are compared as TEXT, never cast to a timestamp.
|
|
102
|
+
// `toISOString()` is fixed-width UTC, so it sorts lexicographically in the
|
|
103
|
+
// same order it sorts chronologically.
|
|
104
|
+
expect(sql).not.toContain('::timestamp')
|
|
105
|
+
expect(sql).toContain(`to_char(now() AT TIME ZONE 'UTC'`)
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('the generated Postgres route', () => {
|
|
110
|
+
const productsRoute = generatePostgreSQLFetcher(PG_CONFIG, 'teleport_products')
|
|
111
|
+
|
|
112
|
+
it('parses as valid JavaScript', () => {
|
|
113
|
+
expect(() => parse(productsRoute, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('orders through the mapper rather than the raw field', () => {
|
|
117
|
+
expect(productsRoute).toContain('sortFieldSql(sort.field)')
|
|
118
|
+
expect(productsRoute).toContain('if (field === "price")')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('keeps a plain-column ORDER BY to fall back to', () => {
|
|
122
|
+
// If a database ever rejects the sub-select, the page must degrade to the
|
|
123
|
+
// previous ordering rather than serve nothing.
|
|
124
|
+
expect(productsRoute).toContain('const usedDiscountAwareSort = orderBySql !== plainOrderBySql')
|
|
125
|
+
expect(productsRoute).toContain('if (!usedDiscountAwareSort) throw sortError')
|
|
126
|
+
expect(productsRoute).toContain('baseSql + plainOrderBySql + sqlTail')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('leaves every other table ordering exactly as it was', () => {
|
|
130
|
+
const ordersRoute = generatePostgreSQLFetcher(PG_CONFIG, 'teleport_orders')
|
|
131
|
+
expect(ordersRoute).toContain('sortFieldSql(sort.field)')
|
|
132
|
+
expect(ordersRoute).not.toContain('if (field ===')
|
|
133
|
+
expect(ordersRoute).not.toContain('jsonb_array_elements')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('still parses for a non-products table', () => {
|
|
137
|
+
expect(() =>
|
|
138
|
+
parse(generatePostgreSQLFetcher(PG_CONFIG, 'teleport_orders'), { sourceType: 'module' })
|
|
139
|
+
).not.toThrow()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('the discount ordering', () => {
|
|
144
|
+
const sql = buildProductDiscountPercentageSql()
|
|
145
|
+
|
|
146
|
+
it('sorts on the real `discounts` column, never a virtual field name', () => {
|
|
147
|
+
// The same field reaches sixteen other fetchers and the editor's data API,
|
|
148
|
+
// none of which know about the rewrite. A name that is not a column becomes
|
|
149
|
+
// `ORDER BY discount` and fails the entire query; the real column can only
|
|
150
|
+
// ever be ordered by uselessly.
|
|
151
|
+
expect(PRODUCT_DISCOUNT_SORT_FIELD).toBe('discounts')
|
|
152
|
+
expect(generateSortFieldSqlHelper('teleport_products')).toContain('if (field === "discounts")')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('measures the markdown as a percentage of the list price', () => {
|
|
156
|
+
// Comparable across a mixed catalogue: $20 off a $2000 sofa is a worse deal
|
|
157
|
+
// than $5 off a $10 mug, and an absolute saving would rank them backwards.
|
|
158
|
+
expect(sql).toContain('* 100')
|
|
159
|
+
expect(sql).toContain('/ "price"::numeric')
|
|
160
|
+
expect(sql).toContain(buildProductEffectivePriceSql())
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('cannot divide by zero, and treats a priceless row as undiscounted', () => {
|
|
164
|
+
// `NULL > 0` is NULL, which is not true — so a NULL price takes the ELSE.
|
|
165
|
+
expect(sql).toContain('WHEN "price"::numeric > 0')
|
|
166
|
+
expect(sql).toContain('ELSE 0')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('rounds, so ties are stable rather than floating-point noise', () => {
|
|
170
|
+
expect(sql).toContain('ROUND(')
|
|
171
|
+
expect(sql).toContain(', 4)')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('degrading safely', () => {
|
|
176
|
+
it('falls back to a column that is certain to exist', () => {
|
|
177
|
+
const helper = generateSortFallbackFieldHelper('teleport_products')
|
|
178
|
+
// Never back to `discounts` itself: it is the JSON column, and a store
|
|
179
|
+
// provisioned before discounts shipped does not have it at all — retrying
|
|
180
|
+
// with it would fail exactly like the expression did.
|
|
181
|
+
expect(helper).toContain('if (field === "price" || field === "discounts")')
|
|
182
|
+
expect(helper).toContain('return "price"')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('leaves other tables and other fields alone', () => {
|
|
186
|
+
const helper = generateSortFallbackFieldHelper('teleport_orders')
|
|
187
|
+
expect(helper).not.toContain('if (field ===')
|
|
188
|
+
expect(helper).toContain('return field')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('is valid JS for both tables', () => {
|
|
192
|
+
expect(() =>
|
|
193
|
+
parse(generateSortFallbackFieldHelper('teleport_products'), { sourceType: 'script' })
|
|
194
|
+
).not.toThrow()
|
|
195
|
+
expect(() =>
|
|
196
|
+
parse(generateSortFallbackFieldHelper('teleport_orders'), { sourceType: 'script' })
|
|
197
|
+
).not.toThrow()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('stable pagination', () => {
|
|
202
|
+
it('breaks ties on the products table so pages cannot repeat or skip rows', () => {
|
|
203
|
+
// "Sort by discount" ties every undiscounted product at zero. Equal rows
|
|
204
|
+
// with no defined order can appear on two pages, or on none.
|
|
205
|
+
expect(generateSortTiebreakSql('teleport_products')).toBe(', "id" ASC')
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('adds nothing to a table it does not own', () => {
|
|
209
|
+
expect(generateSortTiebreakSql('teleport_orders')).toBe('')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('is applied to both the real and the fallback ORDER BY', () => {
|
|
213
|
+
const route = generatePostgreSQLFetcher(PG_CONFIG, 'teleport_products')
|
|
214
|
+
const orderByLines = route
|
|
215
|
+
.split('\n')
|
|
216
|
+
.filter((line) => line.includes('ORDER BY') && line.includes('join'))
|
|
217
|
+
expect(orderByLines).toHaveLength(2)
|
|
218
|
+
orderByLines.forEach((line) => expect(line).toContain('"id" ASC'))
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The half of this that was missed the first time.
|
|
224
|
+
*
|
|
225
|
+
* A generated storefront's products table is TELEPORT-hosted, so its route is
|
|
226
|
+
* built by `generateTeleportFetcher` — not by `generatePostgreSQLFetcher`,
|
|
227
|
+
* which every test above exercises. The rewrite went into the Postgres fetcher
|
|
228
|
+
* alone, so a real store still ordered by the undiscounted list price while the
|
|
229
|
+
* whole suite passed.
|
|
230
|
+
*
|
|
231
|
+
* These tests go through `generateFetcher`, the function the project generator
|
|
232
|
+
* actually calls, so what is asserted is the route a store really ships.
|
|
233
|
+
*/
|
|
234
|
+
describe('the fetcher a generated storefront actually gets', () => {
|
|
235
|
+
const teleportProducts = generateDataSourceFetcher(
|
|
236
|
+
{ type: 'teleport', config: { host: 'db', database: 'store' } },
|
|
237
|
+
'teleport_products'
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
it('is discount-aware for a teleport-hosted products table', () => {
|
|
241
|
+
expect(teleportProducts).toContain('sortFieldSql(sort.field)')
|
|
242
|
+
expect(teleportProducts).toContain('if (field === "price")')
|
|
243
|
+
expect(teleportProducts).toContain('if (field === "discounts")')
|
|
244
|
+
expect(teleportProducts).toContain('jsonb_array_elements')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('keeps the plain-column fallback and the tiebreaker', () => {
|
|
248
|
+
expect(teleportProducts).toContain(
|
|
249
|
+
'const usedDiscountAwareSort = orderBySql !== plainOrderBySql'
|
|
250
|
+
)
|
|
251
|
+
expect(teleportProducts).toContain('if (!usedDiscountAwareSort) throw sortError')
|
|
252
|
+
expect(teleportProducts).toContain('baseSql + plainOrderBySql + sqlTail')
|
|
253
|
+
expect(teleportProducts).toContain('"id" ASC')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('parses as valid JavaScript', () => {
|
|
257
|
+
expect(() => parse(teleportProducts, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('leaves a teleport-hosted table that is not products alone', () => {
|
|
261
|
+
const orders = generateDataSourceFetcher(
|
|
262
|
+
{ type: 'teleport', config: { host: 'db', database: 'store' } },
|
|
263
|
+
'teleport_orders'
|
|
264
|
+
)
|
|
265
|
+
expect(orders).not.toContain('if (field ===')
|
|
266
|
+
expect(orders).not.toContain('jsonb_array_elements')
|
|
267
|
+
expect(() => parse(orders, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('is discount-aware for a self-hosted Postgres products table too', () => {
|
|
271
|
+
const pg = generateDataSourceFetcher(
|
|
272
|
+
{ type: 'postgresql', config: PG_CONFIG },
|
|
273
|
+
'teleport_products'
|
|
274
|
+
)
|
|
275
|
+
expect(pg).toContain('if (field === "price")')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The guard that makes the miss above impossible to repeat: every SQL data
|
|
280
|
+
* source that can serve a products table must route its sorts through
|
|
281
|
+
* `sortFieldSql`. A new Postgres-compatible fetcher, or a rewrite of an
|
|
282
|
+
* existing one, fails here rather than silently shipping the list price.
|
|
283
|
+
*/
|
|
284
|
+
it.each([
|
|
285
|
+
['teleport', { host: 'db', database: 'store' }],
|
|
286
|
+
['postgresql', PG_CONFIG],
|
|
287
|
+
['cockroachdb', PG_CONFIG],
|
|
288
|
+
])('routes %s sorts through the mapper', (type, config) => {
|
|
289
|
+
const route = generateDataSourceFetcher(
|
|
290
|
+
{ type, config } as unknown as Parameters<typeof generateDataSourceFetcher>[0],
|
|
291
|
+
'teleport_products'
|
|
292
|
+
)
|
|
293
|
+
expect(route).toContain('sortFieldSql(sort.field)')
|
|
294
|
+
expect(route).not.toMatch(/return `\$\{sort\.field\} \$\{order\}`/)
|
|
295
|
+
})
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
describe('the teleport fetcher, directly', () => {
|
|
299
|
+
it('emits the helpers exactly once, so the route has no duplicate declaration', () => {
|
|
300
|
+
const route = generateTeleportFetcher({ host: 'db' }, 'teleport_products')
|
|
301
|
+
expect(route.split('function sortFieldSql(')).toHaveLength(2)
|
|
302
|
+
expect(route.split('function sortFallbackField(')).toHaveLength(2)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('does not add an ORDER BY to the count query', () => {
|
|
306
|
+
// The count route shares this file; a tiebreaker or a sub-select there
|
|
307
|
+
// would be wasted work at best and a syntax error at worst.
|
|
308
|
+
const route = generateTeleportFetcher({ host: 'db' }, 'teleport_products')
|
|
309
|
+
const countBody = route.slice(route.indexOf('async function getCount'))
|
|
310
|
+
expect(countBody).not.toContain('ORDER BY ${')
|
|
311
|
+
expect(countBody).not.toContain('sortFieldSql(')
|
|
312
|
+
})
|
|
313
|
+
})
|
|
@@ -96,9 +96,12 @@ describe('pagination plugin — search input URL two-way sync (searchUrlParamKey
|
|
|
96
96
|
// useRouter is injected for the effects.
|
|
97
97
|
expect(code).toContain('const router = useRouter()')
|
|
98
98
|
|
|
99
|
-
// Write-back (debounced query → URL), keyed on the DEBOUNCED value
|
|
100
|
-
|
|
101
|
-
expect(code).toContain('
|
|
99
|
+
// Write-back (debounced query → URL), keyed on the DEBOUNCED value and
|
|
100
|
+
// routed through the shared writer so it cannot race the page's own write.
|
|
101
|
+
expect(code).toContain('const __tqQuerySyncRef = useRef(')
|
|
102
|
+
expect(code).toContain(
|
|
103
|
+
'__tqWriteQueryParam("searchKeyword", ds_0_state.debouncedQuery === "" || ds_0_state.debouncedQuery == null ? undefined : ds_0_state.debouncedQuery)'
|
|
104
|
+
)
|
|
102
105
|
expect(code).toContain('}, [ds_0_state.debouncedQuery, router.isReady])')
|
|
103
106
|
|
|
104
107
|
// Read-back (URL → input), functional setState bail-out (loop-free).
|
|
@@ -115,6 +118,7 @@ describe('pagination plugin — search input URL two-way sync (searchUrlParamKey
|
|
|
115
118
|
expect(code).not.toContain('searchKeyword')
|
|
116
119
|
expect(code).not.toContain('window.location.search')
|
|
117
120
|
expect(code).not.toContain('router.replace')
|
|
121
|
+
expect(code).not.toContain('__tqWriteQueryParam')
|
|
118
122
|
expect(code).not.toContain('useRouter')
|
|
119
123
|
})
|
|
120
124
|
})
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { UIDLElementNode, UIDLNode } from '@teleporthq/teleport-types';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Not part of the generation pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Array-mapper pagination is generated by `pagination-plugin.ts` (with
|
|
6
|
+
* `numbered-pagination.ts`, `infinite-scroll.ts` and `pagination-controls.ts`).
|
|
7
|
+
* This module models an older client-side slicing design and is kept only
|
|
8
|
+
* because it is re-exported from the package index; nothing in the pipeline
|
|
9
|
+
* calls it. Add nothing here — the two would drift apart in silence.
|
|
10
|
+
*/
|
|
2
11
|
export interface ArrayMapperPaginationInfo {
|
|
3
12
|
paginationNodeId: string;
|
|
4
13
|
dataSourceIdentifier: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array-mapper-pagination.d.ts","sourceRoot":"","sources":["../../src/array-mapper-pagination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAGtE,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,EAAE,MAAM,CAAA;IACxB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,eAAe,CAAA;IAC/B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CACnC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,kBAAkB,EAAE,CA2CxE;AAED,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EACxB,oBAAoB,EAAE,MAAM,EAC5B,OAAO,GAAE,MAAW,EACpB,YAAY,CAAC,EAAE;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,EACjE,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,yBAAyB,CA+B3B"}
|
|
1
|
+
{"version":3,"file":"array-mapper-pagination.d.ts","sourceRoot":"","sources":["../../src/array-mapper-pagination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAGtE;;;;;;;;GAQG;AAEH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB,EAAE,MAAM,CAAA;IACxB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,eAAe,CAAA;IAC/B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CACnC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,kBAAkB,EAAE,CA2CxE;AAED,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EACxB,oBAAoB,EAAE,MAAM,EAC5B,OAAO,GAAE,MAAW,EACpB,YAAY,CAAC,EAAE;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,EACjE,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,yBAAyB,CA+B3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array-mapper-pagination.js","sourceRoot":"","sources":["../../src/array-mapper-pagination.ts"],"names":[],"mappings":";;;AACA,+DAAuD;
|
|
1
|
+
{"version":3,"file":"array-mapper-pagination.js","sourceRoot":"","sources":["../../src/array-mapper-pagination.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as types from '@babel/types';
|
|
2
|
+
/**
|
|
3
|
+
* The client-side "how many rows match?" fetch that drives `ds_N_maxPages`.
|
|
4
|
+
*
|
|
5
|
+
* One builder for every caller, because the two hand-written copies had already
|
|
6
|
+
* drifted: the search-enabled list sent the query, the columns and the filters,
|
|
7
|
+
* while the paginated-only list fetched a bare `/api/<file>-count` with NO
|
|
8
|
+
* params at all — so a filtered list counted the WHOLE table and "Next" stayed
|
|
9
|
+
* enabled long past the end of the filtered results.
|
|
10
|
+
*
|
|
11
|
+
* The endpoint deliberately receives no `sorts` and no `limit`: ordering cannot
|
|
12
|
+
* change how many rows match, and a limit would count the page rather than the
|
|
13
|
+
* result set.
|
|
14
|
+
*/
|
|
15
|
+
interface CountEffectOptions {
|
|
16
|
+
/** `<fileName>-count` is the generated API route. */
|
|
17
|
+
fileName: string;
|
|
18
|
+
/** Divisor for the page count; the mapper's resolved `perPage`. */
|
|
19
|
+
perPage: number;
|
|
20
|
+
/** Name of the `setDs_N_maxPages` setter. */
|
|
21
|
+
setMaxPagesVar: string;
|
|
22
|
+
/**
|
|
23
|
+
* Params sent to the count endpoint: the search query (when the list has
|
|
24
|
+
* one), `queryColumns`, and the filter array. Built by the caller because
|
|
25
|
+
* only it knows how this usage's filter destinations resolve to expressions.
|
|
26
|
+
*/
|
|
27
|
+
urlParams: types.ObjectProperty[];
|
|
28
|
+
/** Dependencies that should re-run the count. */
|
|
29
|
+
deps: types.Expression[];
|
|
30
|
+
}
|
|
31
|
+
export declare const buildCountFetchStatement: (options: Pick<CountEffectOptions, 'fileName' | 'perPage' | 'setMaxPagesVar' | 'urlParams'>) => types.Statement;
|
|
32
|
+
/**
|
|
33
|
+
* The whole `useEffect(...)` wrapper.
|
|
34
|
+
*
|
|
35
|
+
* ⛔ No skip-on-mount guard, on purpose: a page seeds `maxPages` from the
|
|
36
|
+
* build-time `getStaticProps` count, and that snapshot goes stale the moment
|
|
37
|
+
* rows are added after the build — leaving "Next" disabled on a list that has
|
|
38
|
+
* grown. Re-counting on mount is what corrects it.
|
|
39
|
+
*/
|
|
40
|
+
export declare const buildCountFetchEffect: (options: CountEffectOptions) => types.ExpressionStatement;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=count-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count-effect.d.ts","sourceRoot":"","sources":["../../src/count-effect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC;;;;;;;;;;;;GAYG;AAEH,UAAU,kBAAkB;IAC1B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAA;IACf,6CAA6C;IAC7C,cAAc,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,SAAS,EAAE,KAAK,CAAC,cAAc,EAAE,CAAA;IACjC,iDAAiD;IACjD,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,CAAA;CACzB;AA6BD,eAAO,MAAM,wBAAwB,YAC1B,KAAK,kBAAkB,EAAE,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,CAAC,KACzF,MAAM,SA0DN,CAAA;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,YAAa,kBAAkB,KAAG,MAAM,mBAMvE,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.buildCountFetchEffect = exports.buildCountFetchStatement = void 0;
|
|
27
|
+
var types = __importStar(require("@babel/types"));
|
|
28
|
+
/**
|
|
29
|
+
* `data.count === 0 ? 0 : Math.ceil(data.count / perPage)`
|
|
30
|
+
*
|
|
31
|
+
* Zero is special-cased rather than left to `Math.ceil(0 / n)` so the intent —
|
|
32
|
+
* "no results at all", which numbered pagination renders as an empty strip — is
|
|
33
|
+
* visible in the generated code.
|
|
34
|
+
*/
|
|
35
|
+
var buildMaxPagesExpression = function (perPage) {
|
|
36
|
+
return types.conditionalExpression(types.binaryExpression('===', types.memberExpression(types.identifier('data'), types.identifier('count')), types.numericLiteral(0)), types.numericLiteral(0), types.callExpression(types.memberExpression(types.identifier('Math'), types.identifier('ceil')), [
|
|
37
|
+
types.binaryExpression('/', types.memberExpression(types.identifier('data'), types.identifier('count')), types.numericLiteral(perPage)),
|
|
38
|
+
]));
|
|
39
|
+
};
|
|
40
|
+
var buildCountFetchStatement = function (options) {
|
|
41
|
+
return types.expressionStatement(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.callExpression(types.identifier('fetch'), [
|
|
42
|
+
types.templateLiteral([
|
|
43
|
+
types.templateElement({
|
|
44
|
+
raw: "/api/".concat(options.fileName, "-count?"),
|
|
45
|
+
cooked: "/api/".concat(options.fileName, "-count?"),
|
|
46
|
+
}),
|
|
47
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
48
|
+
], [
|
|
49
|
+
types.newExpression(types.identifier('URLSearchParams'), [
|
|
50
|
+
types.objectExpression(options.urlParams),
|
|
51
|
+
]),
|
|
52
|
+
]),
|
|
53
|
+
]), types.identifier('then')), [
|
|
54
|
+
types.arrowFunctionExpression([types.identifier('res')], types.callExpression(types.memberExpression(types.identifier('res'), types.identifier('json')), [])),
|
|
55
|
+
]), types.identifier('then')), [
|
|
56
|
+
types.arrowFunctionExpression([types.identifier('data')], types.blockStatement([
|
|
57
|
+
types.ifStatement(types.logicalExpression('&&', types.identifier('data'), types.binaryExpression('in', types.stringLiteral('count'), types.identifier('data'))), types.blockStatement([
|
|
58
|
+
types.expressionStatement(types.callExpression(types.identifier(options.setMaxPagesVar), [
|
|
59
|
+
buildMaxPagesExpression(options.perPage),
|
|
60
|
+
])),
|
|
61
|
+
])),
|
|
62
|
+
])),
|
|
63
|
+
]));
|
|
64
|
+
};
|
|
65
|
+
exports.buildCountFetchStatement = buildCountFetchStatement;
|
|
66
|
+
/**
|
|
67
|
+
* The whole `useEffect(...)` wrapper.
|
|
68
|
+
*
|
|
69
|
+
* ⛔ No skip-on-mount guard, on purpose: a page seeds `maxPages` from the
|
|
70
|
+
* build-time `getStaticProps` count, and that snapshot goes stale the moment
|
|
71
|
+
* rows are added after the build — leaving "Next" disabled on a list that has
|
|
72
|
+
* grown. Re-counting on mount is what corrects it.
|
|
73
|
+
*/
|
|
74
|
+
var buildCountFetchEffect = function (options) {
|
|
75
|
+
return types.expressionStatement(types.callExpression(types.identifier('useEffect'), [
|
|
76
|
+
types.arrowFunctionExpression([], types.blockStatement([(0, exports.buildCountFetchStatement)(options)])),
|
|
77
|
+
types.arrayExpression(options.deps),
|
|
78
|
+
]));
|
|
79
|
+
};
|
|
80
|
+
exports.buildCountFetchEffect = buildCountFetchEffect;
|
|
81
|
+
//# sourceMappingURL=count-effect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count-effect.js","sourceRoot":"","sources":["../../src/count-effect.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresql.d.ts","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"postgresql.d.ts","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,yBAAyB,WAC5B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA8QF,CAAA;AAED,eAAO,MAAM,8BAA8B,WACjC,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA8FF,CAAA"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generatePostgreSQLCountFetcher = exports.generatePostgreSQLFetcher = void 0;
|
|
4
4
|
var utils_1 = require("../utils");
|
|
5
|
+
var product_price_sort_1 = require("../product-price-sort");
|
|
5
6
|
var generatePostgreSQLFetcher = function (config, tableName) {
|
|
6
7
|
var _a;
|
|
7
8
|
var pgConfig = config;
|
|
@@ -13,7 +14,7 @@ var generatePostgreSQLFetcher = function (config, tableName) {
|
|
|
13
14
|
: pgConfig.sslConfig
|
|
14
15
|
? "{\n ".concat(pgConfig.sslConfig.ca ? "ca: ".concat((0, utils_1.replaceSecretReference)(pgConfig.sslConfig.ca), ",") : '', "\n ").concat(pgConfig.sslConfig.cert ? "cert: ".concat((0, utils_1.replaceSecretReference)(pgConfig.sslConfig.cert), ",") : '', "\n ").concat(pgConfig.sslConfig.key ? "key: ".concat((0, utils_1.replaceSecretReference)(pgConfig.sslConfig.key), ",") : '', "\n rejectUnauthorized: false\n }")
|
|
15
16
|
: '{ rejectUnauthorized: false }', "\n }");
|
|
16
|
-
return "import { Client } from 'pg'\n\nconst getClient = () => {\n return new Client(".concat(clientConfig, ")\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n\n// Helper function to process filters and build conditions\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const parsedFilters = safeJSONParse(filters)\n \n if (Array.isArray(parsedFilters)) {\n parsedFilters.forEach((filter) => {\n if (!filter.source || filter.destination === undefined) return\n \n const field = filter.source\n const value = filter.destination\n const operand = filter.operand || '='\n \n if (Array.isArray(value)) {\n if (value.length === 0) return\n if (operand === 'array_overlap') {\n conditions.push(`jsonb_exists_any(NULLIF(${field}, '')::jsonb, $${paramIndex}::text[])`)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n if (operand === '!=') {\n conditions.push(`${field} NOT IN (${placeholders.join(', ')})`)\n } else {\n conditions.push(`${field} IN (${placeholders.join(', ')})`)\n }\n } else {\n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return\n // A single comma-joined string (the multi-select Category Filter's\n // ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.\n const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)\n if (overlapValues.length === 0) return\n conditions.push(`jsonb_exists_any(NULLIF(${field}, '')::jsonb, $${paramIndex}::text[])`)\n queryParams.push(overlapValues)\n paramIndex++\n return\n }\n if (value === null) {\n if (operand === '=') {\n conditions.push(`${field} IS NULL`)\n } else if (operand === '!=') {\n conditions.push(`${field} IS NOT NULL`)\n }\n } else {\n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n conditions.push(`${field} ${sqlOperator} $${paramIndex}`)\n queryParams.push(value)\n paramIndex++\n }\n }\n })\n } else {\n Object.entries(parsedFilters).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n conditions.push(`${key} IN (${placeholders.join(', ')})`)\n } else {\n conditions.push(`${key} = $${paramIndex}`)\n queryParams.push(value)\n paramIndex++\n }\n })\n }\n \n return paramIndex\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n const client = getClient()\n \n try {\n await client.connect()\n ").concat(schema ? "await client.query('SET search_path TO ".concat(schema, "')") : '', "\n \n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns. Wrap non-arrays so that a single\n // column passed as a bare string doesn't get iterated as chars.\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n // Fallback: Get all columns from information_schema\n try {\n const schemaQuery = `\n SELECT column_name\n FROM information_schema.columns\n WHERE table_name = $1\n ").concat(schema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = schema \n ? [").concat(JSON.stringify(tableName), ", ").concat(JSON.stringify(schema), "]\n : [").concat(JSON.stringify(tableName), "]\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n const pattern = '%' + escapeLikePattern(query) + '%'\n const placeholder = '$' + paramIndex\n paramIndex++\n queryParams.push(pattern)\n const searchConditions = columns.map(\n (col) => '\"' + sanitizeSearchIdentifier(col) + '\"::text ILIKE ' + placeholder + \" ESCAPE '|'\"\n )\n conditions.push('(' + searchConditions.join(' OR ') + ')')\n }\n }\n\n // Apply filters using helper function\n paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\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
|
|
17
|
+
return "import { Client } from 'pg'\n\nconst getClient = () => {\n return new Client(".concat(clientConfig, ")\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n\n// Helper function to process filters and build conditions\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const parsedFilters = safeJSONParse(filters)\n \n if (Array.isArray(parsedFilters)) {\n parsedFilters.forEach((filter) => {\n if (!filter.source || filter.destination === undefined) return\n \n const field = filter.source\n const value = filter.destination\n const operand = filter.operand || '='\n \n if (Array.isArray(value)) {\n if (value.length === 0) return\n if (operand === 'array_overlap') {\n conditions.push(`jsonb_exists_any(NULLIF(${field}, '')::jsonb, $${paramIndex}::text[])`)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n if (operand === '!=') {\n conditions.push(`${field} NOT IN (${placeholders.join(', ')})`)\n } else {\n conditions.push(`${field} IN (${placeholders.join(', ')})`)\n }\n } else {\n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return\n // A single comma-joined string (the multi-select Category Filter's\n // ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.\n const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)\n if (overlapValues.length === 0) return\n conditions.push(`jsonb_exists_any(NULLIF(${field}, '')::jsonb, $${paramIndex}::text[])`)\n queryParams.push(overlapValues)\n paramIndex++\n return\n }\n if (value === null) {\n if (operand === '=') {\n conditions.push(`${field} IS NULL`)\n } else if (operand === '!=') {\n conditions.push(`${field} IS NOT NULL`)\n }\n } else {\n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n conditions.push(`${field} ${sqlOperator} $${paramIndex}`)\n queryParams.push(value)\n paramIndex++\n }\n }\n })\n } else {\n Object.entries(parsedFilters).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n conditions.push(`${key} IN (${placeholders.join(', ')})`)\n } else {\n conditions.push(`${key} = $${paramIndex}`)\n queryParams.push(value)\n paramIndex++\n }\n })\n }\n \n return paramIndex\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, product_price_sort_1.generateSortFieldSqlHelper)(tableName), "\n\n").concat((0, product_price_sort_1.generateSortFallbackFieldHelper)(tableName), "\n\nexport default async function handler(req, res) {\n const client = getClient()\n \n try {\n await client.connect()\n ").concat(schema ? "await client.query('SET search_path TO ".concat(schema, "')") : '', "\n \n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns. Wrap non-arrays so that a single\n // column passed as a bare string doesn't get iterated as chars.\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n // Fallback: Get all columns from information_schema\n try {\n const schemaQuery = `\n SELECT column_name\n FROM information_schema.columns\n WHERE table_name = $1\n ").concat(schema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = schema \n ? [").concat(JSON.stringify(tableName), ", ").concat(JSON.stringify(schema), "]\n : [").concat(JSON.stringify(tableName), "]\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n const pattern = '%' + escapeLikePattern(query) + '%'\n const placeholder = '$' + paramIndex\n paramIndex++\n queryParams.push(pattern)\n const searchConditions = columns.map(\n (col) => '\"' + sanitizeSearchIdentifier(col) + '\"::text ILIKE ' + placeholder + \" ESCAPE '|'\"\n )\n conditions.push('(' + searchConditions.join(' OR ') + ')')\n }\n }\n\n // Apply filters using helper function\n paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\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. Two ORDER BY clauses are built: the one\n // actually used, and a plain-column twin kept only as the fallback below.\n let orderBySql = ''\n let plainOrderBySql = ''\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n const valid = parsedSorts.filter((sort) => sort && sort.field)\n const orderOf = (sort) => (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const orderClauses = valid.map((sort) => `${sortFieldSql(sort.field)} ${orderOf(sort)}`)\n const plainClauses = valid.map((sort) => `${sortFallbackField(sort.field)} ${orderOf(sort)}`)\n\n if (orderClauses.length > 0) {\n // A deterministic tiebreaker keeps pagination stable: \"sort by\n // discount\" ties every undiscounted product at zero, and equal rows\n // with no defined order can repeat across pages or vanish between them.\n orderBySql = ` ORDER BY ${orderClauses.join(', ')}").concat((0, product_price_sort_1.generateSortTiebreakSql)(tableName), "`\n plainOrderBySql = ` ORDER BY ${plainClauses.join(', ')}").concat((0, product_price_sort_1.generateSortTiebreakSql)(tableName), "`\n }\n }\n } else if (sortBy) {\n orderBySql = ` ORDER BY ${sortBy} ${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}`\n plainOrderBySql = orderBySql\n }\n const usedDiscountAwareSort = orderBySql !== plainOrderBySql\n\n const limitValue = limit || perPage\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n\n let sqlTail = ''\n if (limitValue) {\n sqlTail += ` LIMIT ${limitValue}`\n }\n \n if (offsetValue !== undefined) {\n sqlTail += ` OFFSET ${offsetValue}`\n }\n\n const baseSql = sql\n sql = baseSql + orderBySql + sqlTail\n \n // The discount-aware price ordering is an inline sub-select over a JSON\n // column. It is written to be unraisable, but it runs inside ORDER BY for\n // the whole table \u2014 so if a database ever rejects it, fall back to ordering\n // by the stored list price rather than serving an empty products page.\n let result\n try {\n result = await client.query(sql, queryParams)\n } catch (sortError) {\n if (!usedDiscountAwareSort) throw sortError\n console.warn(\n 'Discount-aware price sort failed; falling back to the list price:',\n sortError && sortError.message\n )\n result = await client.query(baseSql + plainOrderBySql + sqlTail, queryParams)\n }\n const rows = Array.isArray(result?.rows) ? result.rows : []\n const plainRows = rows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const safeData = JSON.parse(JSON.stringify(plainRows, 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('PostgreSQL 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 } finally {\n if (client) {\n try {\n await client.end()\n } catch (error) {\n console.error('Error closing PostgreSQL client:', error)\n }\n }\n }\n}\n");
|
|
17
18
|
};
|
|
18
19
|
exports.generatePostgreSQLFetcher = generatePostgreSQLFetcher;
|
|
19
20
|
var generatePostgreSQLCountFetcher = function (config, tableName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresql.js","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":";;;AAAA,kCAKiB;
|
|
1
|
+
{"version":3,"file":"postgresql.js","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,gCAAgC,EACtC,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,gCAAgC,EACtC,MAAM,oBAAoB,CAAA;AAmC3B,eAAO,MAAM,sBAAsB,WACzB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAMpC,CAAA;AAED,eAAO,MAAM,uBAAuB,WAC1B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,qBACC,gCAAgC,KACjD,MAoZF,CAAA;AAED,eAAO,MAAM,4BAA4B,WAC/B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA0FF,CAAA"}
|