Primitive schema validator for pagination contract validation (limit/offset, cursor-based)
npm install @bernierllc/validators-pagination-contractPrimitive schema validator for pagination contract validation (limit/offset, cursor-based).
``bash`
npm install @bernierllc/validators-pagination-contract
`typescript
import {
validatePaginationParams,
validatePaginationResponse,
isValidPagination,
} from '@bernierllc/validators-pagination-contract';
// Validate offset/limit pagination parameters
const problems = await validatePaginationParams({
limit: 20,
offset: 0,
});
if (problems.length === 0) {
console.log('Valid pagination parameters');
}
// Validate pagination response structure
const response = {
data: [{ id: 1 }, { id: 2 }],
meta: {
total: 100,
count: 2,
hasNext: true,
},
};
const responseProblems = await validatePaginationResponse(response);
// Quick validation check
const isValid = await isValidPagination({ limit: 10, offset: 0 });
`
`typescript
import { validatePaginationParams } from '@bernierllc/validators-pagination-contract';
// Validate cursor pagination
const problems = await validatePaginationParams(
{
cursor: 'abc123',
limit: 20,
},
{
paginationType: 'cursor',
}
);
// Validate with after/before cursors
const cursorProblems = await validatePaginationParams(
{
after: 'xyz789',
limit: 10,
},
{
paginationType: 'cursor',
}
);
`
`typescript
import { validatePaginationParams } from '@bernierllc/validators-pagination-contract';
const problems = await validatePaginationParams(
{
limit: 50,
offset: 100,
},
{
maxLimit: 100,
minLimit: 1,
allowNegativeOffset: false,
checkTotalCount: true,
checkNavigationLinks: true,
checkPageBoundaries: true,
}
);
`
`typescript
import { detectPaginationType } from '@bernierllc/validators-pagination-contract';
const type = detectPaginationType({ limit: 10, offset: 0 });
// Returns: 'offset'
const cursorType = detectPaginationType({ cursor: 'abc123' });
// Returns: 'cursor'
const pageType = detectPaginationType({ page: 1, limit: 10 });
// Returns: 'page'
`
`typescript
import { normalizePaginationParams } from '@bernierllc/validators-pagination-contract';
// Normalize string parameters to numbers
const normalized = await normalizePaginationParams({
limit: '20',
offset: '0',
});
// Returns: { limit: 20, offset: 0 }
// Clamp values to allowed ranges
const clamped = await normalizePaginationParams(
{ limit: 200 },
{ maxLimit: 100 }
);
// Returns: { limit: 100 }
`
#### validatePaginationParams(data, options?, utils?)
Validates pagination parameters against contract conventions.
Parameters:
- data (unknown) - Pagination parameters to validateoptions
- (PaginationContractOptions) - Validation options (optional)utils
- (SharedUtils) - Shared utilities (optional)
Returns: Promise - Array of validation problems
#### validatePaginationResponse(response, options?, utils?)
Validates pagination response structure.
Parameters:
- response (unknown) - Pagination response to validateoptions
- (PaginationContractOptions) - Validation options (optional)utils
- (SharedUtils) - Shared utilities (optional)
Returns: Promise - Array of validation problems
#### isValidPagination(data, options?)
Quick check if pagination parameters are valid.
Parameters:
- data (unknown) - Pagination parameters to checkoptions
- (PaginationContractOptions) - Validation options (optional)
Returns: Promise - true if valid, false otherwise
#### detectPaginationType(data)
Detects the pagination type from parameters.
Parameters:
- data (unknown) - Pagination parameters
Returns: PaginationType | null - Detected type ('offset', 'cursor', 'page') or null
#### normalizePaginationParams(data, options?)
Normalizes and validates pagination parameters.
Parameters:
- data (unknown) - Raw pagination parametersoptions
- (PaginationContractOptions) - Validation options (optional)
Returns: Promise - Normalized parameters or null if invalid
The package includes the following validation rules:
#### offsetLimitRule
Validates offset/limit pagination parameters:
- Limit must be a positive integer within bounds
- Offset must be a non-negative integer (unless allowNegativeOffset is true)
- Checks min/max limit constraints
#### cursorPaginationRule
Validates cursor-based pagination:
- Cursor must be a non-empty string
- Cannot specify both after and before cursors
- Validates cursor format if enabled
#### responseStructureRule
Validates pagination response structure:
- Data field must be an array (if required)
- Metadata fields must have correct types
- Links must be valid URLs
#### totalCountRule
Validates total count consistency:
- Count must match data array length
- Count cannot exceed total
- Total pages calculation must be correct
- Current page must be within bounds
#### navigationLinksRule
Validates navigation links consistency:
- hasNext must match presence of nextCursor or next linkhasPrevious
- must match presence of previousCursor or previous link
- Page numbers must be sequential
- Link URLs must be valid
#### PaginationContractOptions
`typescript`
interface PaginationContractOptions {
paginationType?: 'offset' | 'cursor' | 'page';
checkTotalCount?: boolean;
checkNavigationLinks?: boolean;
checkPageBoundaries?: boolean;
maxLimit?: number;
minLimit?: number;
allowNegativeOffset?: boolean;
requireDataArray?: boolean;
validateCursorFormat?: boolean;
}
#### PaginationResponse
`typescript`
interface PaginationResponse
data: T[];
meta?: PaginationMeta;
links?: {
next?: string;
previous?: string;
first?: string;
last?: string;
};
pagination?: PaginationMeta;
}
`typescript
import { validatePaginationParams, normalizePaginationParams } from '@bernierllc/validators-pagination-contract';
async function paginationMiddleware(req, res, next) {
const normalized = await normalizePaginationParams(req.query, {
maxLimit: 100,
minLimit: 1,
});
if (!normalized) {
return res.status(400).json({ error: 'Invalid pagination parameters' });
}
req.pagination = normalized;
next();
}
`
`typescript
import { validatePaginationResponse } from '@bernierllc/validators-pagination-contract';
async function validateApiResponse(response) {
const problems = await validatePaginationResponse(response, {
requireDataArray: true,
checkTotalCount: true,
checkNavigationLinks: true,
});
if (problems.length > 0) {
console.error('Response validation errors:', problems);
return false;
}
return true;
}
`
`typescript
import { validatePaginationParams } from '@bernierllc/validators-pagination-contract';
async function validateGraphQLPagination(args) {
const problems = await validatePaginationParams(
{
after: args.after,
before: args.before,
limit: args.first || args.last,
},
{
paginationType: 'cursor',
maxLimit: 100,
}
);
if (problems.length > 0) {
throw new Error(Invalid pagination: ${problems[0].message});``
}
}
The validator reports problems with different severity levels:
- error: Critical issues that violate API contract (invalid types, out of bounds values)
- warning: Inconsistencies that may indicate bugs (mismatched flags and links)
- Lightweight validation with minimal overhead
- Synchronous type checks with async API for consistency
- No external dependencies beyond @bernierllc/validators-core
- Logger: not-applicable - Pure validation utility with no logging requirements
- Docs-Suite: ready - Complete TypeDoc documentation with markdown README
- NeverHub: not-applicable - Stateless validation utility with no service dependencies
- @bernierllc/validators-core - Core validation framework
- @bernierllc/validators-json-structure - JSON structure validation
- @bernierllc/validators-runner - Validation execution engine
Copyright (c) 2025 Bernier LLC. All rights reserved.