npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bernierllc/validators-pagination-contract

v1.2.0

Published

Primitive schema validator for pagination contract validation (limit/offset, cursor-based)

Downloads

103

Readme

@bernierllc/validators-pagination-contract

Primitive schema validator for pagination contract validation (limit/offset, cursor-based).

Installation

npm install @bernierllc/validators-pagination-contract

Usage

Basic Validation

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 });

Cursor-based Pagination

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',
  }
);

Custom Validation Options

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,
  }
);

Type Detection

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'

Parameter Normalization

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 }

API Reference

Main Functions

validatePaginationParams(data, options?, utils?)

Validates pagination parameters against contract conventions.

Parameters:

  • data (unknown) - Pagination parameters to validate
  • options (PaginationContractOptions) - Validation options (optional)
  • utils (SharedUtils) - Shared utilities (optional)

Returns: Promise<Problem[]> - Array of validation problems

validatePaginationResponse(response, options?, utils?)

Validates pagination response structure.

Parameters:

  • response (unknown) - Pagination response to validate
  • options (PaginationContractOptions) - Validation options (optional)
  • utils (SharedUtils) - Shared utilities (optional)

Returns: Promise<Problem[]> - Array of validation problems

isValidPagination(data, options?)

Quick check if pagination parameters are valid.

Parameters:

  • data (unknown) - Pagination parameters to check
  • options (PaginationContractOptions) - Validation options (optional)

Returns: Promise<boolean> - 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 parameters
  • options (PaginationContractOptions) - Validation options (optional)

Returns: Promise<Record<string, unknown> | null> - Normalized parameters or null if invalid

Validation Rules

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 link
  • hasPrevious must match presence of previousCursor or previous link
  • Page numbers must be sequential
  • Link URLs must be valid

Types

PaginationContractOptions

interface PaginationContractOptions {
  paginationType?: 'offset' | 'cursor' | 'page';
  checkTotalCount?: boolean;
  checkNavigationLinks?: boolean;
  checkPageBoundaries?: boolean;
  maxLimit?: number;
  minLimit?: number;
  allowNegativeOffset?: boolean;
  requireDataArray?: boolean;
  validateCursorFormat?: boolean;
}

PaginationResponse

interface PaginationResponse<T = unknown> {
  data: T[];
  meta?: PaginationMeta;
  links?: {
    next?: string;
    previous?: string;
    first?: string;
    last?: string;
  };
  pagination?: PaginationMeta;
}

Examples

Express API Middleware

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();
}

Response Validation

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;
}

GraphQL Pagination Validator

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}`);
  }
}

Validation Severity

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)

Performance

  • Lightweight validation with minimal overhead
  • Synchronous type checks with async API for consistency
  • No external dependencies beyond @bernierllc/validators-core

Integration Status

  • 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

See Also

License

Copyright (c) 2025 Bernier LLC. All rights reserved.