{"id":23002659,"url":"https://github.com/treedomtrees/paginated-connection","last_synced_at":"2025-08-14T01:31:27.420Z","repository":{"id":244393072,"uuid":"761280064","full_name":"treedomtrees/paginated-connection","owner":"treedomtrees","description":"Paginated connection utility library","archived":false,"fork":false,"pushed_at":"2024-09-02T07:51:15.000Z","size":114,"stargazers_count":1,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-08-08T23:27:33.631Z","etag":null,"topics":["graphql","nodejs","pagination"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/treedomtrees.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":["https://www.treedom.net/en/organization/treedom/event/treedom-open-source?utm_source=github"]}},"created_at":"2024-02-21T15:19:34.000Z","updated_at":"2024-09-02T07:50:10.000Z","dependencies_parsed_at":"2024-12-15T07:12:10.622Z","dependency_job_id":"5e16aa5c-cf9e-4a93-b345-f24225b23a7e","html_url":"https://github.com/treedomtrees/paginated-connection","commit_stats":null,"previous_names":["treedomtrees/paginated-connection"],"tags_count":10,"template":false,"template_full_name":"treedomtrees/ts-package-template","purl":"pkg:github/treedomtrees/paginated-connection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treedomtrees%2Fpaginated-connection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treedomtrees%2Fpaginated-connection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treedomtrees%2Fpaginated-connection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treedomtrees%2Fpaginated-connection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/treedomtrees","download_url":"https://codeload.github.com/treedomtrees/paginated-connection/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/treedomtrees%2Fpaginated-connection/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270347431,"owners_count":24568569,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-13T02:00:09.904Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","nodejs","pagination"],"created_at":"2024-12-15T07:11:57.597Z","updated_at":"2025-08-14T01:31:27.182Z","avatar_url":"https://github.com/treedomtrees.png","language":"TypeScript","funding_links":["https://www.treedom.net/en/organization/treedom/event/treedom-open-source?utm_source=github"],"categories":[],"sub_categories":[],"readme":"# Paginated Connection\n\n\u003ca href=\"https://www.treedom.net/it/organization/treedom/event/treedom-open-source\"\u003e\u003cimg src=\"https://badges.treedom.net/badge/f/treedom-open-source\" alt=\"plant-a-tree\" border=\"0\" /\u003e\u003c/a\u003e\n\nPaginated 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](https://relay.dev/graphql/connections.htm) \n\n__Made with ❤️ at\u0026nbsp;\u0026nbsp;[\u003cimg src=\"https://assets.treedom.net/image/upload/manual_uploads/treedom-logo-contrib_gjrzt6.png\" height=\"24\" alt=\"Treedom\" border=\"0\" align=\"top\" /\u003e](#-join-us-in-making-a-difference-)__, [join us in making a difference](#-join-us-in-making-a-difference-)! \n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Basic Example](#basic-example)\n  - [MySQL Example](#mysql-example)\n  - [MongoDB Example](#mongodb-example)\n  - [Return value](#return-value)\n- [Edges](#edges)\n  - [Compose using getEdge](#compose-using-getEdge)\n  - [Compose using getEdges](#compose-using-getEdges)\n  - [Compose manually](#compose-manually)\n- [Cursor Types](#cursor-types)\n  - [Default Cursor Type](#default-cursor-type)\n  - [Custom Cursor Type](#custom-cursor-type)\n\n- [API Reference](#api-reference)\n  - [paginatedConnection](#paginatedconnection)\n  - [mysqlPaginatedConnection](#mysqlpaginatedconnection)\n  - [mongoDbPaginatedConnection](#mongodbpaginatedconnection)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Introduction\n\nPagination 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.\n\n## Features\n\n- Easy to set up and use\n- Highly customizable\n- Fully complaint with [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) \n- Supports various pagination strategies\n- Works seamlessly with different data sources\n\n## Installation\n\nTo install Paginated Connection:\n\n```sh\nnpm install @treedom/paginated-connection\n```\n\n## Usage\n\n### Basic example\n\nHere is a basic example to get you started with Paginated Connection:\n\n```typescript\nimport { paginatedConnection, PaginationInput } from '@treedom/paginated-connection'\n\n// Define a simple node type\ntype Node = {\n  id: string;\n};\n\n// Define encode and decode functions\n\n// Function to get cursor object from node\nconst getCursor = node =\u003e ({ after: node.id });\n\n// encodeCursor should return a string\nconst encodeCursor = ({ node, getCursor }) =\u003e Buffer.from(JSON.stringify(getCursor())).toString('base64');\n\n// decodeCursor should return an object \nconst decodeCursor = cursor =\u003e JSON.parse(Buffer.from(cursor, 'base64url').toString())\n\n// Sample data loader\nconst dataLoader = async ({ cursor, first, encodeCursor, getEdge }) =\u003e {\n  // Fetch data based on cursor and first\n  const edges = fetchDataFromDataSource(cursor, first);\n  return {\n    edges: edges.map(node =\u003e getEdge(node, getCursor)),\n    hasNextPage: checkIfHasNextPage(),\n  };\n};\n\n// Sample count loader\nconst countLoader = async ({ cursor }) =\u003e {\n  return countDataFromDataSource(cursor);\n};\n\nconst paginationInput: PaginationInput = { after: 'cursor123', first: 10 };\nconst paginationSafeLimit = 50;\n\nconst result = await paginatedConnection\u003cNode\u003e({\n  pagination: paginationInput,\n  paginationSafeLimit,\n  dataLoader,\n  encodeCursor,\n  decodeCursor,\n  countLoader,\n});\n\nconsole.log(result);\n\n```\n\n### Mysql Example\n\nUsing Paginated Connection with MySQL:\n\n```typescript\nimport { mysqlPaginatedConnection } from '@treedom/paginated-connection';\n\n// Define a simple node type\ntype Node = {\n  id: string;\n};\n\n// Function to get cursor object from node\nconst getCursor = node =\u003e ({ after: node.id });\n\n// Define MySQL specific data loader\nconst mysqlDataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  // Fetch data from MySQL database\n  const edges = fetchDataFromMySQL(cursor, first);\n  return {\n    edges: edges.map(node =\u003e getEdge(node, getCursor)),\n  };\n};\n\n// Define MySQL specific count loader\nconst mysqlCountLoader = async ({ cursor }) =\u003e {\n  return countDataInMySQL(cursor);\n};\n\nconst paginationInput = { after: 'cursor123', first: 10 };\nconst paginationSafeLimit = 50;\n\nconst result = await mysqlPaginatedConnection\u003cNode\u003e({\n  pagination: paginationInput,\n  paginationSafeLimit,\n  dataLoader: mysqlDataLoader,\n  countLoader: mysqlCountLoader,\n});\n\nconsole.log(result);\n\n```\n\nIn 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.\n\n### MongoDB Example\n\nUsing Paginated Connection with MongoDB:\n\n```typescript\nimport { mongoDbPaginatedConnection } from '@treedom/paginated-connection';\n\n// Define a simple node type\ntype Node = {\n  id: string;\n};\n\n// Function to get cursor object from node\nconst getCursor = node =\u003e ({ after: node.id });\n\n// Define MongoDB specific data loader\nconst mongoDbDataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  // Fetch data from MongoDB\n  const edges = fetchDataFromMongoDB(cursor, first);\n  return {\n    edges: edges.map(node =\u003e getEdge(node, getCursor))\n  };\n};\n\n// Define MongoDB specific count loader\nconst mongoDbCountLoader = async ({ cursor }) =\u003e {\n  return countDataInMongoDB(cursor);\n};\n\n\nconst paginationInput = { after: 'cursor123', first: 10 };\nconst paginationSafeLimit = 50;\n\nconst result = await mongoDbPaginatedConnection\u003cNode\u003e({\n  pagination: paginationInput,\n  paginationSafeLimit,\n  dataLoader: mongoDbDataLoader,\n  countLoader: mongoDbCountLoader,\n});\n\nconsole.log(result);\n```\n\nIn 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.\n\n### Return value\n\nEvery paginatedConnection function returns an object of `PaginatedConnectionReturnType`:\n\n```typescript\nexport type PaginatedConnectionReturnType\u003cTNode\u003e = Promise\u003c{\n  totalCount: () =\u003e Promise\u003cnumber\u003e\n  pageInfo: {\n    endCursor: string\n    hasNextPage: boolean\n  }\n  edges: Array\u003c{\n    node: TNode\n    cursor: string\n  }\u003e\n}\u003e\n```\n\nwhere `TNode` is the type of the `node` loaded by `dataLoader` function.\n\n## Edges\n\n### Compose using getEdge\n\nWhen 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.\n\nThis 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`.\n\n```typescript\nconst dataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  const nodes = fetchDataFromDataSource(cursor, first);\n  return {\n    edges: nodes.map(node =\u003e getEdge(node, getCursor)),\n    hasNextPage: checkIfHasNextPage(),\n  };\n};\n```\n\nFunction `getEdge` gets in input:\n - `node` object, which should has `TNode` type;\n - `getCursor` function, which should returns an object of type `TCursor`.\n\n\n### Compose using getEdges\n\nWhen 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.\n\nThis 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`.\n\n```typescript\nconst dataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  const nodes = fetchDataFromDataSource(cursor, first); // nodes is an array of TNode object\n  return {\n    edges: getEdges(nodes, getCursor),\n    hasNextPage: checkIfHasNextPage(),\n  };\n};\n```\n\nFunction `getEdges` gets in input:\n - `nodes` array, which should has `Array\u003cTNode\u003e` type;\n - `getCursor` function, which should returns an object of type `TCursor`.\n\nUnder the hood, it executes the `encodeCursor` function, in order to provide the cursor inside of `Edge`.\n\n\n### Compose manually\nIf you need more customization of data, `Edges` could be manually composed, returning an array of `Edge`.\n\n```typescript\nconst dataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  const edges = fetchDataFromDataSource(cursor, first);\n  return {\n    edges: edges.map(node =\u003e ({ node, cursor: encodeCursor({ node, getCursor }) })),\n    hasNextPage: checkIfHasNextPage(),\n  };\n};\n```\n\n## Cursor Types\n\n### Default Cursor Type\nBy default, the cursor type only includes an `after` field, which is a string. This is simple and suitable for basic pagination scenarios.\n\n```typescript\n{ after: string };\n```\n\nThe default cursor is used when no specific cursor type is provided to `paginatedConnection` (or `mysqlPaginatedConnection`, `mongoDbPaginatedConnection`, ecc...):\n\n```typescript\ntype Node = {\n  id: string;\n};\n\nconst paginationInput = { after: 'cursor123', first: 10 };\n\n// Return value should be an object containing `after` field only\nconst getCursor = (node): { after: string } =\u003e ({\n  after: node.id,\n});\n\n// Here we're not passing any custom cursor type to paginatedConnection, so it'll use the default type\nconst result = await paginatedConnection\u003cNode\u003e({\n  ...\n  dataLoader,\n  ...\n});\n```\n\n\n### Custom Cursor Type\nFor 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:\n-  `string`\n-  `number`\n-  `boolean`\n\n```typescript\ntype CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc', ranking: number, includeMetadata: boolean };\n```\n\nWhen using a custom cursor type, you need to type the `paginatedConnection` (or `mysqlPaginatedConnection`, `mongoDbPaginatedConnection`, ecc...), providing cursor custom type:\n\n```typescript\nimport { paginatedConnection } from '@treedom/paginated-connection';\n\ntype Node = {\n  id: string;\n  sortField: string;\n};\n\n// Custom cursor type\ntype CustomCursor = { after: string; sortField: string; sortOrder: 'asc' | 'desc' };\n\n// Return value should be an object of type `CustomCursor`\nconst getCursor = (node): CustomCursor =\u003e ({\n  after: node.id,\n  sortField: node.sortField,\n  sortOrder: 'asc',\n});\n\n// Sample data loader\nconst dataLoader = async ({ cursor, first, encodeCursor }) =\u003e {\n  const edges = fetchDataFromDataSource(cursor, first);\n  return {\n    edges: edges.map(node =\u003e getEdge(node, getCursor)),\n    hasNextPage: checkIfHasNextPage(),\n  };\n};\n\n// Provide CustomCursor type\nconst result = await paginatedConnection\u003cNode, CustomCursor\u003e({\n  ...\n  dataLoader,\n  ...\n});\n\nconsole.log(result);\n```\n## API Reference\n\n### paginatedConnection\n\n`paginatedConnection\u003cTNode, TCursor\u003e(props: PaginatedConnectionProps\u003cTNode, TCursor\u003e)`\n\nHandles pagination to offset-style ordering, returning Connection-style GraphQL result.\n\n- props (`PaginatedConnectionProps`):\n    - `pagination` (`PaginationInput`): Pagination parameters.\n    - `paginationSafeLimit` (`number`): Safe limit for pagination.\n    - `dataLoader` (`(props: DataloaderProps\u003cTNode, TCursor\u003e) =\u003e Promise\u003c{ edges: { node: TNode; cursor: string }[]; -- hasNextPage: boolean }\u003e`): Data loader function.\n    - `encodeCursor` (`EncodeCursor\u003cTNode, TCursor\u003e`): Function to encode cursor, it should return a `string.`\n    - `decodeCursor` (`(cursor: string) =\u003e TCursor`): Function to decode cursor.\n    - `countLoader` (`(props: CountLoaderProps\u003cTCursor\u003e) =\u003e Promise\u003cnumber\u003e`): Count loader function.\n\n### mysqlPaginatedConnection\n\n`mysqlPaginatedConnection\u003cTNode, TCursor\u003e(props: MysqlPaginatedConnectionProps\u003cTNode, TCursor\u003e)`\n\nHandles pagination for MySQL databases, extending the basic `paginatedConnection`.\n\n- props (`MysqlPaginatedConnectionProps`):\n    - `dataLoader` (`(props: DataloaderProps\u003cTNode, TCursor\u003e) =\u003e Promise\u003c{ edges: { node: TNode; cursor: string }[]; }\u003e`): MySQL data loader.\n    - `countLoader` (`(props: CountLoaderProps\u003cTCursor\u003e) =\u003e Promise\u003cnumber\u003e`): MySQL count loader.\n    - `pagination` (`PaginationInput`): Pagination parameters.\n    - `paginationSafeLimit` (`number`): Safe limit for pagination.\n\nIn 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.\n\n### mongoDbPaginatedConnection\n\n`mongoDbPaginatedConnection\u003cTNode, TCursor\u003e(props: MongoDbPaginatedConnectionProps\u003cTNode, TCursor\u003e)`\n\nHandles pagination for MongoDB databases, extending the basic paginatedConnection.\n\n- props (`MongoDbPaginatedConnectionProps`):\n    - `dataLoader` (`(props: DataloaderProps\u003cTNode, TCursor\u003e) =\u003e Promise\u003c{ edges: { node: TNode; cursor: string }[]; }\u003e`): MongoDB data loader.\n    - `countLoader` (`(props: CountLoaderProps\u003cTCursor\u003e) =\u003e Promise\u003cnumber\u003e`): MongoDB count loader.\n    - `pagination` (`PaginationInput`): Pagination parameters.\n    - `paginationSafeLimit` (`number`): Safe limit for pagination.\n\nIn 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.\n\n\n## 🌳 Join Us in Making a Difference! 🌳\n\nWe 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](https://www.treedom.net/en/organization/treedom/event/treedom-open-source) to plant your tree today and join our community of eco-conscious developers.\n\nAdditionally, 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](https://github.com/treedomtrees/.github/blob/main/TREEDOM_BADGE.md) to get started.\n\nTogether, we can make a lasting impact! 🌍💚\n\n## Contributing\n\nContributions are welcome! Please read the contributing guidelines before submitting a pull request.\n\n## License\n\nThis project is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftreedomtrees%2Fpaginated-connection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftreedomtrees%2Fpaginated-connection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftreedomtrees%2Fpaginated-connection/lists"}