@teleporthq/teleport-plugin-next-data-source 0.43.63 → 0.43.65

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 (134) hide show
  1. package/__tests__/collection-path.test.ts +191 -0
  2. package/__tests__/count-collection-path.test.ts +59 -0
  3. package/__tests__/filter-groups.test.ts +174 -0
  4. package/dist/cjs/count-fetchers.d.ts.map +1 -1
  5. package/dist/cjs/count-fetchers.js +1 -1
  6. package/dist/cjs/count-fetchers.js.map +1 -1
  7. package/dist/cjs/fetchers/airtable.d.ts.map +1 -1
  8. package/dist/cjs/fetchers/airtable.js +1 -1
  9. package/dist/cjs/fetchers/airtable.js.map +1 -1
  10. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -1
  11. package/dist/cjs/fetchers/clickhouse.js +1 -1
  12. package/dist/cjs/fetchers/clickhouse.js.map +1 -1
  13. package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
  14. package/dist/cjs/fetchers/csv-file.js +3 -3
  15. package/dist/cjs/fetchers/csv-file.js.map +1 -1
  16. package/dist/cjs/fetchers/firestore.d.ts.map +1 -1
  17. package/dist/cjs/fetchers/firestore.js +1 -1
  18. package/dist/cjs/fetchers/firestore.js.map +1 -1
  19. package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
  20. package/dist/cjs/fetchers/google-sheets.js +1 -1
  21. package/dist/cjs/fetchers/google-sheets.js.map +1 -1
  22. package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
  23. package/dist/cjs/fetchers/javascript.js +2 -2
  24. package/dist/cjs/fetchers/javascript.js.map +1 -1
  25. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
  26. package/dist/cjs/fetchers/mariadb.js +1 -1
  27. package/dist/cjs/fetchers/mariadb.js.map +1 -1
  28. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -1
  29. package/dist/cjs/fetchers/mongodb.js +1 -1
  30. package/dist/cjs/fetchers/mongodb.js.map +1 -1
  31. package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
  32. package/dist/cjs/fetchers/mysql.js +1 -1
  33. package/dist/cjs/fetchers/mysql.js.map +1 -1
  34. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  35. package/dist/cjs/fetchers/postgresql.js +1 -1
  36. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  37. package/dist/cjs/fetchers/redis.d.ts.map +1 -1
  38. package/dist/cjs/fetchers/redis.js +1 -1
  39. package/dist/cjs/fetchers/redis.js.map +1 -1
  40. package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
  41. package/dist/cjs/fetchers/redshift.js +2 -2
  42. package/dist/cjs/fetchers/redshift.js.map +1 -1
  43. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
  44. package/dist/cjs/fetchers/rest-api.js +11 -3
  45. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  46. package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
  47. package/dist/cjs/fetchers/supabase.js +1 -1
  48. package/dist/cjs/fetchers/supabase.js.map +1 -1
  49. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  50. package/dist/cjs/fetchers/teleport.js +1 -1
  51. package/dist/cjs/fetchers/teleport.js.map +1 -1
  52. package/dist/cjs/fetchers/turso.d.ts.map +1 -1
  53. package/dist/cjs/fetchers/turso.js +1 -1
  54. package/dist/cjs/fetchers/turso.js.map +1 -1
  55. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  56. package/dist/cjs/utils.d.ts +47 -0
  57. package/dist/cjs/utils.d.ts.map +1 -1
  58. package/dist/cjs/utils.js +134 -18
  59. package/dist/cjs/utils.js.map +1 -1
  60. package/dist/esm/count-fetchers.d.ts.map +1 -1
  61. package/dist/esm/count-fetchers.js +1 -1
  62. package/dist/esm/count-fetchers.js.map +1 -1
  63. package/dist/esm/fetchers/airtable.d.ts.map +1 -1
  64. package/dist/esm/fetchers/airtable.js +2 -2
  65. package/dist/esm/fetchers/airtable.js.map +1 -1
  66. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -1
  67. package/dist/esm/fetchers/clickhouse.js +2 -2
  68. package/dist/esm/fetchers/clickhouse.js.map +1 -1
  69. package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
  70. package/dist/esm/fetchers/csv-file.js +4 -4
  71. package/dist/esm/fetchers/csv-file.js.map +1 -1
  72. package/dist/esm/fetchers/firestore.d.ts.map +1 -1
  73. package/dist/esm/fetchers/firestore.js +2 -2
  74. package/dist/esm/fetchers/firestore.js.map +1 -1
  75. package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
  76. package/dist/esm/fetchers/google-sheets.js +2 -2
  77. package/dist/esm/fetchers/google-sheets.js.map +1 -1
  78. package/dist/esm/fetchers/javascript.d.ts.map +1 -1
  79. package/dist/esm/fetchers/javascript.js +3 -3
  80. package/dist/esm/fetchers/javascript.js.map +1 -1
  81. package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
  82. package/dist/esm/fetchers/mariadb.js +2 -2
  83. package/dist/esm/fetchers/mariadb.js.map +1 -1
  84. package/dist/esm/fetchers/mongodb.d.ts.map +1 -1
  85. package/dist/esm/fetchers/mongodb.js +2 -2
  86. package/dist/esm/fetchers/mongodb.js.map +1 -1
  87. package/dist/esm/fetchers/mysql.d.ts.map +1 -1
  88. package/dist/esm/fetchers/mysql.js +2 -2
  89. package/dist/esm/fetchers/mysql.js.map +1 -1
  90. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  91. package/dist/esm/fetchers/postgresql.js +2 -2
  92. package/dist/esm/fetchers/postgresql.js.map +1 -1
  93. package/dist/esm/fetchers/redis.d.ts.map +1 -1
  94. package/dist/esm/fetchers/redis.js +2 -2
  95. package/dist/esm/fetchers/redis.js.map +1 -1
  96. package/dist/esm/fetchers/redshift.d.ts.map +1 -1
  97. package/dist/esm/fetchers/redshift.js +3 -3
  98. package/dist/esm/fetchers/redshift.js.map +1 -1
  99. package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
  100. package/dist/esm/fetchers/rest-api.js +12 -4
  101. package/dist/esm/fetchers/rest-api.js.map +1 -1
  102. package/dist/esm/fetchers/supabase.d.ts.map +1 -1
  103. package/dist/esm/fetchers/supabase.js +2 -2
  104. package/dist/esm/fetchers/supabase.js.map +1 -1
  105. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  106. package/dist/esm/fetchers/teleport.js +2 -2
  107. package/dist/esm/fetchers/teleport.js.map +1 -1
  108. package/dist/esm/fetchers/turso.d.ts.map +1 -1
  109. package/dist/esm/fetchers/turso.js +2 -2
  110. package/dist/esm/fetchers/turso.js.map +1 -1
  111. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  112. package/dist/esm/utils.d.ts +47 -0
  113. package/dist/esm/utils.d.ts.map +1 -1
  114. package/dist/esm/utils.js +129 -17
  115. package/dist/esm/utils.js.map +1 -1
  116. package/package.json +5 -5
  117. package/src/count-fetchers.ts +6 -2
  118. package/src/fetchers/airtable.ts +35 -36
  119. package/src/fetchers/clickhouse.ts +26 -36
  120. package/src/fetchers/csv-file.ts +13 -21
  121. package/src/fetchers/firestore.ts +41 -35
  122. package/src/fetchers/google-sheets.ts +15 -23
  123. package/src/fetchers/javascript.ts +22 -25
  124. package/src/fetchers/mariadb.ts +33 -44
  125. package/src/fetchers/mongodb.ts +66 -81
  126. package/src/fetchers/mysql.ts +33 -45
  127. package/src/fetchers/postgresql.ts +52 -62
  128. package/src/fetchers/redis.ts +6 -5
  129. package/src/fetchers/redshift.ts +29 -41
  130. package/src/fetchers/rest-api.ts +29 -22
  131. package/src/fetchers/supabase.ts +104 -41
  132. package/src/fetchers/teleport.ts +53 -64
  133. package/src/fetchers/turso.ts +27 -39
  134. package/src/utils.ts +227 -31
