@teleporthq/teleport-plugin-next-data-source 0.43.71 → 0.43.75
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 +8 -1
- package/__tests__/blog-adjacent-arranged.test.ts +176 -0
- package/__tests__/blog-post-contents.test.ts +212 -0
- package/__tests__/blog-post-page-enrichment.test.ts +28 -5
- package/__tests__/blog-post-picture-stand-in.test.ts +48 -0
- package/__tests__/blog-post-reading-time.test.ts +55 -0
- package/__tests__/browser-row-policy.test.ts +5 -1
- package/__tests__/content-preset-wiring.test.ts +108 -0
- package/__tests__/fixed-list-sort.test.ts +183 -0
- package/__tests__/pagination-filter-groups.test.ts +186 -0
- package/__tests__/plain-list-sort.test.ts +215 -0
- package/__tests__/product-price-sort.ts +47 -19
- package/__tests__/rest-api-key-header.test.ts +18 -0
- package/__tests__/search-url-sync.test.ts +34 -15
- package/__tests__/shared-resource-prefetch.test.ts +65 -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/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +3 -1
- package/dist/cjs/fetchers/rest-api.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/fetchers/utils/browser-row-policy.d.ts +7 -6
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +7 -33
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/filter-utils.d.ts +19 -7
- package/dist/cjs/filter-utils.d.ts.map +1 -1
- package/dist/cjs/filter-utils.js +49 -13
- package/dist/cjs/filter-utils.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +137 -118
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/product-price-sort.d.ts +0 -9
- package/dist/cjs/product-price-sort.d.ts.map +1 -1
- package/dist/cjs/product-price-sort.js +1 -13
- package/dist/cjs/product-price-sort.js.map +1 -1
- package/dist/cjs/row-order.d.ts +19 -0
- package/dist/cjs/row-order.d.ts.map +1 -0
- package/dist/cjs/row-order.js +45 -0
- package/dist/cjs/row-order.js.map +1 -0
- package/dist/cjs/sort-utils.d.ts +10 -0
- package/dist/cjs/sort-utils.d.ts.map +1 -1
- package/dist/cjs/sort-utils.js +22 -1
- package/dist/cjs/sort-utils.js.map +1 -1
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts +11 -1
- package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-adjacent-posts.js +14 -1
- package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -1
- package/dist/cjs/transformations/blog-comments.d.ts +1 -1
- package/dist/cjs/transformations/blog-comments.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-comments.js +1 -1
- package/dist/cjs/transformations/blog-comments.js.map +1 -1
- package/dist/cjs/transformations/blog-post.d.ts +14 -0
- package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
- package/dist/cjs/transformations/blog-post.js +4 -1
- package/dist/cjs/transformations/blog-post.js.map +1 -1
- package/dist/cjs/transformations/heading-anchors.d.ts +32 -8
- package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -1
- package/dist/cjs/transformations/heading-anchors.js +34 -10
- package/dist/cjs/transformations/heading-anchors.js.map +1 -1
- package/dist/cjs/transformations/index.d.ts +24 -12
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +59 -11
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +52 -3
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +3 -2
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -1
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +4 -3
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +7 -6
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +5 -31
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/filter-utils.d.ts +19 -7
- package/dist/esm/filter-utils.d.ts.map +1 -1
- package/dist/esm/filter-utils.js +46 -12
- package/dist/esm/filter-utils.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +140 -121
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/product-price-sort.d.ts +0 -9
- package/dist/esm/product-price-sort.d.ts.map +1 -1
- package/dist/esm/product-price-sort.js +0 -11
- package/dist/esm/product-price-sort.js.map +1 -1
- package/dist/esm/row-order.d.ts +19 -0
- package/dist/esm/row-order.d.ts.map +1 -0
- package/dist/esm/row-order.js +40 -0
- package/dist/esm/row-order.js.map +1 -0
- package/dist/esm/sort-utils.d.ts +10 -0
- package/dist/esm/sort-utils.d.ts.map +1 -1
- package/dist/esm/sort-utils.js +20 -0
- package/dist/esm/sort-utils.js.map +1 -1
- package/dist/esm/transformations/blog-adjacent-posts.d.ts +11 -1
- package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -1
- package/dist/esm/transformations/blog-adjacent-posts.js +14 -1
- package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -1
- package/dist/esm/transformations/blog-comments.d.ts +1 -1
- package/dist/esm/transformations/blog-comments.d.ts.map +1 -1
- package/dist/esm/transformations/blog-comments.js +1 -1
- package/dist/esm/transformations/blog-comments.js.map +1 -1
- package/dist/esm/transformations/blog-post.d.ts +14 -0
- package/dist/esm/transformations/blog-post.d.ts.map +1 -1
- package/dist/esm/transformations/blog-post.js +4 -1
- package/dist/esm/transformations/blog-post.js.map +1 -1
- package/dist/esm/transformations/heading-anchors.d.ts +32 -8
- package/dist/esm/transformations/heading-anchors.d.ts.map +1 -1
- package/dist/esm/transformations/heading-anchors.js +33 -9
- package/dist/esm/transformations/heading-anchors.js.map +1 -1
- package/dist/esm/transformations/index.d.ts +24 -12
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +58 -11
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +52 -3
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/fetchers/postgresql.ts +11 -15
- package/src/fetchers/rest-api.ts +3 -1
- package/src/fetchers/teleport.ts +12 -16
- package/src/fetchers/utils/browser-row-policy.ts +10 -20
- package/src/filter-utils.ts +102 -28
- package/src/pagination-plugin.ts +211 -175
- package/src/product-price-sort.ts +0 -11
- package/src/row-order.ts +54 -0
- package/src/sort-utils.ts +25 -0
- package/src/transformations/blog-adjacent-posts.ts +89 -3
- package/src/transformations/blog-comments.ts +5 -5
- package/src/transformations/blog-post.ts +64 -13
- package/src/transformations/heading-anchors.ts +79 -16
- package/src/transformations/index.ts +80 -13
- package/src/utils.ts +74 -3
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
buildProductEffectivePriceSql,
|
|
6
6
|
generateSortFallbackFieldHelper,
|
|
7
7
|
generateSortFieldSqlHelper,
|
|
8
|
-
generateSortTiebreakSql,
|
|
9
8
|
supportsProductPriceSort,
|
|
10
9
|
} from '../src/product-price-sort'
|
|
11
10
|
import { generatePostgreSQLFetcher } from '../src/fetchers/postgresql'
|
|
12
11
|
import { generateTeleportFetcher } from '../src/fetchers/teleport'
|
|
13
12
|
import { generateDataSourceFetcher } from '../src/data-source-fetchers'
|
|
13
|
+
import { generateRowOrderTiebreakHelper, rowIdentityColumn } from '../src/row-order'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Ordering a products list by what customers actually pay.
|
|
@@ -201,23 +201,50 @@ describe('degrading safely', () => {
|
|
|
201
201
|
})
|
|
202
202
|
|
|
203
203
|
describe('stable pagination', () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
204
|
+
// Rows that share every requested key have no order of their own: "sort by
|
|
205
|
+
// discount" ties every undiscounted product at zero, an import dates hundreds
|
|
206
|
+
// of articles alike. Equal rows can appear on two pages, or on none.
|
|
207
|
+
const tiebreakOf = (identityColumn: string | null) =>
|
|
208
|
+
// eslint-disable-next-line no-new-func
|
|
209
|
+
new Function(`${generateRowOrderTiebreakHelper(identityColumn)}\nreturn rowOrderTiebreak`)()
|
|
210
|
+
|
|
211
|
+
it("ends every order with the row's own key, in the order's direction", () => {
|
|
212
|
+
const tiebreak = tiebreakOf('id')
|
|
213
|
+
expect(tiebreak(['created_at'], 'DESC')).toBe(', "id" DESC')
|
|
214
|
+
expect(tiebreak(['price'], 'ASC')).toBe(', "id" ASC')
|
|
215
|
+
expect(tiebreak(['id'], 'ASC')).toBe('')
|
|
216
|
+
expect(tiebreakOf(null)(['created_at'], 'DESC')).toBe('')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it("takes the table's primary key, else its id, and the products table's id", () => {
|
|
220
|
+
const saved = (columns: unknown[]) => ({ selectedTables: { posts: { columns } } })
|
|
221
|
+
expect(
|
|
222
|
+
rowIdentityColumn(saved([{ name: 'sku', isPrimaryKey: true }, { name: 'id' }]), 'posts')
|
|
223
|
+
).toBe('sku')
|
|
224
|
+
expect(rowIdentityColumn(saved([{ name: 'title' }, { name: 'id' }]), 'posts')).toBe('id')
|
|
225
|
+
expect(rowIdentityColumn(saved([{ name: 'title' }]), 'posts')).toBeNull()
|
|
226
|
+
expect(rowIdentityColumn({}, 'teleport_products')).toBe('id')
|
|
227
|
+
expect(rowIdentityColumn({}, 'teleport_orders')).toBeNull()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('is applied to the real and the fallback ORDER BY, and to a single sort column', () => {
|
|
231
|
+
for (const route of [
|
|
232
|
+
generatePostgreSQLFetcher(PG_CONFIG, 'teleport_products'),
|
|
233
|
+
generateTeleportFetcher(
|
|
234
|
+
{
|
|
235
|
+
selectedTables: {
|
|
236
|
+
teleport_help_articles: { columns: [{ name: 'id', isPrimaryKey: true }] },
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
'teleport_help_articles'
|
|
240
|
+
),
|
|
241
|
+
]) {
|
|
242
|
+
const orderByLines = route.split('\n').filter((line) => line.includes('ORDER BY ${'))
|
|
243
|
+
expect(orderByLines.length).toBeGreaterThanOrEqual(3)
|
|
244
|
+
orderByLines.forEach((line) => expect(line).toMatch(/tiebreak|rowOrderTiebreak/))
|
|
245
|
+
expect(route).toContain('const rowOrderTiebreak = (fields, direction) =>')
|
|
246
|
+
expect(() => parse(route, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow()
|
|
247
|
+
}
|
|
221
248
|
})
|
|
222
249
|
})
|
|
223
250
|
|
|
@@ -252,7 +279,8 @@ describe('the fetcher a generated storefront actually gets', () => {
|
|
|
252
279
|
)
|
|
253
280
|
expect(teleportProducts).toContain('if (!usedDiscountAwareSort) throw sortError')
|
|
254
281
|
expect(teleportProducts).toContain('baseSql + plainOrderBySql + sqlTail')
|
|
255
|
-
expect(teleportProducts).toContain('
|
|
282
|
+
expect(teleportProducts).toContain('const rowOrderTiebreak = (fields, direction) =>')
|
|
283
|
+
expect(teleportProducts).toMatch(/\$\{tiebreak\}/)
|
|
256
284
|
})
|
|
257
285
|
|
|
258
286
|
it('parses as valid JavaScript', () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generateRESTAPIFetcher } from '../src/fetchers/rest-api'
|
|
2
|
+
|
|
3
|
+
describe('REST API fetcher with API key authorization', () => {
|
|
4
|
+
/*
|
|
5
|
+
* The editor's data fetcher sends the key as `X-API-Key`. The live site sent
|
|
6
|
+
* `Authorization: ApiKey …`, so a source that worked on the canvas failed once
|
|
7
|
+
* published.
|
|
8
|
+
*/
|
|
9
|
+
it('sends the key in the same header the editor sends', () => {
|
|
10
|
+
const code = generateRESTAPIFetcher({
|
|
11
|
+
url: 'https://api.example.com/items',
|
|
12
|
+
method: 'GET',
|
|
13
|
+
authorization: { type: 'api-key', credentials: { apiKey: 'k-1' } },
|
|
14
|
+
})
|
|
15
|
+
expect(code).toContain("headers['X-API-Key'] = 'k-1'")
|
|
16
|
+
expect(code).not.toContain('ApiKey ')
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -67,10 +67,12 @@ const makeUidlNode = (searchUrlParamKey?: string): any => ({
|
|
|
67
67
|
},
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
const runPlugin = async (searchUrlParamKey?: string): Promise<string> => {
|
|
70
|
+
const runPlugin = async (searchUrlParamKey?: string, paginated = true): Promise<string> => {
|
|
71
71
|
const chunk = makeComponentChunk()
|
|
72
|
+
const node = makeUidlNode(searchUrlParamKey)
|
|
73
|
+
node.content.nodes.success.content.paginated = paginated
|
|
72
74
|
const structure: ComponentStructure = {
|
|
73
|
-
uidl: { name: 'TestComponent', node
|
|
75
|
+
uidl: { name: 'TestComponent', node },
|
|
74
76
|
chunks: [chunk],
|
|
75
77
|
dependencies: {},
|
|
76
78
|
options: { dataSources: {}, extractedResources: {} },
|
|
@@ -81,33 +83,50 @@ const runPlugin = async (searchUrlParamKey?: string): Promise<string> => {
|
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
describe('pagination plugin — search input URL two-way sync (searchUrlParamKey)', () => {
|
|
84
|
-
it('
|
|
86
|
+
it('starts from the default and adopts the address after mount, with paired read-back/write-back effects', async () => {
|
|
85
87
|
const code = await runPlugin('searchKeyword')
|
|
86
88
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
expect(
|
|
93
|
-
expect(
|
|
94
|
-
expect(code).toContain('typeof window !== "undefined"')
|
|
89
|
+
// ⛔ Never seeded from the address: a statically generated page is rendered
|
|
90
|
+
// on the server without the visitor's query, and the first browser render
|
|
91
|
+
// must match it — a seeded query filled the input and skipped the
|
|
92
|
+
// prefetched rows there, and React warned of a hydration mismatch.
|
|
93
|
+
expect(code).not.toContain('window.location.search')
|
|
94
|
+
expect(code).toContain('const [ds_0_searchQuery, setDs_0_searchQuery] = useState("")')
|
|
95
|
+
expect(code).toContain('debouncedQuery: ""')
|
|
95
96
|
|
|
96
97
|
// useRouter is injected for the effects.
|
|
97
98
|
expect(code).toContain('const router = useRouter()')
|
|
98
99
|
|
|
99
|
-
// Write-back (debounced query → URL), keyed on the DEBOUNCED value
|
|
100
|
-
//
|
|
100
|
+
// Write-back (debounced query → URL), keyed on the DEBOUNCED value, routed
|
|
101
|
+
// through the shared writer, and silent until the address was adopted —
|
|
102
|
+
// its first run would otherwise delete the key the read-back adopts.
|
|
103
|
+
expect(code).toContain('const ds_0_searchUrlAdopted = useRef(false)')
|
|
101
104
|
expect(code).toContain('const __tqQuerySyncRef = useRef(')
|
|
105
|
+
expect(code).toContain('if (!ds_0_searchUrlAdopted.current) return;')
|
|
102
106
|
expect(code).toContain(
|
|
103
107
|
'__tqWriteQueryParam("searchKeyword", ds_0_state.debouncedQuery === "" || ds_0_state.debouncedQuery == null ? undefined : ds_0_state.debouncedQuery)'
|
|
104
108
|
)
|
|
105
109
|
expect(code).toContain('}, [ds_0_state.debouncedQuery, router.isReady])')
|
|
106
110
|
|
|
107
|
-
// Read-back (URL → input
|
|
111
|
+
// Read-back (URL → input AND applied query at once, keeping the page),
|
|
112
|
+
// functional setState bail-out (loop-free), then the handshake flag.
|
|
108
113
|
expect(code).toContain('const __urlValue = router.query.searchKeyword')
|
|
109
|
-
expect(code).toContain('
|
|
114
|
+
expect(code).toContain('ds_0_adoptUrlSearch(prev => prev === __nextValue ? prev : __nextValue)')
|
|
115
|
+
expect(code).toContain('ds_0_searchUrlAdopted.current = true')
|
|
110
116
|
expect(code).toContain('}, [router.query.searchKeyword, router.isReady])')
|
|
117
|
+
expect(code.replace(/\s+/g, ' ')).toContain(
|
|
118
|
+
'const ds_0_adoptUrlSearch = update => { setDs_0_searchQuery(update); setDs_0_state(state => { const next = update(state.debouncedQuery); return next === state.debouncedQuery ? state : { ...state, debouncedQuery: next }; }); };'
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('adopts the address into both queries of a list without pages too', async () => {
|
|
123
|
+
const code = await runPlugin('searchKeyword', false)
|
|
124
|
+
expect(code).not.toContain('window.location.search')
|
|
125
|
+
expect(code.replace(/\s+/g, ' ')).toContain(
|
|
126
|
+
'const ds_0_adoptUrlSearch = update => { setDs_0_searchQuery(update); setDs_0_debouncedQuery(update); };'
|
|
127
|
+
)
|
|
128
|
+
expect(code).toContain('ds_0_adoptUrlSearch(prev => prev === __nextValue ? prev : __nextValue)')
|
|
129
|
+
expect(code).toContain('if (!ds_0_searchUrlAdopted.current) return;')
|
|
111
130
|
})
|
|
112
131
|
|
|
113
132
|
it('does NOT emit any URL sync when the repeater has no searchUrlParamKey (unchanged behaviour)', async () => {
|
|
@@ -109,3 +109,68 @@ describe('getStaticProps prefetch of providers sharing one resource', () => {
|
|
|
109
109
|
expect(code.match(/fetchData\(/g)).toHaveLength(1)
|
|
110
110
|
})
|
|
111
111
|
})
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A plain list's server prefetch carries the sort its repeater fixed: the page
|
|
115
|
+
* is pre-rendered with those rows and the browser keeps them (`initialData`
|
|
116
|
+
* skips the first fetch), so a prefetch without it showed the rows in the
|
|
117
|
+
* table's own order — "the latest three posts" as the first three ever.
|
|
118
|
+
*/
|
|
119
|
+
describe('getStaticProps prefetch of a sorted plain list', () => {
|
|
120
|
+
const listNode = (
|
|
121
|
+
sort?: string,
|
|
122
|
+
params: Record<string, unknown> = {}
|
|
123
|
+
): UIDLDataSourceItemNode => {
|
|
124
|
+
const node = nodeWithParams(params)
|
|
125
|
+
;(node.content as { children: unknown[] }).children = [
|
|
126
|
+
{
|
|
127
|
+
type: 'cms-list-repeater',
|
|
128
|
+
content: {
|
|
129
|
+
renderPropIdentifier: 'post',
|
|
130
|
+
nodes: { list: { type: 'element', content: { elementType: 'div' } } },
|
|
131
|
+
...(sort
|
|
132
|
+
? {
|
|
133
|
+
sort: { type: 'static', content: sort },
|
|
134
|
+
sortDirection: { type: 'static', content: 'desc' },
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
return node
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
it('prefetches the rows in the order the list shows them', () => {
|
|
144
|
+
const code = prefetch([{ node: listNode('created_at') }], [providerWithLimit()])
|
|
145
|
+
expect(code).toContain(
|
|
146
|
+
'fetchData({ "sorts": JSON.stringify([{ field: "created_at", order: "desc" }]) })'
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('keeps the sort the resource itself names', () => {
|
|
151
|
+
const code = prefetch(
|
|
152
|
+
[
|
|
153
|
+
{
|
|
154
|
+
node: listNode('created_at', {
|
|
155
|
+
sorts: { type: 'static', content: [{ field: 'title', order: 'asc' }] },
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
[providerWithLimit()]
|
|
160
|
+
)
|
|
161
|
+
expect(code).toContain('"sorts": JSON.stringify([{ "field": "title", "order": "asc" }])')
|
|
162
|
+
expect(code).not.toContain('created_at')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('gives two lists sorted differently their own prefetch', () => {
|
|
166
|
+
const newest = providerWithLimit()
|
|
167
|
+
const oldest = providerWithLimit()
|
|
168
|
+
const code = prefetch(
|
|
169
|
+
[{ node: listNode('created_at') }, { node: listNode('title') }],
|
|
170
|
+
[newest, oldest]
|
|
171
|
+
)
|
|
172
|
+
expect(initialDataOf(newest)).toBe('{props.data_dsjsdata}')
|
|
173
|
+
expect(initialDataOf(oldest)).toBe('{props.data_dsjsdata_2}')
|
|
174
|
+
expect(code.match(/fetchData\(/g)).toHaveLength(2)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -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":"AA2BA,eAAO,MAAM,yBAAyB,WAC5B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,uBACG,cAAc,MAAM,CAAC,KACxC,MAkSF,CAAA;AAED,eAAO,MAAM,8BAA8B,WACjC,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MAkGF,CAAA"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.generatePostgreSQLCountFetcher = exports.generatePostgreSQLFetcher = void 0;
|
|
4
4
|
var utils_1 = require("../utils");
|
|
5
5
|
var product_price_sort_1 = require("../product-price-sort");
|
|
6
|
+
var row_order_1 = require("../row-order");
|
|
6
7
|
var product_filter_fields_1 = require("../product-filter-fields");
|
|
7
8
|
var table_access_guard_1 = require("./utils/table-access-guard");
|
|
8
9
|
var request_sql_guards_1 = require("./utils/request-sql-guards");
|
|
@@ -19,7 +20,7 @@ var generatePostgreSQLFetcher = function (config, tableName, trustedReaderRoles)
|
|
|
19
20
|
: pgConfig.sslConfig
|
|
20
21
|
? "{\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 }")
|
|
21
22
|
: '{ rejectUnauthorized: false }', "\n }");
|
|
22
|
-
return "import { Client } from 'pg'\n\n".concat((0, table_access_guard_1.generateTableAccessPreamble)(trustedReaderRoles), "\n\nconst getClient = () => {\n return new Client(").concat(clientConfig, ")\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n\n// Builds one SQL clause for the whole filter tree, so an OR group nested in\n// the root AND keeps its meaning instead of being flattened into ANDs.\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql([field], '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\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 null\n const clause = arrayOverlapSql([field], '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\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\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, null), "\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 // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, 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 if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\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 if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\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 valid.forEach((sort) => assertRequestColumn(sort.field, '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 assertRequestColumn(sortBy, 'sort field')\n orderBySql = ` ORDER BY ${sortBy} ${(sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'}`\n plainOrderBySql = orderBySql\n }\n const usedDiscountAwareSort = orderBySql !== plainOrderBySql\n\n const limitValue = requestRowCount(limit || perPage)\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n\n let sqlTail = ''\n if (limitValue !== undefined) {\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: __taWithoutCredentials(\n __rowPolicy ? __brpStripHidden(__rowPolicy, safeData) : safeData,\n ").concat(JSON.stringify(tableName), "\n ),\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('PostgreSQL fetch error:', error)\n // A database error names tables, columns and values: it stays in the server\n // log. Only an error this route raised for the caller (it carries a status)\n // is repeated to it.\n return res.status(error.status || 500).json({\n success: false,\n error: error.status ? 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");
|
|
23
|
+
return "import { Client } from 'pg'\n\n".concat((0, table_access_guard_1.generateTableAccessPreamble)(trustedReaderRoles), "\n\nconst getClient = () => {\n return new Client(").concat(clientConfig, ")\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n\n// Builds one SQL clause for the whole filter tree, so an OR group nested in\n// the root AND keeps its meaning instead of being flattened into ANDs.\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql([field], '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\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 null\n const clause = arrayOverlapSql([field], '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\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\n").concat((0, row_order_1.generateRowOrderTiebreakHelper)((0, row_order_1.rowIdentityColumn)(config, tableName)), "\n\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, null), "\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 // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, 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 if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\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 if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\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 valid.forEach((sort) => assertRequestColumn(sort.field, '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 // The row's own key last, so equal rows can't repeat across pages\n // or vanish between them.\n const tiebreak = rowOrderTiebreak(valid.map((sort) => sort.field), orderOf(valid[valid.length - 1]))\n orderBySql = ` ORDER BY ${orderClauses.join(', ')}${tiebreak}`\n plainOrderBySql = ` ORDER BY ${plainClauses.join(', ')}${tiebreak}`\n }\n }\n } else if (sortBy) {\n assertRequestColumn(sortBy, 'sort field')\n const sortByDirection = (sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n orderBySql = ` ORDER BY ${sortBy} ${sortByDirection}${rowOrderTiebreak([sortBy], sortByDirection)}`\n plainOrderBySql = orderBySql\n }\n const usedDiscountAwareSort = orderBySql !== plainOrderBySql\n\n const limitValue = requestRowCount(limit || perPage)\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n\n let sqlTail = ''\n if (limitValue !== undefined) {\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: __taWithoutCredentials(\n __rowPolicy ? __brpStripHidden(__rowPolicy, safeData) : safeData,\n ").concat(JSON.stringify(tableName), "\n ),\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('PostgreSQL fetch error:', error)\n // A database error names tables, columns and values: it stays in the server\n // log. Only an error this route raised for the caller (it carries a status)\n // is repeated to it.\n return res.status(error.status || 500).json({\n success: false,\n error: error.status ? 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");
|
|
23
24
|
};
|
|
24
25
|
exports.generatePostgreSQLFetcher = generatePostgreSQLFetcher;
|
|
25
26
|
var generatePostgreSQLCountFetcher = function (config, tableName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresql.js","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;AACjB,
|
|
1
|
+
{"version":3,"file":"postgresql.js","sourceRoot":"","sources":["../../../src/fetchers/postgresql.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../../src/fetchers/rest-api.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,qBAAqB,WACxB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CA0BpC,CAAA;
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../../src/fetchers/rest-api.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,qBAAqB,WACxB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CA0BpC,CAAA;AAyED,eAAO,MAAM,sBAAsB,WAAY,OAAO,MAAM,EAAE,OAAO,CAAC,KAAG,MAyOxE,CAAA"}
|
|
@@ -35,7 +35,9 @@ var generateAuthCode = function (authorization) {
|
|
|
35
35
|
var type = authorization.type, credentials = authorization.credentials;
|
|
36
36
|
switch (type) {
|
|
37
37
|
case 'api-key':
|
|
38
|
-
|
|
38
|
+
// The same header the editor's data fetcher sends, so a source that works
|
|
39
|
+
// on the canvas works on the live site.
|
|
40
|
+
return "headers['X-API-Key'] = '".concat(credentials.apiKey, "'");
|
|
39
41
|
case 'bearer-token':
|
|
40
42
|
case 'jwt-bearer':
|
|
41
43
|
return "headers['Authorization'] = 'Bearer ".concat(credentials.token, "'");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../../src/fetchers/rest-api.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;AACjB,2EAIwC;AAEjC,IAAM,qBAAqB,GAAG,UACnC,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;KAClE;IAED,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC7E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAA;KACpD;IAED,IAAI;QACF,IAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;YAC/C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAA;SACxE;KACF;IAAC,WAAM;QACN,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAA;KACvD;IAED,IAAI,MAAM,CAAC,MAAM,EAAE;QACjB,IAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;QAC9D,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE;YAC5F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;SACxD;KACF;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC,CAAA;AA5BY,QAAA,qBAAqB,yBA4BjC;AAaD,IAAM,gBAAgB,GAAG,UAAC,aAA4B;IACpD,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC,IAAI,KAAK,MAAM,EAAE;QACnD,OAAO,EAAE,CAAA;KACV;IAEO,IAAA,IAAI,GAAkB,aAAa,KAA/B,EAAE,WAAW,GAAK,aAAa,YAAlB,CAAkB;IAE3C,QAAQ,IAAI,EAAE;QACZ,KAAK,SAAS;YACZ,OAAO,
|
|
1
|
+
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../../src/fetchers/rest-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;AAwC3B,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,sBAAsB,KACvC,MA0bF,CAAA;AAED,eAAO,MAAM,4BAA4B,WAC/B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA8FF,CAAA"}
|
|
@@ -4,6 +4,7 @@ exports.generateTeleportCountFetcher = exports.generateTeleportFetcher = exports
|
|
|
4
4
|
var utils_1 = require("../utils");
|
|
5
5
|
var transformations_1 = require("../transformations");
|
|
6
6
|
var product_price_sort_1 = require("../product-price-sort");
|
|
7
|
+
var row_order_1 = require("../row-order");
|
|
7
8
|
var product_filter_fields_1 = require("../product-filter-fields");
|
|
8
9
|
var localized_columns_1 = require("../localized-columns");
|
|
9
10
|
var request_locale_1 = require("../request-locale");
|
|
@@ -56,9 +57,9 @@ var generateTeleportFetcher = function (config, tableName, transformOptions) {
|
|
|
56
57
|
? "key: ".concat(resolveEnvReference(dbConfig.sslConfig.key, 'TELEPORT_DB_SSL_KEY'), ",")
|
|
57
58
|
: '', "\n rejectUnauthorized: false\n }")
|
|
58
59
|
: "(process.env.".concat(DEFAULT_ENV_KEYS.ssl, " === 'false' ? false : process.env.").concat(DEFAULT_ENV_KEYS.ssl, " === 'true' ? { rejectUnauthorized: false } : undefined)");
|
|
59
|
-
return "import { Client } from 'pg'\n\n".concat((0, table_access_guard_1.generateTableAccessPreamble)(transformOptions.trustedReaderRoles || []), "\nfunction normalizePostgresConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n if (/^postgresql:\\/(?!\\/)/i.test(connectionString)) {\n return connectionString.replace(/^postgresql:\\//i, 'postgresql://');\n }\n return connectionString;\n}\n\nfunction stripSslQueryParamsFromConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n try {\n var u = new URL(connectionString.replace(/^postgresql:/i, 'postgres:'));\n u.searchParams.delete('sslmode');\n u.searchParams.delete('ssl');\n u.searchParams.delete('sslrootcert');\n u.searchParams.delete('sslcert');\n u.searchParams.delete('sslkey');\n return u.toString().replace(/^postgres:/i, 'postgresql:');\n } catch (e) {\n return connectionString;\n }\n}\n\nconst getClient = () => {\n var ssl = ").concat(sslCode, ";\n var connStr = process.env.TELEPORT_DB_CONNECTION_STRING;\n if (connStr) {\n connStr = normalizePostgresConnectionString(connStr);\n }\n if (ssl === false && connStr) {\n connStr = stripSslQueryParamsFromConnectionString(connStr);\n }\n if (connStr) {\n return new Client(Object.assign(\n { connectionString: connStr },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n }\n return new Client(Object.assign(\n {\n host: ").concat(hostRef, ",\n port: parseInt(").concat(portRef, " || '5432', 10),\n user: ").concat(userRef, ",\n password: ").concat(passwordRef, ",\n database: ").concat(databaseRef, ",\n },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n").concat((0, transformations_1.getTransformationCode)(tableName, transformOptions), "\n").concat((0, transformations_1.getTransformWrapperCode)(tableName, transformOptions), "\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n\n // A translatable column is compared against the value in every language,\n // so a localized slug resolves under any locale - see localized-columns.ts.\n const localizedClause = localizedEqualityClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (localizedClause !== null) return localizedClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\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 null\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\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\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\n").concat((0, localized_columns_1.generateLocalizedColumnsHelper)(localizedColumns), "\n\n// Matches DDL / dangerous statements the raw-query branch should refuse.\n// Keep this list conservative \u2014 anything destructive or schema-changing is\n// out of scope for a client-triggered fetch. SELECT and CTEs (WITH ...) are\n// the only shapes consumers legitimately need.\nconst BLOCKED_RAW_QUERY_PATTERNS = [\n /\\bcreate\\s+(?:temporary\\s+|temp\\s+|unlogged\\s+|global\\s+|local\\s+)?table\\b/i,\n /\\bcreate\\s+(?:unique\\s+)?index\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:materialized\\s+)?view\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?trigger\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:aggregate\\s+)?function\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?procedure\\b/i,\n /\\bcreate\\s+(?:database|schema|sequence|extension|type|role|user)\\b/i,\n /\\bdrop\\s+(?:table|view|index|schema|database|sequence|trigger|function|procedure|role|user|extension|type|materialized)\\b/i,\n /\\balter\\s+(?:table|view|index|schema|database|sequence|role|user|system)\\b/i,\n /\\btruncate\\b/i,\n /\\bgrant\\b/i,\n /\\brevoke\\b/i,\n /\\binsert\\b/i,\n /\\bupdate\\b/i,\n /\\bdelete\\b/i,\n /\\bcopy\\b/i,\n /\\bvacuum\\b/i,\n /\\breindex\\b/i,\n /\\bcluster\\b/i,\n]\n\nfunction assertRawQuerySafe(rawQuery) {\n if (typeof rawQuery !== 'string' || rawQuery.length === 0) {\n throw new Error('rawQuery must be a non-empty string')\n }\n // Reject multi-statement payloads \u2014 only single SELECT / WITH statements.\n // A trailing semicolon is tolerated but any content after it fails.\n var trimmed = rawQuery.trim().replace(/;\\s*$/, '')\n if (trimmed.indexOf(';') !== -1) {\n throw new Error('rawQuery must contain exactly one statement')\n }\n for (var i = 0; i < BLOCKED_RAW_QUERY_PATTERNS.length; i++) {\n if (BLOCKED_RAW_QUERY_PATTERNS[i].test(trimmed)) {\n throw new Error('rawQuery contains a blocked statement')\n }\n }\n}\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, 'req.query && req.query.rawQuery'), "\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 // If the caller supplied a rawQuery, run it verbatim (after a safety\n // guard). The schema-driven branch below builds `SELECT * FROM ").concat(tableName, "`\n // with optional filters \u2014 it's the default read path and ignores\n // rawQuery. Page-load workflows and other consumers that need a JOIN\n // or a user-scoped filter pass their fully-rendered SQL here via\n // `fetchData({ rawQuery })` and expect it to execute verbatim.\n if (req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0) {\n assertRawQuerySafe(req.query.rawQuery)\n const rawResult = await client.query(req.query.rawQuery)\n const rawRows = Array.isArray(rawResult?.rows) ? rawResult.rows : []\n const rawPlain = rawRows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const rawSafe = JSON.parse(JSON.stringify(rawPlain, dateReplacer))\n return res.status(200).json({\n success: true,\n // Every row here ends up in a browser (a page's props or a client\n // refetch), so no credential column survives, whatever joined it.\n data: __taWithoutCredentials(rawSafe, null),\n timestamp: Date.now()\n })\n }\n\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n // The language the page is in - what a translatable sort orders by.\n const requestLocale = typeof req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " === 'string' ? req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " : null\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\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 = ").concat(schema
|
|
60
|
+
return "import { Client } from 'pg'\n\n".concat((0, table_access_guard_1.generateTableAccessPreamble)(transformOptions.trustedReaderRoles || []), "\nfunction normalizePostgresConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n if (/^postgresql:\\/(?!\\/)/i.test(connectionString)) {\n return connectionString.replace(/^postgresql:\\//i, 'postgresql://');\n }\n return connectionString;\n}\n\nfunction stripSslQueryParamsFromConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n try {\n var u = new URL(connectionString.replace(/^postgresql:/i, 'postgres:'));\n u.searchParams.delete('sslmode');\n u.searchParams.delete('ssl');\n u.searchParams.delete('sslrootcert');\n u.searchParams.delete('sslcert');\n u.searchParams.delete('sslkey');\n return u.toString().replace(/^postgres:/i, 'postgresql:');\n } catch (e) {\n return connectionString;\n }\n}\n\nconst getClient = () => {\n var ssl = ").concat(sslCode, ";\n var connStr = process.env.TELEPORT_DB_CONNECTION_STRING;\n if (connStr) {\n connStr = normalizePostgresConnectionString(connStr);\n }\n if (ssl === false && connStr) {\n connStr = stripSslQueryParamsFromConnectionString(connStr);\n }\n if (connStr) {\n return new Client(Object.assign(\n { connectionString: connStr },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n }\n return new Client(Object.assign(\n {\n host: ").concat(hostRef, ",\n port: parseInt(").concat(portRef, " || '5432', 10),\n user: ").concat(userRef, ",\n password: ").concat(passwordRef, ",\n database: ").concat(databaseRef, ",\n },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n").concat((0, transformations_1.getTransformationCode)(tableName, transformOptions), "\n").concat((0, transformations_1.getTransformWrapperCode)(tableName, transformOptions), "\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n\n // A translatable column is compared against the value in every language,\n // so a localized slug resolves under any locale - see localized-columns.ts.\n const localizedClause = localizedEqualityClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (localizedClause !== null) return localizedClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\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 null\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\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\n").concat((0, row_order_1.generateRowOrderTiebreakHelper)((0, row_order_1.rowIdentityColumn)(config, tableName)), "\n\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\n").concat((0, localized_columns_1.generateLocalizedColumnsHelper)(localizedColumns), "\n\n// Matches DDL / dangerous statements the raw-query branch should refuse.\n// Keep this list conservative \u2014 anything destructive or schema-changing is\n// out of scope for a client-triggered fetch. SELECT and CTEs (WITH ...) are\n// the only shapes consumers legitimately need.\nconst BLOCKED_RAW_QUERY_PATTERNS = [\n /\\bcreate\\s+(?:temporary\\s+|temp\\s+|unlogged\\s+|global\\s+|local\\s+)?table\\b/i,\n /\\bcreate\\s+(?:unique\\s+)?index\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:materialized\\s+)?view\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?trigger\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:aggregate\\s+)?function\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?procedure\\b/i,\n /\\bcreate\\s+(?:database|schema|sequence|extension|type|role|user)\\b/i,\n /\\bdrop\\s+(?:table|view|index|schema|database|sequence|trigger|function|procedure|role|user|extension|type|materialized)\\b/i,\n /\\balter\\s+(?:table|view|index|schema|database|sequence|role|user|system)\\b/i,\n /\\btruncate\\b/i,\n /\\bgrant\\b/i,\n /\\brevoke\\b/i,\n /\\binsert\\b/i,\n /\\bupdate\\b/i,\n /\\bdelete\\b/i,\n /\\bcopy\\b/i,\n /\\bvacuum\\b/i,\n /\\breindex\\b/i,\n /\\bcluster\\b/i,\n]\n\nfunction assertRawQuerySafe(rawQuery) {\n if (typeof rawQuery !== 'string' || rawQuery.length === 0) {\n throw new Error('rawQuery must be a non-empty string')\n }\n // Reject multi-statement payloads \u2014 only single SELECT / WITH statements.\n // A trailing semicolon is tolerated but any content after it fails.\n var trimmed = rawQuery.trim().replace(/;\\s*$/, '')\n if (trimmed.indexOf(';') !== -1) {\n throw new Error('rawQuery must contain exactly one statement')\n }\n for (var i = 0; i < BLOCKED_RAW_QUERY_PATTERNS.length; i++) {\n if (BLOCKED_RAW_QUERY_PATTERNS[i].test(trimmed)) {\n throw new Error('rawQuery contains a blocked statement')\n }\n }\n}\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, 'req.query && req.query.rawQuery'), "\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 // If the caller supplied a rawQuery, run it verbatim (after a safety\n // guard). The schema-driven branch below builds `SELECT * FROM ").concat(tableName, "`\n // with optional filters \u2014 it's the default read path and ignores\n // rawQuery. Page-load workflows and other consumers that need a JOIN\n // or a user-scoped filter pass their fully-rendered SQL here via\n // `fetchData({ rawQuery })` and expect it to execute verbatim.\n if (req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0) {\n assertRawQuerySafe(req.query.rawQuery)\n const rawResult = await client.query(req.query.rawQuery)\n const rawRows = Array.isArray(rawResult?.rows) ? rawResult.rows : []\n const rawPlain = rawRows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const rawSafe = JSON.parse(JSON.stringify(rawPlain, dateReplacer))\n return res.status(200).json({\n success: true,\n // Every row here ends up in a browser (a page's props or a client\n // refetch), so no credential column survives, whatever joined it.\n data: __taWithoutCredentials(rawSafe, null),\n timestamp: Date.now()\n })\n }\n\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n // The language the page is in - what a translatable sort orders by.\n const requestLocale = typeof req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " === 'string' ? req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " : null\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\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 = ").concat(schema
|
|
60
61
|
? "[".concat(JSON.stringify(tableName), ", ").concat(JSON.stringify(schema), "]")
|
|
61
|
-
: "[".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 }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\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 paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\n if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\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 (a translatable column in the request's language, the\n // products table's price rewrites), and a plain-column twin kept only as\n // 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 valid.forEach((sort) => assertRequestColumn(sort.field, 'sort field'))\n const orderOf = (sort) => (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const orderClauses = valid.map((sort) => `${localizedSortFieldSql(sort.field, requestLocale) || sortFieldSql(sort.field)} ${orderOf(sort)}`)\n const plainClauses = valid.map((sort) => `${sortFallbackField(sort.field)} ${orderOf(sort)}`)\n\n if (orderClauses.length > 0) {\n //
|
|
62
|
+
: "[".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 }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\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 paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\n if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\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 (a translatable column in the request's language, the\n // products table's price rewrites), and a plain-column twin kept only as\n // 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 valid.forEach((sort) => assertRequestColumn(sort.field, 'sort field'))\n const orderOf = (sort) => (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const orderClauses = valid.map((sort) => `${localizedSortFieldSql(sort.field, requestLocale) || sortFieldSql(sort.field)} ${orderOf(sort)}`)\n const plainClauses = valid.map((sort) => `${sortFallbackField(sort.field)} ${orderOf(sort)}`)\n\n if (orderClauses.length > 0) {\n // The row's own key last, so equal rows can't repeat across pages\n // or vanish between them.\n const tiebreak = rowOrderTiebreak(valid.map((sort) => sort.field), orderOf(valid[valid.length - 1]))\n orderBySql = ` ORDER BY ${orderClauses.join(', ')}${tiebreak}`\n plainOrderBySql = ` ORDER BY ${plainClauses.join(', ')}${tiebreak}`\n }\n }\n } else if (sortBy) {\n assertRequestColumn(sortBy, 'sort field')\n const sortByDirection = (sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const tiebreak = rowOrderTiebreak([sortBy], sortByDirection)\n orderBySql = ` ORDER BY ${localizedSortFieldSql(sortBy, requestLocale) || sortBy} ${sortByDirection}${tiebreak}`\n plainOrderBySql = ` ORDER BY ${sortBy} ${sortByDirection}${tiebreak}`\n }\n const usedDiscountAwareSort = orderBySql !== plainOrderBySql\n\n const limitValue = requestRowCount(limit || perPage)\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n\n let sqlTail = ''\n if (limitValue !== undefined) {\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 - so if a database ever rejects it, fall back to ordering\n // by the stored list price rather than serving an empty products page. The\n // translatable-column ordering takes the same exit: a per-language column\n // the schema had at generation time may have been dropped since.\n let result\n try {\n result = await client.query(sql, queryParams)\n } catch (sortError) {\n if (!usedDiscountAwareSort) throw sortError\n console.warn(\n 'Rewritten sort failed; falling back to the plain column:',\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 ").concat((0, transformations_1.getTransformExpression)(tableName)
|
|
62
63
|
? "const transformedData = ".concat((0, transformations_1.getTransformExpression)(tableName))
|
|
63
64
|
: '', "\n\n // Stamp each row with a globally-correct 1-based `__rowNumber` (the page\n // offset is applied server-side above), so list index columns keep counting\n // across pages instead of restarting at 1. Renderer-internal field \u2014 it is\n // filtered from ordinary field bindings and only read by the index column.\n const __finalData = ").concat((0, transformations_1.getTransformExpression)(tableName) ? 'transformedData' : 'safeData', "\n const __rowNumberStart = (typeof offsetValue === 'number' && !isNaN(offsetValue)) ? offsetValue : 0\n const __numberedData = Array.isArray(__finalData)\n ? __finalData.map((__row, __i) =>\n __row && typeof __row === 'object' && !Array.isArray(__row)\n ? Object.assign({}, __row, { __rowNumber: __rowNumberStart + __i + 1 })\n : __row)\n : __finalData\n\n return res.status(200).json({\n success: true,\n data: __taWithoutCredentials(\n __rowPolicy ? __brpStripHidden(__rowPolicy, __numberedData) : __numberedData,\n ").concat(JSON.stringify(tableName), "\n ),\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Teleport DB fetch error:', error)\n // A database error names tables, columns and values: it stays in the server\n // log. Only an error this route raised for the caller (it carries a status)\n // is repeated to it.\n return res.status(error.status || 500).json({\n success: false,\n error: error.status ? 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 database client:', error)\n }\n }\n }\n}\n");
|
|
64
65
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teleport.js","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;AACjB,sDAK2B;AAC3B,
|
|
1
|
+
{"version":3,"file":"teleport.js","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ContentTables } from '@teleporthq/teleport-shared';
|
|
1
2
|
/**
|
|
2
3
|
* What a per-table read route (`/api/teleport-<table>-<source>` and its count
|
|
3
4
|
* twin) serves a BROWSER of the tables whose unpublished rows sit beside the
|
|
@@ -17,12 +18,12 @@
|
|
|
17
18
|
* bindings name every text column of the table — the blog list asks to search
|
|
18
19
|
* `author_email` — and refusing them emptied the blog for every visitor.
|
|
19
20
|
*/
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export type BrowserRowPolicy = ContentTables.VisitorRowPolicy;
|
|
22
|
+
/**
|
|
23
|
+
* One map for both route families: the content presets' posts tables (ONLY
|
|
24
|
+
* `published` rows, never the author's email — see
|
|
25
|
+
* `CONTENT_POSTS_VISITOR_ROW_POLICY`) and the catalogue's products.
|
|
26
|
+
*/
|
|
26
27
|
export declare const BROWSER_ROW_POLICIES: Readonly<Record<string, BrowserRowPolicy>>;
|
|
27
28
|
/**
|
|
28
29
|
* ES5 source inlined after the table-access preamble (it uses `__ta*` and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-row-policy.d.ts","sourceRoot":"","sources":["../../../../src/fetchers/utils/browser-row-policy.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-row-policy.d.ts","sourceRoot":"","sources":["../../../../src/fetchers/utils/browser-row-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,gBAAgB,CAAA;AAE7D;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CACxC,CAAA;AAEpC;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,QACnC,MA+HL,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,cACpB,MAAM,mBACA,cAAc,MAAM,CAAC,KACrC,OAUF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,cAC5B,MAAM,gBACH,MAAM,GAAG,IAAI,KAC1B,MASF,CAAA"}
|
|
@@ -1,39 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* What a per-table read route (`/api/teleport-<table>-<source>` and its count
|
|
4
|
-
* twin) serves a BROWSER of the tables whose unpublished rows sit beside the
|
|
5
|
-
* public ones. (The tables it serves a browser not at all — the AI assistant's
|
|
6
|
-
* among them — are `TableAccess.PROTECTED_TABLES`, guarded by `__taGuardRead`.)
|
|
7
|
-
*
|
|
8
|
-
* The route answers whoever asks, with the whole table. For the blog that was
|
|
9
|
-
* every draft, scheduled and archived post — and each author's email address;
|
|
10
|
-
* for the catalogue, every draft and inactive product. The storefront's own
|
|
11
|
-
* bindings ask for the public rows, but the route is a URL anyone can call
|
|
12
|
-
* without them. So a caller that is not the store itself (its server code, or a
|
|
13
|
-
* signed-in administrator — the generated admin edits drafts through these
|
|
14
|
-
* routes) is served only the rows the storefront publishes, never a hidden
|
|
15
|
-
* column, and may neither filter nor sort by a hidden column: a filter is an
|
|
16
|
-
* oracle for a column it never returns. A hidden SEARCH column is dropped from
|
|
17
|
-
* the search instead (`__brpVisibleColumns`), because the storefront's own list
|
|
18
|
-
* bindings name every text column of the table — the blog list asks to search
|
|
19
|
-
* `author_email` — and refusing them emptied the blog for every visitor.
|
|
20
|
-
*/
|
|
21
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
3
|
exports.generateBrowserRowAccessCall = exports.isViewDependentTable = exports.generateBrowserRowPolicyCode = exports.BROWSER_ROW_POLICIES = void 0;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// The storefront sells `active` products; a draft or inactive one is the
|
|
31
|
-
// merchant's, spelled in any case the admin form stored it in.
|
|
32
|
-
teleport_products: {
|
|
33
|
-
predicate: "LOWER(TRIM(status)) = 'active'",
|
|
34
|
-
hiddenColumns: [],
|
|
35
|
-
},
|
|
36
|
-
};
|
|
4
|
+
var teleport_shared_1 = require("@teleporthq/teleport-shared");
|
|
5
|
+
/**
|
|
6
|
+
* One map for both route families: the content presets' posts tables (ONLY
|
|
7
|
+
* `published` rows, never the author's email — see
|
|
8
|
+
* `CONTENT_POSTS_VISITOR_ROW_POLICY`) and the catalogue's products.
|
|
9
|
+
*/
|
|
10
|
+
exports.BROWSER_ROW_POLICIES = teleport_shared_1.ContentTables.VISITOR_ROW_POLICIES;
|
|
37
11
|
/**
|
|
38
12
|
* ES5 source inlined after the table-access preamble (it uses `__ta*` and
|
|
39
13
|
* `__tqSessionToken`). Declares `__brpAccess(req, tableName, rawQuery)`, which
|