원클릭으로
pagination
Implement cursor-based and offset pagination for APIs. Covers efficient database queries, stable sorting, and pagination metadata.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Implement cursor-based and offset pagination for APIs. Covers efficient database queries, stable sorting, and pagination metadata.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Two-phase commit matchmaking that verifies both player connections before creating a match. Handles disconnections gracefully with automatic re-queue of healthy players.
Implement robust background job processing with dead letter queues, retries, and state machines. Use when building async workflows, scheduled tasks, or any work that shouldn't block the request/response cycle.
Manage data flow when producers outpace consumers. Bounded buffers, adaptive flushing, and graceful degradation prevent OOM crashes and data loss.
Collect-then-batch pattern for database operations achieving 30-40% throughput improvement. Includes graceful fallback to sequential processing when batch operations fail.
Implement multi-layer caching with Redis, in-memory, and HTTP caching. Covers cache invalidation, stampede prevention, and cache-aside patterns.
Exactly-once processing semantics with distributed coordination for file-based data pipelines. Atomic file claiming, status tracking, and automatic retry with in-memory fallback.
| name | pagination |
| description | Implement cursor-based and offset pagination for APIs. Covers efficient database queries, stable sorting, and pagination metadata. |
| license | MIT |
| compatibility | TypeScript/JavaScript, Python |
| metadata | {"category":"api","time":"2h","source":"drift-masterguide"} |
Return large datasets efficiently without killing your database.
GET /users?page=2&limit=20
Pros: Simple, supports "jump to page" Cons: Slow on large datasets, inconsistent with concurrent writes
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
Pros: Fast, consistent, works with real-time data Cons: No "jump to page", slightly more complex
// pagination.ts
interface PaginationParams {
cursor?: string;
limit?: number;
direction?: 'forward' | 'backward';
}
interface PaginatedResult<T> {
data: T[];
pagination: {
hasMore: boolean;
nextCursor: string | null;
prevCursor: string | null;
total?: number;
};
}
function encodeCursor(data: Record<string, unknown>): string {
return Buffer.from(JSON.stringify(data)).toString('base64url');
}
function decodeCursor(cursor: string): Record<string, unknown> {
return JSON.parse(Buffer.from(cursor, 'base64url').toString());
}
async function paginate<T extends { id: string; createdAt: Date }>(
query: (where: any, orderBy: any, take: number) => Promise<T[]>,
params: PaginationParams,
defaultLimit = 20,
maxLimit = 100
): Promise<PaginatedResult<T>> {
const limit = Math.min(params.limit || defaultLimit, maxLimit);
const direction = params.direction || 'forward';
let where: any = {};
let orderBy: any = { createdAt: 'desc', id: 'desc' };
if (params.cursor) {
const decoded = decodeCursor(params.cursor);
if (direction === 'forward') {
where = {
OR: [
{ createdAt: { lt: decoded.createdAt } },
{ createdAt: decoded.createdAt, id: { lt: decoded.id } },
],
};
} else {
where = {
OR: [
{ createdAt: { gt: decoded.createdAt } },
{ createdAt: decoded.createdAt, id: { gt: decoded.id } },
],
};
orderBy = { createdAt: 'asc', id: 'asc' };
}
}
// Fetch one extra to check if there's more
const items = await query(where, orderBy, limit + 1);
const hasMore = items.length > limit;
const data = hasMore ? items.slice(0, limit) : items;
// Reverse if going backward
if (direction === 'backward') {
data.reverse();
}
return {
data,
pagination: {
hasMore,
nextCursor: data.length > 0
? encodeCursor({ createdAt: data[data.length - 1].createdAt, id: data[data.length - 1].id })
: null,
prevCursor: data.length > 0
? encodeCursor({ createdAt: data[0].createdAt, id: data[0].id })
: null,
},
};
}
export { paginate, PaginationParams, PaginatedResult, encodeCursor, decodeCursor };
// users-route.ts
import { paginate } from './pagination';
router.get('/users', async (req, res) => {
const { cursor, limit } = req.query;
const result = await paginate(
(where, orderBy, take) =>
db.users.findMany({ where, orderBy, take }),
{ cursor: cursor as string, limit: Number(limit) || 20 }
);
res.json(result);
});
{
"data": [
{ "id": "user_123", "name": "Alice", "createdAt": "2024-01-15T10:00:00Z" },
{ "id": "user_122", "name": "Bob", "createdAt": "2024-01-14T09:00:00Z" }
],
"pagination": {
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI0LTAxLTE0VDA5OjAwOjAwWiIsImlkIjoidXNlcl8xMjIifQ",
"prevCursor": "eyJjcmVhdGVkQXQiOiIyMDI0LTAxLTE1VDEwOjAwOjAwWiIsImlkIjoidXNlcl8xMjMifQ"
}
}
# pagination.py
import base64
import json
from dataclasses import dataclass
from typing import TypeVar, Generic, Callable, Optional
T = TypeVar('T')
@dataclass
class PaginatedResult(Generic[T]):
data: list[T]
has_more: bool
next_cursor: Optional[str]
prev_cursor: Optional[str]
def encode_cursor(data: dict) -> str:
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode()
def decode_cursor(cursor: str) -> dict:
return json.loads(base64.urlsafe_b64decode(cursor).decode())
async def paginate(
query_fn: Callable,
cursor: Optional[str] = None,
limit: int = 20,
max_limit: int = 100,
) -> PaginatedResult:
limit = min(limit, max_limit)
filters = {}
if cursor:
decoded = decode_cursor(cursor)
filters = {"created_at__lt": decoded["created_at"]}
items = await query_fn(filters, limit + 1)
has_more = len(items) > limit
data = items[:limit] if has_more else items
return PaginatedResult(
data=data,
has_more=has_more,
next_cursor=encode_cursor({"created_at": data[-1].created_at.isoformat()}) if data else None,
prev_cursor=encode_cursor({"created_at": data[0].created_at.isoformat()}) if data else None,
)
@router.get("/users")
async def list_users(cursor: str = None, limit: int = 20):
async def query(filters, take):
return await db.users.find_many(
where=filters,
order_by={"created_at": "desc"},
take=take,
)
result = await paginate(query, cursor=cursor, limit=limit)
return {
"data": result.data,
"pagination": {
"hasMore": result.has_more,
"nextCursor": result.next_cursor,
},
}
-- Essential index for cursor pagination
CREATE INDEX idx_users_pagination ON users(created_at DESC, id DESC);
-- For filtered pagination
CREATE INDEX idx_users_org_pagination ON users(organization_id, created_at DESC, id DESC);
// useInfiniteQuery with cursor pagination
function useUsers() {
return useInfiniteQuery({
queryKey: ['users'],
queryFn: ({ pageParam }) =>
fetch(`/api/users?cursor=${pageParam || ''}`).then(r => r.json()),
getNextPageParam: (lastPage) =>
lastPage.pagination.hasMore ? lastPage.pagination.nextCursor : undefined,
});
}
// Usage
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useUsers();
const allUsers = data?.pages.flatMap(page => page.data) ?? [];