@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.
Files changed (127) hide show
  1. package/__tests__/_helpers/pagination-widget-fixture.ts +230 -0
  2. package/__tests__/ecommerce-product-discounts.test.ts +196 -0
  3. package/__tests__/infinite-scroll.test.ts +143 -0
  4. package/__tests__/numbered-pagination.test.ts +113 -0
  5. package/__tests__/page-reset-on-filter-sort.test.ts +182 -0
  6. package/__tests__/page-url-sync.test.ts +208 -0
  7. package/__tests__/product-price-sort.ts +313 -0
  8. package/__tests__/search-url-sync.test.ts +7 -3
  9. package/dist/cjs/array-mapper-pagination.d.ts +9 -0
  10. package/dist/cjs/array-mapper-pagination.d.ts.map +1 -1
  11. package/dist/cjs/array-mapper-pagination.js.map +1 -1
  12. package/dist/cjs/count-effect.d.ts +42 -0
  13. package/dist/cjs/count-effect.d.ts.map +1 -0
  14. package/dist/cjs/count-effect.js +81 -0
  15. package/dist/cjs/count-effect.js.map +1 -0
  16. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  17. package/dist/cjs/fetchers/postgresql.js +2 -1
  18. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  19. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  20. package/dist/cjs/fetchers/teleport.js +3 -2
  21. package/dist/cjs/fetchers/teleport.js.map +1 -1
  22. package/dist/cjs/infinite-scroll.d.ts +65 -0
  23. package/dist/cjs/infinite-scroll.d.ts.map +1 -0
  24. package/dist/cjs/infinite-scroll.js +190 -0
  25. package/dist/cjs/infinite-scroll.js.map +1 -0
  26. package/dist/cjs/numbered-pagination.d.ts +42 -0
  27. package/dist/cjs/numbered-pagination.d.ts.map +1 -0
  28. package/dist/cjs/numbered-pagination.js +167 -0
  29. package/dist/cjs/numbered-pagination.js.map +1 -0
  30. package/dist/cjs/page-reset.d.ts +42 -0
  31. package/dist/cjs/page-reset.d.ts.map +1 -0
  32. package/dist/cjs/page-reset.js +116 -0
  33. package/dist/cjs/page-reset.js.map +1 -0
  34. package/dist/cjs/page-url-sync.d.ts +42 -0
  35. package/dist/cjs/page-url-sync.d.ts.map +1 -0
  36. package/dist/cjs/page-url-sync.js +180 -0
  37. package/dist/cjs/page-url-sync.js.map +1 -0
  38. package/dist/cjs/pagination-controls.d.ts +55 -0
  39. package/dist/cjs/pagination-controls.d.ts.map +1 -0
  40. package/dist/cjs/pagination-controls.js +159 -0
  41. package/dist/cjs/pagination-controls.js.map +1 -0
  42. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  43. package/dist/cjs/pagination-plugin.js +506 -160
  44. package/dist/cjs/pagination-plugin.js.map +1 -1
  45. package/dist/cjs/product-price-sort.d.ts +116 -0
  46. package/dist/cjs/product-price-sort.d.ts.map +1 -0
  47. package/dist/cjs/product-price-sort.js +169 -0
  48. package/dist/cjs/product-price-sort.js.map +1 -0
  49. package/dist/cjs/transformations/ecommerce-product.d.ts.map +1 -1
  50. package/dist/cjs/transformations/ecommerce-product.js +1 -1
  51. package/dist/cjs/transformations/ecommerce-product.js.map +1 -1
  52. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  53. package/dist/esm/array-mapper-pagination.d.ts +9 -0
  54. package/dist/esm/array-mapper-pagination.d.ts.map +1 -1
  55. package/dist/esm/array-mapper-pagination.js.map +1 -1
  56. package/dist/esm/count-effect.d.ts +42 -0
  57. package/dist/esm/count-effect.d.ts.map +1 -0
  58. package/dist/esm/count-effect.js +53 -0
  59. package/dist/esm/count-effect.js.map +1 -0
  60. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  61. package/dist/esm/fetchers/postgresql.js +2 -1
  62. package/dist/esm/fetchers/postgresql.js.map +1 -1
  63. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  64. package/dist/esm/fetchers/teleport.js +3 -2
  65. package/dist/esm/fetchers/teleport.js.map +1 -1
  66. package/dist/esm/infinite-scroll.d.ts +65 -0
  67. package/dist/esm/infinite-scroll.d.ts.map +1 -0
  68. package/dist/esm/infinite-scroll.js +159 -0
  69. package/dist/esm/infinite-scroll.js.map +1 -0
  70. package/dist/esm/numbered-pagination.d.ts +42 -0
  71. package/dist/esm/numbered-pagination.d.ts.map +1 -0
  72. package/dist/esm/numbered-pagination.js +138 -0
  73. package/dist/esm/numbered-pagination.js.map +1 -0
  74. package/dist/esm/page-reset.d.ts +42 -0
  75. package/dist/esm/page-reset.d.ts.map +1 -0
  76. package/dist/esm/page-reset.js +87 -0
  77. package/dist/esm/page-reset.js.map +1 -0
  78. package/dist/esm/page-url-sync.d.ts +42 -0
  79. package/dist/esm/page-url-sync.d.ts.map +1 -0
  80. package/dist/esm/page-url-sync.js +152 -0
  81. package/dist/esm/page-url-sync.js.map +1 -0
  82. package/dist/esm/pagination-controls.d.ts +55 -0
  83. package/dist/esm/pagination-controls.d.ts.map +1 -0
  84. package/dist/esm/pagination-controls.js +126 -0
  85. package/dist/esm/pagination-controls.js.map +1 -0
  86. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  87. package/dist/esm/pagination-plugin.js +507 -161
  88. package/dist/esm/pagination-plugin.js.map +1 -1
  89. package/dist/esm/product-price-sort.d.ts +116 -0
  90. package/dist/esm/product-price-sort.d.ts.map +1 -0
  91. package/dist/esm/product-price-sort.js +160 -0
  92. package/dist/esm/product-price-sort.js.map +1 -0
  93. package/dist/esm/transformations/ecommerce-product.d.ts.map +1 -1
  94. package/dist/esm/transformations/ecommerce-product.js +2 -2
  95. package/dist/esm/transformations/ecommerce-product.js.map +1 -1
  96. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +5 -5
  98. package/src/array-mapper-pagination.ts +10 -0
  99. package/src/count-effect.ts +137 -0
  100. package/src/fetchers/postgresql.ts +52 -13
  101. package/src/fetchers/teleport.ts +53 -13
  102. package/src/infinite-scroll.ts +401 -0
  103. package/src/numbered-pagination.ts +292 -0
  104. package/src/page-reset.ts +162 -0
  105. package/src/page-url-sync.ts +241 -0
  106. package/src/pagination-controls.ts +176 -0
  107. package/src/pagination-plugin.ts +755 -346
  108. package/src/product-price-sort.ts +204 -0
  109. package/src/transformations/ecommerce-product.ts +33 -7
  110. package/dist/cjs/array-mapper-registry.d.ts +0 -85
  111. package/dist/cjs/array-mapper-registry.d.ts.map +0 -1
  112. package/dist/cjs/array-mapper-registry.js +0 -296
  113. package/dist/cjs/array-mapper-registry.js.map +0 -1
  114. package/dist/cjs/pagination-with-count.d.ts +0 -6
  115. package/dist/cjs/pagination-with-count.d.ts.map +0 -1
  116. package/dist/cjs/pagination-with-count.js +0 -63
  117. package/dist/cjs/pagination-with-count.js.map +0 -1
  118. package/dist/esm/array-mapper-registry.d.ts +0 -85
  119. package/dist/esm/array-mapper-registry.d.ts.map +0 -1
  120. package/dist/esm/array-mapper-registry.js +0 -292
  121. package/dist/esm/array-mapper-registry.js.map +0 -1
  122. package/dist/esm/pagination-with-count.d.ts +0 -6
  123. package/dist/esm/pagination-with-count.d.ts.map +0 -1
  124. package/dist/esm/pagination-with-count.js +0 -34
  125. package/dist/esm/pagination-with-count.js.map +0 -1
  126. package/src/array-mapper-registry.ts +0 -416
  127. 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
