用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/majiayu000/claude-skill-registry --skill pagination-implementation命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | pagination-implementation |
| description | Pagination patterns for LIST operations including offset/limit and token-based |
Complete patterns for implementing pagination in LIST operations across all producer implementations.
There are TWO different pagination approaches. YOU CANNOT MIX THEM:
NEVER use both in the same implementation!
Use this for APIs that support offset/limit query parameters.
This is the STANDARD pattern for most LIST operations:
async list(results: PagedResults<ResourceType>, organizationId: string): Promise<void> {
const params: Record<string, number> = {};
// ✅ REQUIRED: Convert pageNumber/pageSize to offset/limit
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
// 🚨 MANDATORY: Always initialize offset when pagination not provided
params.offset = 0;
}
const response = await this.httpClient.get(
`/orgs/${organizationId}/resources`,
{ params }
);
// ✅ REQUIRED: Validate response structure before mapping
if (!response.data || !Array.isArray(response.data.data)) {
throw new UnexpectedError('Invalid response format: expected data array');
}
// ✅ Map without ternary (validation ensures array exists)
results.items = response.data.data.map(toResource);
results.count = response.data.totalCount || 0;
// ❌ DO NOT assign pageToken when using offset/limit pagination
// results.pageToken = ... // WRONG!
}
Use this ONLY for APIs that use cursor-based pagination with tokens.
async list(results: PagedResults<ResourceType>, organizationId: string): Promise<void> {
const params: Record<string, string> = {};
// ✅ Use pageToken from previous request if available
if (results.pageToken) {
params.pageToken = results.pageToken;
}
// ❌ DO NOT use offset/limit when using token-based pagination
// if (results.pageNumber && results.pageSize) { ... } // WRONG!
const response = await this.httpClient.get(
`/orgs/${organizationId}/resources`,
{ params }
);
// ✅ REQUIRED: Validate response structure before mapping
if (!response.data || !Array.isArray(response.data.data)) {
throw new UnexpectedError('Invalid response format: expected data array');
}
// ✅ Map without ternary (validation ensures array exists)
results.items = response.data.data.map(toResource);
results.count = response.data.totalCount || ;
results. = response.[];
}
CRITICAL: The else clause with params.offset = 0 is MANDATORY:
// ✅ CORRECT - Always initialize offset
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
params.offset = 0; // MANDATORY
}
// ❌ WRONG - Missing offset initialization
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
}
// Missing else clause causes undefined offset when pagination not provided
WHY: APIs may require offset parameter even for first page. Missing offset can cause request failures or incorrect results.
REQUIRED: Always validate response structure before mapping:
// ✅ CORRECT - Validate before mapping
if (!response.data || !Array.isArray(response.data.data)) {
throw new UnexpectedError('Invalid response format: expected data array');
}
results.items = response.data.data.map(toResource);
// ❌ WRONG - Ternary without validation
results.items = response.data?.data?.map(toResource) || [];
WHY:
Enforce minimum and maximum limits:
// ✅ CORRECT - Enforce bounds
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
// Breakdown:
// Math.max(results.pageSize, 1) → Minimum 1 item
// Math.min(..., 1000) → Maximum 1000 items
WHY:
Standard conversion pattern:
// Input: PagedResults object with pageNumber and pageSize
// Output: API params with offset and limit
const params: Record<string, number> = {};
if (results.pageNumber && results.pageSize) {
// Page 1, Size 10 → offset: 0, limit: 10
// Page 2, Size 10 → offset: 10, limit: 10
// Page 3, Size 10 → offset: 20, limit: 10
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
// No pagination provided → start from beginning
params.offset = 0;
}
Examples:
Most APIs return paginated responses in this format:
{
"data": [
{ "id": "1", "name": "Resource 1" },
{ "id": "2", "name": "Resource 2" }
],
"totalCount": 42,
"metadata": { ... }
}
// ✅ CORRECT - Offset/Limit pagination mapping
results.items = response.data.data.map(toResource); // Array of mapped items
results.count = response.data.totalCount || 0; // Total count for pagination UI
// ❌ DO NOT assign pageToken for offset/limit pagination
Fields for Offset/Limit Pagination:
items: Mapped domain objects (not raw API data)count: Total number of items across all pagespageToken: NOT USED - left undefined// ✅ CORRECT - Token-based pagination mapping
results.items = response.data.data.map(toResource); // Array of mapped items
results.count = response.data.totalCount || 0; // Total count (if available)
results.pageToken = response.headers['x-next-page-token']; // ✅ Token for next page
Fields for Token-Based Pagination:
items: Mapped domain objects (not raw API data)count: Total count (may not be available in cursor-based pagination)pageToken: Next page token from response headersUse Offset/Limit (Approach 1) when:
offset and limit query parameterstotalCount in responseUse Token-Based (Approach 2) when:
pageToken parameterNEVER:
pageToken when using offset/limitasync listUsers(results: PagedResults<User>, organizationId: string): Promise<void> {
const params: Record<string, number> = {};
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
params.offset = 0;
}
const response = await this.httpClient.get(`/orgs/${organizationId}/users`, { params });
if (!response.data || !Array.isArray(response.data.data)) {
throw new UnexpectedError('Invalid response format: expected data array');
}
results.items = response.data..(toUser);
results. = response.. || ;
}
async listGroupUsers(
results: PagedResults<UserInfo>,
organizationId: string,
groupId: string
): Promise<void> {
const params: Record<string, number> = {};
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
params.offset = 0;
}
const response = await this.httpClient.get(
`/orgs/${organizationId}/groups/${groupId}/users`,
{ params }
);
if (!response.data || !Array.isArray(response.data.data)) {
throw new UnexpectedError('Invalid response format: expected data array');
}
results. = response...(toUserInfo);
results. = response.. || ;
}
async searchResources(
results: PagedResults<Resource>,
organizationId: string,
filter?: string
): Promise<void> {
const params: Record<string, string | number> = {};
// Pagination
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
} else {
params.offset = 0;
}
// Additional filters
if (filter) {
params.q = filter;
}
const response = await this.httpClient.get(
`/orgs/${organizationId}/resources`,
{ params }
);
if (!response.data || !Array.isArray(response.data.)) {
();
}
results. = response...(toResource);
results. = response.. || ;
}
// ❌ WRONG - No else clause
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
}
// params.offset is undefined when pagination not provided
Impact: API requests may fail or return unexpected results.
// ❌ WRONG - Silent failure with empty array
results.items = response.data?.data?.map(toResource) || [];
Impact:
// ❌ WRONG - No bounds checking
params.limit = results.pageSize;
Impact:
// ❌ WRONG - Using pageToken with offset/limit pagination
async list(results: PagedResults<Resource>, orgId: string): Promise<void> {
const params: Record<string, number> = {};
if (results.pageNumber && results.pageSize) {
params.offset = (results.pageNumber - 1) * results.pageSize;
params.limit = Math.min(Math.max(results.pageSize, 1), 1000);
}
// ... fetch and map ...
results.pageToken = response.headers['x-next-page-token']; // ❌ WRONG!
}
Impact:
Correct:
// ✅ CORRECT - Offset/limit WITHOUT pageToken
results.items = response.data.data.map(toResource);
results.count = response.data.totalCount || 0;
// No pageToken assignment for offset/limit pagination
// ❌ WRONG - Using items length as fallback
results.count = response.data.totalCount || response.data.data.length;
Impact:
Correct:
// ✅ CORRECT - Fallback to 0 if missing
results.count = response.data.totalCount || 0;
Before completing a LIST operation with offset/limit pagination:
params object declared as Record<string, number>if (results.pageNumber && results.pageSize) condition presentparams.offset = (results.pageNumber - 1) * results.pageSize calculationparams.limit = Math.min(Math.max(results.pageSize, 1), 1000) boundselse { params.offset = 0; } clause present (MANDATORY)UnexpectedError before mappingresults.items = response.data.data.map(toMapper) (no ternary)results.count = response.data.totalCount || 0 assignmentresults.pageToken assignment/orgs/ URL prefix usedBefore completing a LIST operation with token-based pagination:
params object declared as Record<string, string>if (results.pageToken) condition to use token from previous requestparams.pageToken = results.pageToken assignment when token existsUnexpectedError before mappingresults.items = response.data.data.map(toMapper) (no ternary)results.count = response.data.totalCount || 0 assignment (if available)results.pageToken = response.headers['x-next-page-token'] assignment/orgs/ URL prefix usedThe PagedResults<T> type is provided by @auditmation/types-core-js:
import { PagedResults } from '@auditmation/types-core-js';
interface PagedResults<T> {
items: T[]; // Array of domain objects (output)
count: number; // Total count across all pages (output)
pageNumber?: number; // Current page number (input, optional)
pageSize?: number; // Items per page (input, optional)
pageToken?: string; // Token for next page (output, optional)
}
Input Parameters (provided by caller):
pageNumber: Which page to retrieve (1-based) - Offset/Limit onlypageSize: How many items per page - Offset/Limit onlypageToken: Token from previous response - Token-Based onlyOutput Fields (set by LIST method):
items: Mapped domain objects for current page - Always setcount: Total number of items across all pages - Always setpageToken: Token for next page - Token-Based only, NOT set for Offset/Limit