@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.
Files changed (240) hide show
  1. package/ARRAY_MAPPER_PAGINATION.md +1128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +40 -0
  4. package/SEARCH_IMPLEMENTATION_SUMMARY.md +983 -0
  5. package/__tests__/fetchers.test.ts +545 -0
  6. package/__tests__/integration.test.ts +561 -0
  7. package/__tests__/mocks.ts +241 -0
  8. package/__tests__/pagination.test.ts +31 -0
  9. package/__tests__/plugin.test.ts +577 -0
  10. package/__tests__/utils.test.ts +430 -0
  11. package/__tests__/validation.test.ts +348 -0
  12. package/dist/cjs/array-mapper-pagination.d.ts +32 -0
  13. package/dist/cjs/array-mapper-pagination.d.ts.map +1 -0
  14. package/dist/cjs/array-mapper-pagination.js +77 -0
  15. package/dist/cjs/array-mapper-pagination.js.map +1 -0
  16. package/dist/cjs/count-fetchers.d.ts +12 -0
  17. package/dist/cjs/count-fetchers.d.ts.map +1 -0
  18. package/dist/cjs/count-fetchers.js +46 -0
  19. package/dist/cjs/count-fetchers.js.map +1 -0
  20. package/dist/cjs/data-source-fetchers.d.ts +14 -0
  21. package/dist/cjs/data-source-fetchers.d.ts.map +1 -0
  22. package/dist/cjs/data-source-fetchers.js +185 -0
  23. package/dist/cjs/data-source-fetchers.js.map +1 -0
  24. package/dist/cjs/fetchers/airtable.d.ts +6 -0
  25. package/dist/cjs/fetchers/airtable.d.ts.map +1 -0
  26. package/dist/cjs/fetchers/airtable.js +27 -0
  27. package/dist/cjs/fetchers/airtable.js.map +1 -0
  28. package/dist/cjs/fetchers/clickhouse.d.ts +6 -0
  29. package/dist/cjs/fetchers/clickhouse.d.ts.map +1 -0
  30. package/dist/cjs/fetchers/clickhouse.js +29 -0
  31. package/dist/cjs/fetchers/clickhouse.js.map +1 -0
  32. package/dist/cjs/fetchers/csv-file.d.ts +7 -0
  33. package/dist/cjs/fetchers/csv-file.d.ts.map +1 -0
  34. package/dist/cjs/fetchers/csv-file.js +36 -0
  35. package/dist/cjs/fetchers/csv-file.js.map +1 -0
  36. package/dist/cjs/fetchers/firestore.d.ts +6 -0
  37. package/dist/cjs/fetchers/firestore.d.ts.map +1 -0
  38. package/dist/cjs/fetchers/firestore.js +35 -0
  39. package/dist/cjs/fetchers/firestore.js.map +1 -0
  40. package/dist/cjs/fetchers/google-sheets.d.ts +6 -0
  41. package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -0
  42. package/dist/cjs/fetchers/google-sheets.js +30 -0
  43. package/dist/cjs/fetchers/google-sheets.js.map +1 -0
  44. package/dist/cjs/fetchers/index.d.ts +17 -0
  45. package/dist/cjs/fetchers/index.d.ts.map +1 -0
  46. package/dist/cjs/fetchers/index.js +56 -0
  47. package/dist/cjs/fetchers/index.js.map +1 -0
  48. package/dist/cjs/fetchers/javascript.d.ts +7 -0
  49. package/dist/cjs/fetchers/javascript.d.ts.map +1 -0
  50. package/dist/cjs/fetchers/javascript.js +40 -0
  51. package/dist/cjs/fetchers/javascript.js.map +1 -0
  52. package/dist/cjs/fetchers/mariadb.d.ts +3 -0
  53. package/dist/cjs/fetchers/mariadb.d.ts.map +1 -0
  54. package/dist/cjs/fetchers/mariadb.js +23 -0
  55. package/dist/cjs/fetchers/mariadb.js.map +1 -0
  56. package/dist/cjs/fetchers/mongodb.d.ts +7 -0
  57. package/dist/cjs/fetchers/mongodb.d.ts.map +1 -0
  58. package/dist/cjs/fetchers/mongodb.js +52 -0
  59. package/dist/cjs/fetchers/mongodb.js.map +1 -0
  60. package/dist/cjs/fetchers/mysql.d.ts +3 -0
  61. package/dist/cjs/fetchers/mysql.d.ts.map +1 -0
  62. package/dist/cjs/fetchers/mysql.js +30 -0
  63. package/dist/cjs/fetchers/mysql.js.map +1 -0
  64. package/dist/cjs/fetchers/postgresql.d.ts +3 -0
  65. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -0
  66. package/dist/cjs/fetchers/postgresql.js +25 -0
  67. package/dist/cjs/fetchers/postgresql.js.map +1 -0
  68. package/dist/cjs/fetchers/redis.d.ts +6 -0
  69. package/dist/cjs/fetchers/redis.d.ts.map +1 -0
  70. package/dist/cjs/fetchers/redis.js +46 -0
  71. package/dist/cjs/fetchers/redis.js.map +1 -0
  72. package/dist/cjs/fetchers/redshift.d.ts +2 -0
  73. package/dist/cjs/fetchers/redshift.d.ts.map +1 -0
  74. package/dist/cjs/fetchers/redshift.js +24 -0
  75. package/dist/cjs/fetchers/redshift.js.map +1 -0
  76. package/dist/cjs/fetchers/rest-api.d.ts +6 -0
  77. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -0
  78. package/dist/cjs/fetchers/rest-api.js +58 -0
  79. package/dist/cjs/fetchers/rest-api.js.map +1 -0
  80. package/dist/cjs/fetchers/static-collection.d.ts +7 -0
  81. package/dist/cjs/fetchers/static-collection.d.ts.map +1 -0
  82. package/dist/cjs/fetchers/static-collection.js +24 -0
  83. package/dist/cjs/fetchers/static-collection.js.map +1 -0
  84. package/dist/cjs/fetchers/supabase.d.ts +7 -0
  85. package/dist/cjs/fetchers/supabase.d.ts.map +1 -0
  86. package/dist/cjs/fetchers/supabase.js +42 -0
  87. package/dist/cjs/fetchers/supabase.js.map +1 -0
  88. package/dist/cjs/fetchers/turso.d.ts +6 -0
  89. package/dist/cjs/fetchers/turso.d.ts.map +1 -0
  90. package/dist/cjs/fetchers/turso.js +25 -0
  91. package/dist/cjs/fetchers/turso.js.map +1 -0
  92. package/dist/cjs/index.d.ts +9 -0
  93. package/dist/cjs/index.d.ts.map +1 -0
  94. package/dist/cjs/index.js +325 -0
  95. package/dist/cjs/index.js.map +1 -0
  96. package/dist/cjs/pagination-plugin.d.ts +5 -0
  97. package/dist/cjs/pagination-plugin.d.ts.map +1 -0
  98. package/dist/cjs/pagination-plugin.js +1484 -0
  99. package/dist/cjs/pagination-plugin.js.map +1 -0
  100. package/dist/cjs/pagination-with-count.d.ts +6 -0
  101. package/dist/cjs/pagination-with-count.d.ts.map +1 -0
  102. package/dist/cjs/pagination-with-count.js +63 -0
  103. package/dist/cjs/pagination-with-count.js.map +1 -0
  104. package/dist/cjs/tsconfig.tsbuildinfo +1 -0
  105. package/dist/cjs/utils.d.ts +31 -0
  106. package/dist/cjs/utils.d.ts.map +1 -0
  107. package/dist/cjs/utils.js +763 -0
  108. package/dist/cjs/utils.js.map +1 -0
  109. package/dist/cjs/validation.d.ts +5 -0
  110. package/dist/cjs/validation.d.ts.map +1 -0
  111. package/dist/cjs/validation.js +29 -0
  112. package/dist/cjs/validation.js.map +1 -0
  113. package/dist/esm/array-mapper-pagination.d.ts +32 -0
  114. package/dist/esm/array-mapper-pagination.d.ts.map +1 -0
  115. package/dist/esm/array-mapper-pagination.js +72 -0
  116. package/dist/esm/array-mapper-pagination.js.map +1 -0
  117. package/dist/esm/count-fetchers.d.ts +12 -0
  118. package/dist/esm/count-fetchers.d.ts.map +1 -0
  119. package/dist/esm/count-fetchers.js +35 -0
  120. package/dist/esm/count-fetchers.js.map +1 -0
  121. package/dist/esm/data-source-fetchers.d.ts +14 -0
  122. package/dist/esm/data-source-fetchers.d.ts.map +1 -0
  123. package/dist/esm/data-source-fetchers.js +179 -0
  124. package/dist/esm/data-source-fetchers.js.map +1 -0
  125. package/dist/esm/fetchers/airtable.d.ts +6 -0
  126. package/dist/esm/fetchers/airtable.d.ts.map +1 -0
  127. package/dist/esm/fetchers/airtable.js +22 -0
  128. package/dist/esm/fetchers/airtable.js.map +1 -0
  129. package/dist/esm/fetchers/clickhouse.d.ts +6 -0
  130. package/dist/esm/fetchers/clickhouse.d.ts.map +1 -0
  131. package/dist/esm/fetchers/clickhouse.js +24 -0
  132. package/dist/esm/fetchers/clickhouse.js.map +1 -0
  133. package/dist/esm/fetchers/csv-file.d.ts +7 -0
  134. package/dist/esm/fetchers/csv-file.d.ts.map +1 -0
  135. package/dist/esm/fetchers/csv-file.js +30 -0
  136. package/dist/esm/fetchers/csv-file.js.map +1 -0
  137. package/dist/esm/fetchers/firestore.d.ts +6 -0
  138. package/dist/esm/fetchers/firestore.d.ts.map +1 -0
  139. package/dist/esm/fetchers/firestore.js +30 -0
  140. package/dist/esm/fetchers/firestore.js.map +1 -0
  141. package/dist/esm/fetchers/google-sheets.d.ts +6 -0
  142. package/dist/esm/fetchers/google-sheets.d.ts.map +1 -0
  143. package/dist/esm/fetchers/google-sheets.js +25 -0
  144. package/dist/esm/fetchers/google-sheets.js.map +1 -0
  145. package/dist/esm/fetchers/index.d.ts +17 -0
  146. package/dist/esm/fetchers/index.d.ts.map +1 -0
  147. package/dist/esm/fetchers/index.js +17 -0
  148. package/dist/esm/fetchers/index.js.map +1 -0
  149. package/dist/esm/fetchers/javascript.d.ts +7 -0
  150. package/dist/esm/fetchers/javascript.d.ts.map +1 -0
  151. package/dist/esm/fetchers/javascript.js +34 -0
  152. package/dist/esm/fetchers/javascript.js.map +1 -0
  153. package/dist/esm/fetchers/mariadb.d.ts +3 -0
  154. package/dist/esm/fetchers/mariadb.d.ts.map +1 -0
  155. package/dist/esm/fetchers/mariadb.js +18 -0
  156. package/dist/esm/fetchers/mariadb.js.map +1 -0
  157. package/dist/esm/fetchers/mongodb.d.ts +7 -0
  158. package/dist/esm/fetchers/mongodb.d.ts.map +1 -0
  159. package/dist/esm/fetchers/mongodb.js +46 -0
  160. package/dist/esm/fetchers/mongodb.js.map +1 -0
  161. package/dist/esm/fetchers/mysql.d.ts +3 -0
  162. package/dist/esm/fetchers/mysql.d.ts.map +1 -0
  163. package/dist/esm/fetchers/mysql.js +25 -0
  164. package/dist/esm/fetchers/mysql.js.map +1 -0
  165. package/dist/esm/fetchers/postgresql.d.ts +3 -0
  166. package/dist/esm/fetchers/postgresql.d.ts.map +1 -0
  167. package/dist/esm/fetchers/postgresql.js +20 -0
  168. package/dist/esm/fetchers/postgresql.js.map +1 -0
  169. package/dist/esm/fetchers/redis.d.ts +6 -0
  170. package/dist/esm/fetchers/redis.d.ts.map +1 -0
  171. package/dist/esm/fetchers/redis.js +41 -0
  172. package/dist/esm/fetchers/redis.js.map +1 -0
  173. package/dist/esm/fetchers/redshift.d.ts +2 -0
  174. package/dist/esm/fetchers/redshift.d.ts.map +1 -0
  175. package/dist/esm/fetchers/redshift.js +20 -0
  176. package/dist/esm/fetchers/redshift.js.map +1 -0
  177. package/dist/esm/fetchers/rest-api.d.ts +6 -0
  178. package/dist/esm/fetchers/rest-api.d.ts.map +1 -0
  179. package/dist/esm/fetchers/rest-api.js +53 -0
  180. package/dist/esm/fetchers/rest-api.js.map +1 -0
  181. package/dist/esm/fetchers/static-collection.d.ts +7 -0
  182. package/dist/esm/fetchers/static-collection.d.ts.map +1 -0
  183. package/dist/esm/fetchers/static-collection.js +18 -0
  184. package/dist/esm/fetchers/static-collection.js.map +1 -0
  185. package/dist/esm/fetchers/supabase.d.ts +7 -0
  186. package/dist/esm/fetchers/supabase.d.ts.map +1 -0
  187. package/dist/esm/fetchers/supabase.js +36 -0
  188. package/dist/esm/fetchers/supabase.js.map +1 -0
  189. package/dist/esm/fetchers/turso.d.ts +6 -0
  190. package/dist/esm/fetchers/turso.d.ts.map +1 -0
  191. package/dist/esm/fetchers/turso.js +20 -0
  192. package/dist/esm/fetchers/turso.js.map +1 -0
  193. package/dist/esm/index.d.ts +9 -0
  194. package/dist/esm/index.d.ts.map +1 -0
  195. package/dist/esm/index.js +306 -0
  196. package/dist/esm/index.js.map +1 -0
  197. package/dist/esm/pagination-plugin.d.ts +5 -0
  198. package/dist/esm/pagination-plugin.d.ts.map +1 -0
  199. package/dist/esm/pagination-plugin.js +1457 -0
  200. package/dist/esm/pagination-plugin.js.map +1 -0
  201. package/dist/esm/pagination-with-count.d.ts +6 -0
  202. package/dist/esm/pagination-with-count.d.ts.map +1 -0
  203. package/dist/esm/pagination-with-count.js +34 -0
  204. package/dist/esm/pagination-with-count.js.map +1 -0
  205. package/dist/esm/tsconfig.tsbuildinfo +1 -0
  206. package/dist/esm/utils.d.ts +31 -0
  207. package/dist/esm/utils.d.ts.map +1 -0
  208. package/dist/esm/utils.js +722 -0
  209. package/dist/esm/utils.js.map +1 -0
  210. package/dist/esm/validation.d.ts +5 -0
  211. package/dist/esm/validation.d.ts.map +1 -0
  212. package/dist/esm/validation.js +25 -0
  213. package/dist/esm/validation.js.map +1 -0
  214. package/package.json +33 -0
  215. package/src/array-mapper-pagination.ts +113 -0
  216. package/src/count-fetchers.ts +99 -0
  217. package/src/data-source-fetchers.ts +313 -0
  218. package/src/fetchers/airtable.ts +153 -0
  219. package/src/fetchers/clickhouse.ts +127 -0
  220. package/src/fetchers/csv-file.ts +163 -0
  221. package/src/fetchers/firestore.ts +138 -0
  222. package/src/fetchers/google-sheets.ts +189 -0
  223. package/src/fetchers/index.ts +32 -0
  224. package/src/fetchers/javascript.ts +150 -0
  225. package/src/fetchers/mariadb.ts +230 -0
  226. package/src/fetchers/mongodb.ts +239 -0
  227. package/src/fetchers/mysql.ts +237 -0
  228. package/src/fetchers/postgresql.ts +247 -0
  229. package/src/fetchers/redis.ts +152 -0
  230. package/src/fetchers/redshift.ts +138 -0
  231. package/src/fetchers/rest-api.ts +148 -0
  232. package/src/fetchers/static-collection.ts +149 -0
  233. package/src/fetchers/supabase.ts +246 -0
  234. package/src/fetchers/turso.ts +131 -0
  235. package/src/index.ts +352 -0
  236. package/src/pagination-plugin.ts +2335 -0
  237. package/src/pagination-with-count.ts +89 -0
  238. package/src/utils.ts +1013 -0
  239. package/src/validation.ts +32 -0
  240. package/tsconfig.json +9 -0
