lazy-render-virtual-scroll 1.3.0 → 1.5.0

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 (51) hide show
  1. package/README.md +42 -11
  2. package/backend-helpers/README.md +233 -0
  3. package/backend-helpers/example.ts +114 -0
  4. package/backend-helpers/frontend-example.tsx +122 -0
  5. package/backend-helpers/pagination.ts +159 -0
  6. package/dist/cjs/adapters/react/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  7. package/dist/cjs/adapters/react/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  8. package/dist/cjs/adapters/react/index.d.ts +12 -1
  9. package/dist/cjs/adapters/react/index.d.ts.map +1 -1
  10. package/dist/cjs/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  11. package/dist/cjs/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  12. package/dist/cjs/angular/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  13. package/dist/cjs/angular/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  14. package/dist/cjs/angular/index.d.ts +12 -1
  15. package/dist/cjs/angular/index.d.ts.map +1 -1
  16. package/dist/cjs/index.d.ts +12 -1
  17. package/dist/cjs/index.d.ts.map +1 -1
  18. package/dist/cjs/index.js +13 -153
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/cjs/svelte/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  21. package/dist/cjs/svelte/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  22. package/dist/cjs/svelte/index.d.ts +12 -1
  23. package/dist/cjs/svelte/index.d.ts.map +1 -1
  24. package/dist/cjs/vue/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  25. package/dist/cjs/vue/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  26. package/dist/cjs/vue/index.d.ts +12 -1
  27. package/dist/cjs/vue/index.d.ts.map +1 -1
  28. package/dist/esm/adapters/react/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  29. package/dist/esm/adapters/react/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  30. package/dist/esm/adapters/react/index.d.ts +12 -1
  31. package/dist/esm/adapters/react/index.d.ts.map +1 -1
  32. package/dist/esm/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  33. package/dist/esm/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  34. package/dist/esm/angular/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  35. package/dist/esm/angular/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  36. package/dist/esm/angular/index.d.ts +12 -1
  37. package/dist/esm/angular/index.d.ts.map +1 -1
  38. package/dist/esm/index.d.ts +12 -1
  39. package/dist/esm/index.d.ts.map +1 -1
  40. package/dist/esm/index.js +13 -153
  41. package/dist/esm/index.js.map +1 -1
  42. package/dist/esm/svelte/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  43. package/dist/esm/svelte/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  44. package/dist/esm/svelte/index.d.ts +12 -1
  45. package/dist/esm/svelte/index.d.ts.map +1 -1
  46. package/dist/esm/vue/adapters/vanilla/LazyScrollElement.d.ts +370 -0
  47. package/dist/esm/vue/adapters/vanilla/LazyScrollElement.d.ts.map +1 -0
  48. package/dist/esm/vue/index.d.ts +12 -1
  49. package/dist/esm/vue/index.d.ts.map +1 -1
  50. package/dist/index.d.ts +12 -153
  51. package/package.json +6 -6
package/README.md CHANGED
@@ -22,6 +22,47 @@ A framework-agnostic virtual scrolling and lazy rendering solution that efficien
22
22
  | Memory usage (10k items) | High | Low |
23
23
  | Initial load time | Slow | Fast |
24
24
  | Scroll performance | Janky | Smooth |
