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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@treedom/paginated-connection

v0.2.1

Published

Paginated connection utility library

Downloads

101

Readme

Paginated Connection

Paginated Connection is a utility library for handling pagination in your applications. It simplifies the process of managing paginated data, making it easy to integrate into your projects. It has built for GraphQL, and it's fully compliant with GraphQL Cursor Connections Specification

Made with ❤️ at  , join us in making a difference!

Table of Contents

Introduction

Pagination is essential for managing large datasets in a user-friendly manner. Paginated Connection provides a straightforward way to implement pagination logic in your applications, supporting both simple and complex use cases.

Features

Installation

To install Paginated Connection:

npm install @treedom/paginated-connection

Usage

Basic example

Here is a basic example to get you started with Paginated Connection:

import { paginatedConnection, PaginationInput } from '@treedom/paginated-connection'

// Define a simple node type
type Node = {
  id: string;
};

// Define encode and decode functions

// Function to get cursor object from node
const getCursor = node => ({ after: node.id });

// encodeCursor should return a string
const encodeCursor = ({ node, getCursor }) => Buffer.from(JSON.stringify(getCursor())).toString('base64');

// decodeCursor should return an object 
const decodeCursor = cursor => JSON.parse(Buffer.from(cursor, 'base64url').toString())

// Sample data loader
const dataLoader = async ({ cursor, first, encodeCursor, getEdge }) => {
  // Fetch data based on cursor and first
  const edges = fetchDataFromDataSource(cursor, first);
  return {
    edges: edges.map(node => getEdge(node, getCursor)),
    hasNextPage: checkIfHasNextPage(),
  };
};

// Sample count loader
const countLoader = async ({ cursor }) => {
  return countDataFromDataSource(cursor);
};

const paginationInput: PaginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;

const result = await paginatedConnection<Node>({
  pagination: paginationInput,
  paginationSafeLimit,
  dataLoader,
  encodeCursor,
  decodeCursor,
  countLoader,
});

console.log(result);

Mysql Example

Using Paginated Connection with MySQL:

import { mysqlPaginatedConnection } from '@treedom/paginated-connection';

// Define a simple node type
type Node = {
  id: string;
};

// Function to get cursor object from node
const getCursor = node => ({ after: node.id });

// Define MySQL specific data loader
const mysqlDataLoader = async ({ cursor, first, encodeCursor }) => {
  // Fetch data from MySQL database
  const edges = fetchDataFromMySQL(cursor, first);
  return {
    edges: edges.map(node => getEdge(node, getCursor)),
  };
};

// Define MySQL specific count loader
const mysqlCountLoader = async ({ cursor }) => {
  return countDataInMySQL(cursor);
};

const paginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;

const result = await mysqlPaginatedConnection<Node>({
  pagination: paginationInput,
  paginationSafeLimit,
  dataLoader: mysqlDataLoader,
  countLoader: mysqlCountLoader,
});

console.log(result);

In the MySQL implementation, the +1 handling of data for the calculation of the hasNextPage value is implicitly managed by the function execution. This means you don't need to handle it yourself. The hasNextPage value is automatically calculated, so you should not return it in your data loader.

MongoDB Example

Using Paginated Connection with MongoDB:

import { mongoDbPaginatedConnection } from '@treedom/paginated-connection';

// Define a simple node type
type Node = {
  id: string;
};

// Function to get cursor object from node
const getCursor = node => ({ after: node.id });

// Define MongoDB specific data loader
const mongoDbDataLoader = async ({ cursor, first, encodeCursor }) => {
  // Fetch data from MongoDB
  const edges = fetchDataFromMongoDB(cursor, first);
  return {
    edges: edges.map(node => getEdge(node, getCursor))
  };
};

// Define MongoDB specific count loader
const mongoDbCountLoader = async ({ cursor }) => {
  return countDataInMongoDB(cursor);
};


const paginationInput = { after: 'cursor123', first: 10 };
const paginationSafeLimit = 50;

