@teleporthq/teleport-plugin-next-data-source 0.43.68 → 0.43.70
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__/browser-row-policy.test.ts +21 -1
- package/__tests__/rest-list-edge-cases.test.ts +211 -0
- package/__tests__/shared-resource-prefetch.test.ts +111 -0
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +25 -1
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +5 -2
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +6 -3
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +7 -1
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +57 -8
- package/dist/cjs/infinite-scroll.js.map +1 -1
- package/dist/cjs/items-path.d.ts.map +1 -1
- package/dist/cjs/items-path.js +4 -3
- package/dist/cjs/items-path.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +5 -2
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/repeater-page-size.d.ts +16 -0
- package/dist/cjs/repeater-page-size.d.ts.map +1 -0
- package/dist/cjs/repeater-page-size.js +27 -0
- package/dist/cjs/repeater-page-size.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +31 -4
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +25 -1
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts +5 -2
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +6 -3
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +7 -1
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +57 -8
- package/dist/esm/infinite-scroll.js.map +1 -1
- package/dist/esm/items-path.d.ts.map +1 -1
- package/dist/esm/items-path.js +4 -3
- package/dist/esm/items-path.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +5 -2
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/repeater-page-size.d.ts +16 -0
- package/dist/esm/repeater-page-size.d.ts.map +1 -0
- package/dist/esm/repeater-page-size.js +23 -0
- package/dist/esm/repeater-page-size.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +31 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/fetchers/rest-api.ts +27 -1
- package/src/fetchers/utils/browser-row-policy.ts +7 -3
- package/src/index.ts +3 -2
- package/src/infinite-scroll.ts +107 -43
- package/src/items-path.ts +3 -2
- package/src/pagination-plugin.ts +6 -2
- package/src/repeater-page-size.ts +24 -0
- package/src/utils.ts +54 -6
|
@@ -261,7 +261,6 @@ describe('blog posts: a visitor reads the published posts, never an author email
|
|
|
261
261
|
{ filters },
|
|
262
262
|
{ sorts: JSON.stringify([{ field: 'author_email', order: 'asc' }]) },
|
|
263
263
|
{ sortBy: 'author_email' },
|
|
264
|
-
{ query: 'ana', queryColumns: JSON.stringify(['author_email']) },
|
|
265
264
|
]) {
|
|
266
265
|
const refused = await posts.run({ query })
|
|
267
266
|
expect(refused.status).toBe(403)
|
|
@@ -273,6 +272,27 @@ describe('blog posts: a visitor reads the published posts, never an author email
|
|
|
273
272
|
expect((await posts.run({ query: { filters }, session: { role: 'admin' } })).status).toBe(200)
|
|
274
273
|
})
|
|
275
274
|
|
|
275
|
+
it('drops the hidden column from the search columns the blog list sends', async () => {
|
|
276
|
+
// The generated blog list names every text column of the table, the
|
|
277
|
+
// author's email among them, on every read — with or without a search term.
|
|
278
|
+
const queryColumns = JSON.stringify(['title', 'author_email'])
|
|
279
|
+
for (const query of [{ queryColumns }, { query: 'ana', queryColumns }]) {
|
|
280
|
+
const guest = await posts.run({ query })
|
|
281
|
+
expect(guest.status).toBe(200)
|
|
282
|
+
expect(mainQuery(guest)).not.toContain('"author_email"')
|
|
283
|
+
expect(JSON.stringify(guest.body.data)).not.toContain('ana@example.com')
|
|
284
|
+
expect((await posts.run({ handler: 'getCount', query })).status).toBe(200)
|
|
285
|
+
}
|
|
286
|
+
const searched = await posts.run({ query: { query: 'ana', queryColumns } })
|
|
287
|
+
expect(mainQuery(searched)).toContain('"title"::text ILIKE')
|
|
288
|
+
|
|
289
|
+
const admin = await posts.run({
|
|
290
|
+
query: { query: 'ana', queryColumns },
|
|
291
|
+
session: { role: 'admin' },
|
|
292
|
+
})
|
|
293
|
+
expect(mainQuery(admin)).toContain('"author_email"::text ILIKE')
|
|
294
|
+
})
|
|
295
|
+
|
|
276
296
|
it('searches a visitor only through the columns it is served', async () => {
|
|
277
297
|
const guest = await posts.run({ query: { query: 'ana' } })
|
|
278
298
|
expect(guest.status).toBe(200)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generator from '@babel/generator'
|
|
3
|
+
import { ComponentStructure } from '@teleporthq/teleport-types'
|
|
4
|
+
import { createNextArrayMapperPaginationPlugin } from '../src/pagination-plugin'
|
|
5
|
+
import { extractItemsPath } from '../src/items-path'
|
|
6
|
+
import { generateRESTAPIFetcher } from '../src/fetchers/rest-api'
|
|
7
|
+
import { buildAccumulatingResponseHandler, getInfiniteScrollVars } from '../src/infinite-scroll'
|
|
8
|
+
import { DEFAULT_REPEATER_PER_PAGE, resolveRepeaterPerPage } from '../src/repeater-page-size'
|
|
9
|
+
import {
|
|
10
|
+
makeUidlNode,
|
|
11
|
+
makeWidgetComponentChunk,
|
|
12
|
+
type RepeaterOptions,
|
|
13
|
+
} from './_helpers/pagination-widget-fixture'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A list bound to a REST API payload, in the shapes the editor lets a builder
|
|
17
|
+
* bind: a list wrapped in an object, a list inside an item of a list, an
|
|
18
|
+
* appending list, a list whose page size was never set.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const runHandler = async (
|
|
22
|
+
code: string,
|
|
23
|
+
query: Record<string, unknown>,
|
|
24
|
+
body: unknown
|
|
25
|
+
): Promise<{ data?: unknown; url?: string }> => {
|
|
26
|
+
let requestedURL: string | undefined
|
|
27
|
+
const fetchImpl = (url: string) => {
|
|
28
|
+
requestedURL = url
|
|
29
|
+
return Promise.resolve({
|
|
30
|
+
ok: true,
|
|
31
|
+
status: 200,
|
|
32
|
+
statusText: 'OK',
|
|
33
|
+
json: () => Promise.resolve(JSON.parse(JSON.stringify(body))),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
const handler = new Function(
|
|
37
|
+
'fetch',
|
|
38
|
+
`${code
|
|
39
|
+
.replace(/^import\s+.*?$/gm, '')
|
|
40
|
+
.replace('export default async function handler', 'async function handler')}
|
|
41
|
+
return handler`
|
|
42
|
+
)(fetchImpl)
|
|
43
|
+
|
|
44
|
+
let payload: { data?: unknown } = {}
|
|
45
|
+
const res = {
|
|
46
|
+
status: () => res,
|
|
47
|
+
json: (data: { data?: unknown }) => {
|
|
48
|
+
payload = data
|
|
49
|
+
return res
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
await handler({ query, body: undefined }, res)
|
|
53
|
+
return { data: payload.data, url: requestedURL }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rows = Array.from({ length: 25 }, (_, index) => ({ id: index + 1 }))
|
|
57
|
+
const restFetcher = (config: Record<string, unknown> = {}) =>
|
|
58
|
+
generateRESTAPIFetcher({ url: 'https://api.test/items', method: 'GET', ...config })
|
|
59
|
+
|
|
60
|
+
describe('a list inside an item of a list (`source?.[0]?.items`)', () => {
|
|
61
|
+
it('reads the index as a path segment', () => {
|
|
62
|
+
expect(extractItemsPath('ds?.[0]?.items || []', 'ds', 'rest-api')).toEqual(['0', 'items'])
|
|
63
|
+
expect(extractItemsPath('ds?.data?.[2]?.["my list"]', 'ds', 'rest-api')).toEqual([
|
|
64
|
+
'data',
|
|
65
|
+
'2',
|
|
66
|
+
'my list',
|
|
67
|
+
])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('pages that inner list and keeps the list around it', async () => {
|
|
71
|
+
const { data } = await runHandler(
|
|
72
|
+
restFetcher(),
|
|
73
|
+
{ page: '2', perPage: '10', itemsPath: JSON.stringify(['0', 'items']) },
|
|
74
|
+
[{ items: rows }, { items: [] }]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const payload = data as Array<{ items: Array<{ id: number }> }>
|
|
78
|
+
expect(payload).toHaveLength(2)
|
|
79
|
+
expect(payload[0].items.map((row) => row.id)).toEqual([11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
|
|
80
|
+
expect(payload[1].items).toEqual([])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('leaves the payload alone when the index is past the end of the list', async () => {
|
|
84
|
+
const { data } = await runHandler(
|
|
85
|
+
restFetcher(),
|
|
86
|
+
{ limit: '10', itemsPath: JSON.stringify(['3', 'items']) },
|
|
87
|
+
[{ items: rows }]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
expect((data as Array<{ items: unknown[] }>)[0].items).toHaveLength(25)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('an appending (infinite scroll) list over a wrapped payload', () => {
|
|
95
|
+
const runAccumulator = (itemsPath: string[] | undefined) => {
|
|
96
|
+
const vars = getInfiniteScrollVars(0)
|
|
97
|
+
const source = generator(buildAccumulatingResponseHandler(vars, 2, itemsPath)).code
|
|
98
|
+
const accumRef = { current: { sig: '', pages: {} } }
|
|
99
|
+
const hasMore: boolean[] = []
|
|
100
|
+
return (params: Record<string, unknown>, data: unknown) =>
|
|
101
|
+
new Function(vars.accumRefVar, vars.setHasMoreFlagVar, 'params', `return (${source})`)(
|
|
102
|
+
accumRef,
|
|
103
|
+
(value: boolean) => hasMore.push(value),
|
|
104
|
+
params
|
|
105
|
+
)({ data }) as unknown
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
it('reads the rows at the path and puts every page back there', () => {
|
|
109
|
+
const accumulate = runAccumulator(['data', 'items'])
|
|
110
|
+
|
|
111
|
+
accumulate({ page: 1 }, { data: { items: [{ id: 1 }, { id: 2 }] }, total: 3 })
|
|
112
|
+
const second = accumulate({ page: 2 }, { data: { items: [{ id: 3 }] }, total: 3 }) as {
|
|
113
|
+
data: { items: Array<{ id: number }> }
|
|
114
|
+
total: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
expect(second.data.items.map((row) => row.id)).toEqual([1, 2, 3])
|
|
118
|
+
expect(second.total).toBe(3)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('still accumulates a payload that IS the list', () => {
|
|
122
|
+
const accumulate = runAccumulator(undefined)
|
|
123
|
+
|
|
124
|
+
accumulate({ page: 1 }, [{ id: 1 }, { id: 2 }])
|
|
125
|
+
expect(accumulate({ page: 2 }, [{ id: 3 }])).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('is wired for a list bound inside a REST payload', async () => {
|
|
129
|
+
const code = await runPaginationPlugin({ infiniteScroll: true }, 'items?.templates || []')
|
|
130
|
+
|
|
131
|
+
expect(code).toContain('const rows = response?.data?.templates || []')
|
|
132
|
+
expect(code).toContain('...response?.data,')
|
|
133
|
+
expect(code).toContain('templates: Object.keys(ds_0_accumRef.current.pages)')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('a paginated list with no page size', () => {
|
|
138
|
+
it('pages by the editor default instead of failing the project', async () => {
|
|
139
|
+
const code = await runPaginationPlugin({}, 'items?.templates || []', (node) => {
|
|
140
|
+
delete node.content.nodes.success.content.perPage
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
expect(code).toContain(`perPage: ${DEFAULT_REPEATER_PER_PAGE}`)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it.each([
|
|
147
|
+
[undefined, DEFAULT_REPEATER_PER_PAGE],
|
|
148
|
+
[null, DEFAULT_REPEATER_PER_PAGE],
|
|
149
|
+
['', DEFAULT_REPEATER_PER_PAGE],
|
|
150
|
+
[0, DEFAULT_REPEATER_PER_PAGE],
|
|
151
|
+
[-5, DEFAULT_REPEATER_PER_PAGE],
|
|
152
|
+
[NaN, DEFAULT_REPEATER_PER_PAGE],
|
|
153
|
+
['abc', DEFAULT_REPEATER_PER_PAGE],
|
|
154
|
+
[12, 12],
|
|
155
|
+
['24', 24],
|
|
156
|
+
[7.9, 7],
|
|
157
|
+
])('reads %p as %p', (perPage, expected) => {
|
|
158
|
+
expect(resolveRepeaterPerPage(perPage)).toBe(expected)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('the query params configured on a REST API data source', () => {
|
|
163
|
+
it('are sent with every request, as the editor sends them', async () => {
|
|
164
|
+
const { url } = await runHandler(
|
|
165
|
+
restFetcher({ queryParams: { per_page: '10', category: 'a b' } }),
|
|
166
|
+
{},
|
|
167
|
+
rows
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(url).toBe('https://api.test/items?per_page=10&category=a+b')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('join the params already in the URL', async () => {
|
|
174
|
+
const { url } = await runHandler(
|
|
175
|
+
generateRESTAPIFetcher({
|
|
176
|
+
url: 'https://api.test/items?sort=new',
|
|
177
|
+
method: 'GET',
|
|
178
|
+
queryParams: { page: '2' },
|
|
179
|
+
}),
|
|
180
|
+
{},
|
|
181
|
+
rows
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
expect(url).toBe('https://api.test/items?sort=new&page=2')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('leave the URL exactly as configured when there are none', () => {
|
|
188
|
+
expect(restFetcher({ queryParams: {} })).toBe(restFetcher())
|
|
189
|
+
expect(restFetcher()).toContain('const url = "https://api.test/items"')
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
async function runPaginationPlugin(
|
|
194
|
+
repeater: RepeaterOptions,
|
|
195
|
+
source: string,
|
|
196
|
+
adjust: (node: ReturnType<typeof makeUidlNode>) => void = () => undefined
|
|
197
|
+
): Promise<string> {
|
|
198
|
+
const node = makeUidlNode(repeater)
|
|
199
|
+
node.content.resourceDefinition.dataSourceType = 'rest-api'
|
|
200
|
+
node.content.nodes.success.content.source = source
|
|
201
|
+
adjust(node)
|
|
202
|
+
const chunk = makeWidgetComponentChunk({ controls: ['previous', 'next'] })
|
|
203
|
+
const structure: ComponentStructure = {
|
|
204
|
+
uidl: { name: 'TestComponent', node },
|
|
205
|
+
chunks: [chunk],
|
|
206
|
+
dependencies: {},
|
|
207
|
+
options: { dataSources: {}, extractedResources: {} },
|
|
208
|
+
} as never
|
|
209
|
+
await createNextArrayMapperPaginationPlugin()(structure)
|
|
210
|
+
return generator(chunk.content as types.Node).code
|
|
211
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import generator from '@babel/generator'
|
|
3
|
+
import { UIDLDataSourceItemNode } from '@teleporthq/teleport-types'
|
|
4
|
+
import { extractDataSourceIntoGetStaticProps } from '../src/utils'
|
|
5
|
+
import {
|
|
6
|
+
createComponentChunk,
|
|
7
|
+
createDataSourceNode,
|
|
8
|
+
createJavaScriptDataSource,
|
|
9
|
+
createMockJSXElementWithResourceDef,
|
|
10
|
+
} from './mocks'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Every provider bound to one table carries the SAME resource id
|
|
14
|
+
* (`TQ_<source><table>`) — the editor derives it from the source and the table
|
|
15
|
+
* only — and its own params. A list showing 10 rows and a text bound to the
|
|
16
|
+
* same source therefore asked for one prefetch, and the second provider
|
|
17
|
+
* rendered the first one's rows (`initialData` skips its own first fetch).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DS_ID = 'ds-js'
|
|
21
|
+
const RESOURCE_ID = 'TQ_dsjsdata'
|
|
22
|
+
|
|
23
|
+
const nodeWithParams = (params: Record<string, unknown>): UIDLDataSourceItemNode => {
|
|
24
|
+
const node = createDataSourceNode(DS_ID, 'data', 'javascript')
|
|
25
|
+
;(node.content as { resource: unknown }).resource = { id: RESOURCE_ID, params }
|
|
26
|
+
return node
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const providerWithLimit = (limit?: number): types.JSXElement => {
|
|
30
|
+
const element = createMockJSXElementWithResourceDef(DS_ID, 'data', 'javascript')
|
|
31
|
+
if (limit !== undefined) {
|
|
32
|
+
element.openingElement.attributes.push(
|
|
33
|
+
types.jsxAttribute(
|
|
34
|
+
types.jsxIdentifier('params'),
|
|
35
|
+
types.jsxExpressionContainer(
|
|
36
|
+
types.objectExpression([
|
|
37
|
+
types.objectProperty(types.stringLiteral('limit'), types.numericLiteral(limit)),
|
|
38
|
+
])
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
return element
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const initialDataOf = (element: types.JSXElement): string => {
|
|
47
|
+
const attr = element.openingElement.attributes.find(
|
|
48
|
+
(attribute) => (attribute as types.JSXAttribute).name?.name === 'initialData'
|
|
49
|
+
) as types.JSXAttribute | undefined
|
|
50
|
+
return attr ? generator(attr.value as types.Node).code : ''
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const prefetch = (
|
|
54
|
+
providers: Array<{ node: UIDLDataSourceItemNode }>,
|
|
55
|
+
elements: types.JSXElement[]
|
|
56
|
+
) => {
|
|
57
|
+
const componentChunk = createComponentChunk()
|
|
58
|
+
componentChunk.content = types.jsxFragment(
|
|
59
|
+
types.jsxOpeningFragment(),
|
|
60
|
+
types.jsxClosingFragment(),
|
|
61
|
+
elements
|
|
62
|
+
)
|
|
63
|
+
let getStaticPropsChunk = null
|
|
64
|
+
for (const { node } of providers) {
|
|
65
|
+
const result = extractDataSourceIntoGetStaticProps(
|
|
66
|
+
node,
|
|
67
|
+
{ [DS_ID]: createJavaScriptDataSource(DS_ID) },
|
|
68
|
+
componentChunk,
|
|
69
|
+
getStaticPropsChunk,
|
|
70
|
+
[],
|
|
71
|
+
{},
|
|
72
|
+
{}
|
|
73
|
+
)
|
|
74
|
+
getStaticPropsChunk = result.chunk
|
|
75
|
+
}
|
|
76
|
+
return generator(getStaticPropsChunk.content as types.Node).code.replace(/\s+/g, ' ')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe('getStaticProps prefetch of providers sharing one resource', () => {
|
|
80
|
+
it('gives providers with different params their own prefetch', () => {
|
|
81
|
+
const limitedList = providerWithLimit(10)
|
|
82
|
+
const text = providerWithLimit()
|
|
83
|
+
|
|
84
|
+
const code = prefetch(
|
|
85
|
+
[
|
|
86
|
+
{ node: nodeWithParams({ limit: { type: 'static', content: 10 } }) },
|
|
87
|
+
{ node: nodeWithParams({}) },
|
|
88
|
+
],
|
|
89
|
+
[limitedList, text]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(initialDataOf(limitedList)).toBe('{props.data_dsjsdata}')
|
|
93
|
+
expect(initialDataOf(text)).toBe('{props.data_dsjsdata_2}')
|
|
94
|
+
expect(code).toContain('fetchData({ "limit": 10 })')
|
|
95
|
+
expect(code).toContain('fetchData({})')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('keeps ONE prefetch for providers with the same params', () => {
|
|
99
|
+
const first = providerWithLimit()
|
|
100
|
+
const second = providerWithLimit()
|
|
101
|
+
|
|
102
|
+
const code = prefetch(
|
|
103
|
+
[{ node: nodeWithParams({}) }, { node: nodeWithParams({}) }],
|
|
104
|
+
[first, second]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
expect(initialDataOf(first)).toBe('{props.data_dsjsdata}')
|
|
108
|
+
expect(initialDataOf(second)).toBe('{props.data_dsjsdata}')
|
|
109
|
+
expect(code.match(/fetchData\(/g)).toHaveLength(1)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -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;AAuED,eAAO,MAAM,sBAAsB,WAAY,OAAO,MAAM,EAAE,OAAO,CAAC,KAAG,MAyOxE,CAAA"}
|
|
@@ -47,6 +47,30 @@ var generateAuthCode = function (authorization) {
|
|
|
47
47
|
return '';
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The URL with the data source's query params appended, built the way the
|
|
52
|
+
* editor's own fetch builds it (`RestApiService.constructUrl` in the
|
|
53
|
+
* data-fetcher worker: `searchParams.append` per param).
|
|
54
|
+
*
|
|
55
|
+
* The params used to be dropped here, so the published site asked the API a
|
|
56
|
+
* different question than the editor did — `?per_page=10` or `?category=x`
|
|
57
|
+
* configured on the source, and every row of the unfiltered response came back.
|
|
58
|
+
*/
|
|
59
|
+
var buildRequestURL = function (url, queryParams) {
|
|
60
|
+
var params = Object.entries(queryParams || {}).filter(function (_a) {
|
|
61
|
+
var key = _a[0], value = _a[1];
|
|
62
|
+
return key.trim() !== '' && typeof value === 'string';
|
|
63
|
+
});
|
|
64
|
+
if (!url || params.length === 0) {
|
|
65
|
+
return url;
|
|
66
|
+
}
|
|
67
|
+
var requestURL = new URL(url);
|
|
68
|
+
params.forEach(function (_a) {
|
|
69
|
+
var key = _a[0], value = _a[1];
|
|
70
|
+
return requestURL.searchParams.append(key, value);
|
|
71
|
+
});
|
|
72
|
+
return requestURL.toString();
|
|
73
|
+
};
|
|
50
74
|
var generateRESTAPIFetcher = function (config) {
|
|
51
75
|
var restConfig = config;
|
|
52
76
|
var authCode = generateAuthCode(restConfig.authorization || {});
|
|
@@ -54,7 +78,7 @@ var generateRESTAPIFetcher = function (config) {
|
|
|
54
78
|
// uncached data source keeps generating byte-identical output.
|
|
55
79
|
var cacheTTLSeconds = (0, rest_api_response_cache_1.resolveRESTAPICacheTTLSeconds)(restConfig.cache);
|
|
56
80
|
var cached = cacheTTLSeconds !== undefined;
|
|
57
|
-
return "import fetch from 'node-fetch'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateCollectionPathHelperCode)()).concat(cached ? "\n\n".concat((0, rest_api_response_cache_1.generateRESTAPIResponseCacheCode)(cacheTTLSeconds)) : '', "\n\nexport default async function handler(req, res) {\n try {\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset, collectionPath, itemsPath } = req.query\n \n const url = ").concat(JSON.stringify(restConfig.url), "\n const method = ").concat(JSON.stringify(restConfig.method || 'GET'), "\n \n const headers = ").concat(JSON.stringify(restConfig.headers || {}), "\n ").concat(authCode, "\n \n const options = {\n method,\n headers\n }\n \n ").concat(restConfig.method === 'POST' || restConfig.method === 'PUT' || restConfig.method === 'PATCH'
|
|
81
|
+
return "import fetch from 'node-fetch'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, utils_1.generateSortFilterHelperCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateCollectionPathHelperCode)()).concat(cached ? "\n\n".concat((0, rest_api_response_cache_1.generateRESTAPIResponseCacheCode)(cacheTTLSeconds)) : '', "\n\nexport default async function handler(req, res) {\n try {\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset, collectionPath, itemsPath } = req.query\n \n const url = ").concat(JSON.stringify(buildRequestURL(restConfig.url, restConfig.queryParams)), "\n const method = ").concat(JSON.stringify(restConfig.method || 'GET'), "\n \n const headers = ").concat(JSON.stringify(restConfig.headers || {}), "\n ").concat(authCode, "\n \n const options = {\n method,\n headers\n }\n \n ").concat(restConfig.method === 'POST' || restConfig.method === 'PUT' || restConfig.method === 'PATCH'
|
|
58
82
|
? "\n if (req.body) {\n options.body = ".concat(restConfig.bodyType === 'json' || !restConfig.bodyType
|
|
59
83
|
? 'JSON.stringify(req.body)'
|
|
60
84
|
: 'req.body', "\n }").concat(
|
|
@@ -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,6CAAsC,WAAW,CAAC,MAAM,MAAG,CAAA;QACpE,KAAK,cAAc,CAAC;QACpB,KAAK,YAAY;YACf,OAAO,6CAAsC,WAAW,CAAC,KAAK,MAAG,CAAA;QACnE,KAAK,YAAY;YACf,OAAO,6DAAsD,WAAW,CAAC,QAAQ,cAAI,WAAW,CAAC,QAAQ,0BAAuB,CAAA;QAClI,KAAK,QAAQ;YACX,OAAO,6CAAsC,WAAW,CAAC,YAAY,MAAG,CAAA;QAC1E;YACE,OAAO,EAAE,CAAA;KACZ;AACH,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../../src/fetchers/rest-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* without them. So a caller that is not the store itself (its server code, or a
|
|
12
12
|
* signed-in administrator — the generated admin edits drafts through these
|
|
13
13
|
* routes) is served only the rows the storefront publishes, never a hidden
|
|
14
|
-
* column, and may neither filter
|
|
15
|
-
*
|
|
14
|
+
* column, and may neither filter nor sort by a hidden column: a filter is an
|
|
15
|
+
* oracle for a column it never returns. A hidden SEARCH column is dropped from
|
|
16
|
+
* the search instead (`__brpVisibleColumns`), because the storefront's own list
|
|
17
|
+
* bindings name every text column of the table — the blog list asks to search
|
|
18
|
+
* `author_email` — and refusing them emptied the blog for every visitor.
|
|
16
19
|
*/
|
|
17
20
|
export interface BrowserRowPolicy {
|
|
18
21
|
/** SQL every browser read of the table is narrowed with. */
|
|
@@ -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;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,8FAA8F;IAC9F,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAa3E,CAAA;AAED;;;;;;;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"}
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
* without them. So a caller that is not the store itself (its server code, or a
|
|
13
13
|
* signed-in administrator — the generated admin edits drafts through these
|
|
14
14
|
* routes) is served only the rows the storefront publishes, never a hidden
|
|
15
|
-
* column, and may neither filter
|
|
16
|
-
*
|
|
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.
|
|
17
20
|
*/
|
|
18
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
22
|
exports.generateBrowserRowAccessCall = exports.isViewDependentTable = exports.generateBrowserRowPolicyCode = exports.BROWSER_ROW_POLICIES = void 0;
|
|
@@ -39,7 +42,7 @@ exports.BROWSER_ROW_POLICIES = {
|
|
|
39
42
|
* the table whole). Plus the helpers that apply a policy to a request and to
|
|
40
43
|
* its rows.
|
|
41
44
|
*/
|
|
42
|
-
var generateBrowserRowPolicyCode = function () { return "// GENERATED \u2014 see browser-row-policy.ts in teleport-plugin-next-data-source.\nvar __BRP_POLICIES = ".concat(JSON.stringify(exports.BROWSER_ROW_POLICIES), ";\n\n// Who is asking: the store's own server code and its administrators read every\n// row; anyone else is a visitor.\nasync function __brpReader(req) {\n if (__taIsInternalRequest(req)) return { trusted: true };\n var token = null;\n try {\n token = await __tqSessionToken(req);\n } catch (e) {\n token = null;\n }\n var role = token ? __taRoleOf(token) : null;\n return { trusted: !!role && __TA_TRUSTED_READER_ROLES.indexOf(role) !== -1 };\n}\n\nfunction __brpMentionsAny(rawQuery, tables) {\n for (var i = 0; i < tables.length; i++) {\n if (__taSqlMentionsTable(rawQuery, tables[i])) return true;\n }\n return false;\n}\n\n// A raw statement cannot be narrowed, so a visitor's statement that names a\n// narrowed table is refused whole. The session is only decoded when the read\n// touches one of these tables.\nasync function __brpAccess(req, tableName, rawQuery) {\n var raw = typeof rawQuery === 'string' && rawQuery.length > 0 ? rawQuery : null;\n var bare = __taNormalizeTable(tableName);\n var rawNarrowed = !!raw && __brpMentionsAny(raw, Object.keys(__BRP_POLICIES));\n var policy = raw ? null : (Object.prototype.hasOwnProperty.call(__BRP_POLICIES, bare) ? __BRP_POLICIES[bare] : null);\n if (!rawNarrowed && !policy) return { denied: null, policy: null };\n var reader = await __brpReader(req);\n if (reader.trusted) return { denied: null, policy: null };\n if (rawNarrowed) {\n return { denied: { status: 403, message: 'Forbidden: raw queries over this table are only available to server-side calls' }, policy: null };\n }\n return { denied: null, policy: policy };\n}\n\nfunction __brpColumnName(field) {\n var parts = String(field == null ? '' : field).replace(/\"/g, '').trim().split('.');\n return parts[parts.length - 1];\n}\n\nfunction __brpIsHidden(policy, field) {\n var name = __brpColumnName(field).toLowerCase();\n for (var i = 0; i < policy.hiddenColumns.length; i++) {\n if (policy.hiddenColumns[i].toLowerCase() === name) return true;\n }\n return false;\n}\n\n// Every field a request filters, sorts or searches by, however deep a filter\n// group nests it. A condition's value is data, not a field.\nfunction __brpRequestFields(value, out) {\n if (Array.isArray(value)) {\n for (var i = 0; i < value.length; i++) __brpRequestFields(value[i], out);\n } else if (value && typeof value === 'object') {\n if (typeof value.source === 'string') out.push(value.source);\n if (typeof value.field === 'string') out.push(value.field);\n var keys = Object.keys(value);\n for (var k = 0; k < keys.length; k++) {\n if (keys[k] === 'destination' || keys[k] === 'value') continue;\n if (value[keys[k]] && typeof value[keys[k]] === 'object') __brpRequestFields(value[keys[k]], out);\n }\n } else if (typeof value === 'string' && value) {\n out.push(value);\n }\n return out;\n}\n\nfunction __brpParse(value) {\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch (e) {\n return value;\n }\n}\n\nfunction __brpAssertVisibleFields(policy, query) {\n var q = query || {};\n var fields = [];\n __brpRequestFields(__brpParse(q.filters), fields);\n __brpRequestFields(__brpParse(q.sorts), fields);\n
|
|
45
|
+
var generateBrowserRowPolicyCode = function () { return "// GENERATED \u2014 see browser-row-policy.ts in teleport-plugin-next-data-source.\nvar __BRP_POLICIES = ".concat(JSON.stringify(exports.BROWSER_ROW_POLICIES), ";\n\n// Who is asking: the store's own server code and its administrators read every\n// row; anyone else is a visitor.\nasync function __brpReader(req) {\n if (__taIsInternalRequest(req)) return { trusted: true };\n var token = null;\n try {\n token = await __tqSessionToken(req);\n } catch (e) {\n token = null;\n }\n var role = token ? __taRoleOf(token) : null;\n return { trusted: !!role && __TA_TRUSTED_READER_ROLES.indexOf(role) !== -1 };\n}\n\nfunction __brpMentionsAny(rawQuery, tables) {\n for (var i = 0; i < tables.length; i++) {\n if (__taSqlMentionsTable(rawQuery, tables[i])) return true;\n }\n return false;\n}\n\n// A raw statement cannot be narrowed, so a visitor's statement that names a\n// narrowed table is refused whole. The session is only decoded when the read\n// touches one of these tables.\nasync function __brpAccess(req, tableName, rawQuery) {\n var raw = typeof rawQuery === 'string' && rawQuery.length > 0 ? rawQuery : null;\n var bare = __taNormalizeTable(tableName);\n var rawNarrowed = !!raw && __brpMentionsAny(raw, Object.keys(__BRP_POLICIES));\n var policy = raw ? null : (Object.prototype.hasOwnProperty.call(__BRP_POLICIES, bare) ? __BRP_POLICIES[bare] : null);\n if (!rawNarrowed && !policy) return { denied: null, policy: null };\n var reader = await __brpReader(req);\n if (reader.trusted) return { denied: null, policy: null };\n if (rawNarrowed) {\n return { denied: { status: 403, message: 'Forbidden: raw queries over this table are only available to server-side calls' }, policy: null };\n }\n return { denied: null, policy: policy };\n}\n\nfunction __brpColumnName(field) {\n var parts = String(field == null ? '' : field).replace(/\"/g, '').trim().split('.');\n return parts[parts.length - 1];\n}\n\nfunction __brpIsHidden(policy, field) {\n var name = __brpColumnName(field).toLowerCase();\n for (var i = 0; i < policy.hiddenColumns.length; i++) {\n if (policy.hiddenColumns[i].toLowerCase() === name) return true;\n }\n return false;\n}\n\n// Every field a request filters, sorts or searches by, however deep a filter\n// group nests it. A condition's value is data, not a field.\nfunction __brpRequestFields(value, out) {\n if (Array.isArray(value)) {\n for (var i = 0; i < value.length; i++) __brpRequestFields(value[i], out);\n } else if (value && typeof value === 'object') {\n if (typeof value.source === 'string') out.push(value.source);\n if (typeof value.field === 'string') out.push(value.field);\n var keys = Object.keys(value);\n for (var k = 0; k < keys.length; k++) {\n if (keys[k] === 'destination' || keys[k] === 'value') continue;\n if (value[keys[k]] && typeof value[keys[k]] === 'object') __brpRequestFields(value[keys[k]], out);\n }\n } else if (typeof value === 'string' && value) {\n out.push(value);\n }\n return out;\n}\n\nfunction __brpParse(value) {\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch (e) {\n return value;\n }\n}\n\n// Search columns are not checked here: the read drops the hidden ones from the\n// search (`__brpVisibleColumns`), and the storefront's own list bindings name them.\nfunction __brpAssertVisibleFields(policy, query) {\n var q = query || {};\n var fields = [];\n __brpRequestFields(__brpParse(q.filters), fields);\n __brpRequestFields(__brpParse(q.sorts), fields);\n if (typeof q.sortBy === 'string') fields.push(q.sortBy);\n for (var i = 0; i < fields.length; i++) {\n if (__brpIsHidden(policy, fields[i])) {\n var err = new Error('Forbidden: ' + __brpColumnName(fields[i]) + ' is not readable');\n err.status = 403;\n throw err;\n }\n }\n}\n\nfunction __brpVisibleColumns(policy, columns) {\n return columns.filter(function(column) { return !__brpIsHidden(policy, column); });\n}\n\n// The rows without a hidden key, at any depth: a transformed row nests related\n// rows (a post's related and adjacent posts) built the same way.\nfunction __brpStripHidden(policy, value) {\n if (Array.isArray(value)) {\n return value.map(function(entry) { return __brpStripHidden(policy, entry); });\n }\n if (!value || typeof value !== 'object') return value;\n var copy = {};\n Object.keys(value).forEach(function(key) {\n if (!__brpIsHidden(policy, key)) copy[key] = __brpStripHidden(policy, value[key]);\n });\n return copy;\n}\n\n// Whether this request is answered exactly as an anonymous visitor's would be,\n// so a response cached for one may serve the other. A read whose answer depends\n// on who asks (`viewDependent`: a narrowed, protected or admin-only table) or\n// that runs a raw statement differs for the store's own server code and its\n// administrators, and must never share a cache entry with a visitor.\nasync function __brpSharesPublicView(req, viewDependent) {\n var raw = !!(req && req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0);\n if (!viewDependent && !raw) return true;\n return !(await __brpReader(req)).trusted;\n}\n"); };
|
|
43
46
|
exports.generateBrowserRowPolicyCode = generateBrowserRowPolicyCode;
|
|
44
47
|
/**
|
|
45
48
|
* Whether a per-table read route's answer depends on who asks — the rows it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-row-policy.js","sourceRoot":"","sources":["../../../../src/fetchers/utils/browser-row-policy.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"browser-row-policy.js","sourceRoot":"","sources":["../../../../src/fetchers/utils/browser-row-policy.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AASU,QAAA,oBAAoB,GAA+C;IAC9E,kEAAkE;IAClE,wEAAwE;IACxE,mBAAmB,EAAE;QACnB,SAAS,EAAE,sBAAsB;QACjC,aAAa,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC;KAC/C;IACD,yEAAyE;IACzE,+DAA+D;IAC/D,iBAAiB,EAAE;QACjB,SAAS,EAAE,gCAAgC;QAC3C,aAAa,EAAE,EAAE;KAClB;CACF,CAAA;AAED;;;;;;;GAOG;AACI,IAAM,4BAA4B,GACvC,cAAc,OAAA,mHACO,IAAI,CAAC,SAAS,CAAC,4BAAoB,CAAC,q8JA8H1D,EA/He,CA+Hf,CAAA;AAhIY,QAAA,4BAA4B,gCAgIxC;AAED;;;GAGG;AACI,IAAM,oBAAoB,GAAG,UAClC,SAAiB,EACjB,eAAsC;IAEtC,IAAM,KAAK,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;SAClC,IAAI,EAAE;SACN,WAAW,EAAE;SACb,KAAK,CAAC,GAAG,CAAC,CAAA;IACb,IAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACtD,OAAO,CACL,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,4BAAoB,EAAE,IAAI,CAAC;QAChE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CACrC,CAAA;AACH,CAAC,CAAA;AAbY,QAAA,oBAAoB,wBAahC;AAED;;;GAGG;AACI,IAAM,4BAA4B,GAAG,UAC1C,SAAiB,EACjB,YAA2B;IAE3B,OAAO,uDAAgD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,eAC9E,YAAY,IAAI,MAAM,yNAMzB,CAAA;AACD,CAAC,CAAA;AAZY,QAAA,4BAA4B,gCAYxC"}
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,sBAAsB,EAMvB,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,sBAAsB,EAMvB,MAAM,4BAA4B,CAAA;AAodnC,eAAO,MAAM,+BAA+B,EAAE,sBAAsB,CAAC,EAAE,CAyftE,CAAA;AAiOD,eAAO,MAAM,mCAAmC,EAAE,sBAAsB,CAAC,EAAE,CA0P1E,CAAA;AAED,cAAc,wBAAwB,CAAA;AACtC,cAAc,SAAS,CAAA;AACvB,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AAIvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,gCAAgC,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAGjG,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAA;AACvF,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -67,6 +67,7 @@ var teleport_types_1 = require("@teleporthq/teleport-types");
|
|
|
67
67
|
var teleport_shared_1 = require("@teleporthq/teleport-shared");
|
|
68
68
|
var utils_1 = require("./utils");
|
|
69
69
|
var pagination_plugin_1 = require("./pagination-plugin");
|
|
70
|
+
var repeater_page_size_1 = require("./repeater-page-size");
|
|
70
71
|
var transformations_1 = require("./transformations");
|
|
71
72
|
var isr_1 = require("./isr");
|
|
72
73
|
var request_locale_1 = require("./request-locale");
|
|
@@ -289,8 +290,8 @@ function extractPaginationConfigEarly(uidlNode, resources) {
|
|
|
289
290
|
var renderProp = (_t = node.content) === null || _t === void 0 ? void 0 : _t.renderPropIdentifier;
|
|
290
291
|
var searchEnabled = (_u = node.content) === null || _u === void 0 ? void 0 : _u.searchEnabled;
|
|
291
292
|
var searchDebounce = (_v = node.content) === null || _v === void 0 ? void 0 : _v.searchDebounce;
|
|
292
|
-
if (paginated &&
|
|
293
|
-
perPageMap.set(renderProp, perPage);
|
|
293
|
+
if (paginated && renderProp) {
|
|
294
|
+
perPageMap.set(renderProp, (0, repeater_page_size_1.resolveRepeaterPerPage)(perPage));
|
|
294
295
|
}
|
|
295
296
|
if (searchEnabled && renderProp) {
|
|
296
297
|
searchConfigMap.set(renderProp, {
|