@teleporthq/teleport-plugin-next-data-source 0.40.15
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/ARRAY_MAPPER_PAGINATION.md +1128 -0
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/SEARCH_IMPLEMENTATION_SUMMARY.md +983 -0
- package/__tests__/fetchers.test.ts +545 -0
- package/__tests__/integration.test.ts +561 -0
- package/__tests__/mocks.ts +241 -0
- package/__tests__/pagination.test.ts +31 -0
- package/__tests__/plugin.test.ts +577 -0
- package/__tests__/utils.test.ts +430 -0
- package/__tests__/validation.test.ts +348 -0
- package/dist/cjs/array-mapper-pagination.d.ts +32 -0
- package/dist/cjs/array-mapper-pagination.d.ts.map +1 -0
- package/dist/cjs/array-mapper-pagination.js +77 -0
- package/dist/cjs/array-mapper-pagination.js.map +1 -0
- package/dist/cjs/count-fetchers.d.ts +12 -0
- package/dist/cjs/count-fetchers.d.ts.map +1 -0
- package/dist/cjs/count-fetchers.js +46 -0
- package/dist/cjs/count-fetchers.js.map +1 -0
- package/dist/cjs/data-source-fetchers.d.ts +14 -0
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -0
- package/dist/cjs/data-source-fetchers.js +185 -0
- package/dist/cjs/data-source-fetchers.js.map +1 -0
- package/dist/cjs/fetchers/airtable.d.ts +6 -0
- package/dist/cjs/fetchers/airtable.d.ts.map +1 -0
- package/dist/cjs/fetchers/airtable.js +27 -0
- package/dist/cjs/fetchers/airtable.js.map +1 -0
- package/dist/cjs/fetchers/clickhouse.d.ts +6 -0
- package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -0
- package/dist/cjs/fetchers/clickhouse.js +29 -0
- package/dist/cjs/fetchers/clickhouse.js.map +1 -0
- package/dist/cjs/fetchers/csv-file.d.ts +7 -0
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -0
- package/dist/cjs/fetchers/csv-file.js +36 -0
- package/dist/cjs/fetchers/csv-file.js.map +1 -0
- package/dist/cjs/fetchers/firestore.d.ts +6 -0
- package/dist/cjs/fetchers/firestore.d.ts.map +1 -0
- package/dist/cjs/fetchers/firestore.js +35 -0
- package/dist/cjs/fetchers/firestore.js.map +1 -0
- package/dist/cjs/fetchers/google-sheets.d.ts +6 -0
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -0
- package/dist/cjs/fetchers/google-sheets.js +30 -0
- package/dist/cjs/fetchers/google-sheets.js.map +1 -0
- package/dist/cjs/fetchers/index.d.ts +17 -0
- package/dist/cjs/fetchers/index.d.ts.map +1 -0
- package/dist/cjs/fetchers/index.js +56 -0
- package/dist/cjs/fetchers/index.js.map +1 -0
- package/dist/cjs/fetchers/javascript.d.ts +7 -0
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -0
- package/dist/cjs/fetchers/javascript.js +40 -0
- package/dist/cjs/fetchers/javascript.js.map +1 -0
- package/dist/cjs/fetchers/mariadb.d.ts +3 -0
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -0
- package/dist/cjs/fetchers/mariadb.js +23 -0
- package/dist/cjs/fetchers/mariadb.js.map +1 -0
- package/dist/cjs/fetchers/mongodb.d.ts +7 -0
- package/dist/cjs/fetchers/mongodb.d.ts.map +1 -0
- package/dist/cjs/fetchers/mongodb.js +52 -0
- package/dist/cjs/fetchers/mongodb.js.map +1 -0
- package/dist/cjs/fetchers/mysql.d.ts +3 -0
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -0
- package/dist/cjs/fetchers/mysql.js +30 -0
- package/dist/cjs/fetchers/mysql.js.map +1 -0
- package/dist/cjs/fetchers/postgresql.d.ts +3 -0
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -0
- package/dist/cjs/fetchers/postgresql.js +25 -0
- package/dist/cjs/fetchers/postgresql.js.map +1 -0
- package/dist/cjs/fetchers/redis.d.ts +6 -0
- package/dist/cjs/fetchers/redis.d.ts.map +1 -0
- package/dist/cjs/fetchers/redis.js +46 -0
- package/dist/cjs/fetchers/redis.js.map +1 -0
- package/dist/cjs/fetchers/redshift.d.ts +2 -0
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -0
- package/dist/cjs/fetchers/redshift.js +24 -0
- package/dist/cjs/fetchers/redshift.js.map +1 -0
- package/dist/cjs/fetchers/rest-api.d.ts +6 -0
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -0
- package/dist/cjs/fetchers/rest-api.js +58 -0
- package/dist/cjs/fetchers/rest-api.js.map +1 -0
- package/dist/cjs/fetchers/static-collection.d.ts +7 -0
- package/dist/cjs/fetchers/static-collection.d.ts.map +1 -0
- package/dist/cjs/fetchers/static-collection.js +24 -0
- package/dist/cjs/fetchers/static-collection.js.map +1 -0
- package/dist/cjs/fetchers/supabase.d.ts +7 -0
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -0
- package/dist/cjs/fetchers/supabase.js +42 -0
- package/dist/cjs/fetchers/supabase.js.map +1 -0
- package/dist/cjs/fetchers/turso.d.ts +6 -0
- package/dist/cjs/fetchers/turso.d.ts.map +1 -0
- package/dist/cjs/fetchers/turso.js +25 -0
- package/dist/cjs/fetchers/turso.js.map +1 -0
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +325 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/pagination-plugin.d.ts +5 -0
- package/dist/cjs/pagination-plugin.d.ts.map +1 -0
- package/dist/cjs/pagination-plugin.js +1484 -0
- package/dist/cjs/pagination-plugin.js.map +1 -0
- package/dist/cjs/pagination-with-count.d.ts +6 -0
- package/dist/cjs/pagination-with-count.d.ts.map +1 -0
- package/dist/cjs/pagination-with-count.js +63 -0
- package/dist/cjs/pagination-with-count.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/cjs/utils.d.ts +31 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +763 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/cjs/validation.d.ts +5 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +29 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/array-mapper-pagination.d.ts +32 -0
- package/dist/esm/array-mapper-pagination.d.ts.map +1 -0
- package/dist/esm/array-mapper-pagination.js +72 -0
- package/dist/esm/array-mapper-pagination.js.map +1 -0
- package/dist/esm/count-fetchers.d.ts +12 -0
- package/dist/esm/count-fetchers.d.ts.map +1 -0
- package/dist/esm/count-fetchers.js +35 -0
- package/dist/esm/count-fetchers.js.map +1 -0
- package/dist/esm/data-source-fetchers.d.ts +14 -0
- package/dist/esm/data-source-fetchers.d.ts.map +1 -0
- package/dist/esm/data-source-fetchers.js +179 -0
- package/dist/esm/data-source-fetchers.js.map +1 -0
- package/dist/esm/fetchers/airtable.d.ts +6 -0
- package/dist/esm/fetchers/airtable.d.ts.map +1 -0
- package/dist/esm/fetchers/airtable.js +22 -0
- package/dist/esm/fetchers/airtable.js.map +1 -0
- package/dist/esm/fetchers/clickhouse.d.ts +6 -0
- package/dist/esm/fetchers/clickhouse.d.ts.map +1 -0
- package/dist/esm/fetchers/clickhouse.js +24 -0
- package/dist/esm/fetchers/clickhouse.js.map +1 -0
- package/dist/esm/fetchers/csv-file.d.ts +7 -0
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -0
- package/dist/esm/fetchers/csv-file.js +30 -0
- package/dist/esm/fetchers/csv-file.js.map +1 -0
- package/dist/esm/fetchers/firestore.d.ts +6 -0
- package/dist/esm/fetchers/firestore.d.ts.map +1 -0
- package/dist/esm/fetchers/firestore.js +30 -0
- package/dist/esm/fetchers/firestore.js.map +1 -0
- package/dist/esm/fetchers/google-sheets.d.ts +6 -0
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -0
- package/dist/esm/fetchers/google-sheets.js +25 -0
- package/dist/esm/fetchers/google-sheets.js.map +1 -0
- package/dist/esm/fetchers/index.d.ts +17 -0
- package/dist/esm/fetchers/index.d.ts.map +1 -0
- package/dist/esm/fetchers/index.js +17 -0
- package/dist/esm/fetchers/index.js.map +1 -0
- package/dist/esm/fetchers/javascript.d.ts +7 -0
- package/dist/esm/fetchers/javascript.d.ts.map +1 -0
- package/dist/esm/fetchers/javascript.js +34 -0
- package/dist/esm/fetchers/javascript.js.map +1 -0
- package/dist/esm/fetchers/mariadb.d.ts +3 -0
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -0
- package/dist/esm/fetchers/mariadb.js +18 -0
- package/dist/esm/fetchers/mariadb.js.map +1 -0
- package/dist/esm/fetchers/mongodb.d.ts +7 -0
- package/dist/esm/fetchers/mongodb.d.ts.map +1 -0
- package/dist/esm/fetchers/mongodb.js +46 -0
- package/dist/esm/fetchers/mongodb.js.map +1 -0
- package/dist/esm/fetchers/mysql.d.ts +3 -0
- package/dist/esm/fetchers/mysql.d.ts.map +1 -0
- package/dist/esm/fetchers/mysql.js +25 -0
- package/dist/esm/fetchers/mysql.js.map +1 -0
- package/dist/esm/fetchers/postgresql.d.ts +3 -0
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -0
- package/dist/esm/fetchers/postgresql.js +20 -0
- package/dist/esm/fetchers/postgresql.js.map +1 -0
- package/dist/esm/fetchers/redis.d.ts +6 -0
- package/dist/esm/fetchers/redis.d.ts.map +1 -0
- package/dist/esm/fetchers/redis.js +41 -0
- package/dist/esm/fetchers/redis.js.map +1 -0
- package/dist/esm/fetchers/redshift.d.ts +2 -0
- package/dist/esm/fetchers/redshift.d.ts.map +1 -0
- package/dist/esm/fetchers/redshift.js +20 -0
- package/dist/esm/fetchers/redshift.js.map +1 -0
- package/dist/esm/fetchers/rest-api.d.ts +6 -0
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -0
- package/dist/esm/fetchers/rest-api.js +53 -0
- package/dist/esm/fetchers/rest-api.js.map +1 -0
- package/dist/esm/fetchers/static-collection.d.ts +7 -0
- package/dist/esm/fetchers/static-collection.d.ts.map +1 -0
- package/dist/esm/fetchers/static-collection.js +18 -0
- package/dist/esm/fetchers/static-collection.js.map +1 -0
- package/dist/esm/fetchers/supabase.d.ts +7 -0
- package/dist/esm/fetchers/supabase.d.ts.map +1 -0
- package/dist/esm/fetchers/supabase.js +36 -0
- package/dist/esm/fetchers/supabase.js.map +1 -0
- package/dist/esm/fetchers/turso.d.ts +6 -0
- package/dist/esm/fetchers/turso.d.ts.map +1 -0
- package/dist/esm/fetchers/turso.js +20 -0
- package/dist/esm/fetchers/turso.js.map +1 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +306 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/pagination-plugin.d.ts +5 -0
- package/dist/esm/pagination-plugin.d.ts.map +1 -0
- package/dist/esm/pagination-plugin.js +1457 -0
- package/dist/esm/pagination-plugin.js.map +1 -0
- package/dist/esm/pagination-with-count.d.ts +6 -0
- package/dist/esm/pagination-with-count.d.ts.map +1 -0
- package/dist/esm/pagination-with-count.js +34 -0
- package/dist/esm/pagination-with-count.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/utils.d.ts +31 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +722 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/validation.d.ts +5 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +25 -0
- package/dist/esm/validation.js.map +1 -0
- package/package.json +33 -0
- package/src/array-mapper-pagination.ts +113 -0
- package/src/count-fetchers.ts +99 -0
- package/src/data-source-fetchers.ts +313 -0
- package/src/fetchers/airtable.ts +153 -0
- package/src/fetchers/clickhouse.ts +127 -0
- package/src/fetchers/csv-file.ts +163 -0
- package/src/fetchers/firestore.ts +138 -0
- package/src/fetchers/google-sheets.ts +189 -0
- package/src/fetchers/index.ts +32 -0
- package/src/fetchers/javascript.ts +150 -0
- package/src/fetchers/mariadb.ts +230 -0
- package/src/fetchers/mongodb.ts +239 -0
- package/src/fetchers/mysql.ts +237 -0
- package/src/fetchers/postgresql.ts +247 -0
- package/src/fetchers/redis.ts +152 -0
- package/src/fetchers/redshift.ts +138 -0
- package/src/fetchers/rest-api.ts +148 -0
- package/src/fetchers/static-collection.ts +149 -0
- package/src/fetchers/supabase.ts +246 -0
- package/src/fetchers/turso.ts +131 -0
- package/src/index.ts +352 -0
- package/src/pagination-plugin.ts +2335 -0
- package/src/pagination-with-count.ts +89 -0
- package/src/utils.ts +1013 -0
- package/src/validation.ts +32 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,2335 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentPlugin,
|
|
3
|
+
ComponentPluginFactory,
|
|
4
|
+
ChunkType,
|
|
5
|
+
FileType,
|
|
6
|
+
} from '@teleporthq/teleport-types'
|
|
7
|
+
import * as types from '@babel/types'
|
|
8
|
+
import { generatePaginationLogic, ArrayMapperPaginationInfo } from './array-mapper-pagination'
|
|
9
|
+
import { extractDataSourceIntoNextAPIFolder } from './utils'
|
|
10
|
+
|
|
11
|
+
interface DetectedPagination {
|
|
12
|
+
paginationNodeClass: string
|
|
13
|
+
prevButtonClass: string | null
|
|
14
|
+
nextButtonClass: string | null
|
|
15
|
+
dataSourceIdentifier: string
|
|
16
|
+
dataProviderJSX: any
|
|
17
|
+
arrayMapperRenderProp?: string
|
|
18
|
+
searchInputClass?: string | null
|
|
19
|
+
searchInputJSX?: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> = () => {
|
|
23
|
+
const paginationPlugin: ComponentPlugin = async (structure) => {
|
|
24
|
+
const { uidl, chunks, dependencies, options } = structure
|
|
25
|
+
|
|
26
|
+
const componentChunk = chunks.find((chunk) => chunk.name === 'jsx-component')
|
|
27
|
+
if (!componentChunk || componentChunk.type !== ChunkType.AST) {
|
|
28
|
+
return structure
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!options || !options.dataSources || !options.extractedResources) {
|
|
32
|
+
return structure
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const variableDeclaration = componentChunk.content as types.VariableDeclaration
|
|
36
|
+
if (!variableDeclaration.declarations || variableDeclaration.declarations.length === 0) {
|
|
37
|
+
return structure
|
|
38
|
+
}
|
|
39
|
+
const declarator = variableDeclaration.declarations[0] as types.VariableDeclarator
|
|
40
|
+
if (!declarator.init) {
|
|
41
|
+
return structure
|
|
42
|
+
}
|
|
43
|
+
const arrowFunction = declarator.init as types.ArrowFunctionExpression
|
|
44
|
+
if (!arrowFunction.body) {
|
|
45
|
+
return structure
|
|
46
|
+
}
|
|
47
|
+
const blockStatement = arrowFunction.body as types.BlockStatement
|
|
48
|
+
|
|
49
|
+
const { paginatedMappers, searchOnlyMappers, paginationOnlyMappers } =
|
|
50
|
+
detectPaginationsAndSearchFromJSX(blockStatement, uidl.node)
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
paginatedMappers.length === 0 &&
|
|
54
|
+
searchOnlyMappers.length === 0 &&
|
|
55
|
+
paginationOnlyMappers.length === 0
|
|
56
|
+
) {
|
|
57
|
+
return structure
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Combine pagination+search and pagination-only into one array for processing
|
|
61
|
+
const detectedPaginations = [...paginatedMappers, ...paginationOnlyMappers]
|
|
62
|
+
const detectedSearchOnly = searchOnlyMappers
|
|
63
|
+
|
|
64
|
+
if (!dependencies.useState) {
|
|
65
|
+
dependencies.useState = {
|
|
66
|
+
type: 'library',
|
|
67
|
+
path: 'react',
|
|
68
|
+
version: '',
|
|
69
|
+
meta: {
|
|
70
|
+
namedImport: true,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!dependencies.useMemo) {
|
|
76
|
+
dependencies.useMemo = {
|
|
77
|
+
type: 'library',
|
|
78
|
+
path: 'react',
|
|
79
|
+
version: '',
|
|
80
|
+
meta: {
|
|
81
|
+
namedImport: true,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!dependencies.useCallback) {
|
|
87
|
+
dependencies.useCallback = {
|
|
88
|
+
type: 'library',
|
|
89
|
+
path: 'react',
|
|
90
|
+
version: '',
|
|
91
|
+
meta: {
|
|
92
|
+
namedImport: true,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!dependencies.useRef) {
|
|
98
|
+
dependencies.useRef = {
|
|
99
|
+
type: 'library',
|
|
100
|
+
path: 'react',
|
|
101
|
+
version: '',
|
|
102
|
+
meta: {
|
|
103
|
+
namedImport: true,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!componentChunk.meta) {
|
|
109
|
+
componentChunk.meta = {}
|
|
110
|
+
}
|
|
111
|
+
componentChunk.meta.isClientComponent = true
|
|
112
|
+
|
|
113
|
+
const paginationInfos: ArrayMapperPaginationInfo[] = []
|
|
114
|
+
|
|
115
|
+
// Check if this is a page (has getStaticProps) or a component
|
|
116
|
+
const getStaticPropsChunk = chunks.find((chunk) => chunk.name === 'getStaticProps')
|
|
117
|
+
const isPage = !!getStaticPropsChunk
|
|
118
|
+
const isComponent = !isPage
|
|
119
|
+
|
|
120
|
+
// Get pagination and search config from early extraction (done before transformations)
|
|
121
|
+
const opts = options as any
|
|
122
|
+
const perPageMap = opts.paginationConfig?.perPageMap || new Map<string, number>()
|
|
123
|
+
const searchConfigMap = opts.paginationConfig?.searchConfigMap || new Map<string, any>()
|
|
124
|
+
const queryColumnsMap = opts.paginationConfig?.queryColumnsMap || new Map<string, string[]>()
|
|
125
|
+
|
|
126
|
+
detectedPaginations.forEach((detected, index) => {
|
|
127
|
+
const paginationNodeId = `pg_${index}`
|
|
128
|
+
// Use arrayMapperRenderProp if available, otherwise fall back to dataSourceIdentifier
|
|
129
|
+
const lookupKey = detected.arrayMapperRenderProp || detected.dataSourceIdentifier
|
|
130
|
+
const perPage = perPageMap.get(lookupKey) || 10
|
|
131
|
+
const searchConfig = searchConfigMap.get(lookupKey)
|
|
132
|
+
// queryColumns is keyed by dataSourceIdentifier, not array mapper render prop
|
|
133
|
+
const queryColumns = queryColumnsMap.get(detected.dataSourceIdentifier)
|
|
134
|
+
|
|
135
|
+
const info = generatePaginationLogic(
|
|
136
|
+
paginationNodeId,
|
|
137
|
+
detected.dataSourceIdentifier,
|
|
138
|
+
perPage,
|
|
139
|
+
searchConfig,
|
|
140
|
+
queryColumns
|
|
141
|
+
)
|
|
142
|
+
paginationInfos.push(info)
|
|
143
|
+
|
|
144
|
+
// If both pagination and search are enabled, combine them into a single state object
|
|
145
|
+
if (info.searchEnabled && info.searchQueryVar && info.setSearchQueryVar) {
|
|
146
|
+
// Combined state: { page: 1, debouncedQuery: '' }
|
|
147
|
+
const combinedStateVar = `paginationState_pg_${index}`
|
|
148
|
+
const setCombinedStateVar = `setPaginationState_pg_${index}`
|
|
149
|
+
|
|
150
|
+
const combinedStateAST = types.variableDeclaration('const', [
|
|
151
|
+
types.variableDeclarator(
|
|
152
|
+
types.arrayPattern([
|
|
153
|
+
types.identifier(combinedStateVar),
|
|
154
|
+
types.identifier(setCombinedStateVar),
|
|
155
|
+
]),
|
|
156
|
+
types.callExpression(types.identifier('useState'), [
|
|
157
|
+
types.objectExpression([
|
|
158
|
+
types.objectProperty(types.identifier('page'), types.numericLiteral(1)),
|
|
159
|
+
types.objectProperty(types.identifier('debouncedQuery'), types.stringLiteral('')),
|
|
160
|
+
]),
|
|
161
|
+
])
|
|
162
|
+
),
|
|
163
|
+
])
|
|
164
|
+
blockStatement.body.unshift(combinedStateAST)
|
|
165
|
+
|
|
166
|
+
// Still need the immediate search query state for the input
|
|
167
|
+
const searchStateAST = types.variableDeclaration('const', [
|
|
168
|
+
types.variableDeclarator(
|
|
169
|
+
types.arrayPattern([
|
|
170
|
+
types.identifier(info.searchQueryVar),
|
|
171
|
+
types.identifier(info.setSearchQueryVar),
|
|
172
|
+
]),
|
|
173
|
+
types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
|
|
174
|
+
),
|
|
175
|
+
])
|
|
176
|
+
blockStatement.body.unshift(searchStateAST)
|
|
177
|
+
|
|
178
|
+
// Store the combined state var names
|
|
179
|
+
;(info as any).combinedStateVar = combinedStateVar
|
|
180
|
+
;(info as any).setCombinedStateVar = setCombinedStateVar
|
|
181
|
+
} else {
|
|
182
|
+
// If search is not enabled, just add regular page state
|
|
183
|
+
const pageStateAST = types.variableDeclaration('const', [
|
|
184
|
+
types.variableDeclarator(
|
|
185
|
+
types.arrayPattern([
|
|
186
|
+
types.identifier(info.pageStateVar),
|
|
187
|
+
types.identifier(info.setPageStateVar),
|
|
188
|
+
]),
|
|
189
|
+
types.callExpression(types.identifier('useState'), [types.numericLiteral(1)])
|
|
190
|
+
),
|
|
191
|
+
])
|
|
192
|
+
blockStatement.body.unshift(pageStateAST)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Add maxPages state
|
|
196
|
+
const maxPagesStateVar = `${info.pageStateVar.replace('_page', '')}_maxPages`
|
|
197
|
+
const setMaxPagesStateVar = `set${
|
|
198
|
+
maxPagesStateVar.charAt(0).toUpperCase() + maxPagesStateVar.slice(1)
|
|
199
|
+
}`
|
|
200
|
+
|
|
201
|
+
// For pages: initialize from props (with pagination-specific prop name), for components: initialize to 0
|
|
202
|
+
const maxPagesInitValue = isPage
|
|
203
|
+
? types.logicalExpression(
|
|
204
|
+
'||',
|
|
205
|
+
types.optionalMemberExpression(
|
|
206
|
+
types.identifier('props'),
|
|
207
|
+
types.identifier(`${info.dataSourceIdentifier}_pg_${index}_maxPages`),
|
|
208
|
+
false,
|
|
209
|
+
true
|
|
210
|
+
),
|
|
211
|
+
types.numericLiteral(0)
|
|
212
|
+
)
|
|
213
|
+
: types.numericLiteral(0)
|
|
214
|
+
|
|
215
|
+
const maxPagesStateAST = types.variableDeclaration('const', [
|
|
216
|
+
types.variableDeclarator(
|
|
217
|
+
types.arrayPattern([
|
|
218
|
+
types.identifier(maxPagesStateVar),
|
|
219
|
+
types.identifier(setMaxPagesStateVar),
|
|
220
|
+
]),
|
|
221
|
+
types.callExpression(types.identifier('useState'), [maxPagesInitValue])
|
|
222
|
+
),
|
|
223
|
+
])
|
|
224
|
+
blockStatement.body.unshift(maxPagesStateAST)
|
|
225
|
+
|
|
226
|
+
// Store these for later use
|
|
227
|
+
;(info as any).maxPagesStateVar = maxPagesStateVar
|
|
228
|
+
;(info as any).setMaxPagesStateVar = setMaxPagesStateVar
|
|
229
|
+
|
|
230
|
+
// Add refs to track first render for each useEffect
|
|
231
|
+
if (info.searchEnabled) {
|
|
232
|
+
const skipDebounceOnMountRefVar = `skipDebounceOnMount_pg_${index}`
|
|
233
|
+
const skipDebounceRefAST = types.variableDeclaration('const', [
|
|
234
|
+
types.variableDeclarator(
|
|
235
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
236
|
+
types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
|
|
237
|
+
),
|
|
238
|
+
])
|
|
239
|
+
blockStatement.body.unshift(skipDebounceRefAST)
|
|
240
|
+
;(info as any).skipDebounceOnMountRefVar = skipDebounceOnMountRefVar
|
|
241
|
+
|
|
242
|
+
const skipCountFetchOnMountRefVar = `skipCountFetchOnMount_pg_${index}`
|
|
243
|
+
const skipCountFetchRefAST = types.variableDeclaration('const', [
|
|
244
|
+
types.variableDeclarator(
|
|
245
|
+
types.identifier(skipCountFetchOnMountRefVar),
|
|
246
|
+
types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
|
|
247
|
+
),
|
|
248
|
+
])
|
|
249
|
+
blockStatement.body.unshift(skipCountFetchRefAST)
|
|
250
|
+
;(info as any).skipCountFetchOnMountRefVar = skipCountFetchOnMountRefVar
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Add useEffect dependency if any pagination has search enabled
|
|
255
|
+
const hasSearchEnabled = paginationInfos.some((info) => info.searchEnabled)
|
|
256
|
+
if (hasSearchEnabled && !dependencies.useEffect) {
|
|
257
|
+
dependencies.useEffect = {
|
|
258
|
+
type: 'library',
|
|
259
|
+
path: 'react',
|
|
260
|
+
version: '',
|
|
261
|
+
meta: {
|
|
262
|
+
namedImport: true,
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Add all useEffect hooks AFTER state declarations
|
|
268
|
+
// Find the first return statement and insert effects before it
|
|
269
|
+
const firstReturnIndex = blockStatement.body.findIndex(
|
|
270
|
+
(stmt: any) => stmt.type === 'ReturnStatement'
|
|
271
|
+
)
|
|
272
|
+
const insertIndex = firstReturnIndex !== -1 ? firstReturnIndex : blockStatement.body.length
|
|
273
|
+
|
|
274
|
+
// Add effects in reverse order so they appear in correct order
|
|
275
|
+
paginationInfos.forEach((info) => {
|
|
276
|
+
if (
|
|
277
|
+
info.searchEnabled &&
|
|
278
|
+
info.searchQueryVar &&
|
|
279
|
+
info.debouncedSearchQueryVar &&
|
|
280
|
+
info.setDebouncedSearchQueryVar
|
|
281
|
+
) {
|
|
282
|
+
// Add effect that updates debounced search after delay
|
|
283
|
+
const skipDebounceOnMountRefVar = (info as any).skipDebounceOnMountRefVar
|
|
284
|
+
const setCombinedStateVar = (info as any).setCombinedStateVar
|
|
285
|
+
|
|
286
|
+
const debounceEffect = types.expressionStatement(
|
|
287
|
+
types.callExpression(types.identifier('useEffect'), [
|
|
288
|
+
types.arrowFunctionExpression(
|
|
289
|
+
[],
|
|
290
|
+
types.blockStatement([
|
|
291
|
+
types.ifStatement(
|
|
292
|
+
types.memberExpression(
|
|
293
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
294
|
+
types.identifier('current')
|
|
295
|
+
),
|
|
296
|
+
types.blockStatement([
|
|
297
|
+
types.expressionStatement(
|
|
298
|
+
types.assignmentExpression(
|
|
299
|
+
'=',
|
|
300
|
+
types.memberExpression(
|
|
301
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
302
|
+
types.identifier('current')
|
|
303
|
+
),
|
|
304
|
+
types.booleanLiteral(false)
|
|
305
|
+
)
|
|
306
|
+
),
|
|
307
|
+
types.returnStatement(),
|
|
308
|
+
])
|
|
309
|
+
),
|
|
310
|
+
types.variableDeclaration('const', [
|
|
311
|
+
types.variableDeclarator(
|
|
312
|
+
types.identifier('timer'),
|
|
313
|
+
types.callExpression(types.identifier('setTimeout'), [
|
|
314
|
+
types.arrowFunctionExpression(
|
|
315
|
+
[],
|
|
316
|
+
types.blockStatement([
|
|
317
|
+
types.expressionStatement(
|
|
318
|
+
types.callExpression(types.identifier(setCombinedStateVar), [
|
|
319
|
+
types.objectExpression([
|
|
320
|
+
types.objectProperty(
|
|
321
|
+
types.identifier('page'),
|
|
322
|
+
types.numericLiteral(1)
|
|
323
|
+
),
|
|
324
|
+
types.objectProperty(
|
|
325
|
+
types.identifier('debouncedQuery'),
|
|
326
|
+
types.identifier(info.searchQueryVar)
|
|
327
|
+
),
|
|
328
|
+
]),
|
|
329
|
+
])
|
|
330
|
+
),
|
|
331
|
+
])
|
|
332
|
+
),
|
|
333
|
+
types.numericLiteral(info.searchDebounce || 300),
|
|
334
|
+
])
|
|
335
|
+
),
|
|
336
|
+
]),
|
|
337
|
+
types.returnStatement(
|
|
338
|
+
types.arrowFunctionExpression(
|
|
339
|
+
[],
|
|
340
|
+
types.callExpression(types.identifier('clearTimeout'), [
|
|
341
|
+
types.identifier('timer'),
|
|
342
|
+
])
|
|
343
|
+
)
|
|
344
|
+
),
|
|
345
|
+
])
|
|
346
|
+
),
|
|
347
|
+
types.arrayExpression([types.identifier(info.searchQueryVar)]),
|
|
348
|
+
])
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
blockStatement.body.splice(insertIndex, 0, debounceEffect)
|
|
352
|
+
|
|
353
|
+
// Add useEffect to refetch count when search changes (for both pages and components)
|
|
354
|
+
if (info.queryColumns && info.queryColumns.length > 0) {
|
|
355
|
+
const detected = detectedPaginations.find(
|
|
356
|
+
(d) => d.dataSourceIdentifier === info.dataSourceIdentifier
|
|
357
|
+
)
|
|
358
|
+
if (!detected) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const resourceDefAttr = detected.dataProviderJSX.openingElement.attributes.find(
|
|
363
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
if (
|
|
367
|
+
resourceDefAttr &&
|
|
368
|
+
resourceDefAttr.value &&
|
|
369
|
+
resourceDefAttr.value.type === 'JSXExpressionContainer'
|
|
370
|
+
) {
|
|
371
|
+
const resourceDef = resourceDefAttr.value.expression
|
|
372
|
+
if (resourceDef.type === 'ObjectExpression') {
|
|
373
|
+
const dataSourceIdProp = (resourceDef.properties as any[]).find(
|
|
374
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceId'
|
|
375
|
+
)
|
|
376
|
+
const tableNameProp = (resourceDef.properties as any[]).find(
|
|
377
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'tableName'
|
|
378
|
+
)
|
|
379
|
+
const dataSourceTypeProp = (resourceDef.properties as any[]).find(
|
|
380
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceType'
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
if (dataSourceIdProp && tableNameProp && dataSourceTypeProp) {
|
|
384
|
+
const dataSourceId = dataSourceIdProp.value.value
|
|
385
|
+
const tableName = tableNameProp.value.value
|
|
386
|
+
const dataSourceType = dataSourceTypeProp.value.value
|
|
387
|
+
const fileName = `${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
|
|
388
|
+
const setMaxPagesStateVar = (info as any).setMaxPagesStateVar
|
|
389
|
+
|
|
390
|
+
// Create useEffect to refetch count when debounced search changes
|
|
391
|
+
const skipCountFetchOnMountRefVar = (info as any).skipCountFetchOnMountRefVar
|
|
392
|
+
const combinedStateVar = (info as any).combinedStateVar
|
|
393
|
+
|
|
394
|
+
const refetchCountEffect = types.expressionStatement(
|
|
395
|
+
types.callExpression(types.identifier('useEffect'), [
|
|
396
|
+
types.arrowFunctionExpression(
|
|
397
|
+
[],
|
|
398
|
+
types.blockStatement([
|
|
399
|
+
types.ifStatement(
|
|
400
|
+
types.memberExpression(
|
|
401
|
+
types.identifier(skipCountFetchOnMountRefVar),
|
|
402
|
+
types.identifier('current')
|
|
403
|
+
),
|
|
404
|
+
types.blockStatement([
|
|
405
|
+
types.expressionStatement(
|
|
406
|
+
types.assignmentExpression(
|
|
407
|
+
'=',
|
|
408
|
+
types.memberExpression(
|
|
409
|
+
types.identifier(skipCountFetchOnMountRefVar),
|
|
410
|
+
types.identifier('current')
|
|
411
|
+
),
|
|
412
|
+
types.booleanLiteral(false)
|
|
413
|
+
)
|
|
414
|
+
),
|
|
415
|
+
types.returnStatement(),
|
|
416
|
+
])
|
|
417
|
+
),
|
|
418
|
+
types.expressionStatement(
|
|
419
|
+
types.callExpression(
|
|
420
|
+
types.memberExpression(
|
|
421
|
+
types.callExpression(
|
|
422
|
+
types.memberExpression(
|
|
423
|
+
types.callExpression(types.identifier('fetch'), [
|
|
424
|
+
types.templateLiteral(
|
|
425
|
+
[
|
|
426
|
+
types.templateElement({
|
|
427
|
+
raw: `/api/${fileName}-count?`,
|
|
428
|
+
cooked: `/api/${fileName}-count?`,
|
|
429
|
+
}),
|
|
430
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
431
|
+
],
|
|
432
|
+
[
|
|
433
|
+
types.newExpression(types.identifier('URLSearchParams'), [
|
|
434
|
+
types.objectExpression([
|
|
435
|
+
types.objectProperty(
|
|
436
|
+
types.identifier('query'),
|
|
437
|
+
types.memberExpression(
|
|
438
|
+
types.identifier(combinedStateVar),
|
|
439
|
+
types.identifier('debouncedQuery')
|
|
440
|
+
)
|
|
441
|
+
),
|
|
442
|
+
types.objectProperty(
|
|
443
|
+
types.identifier('queryColumns'),
|
|
444
|
+
types.callExpression(
|
|
445
|
+
types.memberExpression(
|
|
446
|
+
types.identifier('JSON'),
|
|
447
|
+
types.identifier('stringify')
|
|
448
|
+
),
|
|
449
|
+
[
|
|
450
|
+
types.arrayExpression(
|
|
451
|
+
info.queryColumns.map((col) =>
|
|
452
|
+
types.stringLiteral(col)
|
|
453
|
+
)
|
|
454
|
+
),
|
|
455
|
+
]
|
|
456
|
+
)
|
|
457
|
+
),
|
|
458
|
+
]),
|
|
459
|
+
]),
|
|
460
|
+
]
|
|
461
|
+
),
|
|
462
|
+
]),
|
|
463
|
+
types.identifier('then')
|
|
464
|
+
),
|
|
465
|
+
[
|
|
466
|
+
types.arrowFunctionExpression(
|
|
467
|
+
[types.identifier('res')],
|
|
468
|
+
types.callExpression(
|
|
469
|
+
types.memberExpression(
|
|
470
|
+
types.identifier('res'),
|
|
471
|
+
types.identifier('json')
|
|
472
|
+
),
|
|
473
|
+
[]
|
|
474
|
+
)
|
|
475
|
+
),
|
|
476
|
+
]
|
|
477
|
+
),
|
|
478
|
+
types.identifier('then')
|
|
479
|
+
),
|
|
480
|
+
[
|
|
481
|
+
types.arrowFunctionExpression(
|
|
482
|
+
[types.identifier('data')],
|
|
483
|
+
types.blockStatement([
|
|
484
|
+
types.ifStatement(
|
|
485
|
+
types.logicalExpression(
|
|
486
|
+
'&&',
|
|
487
|
+
types.identifier('data'),
|
|
488
|
+
types.binaryExpression(
|
|
489
|
+
'in',
|
|
490
|
+
types.stringLiteral('count'),
|
|
491
|
+
types.identifier('data')
|
|
492
|
+
)
|
|
493
|
+
),
|
|
494
|
+
types.blockStatement([
|
|
495
|
+
types.expressionStatement(
|
|
496
|
+
types.callExpression(
|
|
497
|
+
types.identifier(setMaxPagesStateVar),
|
|
498
|
+
[
|
|
499
|
+
types.conditionalExpression(
|
|
500
|
+
types.binaryExpression(
|
|
501
|
+
'===',
|
|
502
|
+
types.memberExpression(
|
|
503
|
+
types.identifier('data'),
|
|
504
|
+
types.identifier('count')
|
|
505
|
+
),
|
|
506
|
+
types.numericLiteral(0)
|
|
507
|
+
),
|
|
508
|
+
types.numericLiteral(0),
|
|
509
|
+
types.callExpression(
|
|
510
|
+
types.memberExpression(
|
|
511
|
+
types.identifier('Math'),
|
|
512
|
+
types.identifier('ceil')
|
|
513
|
+
),
|
|
514
|
+
[
|
|
515
|
+
types.binaryExpression(
|
|
516
|
+
'/',
|
|
517
|
+
types.memberExpression(
|
|
518
|
+
types.identifier('data'),
|
|
519
|
+
types.identifier('count')
|
|
520
|
+
),
|
|
521
|
+
types.numericLiteral(info.perPage)
|
|
522
|
+
),
|
|
523
|
+
]
|
|
524
|
+
)
|
|
525
|
+
),
|
|
526
|
+
]
|
|
527
|
+
)
|
|
528
|
+
),
|
|
529
|
+
])
|
|
530
|
+
),
|
|
531
|
+
])
|
|
532
|
+
),
|
|
533
|
+
]
|
|
534
|
+
)
|
|
535
|
+
),
|
|
536
|
+
])
|
|
537
|
+
),
|
|
538
|
+
types.arrayExpression([
|
|
539
|
+
types.memberExpression(
|
|
540
|
+
types.identifier(combinedStateVar),
|
|
541
|
+
types.identifier('debouncedQuery')
|
|
542
|
+
),
|
|
543
|
+
]),
|
|
544
|
+
])
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
blockStatement.body.splice(insertIndex, 0, refetchCountEffect)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
// For components, add useEffect to fetch count on mount
|
|
556
|
+
if (isComponent) {
|
|
557
|
+
if (!dependencies.useEffect) {
|
|
558
|
+
dependencies.useEffect = {
|
|
559
|
+
type: 'library',
|
|
560
|
+
path: 'react',
|
|
561
|
+
version: '',
|
|
562
|
+
meta: {
|
|
563
|
+
namedImport: true,
|
|
564
|
+
},
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Group paginationInfos by data source identifier to avoid duplicate fetches
|
|
569
|
+
const dataSourceToInfos = new Map<
|
|
570
|
+
string,
|
|
571
|
+
Array<{ info: ArrayMapperPaginationInfo; detected: any; fileName: string }>
|
|
572
|
+
>()
|
|
573
|
+
|
|
574
|
+
paginationInfos.forEach((info) => {
|
|
575
|
+
const detected = detectedPaginations.find(
|
|
576
|
+
(d) => d.dataSourceIdentifier === info.dataSourceIdentifier
|
|
577
|
+
)
|
|
578
|
+
if (!detected) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const resourceDefAttr = detected.dataProviderJSX.openingElement.attributes.find(
|
|
583
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
if (
|
|
587
|
+
!resourceDefAttr ||
|
|
588
|
+
!resourceDefAttr.value ||
|
|
589
|
+
resourceDefAttr.value.type !== 'JSXExpressionContainer'
|
|
590
|
+
) {
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const resourceDef = resourceDefAttr.value.expression
|
|
595
|
+
if (resourceDef.type !== 'ObjectExpression') {
|
|
596
|
+
return
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const dataSourceIdProp = (resourceDef.properties as any[]).find(
|
|
600
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceId'
|
|
601
|
+
)
|
|
602
|
+
const tableNameProp = (resourceDef.properties as any[]).find(
|
|
603
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'tableName'
|
|
604
|
+
)
|
|
605
|
+
const dataSourceTypeProp = (resourceDef.properties as any[]).find(
|
|
606
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceType'
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if (!dataSourceIdProp || !tableNameProp || !dataSourceTypeProp) {
|
|
610
|
+
return
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const dataSourceId = dataSourceIdProp.value.value
|
|
614
|
+
const tableName = tableNameProp.value.value
|
|
615
|
+
const dataSourceType = dataSourceTypeProp.value.value
|
|
616
|
+
const fileName = `${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
|
|
617
|
+
|
|
618
|
+
// Group by data source identifier
|
|
619
|
+
if (!dataSourceToInfos.has(info.dataSourceIdentifier)) {
|
|
620
|
+
dataSourceToInfos.set(info.dataSourceIdentifier, [])
|
|
621
|
+
}
|
|
622
|
+
dataSourceToInfos.get(info.dataSourceIdentifier)!.push({ info, detected, fileName })
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
// Create ONE useEffect per unique data source
|
|
626
|
+
dataSourceToInfos.forEach((infos) => {
|
|
627
|
+
const { fileName } = infos[0]
|
|
628
|
+
|
|
629
|
+
// Create array of setState calls - one for each pagination using this data source
|
|
630
|
+
const setStateStatements = infos.map(({ info }) => {
|
|
631
|
+
const setMaxPagesStateVar = (info as any).setMaxPagesStateVar
|
|
632
|
+
return types.expressionStatement(
|
|
633
|
+
types.callExpression(types.identifier(setMaxPagesStateVar), [
|
|
634
|
+
types.callExpression(
|
|
635
|
+
types.memberExpression(types.identifier('Math'), types.identifier('ceil')),
|
|
636
|
+
[
|
|
637
|
+
types.binaryExpression(
|
|
638
|
+
'/',
|
|
639
|
+
types.memberExpression(types.identifier('data'), types.identifier('count')),
|
|
640
|
+
types.numericLiteral(info.perPage)
|
|
641
|
+
),
|
|
642
|
+
]
|
|
643
|
+
),
|
|
644
|
+
])
|
|
645
|
+
)
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
// Create useEffect to fetch count on mount - sets ALL maxPages for this data source
|
|
649
|
+
const useEffectAST = types.expressionStatement(
|
|
650
|
+
types.callExpression(types.identifier('useEffect'), [
|
|
651
|
+
types.arrowFunctionExpression(
|
|
652
|
+
[],
|
|
653
|
+
types.blockStatement([
|
|
654
|
+
types.expressionStatement(
|
|
655
|
+
types.callExpression(
|
|
656
|
+
types.memberExpression(
|
|
657
|
+
types.callExpression(
|
|
658
|
+
types.memberExpression(
|
|
659
|
+
types.callExpression(types.identifier('fetch'), [
|
|
660
|
+
types.stringLiteral(`/api/${fileName}-count`),
|
|
661
|
+
]),
|
|
662
|
+
types.identifier('then')
|
|
663
|
+
),
|
|
664
|
+
[
|
|
665
|
+
types.arrowFunctionExpression(
|
|
666
|
+
[types.identifier('res')],
|
|
667
|
+
types.callExpression(
|
|
668
|
+
types.memberExpression(
|
|
669
|
+
types.identifier('res'),
|
|
670
|
+
types.identifier('json')
|
|
671
|
+
),
|
|
672
|
+
[]
|
|
673
|
+
)
|
|
674
|
+
),
|
|
675
|
+
]
|
|
676
|
+
),
|
|
677
|
+
types.identifier('then')
|
|
678
|
+
),
|
|
679
|
+
[
|
|
680
|
+
types.arrowFunctionExpression(
|
|
681
|
+
[types.identifier('data')],
|
|
682
|
+
types.blockStatement([
|
|
683
|
+
types.ifStatement(
|
|
684
|
+
types.logicalExpression(
|
|
685
|
+
'&&',
|
|
686
|
+
types.identifier('data'),
|
|
687
|
+
types.memberExpression(
|
|
688
|
+
types.identifier('data'),
|
|
689
|
+
types.identifier('count')
|
|
690
|
+
)
|
|
691
|
+
),
|
|
692
|
+
types.blockStatement(setStateStatements)
|
|
693
|
+
),
|
|
694
|
+
])
|
|
695
|
+
),
|
|
696
|
+
]
|
|
697
|
+
)
|
|
698
|
+
),
|
|
699
|
+
])
|
|
700
|
+
),
|
|
701
|
+
types.arrayExpression([]),
|
|
702
|
+
])
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
blockStatement.body.unshift(useEffectAST)
|
|
706
|
+
})
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
createAPIRoutesForPaginatedDataSources(
|
|
710
|
+
uidl.node,
|
|
711
|
+
options.dataSources,
|
|
712
|
+
componentChunk,
|
|
713
|
+
options.extractedResources,
|
|
714
|
+
paginationInfos,
|
|
715
|
+
isComponent
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
detectedPaginations.forEach((detected, index) => {
|
|
719
|
+
addPaginationParamsToDataProvider(detected.dataProviderJSX, paginationInfos[index], index)
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
modifyPaginationButtons(blockStatement, detectedPaginations, paginationInfos)
|
|
723
|
+
modifySearchInputs(blockStatement, detectedPaginations, paginationInfos)
|
|
724
|
+
|
|
725
|
+
if (isPage) {
|
|
726
|
+
modifyGetStaticPropsForPagination(chunks, paginationInfos)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Handle search-only array mappers (no need to filter - they're already separate)
|
|
730
|
+
// The detection function already separated them into different arrays
|
|
731
|
+
const searchOnlyDataSources = detectedSearchOnly
|
|
732
|
+
|
|
733
|
+
if (searchOnlyDataSources.length > 0) {
|
|
734
|
+
handleSearchOnlyArrayMappers(
|
|
735
|
+
blockStatement,
|
|
736
|
+
searchOnlyDataSources,
|
|
737
|
+
searchConfigMap,
|
|
738
|
+
queryColumnsMap,
|
|
739
|
+
insertIndex,
|
|
740
|
+
dependencies
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
// Create API routes for search-only data sources
|
|
744
|
+
createAPIRoutesForSearchOnlyDataSources(
|
|
745
|
+
uidl.node,
|
|
746
|
+
options.dataSources,
|
|
747
|
+
componentChunk,
|
|
748
|
+
options.extractedResources,
|
|
749
|
+
searchOnlyDataSources
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return structure
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return paginationPlugin
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function detectPaginationsAndSearchFromJSX(
|
|
760
|
+
blockStatement: types.BlockStatement,
|
|
761
|
+
uidlNode: any
|
|
762
|
+
): {
|
|
763
|
+
paginatedMappers: DetectedPagination[]
|
|
764
|
+
searchOnlyMappers: DetectedPagination[]
|
|
765
|
+
paginationOnlyMappers: DetectedPagination[]
|
|
766
|
+
plainMappers: DetectedPagination[]
|
|
767
|
+
} {
|
|
768
|
+
interface DataProviderInfo {
|
|
769
|
+
identifier: string
|
|
770
|
+
dataProvider: any
|
|
771
|
+
arrayMapperRenderProp?: string
|
|
772
|
+
paginationNode?: { class: string; prevClass: string | null; nextClass: string | null }
|
|
773
|
+
searchInput?: { class: string | null; jsx: any }
|
|
774
|
+
hasPagination: boolean
|
|
775
|
+
hasSearch: boolean
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const dataProviderMap = new Map<string, DataProviderInfo>()
|
|
779
|
+
|
|
780
|
+
// First pass: collect array mapper info from UIDL, keyed by array mapper render prop (unique)
|
|
781
|
+
interface ArrayMapperInfo {
|
|
782
|
+
dataSourceIdentifier: string
|
|
783
|
+
renderProp: string
|
|
784
|
+
paginated: boolean
|
|
785
|
+
searchEnabled: boolean
|
|
786
|
+
}
|
|
787
|
+
const arrayMapperInfoMap = new Map<string, ArrayMapperInfo>()
|
|
788
|
+
|
|
789
|
+
if (uidlNode) {
|
|
790
|
+
const collectArrayMapperInfo = (node: any): void => {
|
|
791
|
+
if (!node) {
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
if (node.type === 'data-source-list' && node.content?.renderPropIdentifier) {
|
|
795
|
+
const dataSourceIdentifier = node.content.renderPropIdentifier
|
|
796
|
+
if (node.content.nodes?.success?.content?.children) {
|
|
797
|
+
node.content.nodes.success.content.children.forEach((child: any) => {
|
|
798
|
+
if (child.type === 'cms-list-repeater' && child.content?.renderPropIdentifier) {
|
|
799
|
+
const arrayMapperRenderProp = child.content.renderPropIdentifier
|
|
800
|
+
// Key by array mapper render prop (unique), not data source identifier
|
|
801
|
+
arrayMapperInfoMap.set(arrayMapperRenderProp, {
|
|
802
|
+
dataSourceIdentifier,
|
|
803
|
+
renderProp: arrayMapperRenderProp,
|
|
804
|
+
paginated: child.content.paginated || false,
|
|
805
|
+
searchEnabled: child.content.searchEnabled || false,
|
|
806
|
+
})
|
|
807
|
+
}
|
|
808
|
+
})
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (node.content?.children) {
|
|
812
|
+
node.content.children.forEach((child: any) => collectArrayMapperInfo(child))
|
|
813
|
+
}
|
|
814
|
+
if (node.children && Array.isArray(node.children)) {
|
|
815
|
+
node.children.forEach((child: any) => collectArrayMapperInfo(child))
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
collectArrayMapperInfo(uidlNode)
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Second pass: find all DataProviders and their parent containers
|
|
822
|
+
interface DataProviderWithParent {
|
|
823
|
+
dataProvider: any
|
|
824
|
+
parent: any
|
|
825
|
+
}
|
|
826
|
+
const dataProvidersWithParents: DataProviderWithParent[] = []
|
|
827
|
+
|
|
828
|
+
const findDataProvidersAndParents = (node: any, parent: any = null): void => {
|
|
829
|
+
if (!node) {
|
|
830
|
+
return
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Check if this is a DataProvider
|
|
834
|
+
if (node.type === 'JSXElement' && node.openingElement?.name?.name === 'DataProvider') {
|
|
835
|
+
dataProvidersWithParents.push({
|
|
836
|
+
dataProvider: node,
|
|
837
|
+
parent,
|
|
838
|
+
})
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Only recurse through JSX structure, not into embedded expressions or code
|
|
842
|
+
if (node.type === 'ReturnStatement' && node.argument) {
|
|
843
|
+
findDataProvidersAndParents(node.argument, parent)
|
|
844
|
+
} else if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
|
|
845
|
+
// Recurse through JSX children
|
|
846
|
+
if (node.children && Array.isArray(node.children)) {
|
|
847
|
+
node.children.forEach((child: any) => findDataProvidersAndParents(child, node))
|
|
848
|
+
}
|
|
849
|
+
} else if (node.type === 'JSXExpressionContainer') {
|
|
850
|
+
// For expression containers, continue but don't go into the expression itself
|
|
851
|
+
if (
|
|
852
|
+
node.expression &&
|
|
853
|
+
(node.expression.type === 'JSXElement' || node.expression.type === 'JSXFragment')
|
|
854
|
+
) {
|
|
855
|
+
findDataProvidersAndParents(node.expression, parent)
|
|
856
|
+
}
|
|
857
|
+
} else if (node.type === 'BlockStatement') {
|
|
858
|
+
// For block statements (function bodies), look through body array
|
|
859
|
+
if (node.body && Array.isArray(node.body)) {
|
|
860
|
+
node.body.forEach((stmt: any) => findDataProvidersAndParents(stmt, node))
|
|
861
|
+
}
|
|
862
|
+
} else if (node.type === 'ConditionalExpression') {
|
|
863
|
+
// Check both branches of conditional
|
|
864
|
+
findDataProvidersAndParents(node.consequent, parent)
|
|
865
|
+
findDataProvidersAndParents(node.alternate, parent)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
findDataProvidersAndParents(blockStatement)
|
|
870
|
+
|
|
871
|
+
// Now process each DataProvider and find its siblings
|
|
872
|
+
dataProvidersWithParents.forEach(({ dataProvider, parent }) => {
|
|
873
|
+
const nameAttr = dataProvider.openingElement.attributes.find(
|
|
874
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'name'
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
// Find the array mapper render prop by looking inside renderSuccess -> Repeater -> renderItem param
|
|
878
|
+
const renderSuccessAttr = dataProvider.openingElement.attributes.find(
|
|
879
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'renderSuccess'
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
let arrayMapperRenderProp: string | undefined
|
|
883
|
+
if (renderSuccessAttr && renderSuccessAttr.value?.type === 'JSXExpressionContainer') {
|
|
884
|
+
const renderFunc = renderSuccessAttr.value.expression
|
|
885
|
+
if (renderFunc.type === 'ArrowFunctionExpression') {
|
|
886
|
+
// Look for Repeater inside the render function
|
|
887
|
+
const findRepeater = (node: any): any => {
|
|
888
|
+
if (!node) {
|
|
889
|
+
return null
|
|
890
|
+
}
|
|
891
|
+
if (node.type === 'JSXElement' && node.openingElement?.name?.name === 'Repeater') {
|
|
892
|
+
return node
|
|
893
|
+
}
|
|
894
|
+
if (node.body) {
|
|
895
|
+
return findRepeater(node.body)
|
|
896
|
+
}
|
|
897
|
+
if (node.children && Array.isArray(node.children)) {
|
|
898
|
+
for (const child of node.children) {
|
|
899
|
+
const result = findRepeater(child)
|
|
900
|
+
if (result) {
|
|
901
|
+
return result
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (node.type === 'JSXFragment' || node.type === 'JSXElement') {
|
|
906
|
+
if (node.children && Array.isArray(node.children)) {
|
|
907
|
+
for (const child of node.children) {
|
|
908
|
+
const result = findRepeater(child)
|
|
909
|
+
if (result) {
|
|
910
|
+
return result
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (node.type === 'JSXExpressionContainer') {
|
|
916
|
+
return findRepeater(node.expression)
|
|
917
|
+
}
|
|
918
|
+
if (node.expression) {
|
|
919
|
+
return findRepeater(node.expression)
|
|
920
|
+
}
|
|
921
|
+
if (node.consequent) {
|
|
922
|
+
const result = findRepeater(node.consequent)
|
|
923
|
+
if (result) {
|
|
924
|
+
return result
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (node.alternate) {
|
|
928
|
+
return findRepeater(node.alternate)
|
|
929
|
+
}
|
|
930
|
+
return null
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const repeater = findRepeater(renderFunc)
|
|
934
|
+
if (repeater) {
|
|
935
|
+
// Find renderItem attribute on Repeater
|
|
936
|
+
const renderItemAttr = repeater.openingElement.attributes.find(
|
|
937
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'renderItem'
|
|
938
|
+
)
|
|
939
|
+
if (renderItemAttr && renderItemAttr.value?.type === 'JSXExpressionContainer') {
|
|
940
|
+
const renderItemFunc = renderItemAttr.value.expression
|
|
941
|
+
if (
|
|
942
|
+
renderItemFunc.type === 'ArrowFunctionExpression' &&
|
|
943
|
+
renderItemFunc.params &&
|
|
944
|
+
renderItemFunc.params.length > 0
|
|
945
|
+
) {
|
|
946
|
+
const param = renderItemFunc.params[0]
|
|
947
|
+
if (param.type === 'Identifier') {
|
|
948
|
+
arrayMapperRenderProp = param.name
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (
|
|
957
|
+
!nameAttr ||
|
|
958
|
+
!nameAttr.value ||
|
|
959
|
+
nameAttr.value.type !== 'JSXExpressionContainer' ||
|
|
960
|
+
!arrayMapperRenderProp
|
|
961
|
+
) {
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const dataProviderIdentifier = nameAttr.value.expression.value
|
|
966
|
+
let paginationNodeInfo: {
|
|
967
|
+
class: string
|
|
968
|
+
prevClass: string | null
|
|
969
|
+
nextClass: string | null
|
|
970
|
+
} | null = null
|
|
971
|
+
let searchInputInfo: { class: string | null; jsx: any } | null = null
|
|
972
|
+
|
|
973
|
+
// Look through parent's children for siblings
|
|
974
|
+
if (parent && parent.children && Array.isArray(parent.children)) {
|
|
975
|
+
parent.children.forEach((sibling: any) => {
|
|
976
|
+
if (sibling === dataProvider) {
|
|
977
|
+
return
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (sibling.type === 'JSXElement') {
|
|
981
|
+
const siblingClassName = getClassName(sibling.openingElement?.attributes || [])
|
|
982
|
+
const siblingElementName = sibling.openingElement?.name?.name
|
|
983
|
+
|
|
984
|
+
// Found pagination node
|
|
985
|
+
if (siblingClassName && siblingClassName.includes('cms-pagination-node')) {
|
|
986
|
+
const prevClass = findChildWithClass(sibling, 'previous')
|
|
987
|
+
const nextClass = findChildWithClass(sibling, 'next')
|
|
988
|
+
if (prevClass || nextClass) {
|
|
989
|
+
paginationNodeInfo = {
|
|
990
|
+
class: siblingClassName,
|
|
991
|
+
prevClass,
|
|
992
|
+
nextClass,
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Found search container - search for input inside it
|
|
998
|
+
if (siblingClassName && siblingClassName.includes('data-source-search-node')) {
|
|
999
|
+
if (sibling.children && Array.isArray(sibling.children)) {
|
|
1000
|
+
sibling.children.forEach((searchChild: any) => {
|
|
1001
|
+
if (searchChild.type === 'JSXElement') {
|
|
1002
|
+
const searchChildElementName = searchChild.openingElement?.name?.name
|
|
1003
|
+
const searchChildClassName = getClassName(
|
|
1004
|
+
searchChild.openingElement?.attributes || []
|
|
1005
|
+
)
|
|
1006
|
+
if (
|
|
1007
|
+
searchChildClassName &&
|
|
1008
|
+
searchChildClassName.includes('search-input') &&
|
|
1009
|
+
searchChildElementName === 'input'
|
|
1010
|
+
) {
|
|
1011
|
+
searchInputInfo = {
|
|
1012
|
+
class: searchChildClassName,
|
|
1013
|
+
jsx: searchChild,
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
})
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Also check if search input is a direct sibling
|
|
1022
|
+
if (
|
|
1023
|
+
siblingClassName &&
|
|
1024
|
+
siblingClassName.includes('search-input') &&
|
|
1025
|
+
siblingElementName === 'input'
|
|
1026
|
+
) {
|
|
1027
|
+
searchInputInfo = {
|
|
1028
|
+
class: siblingClassName,
|
|
1029
|
+
jsx: sibling,
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
})
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Record the DataProvider with its pagination/search info
|
|
1037
|
+
const uniqueKey = `${dataProviderIdentifier}__${arrayMapperRenderProp}`
|
|
1038
|
+
dataProviderMap.set(uniqueKey, {
|
|
1039
|
+
identifier: dataProviderIdentifier,
|
|
1040
|
+
dataProvider,
|
|
1041
|
+
arrayMapperRenderProp,
|
|
1042
|
+
paginationNode: paginationNodeInfo || undefined,
|
|
1043
|
+
searchInput: searchInputInfo || undefined,
|
|
1044
|
+
hasPagination: !!paginationNodeInfo,
|
|
1045
|
+
hasSearch: !!searchInputInfo,
|
|
1046
|
+
})
|
|
1047
|
+
})
|
|
1048
|
+
|
|
1049
|
+
// Categorize data providers
|
|
1050
|
+
const paginatedMappers: DetectedPagination[] = []
|
|
1051
|
+
const searchOnlyMappers: DetectedPagination[] = []
|
|
1052
|
+
const paginationOnlyMappers: DetectedPagination[] = []
|
|
1053
|
+
const plainMappers: DetectedPagination[] = []
|
|
1054
|
+
|
|
1055
|
+
dataProviderMap.forEach((info) => {
|
|
1056
|
+
if (info.hasPagination && info.hasSearch) {
|
|
1057
|
+
// Pagination + Search
|
|
1058
|
+
paginatedMappers.push({
|
|
1059
|
+
paginationNodeClass: info.paginationNode!.class,
|
|
1060
|
+
prevButtonClass: info.paginationNode!.prevClass,
|
|
1061
|
+
nextButtonClass: info.paginationNode!.nextClass,
|
|
1062
|
+
dataSourceIdentifier: info.identifier,
|
|
1063
|
+
dataProviderJSX: info.dataProvider,
|
|
1064
|
+
arrayMapperRenderProp: info.arrayMapperRenderProp,
|
|
1065
|
+
searchInputClass: info.searchInput?.class,
|
|
1066
|
+
searchInputJSX: info.searchInput?.jsx,
|
|
1067
|
+
})
|
|
1068
|
+
} else if (info.hasPagination && !info.hasSearch) {
|
|
1069
|
+
// Pagination only
|
|
1070
|
+
paginationOnlyMappers.push({
|
|
1071
|
+
paginationNodeClass: info.paginationNode!.class,
|
|
1072
|
+
prevButtonClass: info.paginationNode!.prevClass,
|
|
1073
|
+
nextButtonClass: info.paginationNode!.nextClass,
|
|
1074
|
+
dataSourceIdentifier: info.identifier,
|
|
1075
|
+
dataProviderJSX: info.dataProvider,
|
|
1076
|
+
arrayMapperRenderProp: info.arrayMapperRenderProp,
|
|
1077
|
+
searchInputClass: undefined,
|
|
1078
|
+
searchInputJSX: undefined,
|
|
1079
|
+
})
|
|
1080
|
+
} else if (!info.hasPagination && info.hasSearch) {
|
|
1081
|
+
// Search only
|
|
1082
|
+
searchOnlyMappers.push({
|
|
1083
|
+
paginationNodeClass: '',
|
|
1084
|
+
prevButtonClass: null,
|
|
1085
|
+
nextButtonClass: null,
|
|
1086
|
+
dataSourceIdentifier: info.identifier,
|
|
1087
|
+
dataProviderJSX: info.dataProvider,
|
|
1088
|
+
arrayMapperRenderProp: info.arrayMapperRenderProp,
|
|
1089
|
+
searchInputClass: info.searchInput?.class,
|
|
1090
|
+
searchInputJSX: info.searchInput?.jsx,
|
|
1091
|
+
})
|
|
1092
|
+
} else {
|
|
1093
|
+
// Plain (no pagination, no search)
|
|
1094
|
+
plainMappers.push({
|
|
1095
|
+
paginationNodeClass: '',
|
|
1096
|
+
prevButtonClass: null,
|
|
1097
|
+
nextButtonClass: null,
|
|
1098
|
+
dataSourceIdentifier: info.identifier,
|
|
1099
|
+
dataProviderJSX: info.dataProvider,
|
|
1100
|
+
arrayMapperRenderProp: info.arrayMapperRenderProp,
|
|
1101
|
+
searchInputClass: undefined,
|
|
1102
|
+
searchInputJSX: undefined,
|
|
1103
|
+
})
|
|
1104
|
+
}
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
return { paginatedMappers, searchOnlyMappers, paginationOnlyMappers, plainMappers }
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function handleSearchOnlyArrayMappers(
|
|
1111
|
+
blockStatement: types.BlockStatement,
|
|
1112
|
+
searchOnlyDataSources: DetectedPagination[],
|
|
1113
|
+
searchConfigMap: Map<string, any>,
|
|
1114
|
+
queryColumnsMap: Map<string, string[]>,
|
|
1115
|
+
insertIndex: number,
|
|
1116
|
+
dependencies: any
|
|
1117
|
+
): void {
|
|
1118
|
+
searchOnlyDataSources.forEach((detected, index) => {
|
|
1119
|
+
const searchId = `search_${index}`
|
|
1120
|
+
const searchQueryVar = `${searchId}_query`
|
|
1121
|
+
const setSearchQueryVar = `set${
|
|
1122
|
+
searchQueryVar.charAt(0).toUpperCase() + searchQueryVar.slice(1)
|
|
1123
|
+
}`
|
|
1124
|
+
const debouncedSearchQueryVar = `debounced${
|
|
1125
|
+
searchQueryVar.charAt(0).toUpperCase() + searchQueryVar.slice(1)
|
|
1126
|
+
}`
|
|
1127
|
+
const setDebouncedSearchQueryVar = `set${
|
|
1128
|
+
debouncedSearchQueryVar.charAt(0).toUpperCase() + debouncedSearchQueryVar.slice(1)
|
|
1129
|
+
}`
|
|
1130
|
+
const skipDebounceOnMountRefVar = `skipDebounceOnMount${searchId}`
|
|
1131
|
+
|
|
1132
|
+
const searchConfig = searchConfigMap.get(detected.dataSourceIdentifier)
|
|
1133
|
+
const searchDebounce = searchConfig?.searchDebounce || 300
|
|
1134
|
+
const queryColumns = queryColumnsMap.get(detected.dataSourceIdentifier)
|
|
1135
|
+
|
|
1136
|
+
// Add search query state
|
|
1137
|
+
const searchStateAST = types.variableDeclaration('const', [
|
|
1138
|
+
types.variableDeclarator(
|
|
1139
|
+
types.arrayPattern([types.identifier(searchQueryVar), types.identifier(setSearchQueryVar)]),
|
|
1140
|
+
types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
|
|
1141
|
+
),
|
|
1142
|
+
])
|
|
1143
|
+
blockStatement.body.unshift(searchStateAST)
|
|
1144
|
+
|
|
1145
|
+
// Add debounced search state
|
|
1146
|
+
const debouncedSearchStateAST = types.variableDeclaration('const', [
|
|
1147
|
+
types.variableDeclarator(
|
|
1148
|
+
types.arrayPattern([
|
|
1149
|
+
types.identifier(debouncedSearchQueryVar),
|
|
1150
|
+
types.identifier(setDebouncedSearchQueryVar),
|
|
1151
|
+
]),
|
|
1152
|
+
types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
|
|
1153
|
+
),
|
|
1154
|
+
])
|
|
1155
|
+
blockStatement.body.unshift(debouncedSearchStateAST)
|
|
1156
|
+
|
|
1157
|
+
// Add skip ref
|
|
1158
|
+
const skipRefAST = types.variableDeclaration('const', [
|
|
1159
|
+
types.variableDeclarator(
|
|
1160
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
1161
|
+
types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
|
|
1162
|
+
),
|
|
1163
|
+
])
|
|
1164
|
+
blockStatement.body.unshift(skipRefAST)
|
|
1165
|
+
|
|
1166
|
+
// Add useEffect for debouncing
|
|
1167
|
+
if (!dependencies.useEffect) {
|
|
1168
|
+
dependencies.useEffect = {
|
|
1169
|
+
type: 'library',
|
|
1170
|
+
path: 'react',
|
|
1171
|
+
version: '',
|
|
1172
|
+
meta: {
|
|
1173
|
+
namedImport: true,
|
|
1174
|
+
},
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const debounceEffect = types.expressionStatement(
|
|
1179
|
+
types.callExpression(types.identifier('useEffect'), [
|
|
1180
|
+
types.arrowFunctionExpression(
|
|
1181
|
+
[],
|
|
1182
|
+
types.blockStatement([
|
|
1183
|
+
types.ifStatement(
|
|
1184
|
+
types.memberExpression(
|
|
1185
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
1186
|
+
types.identifier('current')
|
|
1187
|
+
),
|
|
1188
|
+
types.blockStatement([
|
|
1189
|
+
types.expressionStatement(
|
|
1190
|
+
types.assignmentExpression(
|
|
1191
|
+
'=',
|
|
1192
|
+
types.memberExpression(
|
|
1193
|
+
types.identifier(skipDebounceOnMountRefVar),
|
|
1194
|
+
types.identifier('current')
|
|
1195
|
+
),
|
|
1196
|
+
types.booleanLiteral(false)
|
|
1197
|
+
)
|
|
1198
|
+
),
|
|
1199
|
+
types.returnStatement(),
|
|
1200
|
+
])
|
|
1201
|
+
),
|
|
1202
|
+
types.variableDeclaration('const', [
|
|
1203
|
+
types.variableDeclarator(
|
|
1204
|
+
types.identifier('timer'),
|
|
1205
|
+
types.callExpression(types.identifier('setTimeout'), [
|
|
1206
|
+
types.arrowFunctionExpression(
|
|
1207
|
+
[],
|
|
1208
|
+
types.blockStatement([
|
|
1209
|
+
types.expressionStatement(
|
|
1210
|
+
types.callExpression(types.identifier(setDebouncedSearchQueryVar), [
|
|
1211
|
+
types.identifier(searchQueryVar),
|
|
1212
|
+
])
|
|
1213
|
+
),
|
|
1214
|
+
])
|
|
1215
|
+
),
|
|
1216
|
+
types.numericLiteral(searchDebounce),
|
|
1217
|
+
])
|
|
1218
|
+
),
|
|
1219
|
+
]),
|
|
1220
|
+
types.returnStatement(
|
|
1221
|
+
types.arrowFunctionExpression(
|
|
1222
|
+
[],
|
|
1223
|
+
types.callExpression(types.identifier('clearTimeout'), [types.identifier('timer')])
|
|
1224
|
+
)
|
|
1225
|
+
),
|
|
1226
|
+
])
|
|
1227
|
+
),
|
|
1228
|
+
types.arrayExpression([types.identifier(searchQueryVar)]),
|
|
1229
|
+
])
|
|
1230
|
+
)
|
|
1231
|
+
blockStatement.body.splice(insertIndex, 0, debounceEffect)
|
|
1232
|
+
|
|
1233
|
+
// Modify DataProvider to include search params (even without queryColumns)
|
|
1234
|
+
if (detected.dataProviderJSX) {
|
|
1235
|
+
addSearchParamsToDataProvider(detected.dataProviderJSX, {
|
|
1236
|
+
debouncedSearchQueryVar,
|
|
1237
|
+
queryColumns: queryColumns || [],
|
|
1238
|
+
})
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Modify search input
|
|
1242
|
+
if (detected.searchInputJSX) {
|
|
1243
|
+
addSearchInputHandlers(detected.searchInputJSX, {
|
|
1244
|
+
searchQueryVar,
|
|
1245
|
+
setSearchQueryVar,
|
|
1246
|
+
searchEnabled: true,
|
|
1247
|
+
} as any)
|
|
1248
|
+
}
|
|
1249
|
+
})
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function addSearchParamsToDataProvider(
|
|
1253
|
+
dataProviderJSX: any,
|
|
1254
|
+
config: { debouncedSearchQueryVar: string; queryColumns: string[] }
|
|
1255
|
+
): void {
|
|
1256
|
+
if (!dataProviderJSX || !dataProviderJSX.openingElement) {
|
|
1257
|
+
return
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const { debouncedSearchQueryVar, queryColumns } = config
|
|
1261
|
+
|
|
1262
|
+
// 1. Update params to be wrapped in useMemo
|
|
1263
|
+
const existingParamsAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1264
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'params'
|
|
1265
|
+
)
|
|
1266
|
+
|
|
1267
|
+
if (existingParamsAttr && existingParamsAttr.value?.type === 'JSXExpressionContainer') {
|
|
1268
|
+
const paramsObj = existingParamsAttr.value.expression
|
|
1269
|
+
|
|
1270
|
+
if (paramsObj.type === 'ObjectExpression') {
|
|
1271
|
+
// Add query to existing params properties
|
|
1272
|
+
paramsObj.properties.push(
|
|
1273
|
+
types.objectProperty(types.identifier('query'), types.identifier(debouncedSearchQueryVar))
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
// Add queryColumns only if they exist
|
|
1277
|
+
if (queryColumns.length > 0) {
|
|
1278
|
+
paramsObj.properties.push(
|
|
1279
|
+
types.objectProperty(
|
|
1280
|
+
types.identifier('queryColumns'),
|
|
1281
|
+
types.callExpression(
|
|
1282
|
+
types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
|
|
1283
|
+
[types.arrayExpression(queryColumns.map((col) => types.stringLiteral(col)))]
|
|
1284
|
+
)
|
|
1285
|
+
)
|
|
1286
|
+
)
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Wrap in useMemo with debouncedSearchQueryVar as dependency
|
|
1290
|
+
const memoizedParamsValue = types.callExpression(types.identifier('useMemo'), [
|
|
1291
|
+
types.arrowFunctionExpression([], paramsObj),
|
|
1292
|
+
types.arrayExpression([types.identifier(debouncedSearchQueryVar)]),
|
|
1293
|
+
])
|
|
1294
|
+
|
|
1295
|
+
existingParamsAttr.value.expression = memoizedParamsValue
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// 2. Update initialData to be conditional on empty search
|
|
1300
|
+
const existingInitialDataAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1301
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'initialData'
|
|
1302
|
+
)
|
|
1303
|
+
|
|
1304
|
+
if (existingInitialDataAttr && existingInitialDataAttr.value?.type === 'JSXExpressionContainer') {
|
|
1305
|
+
const currentInitialData = existingInitialDataAttr.value.expression
|
|
1306
|
+
|
|
1307
|
+
// Make it conditional: only use initialData if search is empty
|
|
1308
|
+
existingInitialDataAttr.value.expression = types.conditionalExpression(
|
|
1309
|
+
types.unaryExpression('!', types.identifier(debouncedSearchQueryVar), true),
|
|
1310
|
+
currentInitialData,
|
|
1311
|
+
types.identifier('undefined')
|
|
1312
|
+
)
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// 3. Update key to include search query
|
|
1316
|
+
const existingKeyAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1317
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'key'
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
const keyExpression = types.templateLiteral(
|
|
1321
|
+
[
|
|
1322
|
+
types.templateElement({ raw: 'search-', cooked: 'search-' }),
|
|
1323
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
1324
|
+
],
|
|
1325
|
+
[types.identifier(debouncedSearchQueryVar)]
|
|
1326
|
+
)
|
|
1327
|
+
|
|
1328
|
+
const keyAttr = types.jsxAttribute(
|
|
1329
|
+
types.jsxIdentifier('key'),
|
|
1330
|
+
types.jsxExpressionContainer(keyExpression)
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
if (existingKeyAttr) {
|
|
1334
|
+
const index = dataProviderJSX.openingElement.attributes.indexOf(existingKeyAttr)
|
|
1335
|
+
dataProviderJSX.openingElement.attributes[index] = keyAttr
|
|
1336
|
+
} else {
|
|
1337
|
+
dataProviderJSX.openingElement.attributes.push(keyAttr)
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function addPaginationParamsToDataProvider(
|
|
1342
|
+
dataProviderJSX: any,
|
|
1343
|
+
info: ArrayMapperPaginationInfo,
|
|
1344
|
+
paginationIndex: number
|
|
1345
|
+
): void {
|
|
1346
|
+
if (!dataProviderJSX || !dataProviderJSX.openingElement) {
|
|
1347
|
+
return
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
const combinedStateVar = (info as any).combinedStateVar
|
|
1351
|
+
|
|
1352
|
+
let paramsProperties: any[]
|
|
1353
|
+
let dependencies: any[]
|
|
1354
|
+
|
|
1355
|
+
if (info.searchEnabled && combinedStateVar) {
|
|
1356
|
+
// Use combined state for both page and query
|
|
1357
|
+
paramsProperties = [
|
|
1358
|
+
types.objectProperty(
|
|
1359
|
+
types.identifier('page'),
|
|
1360
|
+
types.memberExpression(types.identifier(combinedStateVar), types.identifier('page'))
|
|
1361
|
+
),
|
|
1362
|
+
types.objectProperty(types.identifier('perPage'), types.numericLiteral(info.perPage)),
|
|
1363
|
+
types.objectProperty(
|
|
1364
|
+
types.identifier('query'),
|
|
1365
|
+
types.memberExpression(
|
|
1366
|
+
types.identifier(combinedStateVar),
|
|
1367
|
+
types.identifier('debouncedQuery')
|
|
1368
|
+
)
|
|
1369
|
+
),
|
|
1370
|
+
]
|
|
1371
|
+
|
|
1372
|
+
if (info.queryColumns && info.queryColumns.length > 0) {
|
|
1373
|
+
paramsProperties.push(
|
|
1374
|
+
types.objectProperty(
|
|
1375
|
+
types.identifier('queryColumns'),
|
|
1376
|
+
types.callExpression(
|
|
1377
|
+
types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),
|
|
1378
|
+
[types.arrayExpression(info.queryColumns.map((col) => types.stringLiteral(col)))]
|
|
1379
|
+
)
|
|
1380
|
+
)
|
|
1381
|
+
)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Single dependency: the combined state object
|
|
1385
|
+
dependencies = [types.identifier(combinedStateVar)]
|
|
1386
|
+
} else {
|
|
1387
|
+
// Pagination only (no search)
|
|
1388
|
+
paramsProperties = [
|
|
1389
|
+
types.objectProperty(types.identifier('page'), types.identifier(info.pageStateVar)),
|
|
1390
|
+
types.objectProperty(types.identifier('perPage'), types.numericLiteral(info.perPage)),
|
|
1391
|
+
]
|
|
1392
|
+
|
|
1393
|
+
dependencies = [types.identifier(info.pageStateVar)]
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Wrap params in useMemo to prevent unnecessary refetches
|
|
1397
|
+
const memoizedParamsValue = types.callExpression(types.identifier('useMemo'), [
|
|
1398
|
+
types.arrowFunctionExpression([], types.objectExpression(paramsProperties)),
|
|
1399
|
+
types.arrayExpression(dependencies),
|
|
1400
|
+
])
|
|
1401
|
+
|
|
1402
|
+
const paramsAttr = types.jsxAttribute(
|
|
1403
|
+
types.jsxIdentifier('params'),
|
|
1404
|
+
types.jsxExpressionContainer(memoizedParamsValue)
|
|
1405
|
+
)
|
|
1406
|
+
|
|
1407
|
+
const existingParamsAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1408
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'params'
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
if (existingParamsAttr) {
|
|
1412
|
+
const index = dataProviderJSX.openingElement.attributes.indexOf(existingParamsAttr)
|
|
1413
|
+
dataProviderJSX.openingElement.attributes[index] = paramsAttr
|
|
1414
|
+
} else {
|
|
1415
|
+
dataProviderJSX.openingElement.attributes.push(paramsAttr)
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
const existingInitialDataAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1419
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'initialData'
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1422
|
+
if (existingInitialDataAttr && existingInitialDataAttr.value) {
|
|
1423
|
+
// Update initialData to use the paginated prop name
|
|
1424
|
+
const paginatedPropName = `${info.dataSourceIdentifier}_pg_${paginationIndex}`
|
|
1425
|
+
|
|
1426
|
+
// If search is enabled, use combined state; otherwise use page state
|
|
1427
|
+
let condition: any
|
|
1428
|
+
|
|
1429
|
+
if (info.searchEnabled && combinedStateVar) {
|
|
1430
|
+
condition = types.logicalExpression(
|
|
1431
|
+
'&&',
|
|
1432
|
+
types.binaryExpression(
|
|
1433
|
+
'===',
|
|
1434
|
+
types.memberExpression(types.identifier(combinedStateVar), types.identifier('page')),
|
|
1435
|
+
types.numericLiteral(1)
|
|
1436
|
+
),
|
|
1437
|
+
types.unaryExpression(
|
|
1438
|
+
'!',
|
|
1439
|
+
types.memberExpression(
|
|
1440
|
+
types.identifier(combinedStateVar),
|
|
1441
|
+
types.identifier('debouncedQuery')
|
|
1442
|
+
),
|
|
1443
|
+
true
|
|
1444
|
+
)
|
|
1445
|
+
)
|
|
1446
|
+
} else {
|
|
1447
|
+
condition = types.binaryExpression(
|
|
1448
|
+
'===',
|
|
1449
|
+
types.identifier(info.pageStateVar),
|
|
1450
|
+
types.numericLiteral(1)
|
|
1451
|
+
)
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
existingInitialDataAttr.value.expression = types.conditionalExpression(
|
|
1455
|
+
condition,
|
|
1456
|
+
types.optionalMemberExpression(
|
|
1457
|
+
types.identifier('props'),
|
|
1458
|
+
types.identifier(paginatedPropName),
|
|
1459
|
+
false,
|
|
1460
|
+
true
|
|
1461
|
+
),
|
|
1462
|
+
types.identifier('undefined')
|
|
1463
|
+
)
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const existingKeyAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1467
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'key'
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
// Include page and search in key to trigger refetch when they change
|
|
1471
|
+
let keyExpression: any
|
|
1472
|
+
if (info.searchEnabled && combinedStateVar) {
|
|
1473
|
+
keyExpression = types.templateLiteral(
|
|
1474
|
+
[
|
|
1475
|
+
types.templateElement({
|
|
1476
|
+
raw: `${info.dataSourceIdentifier}-page-`,
|
|
1477
|
+
cooked: `${info.dataSourceIdentifier}-page-`,
|
|
1478
|
+
}),
|
|
1479
|
+
types.templateElement({ raw: '-search-', cooked: '-search-' }),
|
|
1480
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
1481
|
+
],
|
|
1482
|
+
[
|
|
1483
|
+
types.memberExpression(types.identifier(combinedStateVar), types.identifier('page')),
|
|
1484
|
+
types.memberExpression(
|
|
1485
|
+
types.identifier(combinedStateVar),
|
|
1486
|
+
types.identifier('debouncedQuery')
|
|
1487
|
+
),
|
|
1488
|
+
]
|
|
1489
|
+
)
|
|
1490
|
+
} else {
|
|
1491
|
+
keyExpression = types.templateLiteral(
|
|
1492
|
+
[
|
|
1493
|
+
types.templateElement({
|
|
1494
|
+
raw: `${info.dataSourceIdentifier}-`,
|
|
1495
|
+
cooked: `${info.dataSourceIdentifier}-`,
|
|
1496
|
+
}),
|
|
1497
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
1498
|
+
],
|
|
1499
|
+
[types.identifier(info.pageStateVar)]
|
|
1500
|
+
)
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const keyAttr = types.jsxAttribute(
|
|
1504
|
+
types.jsxIdentifier('key'),
|
|
1505
|
+
types.jsxExpressionContainer(keyExpression)
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
if (existingKeyAttr) {
|
|
1509
|
+
const index = dataProviderJSX.openingElement.attributes.indexOf(existingKeyAttr)
|
|
1510
|
+
dataProviderJSX.openingElement.attributes[index] = keyAttr
|
|
1511
|
+
} else {
|
|
1512
|
+
dataProviderJSX.openingElement.attributes.push(keyAttr)
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// For pagination, always create a fresh fetchData that calls the API route
|
|
1516
|
+
// Get the resource definition to build the API URL
|
|
1517
|
+
const resourceDefAttr = dataProviderJSX.openingElement.attributes.find(
|
|
1518
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
|
|
1519
|
+
)
|
|
1520
|
+
|
|
1521
|
+
if (resourceDefAttr && resourceDefAttr.value?.type === 'JSXExpressionContainer') {
|
|
1522
|
+
const resourceDef = resourceDefAttr.value.expression
|
|
1523
|
+
if (resourceDef.type === 'ObjectExpression') {
|
|
1524
|
+
const dataSourceIdProp = (resourceDef.properties as any[]).find(
|
|
1525
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceId'
|
|
1526
|
+
)
|
|
1527
|
+
const tableNameProp = (resourceDef.properties as any[]).find(
|
|
1528
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'tableName'
|
|
1529
|
+
)
|
|
1530
|
+
const dataSourceTypeProp = (resourceDef.properties as any[]).find(
|
|
1531
|
+
(p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceType'
|
|
1532
|
+
)
|
|
1533
|
+
|
|
1534
|
+
if (dataSourceIdProp && tableNameProp && dataSourceTypeProp) {
|
|
1535
|
+
const dataSourceId = dataSourceIdProp.value.value
|
|
1536
|
+
const tableName = tableNameProp.value.value
|
|
1537
|
+
const dataSourceType = dataSourceTypeProp.value.value
|
|
1538
|
+
const fileName = `${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
|
|
1539
|
+
|
|
1540
|
+
// Create fetchData attribute with proper fetch chain wrapped in useCallback
|
|
1541
|
+
const fetchDataValue = types.callExpression(types.identifier('useCallback'), [
|
|
1542
|
+
types.arrowFunctionExpression(
|
|
1543
|
+
[types.identifier('params')],
|
|
1544
|
+
types.callExpression(
|
|
1545
|
+
types.memberExpression(
|
|
1546
|
+
types.callExpression(
|
|
1547
|
+
types.memberExpression(
|
|
1548
|
+
types.callExpression(types.identifier('fetch'), [
|
|
1549
|
+
types.templateLiteral(
|
|
1550
|
+
[
|
|
1551
|
+
types.templateElement({
|
|
1552
|
+
raw: `/api/${fileName}?`,
|
|
1553
|
+
cooked: `/api/${fileName}?`,
|
|
1554
|
+
}),
|
|
1555
|
+
types.templateElement({ raw: '', cooked: '' }),
|
|
1556
|
+
],
|
|
1557
|
+
[
|
|
1558
|
+
types.newExpression(types.identifier('URLSearchParams'), [
|
|
1559
|
+
types.identifier('params'),
|
|
1560
|
+
]),
|
|
1561
|
+
]
|
|
1562
|
+
),
|
|
1563
|
+
types.objectExpression([
|
|
1564
|
+
types.objectProperty(
|
|
1565
|
+
types.identifier('headers'),
|
|
1566
|
+
types.objectExpression([
|
|
1567
|
+
types.objectProperty(
|
|
1568
|
+
types.stringLiteral('Content-Type'),
|
|
1569
|
+
types.stringLiteral('application/json')
|
|
1570
|
+
),
|
|
1571
|
+
])
|
|
1572
|
+
),
|
|
1573
|
+
]),
|
|
1574
|
+
]),
|
|
1575
|
+
types.identifier('then')
|
|
1576
|
+
),
|
|
1577
|
+
[
|
|
1578
|
+
types.arrowFunctionExpression(
|
|
1579
|
+
[types.identifier('res')],
|
|
1580
|
+
types.callExpression(
|
|
1581
|
+
types.memberExpression(types.identifier('res'), types.identifier('json')),
|
|
1582
|
+
[]
|
|
1583
|
+
)
|
|
1584
|
+
),
|
|
1585
|
+
]
|
|
1586
|
+
),
|
|
1587
|
+
types.identifier('then')
|
|
1588
|
+
),
|
|
1589
|
+
[
|
|
1590
|
+
types.arrowFunctionExpression(
|
|
1591
|
+
[types.identifier('response')],
|
|
1592
|
+
types.optionalMemberExpression(
|
|
1593
|
+
types.identifier('response'),
|
|
1594
|
+
types.identifier('data'),
|
|
1595
|
+
false,
|
|
1596
|
+
true
|
|
1597
|
+
)
|
|
1598
|
+
),
|
|
1599
|
+
]
|
|
1600
|
+
)
|
|
1601
|
+
),
|
|
1602
|
+
types.arrayExpression([]),
|
|
1603
|
+
])
|
|
1604
|
+
|
|
1605
|
+
const newFetchDataAttr = types.jsxAttribute(
|
|
1606
|
+
types.jsxIdentifier('fetchData'),
|
|
1607
|
+
types.jsxExpressionContainer(fetchDataValue)
|
|
1608
|
+
)
|
|
1609
|
+
|
|
1610
|
+
// Remove existing fetchData attribute if present
|
|
1611
|
+
const existingFetchDataIndex = dataProviderJSX.openingElement.attributes.findIndex(
|
|
1612
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'fetchData'
|
|
1613
|
+
)
|
|
1614
|
+
|
|
1615
|
+
if (existingFetchDataIndex !== -1) {
|
|
1616
|
+
dataProviderJSX.openingElement.attributes[existingFetchDataIndex] = newFetchDataAttr
|
|
1617
|
+
} else {
|
|
1618
|
+
dataProviderJSX.openingElement.attributes.push(newFetchDataAttr)
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function findChildWithClass(node: any, classSubstring: string): string | null {
|
|
1626
|
+
if (!node) {
|
|
1627
|
+
return null
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (node.type === 'JSXElement') {
|
|
1631
|
+
const className = getClassName(node.openingElement?.attributes || [])
|
|
1632
|
+
if (className && className.includes(classSubstring)) {
|
|
1633
|
+
return className
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (node.children) {
|
|
1638
|
+
for (const child of node.children) {
|
|
1639
|
+
const found = findChildWithClass(child, classSubstring)
|
|
1640
|
+
if (found) {
|
|
1641
|
+
return found
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
if (typeof node === 'object') {
|
|
1647
|
+
for (const value of Object.values(node)) {
|
|
1648
|
+
if (Array.isArray(value)) {
|
|
1649
|
+
for (const item of value) {
|
|
1650
|
+
const found = findChildWithClass(item, classSubstring)
|
|
1651
|
+
if (found) {
|
|
1652
|
+
return found
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
} else if (typeof value === 'object') {
|
|
1656
|
+
const found = findChildWithClass(value, classSubstring)
|
|
1657
|
+
if (found) {
|
|
1658
|
+
return found
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
return null
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
function modifyPaginationButtons(
|
|
1668
|
+
blockStatement: types.BlockStatement,
|
|
1669
|
+
detectedPaginations: DetectedPagination[],
|
|
1670
|
+
paginationInfos: ArrayMapperPaginationInfo[]
|
|
1671
|
+
): void {
|
|
1672
|
+
const modifyNode = (node: any): void => {
|
|
1673
|
+
if (!node) {
|
|
1674
|
+
return
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
if (node.type === 'JSXElement') {
|
|
1678
|
+
const openingElement = node.openingElement
|
|
1679
|
+
if (openingElement && openingElement.name && openingElement.name.type === 'JSXIdentifier') {
|
|
1680
|
+
const className = getClassName(openingElement.attributes)
|
|
1681
|
+
|
|
1682
|
+
if (className) {
|
|
1683
|
+
detectedPaginations.forEach((detected, index) => {
|
|
1684
|
+
const info = paginationInfos[index]
|
|
1685
|
+
if (!info) {
|
|
1686
|
+
return
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
if (className === detected.prevButtonClass) {
|
|
1690
|
+
convertToButton(node, info, 'prev')
|
|
1691
|
+
} else if (className === detected.nextButtonClass) {
|
|
1692
|
+
convertToButton(node, info, 'next')
|
|
1693
|
+
}
|
|
1694
|
+
})
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
if (typeof node === 'object') {
|
|
1700
|
+
Object.values(node).forEach((value) => {
|
|
1701
|
+
if (Array.isArray(value)) {
|
|
1702
|
+
value.forEach((item) => modifyNode(item))
|
|
1703
|
+
} else if (typeof value === 'object') {
|
|
1704
|
+
modifyNode(value)
|
|
1705
|
+
}
|
|
1706
|
+
})
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
modifyNode(blockStatement)
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function modifySearchInputs(
|
|
1714
|
+
blockStatement: types.BlockStatement,
|
|
1715
|
+
detectedPaginations: DetectedPagination[],
|
|
1716
|
+
paginationInfos: ArrayMapperPaginationInfo[]
|
|
1717
|
+
): void {
|
|
1718
|
+
const modifyNode = (node: any): void => {
|
|
1719
|
+
if (!node) {
|
|
1720
|
+
return
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (node.type === 'JSXElement') {
|
|
1724
|
+
const openingElement = node.openingElement
|
|
1725
|
+
if (openingElement && openingElement.name && openingElement.name.type === 'JSXIdentifier') {
|
|
1726
|
+
const className = getClassName(openingElement.attributes)
|
|
1727
|
+
|
|
1728
|
+
if (className) {
|
|
1729
|
+
detectedPaginations.forEach((detected, index) => {
|
|
1730
|
+
const info = paginationInfos[index]
|
|
1731
|
+
if (!info || !info.searchEnabled) {
|
|
1732
|
+
return
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (className === detected.searchInputClass) {
|
|
1736
|
+
addSearchInputHandlers(node, info)
|
|
1737
|
+
} else if (className && className.includes('search-input')) {
|
|
1738
|
+
// Fallback: match any input with 'search-input' in class
|
|
1739
|
+
addSearchInputHandlers(node, info)
|
|
1740
|
+
}
|
|
1741
|
+
})
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
if (typeof node === 'object') {
|
|
1747
|
+
Object.values(node).forEach((value) => {
|
|
1748
|
+
if (Array.isArray(value)) {
|
|
1749
|
+
value.forEach((item) => modifyNode(item))
|
|
1750
|
+
} else if (typeof value === 'object') {
|
|
1751
|
+
modifyNode(value)
|
|
1752
|
+
}
|
|
1753
|
+
})
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
modifyNode(blockStatement)
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function addSearchInputHandlers(jsxElement: any, info: ArrayMapperPaginationInfo): void {
|
|
1761
|
+
if (!info.searchQueryVar || !info.setSearchQueryVar) {
|
|
1762
|
+
return
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const openingElement = jsxElement.openingElement
|
|
1766
|
+
|
|
1767
|
+
removeAttribute(openingElement.attributes, 'onChange')
|
|
1768
|
+
removeAttribute(openingElement.attributes, 'value')
|
|
1769
|
+
|
|
1770
|
+
const onChangeHandler = types.arrowFunctionExpression(
|
|
1771
|
+
[types.identifier('e')],
|
|
1772
|
+
types.callExpression(types.identifier(info.setSearchQueryVar!), [
|
|
1773
|
+
types.memberExpression(
|
|
1774
|
+
types.memberExpression(types.identifier('e'), types.identifier('target')),
|
|
1775
|
+
types.identifier('value')
|
|
1776
|
+
),
|
|
1777
|
+
])
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
openingElement.attributes.push(
|
|
1781
|
+
types.jsxAttribute(
|
|
1782
|
+
types.jsxIdentifier('onChange'),
|
|
1783
|
+
types.jsxExpressionContainer(onChangeHandler)
|
|
1784
|
+
)
|
|
1785
|
+
)
|
|
1786
|
+
|
|
1787
|
+
openingElement.attributes.push(
|
|
1788
|
+
types.jsxAttribute(
|
|
1789
|
+
types.jsxIdentifier('value'),
|
|
1790
|
+
types.jsxExpressionContainer(types.identifier(info.searchQueryVar!))
|
|
1791
|
+
)
|
|
1792
|
+
)
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function convertToButton(
|
|
1796
|
+
jsxElement: any,
|
|
1797
|
+
info: ArrayMapperPaginationInfo,
|
|
1798
|
+
buttonType: 'prev' | 'next'
|
|
1799
|
+
): void {
|
|
1800
|
+
const openingElement = jsxElement.openingElement
|
|
1801
|
+
|
|
1802
|
+
if (openingElement.name.type === 'JSXIdentifier') {
|
|
1803
|
+
openingElement.name.name = 'button'
|
|
1804
|
+
}
|
|
1805
|
+
if (jsxElement.closingElement && jsxElement.closingElement.name.type === 'JSXIdentifier') {
|
|
1806
|
+
jsxElement.closingElement.name.name = 'button'
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
removeAttribute(openingElement.attributes, 'onClick')
|
|
1810
|
+
removeAttribute(openingElement.attributes, 'disabled')
|
|
1811
|
+
removeAttribute(openingElement.attributes, 'type')
|
|
1812
|
+
|
|
1813
|
+
const combinedStateVar = (info as any).combinedStateVar
|
|
1814
|
+
const setCombinedStateVar = (info as any).setCombinedStateVar
|
|
1815
|
+
|
|
1816
|
+
let onClickHandler: any
|
|
1817
|
+
if (info.searchEnabled && combinedStateVar && setCombinedStateVar) {
|
|
1818
|
+
// Use combined state: update only the page property
|
|
1819
|
+
onClickHandler =
|
|
1820
|
+
buttonType === 'prev'
|
|
1821
|
+
? types.arrowFunctionExpression(
|
|
1822
|
+
[],
|
|
1823
|
+
types.callExpression(types.identifier(setCombinedStateVar), [
|
|
1824
|
+
types.arrowFunctionExpression(
|
|
1825
|
+
[types.identifier('state')],
|
|
1826
|
+
types.objectExpression([
|
|
1827
|
+
types.spreadElement(types.identifier('state')),
|
|
1828
|
+
types.objectProperty(
|
|
1829
|
+
types.identifier('page'),
|
|
1830
|
+
types.callExpression(
|
|
1831
|
+
types.memberExpression(types.identifier('Math'), types.identifier('max')),
|
|
1832
|
+
[
|
|
1833
|
+
types.numericLiteral(1),
|
|
1834
|
+
types.binaryExpression(
|
|
1835
|
+
'-',
|
|
1836
|
+
types.memberExpression(
|
|
1837
|
+
types.identifier('state'),
|
|
1838
|
+
types.identifier('page')
|
|
1839
|
+
),
|
|
1840
|
+
types.numericLiteral(1)
|
|
1841
|
+
),
|
|
1842
|
+
]
|
|
1843
|
+
)
|
|
1844
|
+
),
|
|
1845
|
+
])
|
|
1846
|
+
),
|
|
1847
|
+
])
|
|
1848
|
+
)
|
|
1849
|
+
: types.arrowFunctionExpression(
|
|
1850
|
+
[],
|
|
1851
|
+
types.callExpression(types.identifier(setCombinedStateVar), [
|
|
1852
|
+
types.arrowFunctionExpression(
|
|
1853
|
+
[types.identifier('state')],
|
|
1854
|
+
types.objectExpression([
|
|
1855
|
+
types.spreadElement(types.identifier('state')),
|
|
1856
|
+
types.objectProperty(
|
|
1857
|
+
types.identifier('page'),
|
|
1858
|
+
types.binaryExpression(
|
|
1859
|
+
'+',
|
|
1860
|
+
types.memberExpression(types.identifier('state'), types.identifier('page')),
|
|
1861
|
+
types.numericLiteral(1)
|
|
1862
|
+
)
|
|
1863
|
+
),
|
|
1864
|
+
])
|
|
1865
|
+
),
|
|
1866
|
+
])
|
|
1867
|
+
)
|
|
1868
|
+
} else {
|
|
1869
|
+
// Regular pagination (no search)
|
|
1870
|
+
onClickHandler =
|
|
1871
|
+
buttonType === 'prev'
|
|
1872
|
+
? types.arrowFunctionExpression(
|
|
1873
|
+
[],
|
|
1874
|
+
types.callExpression(types.identifier(info.setPageStateVar), [
|
|
1875
|
+
types.arrowFunctionExpression(
|
|
1876
|
+
[types.identifier('p')],
|
|
1877
|
+
types.callExpression(
|
|
1878
|
+
types.memberExpression(types.identifier('Math'), types.identifier('max')),
|
|
1879
|
+
[
|
|
1880
|
+
types.numericLiteral(1),
|
|
1881
|
+
types.binaryExpression('-', types.identifier('p'), types.numericLiteral(1)),
|
|
1882
|
+
]
|
|
1883
|
+
)
|
|
1884
|
+
),
|
|
1885
|
+
])
|
|
1886
|
+
)
|
|
1887
|
+
: types.arrowFunctionExpression(
|
|
1888
|
+
[],
|
|
1889
|
+
types.callExpression(types.identifier(info.setPageStateVar), [
|
|
1890
|
+
types.arrowFunctionExpression(
|
|
1891
|
+
[types.identifier('p')],
|
|
1892
|
+
types.binaryExpression('+', types.identifier('p'), types.numericLiteral(1))
|
|
1893
|
+
),
|
|
1894
|
+
])
|
|
1895
|
+
)
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
openingElement.attributes.push(
|
|
1899
|
+
types.jsxAttribute(types.jsxIdentifier('onClick'), types.jsxExpressionContainer(onClickHandler))
|
|
1900
|
+
)
|
|
1901
|
+
openingElement.attributes.push(
|
|
1902
|
+
types.jsxAttribute(types.jsxIdentifier('type'), types.stringLiteral('button'))
|
|
1903
|
+
)
|
|
1904
|
+
|
|
1905
|
+
// Add disabled attribute with simple page number checks
|
|
1906
|
+
const maxPagesStateVar = (info as any).maxPagesStateVar
|
|
1907
|
+
let disabledExpr: any
|
|
1908
|
+
|
|
1909
|
+
if (info.searchEnabled && combinedStateVar) {
|
|
1910
|
+
disabledExpr =
|
|
1911
|
+
buttonType === 'prev'
|
|
1912
|
+
? types.binaryExpression(
|
|
1913
|
+
'<=',
|
|
1914
|
+
types.memberExpression(types.identifier(combinedStateVar), types.identifier('page')),
|
|
1915
|
+
types.numericLiteral(1)
|
|
1916
|
+
)
|
|
1917
|
+
: types.binaryExpression(
|
|
1918
|
+
'>=',
|
|
1919
|
+
types.memberExpression(types.identifier(combinedStateVar), types.identifier('page')),
|
|
1920
|
+
types.identifier(maxPagesStateVar)
|
|
1921
|
+
)
|
|
1922
|
+
} else {
|
|
1923
|
+
disabledExpr =
|
|
1924
|
+
buttonType === 'prev'
|
|
1925
|
+
? types.binaryExpression('<=', types.identifier(info.pageStateVar), types.numericLiteral(1))
|
|
1926
|
+
: types.binaryExpression(
|
|
1927
|
+
'>=',
|
|
1928
|
+
types.identifier(info.pageStateVar),
|
|
1929
|
+
types.identifier(maxPagesStateVar)
|
|
1930
|
+
)
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
openingElement.attributes.push(
|
|
1934
|
+
types.jsxAttribute(types.jsxIdentifier('disabled'), types.jsxExpressionContainer(disabledExpr))
|
|
1935
|
+
)
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function getClassName(attributes: any[]): string | null {
|
|
1939
|
+
const classNameAttr = attributes.find(
|
|
1940
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'className'
|
|
1941
|
+
)
|
|
1942
|
+
if (classNameAttr && classNameAttr.value && classNameAttr.value.type === 'StringLiteral') {
|
|
1943
|
+
return classNameAttr.value.value
|
|
1944
|
+
}
|
|
1945
|
+
return null
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function removeAttribute(attrs: any[], attributeName: string): void {
|
|
1949
|
+
const index = attrs.findIndex(
|
|
1950
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === attributeName
|
|
1951
|
+
)
|
|
1952
|
+
if (index !== -1) {
|
|
1953
|
+
attrs.splice(index, 1)
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
function modifyGetStaticPropsForPagination(
|
|
1958
|
+
chunks: any[],
|
|
1959
|
+
paginationInfos: ArrayMapperPaginationInfo[]
|
|
1960
|
+
): void {
|
|
1961
|
+
const getStaticPropsChunk = chunks.find((chunk) => chunk.name === 'getStaticProps')
|
|
1962
|
+
if (!getStaticPropsChunk || getStaticPropsChunk.type !== 'ast') {
|
|
1963
|
+
return
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
const exportDeclaration = getStaticPropsChunk.content as types.ExportNamedDeclaration
|
|
1967
|
+
if (!exportDeclaration || exportDeclaration.type !== 'ExportNamedDeclaration') {
|
|
1968
|
+
return
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
const functionDeclaration = exportDeclaration.declaration as types.FunctionDeclaration
|
|
1972
|
+
if (!functionDeclaration || functionDeclaration.type !== 'FunctionDeclaration') {
|
|
1973
|
+
return
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// Find Promise.all and add NEW fetchData calls for each paginated DataProvider
|
|
1977
|
+
const functionBody = functionDeclaration.body.body
|
|
1978
|
+
const tryBlock = functionBody.find((stmt: any) => stmt.type === 'TryStatement') as
|
|
1979
|
+
| types.TryStatement
|
|
1980
|
+
| undefined
|
|
1981
|
+
|
|
1982
|
+
if (!tryBlock) {
|
|
1983
|
+
return
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
const tryBody = tryBlock.block.body
|
|
1987
|
+
|
|
1988
|
+
// Find the Promise.all statement
|
|
1989
|
+
const promiseAllStmt = tryBody.find(
|
|
1990
|
+
(stmt: any) =>
|
|
1991
|
+
stmt.type === 'VariableDeclaration' &&
|
|
1992
|
+
stmt.declarations?.[0]?.init?.type === 'AwaitExpression' &&
|
|
1993
|
+
stmt.declarations?.[0]?.init?.argument?.type === 'CallExpression' &&
|
|
1994
|
+
stmt.declarations?.[0]?.init?.argument?.callee?.type === 'MemberExpression' &&
|
|
1995
|
+
stmt.declarations?.[0]?.init?.argument?.callee?.property?.name === 'all'
|
|
1996
|
+
) as types.VariableDeclaration | undefined
|
|
1997
|
+
|
|
1998
|
+
if (!promiseAllStmt) {
|
|
1999
|
+
return
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
const awaitExpr = promiseAllStmt.declarations[0].init as types.AwaitExpression
|
|
2003
|
+
const promiseAllCall = awaitExpr.argument as types.CallExpression
|
|
2004
|
+
const promiseArray = promiseAllCall.arguments[0] as types.ArrayExpression
|
|
2005
|
+
const destructuringPattern = promiseAllStmt.declarations[0].id as types.ArrayPattern
|
|
2006
|
+
|
|
2007
|
+
// Map import names to data source identifiers from existing fetchData calls
|
|
2008
|
+
// Also track which indices to remove (non-paginated calls that will be replaced)
|
|
2009
|
+
const importToDataSource = new Map<string, string>()
|
|
2010
|
+
const indicesToRemove: number[] = []
|
|
2011
|
+
|
|
2012
|
+
promiseArray.elements.forEach((element: any, index: number) => {
|
|
2013
|
+
if (element && element.type === 'CallExpression') {
|
|
2014
|
+
let fetchCallExpr = element
|
|
2015
|
+
|
|
2016
|
+
// If wrapped in .catch(), unwrap it
|
|
2017
|
+
if (
|
|
2018
|
+
element.callee?.type === 'MemberExpression' &&
|
|
2019
|
+
element.callee?.property?.name === 'catch' &&
|
|
2020
|
+
element.callee?.object?.type === 'CallExpression'
|
|
2021
|
+
) {
|
|
2022
|
+
fetchCallExpr = element.callee.object
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// Now find the .fetchData() call
|
|
2026
|
+
if (
|
|
2027
|
+
fetchCallExpr.callee?.type === 'MemberExpression' &&
|
|
2028
|
+
fetchCallExpr.callee?.property?.name === 'fetchData' &&
|
|
2029
|
+
fetchCallExpr.callee?.object?.type === 'Identifier'
|
|
2030
|
+
) {
|
|
2031
|
+
const importName = fetchCallExpr.callee.object.name
|
|
2032
|
+
const dataSourceVar = (destructuringPattern.elements[index] as types.Identifier).name
|
|
2033
|
+
|
|
2034
|
+
// Check if this fetchData call has page/perPage params
|
|
2035
|
+
const params = fetchCallExpr.arguments[0]
|
|
2036
|
+
const hasPageParam =
|
|
2037
|
+
params &&
|
|
2038
|
+
params.type === 'ObjectExpression' &&
|
|
2039
|
+
params.properties.some(
|
|
2040
|
+
(prop: any) =>
|
|
2041
|
+
prop.type === 'ObjectProperty' &&
|
|
2042
|
+
(prop.key.name === 'page' || prop.key.name === 'perPage')
|
|
2043
|
+
)
|
|
2044
|
+
|
|
2045
|
+
// If this is a data source that will be paginated but this call has NO pagination params,
|
|
2046
|
+
// mark it for removal
|
|
2047
|
+
if (
|
|
2048
|
+
!hasPageParam &&
|
|
2049
|
+
paginationInfos.some((info) => info.dataSourceIdentifier === dataSourceVar)
|
|
2050
|
+
) {
|
|
2051
|
+
indicesToRemove.push(index)
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
importToDataSource.set(importName, dataSourceVar)
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
})
|
|
2058
|
+
|
|
2059
|
+
// Remove non-paginated fetchData calls in reverse order to preserve indices
|
|
2060
|
+
indicesToRemove.reverse().forEach((index) => {
|
|
2061
|
+
// Get the prop name BEFORE removing it
|
|
2062
|
+
const propToRemove = (destructuringPattern.elements[index] as types.Identifier)?.name
|
|
2063
|
+
|
|
2064
|
+
promiseArray.elements.splice(index, 1)
|
|
2065
|
+
destructuringPattern.elements.splice(index, 1)
|
|
2066
|
+
|
|
2067
|
+
// Also remove from props in return statement
|
|
2068
|
+
if (propToRemove) {
|
|
2069
|
+
const foundReturnStmt = tryBody.find((stmt: any) => stmt.type === 'ReturnStatement') as
|
|
2070
|
+
| types.ReturnStatement
|
|
2071
|
+
| undefined
|
|
2072
|
+
|
|
2073
|
+
if (foundReturnStmt && foundReturnStmt.argument?.type === 'ObjectExpression') {
|
|
2074
|
+
const propsProperty = (foundReturnStmt.argument as types.ObjectExpression).properties.find(
|
|
2075
|
+
(prop: any) =>
|
|
2076
|
+
prop.type === 'ObjectProperty' && (prop.key as types.Identifier).name === 'props'
|
|
2077
|
+
) as types.ObjectProperty | undefined
|
|
2078
|
+
|
|
2079
|
+
if (propsProperty && propsProperty.value.type === 'ObjectExpression') {
|
|
2080
|
+
const propsObject = propsProperty.value as types.ObjectExpression
|
|
2081
|
+
|
|
2082
|
+
const propIndex = propsObject.properties.findIndex(
|
|
2083
|
+
(prop: any) =>
|
|
2084
|
+
prop.type === 'ObjectProperty' && (prop.key as types.Identifier).name === propToRemove
|
|
2085
|
+
)
|
|
2086
|
+
if (propIndex !== -1) {
|
|
2087
|
+
propsObject.properties.splice(propIndex, 1)
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
})
|
|
2093
|
+
|
|
2094
|
+
// Add NEW fetchData calls for each paginated DataProvider
|
|
2095
|
+
paginationInfos.forEach((info, index) => {
|
|
2096
|
+
// Try exact match first, then case-insensitive match
|
|
2097
|
+
let importName = Array.from(importToDataSource.entries()).find(
|
|
2098
|
+
([_, dataSourceVar]) => dataSourceVar === info.dataSourceIdentifier
|
|
2099
|
+
)?.[0]
|
|
2100
|
+
|
|
2101
|
+
if (!importName) {
|
|
2102
|
+
// Try case-insensitive match
|
|
2103
|
+
const normalizedIdentifier = info.dataSourceIdentifier.toLowerCase().replace(/[_-]/g, '')
|
|
2104
|
+
importName = Array.from(importToDataSource.entries()).find(
|
|
2105
|
+
([_, dataSourceVar]) =>
|
|
2106
|
+
dataSourceVar.toLowerCase().replace(/[_-]/g, '') === normalizedIdentifier
|
|
2107
|
+
)?.[0]
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
if (importName) {
|
|
2111
|
+
const paginatedVarName = `${info.dataSourceIdentifier}_pg_${index}`
|
|
2112
|
+
|
|
2113
|
+
const fetchParams = [
|
|
2114
|
+
types.objectProperty(types.identifier('page'), types.numericLiteral(1)),
|
|
2115
|
+
types.objectProperty(types.identifier('perPage'), types.numericLiteral(info.perPage)),
|
|
2116
|
+
]
|
|
2117
|
+
|
|
2118
|
+
// Add queryColumns if they exist
|
|
2119
|
+
if (info.queryColumns && info.queryColumns.length > 0) {
|
|
2120
|
+
fetchParams.push(
|
|
2121
|
+
types.objectProperty(
|
|
2122
|
+
types.identifier('queryColumns'),
|
|
2123
|
+
types.arrayExpression(info.queryColumns.map((col) => types.stringLiteral(col)))
|
|
2124
|
+
)
|
|
2125
|
+
)
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
// Create new fetchData call with pagination params
|
|
2129
|
+
const newFetchDataCall = types.callExpression(
|
|
2130
|
+
types.memberExpression(
|
|
2131
|
+
types.callExpression(
|
|
2132
|
+
types.memberExpression(types.identifier(importName), types.identifier('fetchData')),
|
|
2133
|
+
[types.objectExpression(fetchParams)]
|
|
2134
|
+
),
|
|
2135
|
+
types.identifier('catch')
|
|
2136
|
+
),
|
|
2137
|
+
[
|
|
2138
|
+
types.arrowFunctionExpression(
|
|
2139
|
+
[types.identifier('error')],
|
|
2140
|
+
types.blockStatement([
|
|
2141
|
+
types.expressionStatement(
|
|
2142
|
+
types.callExpression(
|
|
2143
|
+
types.memberExpression(types.identifier('console'), types.identifier('error')),
|
|
2144
|
+
[
|
|
2145
|
+
types.stringLiteral(`Error fetching ${paginatedVarName}:`),
|
|
2146
|
+
types.identifier('error'),
|
|
2147
|
+
]
|
|
2148
|
+
)
|
|
2149
|
+
),
|
|
2150
|
+
types.returnStatement(types.arrayExpression([])),
|
|
2151
|
+
])
|
|
2152
|
+
),
|
|
2153
|
+
]
|
|
2154
|
+
)
|
|
2155
|
+
|
|
2156
|
+
promiseArray.elements.push(newFetchDataCall)
|
|
2157
|
+
destructuringPattern.elements.push(types.identifier(paginatedVarName))
|
|
2158
|
+
}
|
|
2159
|
+
})
|
|
2160
|
+
|
|
2161
|
+
// Add fetchCount calls for paginated data sources (deduplicated by data source)
|
|
2162
|
+
|
|
2163
|
+
// Deduplicate by data source identifier
|
|
2164
|
+
const uniqueDataSources = new Set(paginationInfos.map((info) => info.dataSourceIdentifier))
|
|
2165
|
+
const addedCountFetches = new Set<string>()
|
|
2166
|
+
|
|
2167
|
+
uniqueDataSources.forEach((dataSourceId) => {
|
|
2168
|
+
let importName = Array.from(importToDataSource.entries()).find(
|
|
2169
|
+
([_, dataSourceVar]) => dataSourceVar === dataSourceId
|
|
2170
|
+
)?.[0]
|
|
2171
|
+
|
|
2172
|
+
if (!importName) {
|
|
2173
|
+
// Try case-insensitive match
|
|
2174
|
+
const normalizedIdentifier = dataSourceId.toLowerCase().replace(/[_-]/g, '')
|
|
2175
|
+
importName = Array.from(importToDataSource.entries()).find(
|
|
2176
|
+
([_, dataSourceVar]) =>
|
|
2177
|
+
dataSourceVar.toLowerCase().replace(/[_-]/g, '') === normalizedIdentifier
|
|
2178
|
+
)?.[0]
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
if (importName && !addedCountFetches.has(dataSourceId)) {
|
|
2182
|
+
const fetchCountCall = types.callExpression(
|
|
2183
|
+
types.memberExpression(types.identifier(importName), types.identifier('fetchCount')),
|
|
2184
|
+
[]
|
|
2185
|
+
)
|
|
2186
|
+
promiseArray.elements.push(fetchCountCall)
|
|
2187
|
+
destructuringPattern.elements.push(types.identifier(`${dataSourceId}_count`))
|
|
2188
|
+
addedCountFetches.add(dataSourceId)
|
|
2189
|
+
}
|
|
2190
|
+
})
|
|
2191
|
+
|
|
2192
|
+
// Calculate and add maxPages before return
|
|
2193
|
+
const returnStmt = tryBody.find((stmt: any) => stmt.type === 'ReturnStatement') as
|
|
2194
|
+
| types.ReturnStatement
|
|
2195
|
+
| undefined
|
|
2196
|
+
|
|
2197
|
+
if (returnStmt && returnStmt.argument?.type === 'ObjectExpression') {
|
|
2198
|
+
const propsProperty = (returnStmt.argument as types.ObjectExpression).properties.find(
|
|
2199
|
+
(prop: any) =>
|
|
2200
|
+
prop.type === 'ObjectProperty' && (prop.key as types.Identifier).name === 'props'
|
|
2201
|
+
) as types.ObjectProperty | undefined
|
|
2202
|
+
|
|
2203
|
+
if (propsProperty && propsProperty.value.type === 'ObjectExpression') {
|
|
2204
|
+
const propsObject = propsProperty.value as types.ObjectExpression
|
|
2205
|
+
const returnIndex = tryBody.indexOf(returnStmt)
|
|
2206
|
+
|
|
2207
|
+
paginationInfos.forEach((info, index) => {
|
|
2208
|
+
const paginatedVarName = `${info.dataSourceIdentifier}_pg_${index}`
|
|
2209
|
+
const countVarName = `${info.dataSourceIdentifier}_count`
|
|
2210
|
+
const maxPagesVarName = `${info.dataSourceIdentifier}_pg_${index}_maxPages`
|
|
2211
|
+
|
|
2212
|
+
const maxPagesCalc = types.variableDeclaration('const', [
|
|
2213
|
+
types.variableDeclarator(
|
|
2214
|
+
types.identifier(maxPagesVarName),
|
|
2215
|
+
types.callExpression(
|
|
2216
|
+
types.memberExpression(types.identifier('Math'), types.identifier('ceil')),
|
|
2217
|
+
[
|
|
2218
|
+
types.binaryExpression(
|
|
2219
|
+
'/',
|
|
2220
|
+
types.logicalExpression(
|
|
2221
|
+
'||',
|
|
2222
|
+
types.identifier(countVarName),
|
|
2223
|
+
types.numericLiteral(0)
|
|
2224
|
+
),
|
|
2225
|
+
types.numericLiteral(info.perPage)
|
|
2226
|
+
),
|
|
2227
|
+
]
|
|
2228
|
+
)
|
|
2229
|
+
),
|
|
2230
|
+
])
|
|
2231
|
+
|
|
2232
|
+
tryBody.splice(returnIndex, 0, maxPagesCalc)
|
|
2233
|
+
|
|
2234
|
+
// Add both the paginated data and maxPages to props
|
|
2235
|
+
propsObject.properties.push(
|
|
2236
|
+
types.objectProperty(
|
|
2237
|
+
types.identifier(paginatedVarName),
|
|
2238
|
+
types.identifier(paginatedVarName)
|
|
2239
|
+
)
|
|
2240
|
+
)
|
|
2241
|
+
propsObject.properties.push(
|
|
2242
|
+
types.objectProperty(types.identifier(maxPagesVarName), types.identifier(maxPagesVarName))
|
|
2243
|
+
)
|
|
2244
|
+
})
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
function createAPIRoutesForPaginatedDataSources(
|
|
2250
|
+
uidlNode: any,
|
|
2251
|
+
dataSources: any,
|
|
2252
|
+
componentChunk: any,
|
|
2253
|
+
extractedResources: any,
|
|
2254
|
+
paginationInfos: ArrayMapperPaginationInfo[],
|
|
2255
|
+
isComponent: boolean
|
|
2256
|
+
): void {
|
|
2257
|
+
const paginatedDataSourceIds = new Set(paginationInfos.map((info) => info.dataSourceIdentifier))
|
|
2258
|
+
|
|
2259
|
+
const traverseForDataSources = (node: any): void => {
|
|
2260
|
+
if (!node) {
|
|
2261
|
+
return
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
if (node.type === 'data-source-list' || node.type === 'data-source-item') {
|
|
2265
|
+
const renderProp = node.content.renderPropIdentifier
|
|
2266
|
+
|
|
2267
|
+
if (renderProp && paginatedDataSourceIds.has(renderProp)) {
|
|
2268
|
+
extractDataSourceIntoNextAPIFolder(node, dataSources, componentChunk, extractedResources)
|
|
2269
|
+
|
|
2270
|
+
// For components, also create count API route
|
|
2271
|
+
if (isComponent) {
|
|
2272
|
+
const resourceDef = node.content.resourceDefinition
|
|
2273
|
+
if (resourceDef) {
|
|
2274
|
+
const dataSourceId = resourceDef.dataSourceId
|
|
2275
|
+
const tableName = resourceDef.tableName
|
|
2276
|
+
const dataSourceType = resourceDef.dataSourceType
|
|
2277
|
+
const fileName = `${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
|
|
2278
|
+
const countFileName = `${fileName}-count`
|
|
2279
|
+
|
|
2280
|
+
// Create count API route that exports getCount handler
|
|
2281
|
+
extractedResources[`api/${countFileName}`] = {
|
|
2282
|
+
fileName: countFileName,
|
|
2283
|
+
fileType: FileType.JS,
|
|
2284
|
+
path: ['pages', 'api'],
|
|
2285
|
+
content: `import dataSource from '../../utils/data-sources/${fileName}'
|
|
2286
|
+
|
|
2287
|
+
export default dataSource.getCount
|
|
2288
|
+
`,
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
if (node.content?.children) {
|
|
2296
|
+
node.content.children.forEach((child: any) => traverseForDataSources(child))
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
traverseForDataSources(uidlNode)
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
function createAPIRoutesForSearchOnlyDataSources(
|
|
2304
|
+
uidlNode: any,
|
|
2305
|
+
dataSources: any,
|
|
2306
|
+
componentChunk: any,
|
|
2307
|
+
extractedResources: any,
|
|
2308
|
+
searchOnlyDataSources: DetectedPagination[]
|
|
2309
|
+
): void {
|
|
2310
|
+
const searchOnlyDataSourceIds = new Set(
|
|
2311
|
+
searchOnlyDataSources.map((info) => info.dataSourceIdentifier)
|
|
2312
|
+
)
|
|
2313
|
+
|
|
2314
|
+
const traverseForDataSources = (node: any): void => {
|
|
2315
|
+
if (!node) {
|
|
2316
|
+
return
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
if (node.type === 'data-source-list' || node.type === 'data-source-item') {
|
|
2320
|
+
const renderProp = node.content.renderPropIdentifier
|
|
2321
|
+
|
|
2322
|
+
if (renderProp && searchOnlyDataSourceIds.has(renderProp)) {
|
|
2323
|
+
extractDataSourceIntoNextAPIFolder(node, dataSources, componentChunk, extractedResources)
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
if (node.content?.children) {
|
|
2328
|
+
node.content.children.forEach((child: any) => traverseForDataSources(child))
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
traverseForDataSources(uidlNode)
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
export default createNextArrayMapperPaginationPlugin()
|