package/src/utils.ts CHANGED
@@ -566,6 +566,45 @@ export const generateSafeJSONParseCode = (): string => {
566
566
  }`
567
567
  }
568
568
 
569
+ /**
570
+ * Runtime helper that narrows a fetched payload down to the array of records.
571
+ *
572
+ * Emitted into the REST API and JavaScript fetchers, which are the only two
573
+ * whose payload shape is whatever the remote end decided. `collectionPath`
574
+ * arrives as a request param — the JSON-encoded array the UIDL carries
575
+ * (`["results"]`), or an already-parsed array, or not at all. Only a details
576
+ * page sends one; every other caller gets the payload untouched.
577
+ *
578
+ * Returns the payload UNCHANGED when there is no path, and also when the path
579
+ * does not lead to an array — a source whose response shape drifted then keeps
580
+ * behaving exactly as it did before the path was introduced, instead of
581
+ * collapsing to an empty result the caller cannot explain.
582
+ */
583
+ export const generateCollectionPathHelperCode = (): string => {
584
+ return `const resolveCollectionPath = (payload, rawPath) => {
585
+ if (!rawPath) return payload
586
+ let path = rawPath
587
+ if (typeof path === 'string') {
588
+ try {
589
+ path = JSON.parse(path)
590
+ } catch {
591
+ path = path.split('.').filter(Boolean)
592
+ }
593
+ }
594
+ if (!Array.isArray(path) || path.length === 0) return payload
595
+
596
+ let current = payload
597
+ for (const segment of path) {
598
+ if (!current || typeof current !== 'object' || Array.isArray(current) || !(segment in current)) {
599
+ return payload
600
+ }
601
+ current = current[segment]
602
+ }
603
+
604
+ return Array.isArray(current) ? current : payload
605
+ }`
606
+ }
607
+
569
608
  export const generateDateFormatterCode = (): string => {
570
609
  return `const formatDateValue = (date) => {
571
610
  const options = {
@@ -596,6 +635,146 @@ const dateReplacer = (key, value) => {
596
635
  }`
597
636
  }
598
637
 
638
+ /**
639
+ * Every leaf condition of a filters array, unwrapping any `{ type: 'group' }`
640
+ * envelopes the inspector wrapped them in. Generator-side checks that ask
641
+ * "does any destination reference state / a route param?" must look through
642
+ * groups — asking `entry.destination` of a group silently answers "no", which
643
+ * is how a dynamic filter ends up baked into initialData at build time.
644
+ */
645
+ export const collectFilterLeafConditions = (entries: unknown): unknown[] => {
646
+ if (!Array.isArray(entries)) {
647
+ return []
648
+ }
649
+ const leaves: unknown[] = []
650
+ const walk = (entry: unknown): void => {
651
+ if (!entry || typeof entry !== 'object') {
652
+ return
653
+ }
654
+ const node = entry as { type?: string; children?: unknown[] }
655
+ if (node.type === 'group') {
656
+ ;(node.children || []).forEach(walk)
657
+ return
658
+ }
659
+ leaves.push(entry)
660
+ }
661
+ entries.forEach(walk)
662
+ return leaves
663
+ }
664
+
665
+ /**
666
+ * Rebuilds a filters array keeping its group structure, dropping leaf
667
+ * conditions that `keepCondition` rejects and any group left empty by that.
668
+ * Used so the SSG emit can discard unresolvable dynamic destinations without
669
+ * flattening an `or` group into ANDs on the way.
670
+ */
671
+ export const pruneFilterTree = (
672
+ entries: unknown[],
673
+ keepCondition: (condition: unknown) => boolean
674
+ ): unknown[] => {
675
+ const prune = (entry: unknown): unknown | null => {
676
+ if (!entry || typeof entry !== 'object') {
677
+ return null
678
+ }
679
+ const node = entry as { type?: string; children?: unknown[] }
680
+ if (node.type === 'group') {
681
+ const children = (node.children || []).map(prune).filter(Boolean)
682
+ if (children.length === 0) {
683
+ return null
684
+ }
685
+ return { ...node, children }
686
+ }
687
+ return keepCondition(entry) ? entry : null
688
+ }
689
+ return entries.map(prune).filter(Boolean) as unknown[]
690
+ }
691
+
692
+ /**
693
+ * Runtime helpers for the filter TREE the GUI authors.
694
+ *
695
+ * The inspector emits filters as a tree — a root `{ type: 'group', operator,
696
+ * children }` whose children are `{ type: 'condition', source, destination,
697
+ * operand }` leaves or further groups (an `or` group nested inside the root
698
+ * `and` is how "A and (B or C)" is expressed). Older projects, and params built
699
+ * by hand, still send a flat array of bare conditions or a plain `{ key: value }`
700
+ * map.
701
+ *
702
+ * Every fetcher used to hand-roll `parsedFilters.forEach(f => { if (!f.source)
703
+ * return ... })`, which silently DROPPED any group — a group has no `.source`,
704
+ * so the whole filter became a no-op and the page rendered unfiltered rows.
705
+ * These helpers exist so no fetcher has to know the shape: `normalizeFilterTree`
706
+ * turns all three input forms into one tree, and the walkers below consume it.
707
+ */
708
+ export const generateFilterTreeHelpersCode = (): string => {
709
+ return `const normalizeFilterTree = (parsed) => {
710
+ if (parsed === null || parsed === undefined) return null
711
+
712
+ const toNode = (entry) => {
713
+ if (!entry || typeof entry !== 'object') return null
714
+ if (entry.type === 'group') {
715
+ const children = Array.isArray(entry.children)
716
+ ? entry.children.map(toNode).filter(Boolean)
717
+ : []
718
+ if (children.length === 0) return null
719
+ return { type: 'group', operator: entry.operator === 'or' ? 'or' : 'and', children }
720
+ }
721
+ if (!entry.source || entry.destination === undefined) return null
722
+ return {
723
+ type: 'condition',
724
+ source: entry.source,
725
+ destination: entry.destination,
726
+ operand: entry.operand || '=',
727
+ }
728
+ }
729
+
730
+ const children = Array.isArray(parsed)
731
+ ? parsed.map(toNode).filter(Boolean)
732
+ : Object.entries(parsed).map(([source, destination]) =>
733
+ toNode({ source, destination, operand: '=' })
734
+ ).filter(Boolean)
735
+
736
+ if (children.length === 0) return null
737
+ // A single root group is already the tree; wrapping it in another and-group
738
+ // would only add a redundant level.
739
+ if (children.length === 1 && children[0].type === 'group') return children[0]
740
+ return { type: 'group', operator: 'and', children }
741
+ }
742
+
743
+ const evaluateFilterTree = (node, testCondition) => {
744
+ if (!node) return true
745
+ if (node.type === 'group') {
746
+ if (node.children.length === 0) return true
747
+ return node.operator === 'or'
748
+ ? node.children.some((child) => evaluateFilterTree(child, testCondition))
749
+ : node.children.every((child) => evaluateFilterTree(child, testCondition))
750
+ }
751
+ return testCondition(node)
752
+ }
753
+
754
+ const buildFilterTreeClause = (node, buildCondition) => {
755
+ if (!node) return null
756
+ if (node.type === 'group') {
757
+ const parts = []
758
+ for (const child of node.children) {
759
+ const clause = buildFilterTreeClause(child, buildCondition)
760
+ if (clause) parts.push(clause)
761
+ }
762
+ if (parts.length === 0) return null
763
+ if (parts.length === 1) return parts[0]
764
+ return '(' + parts.join(node.operator === 'or' ? ' OR ' : ' AND ') + ')'
765
+ }
766
+ return buildCondition(node)
767
+ }
768
+
769
+ const flattenFilterConditions = (node) => {
770
+ if (!node) return []
771
+ if (node.type === 'group') {
772
+ return node.children.reduce((acc, child) => acc.concat(flattenFilterConditions(child)), [])
773
+ }
774
+ return [node]
775
+ }`
776
+ }
777
+
599
778
  export const generateSortFilterHelperCode = (): string => {
600
779
  return `function getNestedValue(obj, path) {
601
780
  if (!obj || typeof obj !== 'object') return undefined
@@ -801,13 +980,14 @@ export const hasUnresolvableDynamicParams = (
801
980
  }
802
981
 
803
982
  if (param.type === 'static' && Array.isArray(param.content)) {
804
- const hasDynamicDestinations = param.content.some(
983
+ const leafConditions = collectFilterLeafConditions(param.content)
984
+ const hasDynamicDestinations = leafConditions.some(
805
985
  (item: any) =>
806
986
  item && typeof item === 'object' && ASTUtils.isUIDLDynamicReference(item?.destination)
807
987
  )
808
988
 
809
989
  if (hasDynamicDestinations && dynamicRouteAttr) {
810
- const allResolvable = param.content.every((item: any) => {
990
+ const allResolvable = leafConditions.every((item: any) => {
811
991
  if (!item || typeof item !== 'object') {
812
992
  return true
813
993
  }
@@ -1165,9 +1345,9 @@ export const extractDataSourceIntoGetStaticProps = (
1165
1345
  // tslint:disable-next-line:no-any
1166
1346
  const resourceParamsForFilters = (node.content as any).resource?.params || {}
1167
1347
  const nodeFilters = resourceParamsForFilters.filters?.content || []
1168
- const hasDynamicFilterDestinations =
1169
- Array.isArray(nodeFilters) &&
1170
- nodeFilters.some((f: any) => ASTUtils.isUIDLDynamicReference(f?.destination))
1348
+ const hasDynamicFilterDestinations = collectFilterLeafConditions(nodeFilters).some((f: any) =>
1349
+ ASTUtils.isUIDLDynamicReference(f?.destination)
1350
+ )
1171
1351
 
1172
1352
  // Update all target JSX nodes with initialData (only those that don't already have it)
1173
1353
  for (const jsxNode of nodesToUpdate) {
@@ -1291,9 +1471,12 @@ export const extractDataSourceIntoGetStaticProps = (
1291
1471
  if (key === 'sorts' || key === 'filters' || key === 'queryColumns') {
1292
1472
  let itemsToProcess = validItems
1293
1473
  if (key === 'filters') {
1294
- // Keep filter items that are either static or route-resolvable (via dynamicRouteAttr)
1295
- // Filter out items with unresolvable dynamic destinations
1296
- itemsToProcess = validItems.filter((item: any) => {
1474
+ // Keep filter conditions that are either static or route-resolvable
1475
+ // (via dynamicRouteAttr) and drop the ones with unresolvable dynamic
1476
+ // destinations. Pruned through the tree so a condition nested in a
1477
+ // group is judged on its own destination, and an `or` group keeps
1478
+ // its shape on the way to the data source.
1479
+ itemsToProcess = pruneFilterTree(validItems, (item: any) => {
1297
1480
  if (!ASTUtils.isUIDLDynamicReference(item?.destination)) {
1298
1481
  return true
1299
1482
  }
@@ -1310,36 +1493,49 @@ export const extractDataSourceIntoGetStaticProps = (
1310
1493
  const hasRouteResolvableFilters =
1311
1494
  key === 'filters' &&
1312
1495
  dynamicRouteAttr &&
1313
- itemsToProcess.some(
1496
+ collectFilterLeafConditions(itemsToProcess).some(
1314
1497
  (item: any) =>
1315
1498
  item &&
1316
1499
  typeof item === 'object' &&
1317
1500
  ASTUtils.isUIDLDynamicReference(item?.destination)
1318
1501
  )
1319
1502
 
1320
- const arrayExpr = types.arrayExpression(
1321
- itemsToProcess.map((item: any) => {
1322
- if (typeof item === 'string') {
1323
- return types.stringLiteral(item)
1324
- }
1325
- if (typeof item === 'number') {
1326
- return types.numericLiteral(item)
1327
- }
1328
- if (typeof item === 'boolean') {
1329
- return types.booleanLiteral(item)
1330
- }
1331
- if (typeof item === 'object' && item !== null) {
1332
- if (
1333
- hasRouteResolvableFilters &&
1334
- ASTUtils.isUIDLDynamicReference(item?.destination)
1335
- ) {
1336
- return buildFilterObjectAST(item, dynamicRouteAttr)
1337
- }
1338
- return ASTUtils.objectToObjectExpression(item)
1339
- }
1503
+ // Recurses through groups so a route-resolvable destination nested
1504
+ // inside one still gets its `context.params.*` AST instead of being
1505
+ // emitted as a raw UIDL dynamic-reference object.
1506
+ const buildFilterEntryAST = (item: any): types.Expression => {
1507
+ if (typeof item === 'string') {
1508
+ return types.stringLiteral(item)
1509
+ }
1510
+ if (typeof item === 'number') {
1511
+ return types.numericLiteral(item)
1512
+ }
1513
+ if (typeof item === 'boolean') {
1514
+ return types.booleanLiteral(item)
1515
+ }
1516
+ if (typeof item !== 'object' || item === null) {
1340
1517
  return types.nullLiteral()
1341
- })
1342
- )
1518
+ }
1519
+ if (item.type === 'group' && Array.isArray(item.children)) {
1520
+ return types.objectExpression([
1521
+ types.objectProperty(types.identifier('type'), types.stringLiteral('group')),
1522
+ types.objectProperty(
1523
+ types.identifier('operator'),
1524
+ types.stringLiteral(item.operator === 'or' ? 'or' : 'and')
1525
+ ),
1526
+ types.objectProperty(
1527
+ types.identifier('children'),
1528
+ types.arrayExpression(item.children.map(buildFilterEntryAST))
1529
+ ),
1530
+ ])
1531
+ }
1532
+ if (hasRouteResolvableFilters && ASTUtils.isUIDLDynamicReference(item?.destination)) {
1533
+ return buildFilterObjectAST(item, dynamicRouteAttr)
1534
+ }
1535
+ return ASTUtils.objectToObjectExpression(item)
1536
+ }
1537
+
1538
+ const arrayExpr = types.arrayExpression(itemsToProcess.map(buildFilterEntryAST))
1343
1539
  // Wrap in JSON.stringify()
1344
1540
  astValue = types.callExpression(
1345
1541
  types.memberExpression(types.identifier('JSON'), types.identifier('stringify')),