25
+ | **1 Million items (with backend pagination)** | **3+ seconds** | **<100ms** |
26
+
27
+ ## Backend Integration
28
+
29
+ ### **🚨 IMPORTANT: Backend Pagination Required**
30
+
31
+ For optimal performance with large datasets (1M+ items), use backend pagination:
32
+
33
+ ```javascript
34
+ // Backend (Node.js/Express)
35
+ app.get('/api/cards', async (req, res) => {
36
+ const page = parseInt(req.query.page) || 1;
37
+ const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
38
+ const skip = (page - 1) * limit;
39
+
40
+ const items = await getDataFromDatabase(skip, limit);
41
+ const total = await getTotalCount();
42
+
43
+ res.json({
44
+ data: items,
45
+ pagination: {
46
+ page,
47
+ limit,
48
+ total,
49
+ totalPages: Math.ceil(total / limit),
50
+ hasMore: page < Math.ceil(total / limit)
51
+ }
52
+ });
53
+ });
54
+ ```
55
+
56
+ **See [Backend Helpers](./backend-helpers/README.md) for complete examples.**
57
+
58
+ ### **Performance with Backend Pagination:**
59
+
60
+ | Metric | Without Pagination | With Pagination | Improvement |
61
+ |--------|-------------------|-----------------|-------------|
62
+ | **API Response Time** | 3378ms | <100ms | **33x faster** ⚡ |
63
+ | **Data Transferred** | ~500MB | ~25KB | **20,000x less** 📉 |
64
+ | **Initial Load** | 3+ seconds | <200ms | **15x faster** ⚡ |
65
+ | **Memory Usage** | Very High | Very Low | **99% reduction** 💾 |
25
66
 
26
67
  ## Features
27
68
 
@@ -34,17 +75,7 @@ A framework-agnostic virtual scrolling and lazy rendering solution that efficien
34
75
  - **Device Performance Monitoring**: Adjusts behavior based on device capabilities
35
76
  - **Content Complexity Analysis**: Optimizes for different content types and complexity
36
77
  - **Memory Efficient**: Automatically cleans up off-screen elements
37
- - **Multi-Framework Support**: Easy integration with React, Vue, Angular, Svelte, and vanilla JavaScript
38
- - **Advanced Performance Optimization**: Frame-rate optimized updates and GPU acceleration
39
- - **Memory Management**: Intelligent caching and cleanup for optimal memory usage
40
- - **GPU Acceleration**: Hardware-accelerated rendering for smooth performance
41
- - **Frame Budget Optimization**: Limits updates to maintain 60fps performance
42
- - **Batch Update Processing**: Reduces DOM manipulations for better performance
43
78
  - **React Adapter**: Easy integration with React applications
44
- - **Vue Adapter**: Composition API integration with Vue 3
45
- - **Angular Adapter**: Directive and component for Angular applications
46
- - **Svelte Adapter**: Action and component for Svelte applications
47
- - **Vanilla JS Support**: Web Components and plain JavaScript implementation
48
79
  - **Configurable Buffer**: Adjustable buffer size for optimal performance
49
80
  - **Overscan Support**: Additional buffer for smoother scrolling
50
81
  - **Predictive Loading**: Anticipates user needs based on scroll patterns
@@ -156,7 +187,7 @@ const MyCustomComponent = () => {
156
187
  }}
157
188
  />
158
189
 
