@teleporthq/teleport-plugin-next-data-source 0.42.1 → 0.42.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cjs/count-fetchers.d.ts.map +1 -1
  2. package/dist/cjs/count-fetchers.js +1 -1
  3. package/dist/cjs/count-fetchers.js.map +1 -1
  4. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
  5. package/dist/cjs/fetchers/clickhouse.js +1 -1
  6. package/dist/cjs/fetchers/clickhouse.js.map +1 -1
  7. package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
  8. package/dist/cjs/fetchers/firestore.js +1 -1
  9. package/dist/cjs/fetchers/firestore.js.map +1 -1
  10. package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
  11. package/dist/cjs/fetchers/javascript.js +1 -1
  12. package/dist/cjs/fetchers/javascript.js.map +1 -1
  13. package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
  14. package/dist/cjs/fetchers/redshift.js +3 -1
  15. package/dist/cjs/fetchers/redshift.js.map +1 -1
  16. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
  17. package/dist/cjs/fetchers/rest-api.js +2 -2
  18. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  19. package/dist/cjs/fetchers/turso.d.ts.map +1 -1
  20. package/dist/cjs/fetchers/turso.js +1 -1
  21. package/dist/cjs/fetchers/turso.js.map +1 -1
  22. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  23. package/dist/cjs/pagination-plugin.js +180 -131
  24. package/dist/cjs/pagination-plugin.js.map +1 -1
  25. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  26. package/dist/esm/count-fetchers.d.ts.map +1 -1
  27. package/dist/esm/count-fetchers.js +1 -1
  28. package/dist/esm/count-fetchers.js.map +1 -1
  29. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
  30. package/dist/esm/fetchers/clickhouse.js +1 -1
  31. package/dist/esm/fetchers/clickhouse.js.map +1 -1
  32. package/dist/esm/fetchers/firestore.d.ts.map +1 -1
  33. package/dist/esm/fetchers/firestore.js +1 -1
  34. package/dist/esm/fetchers/firestore.js.map +1 -1
  35. package/dist/esm/fetchers/javascript.d.ts.map +1 -1
  36. package/dist/esm/fetchers/javascript.js +1 -1
  37. package/dist/esm/fetchers/javascript.js.map +1 -1
  38. package/dist/esm/fetchers/redshift.d.ts.map +1 -1
  39. package/dist/esm/fetchers/redshift.js +3 -1
  40. package/dist/esm/fetchers/redshift.js.map +1 -1
  41. package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
  42. package/dist/esm/fetchers/rest-api.js +2 -2
  43. package/dist/esm/fetchers/rest-api.js.map +1 -1
  44. package/dist/esm/fetchers/turso.d.ts.map +1 -1
  45. package/dist/esm/fetchers/turso.js +1 -1
  46. package/dist/esm/fetchers/turso.js.map +1 -1
  47. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  48. package/dist/esm/pagination-plugin.js +180 -131
  49. package/dist/esm/pagination-plugin.js.map +1 -1
  50. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +2 -2
  52. package/src/count-fetchers.ts +2 -1
  53. package/src/fetchers/clickhouse.ts +12 -6
  54. package/src/fetchers/firestore.ts +45 -13
  55. package/src/fetchers/javascript.ts +48 -13
  56. package/src/fetchers/redshift.ts +32 -9
  57. package/src/fetchers/rest-api.ts +68 -6
  58. package/src/fetchers/turso.ts +46 -16
  59. package/src/pagination-plugin.ts +336 -266
@@ -123,6 +123,8 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
123
123
  const searchConfigMap = opts.paginationConfig?.searchConfigMap || new Map<string, any>()
124
124
  const queryColumnsMap = opts.paginationConfig?.queryColumnsMap || new Map<string, string[]>()
125
125
 