const result = await mongoDbPaginatedConnection<Node>({
  pagination: paginationInput,
  paginationSafeLimit,
  dataLoader: mongoDbDataLoader,
  countLoader: mongoDbCountLoader,
});

console.log(result);

In the MongoDB implementation, the +1 handling of data for the calculation of the hasNextPage value is implicitly managed by the function execution. This means you don't need to handle it yourself. The hasNextPage value is automatically calculated, so you should not return it in your data loader.

Return value

Every paginatedConnection function returns an object of PaginatedConnectionReturnType:

export type PaginatedConnectionReturnType<TNode> = Promise<{
  totalCount: () => Promise<number>
  pageInfo: {
    endCursor: string
    hasNextPage: boolean
  }
  edges: Array<{
    node: TNode
    cursor: string
  }>
}>

where TNode is the type of the node loaded by dataLoader function.

Edges

Compose using getEdge

When executing dataloader function, it provides getEdge function, which is a shortcut to return an Edge object. Object returned by getEdge will contain both node and cursor values.

This function is very useful to avoid write boilerplate code to compose the Edge object, specially for cursor. Under the hood, it executes the encodeCursor function, providing cursor inside of return object Edge.

const dataLoader = async ({ cursor, first, encodeCursor }) => {
  const nodes = fetchDataFromDataSource(cursor, first);
  return {
    edges: nodes.map(node => getEdge(node, getCursor)),
    hasNextPage: checkIfHasNextPage(),
  };
};

Function getEdge gets in input:

  • node object, which should has TNode type;
  • getCursor function, which should returns an object of type TCursor.

Compose using getEdges

When executing dataloader function, it provides getEdges function, which is a shortcut to return an Edges array. Every item returned by getEdges will contain both node and cursor values.

This function is very useful when you have an array of loaded items, which every item is already typed as TNode and ready to be used as a node inside Edge.

const dataLoader = async ({ cursor, first, encodeCursor }) => {
  const nodes = fetchDataFromDataSource(cursor, first); // nodes is an array of TNode object
  return {
    edges: getEdges(nodes, getCursor),
    hasNextPage: checkIfHasNextPage(),
  };
};

Function getEdges gets in input:

  • nodes array, which should has Array<TNode> type;
  • getCursor function, which should returns an object of type TCursor.

Under the hood, it executes the encodeCursor function, in order to provide the cursor inside of Edge.

Compose manually

If you need more customization of data, Edges could be manually composed, returning an array of Edge.

const dataLoader = async ({ cursor, first, encodeCursor }) => {
  const edges = fetchDataFromDataSource(cursor, first);
  return {
    edges: edges.map(node => ({ node, cursor: encodeCursor({ node, getCursor }) })),
    hasNextPage: checkIfHasNextPage(),
  };
};

Cursor Types

Default Cursor Type

By default, the cursor type only includes an after field, which is a string. This is simple and suitable for basic pagination scenarios.

{ after: string };

The default cursor is used when no specific cursor type is provided to paginatedConnection (or mysqlPaginatedConnection, mongoDbPaginatedConnection, ecc...):

type Node = {
  id: string;
};

const paginationInput = { after: 'cursor123', first: 10 };

// Return value should be an object containing `after` field only
const getCursor = (node): { after: string } => ({
  after: node.id,
});

// Here we're not passing any custom cursor type to paginatedConnection, so it'll use the default type
const result = await paginatedConnection<Node>({
  ...
  dataLoader,
  ...
});

Custom Cursor Type

For more complex scenarios, you can customize the cursor type to include additional fields, such as sorting information. The value of all cursor fields must be one of the following:

  • string
  • number
  • boolean
type CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc', ranking: number, includeMetadata: boolean };

When using a custom cursor type, you need to type the paginatedConnection (or mysqlPaginatedConnection, mongoDbPaginatedConnection, ecc...), providing cursor custom type:

import { paginatedConnection } from '@treedom/paginated-connection';

type Node = {
  id: string;
  sortField: string;
};

// Custom cursor type
type CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc' };

