用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ffsshhttiikk/opencode-agents-skills --skill pagination命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | pagination |
| description | API pagination design and implementation |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"developers","category":"api-design"} |
When implementing pagination in APIs or queries.
-- Simple offset pagination
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 40; -- Page 3 (20 per page)
Pros:
Cons:
-- 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:
Cons:
-- 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;
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[])
Exception:
([T]):
() -> :
.queryset = queryset
.limit = limit
.max_limit = (limit, max_limit)
() -> [, ]:
limit = .max_limit
cursor =
cursor_str:
cursor = Cursor.decode(cursor_str)
cursor:
cursor_values = cursor.values
:
cursor_values =
:
cursor_values =
query = .queryset
cursor_values sort_order == :
filters = {
: cursor_values[],
}
(cursor_values) > :
filters[] = cursor_values[]
query = query.(**filters)
order = + sort_by sort_order == sort_by
items = query.order_by(order)[:limit + ]
has_next = (items) > limit
items = items[:limit]
next_cursor =
has_next items:
last_item = items[-]
next_cursor = Cursor(
values=((last_item, sort_by), last_item.),
created_at=datetime.utcnow().isoformat()
).encode()
{
: items,
: next_cursor,
: has_next,
}
() -> [, ]:
response = {
: items,
: {
: next_cursor,
: has_next,
}
}
total_count :
response[][] = total_count
response
{
"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"
}
],
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() -> [, ]:
filters = {}
key, value params.items():
key cls.SUPPORTED_FILTERS:
key.startswith():
filters[key] = cls._parse_date(value)
key == :
filters[] = value.split()
:
allowed = cls.SUPPORTED_FILTERS[key]
allowed value allowed:
filters[key] = value
filters
() -> datetime:
:
datetime.fromisoformat(value.replace(, ))
ValueError:
ValueError()
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': ,
: ,
: ,
},
: {
: (items),
: (items),
},
}
() -> :
decoded = Cursor.decode(cursor)
decoded:
cursor_time = datetime.fromisoformat(decoded.created_at)
max_age = timedelta(seconds=max_age_seconds)
datetime.utcnow() - cursor_time < max_age
// 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":
...