- expect(code).toContain('__nextQuery.searchKeyword = String(ds_0_state.debouncedQuery)')
101
- expect(code).toContain('if (__nextQuery.searchKeyword === router.query.searchKeyword) return')
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":"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"}
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":"AAoBA,eAAO,MAAM,yBAAyB,WAC5B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA4OF,CAAA;AAED,eAAO,MAAM,8BAA8B,WACjC,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA8FF,CAAA"}
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 orderClauses = parsedSorts.map((sort) => {\n if (!sort.field) return null\n const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n return `${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 ${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(sql, queryParams)\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
+ 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;AAeV,IAAM,yBAAyB,GAAG,UACvC,MAA+B,EAC/B,SAAiB;;IAEjB,IAAM,QAAQ,GAAG,MAA0B,CAAA;IAC3C,IAAM,MAAM,GAAG,MAAA,QAAQ,CAAC,OAAO,0CAAE,MAAM,CAAA;IAEvC,IAAM,YAAY,GAAG,QAAQ,CAAC,gBAAgB;QAC5C,CAAC,CAAC,mCACkB,IAAA,8BAAsB,EAAC,QAAQ,CAAC,gBAAgB,CAAC,WACrE;QACA,CAAC,CAAC,uBACM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,0BAC7B,QAAQ,CAAC,IAAI,IAAI,IAAI,0BACrB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,QAAQ,CAAC,8BAC9C,IAAA,8BAAsB,EAAC,QAAQ,CAAC,QAAQ,CAAC,8BACzC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,yBAE3C,QAAQ,CAAC,GAAG,KAAK,KAAK;YACpB,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,QAAQ,CAAC,SAAS;gBACpB,CAAC,CAAC,mBACF,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,cAAO,IAAA,8BAAsB,EAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,MAAG,CAAC,CAAC,CAAC,EAAE,qBACpF,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAS,IAAA,8BAAsB,EAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,MAAG,CAAC,CAAC,CAAC,EAAE,qBAC1F,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,eAAQ,IAAA,8BAAsB,EAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,MAAG,CAAC,CAAC,CAAC,EAAE,6CAEzF;gBACE,CAAC,CAAC,+BAA+B,UAErC,CAAA;IAEF,OAAO,wFAGa,YAAY,qBAGhC,IAAA,iCAAyB,GAAE,iBAE3B,IAAA,uCAA+B,GAAE,wvFA2EjC,IAAA,iCAAyB,GAAE,+IAOvB,MAAM,CAAC,CAAC,CAAC,iDAA0C,MAAM,OAAI,CAAC,CAAC,CAAC,EAAE,szBAuB1D,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,0HAIlC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,+BACpD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,i/BAyBT,SAAS,09DAgEzC,CAAA;AACD,CAAC,CAAA;AA/OY,QAAA,yBAAyB,6BA+OrC;AAEM,IAAM,8BAA8B,GAAG,UAC5C,MAA+B,EAC/B,SAAiB;;IAEjB,IAAM,QAAQ,GAAG,MAA0B,CAAA;IAC3C,IAAM,SAAS,GAAG,CAAC,CAAC,CAAA,MAAA,QAAQ,CAAC,OAAO,0CAAE,MAAM,CAAA,CAAA;IAE5C,OAAO,2tBAyBK,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,kGAI1C,SAAS;QACP,CAAC,CAAC,WAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAQ,CAAC,MAAM,CAAC,MAAG;QAC/E,CAAC,CAAC,WAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAG,ohCA4BH,SAAS,gvBA8BrD,CAAA;AACD,CAAC,CAAA;AAjGY,QAAA,8BAA8B,kCAiG1C"}
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;AA8B3B,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,MAiXF,CAAA;AAED,eAAO,MAAM,4BAA4B,WAC/B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA0FF,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"}