126
+ const stateDeclarations: types.Statement[] = []
127
+
126
128
  detectedPaginations.forEach((detected, index) => {
127
129
  const paginationNodeId = `pg_${index}`
128
130
  // Use arrayMapperRenderProp if available, otherwise fall back to dataSourceIdentifier
@@ -141,55 +143,27 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
141
143
  )
142
144
  paginationInfos.push(info)
143
145
 
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', [
146
+ // Add refs to track first render for each useEffect (add first)
147
+ if (info.searchEnabled) {
148
+ const skipCountFetchOnMountRefVar = `skipCountFetchOnMount_pg_${index}`
149
+ const skipCountFetchRefAST = types.variableDeclaration('const', [
168
150
  types.variableDeclarator(
169
- types.arrayPattern([
170
- types.identifier(info.searchQueryVar),
171
- types.identifier(info.setSearchQueryVar),
172
- ]),
173
- types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
151
+ types.identifier(skipCountFetchOnMountRefVar),
152
+ types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
174
153
  ),
175
154
  ])
176
- blockStatement.body.unshift(searchStateAST)
155
+ stateDeclarations.push(skipCountFetchRefAST)
156
+ ;(info as any).skipCountFetchOnMountRefVar = skipCountFetchOnMountRefVar
177
157
 
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', [
158
+ const skipDebounceOnMountRefVar = `skipDebounceOnMount_pg_${index}`
159
+ const skipDebounceRefAST = types.variableDeclaration('const', [
184
160
  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)])
161
+ types.identifier(skipDebounceOnMountRefVar),
162
+ types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
190
163
  ),
191
164
  ])
192
- blockStatement.body.unshift(pageStateAST)
165
+ stateDeclarations.push(skipDebounceRefAST)
166
+ ;(info as any).skipDebounceOnMountRefVar = skipDebounceOnMountRefVar
193
167
  }
194
168
 
195
169
  // Add maxPages state
@@ -221,36 +195,69 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
221
195
  types.callExpression(types.identifier('useState'), [maxPagesInitValue])
222
196
  ),
223
197
  ])
224
- blockStatement.body.unshift(maxPagesStateAST)
198
+ stateDeclarations.push(maxPagesStateAST)
225
199
 
226
200
  // Store these for later use
227
201
  ;(info as any).maxPagesStateVar = maxPagesStateVar
228
202
  ;(info as any).setMaxPagesStateVar = setMaxPagesStateVar
229
203
 
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', [
204
+ // If both pagination and search are enabled, combine them into a single state object
205
+ if (info.searchEnabled && info.searchQueryVar && info.setSearchQueryVar) {
206
+ // Combined state: { page: 1, debouncedQuery: '' }
207
+ const combinedStateVar = `paginationState_pg_${index}`
208
+ const setCombinedStateVar = `setPaginationState_pg_${index}`
209
+
210
+ const combinedStateAST = types.variableDeclaration('const', [
234
211
  types.variableDeclarator(
235
- types.identifier(skipDebounceOnMountRefVar),
236
- types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
212
+ types.arrayPattern([
213
+ types.identifier(combinedStateVar),
214
+ types.identifier(setCombinedStateVar),
215
+ ]),
216
+ types.callExpression(types.identifier('useState'), [
217
+ types.objectExpression([
218
+ types.objectProperty(types.identifier('page'), types.numericLiteral(1)),
219
+ types.objectProperty(types.identifier('debouncedQuery'), types.stringLiteral('')),
220
+ ]),
221
+ ])
237
222
  ),
238
223
  ])
239
- blockStatement.body.unshift(skipDebounceRefAST)
240
- ;(info as any).skipDebounceOnMountRefVar = skipDebounceOnMountRefVar
224
+ stateDeclarations.push(combinedStateAST)
241
225
 
242
- const skipCountFetchOnMountRefVar = `skipCountFetchOnMount_pg_${index}`
243
- const skipCountFetchRefAST = types.variableDeclaration('const', [
226
+ // Still need the immediate search query state for the input
227
+ const searchStateAST = types.variableDeclaration('const', [
244
228
  types.variableDeclarator(
245
- types.identifier(skipCountFetchOnMountRefVar),
246
- types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
229
+ types.arrayPattern([
230
+ types.identifier(info.searchQueryVar),
231
+ types.identifier(info.setSearchQueryVar),
232
+ ]),
233
+ types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
247
234
  ),
248
235
  ])
249
- blockStatement.body.unshift(skipCountFetchRefAST)
250
- ;(info as any).skipCountFetchOnMountRefVar = skipCountFetchOnMountRefVar
236
+ stateDeclarations.push(searchStateAST)
237
+
238
+ // Store the combined state var names
239
+ ;(info as any).combinedStateVar = combinedStateVar
240
+ ;(info as any).setCombinedStateVar = setCombinedStateVar
241
+ } else {
242
+ // If search is not enabled, just add regular page state
243
+ const pageStateAST = types.variableDeclaration('const', [
244
+ types.variableDeclarator(
245
+ types.arrayPattern([
246
+ types.identifier(info.pageStateVar),
247
+ types.identifier(info.setPageStateVar),
248
+ ]),
249
+ types.callExpression(types.identifier('useState'), [types.numericLiteral(1)])
250
+ ),
251
+ ])
252
+ stateDeclarations.push(pageStateAST)
251
253
  }
252
254
  })
