{"id":43888,"library":"sql-cursor-pagination","title":"SQL Cursor Pagination","description":"Library for implementing cursor-based pagination from SQL databases, conforming to the GraphQL Cursor Connections Specification (Relay-compatible). Current stable version is 4.3.0, released with support for Node >=18 and TypeScript types built-in. Key differentiators: generates encrypted opaque cursors, supports forward/backward pagination (first/last, before/after), works with any SQL query builder or raw SQL via provided fragments (where, order by). Unlike offset-based pagination, cursors prevent duplicate or skipped items when data changes between requests.","status":"active","version":"4.3.0","language":"javascript","source_language":"en","source_url":"https://github.com/tjenkinson/sql-cursor-pagination","tags":["javascript","cursor-pagination","cursor","sql","pagination","graphql","graphql-cursor-connections","typescript"],"install":[{"cmd":"npm install sql-cursor-pagination","lang":"bash","label":"npm"},{"cmd":"yarn add sql-cursor-pagination","lang":"bash","label":"yarn"},{"cmd":"pnpm add sql-cursor-pagination","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"ESM-only since v4; Node >=18 required. CommonJS require() will fail.","wrong":"const { withPagination } = require('sql-cursor-pagination')","symbol":"withPagination","correct":"import { withPagination } from 'sql-cursor-pagination'"},{"note":"TypeScript type (enum-like union) for sort order values: 'ASC' | 'DESC'. No runtime export for CJS.","wrong":"","symbol":"Order","correct":"import { Order } from 'sql-cursor-pagination'"},{"note":"Direct named export; no subpath import. Function returns a Buffer from a string or Buffer secret.","wrong":"import { buildCursorSecret } from 'sql-cursor-pagination/buildCursorSecret'","symbol":"buildCursorSecret","correct":"import { buildCursorSecret } from 'sql-cursor-pagination'"}],"quickstart":{"code":"import knex from 'knex';\nimport { withPagination, Order, buildCursorSecret } from 'sql-cursor-pagination';\n\nconst db = knex({\n  client: 'sqlite3',\n  connection: { filename: ':memory:' },\n  useNullAsDefault: true,\n});\n\nawait db.schema.createTable('users', (table) => {\n  table.integer('id').notNullable();\n  table.integer('created_at').notNullable();\n  table.string('first_name').notNullable();\n  table.string('last_name').notNullable();\n  table.string('email').notNullable();\n  table.boolean('admin').notNullable();\n});\n\nasync function fetchUsers(userInput: {\n  order: Order;\n  admins: boolean;\n  first?: number;\n  last?: number;\n  before?: string;\n  after?: string;\n}) {\n  const query = db('users').where('admin', userInput.admins);\n\n  const { edges, pageInfo } = await withPagination({\n    query: {\n      first: userInput.first,\n      last: userInput.last,\n      before: userInput.before,\n      after: userInput.after,\n    },\n    setup: {\n      sortFields: [\n        { field: 'first_name', order: userInput.order },\n        { field: 'last_name', order: userInput.order },\n        { field: 'id', order: userInput.order },\n      ],\n      cursorSecret: buildCursorSecret('somethingSecret'),\n      queryName: 'users',\n      runQuery: async ({ limit, whereFragmentBuilder, orderByFragmentBuilder }) => {\n        const whereFragment = whereFragmentBuilder.withArrayBindings();\n        const orderByFragment = orderByFragmentBuilder.withArrayBindings();\n\n        const rows = await query\n          .limit(limit)\n          .whereRaw(whereFragment.sql, whereFragment.bindings)\n          .orderByRaw(orderByFragment.sql, orderByFragment.bindings);\n\n        return rows;\n      },\n    },\n  });\n\n  return { edges: edges, pageInfo: pageInfo };\n}","lang":"typescript","description":"Shows complete setup with Knex and TypeScript: table creation, paginated query with cursor support."},"warnings":[{"fix":"Migrate to ES modules (type: 'module' in package.json or .mjs extension) and ensure Node >=18.","message":"Breaking change in v4: dropped CommonJS support. Only ESM with Node >=18.","severity":"breaking","affected_versions":">=4.0.0"},{"fix":"Update code to use buildCursorSecret() instead of direct buffer variable.","message":"Breaking change in v3: renamed 'cursorSecret' to 'buildCursorSecret' and changed return type to Buffer.","severity":"breaking","affected_versions":">=3.0.0 <4.0.0"},{"fix":"Always set a unique key (like 'id') as the final sort field.","message":"The 'sortFields' array must include a unique key (or combination) as the last field to avoid tie-breaking issues and incorrect pagination.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Use withArrayBindings() for better compatibility with most SQL drivers.","message":"If using fragments with positional bindings (withPositionalBindings) instead of array bindings, the fragment SQL uses '?' placeholders which may conflict with some databases.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"search_vec":"'18':34 '4.3.0':28 'base':9,70 'before/after':51 'builder':57 'built':39 'built-in':38 'chang':80 'compat':23 'conform':14 'connect':19,94 'current':24 'cursor':2,8,18,46,72,85,87,93 'cursor-bas':7 'cursor-pagin':84 'data':79 'databas':13 'differenti':42 'duplic':74 'encrypt':44 'first/last':50 'forward/backward':48 'fragment':63 'generat':43 'graphql':17,90,92 'graphql-cursor-connect':91 'implement':6 'item':77 'javascript':83 'key':41 'librari':4 'node':33 'offset':69 'offset-bas':68 'opaqu':45 'order':65 'pagin':3,10,49,71,86,89 'prevent':73 'provid':62 'queri':56 'raw':59 'relay':22 'relay-compat':21 'releas':29 'request':82 'skip':76 'specif':20 'sql':1,12,55,60,88 'stabl':25 'support':31,47 'type':37 'typescript':36,95 'unlik':67 'version':26 'via':61 'work':52","created_at":"2026-06-05T17:01:57.634382+00:00","updated_at":"2026-06-05T17:01:57.634382+00:00","problems":[{"fix":"Switch to import statement or use dynamic import: const { withPagination } = await import('sql-cursor-pagination');","cause":"Using CommonJS require() with ESM-only package (v4+).","error":"Error: 'withPagination' is not exported from 'sql-cursor-pagination' (or similar import error)"},{"fix":"Add a unique field, e.g., { field: 'id', order: 'ASC' } as the last element in sortFields.","cause":"sortFields array does not include a unique key (like primary key) as the last field.","error":"could not determine unique field in sort fields"},{"fix":"Call whereFragmentBuilder.withArrayBindings() (or the positional variant) to get the fragment object.","cause":"Misuse of whereFragmentBuilder or orderByFragmentBuilder without calling withArrayBindings() or withPositionalBindings().","error":"Cannot read properties of undefined (reading 'sql')"}],"ecosystem":"npm","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":null,"cli_name":null,"cli_version":null,"type":"library","homepage":"https://github.com/tjenkinson/sql-cursor-pagination#readme","github":"https://github.com/tjenkinson/sql-cursor-pagination","docs":null,"changelog":null,"pypi":null,"npm":"sql-cursor-pagination","openapi_spec":null,"status_page":null,"smithery":null,"categories":["database"],"base_url":null,"auth_type":null,"provenance":{"verified_status":null,"verified_at":null,"last_verified":"2026-06-05","next_check":"2026-09-03","install_tag":null}}