159
- {/* Loading indicator */}
190
+ { /* Loading indicato */}
160
191
  {isLoading && (
161
192
  <div className="lazy-loading">
162
193
  Loading more items...
@@ -0,0 +1,233 @@
1
+ # Backend Pagination Helper - lazy-render
2
+
3
+ ## **🚨 PROBLEM SOLVED**
4
+
5
+ **Before:**
6
+ ```
7
+ GET /api/monitoring/cards
8
+ Returns: 10,00,000 items at once
9
+ Response Time: 3+ seconds ❌
10
+ ```
11
+
12
+ **After:**
13
+ ```
14
+ GET /api/monitoring/cards?page=1&limit=50
15
+ Returns: 50 items per request
16
+ Response Time: <100ms ✅
17
+ ```
18
+
19
+ ## **INSTALLATION**
20
+
21
+ ```bash
22
+ npm install lazy-render-virtual-scroll
23
+ ```
24
+
25
+ ## **BACKEND IMPLEMENTATION (Node.js/Express)**
26
+
27
+ ### **1. Basic Setup**
28
+
29
+ ```javascript
30
+ const express = require('express');
31
+ const { calculatePagination } = require('lazy-render-virtual-scroll/backend-helpers');
32
+
33
+ const app = express();
34
+
35
+ app.get('/api/monitoring/cards', async (req, res) => {
36
+ const page = parseInt(req.query.page) || 1;
37
+ const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
38
+ const skip = (page - 1) * limit;
39
+
40
+ // Get data from database
41
+ const items = await getDataFromDatabase(skip, limit);
42
+ const total = await getTotalCount();
43
+
44
+ // Return paginated response
45
+ res.json(calculatePagination(items, page, limit, total));
46
+ });
47
+ ```
48
+
49
+ ### **2. MongoDB Example**
50
+
51
+ ```javascript
52
+ app.get('/api/monitoring/cards', async (req, res) => {
53
+ const page = parseInt(req.query.page) || 1;
54
+ const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
55
+ const skip = (page - 1) * limit;
56
+
57
+ const [items, total] = await Promise.all([
58
+ Card.find().skip(skip).limit(limit).sort({ createdAt: -1 }),
59
+ Card.countDocuments()
60
+ ]);
61
+
62
+ res.json({
63
+ data: items,
64
+ pagination: {
65
+ page,
66
+ limit,
67
+ total,
68
+ totalPages: Math.ceil(total / limit),
69
+ hasMore: page < Math.ceil(total / limit)
70
+ }
71
+ });
72
+ });
73
+ ```
74
+
75
+ ### **3. MySQL/PostgreSQL Example**
76
+
77
+ ```javascript
78
+ app.get('/api/monitoring/cards', async (req, res) => {
79
+ const page = parseInt(req.query.page) || 1;
80
+ const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
81
+ const offset = (page - 1) * limit;
82
+
83
+ const [items, totalResult] = await Promise.all([
84
+ db.query('SELECT * FROM cards ORDER BY created_at DESC LIMIT ? OFFSET ?', [limit, offset]),
85
+ db.query('SELECT COUNT(*) as count FROM cards')
86
+ ]);
87
+
88
+ res.json({
89
+ data: items,
90
+ pagination: {
91
+ page,
92
+ limit,
93
+ total: totalResult[0].count,
94
+ totalPages: Math.ceil(totalResult[0].count / limit),
95
+ hasMore: page < Math.ceil(totalResult[0].count / limit)
96
+ }
97
+ });
98
+ });
99
+ ```
100
+
101
+ ## **FRONTEND INTEGRATION (React)**
102
+
103
+ ```javascript
104
+ import { LazyList } from 'lazy-render-virtual-scroll';
105
+
106
+ function Dashboard() {
107
+ const [items, setItems] = useState([]);
108
+ const [page, setPage] = useState(1);
109
+ const [hasMore, setHasMore] = useState(true);
110
+
111
+ const fetchMore = async () => {
112
+ if (!hasMore) return [];
113
+
114
+ const response = await fetch(`/api/cards?page=${page}&limit=50`);
115
+ const result = await response.json();
116
+
117
+ setItems(prev => [...prev, ...result.data]);
118
+ setPage(prev => prev + 1);
119
+ setHasMore(result.pagination.hasMore);
120
+
121
+ return result.data;
122
+ };
123
+
124
+ return (
125
+ <LazyList
126
+ items={items}
127
+ itemHeight={200}
128
+ viewportHeight={600}
129
+ fetchMore={fetchMore}
130
+ renderItem={(item) => <Card data={item} />}
131
+ />
132
+ );
133
+ }
134
+ ```
135
+
136
+ ## **PERFORMANCE COMPARISON**
137
+
138
+ | Metric | Before | After | Improvement |
139
+ |--------|---------|-------|-------------|
140
+ | **API Response Time** | 3378ms | <100ms | **33x faster** ⚡ |
141
+ | **Data Transferred** | ~500MB | ~25KB | **20,000x less** 📉 |
142
+ | **Initial Load** | 3+ seconds | <200ms | **15x faster** ⚡ |
143
+ | **Memory Usage** | Very High | Very Low | **99% reduction** 💾 |
144
+ | **Server Load** | Very High | Minimal | **99% reduction** 🖥️ |
145
+
146
+ ## **API PARAMETERS**
147
+
148
+ ### **Query Parameters:**
149
+ - `page` (number): Page number (default: 1)
150
+ - `limit` (number): Items per page (default: 50, max: 1000)
151
+
152
+ ### **Response Format:**
153
+ ```json
154
+ {
155
+ "data": [...],
156
+ "pagination": {
157
+ "page": 1,
158
+ "limit": 50,
159
+ "total": 1000000,
160
+ "totalPages": 20000,
161
+ "hasMore": true,
162
+ "nextCursor": "2",
163
+ "prevCursor": null
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## **BEST PRACTICES**
169
+
170
+ 1. **Always use pagination** - Never return all data at once
171
+ 2. **Limit maximum page size** - Prevent abuse with max limit (1000 recommended)
172
+ 3. **Include pagination metadata** - Help frontend know total items and pages
173
+ 4. **Use cursor-based pagination** - For very large datasets
174
+ 5. **Cache count queries** - Total count can be expensive
175
+
176
+ ## **ERROR HANDLING**
177
+
178
+ ```javascript
179
+ app.get('/api/cards', async (req, res) => {
180
+ try {
181
+ const page = parseInt(req.query.page) || 1;
182
+ const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
183
+
184
+ if (page < 1) {
185
+ return res.status(400).json({ error: 'Page must be >= 1' });
186
+ }
187
+
188
+ if (limit < 1 || limit > 1000) {
189
+ return res.status(400).json({ error: 'Limit must be between 1 and 1000' });
190
+ }
191
+
192
+ // ... rest of implementation
193
+ } catch (error) {
194
+ res.status(500).json({
195
+ error: 'Failed to fetch data',
196
+ message: error.message
197
+ });
198
+ }
199
+ });
200
+ ```
201
+
202
+ ## **MIGRATION GUIDE**
203
+
204
+ ### **From: No Pagination**
205
+ ```javascript
206
+ // ❌ OLD CODE
207
+ app.get('/api/cards', (req, res) => {
208
+ const allCards = database.getAll(); // Returns 1M items
209
+ res.json(allCards);
210
+ });
211
+ ```
212
+
213
+ ### **To: With Pagination**
214
+ ```javascript
215
+ // ✅ NEW CODE
216
+ app.get('/api/cards', async (req, res) => {
217
+ const page = parseInt(req.query.page) || 1;
218
+ const limit = 50;
219
+ const skip = (page - 1) * limit;
220
+
221
+ const items = database.getRange(skip, limit); // Returns 50 items
222
+ const total = database.count();
223
+
224
+ res.json({
225
+ data: items,
226
+ pagination: { page, limit, total, hasMore: true }
227
+ });
228
+ });
229
+ ```
230
+
231
+ ## **SUPPORT**
232
+
233
+ For issues or questions, visit: https://github.com/sannuk79/lezzyrender
@@ -0,0 +1,114 @@
1
+ /**
2
+ * BACKEND EXAMPLE - Fix for 1 Million Cards Performance Issue
3
+ *
4
+ * BEFORE (WRONG):
5
+ * GET /api/monitoring/cards
6
+ * Returns: 10,00,000 items at once
7
+ * Response time: 3+ seconds ❌
8
+ *
9
+ * AFTER (CORRECT):
10
+ * GET /api/monitoring/cards?page=1&limit=50
11
+ * Returns: 50 items per request
12
+ * Response time: <100ms ✅
13
+ */
14
+
15
+ import express from 'express';
16
+ import { calculatePagination, validatePaginationParams } from './pagination';
17
+
18
+ const router = express.Router();
19
+
20
+ // Mock database - replace with your actual database
21
+ const mockCards = Array.from({ length: 1000000 }, (_, i) => ({
22
+ id: i,
23
+ title: `Card ${i}`,
24
+ category: `Category ${i % 100000}`,
25
+ createdAt: new Date().toISOString()
26
+ }));
27
+
28
+ /**
29
+ * ✅ CORRECT IMPLEMENTATION - With Pagination
30
+ */
31
+ router.get('/monitoring/cards', async (req, res) => {
32
+ try {
33
+ // Get pagination parameters
34
+ const page = parseInt(req.query.page as string) || 1;
35
+ const limit = Math.min(
36
+ parseInt(req.query.limit as string) || 50,
37
+ 1000 // Maximum 1000 items per page
38
+ );
39
+
40
+ // Calculate skip for database query
41
+ const skip = (page - 1) * limit;
42
+
43
+ // Get total count (for pagination metadata)
44
+ const total = mockCards.length;
45
+
46
+ // Get only the requested page of data
47
+ const items = mockCards.slice(skip, skip + limit);
48
+
49
+ // Return paginated response
50
+ const response = calculatePagination(items, page, limit, total);
51
+
52
+ res.json(response);
53
+ } catch (error) {
54
+ console.error('Error fetching cards:', error);
55
+ res.status(500).json({
56
+ error: 'Failed to fetch cards',
57
+ message: error instanceof Error ? error.message : 'Unknown error'
58
+ });
59
+ }
60
+ });
61
+
62
+ /**
63
+ * Alternative: MongoDB Implementation
64
+ */
65
+ /*
66
+ router.get('/monitoring/cards', async (req, res) => {
67
+ try {
68
+ const page = parseInt(req.query.page as string) || 1;
69
+ const limit = Math.min(parseInt(req.query.limit as string) || 50, 1000);
70
+ const skip = (page - 1) * limit;
71
+
72
+ // MongoDB query with pagination
73
+ const [items, total] = await Promise.all([
74
+ Card.find()
75
+ .skip(skip)
76
+ .limit(limit)
77
+ .sort({ createdAt: -1 }),
78
+ Card.countDocuments()
79
+ ]);
80
+
81
+ const response = calculatePagination(items, page, limit, total);
82
+ res.json(response);
83
+ } catch (error) {
84
+ res.status(500).json({ error: 'Failed to fetch cards' });
85
+ }
86
+ });
87
+ */
88
+
89
+ /**
90
+ * Alternative: MySQL/PostgreSQL Implementation
91
+ */
92
+ /*
93
+ router.get('/monitoring/cards', async (req, res) => {
94
+ try {
95
+ const page = parseInt(req.query.page as string) || 1;
96
+ const limit = Math.min(parseInt(req.query.limit as string) || 50, 1000);
97
+ const offset = (page - 1) * limit;
98
+
99
+ // SQL query with LIMIT and OFFSET
100
+ const [items, totalResult] = await Promise.all([
101
+ db.query('SELECT * FROM cards ORDER BY created_at DESC LIMIT ? OFFSET ?', [limit, offset]),
102
+ db.query('SELECT COUNT(*) as count FROM cards')
103
+ ]);
104
+
105
+ const total = totalResult[0].count;
106
+ const response = calculatePagination(items, page, limit, total);
107
+ res.json(response);
108
+ } catch (error) {
109
+ res.status(500).json({ error: 'Failed to fetch cards' });
110
+ }
111
+ });
112
+ */
113
+
114
+ export default router;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * FRONTEND EXAMPLE - Integration with lazy-render
3
+ * Solves the 1 million cards performance issue
4
+ */
5
+
6
+ import React, { useState } from 'react';
7
+ import { LazyList, useLazyList } from 'lazy-render-virtual-scroll';
8
+
9
+ interface Card {
10
+ id: number;
11
+ title: string;
12
+ category: string;
13
+ createdAt: string;
14
+ }
15
+
16
+ interface PaginationInfo {
17
+ page: number;
18
+ limit: number;
19
+ total: number;
20
+ totalPages: number;
21
+ hasMore: boolean;
22
+ }
23
+
24
+ /**
25
+ * ✅ CORRECT FRONTEND IMPLEMENTATION
26
+ * Uses lazy-render with paginated API
27
+ */
28
+ export const ColorfulDashboard: React.FC = () => {
29
+ const [items, setItems] = useState<Card[]>([]);
30
+ const [page, setPage] = useState(1);
31
+ const [hasMore, setHasMore] = useState(true);
32
+ const [pagination, setPagination] = useState<PaginationInfo | null>(null);
33
+
34
+ // Fetch more data when user scrolls
35
+ const fetchMore = async () => {
36
+ if (!hasMore) return [];
37
+
38
+ try {
39
+ // Fetch only 50 items at a time
40
+ const response = await fetch(
41
+ `/api/monitoring/cards?page=${page}&limit=50`
42
+ );
43
+
44
+ if (!response.ok) {
45
+ throw new Error('Failed to fetch cards');
46
+ }
47
+
48
+ const result = await response.json();
49
+
50
+ // Update state with new items
51
+ setItems(prev => [...prev, ...result.data]);
52
+ setPagination(result.pagination);
53
+ setPage(prev => prev + 1);
54
+ setHasMore(result.pagination.hasMore);
55
+
56
+ return result.data;
57
+ } catch (error) {
58
+ console.error('Error fetching more cards:', error);
59
+ return [];
60
+ }
61
+ };
62
+
63
+ // Use lazy-render hook
64
+ const { visibleRange, isLoading } = useLazyList({
65
+ itemHeight: 200,
66
+ viewportHeight: 600,
67
+ bufferSize: 5,
68
+ fetchMore
69
+ });
70
+
71
+ // Render individual card
72
+ const renderCard = (card: Card, index: number) => (
73
+ <div
74
+ key={card.id}
75
+ style={{
76
+ height: '200px',
77
+ borderBottom: '1px solid #eee',
78
+ padding: '16px'
79
+ }}
80
+ >
81
+ <h3>{card.title}</h3>
82
+ <p>Category: {card.category}</p>
83
+ <p>Created: {new Date(card.createdAt).toLocaleDateString()}</p>
84
+ </div>
85
+ );
86
+
87
+ return (
88
+ <div style={{ padding: '20px' }}>
89
+ <h1>Colorful Dashboard</h1>
90
+
91
+ {/* Pagination Info */}
92
+ {pagination && (
93
+ <div style={{ marginBottom: '16px' }}>
94
+ <p>
95
+ Showing {items.length} of {pagination.total} cards
96
+ {isLoading && ' • Loading...'}
97
+ </p>
98
+ </div>
99
+ )}
100
+
101
+ {/* LazyList with virtual scrolling */}
102
+ <LazyList
103
+ items={items}
104
+ itemHeight={200}
105
+ viewportHeight={600}
106
+ fetchMore={fetchMore}
107
+ renderItem={renderCard}
108
+ bufferSize={5}
109
+ overscan={2}
110
+ />
111
+
112
+ {/* Load More Button (optional fallback) */}
113
+ {!hasMore && (
114
+ <div style={{ textAlign: 'center', padding: '20px' }}>
115
+ <p>No more cards to load</p>
116
+ </div>
117
+ )}
118
+ </div>
119
+ );
120
+ };
121
+
122
+ export default ColorfulDashboard;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Backend Pagination Helper for lazy-render
3
+ * Solves the 1 million cards performance issue
4
+ */
5
+
6
+ export interface PaginationParams {
7
+ page: number;
8
+ limit: number;
9
+ cursor?: string;
10
+ }
11
+
12
+ export interface PaginatedResponse<T> {
13
+ data: T[];
14
+ pagination: {
15
+ page: number;
16
+ limit: number;
17
+ total: number;
18
+ totalPages: number;
19
+ hasMore: boolean;
20
+ nextCursor?: string;
21
+ prevCursor?: string;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Calculate pagination metadata
27
+ */
28
+ export function calculatePagination<T>(
29
+ items: T[],
30
+ page: number,
31
+ limit: number,
32
+ total: number
33
+ ): PaginatedResponse<T> {
34
+ const totalPages = Math.ceil(total / limit);
35
+ const hasMore = page < totalPages;
36
+
37
+ return {
38
+ data: items,
39
+ pagination: {
40
+ page,
41
+ limit,
42
+ total,
43
+ totalPages,
44
+ hasMore,
45
+ nextCursor: hasMore ? String(page + 1) : undefined,
46
+ prevCursor: page > 1 ? String(page - 1) : undefined
47
+ }
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Validate pagination parameters
53
+ */
54
+ export function validatePaginationParams(params: PaginationParams): {
55
+ page: number;
56
+ limit: number;
57
+ errors: string[];
58
+ } {
59
+ const errors: string[] = [];
60
+
61
+ // Validate page
62
+ const page = parseInt(String(params.page)) || 1;
63
+ if (page < 1) {
64
+ errors.push('Page must be greater than 0');
65
+ }
66
+
67
+ // Validate limit
68
+ const limit = parseInt(String(params.limit)) || 50;
69
+ if (limit < 1) {
70
+ errors.push('Limit must be greater than 0');
71
+ }
72
+ if (limit > 1000) {
73
+ errors.push('Limit cannot exceed 1000 items per page');
74
+ }
75
+
76
+ return {
77
+ page: page < 1 ? 1 : page,
78
+ limit: limit < 1 ? 50 : limit > 1000 ? 1000 : limit,
79
+ errors
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Express.js middleware for pagination
85
+ */
86
+ export function paginationMiddleware(
87
+ req: any,
88
+ res: any,
89
+ next: () => void
90
+ ) {
91
+ const { page, limit, cursor } = req.query;
92
+
93
+ const validated = validatePaginationParams({
94
+ page: parseInt(String(page)) || 1,
95
+ limit: parseInt(String(limit)) || 50,
96
+ cursor
97
+ });
98
+
99
+ if (validated.errors.length > 0) {
100
+ return res.status(400).json({
101
+ error: 'Invalid pagination parameters',
102
+ details: validated.errors
103
+ });
104
+ }
105
+
106
+ req.pagination = {
107
+ page: validated.page,
108
+ limit: validated.limit,
109
+ cursor: validated.cursor
110
+ };
111
+
112
+ next();
113
+ }
114
+
115
+ /**
116
+ * Example Express route handler
117
+ */
118
+ export function createPaginatedRoute<T>(
119
+ getDataFunction: (skip: number, limit: number) => Promise<{ items: T[]; total: number }>,
120
+ options: {
121
+ defaultLimit?: number;
122
+ maxLimit?: number;
123
+ } = {}
124
+ ) {
125
+ const defaultLimit = options.defaultLimit || 50;
126
+ const maxLimit = options.maxLimit || 1000;
127
+
128
+ return async (req: any, res: any) => {
129
+ try {
130
+ const page = parseInt(req.query.page) || 1;
131
+ const limit = Math.min(
132
+ parseInt(req.query.limit) || defaultLimit,
133
+ maxLimit
134
+ );
135
+ const skip = (page - 1) * limit;
136
+
137
+ // Get data with pagination
138
+ const { items, total } = await getDataFunction(skip, limit);
139
+
140
+ // Return paginated response
141
+ const response = calculatePagination(items, page, limit, total);
142
+
143
+ res.json(response);
144
+ } catch (error) {
145
+ console.error('Pagination error:', error);
146
+ res.status(500).json({
147
+ error: 'Failed to fetch data',
148
+ message: error instanceof Error ? error.message : 'Unknown error'
149
+ });
150
+ }
151
+ };
152
+ }
153
+
154
+ export default {
155
+ calculatePagination,
156
+ validatePaginationParams,
157
+ paginationMiddleware,
158
+ createPaginatedRoute
159
+ };