253
255
 
256
+ // Add all state declarations at once to the beginning in correct order
257
+ stateDeclarations.reverse().forEach((stateDecl) => {
258
+ blockStatement.body.unshift(stateDecl)
259
+ })
260
+
254
261
  // Add useEffect dependency if any pagination has search enabled
255
262
  const hasSearchEnabled = paginationInfos.some((info) => info.searchEnabled)
256
263
  if (hasSearchEnabled && !dependencies.useEffect) {
@@ -351,201 +358,203 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
351
358
  blockStatement.body.splice(insertIndex, 0, debounceEffect)
352
359
 
353
360
  // 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
+ const detected = detectedPaginations.find(
362
+ (d) => d.dataSourceIdentifier === info.dataSourceIdentifier
363
+ )
364
+ if (!detected) {
365
+ return
366
+ }
361
367
 
362
- const resourceDefAttr = detected.dataProviderJSX.openingElement.attributes.find(
363
- (attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
364
- )
368
+ const resourceDefAttr = detected.dataProviderJSX.openingElement.attributes.find(
369
+ (attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
370
+ )
365
371
 
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
- )
372
+ if (
373
+ resourceDefAttr &&
374
+ resourceDefAttr.value &&
375
+ resourceDefAttr.value.type === 'JSXExpressionContainer'
376
+ ) {
377
+ const resourceDef = resourceDefAttr.value.expression
378
+ if (resourceDef.type === 'ObjectExpression') {
379
+ const dataSourceIdProp = (resourceDef.properties as any[]).find(
380
+ (p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceId'
381
+ )
382
+ const tableNameProp = (resourceDef.properties as any[]).find(
383
+ (p: any) => p.type === 'ObjectProperty' && p.key.value === 'tableName'
384
+ )
385
+ const dataSourceTypeProp = (resourceDef.properties as any[]).find(
386
+ (p: any) => p.type === 'ObjectProperty' && p.key.value === 'dataSourceType'
387
+ )
382
388
 
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
+ if (dataSourceIdProp && tableNameProp && dataSourceTypeProp) {
390
+ const dataSourceId = dataSourceIdProp.value.value
391
+ const tableName = tableNameProp.value.value
392
+ const dataSourceType = dataSourceTypeProp.value.value
393
+ const fileName = `${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
394
+ const setMaxPagesStateVar = (info as any).setMaxPagesStateVar
395
+
396
+ // Create useEffect to refetch count when debounced search changes
397
+ const skipCountFetchOnMountRefVar = (info as any).skipCountFetchOnMountRefVar
398
+ const combinedStateVar = (info as any).combinedStateVar
399
+
400
+ // Build URLSearchParams properties - query is always included, queryColumns is optional
401
+ const urlSearchParamsProperties: any[] = [
402
+ types.objectProperty(
403
+ types.identifier('query'),
404
+ types.memberExpression(
405
+ types.identifier(combinedStateVar),
406
+ types.identifier('debouncedQuery')
407
+ )
408
+ ),
409
+ ]
389
410
 
390
- // Create useEffect to refetch count when debounced search changes
391
- const skipCountFetchOnMountRefVar = (info as any).skipCountFetchOnMountRefVar
392
- const combinedStateVar = (info as any).combinedStateVar
411
+ // Add queryColumns only if they exist
412
+ if (info.queryColumns && info.queryColumns.length > 0) {
413
+ urlSearchParamsProperties.push(
414
+ types.objectProperty(
415
+ types.identifier('queryColumns'),
416
+ types.callExpression(
417
+ types.memberExpression(
418
+ types.identifier('JSON'),
419
+ types.identifier('stringify')
420
+ ),
421
+ [
422
+ types.arrayExpression(
423
+ info.queryColumns.map((col) => types.stringLiteral(col))
424
+ ),
425
+ ]
426
+ )
427
+ )
428
+ )
429
+ }
393
430
 
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
- ])
431
+ const refetchCountEffect = types.expressionStatement(
432
+ types.callExpression(types.identifier('useEffect'), [
433
+ types.arrowFunctionExpression(
434
+ [],
435
+ types.blockStatement([
436
+ types.ifStatement(
437
+ types.memberExpression(
438
+ types.identifier(skipCountFetchOnMountRefVar),
439
+ types.identifier('current')
417
440
  ),
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
- ]
441
+ types.blockStatement([
442
+ types.expressionStatement(
443
+ types.assignmentExpression(
444
+ '=',
445
+ types.memberExpression(
446
+ types.identifier(skipCountFetchOnMountRefVar),
447
+ types.identifier('current')
448
+ ),
449
+ types.booleanLiteral(false)
450
+ )
451
+ ),
452
+ types.returnStatement(),
453
+ ])
454
+ ),
455
+ types.expressionStatement(
456
+ types.callExpression(
457
+ types.memberExpression(
458
+ types.callExpression(
459
+ types.memberExpression(
460
+ types.callExpression(types.identifier('fetch'), [
461
+ types.templateLiteral(
462
+ [
463
+ types.templateElement({
464
+ raw: `/api/${fileName}-count?`,
465
+ cooked: `/api/${fileName}-count?`,
466
+ }),
467
+ types.templateElement({ raw: '', cooked: '' }),
468
+ ],
469
+ [
470
+ types.newExpression(types.identifier('URLSearchParams'), [
471
+ types.objectExpression(urlSearchParamsProperties),
472
+ ]),
473
+ ]
474
+ ),
475
+ ]),
476
+ types.identifier('then')
477
+ ),
478
+ [
479
+ types.arrowFunctionExpression(
480
+ [types.identifier('res')],
481
+ types.callExpression(
482
+ types.memberExpression(
483
+ types.identifier('res'),
484
+ types.identifier('json')
461
485
  ),
462
- ]),
463
- types.identifier('then')
486
+ []
487
+ )
464
488
  ),
465
- [
466
- types.arrowFunctionExpression(
467
- [types.identifier('res')],
468
- types.callExpression(
469
- types.memberExpression(
470
- types.identifier('res'),
471
- types.identifier('json')
472
- ),
473
- []
489
+ ]
490
+ ),
491
+ types.identifier('then')
492
+ ),
493
+ [
494
+ types.arrowFunctionExpression(
495
+ [types.identifier('data')],
496
+ types.blockStatement([
497
+ types.ifStatement(
498
+ types.logicalExpression(
499
+ '&&',
500
+ types.identifier('data'),
501
+ types.binaryExpression(
502
+ 'in',
503
+ types.stringLiteral('count'),
504
+ types.identifier('data')
474
505
  )
475
506
  ),
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(
507
+ types.blockStatement([
508
+ types.expressionStatement(
509
+ types.callExpression(types.identifier(setMaxPagesStateVar), [
510
+ types.conditionalExpression(
511
+ types.binaryExpression(
512
+ '===',
513
+ types.memberExpression(
514
+ types.identifier('data'),
515
+ types.identifier('count')
516
+ ),
517
+ types.numericLiteral(0)
518
+ ),
519
+ types.numericLiteral(0),
520
+ types.callExpression(
521
+ types.memberExpression(
522
+ types.identifier('Math'),
523
+ types.identifier('ceil')
524
+ ),
525
+ [
500
526
  types.binaryExpression(
501
- '===',
527
+ '/',
502
528
  types.memberExpression(
503
529
  types.identifier('data'),
504
530
  types.identifier('count')
505
531
  ),
506
- types.numericLiteral(0)
532
+ types.numericLiteral(info.perPage)
507
533
  ),
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')
534
+ ]
535
+ )
536
+ ),
537
+ ])
538
+ ),
539
+ ])
540
+ ),
541
+ ])
542
+ ),
543
+ ]
544
+ )
542
545
  ),
543
- ]),
544
- ])
545
- )
546
+ ])
547
+ ),
548
+ types.arrayExpression([
549
+ types.memberExpression(
550
+ types.identifier(combinedStateVar),
551
+ types.identifier('debouncedQuery')
552
+ ),
553
+ ]),
554
+ ])
555
+ )
546
556
 
547
- blockStatement.body.splice(insertIndex, 0, refetchCountEffect)
548
- }
557
+ blockStatement.body.splice(insertIndex, 0, refetchCountEffect)
549
558
  }
550
559
  }
551
560
  }
@@ -623,6 +632,9 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
623
632
  })
624
633
 
625
634
  // Create ONE useEffect per unique data source
635
+ // Collect all useEffect statements first
636
+ const componentUseEffects: types.Statement[] = []
637
+
626
638
  dataSourceToInfos.forEach((infos) => {
627
639
  const { fileName } = infos[0]
628
640
 
@@ -702,7 +714,20 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
702
714
  ])
703
715
  )
704
716
 
705
- blockStatement.body.unshift(useEffectAST)
717
+ componentUseEffects.push(useEffectAST)
718
+ })
719
+
720
+ // Insert all component useEffect hooks after state declarations but before return
721
+ // Find the first return statement
722
+ const componentReturnIndex = blockStatement.body.findIndex(
723
+ (stmt: any) => stmt.type === 'ReturnStatement'
724
+ )
725
+ const componentEffectsInsertIndex =
726
+ componentReturnIndex !== -1 ? componentReturnIndex : blockStatement.body.length
727
+
728
+ // Insert in reverse order to maintain correct order
729
+ componentUseEffects.reverse().forEach((effect) => {
730
+ blockStatement.body.splice(componentEffectsInsertIndex, 0, effect)
706
731
  })
707
732
  }
708
733
 
@@ -736,7 +761,6 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
736
761
  searchOnlyDataSources,
737
762
  searchConfigMap,
738
763
  queryColumnsMap,
739
- insertIndex,
740
764
  dependencies
741
765
  )
742
766
 
@@ -830,7 +854,7 @@ function detectPaginationsAndSearchFromJSX(
830
854
  hasSearch: boolean
831
855
  }
832
856
 
833
- const dataProviderMap = new Map<string, DataProviderInfo>()
857
+ const dataProviderList: DataProviderInfo[] = []
834
858
 
835
859
  // First pass: collect array mapper info from UIDL, keyed by array mapper render prop (unique)
836
860
  interface ArrayMapperInfo {
@@ -1025,14 +1049,16 @@ function detectPaginationsAndSearchFromJSX(
1025
1049
  } | null = null
1026
1050
  let searchInputInfo: { class: string | null; jsx: any } | null = null
1027
1051
 
1028
- const findSearchAndPaginationInScope = (scopeNode: any, skipNode: any = null): void => {
1052
+ const findSearchAndPaginationInScope = (scopeNode: any, skipNode: any = null): boolean => {
1029
1053
  if (!scopeNode || !scopeNode.children || !Array.isArray(scopeNode.children)) {
1030
- return
1054
+ return false
1031
1055
  }
1032
1056
 
1033
- scopeNode.children.forEach((child: any) => {
1057
+ let foundSomething = false
1058
+
1059
+ for (const child of scopeNode.children) {
1034
1060
  if (child === skipNode) {
1035
- return
1061
+ continue
1036
1062
  }
1037
1063
 
1038
1064
  if (child.type === 'JSXElement') {
@@ -1049,13 +1075,14 @@ function detectPaginationsAndSearchFromJSX(
1049
1075
  prevClass,
1050
1076
  nextClass,
1051
1077
  }
1078
+ foundSomething = true
1052
1079
  }
1053
1080
  }
1054
1081
 
1055
1082
  // Found search container - search for input inside it
1056
1083
  if (childClassName && childClassName.includes('data-source-search-node')) {
1057
1084
  if (child.children && Array.isArray(child.children)) {
1058
- child.children.forEach((searchChild: any) => {
1085
+ for (const searchChild of child.children) {
1059
1086
  if (searchChild.type === 'JSXElement') {
1060
1087
  const searchChildElementName = searchChild.openingElement?.name?.name
1061
1088
  const searchChildClassName = getClassName(
@@ -1070,9 +1097,10 @@ function detectPaginationsAndSearchFromJSX(
1070
1097
  class: searchChildClassName,
1071
1098
  jsx: searchChild,
1072
1099
  }
1100
+ foundSomething = true
1073
1101
  }
1074
1102
  }
1075
- })
1103
+ }
1076
1104
  }
1077
1105
  }
1078
1106
 
@@ -1086,13 +1114,24 @@ function detectPaginationsAndSearchFromJSX(
1086
1114
  class: childClassName,
1087
1115
  jsx: child,
1088
1116
  }
1117
+ foundSomething = true
1118
+ }
1119
+
1120
+ // Stop searching if we found both or if we found what we're looking for
1121
+ if (foundSomething && (searchInputInfo || paginationNodeInfo)) {
1122
+ return true
1089
1123
  }
1090
1124
 
1125
+ // Only recurse if we haven't found what we're looking for yet
1091
1126
  if (!searchInputInfo || !paginationNodeInfo) {
1092
- findSearchAndPaginationInScope(child, skipNode)
1127
+ if (findSearchAndPaginationInScope(child, skipNode)) {
1128
+ return true
1129
+ }
1093
1130
  }
1094
1131
  }
1095
- })
1132
+ }
1133
+
1134
+ return foundSomething
1096
1135
  }
1097
1136
 
1098
1137
  let currentScope = parent
@@ -1100,14 +1139,18 @@ function detectPaginationsAndSearchFromJSX(
1100
1139
  const maxDepth = 5
1101
1140
 
1102
1141
  while (currentScope && (!searchInputInfo || !paginationNodeInfo) && depth < maxDepth) {
1103
- findSearchAndPaginationInScope(currentScope, depth === 0 ? dataProvider : null)
1142
+ const found = findSearchAndPaginationInScope(currentScope, depth === 0 ? dataProvider : null)
1143
+ // Stop searching up the tree if we found a pagination node at this level
1144
+ if (found && paginationNodeInfo) {
1145
+ break
1146
+ }
1104
1147
  currentScope = findParentNode(blockStatement, currentScope)
1105
1148
  depth++
1106
1149
  }
1107
1150
 
1108
1151
  // Record the DataProvider with its pagination/search info
1109
- const uniqueKey = `${dataProviderIdentifier}__${arrayMapperRenderProp}`
1110
- dataProviderMap.set(uniqueKey, {
1152
+ // Use array instead of Map to handle multiple DataProviders with same name
1153
+ dataProviderList.push({
1111
1154
  identifier: dataProviderIdentifier,
1112
1155
  dataProvider,
1113
1156
  arrayMapperRenderProp,
@@ -1124,8 +1167,17 @@ function detectPaginationsAndSearchFromJSX(
1124
1167
  const paginationOnlyMappers: DetectedPagination[] = []
1125
1168
  const plainMappers: DetectedPagination[] = []
1126
1169
 
1127
- dataProviderMap.forEach((info) => {
1128
- if (info.hasPagination && info.hasSearch) {
1170
+ dataProviderList.forEach((info) => {
1171
+ // Check UIDL flags for this array mapper
1172
+ const uidlInfo = info.arrayMapperRenderProp
1173
+ ? arrayMapperInfoMap.get(info.arrayMapperRenderProp)
1174
+ : null
1175
+
1176
+ // Only process pagination/search if UIDL explicitly enables it
1177
+ const shouldHavePagination = uidlInfo?.paginated && info.hasPagination
1178
+ const shouldHaveSearch = uidlInfo?.searchEnabled && info.hasSearch
1179
+
1180
+ if (shouldHavePagination && shouldHaveSearch) {
1129
1181
  // Pagination + Search
1130
1182
  paginatedMappers.push({
1131
1183
  paginationNodeClass: info.paginationNode!.class,
@@ -1137,7 +1189,7 @@ function detectPaginationsAndSearchFromJSX(
1137
1189
  searchInputClass: info.searchInput?.class,
1138
1190
  searchInputJSX: info.searchInput?.jsx,
1139
1191
  })
1140
- } else if (info.hasPagination && !info.hasSearch) {
1192
+ } else if (shouldHavePagination && !shouldHaveSearch) {
1141
1193
  // Pagination only
1142
1194
  paginationOnlyMappers.push({
1143
1195
  paginationNodeClass: info.paginationNode!.class,
@@ -1149,7 +1201,7 @@ function detectPaginationsAndSearchFromJSX(
1149
1201
  searchInputClass: undefined,
1150
1202
  searchInputJSX: undefined,
1151
1203
  })
1152
- } else if (!info.hasPagination && info.hasSearch) {
1204
+ } else if (!shouldHavePagination && shouldHaveSearch) {
1153
1205
  // Search only
1154
1206
  searchOnlyMappers.push({
1155
1207
  paginationNodeClass: '',
@@ -1162,7 +1214,7 @@ function detectPaginationsAndSearchFromJSX(
1162
1214
  searchInputJSX: info.searchInput?.jsx,
1163
1215
  })
1164
1216
  } else {
1165
- // Plain (no pagination, no search)
1217
+ // Plain (no pagination, no search) - UIDL doesn't enable it or no controls found
1166
1218
  plainMappers.push({
1167
1219
  paginationNodeClass: '',
1168
1220
  prevButtonClass: null,
@@ -1184,9 +1236,11 @@ function handleSearchOnlyArrayMappers(
1184
1236
  searchOnlyDataSources: DetectedPagination[],
1185
1237
  searchConfigMap: Map<string, any>,
1186
1238
  queryColumnsMap: Map<string, string[]>,
1187
- insertIndex: number,
1188
1239
  dependencies: any
1189
1240
  ): void {
1241
+ const searchOnlyStates: types.Statement[] = []
1242
+ const searchOnlyEffects: types.Statement[] = []
1243
+
1190
1244
  searchOnlyDataSources.forEach((detected, index) => {
1191
1245
  const searchId = `search_${index}`
1192
1246
  const searchQueryVar = `${searchId}_query`
@@ -1205,14 +1259,14 @@ function handleSearchOnlyArrayMappers(
1205
1259
  const searchDebounce = searchConfig?.searchDebounce || 300
1206
1260
  const queryColumns = queryColumnsMap.get(detected.dataSourceIdentifier)
1207
1261
 
1208
- // Add search query state
1209
- const searchStateAST = types.variableDeclaration('const', [
1262
+ // Add skip ref
1263
+ const skipRefAST = types.variableDeclaration('const', [
1210
1264
  types.variableDeclarator(
1211
- types.arrayPattern([types.identifier(searchQueryVar), types.identifier(setSearchQueryVar)]),
1212
- types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
1265
+ types.identifier(skipDebounceOnMountRefVar),
1266
+ types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
1213
1267
  ),
1214
1268
  ])
1215
- blockStatement.body.unshift(searchStateAST)
1269
+ searchOnlyStates.push(skipRefAST)
1216
1270
 
1217
1271
  // Add debounced search state
1218
1272
  const debouncedSearchStateAST = types.variableDeclaration('const', [
@@ -1224,16 +1278,16 @@ function handleSearchOnlyArrayMappers(
1224
1278
  types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
1225
1279
  ),
1226
1280
  ])
1227
- blockStatement.body.unshift(debouncedSearchStateAST)
1281
+ searchOnlyStates.push(debouncedSearchStateAST)
1228
1282
 
1229
- // Add skip ref
1230
- const skipRefAST = types.variableDeclaration('const', [
1283
+ // Add search query state
1284
+ const searchStateAST = types.variableDeclaration('const', [
1231
1285
  types.variableDeclarator(
1232
- types.identifier(skipDebounceOnMountRefVar),
1233
- types.callExpression(types.identifier('useRef'), [types.booleanLiteral(true)])
1286
+ types.arrayPattern([types.identifier(searchQueryVar), types.identifier(setSearchQueryVar)]),
1287
+ types.callExpression(types.identifier('useState'), [types.stringLiteral('')])
1234
1288
  ),
1235
1289
  ])
1236
- blockStatement.body.unshift(skipRefAST)
1290
+ searchOnlyStates.push(searchStateAST)
1237
1291
 
1238
1292
  // Add useEffect for debouncing
1239
1293
  if (!dependencies.useEffect) {
@@ -1300,7 +1354,7 @@ function handleSearchOnlyArrayMappers(
1300
1354
  types.arrayExpression([types.identifier(searchQueryVar)]),
1301
1355
  ])
1302
1356
  )
1303
- blockStatement.body.splice(insertIndex, 0, debounceEffect)
1357
+ searchOnlyEffects.push(debounceEffect)
1304
1358
 
1305
1359
  // Modify DataProvider to include search params (even without queryColumns)
1306
1360
  if (detected.dataProviderJSX) {
@@ -1319,6 +1373,22 @@ function handleSearchOnlyArrayMappers(
1319
1373
  } as any)
1320
1374
  }
1321
1375
  })
1376
+
1377
+ // Add all state declarations at the beginning in correct order
1378
+ searchOnlyStates.reverse().forEach((stateDecl) => {
1379
+ blockStatement.body.unshift(stateDecl)
1380
+ })
1381
+
1382
+ // Recalculate insertIndex after adding states (since unshift shifted everything)
1383
+ const newInsertIndex = blockStatement.body.findIndex(
1384
+ (stmt: any) => stmt.type === 'ReturnStatement'
1385
+ )
1386
+ const finalInsertIndex = newInsertIndex !== -1 ? newInsertIndex : blockStatement.body.length
1387
+
1388
+ // Add all useEffect hooks before the return statement
1389
+ searchOnlyEffects.reverse().forEach((effect) => {
1390
+ blockStatement.body.splice(finalInsertIndex, 0, effect)
1391
+ })
1322
1392
  }
1323
1393
 
1324
1394
  function addSearchParamsToDataProvider(