pagination
API pagination design and implementation
معلومات المصدر
- المستودع
- ffsshhttiikk/opencode-agents-skills
- آخر نشاط في المصدر
- ٢٨ فبراير ٢٠٢٦ في ٢٢:٥٣
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٣
- التفرعات
- ٢
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- name
- pagination
- description
- API pagination design and implementation
- license
- MIT
- compatibility
- opencode
- metadata
- {"audience":"developers","category":"api-design"}
## What I do
- Design pagination for REST APIs
- Implement cursor-based pagination
- Handle offset-based pagination
- Optimize for large datasets
- Support sorting and filtering
- Design consistent response formats
- Handle edge cases
- Document pagination behavior
## When to use me
When implementing pagination in APIs or queries.
## Pagination Strategies
### Offset-Based Pagination
```sql
-- Simple offset pagination
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 40; -- Page 3 (20 per page)
```
**Pros:**
- Simple to implement
- Easy to navigate to specific pages
- URLs are shareable
**Cons:**
- Performance degrades with large offsets
- Inconsistent results with changing data
- Cannot efficiently skip to end
### Cursor-Based Pagination
```sql
-- Cursor pagination using created_at and id
SELECT * FROM posts
WHERE (created_at, id) < (LAST_CURSOR_CREATED_AT, LAST_CURSOR_ID)
ORDER BY created_at DESC, id DESC
LIMIT 20;
```
**Pros:**
- Consistent performance regardless of offset
- Handles real-time data better
- More efficient for large datasets
**Cons:**
- Cannot jump to random page
- More complex implementation
- Requires unique, ordered cursor
### Keyset Pagination
```sql
-- Keyset pagination for sorted results
SELECT * FROM posts
WHERE status = 'published'
AND (score, id) < (LAST_SCORE, LAST_ID)
ORDER BY score DESC, id DESC
LIMIT 20;
```
## Cursor-Based Implementation
```python
from dataclasses import dataclass
from typing import Generic, TypeVar, List, Optional, Dict, Any
import base64
import json
T = TypeVar('T')
@dataclass
class Cursor:
"""Base64-encoded cursor containing ordering values."""
values: tuple
created_at: str # ISO format timestamp
def encode(self) -> str:
"""Encode cursor to base64 string."""
data = {
'v': list(self.values),
'c': self.created_at,
}
return base64.urlsafe_b64encode(
json.dumps(data).encode('utf-8')
).decode('utf-8')
@classmethod
def decode(cls, cursor_str: str) -> Optional['Cursor']:
"""Decode cursor from base64 string."""
try:
data = json.loads(
base64.urlsafe_b64decode(
cursor_str.encode('utf-8')
).decode('utf-8')
)
return cls(values=tuple(data['v']), created_at=data['c'])
except Exception:
return None
class Paginator(Generic[T]):
"""Cursor-based paginator for API results."""
def __init__(
self,
queryset,
limit: int = 20,
max_limit: int = 100
) -> None:
self.queryset = queryset
self.limit = limit
self.max_limit = min(limit, max_limit)
def paginate(
self,
cursor_str: Optional[str] = None,
sort_by: str = 'created_at',
sort_order: str = 'desc'
) -> Dict[str, Any]:
"""Get paginated results."""
limit = self.max_limit
# Decode cursor
cursor = None
if cursor_str:
cursor = Cursor.decode(cursor_str)
if cursor:
# Extract cursor values (exclude timestamp)
cursor_values = cursor.values
else:
cursor_values = None
else:
cursor_values = None
# Build query
query = self.queryset
if cursor_values and sort_order == 'desc':
# For descending, use < comparison
filters = {
f'{sort_by}__lt': cursor_values[0],
}
if len(cursor_values) > 1:
filters['id__lt'] = cursor_values[1]
query = query.filter(**filters)
# Order and limit
order = '-' + sort_by if sort_order == 'desc' else sort_by
items = query.order_by(order)[:limit + 1]
# Check if there are more results
has_next = len(items) > limit
items = items[:limit]
# Build response
next_cursor = None
if has_next and items:
last_item = items[-1]
next_cursor = Cursor(
values=(getattr(last_item, sort_by), last_item.id),
created_at=datetime.utcnow().isoformat()
).encode()
return {
'items': items,
'next_cursor': next_cursor,
'has_next': has_next,
}
def paginated_response(
items: List[Any],
next_cursor: Optional[str] = None,
has_next: bool = False,
total_count: Optional[int] = None
) -> Dict[str, Any]:
"""Build standardized paginated API response."""
response = {
'data': items,
'pagination': {
'next_cursor': next_cursor,
'has_next': has_next,
}
}
if total_count is not None:
response['pagination']['total'] = total_count
return response
```
## API Response Format
```json
{
"data": [
{
"id": "507f1f77bcf86cd799439011",
"title": "First Post",
"author": {
"id": "123",
"name": "John Doe"
},
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": "507f1f77bcf86cd799439012",
"title": "Second Post",
"author": {
"id": "456",
"name": "Jane Smith"
},
"created_at": "2024-01-14T15:20:00Z"
}
],
"links": {
"self": "/api/v1/posts?cursor=abc123&limit=20",
"first": "/api/v1/posts?limit=20",
"next": "/api/v1/posts?cursor=def456&limit=20",
"prev": null
},
"meta": {
"total_count": 1500,
"page_size": 20,
"current_page": 1
}
}
```
## Sorting and Filtering
```python
from enum import Enum
class SortOrder(str, Enum):
ASC = "asc"
DESC = "desc"
class SortField:
"""Validate and map sort fields."""
ALLOWED_FIELDS = {
'created_at',
'updated_at',
'title',
'author_name',
'popularity',
}
@classmethod
def validate(cls, field: str) -> Optional[str]:
"""Return validated field or None."""
if field in cls.ALLOWED_FIELDS:
return field
return None
class FilterParser:
"""Parse and apply filter parameters."""
SUPPORTED_FILTERS = {
'status': {'draft', 'published', 'archived'},
'author_id': None, # Any string
'created_after': None, # ISO date
'created_before': None,
'has_tags': None, # Comma-separated
}
@classmethod
def parse(cls, params: Dict[str, str]) -> Dict[str, Any]:
"""Parse filter parameters."""
filters = {}
for key, value in params.items():
if key in cls.SUPPORTED_FILTERS:
if key.startswith('created_'):
filters[key] = cls._parse_date(value)
elif key == 'has_tags':
filters['tags__in'] = value.split(',')
else:
allowed = cls.SUPPORTED_FILTERS[key]
if allowed is None or value in allowed:
filters[key] = value
return filters
@staticmethod
def _parse_date(value: str) -> datetime:
"""Parse ISO date string."""
try:
return datetime.fromisoformat(value.replace('Z', '+00:00'))
except ValueError:
raise ValueError(f"Invalid date format: {value}")
```
## Edge Cases
```python
class PaginationEdgeCases:
"""Handle pagination edge cases."""
@staticmethod
def handle_empty_results(cursor: Optional[str]) -> Dict[str, Any]:
"""Handle empty result set."""
return {
'data': [],
'links': {
'self': f"/api/v1/posts?cursor={cursor or ''}",
'first': "/api/v1/posts",
'next': None,
'prev': None,
},
'meta': {
'total': 0,
'page_size': 20,
},
}
@staticmethod
def handle_single_page(cursor: str, items: List) -> Dict[str, Any]:
"""Handle result set that fits on one page."""
return {
'data': items,
'links': {
'self': f"/api/v1/posts?cursor={cursor}",
'first': "/api/v1/posts",
'next': None,
'prev': None,
},
'meta': {
'total': len(items),
'page_size': len(items),
},
}
@staticmethod
def validate_cursor(
cursor: str,
max_age_seconds: int = 86400
) -> bool:
"""Validate cursor hasn't expired."""
decoded = Cursor.decode(cursor)
if not decoded:
return False
# Check timestamp
cursor_time = datetime.fromisoformat(decoded.created_at)
max_age = timedelta(seconds=max_age_seconds)
return datetime.utcnow() - cursor_time < max_age
```
## Different Pagination Styles
```json
// Offset-based (page 2 of 10 items per page)
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 10,
"total": 100,
"total_pages": 10,
"has_next": true,
"has_prev": true
}
}
// Cursor-based
{
"data": [...],
"pagination": {
"next_cursor": "abc123...",
"has_next": true,
"has_prev": false
}
}
// Relative (links for next/prev)
{
"data": [...],
"links": {
"self": "/posts?page=2",
"first": "/posts?page=1",
"prev": "/posts?page=1",
"next": "/posts?page=3",
"last": "/posts?page=10"
}
}
// Header-based
// Link: </posts?cursor=abc>; rel="next", </posts?cursor=def>; rel="prev"
```
عرض على GitHub