@@ -0,0 +1,1128 @@
1
+ # Array Mapper Pagination - Complete Implementation Guide
2
+
3
+ ## Table of Contents
4
+ 1. [Overview](#overview)
5
+ 2. [Architecture & Design Decisions](#architecture--design-decisions)
6
+ 3. [Critical Implementation Details](#critical-implementation-details)
7
+ 4. [Problems Encountered & Solutions](#problems-encountered--solutions)
8
+ 5. [UIDL Structure](#uidl-structure)
9
+ 6. [Code Generation Flow](#code-generation-flow)
10
+ 7. [Edge Cases & Special Handling](#edge-cases--special-handling)
11
+ 8. [Testing & Verification](#testing--verification)
12
+ 9. [Future Considerations](#future-considerations)
13
+
14
+ ---
15
+
16
+ ## Overview
17
+
18
+ This document describes the complete implementation of server-side pagination for array mapper nodes in Next.js generated projects. The implementation supports in-memory page state management (not URL-based) and works with all data source types (JavaScript, PostgreSQL, MySQL, MongoDB, Supabase, CockroachDB, CSV, Static Collections).
19
+
20
+ ### Key Features
21
+ - **Server-side pagination**: Each page change triggers a new data fetch with pagination parameters
22
+ - **In-memory state**: Page number stored in React state, not URL
23
+ - **Multiple paginations per page/component**: Supports multiple independent paginated lists
24
+ - **Same data source, different perPage**: Same data source can be used with different pagination settings
25
+ - **Mixed paginated/non-paginated**: Same data source can be used both paginated and non-paginated on the same page
26
+ - **Deduplication**: Count fetches and useEffect calls are deduplicated per data source
27
+
28
+ ---
29
+
30
+ ## Architecture & Design Decisions
31
+
32
+ ### 1. Why Server-Side Pagination?
33
+
34
+ **Initial Mistake**: The first implementation used client-side array slicing (fetching all data then slicing in the browser). This was incorrect.
35
+
36
+ **Correct Approach**:
37
+ - For database sources: Pass `page` and `perPage` to the database query (OFFSET/LIMIT, skip/take)
38
+ - For JavaScript sources: Apply pagination in the API handler before returning data
39
+ - For all sources: Fetch only the data needed for the current page
40
+
41
+ ### 2. Why In-Memory State (Not URL-Based)?
42
+
43
+ The CMS collection node already uses URL-based pagination (`?page=1&perPage=10`). For array mappers within a page, we needed a different approach:
44
+ - Allows multiple independent paginated lists on one page
45
+ - No URL pollution or conflicts
46
+ - Simpler state management
47
+ - Better for nested/dynamic content
48
+
49
+ ### 3. Why Two Separate Implementations (Pages vs Components)?
50
+
51
+ **Pages with `getStaticProps`**:
52
+ - Initial data fetched server-side
53
+ - Total count fetched server-side
54
+ - `maxPages` calculated in `getStaticProps` and passed as props
55
+ - Client-side navigation triggers new fetches via DataProvider
56
+
57
+ **Components (no `getStaticProps`)**:
58
+ - Initial data fetched on mount via DataProvider
59
+ - Total count fetched separately via API route using `useEffect`
60
+ - `maxPages` calculated client-side from count response
61
+ - Each page change triggers DataProvider re-fetch
62
+
63
+ ---
64
+
65
+ ## Critical Implementation Details
66
+
67
+ ### 1. The Plugin Execution Order Problem
68
+
69
+ **Critical Discovery**: UIDL elements are transformed **before** custom plugins run.
70
+
71
+ **The Problem**:
72
+ ```typescript
73
+ // UIDL has this:
74
+ { type: 'cms-pagination-node', ... }
75
+ { type: 'cms-navigation-button', name: 'Previous', ... }
76
+
77
+ // But when our pagination plugin runs, it's already transformed to:
78
+ <div className="home-cms-pagination-node1">
79
+ <div className="home-previous1">Previous</div>
80
+ <div className="home-next1">Next</div>
81
+ </div>
82
+ ```
83
+
84
+ **Solution**: We cannot rely on UIDL node types in the pagination plugin. Instead, we traverse the **generated JSX AST** and identify elements by their **class names**:
85
+ - Pagination containers: `className.includes('cms-pagination-node')`
86
+ - Navigation buttons: `className.includes('previous')` or `className.includes('next')`
87
+
88
+ **Files Involved**:
89
+ - `src/pagination-plugin.ts`: `detectPaginationsFromJSX()` function
90
+
91
+ ### 2. The perPage Extraction Timing Problem
92
+
93
+ **The Problem**: We need to read `perPage` from the UIDL, but it gets stripped during validation or transformation.
94
+
95
+ **First Attempt**: Read `perPage` in the pagination plugin → Failed, already transformed
96
+
97
+ **Second Attempt**: Read `perPage` from transformed nodes → Not available
98
+
99
+ **Third Attempt**: Add `perPage` to UIDL type definitions → Validator stripped it anyway
100
+
101
+ **Solution (Multi-Step)**:
102
+
103
+ #### Step 1: Update UIDL Type Definitions
104
+ ```typescript
105
+ // packages/teleport-types/src/uidl.ts
106
+ export interface UIDLCMSListRepeaterNodeContent {
107
+ // ... existing fields
108
+ paginated?: boolean // NEW
109
+ perPage?: number // NEW
110
+ }
111
+ ```
112
+
113
+ #### Step 2: Update UIDL Validator Schema
114
+ ```typescript
115
+ // packages/teleport-uidl-validator/src/decoders/utils.ts
116
+ export const cmsListRepeaterNodeDecoder = object({
117
+ // ... existing fields
118
+ paginated: optional(boolean()), // NEW
119
+ perPage: optional(number()), // NEW
120
+ })
121
+ ```
122
+
123
+ #### Step 3: Early Extraction (Before Any Transformations)
124
+ ```typescript
125
+ // packages/teleport-plugin-next-data-source/src/index.ts
126
+ function extractPerPageValuesEarly(uidlNode: any): Map<string, number> {
127
+ // This runs BEFORE any transformations
128
+ // Traverses raw UIDL and extracts perPage values
129
+ // Stores them in a Map keyed by renderPropIdentifier
130
+ }
131
+ ```
132
+
133
+ **Why renderPropIdentifier**: Initially keyed by data source identifier, but this caused issues when the same data source was used multiple times with different `perPage` values. The `renderPropIdentifier` is unique per array mapper instance.
134
+
135
+ **Traversal Complexity**: The UIDL structure is deeply nested and inconsistent:
136
+ ```typescript
137
+ // Must traverse ALL of these structures:
138
+ node.content.children[] // Regular children
139
+ node.content.node // Single child
140
+ node.content.nodes.success // DataProvider success state
141
+ node.content.nodes.error // DataProvider error state
142
+ node.content.nodes.loading // DataProvider loading state
143
+ node.content.nodes.list // cms-list-repeater list state
144
+ node.content.nodes.empty // cms-list-repeater empty state
145
+ node.children[] // Direct children array
146
+ // Conditional nodes have different structures too
147
+ ```
148
+
149
+ ### 3. The DataProvider Fetch Problem
150
+
151
+ **The Problem**: When we set `initialData` on DataProvider, the `fetchData` function never gets called, even when params change.
152
+
153
+ **Why**: DataProvider has an internal ref `passFetchBecauseWeHaveInitialData`:
154
+ ```javascript
155
+ // Inside DataProvider implementation
156
+ const passFetchBecauseWeHaveInitialData = useRef(false)
157
+
158
+ useEffect(() => {
159
+ if (initialData) {
160
+ passFetchBecauseWeHaveInitialData.current = true
161
+ return
162
+ }
163
+ // ... fetch logic
164
+ }, [initialData, params])
165
+ ```
166
+
167
+ Once `initialData` is provided, this ref is set to `true` and never resets when `params` change.
168
+
169
+ **Solution (Three Parts)**:
170
+
171
+ #### Part 1: Conditional initialData
172
+ ```javascript
173
+ // Only provide initialData for page 1
174
+ initialData={pagination_pg_0_page === 1 ? props?.dataSource_pg_0 : undefined}
175
+ ```
176
+
177
+ #### Part 2: Add Key Prop to Force Remount
178
+ ```javascript
179
+ // Force React to remount DataProvider when page changes
180
+ key={`dataSource-page-${pagination_pg_0_page}`}
181
+ ```
182
+
183
+ #### Part 3: Ensure fetchData Accepts and Uses Params
184
+ ```javascript
185
+ fetchData={(params) =>
186
+ fetch(`/api/data-source?${new URLSearchParams(params).toString()}`)
187
+ .then((res) => res.json())
188
+ }
189
+ ```
190
+
191
+ ### 4. The Multiple DataProvider Instance Problem
192
+
193
+ **The Problem**: When the same data source is used multiple times (some paginated, some not), `getStaticProps` had only one fetch per data source. This meant:
194
+ ```javascript
195
+ // In getStaticProps (WRONG):
196
+ const [test_users_data] = await Promise.all([
197
+ fetchData({ page: 1, perPage: 10 }) // Only fetches 10 items
198
+ ])
199
+
200
+ // Then in the component:
201
+ // Paginated DataProvider - works, gets items 0-9
202
+ <DataProvider name="test_users_data" initialData={props.test_users_data}>
203
+ {test_users_data?.[0]} // ✅ Works
204
+ </DataProvider>
205
+
206
+ // Non-paginated DataProvider - BROKEN
207
+ <DataProvider name="test_users_data" initialData={props.test_users_data}>
208
+ {test_users_data?.[1]} // ❌ undefined! Only 10 items fetched
209
+ </DataProvider>
210
+ ```
211
+
212
+ **Solution**: Generate separate `fetchData` calls for each DataProvider instance in `getStaticProps`:
213
+ ```javascript
214
+ const [
215
+ test_users_data, // Full data, no params
216
+ test_users_data_pg_0, // Paginated data, page 1, perPage 10
217
+ test_users_data_pg_1, // Paginated data, page 1, perPage 5
218
+ ] = await Promise.all([
219
+ fetchData(), // For non-paginated DataProvider
220
+ fetchData({ page: 1, perPage: 10 }), // For first paginated DataProvider
221
+ fetchData({ page: 1, perPage: 5 }), // For second paginated DataProvider
222
+ ])
223
+ ```
224
+
225
+ **Implementation Detail**: We traverse the JSX AST to identify all DataProvider instances, not just unique data sources.
226
+
227
+ ### 5. The fetchData Creation Problem
228
+
229
+ **The Problem**: When a DataProvider is static (data source only used in `getStaticProps`), it doesn't get a `fetchData` prop. But pagination requires `fetchData` for page changes.
230
+
231
+ **Solution**: If `fetchData` doesn't exist on a paginated DataProvider, we create it:
232
+
233
+ ```typescript
234
+ // src/pagination-plugin.ts - addPaginationParamsToDataProvider()
235
+ if (!existingFetchDataAttr) {
236
+ // DataProvider doesn't have fetchData, create it
237
+ const resourceDefAttr = dataProviderJSX.openingElement.attributes.find(
238
+ (attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'resourceDefinition'
239
+ )
240
+
241
+ if (resourceDefAttr) {
242
+ const resourceDef = // ... extract from AST
243
+ const apiPath = `/api/${dataSourceType}-${tableName}-${dataSourceId.substring(0, 8)}`
244
+
245
+ const newFetchData = types.arrowFunctionExpression(
246
+ [types.identifier('params')],
247
+ // ... create fetch call with params
248
+ )
249
+
250
+ dataProviderJSX.openingElement.attributes.push(
251
+ types.jsxAttribute(
252
+ types.jsxIdentifier('fetchData'),
253
+ types.jsxExpressionContainer(newFetchData)
254
+ )
255
+ )
256
+ }
257
+ }
258
+ ```
259
+
260
+ **Also Required**: Ensure API routes are generated by explicitly calling `extractDataSourceIntoNextAPIFolder()` for paginated data sources, even if they're static.
261
+
262
+ ### 6. The Count Fetching Architecture
263
+
264
+ **Wrong Approach**: Return `total` count from the main data handler:
265
+ ```javascript
266
+ // WRONG - couples data fetching with count
267
+ return {
268
+ success: true,
269
+ data: results,
270
+ total: 1000 // ❌ Don't do this
271
+ }
272
+ ```
273
+
274
+ **Correct Approach**: Separate `getCount` handler:
275
+
276
+ ```typescript
277
+ // utils/data-sources/data-source-name.js
278
+ export async function handler(req, res) {
279
+ // Fetch data only
280
+ return res.status(200).json({
281
+ success: true,
282
+ data: results
283
+ })
284
+ }
285
+
286
+ export async function getCount(req, res) {
287
+ // Fetch count only, handle filters/queries
288
+ const { query, queryColumns, filters } = req.query
289
+ // ... apply same filters as handler
290
+ return res.status(200).json({
291
+ success: true,
292
+ count: total
293
+ })
294
+ }
295
+ ```
296
+
297
+ **Why Separate**:
298
+ - Clean separation of concerns
299
+ - Can fetch count without data
300
+ - Easier to optimize queries (COUNT vs SELECT)
301
+ - Same handler signature for consistency
302
+
303
+ **Wrapper Functions**:
304
+ ```typescript
305
+ // Also in utils/data-sources/data-source-name.js
306
+ async function fetchData(params = {}) {
307
+ // Wrapper for server-side calls, simulates req/res
308
+ const req = { query: params, method: 'GET' }
309
+ const res = { /* mock res */ }
310
+ await handler(req, res)
311
+ return result.data
312
+ }
313
+
314
+ async function fetchCount(params = {}) {
315
+ // Wrapper for server-side calls
316
+ const req = { query: params, method: 'GET' }
317
+ const res = { /* mock res */ }
318
+ await getCount(req, res)
319
+ return result.count
320
+ }
321
+
322
+ export { fetchData, fetchCount, handler, getCount }
323
+ ```
324
+
325
+ **Count API Routes**: For components, create separate count endpoints:
326
+ ```javascript
327
+ // pages/api/data-source-name-count.js
328
+ import dataSource from '../../utils/data-sources/data-source-name'
329
+ export default dataSource.getCount
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Problems Encountered & Solutions
335
+
336
+ ### Problem 1: getStaticProps Parsing Complexity
337
+
338
+ **Issue**: Need to inject `fetchCount()` calls into existing `Promise.all` in `getStaticProps`.
339
+
340
+ **Complexity**:
341
+ ```typescript
342
+ // Original code structure:
343
+ const [data1, data2] = await Promise.all([
344
+ import1.fetchData().catch(error => { /* ... */ }),
345
+ import2.fetchData().catch(error => { /* ... */ }),
346
+ ])
347
+ ```
348
+
349
+ **Challenge**: The `CallExpression` is wrapped in `.catch()`, making AST traversal tricky.
350
+
351
+ **Solution**:
352
+ ```typescript
353
+ // Detect fetchData calls even when wrapped
354
+ if (element.callee?.type === 'MemberExpression' &&
355
+ element.callee?.property?.name === 'catch' &&
356
+ element.callee?.object?.type === 'CallExpression') {
357
+ fetchCallExpr = element.callee.object // Unwrap
358
+ }
359
+
360
+ // Now we can access the actual fetchData call
361
+ if (fetchCallExpr.callee?.type === 'MemberExpression' &&
362
+ fetchCallExpr.callee?.property?.name === 'fetchData') {
363
+ const importName = fetchCallExpr.callee.object.name
364
+ // Map import to data source variable
365
+ }
366
+ ```
367
+
368
+ ### Problem 2: Variable Naming Collisions
369
+
370
+ **Issue**: When same data source used multiple times, generated this:
371
+ ```javascript
372
+ const [
373
+ test_users_data,
374
+ test_users_data_count, // For pagination 1
375
+ test_users_data_count, // For pagination 2 - COLLISION!
376
+ ] = await Promise.all([...])
377
+ ```
378
+
379
+ **Solution**:
380
+ - **Count variables**: Deduplicate by data source (only one count needed per source)
381
+ - **Data variables**: Unique per pagination instance: `${dataSource}_pg_${index}`
382
+ - **maxPages variables**: Unique per pagination instance: `${dataSource}_pg_${index}_maxPages`
383
+
384
+ ```javascript
385
+ const [
386
+ test_users_data,
387
+ test_users_data_pg_0,
388
+ test_users_data_pg_1,
389
+ test_users_data_count, // Only one count
390
+ ] = await Promise.all([
391
+ fetchData(),
392
+ fetchData({ page: 1, perPage: 10 }),
393
+ fetchData({ page: 1, perPage: 5 }),
394
+ fetchCount(), // Only one count call
395
+ ])
396
+
397
+ // Then calculate separate maxPages
398
+ const test_users_data_pg_0_maxPages = Math.ceil(test_users_data_count / 10)
399
+ const test_users_data_pg_1_maxPages = Math.ceil(test_users_data_count / 5)
400
+ ```
401
+
402
+ ### Problem 3: Button Disabled States
403
+
404
+ **Initial Mistake**:
405
+ ```javascript
406
+ disabled={!hasPrevPage} // Requires calculating hasPrevPage
407
+ disabled={!hasNextPage} // Requires calculating hasNextPage
408
+ ```
409
+
410
+ **Better Approach**:
411
+ ```javascript
412
+ disabled={page <= 1} // Simple, direct
413
+ disabled={page >= maxPages} // Simple, direct
414
+ ```
415
+
416
+ **Why**:
417
+ - No need to maintain separate `hasPrevPage`/`hasNextPage` state
418
+ - Less state to manage
419
+ - Clearer logic
420
+ - Pages are 1-indexed (page 1 is first page)
421
+
422
+ ### Problem 4: API Routes Not Generated
423
+
424
+ **Issue**: For static data sources (only used in `getStaticProps`), no API routes were created. But pagination needs API routes for client-side fetching.
425
+
426
+ **Root Cause**: The `extractDataSourceIntoNextAPIFolder` function was only called during UIDL traversal for data-source-list nodes. Static sources weren't traversed this way.
427
+
428
+ **Solution**: In the pagination plugin, explicitly call `extractDataSourceIntoNextAPIFolder` for each paginated data source:
429
+
430
+ ```typescript
431
+ // src/pagination-plugin.ts
432
+ function createAPIRoutesForPaginatedDataSources(...) {
433
+ const paginatedDataSourceIds = new Set(
434
+ paginationInfos.map((info) => info.dataSourceIdentifier)
435
+ )
436
+
437
+ // Traverse UIDL to find paginated data sources
438
+ const traverseForDataSources = (node: any): void => {
439
+ if (node.type === 'data-source-list' || node.type === 'data-source-item') {
440
+ const renderProp = node.content.renderPropIdentifier
441
+ if (renderProp && paginatedDataSourceIds.has(renderProp)) {
442
+ // Force API route creation
443
+ extractDataSourceIntoNextAPIFolder(
444
+ node,
445
+ dataSources,
446
+ componentChunk,
447
+ extractedResources
448
+ )
449
+
450
+ // Also create count API route for components
451
+ if (isComponent) {
452
+ extractedResources[`api/${fileName}-count`] = {
453
+ fileName: `${fileName}-count`,
454
+ fileType: FileType.JS,
455
+ path: ['pages', 'api'],
456
+ content: `import dataSource from '../../utils/data-sources/${fileName}'
457
+ export default dataSource.getCount`
458
+ }
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }
464
+ ```
465
+
466
+ ### Problem 5: Duplicate persistDataDuringLoading
467
+
468
+ **Issue**: DataProvider received duplicate `persistDataDuringLoading={true}` attributes.
469
+
470
+ **Root Cause**: Both `extractDataSourceIntoNextAPIFolder` and `extractDataSourceIntoGetStaticProps` were adding the attribute.
471
+
472
+ **Solution**: Check if attribute exists before adding:
473
+ ```typescript
474
+ const existingPersistAttr = jsxNode.openingElement.attributes.find(
475
+ (attr: any) => attr.type === 'JSXAttribute' &&
476
+ attr.name.name === 'persistDataDuringLoading'
477
+ )
478
+
479
+ if (!existingPersistAttr) {
480
+ jsxNode.openingElement.attributes.push(
481
+ types.jsxAttribute(
482
+ types.jsxIdentifier('persistDataDuringLoading'),
483
+ types.jsxExpressionContainer(types.booleanLiteral(true))
484
+ )
485
+ )
486
+ }
487
+ ```
488
+
489
+ ### Problem 6: Component Count Fetching Deduplication
490
+
491
+ **Issue**: In components, when same data source used in multiple paginated lists, we were creating separate `useEffect` calls for each:
492
+
493
+ ```javascript
494
+ // INEFFICIENT
495
+ useEffect(() => {
496
+ fetch('/api/data-source-count')
497
+ .then(res => res.json())
498
+ .then(data => setPagination_pg_0_maxPages(Math.ceil(data.count / 10)))
499
+ }, [])
500
+
501
+ useEffect(() => {
502
+ fetch('/api/data-source-count') // Same fetch!
503
+ .then(res => res.json())
504
+ .then(data => setPagination_pg_1_maxPages(Math.ceil(data.count / 5)))
505
+ }, [])
506
+ ```
507
+
508
+ **Solution**: Group by data source, one `useEffect` per unique source:
509
+
510
+ ```javascript
511
+ // EFFICIENT
512
+ useEffect(() => {
513
+ fetch('/api/data-source-count')
514
+ .then(res => res.json())
515
+ .then(data => {
516
+ setPagination_pg_0_maxPages(Math.ceil(data.count / 10))
517
+ setPagination_pg_1_maxPages(Math.ceil(data.count / 5))
518
+ })
519
+ }, [])
520
+ ```
521
+
522
+ **Implementation**:
523
+ ```typescript
524
+ // Group paginationInfos by fileName (data source)
525
+ const dataSourceToInfos = new Map<string, typeof paginationInfos>()
526
+ paginationInfos.forEach((info) => {
527
+ const key = (info as any).fileName
528
+ if (!dataSourceToInfos.has(key)) {
529
+ dataSourceToInfos.set(key, [])
530
+ }
531
+ dataSourceToInfos.get(key)!.push(info)
532
+ })
533
+
534
+ // Create ONE useEffect per unique data source
535
+ dataSourceToInfos.forEach((infos) => {
536
+ const { fileName } = infos[0]
537
+ const countApiPath = `/api/${fileName}-count`
538
+
539
+ const setStateStatements = infos.map((info) =>
540
+ types.expressionStatement(
541
+ types.callExpression(
542
+ types.identifier((info as any).setMaxPagesStateVar),
543
+ [types.callExpression(
544
+ types.memberExpression(types.identifier('Math'), types.identifier('ceil')),
545
+ [types.binaryExpression('/',
546
+ types.memberExpression(types.identifier('data'), types.identifier('count')),
547
+ types.numericLiteral(info.perPage)
548
+ )]
549
+ )]
550
+ )
551
+ )
552
+ )
553
+
554
+ // Generate single useEffect with multiple setState calls
555
+ })
556
+ ```
557
+
558
+ ---
559
+
560
+ ## UIDL Structure
561
+
562
+ ### Array Mapper with Pagination
563
+
564
+ ```json
565
+ {
566
+ "type": "cms-list-repeater",
567
+ "content": {
568
+ "renderPropIdentifier": "context_abc123",
569
+ "source": "test_users_data[0]",
570
+ "paginated": true,
571
+ "perPage": 10,
572
+ "nodes": {
573
+ "list": {
574
+ "type": "element",
575
+ "content": {
576
+ "elementType": "div",
577
+ "children": [
578
+ {
579
+ "type": "dynamic",
580
+ "content": {
581
+ "referenceType": "local",
582
+ "id": "context_abc123"
583
+ }
584
+ }
585
+ ]
586
+ }
587
+ },
588
+ "empty": null
589
+ }
590
+ }
591
+ }
592
+ ```
593
+
594
+ ### Pagination Container with Buttons
595
+
596
+ ```json
597
+ {
598
+ "type": "element",
599
+ "content": {
600
+ "elementType": "cms-pagination-node",
601
+ "children": [
602
+ {
603
+ "type": "element",
604
+ "content": {
605
+ "elementType": "cms-navigation-button",
606
+ "name": "Previous",
607
+ "children": [
608
+ {
609
+ "type": "static",
610
+ "content": "Previous"
611
+ }
612
+ ]
613
+ }
614
+ },
615
+ {
616
+ "type": "element",
617
+ "content": {
618
+ "elementType": "cms-navigation-button",
619
+ "name": "Next",
620
+ "children": [
621
+ {
622
+ "type": "static",
623
+ "content": "Next"
624
+ }
625
+ ]
626
+ }
627
+ }
628
+ ]
629
+ }
630
+ }
631
+ ```
632
+
633
+ **Important**: Pagination node is a direct sibling of the data source node, not nested inside it.
634
+
635
+ ---
636
+
637
+ ## Code Generation Flow
638
+
639
+ ### 1. Early Extraction Phase (`index.ts`)
640
+
641
+ ```typescript
642
+ // RUNS FIRST, before any transformations
643
+ const perPageMap = extractPerPageValuesEarly(uidl.node)
644
+
645
+ // Store in options for later use
646
+ if (!options.paginationConfig) {
647
+ options.paginationConfig = { perPageMap: new Map() }
648
+ }
649
+
650
+ // Merge maps (important for multiple component generations)
651
+ for (const [key, value] of perPageMap.entries()) {
652
+ options.paginationConfig.perPageMap.set(key, value)
653
+ }
654
+ ```
655
+
656
+ ### 2. Data Source Plugin Execution
657
+
658
+ Standard data source processing happens (creates DataProviders, utils files, etc.)
659
+
660
+ ### 3. Pagination Plugin Execution (`pagination-plugin.ts`)
661
+
662
+ #### Step 3.1: Detect Paginations from JSX
663
+ ```typescript
664
+ const detectedPaginations = detectPaginationsFromJSX(blockStatement, uidl.node)
665
+ // Returns: [
666
+ // {
667
+ // paginationNodeClass: 'home-cms-pagination-node1',
668
+ // prevButtonClass: 'home-previous1',
669
+ // nextButtonClass: 'home-next1',
670
+ // dataSourceIdentifier: 'test_users_data',
671
+ // dataProviderJSX: <DataProvider JSX node>,
672
+ // arrayMapperRenderProp: 'context_abc123'
673
+ // }
674
+ // ]
675
+ ```
676
+
677
+ #### Step 3.2: Generate Pagination Logic
678
+ ```typescript
679
+ detectedPaginations.forEach((detected, index) => {
680
+ const paginationNodeId = `pg_${index}`
681
+ const lookupKey = detected.arrayMapperRenderProp || detected.dataSourceIdentifier
682
+ const perPage = perPageMap.get(lookupKey) || 10
683
+
684
+ const info = generatePaginationLogic(paginationNodeId, detected.dataSourceIdentifier, perPage)
685
+ paginationInfos.push(info)
686
+
687
+ // Add state declarations to component
688
+ // pagination_pg_0_page, setPagination_pg_0_page, etc.
689
+ })
690
+ ```
691
+
692
+ #### Step 3.3: Create API Routes (if needed)
693
+ ```typescript
694
+ createAPIRoutesForPaginatedDataSources(...)
695
+ // - Ensures API routes exist for static data sources
696
+ // - Creates count API routes for components
697
+ ```
698
+
699
+ #### Step 3.4: Add Pagination Params to DataProviders
700
+ ```typescript
701
+ detectedPaginations.forEach((detected, index) => {
702
+ addPaginationParamsToDataProvider(detected.dataProviderJSX, paginationInfos[index], index)
703
+ // - Adds params prop
704
+ // - Modifies or creates fetchData
705
+ // - Sets initialData conditionally
706
+ // - Adds key prop
707
+ })
708
+ ```
709
+
710
+ #### Step 3.5: Modify Navigation Buttons
711
+ ```typescript
712
+ modifyPaginationButtons(blockStatement, detectedPaginations, paginationInfos)
713
+ // - Changes div to button
714
+ // - Adds onClick handlers
715
+ // - Adds disabled attributes
716
+ ```
717
+
718
+ #### Step 3.6: Modify getStaticProps (Pages Only)
719
+ ```typescript
720
+ if (isPage) {
721
+ modifyGetStaticPropsForPagination(chunks, paginationInfos)
722
+ // - Add fetchData calls for each pagination
723
+ // - Add fetchCount calls (deduplicated)
724
+ // - Calculate maxPages
725
+ // - Add to props
726
+ }
727
+ ```
728
+
729
+ #### Step 3.7: Add useEffect for Count (Components Only)
730
+ ```typescript
731
+ if (isComponent) {
732
+ // Add useEffect calls (deduplicated by data source)
733
+ // Fetch count from API
734
+ // Calculate and set maxPages
735
+ }
736
+ ```
737
+
738
+ ### 4. Generated Code Structure
739
+
740
+ #### Page (with getStaticProps):
741
+ ```javascript
742
+ 'use client'
743
+ import { useState } from 'react'
744
+ import { DataProvider } from '@teleporthq/react-components'
745
+ import dataSource1 from '../utils/data-sources/javascript-data-34a5011c'
746
+ import dataSource2 from '../utils/data-sources/cockroachdb-users-4b96921b'
747
+
748
+ const Home = (props) => {
749
+ const [pagination_pg_0_page, setPagination_pg_0_page] = useState(1)
750
+ const [pagination_pg_0_maxPages, setPagination_pg_0_maxPages] = useState(
751
+ props?.test1_data_data_pg_0_maxPages || 0
752
+ )
753
+
754
+ return (
755
+ <div>
756
+ <DataProvider
757
+ name="test1_data_data"
758
+ initialData={pagination_pg_0_page === 1 ? props?.test1_data_data_pg_0 : undefined}
759
+ params={{ page: pagination_pg_0_page, perPage: 3 }}
760
+ fetchData={(params) =>
761
+ fetch(`/api/javascript-data-34a5011c?${new URLSearchParams(params).toString()}`)
762
+ .then((res) => res.json())
763
+ .then((data) => data.data)
764
+ }
765
+ persistDataDuringLoading={true}
766
+ key={`test1_data_data-page-${pagination_pg_0_page}`}
767
+ >
768
+ {/* Array mapper content */}
769
+ </DataProvider>
770
+
771
+ <div className="home-cms-pagination-node1">
772
+ <button
773
+ type="button"
774
+ className="home-previous1"
775
+ onClick={() => setPagination_pg_0_page((p) => Math.max(1, p - 1))}
776
+ disabled={pagination_pg_0_page <= 1}
777
+ >
778
+ Previous
779
+ </button>
780
+ <button
781
+ type="button"
782
+ className="home-next1"
783
+ onClick={() => setPagination_pg_0_page((p) => p + 1)}
784
+ disabled={pagination_pg_0_page >= pagination_pg_0_maxPages}
785
+ >
786
+ Next
787
+ </button>
788
+ </div>
789
+ </div>
790
+ )
791
+ }
792
+
793
+ export async function getStaticProps() {
794
+ try {
795
+ const [
796
+ test1_data_data, // Non-paginated
797
+ test1_data_data_pg_0, // Paginated instance 1
798
+ test1_data_data_pg_1, // Paginated instance 2
799
+ test_users_data, // Non-paginated
800
+ test_users_data_pg_2, // Paginated instance 3
801
+ test1_data_data_count, // Count for test1_data_data
802
+ test_users_data_count, // Count for test_users_data
803
+ ] = await Promise.all([
804
+ dataSource1.fetchData().catch(() => []),
805
+ dataSource1.fetchData({ page: 1, perPage: 3 }).catch(() => []),
806
+ dataSource1.fetchData({ page: 1, perPage: 5 }).catch(() => []),
807
+ dataSource2.fetchData().catch(() => []),
808
+ dataSource2.fetchData({ page: 1, perPage: 10 }).catch(() => []),
809
+ dataSource1.fetchCount(),
810
+ dataSource2.fetchCount(),
811
+ ])
812
+
813
+ const test1_data_data_pg_0_maxPages = Math.ceil(test1_data_data_count / 3)
814
+ const test1_data_data_pg_1_maxPages = Math.ceil(test1_data_data_count / 5)
815
+ const test_users_data_pg_2_maxPages = Math.ceil(test_users_data_count / 10)
816
+
817
+ return {
818
+ props: {
819
+ test1_data_data,
820
+ test1_data_data_pg_0,
821
+ test1_data_data_pg_1,
822
+ test_users_data,
823
+ test_users_data_pg_2,
824
+ test1_data_data_pg_0_maxPages,
825
+ test1_data_data_pg_1_maxPages,
826
+ test_users_data_pg_2_maxPages,
827
+ },
828
+ revalidate: 60,
829
+ }
830
+ } catch (error) {
831
+ return { props: {} }
832
+ }
833
+ }
834
+
835
+ export default Home
836
+ ```
837
+
838
+ #### Component (without getStaticProps):
839
+ ```javascript
840
+ 'use client'
841
+ import { useState, useEffect } from 'react'
842
+ import { DataProvider } from '@teleporthq/react-components'
843
+
844
+ const MyComponent = (props) => {
845
+ const [pagination_pg_0_page, setPagination_pg_0_page] = useState(1)
846
+ const [pagination_pg_0_maxPages, setPagination_pg_0_maxPages] = useState(0)
847
+
848
+ const [pagination_pg_1_page, setPagination_pg_1_page] = useState(1)
849
+ const [pagination_pg_1_maxPages, setPagination_pg_1_maxPages] = useState(0)
850
+
851
+ // One useEffect per unique data source
852
+ useEffect(() => {
853
+ fetch('/api/javascript-data-34a5011c-count')
854
+ .then((res) => res.json())
855
+ .then((data) => {
856
+ setPagination_pg_0_maxPages(Math.ceil(data.count / 3))
857
+ setPagination_pg_1_maxPages(Math.ceil(data.count / 5))
858
+ })
859
+ .catch((error) => console.error('Error fetching count:', error))
860
+ }, [])
861
+
862
+ return (
863
+ <div>
864
+ <DataProvider
865
+ name="test1_data_data"
866
+ params={{ page: pagination_pg_0_page, perPage: 3 }}
867
+ fetchData={(params) =>
868
+ fetch(`/api/javascript-data-34a5011c?${new URLSearchParams(params).toString()}`)
869
+ .then((res) => res.json())
870
+ .then((data) => data.data)
871
+ }
872
+ persistDataDuringLoading={true}
873
+ key={`test1_data_data-page-${pagination_pg_0_page}`}
874
+ >
875
+ {/* Array mapper content */}
876
+ </DataProvider>
877
+
878
+ {/* Pagination buttons same as page example */}
879
+ </div>
880
+ )
881
+ }
882
+
883
+ export default MyComponent
884
+ ```
885
+
886
+ ---
887
+
888
+ ## Edge Cases & Special Handling
889
+
890
+ ### 1. Same Data Source, Different perPage Values
891
+
892
+ **Scenario**: `test_users_data` used twice with `perPage: 10` and `perPage: 5`
893
+
894
+ **Handling**:
895
+ - Key `perPageMap` by `renderPropIdentifier` (not data source identifier)
896
+ - Generate separate `fetchData` calls in `getStaticProps`
897
+ - Each pagination has unique state variables
898
+ - Count fetched only once, used for both maxPages calculations
899
+
900
+ ### 2. Mixed Paginated and Non-Paginated
901
+
902
+ **Scenario**: `test_users_data` used both paginated and non-paginated on same page
903
+
904
+ **Handling**:
905
+ - Non-paginated: `fetchData()` with no params, full data
906
+ - Paginated: `fetchData({ page: 1, perPage: X })`, subset of data
907
+ - Each DataProvider gets appropriate `initialData` reference
908
+
909
+ ### 3. No perPage Specified
910
+
911
+ **Handling**: Default to `perPage: 10`
912
+
913
+ ```typescript
914
+ const perPage = perPageMap.get(lookupKey) || 10
915
+ ```
916
+
917
+ ### 4. Empty Data Sources
918
+
919
+ **Handling**:
920
+ - Count returns 0
921
+ - maxPages becomes 0
922
+ - Next button disabled immediately
923
+ - Previous button disabled (page starts at 1)
924
+
925
+ ### 5. Static Data Sources (No Database)
926
+
927
+ **Scenario**: JavaScript array, CSV file, static collection
928
+
929
+ **Handling**:
930
+ - Still need API routes for client-side fetching
931
+ - Pagination applied in handler:
932
+ ```javascript
933
+ // In handler
934
+ if (Array.isArray(data)) {
935
+ const limitValue = limit || perPage
936
+ const offsetValue = offset !== undefined ? parseInt(offset) :
937
+ (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : 0)
938
+
939
+ if (limitValue) {
940
+ data = data.slice(offsetValue, offsetValue + parseInt(limitValue))
941
+ }
942
+ }
943
+ ```
944
+
945
+ ### 6. Filters and Queries with Pagination
946
+
947
+ **Important**: `getCount` must apply the same filters as `handler`
948
+
949
+ ```typescript
950
+ export async function getCount(req, res) {
951
+ const { query, queryColumns, filters } = req.query
952
+
953
+ // Apply same filters as data handler
954
+ if (queryColumns && query) {
955
+ const columns = Array.isArray(queryColumns) ? queryColumns : [queryColumns]
956
+ const searchConditions = columns.map(col => `${col} ILIKE $?`).join(' OR ')
957
+ conditions.push(`(${searchConditions})`)
958
+ }
959
+
960
+ if (filters) {
961
+ const parsedFilters = JSON.parse(filters)
962
+ for (const filter of parsedFilters) {
963
+ conditions.push(`${filter.column} ${filter.operator} $?`)
964
+ }
965
+ }
966
+
967
+ // Execute COUNT with same conditions
968
+ }
969
+ ```
970
+
971
+ ### 7. Nested or Conditional Array Mappers
972
+
973
+ **Handling**: The `extractPerPageValuesEarly` function traverses all UIDL structures:
974
+ - Conditional nodes
975
+ - Nested data sources
976
+ - Success/error/loading states
977
+ - List/empty states
978
+
979
+ Must handle all these structures or `perPage` will be missed.
980
+
981
+ ---
982
+
983
+ ## Testing & Verification
984
+
985
+ ### Console Logs for Debugging (Now Removed)
986
+
987
+ During development, these logs were crucial:
988
+
989
+ ```typescript
990
+ // Early extraction
991
+ console.log('[EARLY EXTRACT] cms-list-repeater', { perPage, renderProp })
992
+ console.log('[EARLY EXTRACT] perPage map:', Array.from(perPageMap.entries()))
993
+
994
+ // Pagination detection
995
+ console.log('[PAGINATION] Found DataProvider with name:', identifier)
996
+ console.log('[PAGINATION] Found pagination container:', className)
997
+ console.log('[PAGINATION] Array mapper renderProps:', arrayMapperRenderProps)
998
+
999
+ // getStaticProps modification
1000
+ console.log('[PAGINATION] Mapped import to variable:', importToDataSource)
1001
+ console.log('[PAGINATION] Promise.all now has X elements')
1002
+ ```
1003
+
1004
+ **Why They Were Important**:
1005
+ - UIDL transformations are not visible in the code
1006
+ - JSX AST structure is complex
1007
+ - Need to verify data flow through multiple plugin stages
1008
+ - Debugging timing issues (what runs when)
1009
+
1010
+ **Now Removed**: Production code should be clean, but this section documents what to add back if debugging.
1011
+
1012
+ ### Manual Testing Checklist
1013
+
1014
+ - [ ] Single pagination on page
1015
+ - [ ] Multiple paginations on same page
1016
+ - [ ] Same data source, different perPage
1017
+ - [ ] Same data source, mixed paginated/non-paginated
1018
+ - [ ] Page navigation (prev/next buttons work)
1019
+ - [ ] Button disabled states (first page, last page)
1020
+ - [ ] Initial data loads correctly (page 1)
1021
+ - [ ] Subsequent pages fetch correctly
1022
+ - [ ] Count is accurate
1023
+ - [ ] maxPages calculated correctly
1024
+ - [ ] Component pagination (useEffect count fetch)
1025
+ - [ ] Page pagination (getStaticProps count fetch)
1026
+ - [ ] All data source types (JS, PostgreSQL, MySQL, MongoDB, etc.)
1027
+ - [ ] With filters/queries applied
1028
+
1029
+ ### Known Limitations
1030
+
1031
+ 1. **Page number validation**: No validation that requested page exists (page 999 of 10 will return empty)
1032
+ 2. **Race conditions**: Rapid clicking may cause multiple fetches
1033
+ 3. **Error states**: No specific UI for fetch errors
1034
+ 4. **Loading states**: Uses DataProvider's built-in loading, no custom pagination loading
1035
+
1036
+ ---
1037
+
1038
+ ## Future Considerations
1039
+
1040
+ ### Potential Enhancements
1041
+
1042
+ 1. **Page number display**: Show "Page 1 of 10"
1043
+ 2. **Jump to page**: Input to go directly to a page
1044
+ 3. **Page size selector**: Allow user to change perPage
1045
+ 4. **First/Last buttons**: Jump to first/last page
1046
+ 5. **URL sync option**: Optional URL-based state for deep linking
1047
+ 6. **Cursor-based pagination**: Support cursor/token-based pagination for large datasets
1048
+ 7. **Virtual scrolling**: Infinite scroll with pagination
1049
+ 8. **Optimistic updates**: Show loading state during page changes
1050
+
1051
+ ### Potential Issues to Watch
1052
+
1053
+ 1. **Memory leaks**: Ensure useEffect cleanup in components
1054
+ 2. **Stale closures**: Be careful with state in callbacks
1055
+ 3. **SSR hydration**: Ensure client/server state matches
1056
+ 4. **Cache invalidation**: When to refetch count
1057
+ 5. **Concurrent mode**: React 18+ concurrent features
1058
+
1059
+ ### Breaking Changes to Avoid
1060
+
1061
+ 1. Don't change UIDL structure without updating validator
1062
+ 2. Don't remove `renderPropIdentifier` from cms-list-repeater
1063
+ 3. Don't change perPageMap key structure
1064
+ 4. Don't modify DataProvider API expectations
1065
+ 5. Don't change getStaticProps return shape
1066
+
1067
+ ---
1068
+
1069
+ ## File Reference
1070
+
1071
+ ### Core Implementation Files
1072
+
1073
+ - `src/index.ts`: Early perPage extraction, plugin orchestration
1074
+ - `src/pagination-plugin.ts`: Main pagination plugin, JSX traversal, code generation
1075
+ - `src/array-mapper-pagination.ts`: Pagination logic helper, types
1076
+ - `src/utils.ts`: API route extraction
1077
+ - `src/data-source-fetchers.ts`: fetchData and fetchCount generation
1078
+ - `src/count-fetchers.ts`: getCount handler generation for all data source types
1079
+ - `src/fetchers/*.ts`: Individual data source handlers
1080
+
1081
+ ### Type Definitions
1082
+
1083
+ - `packages/teleport-types/src/uidl.ts`: UIDL types, added `paginated` and `perPage`
1084
+ - `packages/teleport-uidl-validator/src/decoders/utils.ts`: UIDL validator, added pagination fields
1085
+
1086
+ ### Tests
1087
+
1088
+ - `__tests__/pagination.test.ts`: Unit tests for pagination logic generation
1089
+
1090
+ ---
1091
+
1092
+ ## Key Takeaways for Future Developers
1093
+
1094
+ 1. **UIDL transformations happen early**: You cannot rely on UIDL node types in late-stage plugins. Use JSX AST traversal with class name detection.
1095
+
1096
+ 2. **perPage must be extracted early**: Before any transformations, in the main plugin entry point.
1097
+
1098
+ 3. **renderPropIdentifier is the unique key**: Not the data source identifier. Each array mapper instance has a unique renderPropIdentifier.
1099
+
1100
+ 4. **DataProvider behavior is complex**: The `initialData` + `passFetchBecauseWeHaveInitialData` ref interaction requires conditional initialData and key prop to force remounting.
1101
+
1102
+ 5. **Multiple instances need separate fetches**: Same data source used multiple times = multiple fetchData calls in getStaticProps, not one shared call.
1103
+
1104
+ 6. **Count and data are separate**: Never couple them in the same response. Separate handlers allow independent fetching and optimization.
1105
+
1106
+ 7. **Deduplication is critical**: Count fetches and useEffect calls must be deduplicated by data source to avoid unnecessary API calls.
1107
+
1108
+ 8. **Variable naming must be unique**: When same data source used multiple times, every variable (data, count, maxPages) needs unique naming per instance.
1109
+
1110
+ 9. **API routes must exist**: Even for static sources, client-side pagination requires API routes for fetchData.
1111
+
1112
+ 10. **Console logs are debugging lifesavers**: The transformation pipeline is opaque. Strategic logging is essential during development, but remove for production.
1113
+
1114
+ ---
1115
+
1116
+ ## Conclusion
1117
+
1118
+ This pagination implementation handles complex edge cases and multiple architectural constraints. The key challenges were:
1119
+
1120
+ 1. Working with transformed UIDL (JSX AST traversal)
1121
+ 2. Early extraction of configuration before transformations
1122
+ 3. Understanding and working around DataProvider's internal behavior
1123
+ 4. Handling multiple instances of same data source
1124
+ 5. Proper deduplication of fetches and effects
1125
+ 6. Correct variable naming and scope management
1126
+
1127
+ The implementation is production-ready and handles all known edge cases. Future enhancements should maintain backward compatibility with the UIDL structure and generated code expectations.
1128
+