// Return value should be an object of type `CustomCursor`
const getCursor = (node): CustomCursor => ({
  after: node.id,
  sortField: node.sortField,
  sortOrder: 'asc',
});

// Sample data loader
const dataLoader = async ({ cursor, first, encodeCursor }) => {
  const edges = fetchDataFromDataSource(cursor, first);
  return {
    edges: edges.map(node => getEdge(node, getCursor)),
    hasNextPage: checkIfHasNextPage(),
  };
};

// Provide CustomCursor type
const result = await paginatedConnection<Node, CustomCursor>({
  ...
  dataLoader,
  ...
});

console.log(result);

API Reference

paginatedConnection

paginatedConnection<TNode, TCursor>(props: PaginatedConnectionProps<TNode, TCursor>)

Handles pagination to offset-style ordering, returning Connection-style GraphQL result.

  • props (PaginatedConnectionProps):
    • pagination (PaginationInput): Pagination parameters.
    • paginationSafeLimit (number): Safe limit for pagination.
    • dataLoader ((props: DataloaderProps<TNode, TCursor>) => Promise<{ edges: { node: TNode; cursor: string }[]; -- hasNextPage: boolean }>): Data loader function.
    • encodeCursor (EncodeCursor<TNode, TCursor>): Function to encode cursor, it should return a string.
    • decodeCursor ((cursor: string) => TCursor): Function to decode cursor.
    • countLoader ((props: CountLoaderProps<TCursor>) => Promise<number>): Count loader function.

mysqlPaginatedConnection

mysqlPaginatedConnection<TNode, TCursor>(props: MysqlPaginatedConnectionProps<TNode, TCursor>)

Handles pagination for MySQL databases, extending the basic paginatedConnection.

  • props (MysqlPaginatedConnectionProps):
    • dataLoader ((props: DataloaderProps<TNode, TCursor>) => Promise<{ edges: { node: TNode; cursor: string }[]; }>): MySQL data loader.
    • countLoader ((props: CountLoaderProps<TCursor>) => Promise<number>): MySQL count loader.
    • pagination (PaginationInput): Pagination parameters.
    • paginationSafeLimit (number): Safe limit for pagination.

In the MySQL implementation, the +1 handling of data for the calculation of the hasNextPage value is implicitly managed by the function execution. This means you don't need to handle it yourself. The hasNextPage value is automatically calculated, so you should not return this value in your data loader.

mongoDbPaginatedConnection

mongoDbPaginatedConnection<TNode, TCursor>(props: MongoDbPaginatedConnectionProps<TNode, TCursor>)

Handles pagination for MongoDB databases, extending the basic paginatedConnection.

  • props (MongoDbPaginatedConnectionProps):
    • dataLoader ((props: DataloaderProps<TNode, TCursor>) => Promise<{ edges: { node: TNode; cursor: string }[]; }>): MongoDB data loader.
    • countLoader ((props: CountLoaderProps<TCursor>) => Promise<number>): MongoDB count loader.
    • pagination (PaginationInput): Pagination parameters.
    • paginationSafeLimit (number): Safe limit for pagination.

In the MongoDB implementation, the +1 handling of data for the calculation of the hasNextPage value is implicitly managed by the function execution. This means you don't need to handle it yourself. The hasNextPage value is automatically calculated, so you should not return this value in your data loader.

🌳 Join Us in Making a Difference! 🌳

We invite all developers who use Treedom's open-source code to support our mission of sustainability by planting a tree with us. By contributing to reforestation efforts, you help create a healthier planet and give back to the environment. Visit our Treedom Open Source Forest to plant your tree today and join our community of eco-conscious developers.

Additionally, you can integrate the Treedom GitHub badge into your repository to showcase the number of trees in your Treedom forest and encourage others to plant new ones. Check out our integration guide to get started.

Together, we can make a lasting impact! 🌍💚

Contributing

Contributions are welcome! Please read the contributing guidelines before submitting a pull request.

License

This project is licensed under the MIT License.