{"_id":"@dylanmurzello/vendure-plugin-deep-pagination","_rev":"2-d211ff484b7ffa5e2d34182143989c3f","name":"@dylanmurzello/vendure-plugin-deep-pagination","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dylanmurzello/vendure-plugin-deep-pagination","version":"1.0.0","keywords":["vendure","vendure-plugin","vendure-ecommerce","elasticsearch","elasticsearch-plugin","pagination","cursor-pagination","infinite-scroll","deep-pagination","search-after","ecommerce","e-commerce","headless-commerce","graphql","graphql-api","product-search","product-catalog","nestjs","nestjs-plugin","typescript","scalability","performance-optimization","search-engine","backend","api"],"author":{"url":"https://github.com/Dylanmurzello","name":"Dylan Murzello","email":"dylanmurzello@gmail.com"},"license":"MIT","_id":"@dylanmurzello/vendure-plugin-deep-pagination@1.0.0","maintainers":[{"name":"dylanmurzello","email":"dylanmurzello@gmail.com"}],"homepage":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination#readme","bugs":{"url":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination/issues"},"dist":{"shasum":"4b30746139cf3dd446a52cec86e2d28ef44ab3fc","tarball":"https://registry.npmjs.org/@dylanmurzello/vendure-plugin-deep-pagination/-/vendure-plugin-deep-pagination-1.0.0.tgz","fileCount":24,"integrity":"sha512-cDZ5KisO+Zuih1HoryhPTpsf1N8V7DHCsXHvnW6U74dL/C57aoxH+GrR65Bkhiv+GbqV56pPGsnG2xszEZ/mzQ==","signatures":[{"sig":"MEUCIQDpwbVVUiBKlxbWVdV88IA0ANbMdFEnuKDR0PGD13HSDwIgG+lDDqZJu6mUGvlamElCa+qkyqFPJG8hn5kWWcCCZ6M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31134},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"gitHead":"a35b04e58d1d88b0f388784a125b0786e7690c00","scripts":{"build":"tsc","watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"dylanmurzello","email":"dylanmurzello@gmail.com"},"repository":{"url":"git+https://github.com/Dylanmurzello/vendure-plugin-deep-pagination.git","type":"git"},"_npmVersion":"10.8.2","description":"Infinite product pagination for Vendure e-commerce using Elasticsearch search_after cursors. Bypass the 10k offset limit. O(1) performance at any page depth.","directories":{},"_nodeVersion":"20.19.5","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.2","@vendure/core":"^3.1.2","@elastic/elasticsearch":"^8.15.0"},"peerDependencies":{"@vendure/core":"^3.0.0","@elastic/elasticsearch":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vendure-plugin-deep-pagination_1.0.0_1759902185749_0.5077882885829175","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dylanmurzello/vendure-plugin-deep-pagination","version":"1.0.1","description":"Infinite product pagination for Vendure e-commerce using Elasticsearch search_after cursors. Bypass the 10k offset limit. O(1) performance at any page depth.","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","watch":"tsc --watch","prepublishOnly":"npm run build"},"keywords":["vendure","vendure-plugin","vendure-ecommerce","elasticsearch","elasticsearch-plugin","pagination","cursor-pagination","infinite-scroll","deep-pagination","search-after","ecommerce","e-commerce","headless-commerce","graphql","graphql-api","product-search","product-catalog","nestjs","nestjs-plugin","typescript","scalability","performance-optimization","search-engine","backend","api"],"author":{"name":"Dylan Murzello","email":"dylanmurzello@gmail.com","url":"https://github.com/Dylanmurzello"},"license":"MIT","homepage":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination#readme","repository":{"type":"git","url":"git+https://github.com/Dylanmurzello/vendure-plugin-deep-pagination.git"},"bugs":{"url":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination/issues"},"engines":{"node":">=18.0.0"},"peerDependencies":{"@vendure/core":"^3.0.0","@elastic/elasticsearch":"^8.0.0"},"devDependencies":{"@vendure/core":"^3.1.2","@elastic/elasticsearch":"^8.15.0","typescript":"^5.7.2"},"_id":"@dylanmurzello/vendure-plugin-deep-pagination@1.0.1","gitHead":"e5675af08524b2737af08ae1969d553479f39075","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-07jVAZfPRL3hISv9rnRxooUCm22L3mhPyJtlcDLqBQxyoW93la+EzM7IYkuRDy4cuyVaD+P41XqXsiEpIOs/lA==","shasum":"89fb3cb9d16e41e922270a20589c7649837b5019","tarball":"https://registry.npmjs.org/@dylanmurzello/vendure-plugin-deep-pagination/-/vendure-plugin-deep-pagination-1.0.1.tgz","fileCount":24,"unpackedSize":31158,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF1DKKdYqMGUv9b7qWFp7muLjjvEA0jzo3r66IlUbkR+AiEA1+wKDCiMeMo0mAkvJBpvyYSq5xYEBgaSEo/FE5sH+ic="}]},"_npmUser":{"name":"dylanmurzello","email":"dylanmurzello@gmail.com"},"directories":{},"maintainers":[{"name":"dylanmurzello","email":"dylanmurzello@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vendure-plugin-deep-pagination_1.0.1_1759902531213_0.8042741735878915"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-08T05:43:05.641Z","modified":"2025-10-08T05:48:51.561Z","1.0.0":"2025-10-08T05:43:05.942Z","1.0.1":"2025-10-08T05:48:51.386Z"},"bugs":{"url":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination/issues"},"author":{"name":"Dylan Murzello","email":"dylanmurzello@gmail.com","url":"https://github.com/Dylanmurzello"},"license":"MIT","homepage":"https://github.com/Dylanmurzello/vendure-plugin-deep-pagination#readme","keywords":["vendure","vendure-plugin","vendure-ecommerce","elasticsearch","elasticsearch-plugin","pagination","cursor-pagination","infinite-scroll","deep-pagination","search-after","ecommerce","e-commerce","headless-commerce","graphql","graphql-api","product-search","product-catalog","nestjs","nestjs-plugin","typescript","scalability","performance-optimization","search-engine","backend","api"],"repository":{"type":"git","url":"git+https://github.com/Dylanmurzello/vendure-plugin-deep-pagination.git"},"description":"Infinite product pagination for Vendure e-commerce using Elasticsearch search_after cursors. Bypass the 10k offset limit. O(1) performance at any page depth.","maintainers":[{"name":"dylanmurzello","email":"dylanmurzello@gmail.com"}],"readme":"# Vendure Deep Pagination Plugin\n\n> Infinite product pagination using Elasticsearch `search_after` cursors. Bypass the 10k limit.\n\n[![npm version](https://img.shields.io/npm/v/@dylanmurzello/vendure-plugin-deep-pagination)](https://www.npmjs.com/package/@dylanmurzello/vendure-plugin-deep-pagination)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)\n\n## The Problem\n\nElasticsearch limits offset-based pagination to 10,000 documents. For e-commerce stores with large catalogs:\n\n- Users cannot browse beyond page 834 (12 products/page)\n- Performance degrades linearly with page depth\n- SEO suffers from incomplete product indexing\n\n## The Solution\n\nThis plugin uses Elasticsearch's `search_after` API for cursor-based pagination:\n\n- **No limits** - Navigate through millions of products\n- **O(1) performance** - Constant speed at any page depth\n- **Drop-in replacement** - Extends Vendure's GraphQL API\n\n## Installation\n\n```bash\nnpm install @dylanmurzello/vendure-plugin-deep-pagination\n```\n\n## Quick Start\n\n### 1. Register Plugin\n\n```typescript\n// vendure-config.ts\nimport { DeepPaginationPlugin } from '@gbros/vendure-plugin-deep-pagination';\n\nexport const config: VendureConfig = {\n  plugins: [\n    // ... other plugins\n    DeepPaginationPlugin,\n  ],\n};\n```\n\n### 2. Query Products\n\n```graphql\nquery GetProducts($cursor: String) {\n  cursorSearch(input: { take: 12, cursor: $cursor }) {\n    items {\n      productId\n      productName\n      slug\n      priceWithTax {\n        ... on SinglePrice { value }\n        ... on PriceRange { min max }\n      }\n    }\n    totalItems\n    hasMore\n    nextCursor\n  }\n}\n```\n\n### 3. Navigate Pages\n\n```typescript\n// First page\nconst page1 = await client.request(GET_PRODUCTS, {});\n\n// Next page\nconst page2 = await client.request(GET_PRODUCTS, {\n  cursor: page1.cursorSearch.nextCursor\n});\n```\n\n## API Reference\n\n### Input\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `term` | `string?` | Full-text search query |\n| `facetValueIds` | `string[]?` | Filter by facet values |\n| `facetValueOperator` | `'AND' \\| 'OR'?` | Facet filter logic (default: `OR`) |\n| `collectionId` | `string?` | Filter by collection ID |\n| `collectionSlug` | `string?` | Filter by collection slug |\n| `groupByProduct` | `boolean?` | Group variants by product |\n| `take` | `number?` | Results per page (default: 100, max: 1000) |\n| `cursor` | `string?` | Opaque pagination cursor |\n| `sort` | `object?` | Sort options (see below) |\n\n#### Sort Options\n\n```typescript\n{\n  name?: 'ASC' | 'DESC';\n  price?: 'ASC' | 'DESC';\n}\n```\n\n### Output\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `items` | `SearchResult[]` | Products matching query |\n| `totalItems` | `number` | Total result count |\n| `hasMore` | `boolean` | More pages available |\n| `nextCursor` | `string?` | Cursor for next page |\n\n## How It Works\n\n### Cursor Pagination\n\nTraditional offset pagination (`skip` + `take`) becomes slow at high offsets because Elasticsearch must scan and discard all previous results.\n\nCursor pagination uses `search_after` to resume from the last result's sort values:\n\n```\nPage 1: [A, B, C] -> cursor: \"C's sort values\"\nPage 2: search_after \"C's values\" -> [D, E, F]\n```\n\n### Deterministic Sorting\n\n`search_after` requires stable sort order. We use:\n\n1. User-specified field (name, price, etc.)\n2. `productId` (keyword field)\n3. `productVariantId` (keyword field)\n\nThis ensures consistent ordering even when products share the same name/price.\n\n### Why Keyword Fields?\n\nElasticsearch 9.x disables `fielddata` by default. We use keyword fields for sorting because:\n\n- Keyword fields use `doc_values` (disk-based, efficient)\n- Text fields require `fielddata` (memory-intensive, disabled)\n\n## Limitations\n\n### Forward-Only Navigation\n\nCursor pagination is forward-only. You can:\n\n- Go to next page (use `nextCursor`)\n- Go to first page (omit `cursor`)\n- Jump to arbitrary pages (not supported)\n\n**Solution**: Maintain a cursor stack in your frontend:\n\n```typescript\nconst [cursors, setCursors] = useState<string[]>([]);\n\n// Forward\nconst goNext = () => {\n  setCursors([...cursors, nextCursor]);\n  fetchPage(nextCursor);\n};\n\n// Back\nconst goPrev = () => {\n  const newCursors = cursors.slice(0, -1);\n  setCursors(newCursors);\n  fetchPage(newCursors[newCursors.length - 1]);\n};\n```\n\n### No Total Page Count\n\nYou receive `totalItems` but not total pages. Display pagination as:\n\n```typescript\nconst estimatedPages = Math.ceil(totalItems / take);\n// Show: \"Page 5 of ~1,320\"\n```\n\n## Performance\n\n| Method | Page 1 | Page 100 | Page 1000 |\n|--------|--------|----------|-----------|\n| Offset | 50ms | 200ms | 1000ms |\n| Cursor | 50ms | 50ms | 50ms |\n\nCursor pagination maintains constant performance regardless of page depth.\n\n## Requirements\n\n- Vendure >= 3.0.0\n- Elasticsearch >= 8.0.0\n- Node.js >= 18\n\n## Contributing\n\nContributions welcome! Please open an issue or PR.\n\n### Development\n\n```bash\ngit clone https://github.com/dylanmurzello/vendure-plugin-deep-pagination.git\ncd vendure-plugin-deep-pagination\nnpm install\nnpm run build\n```\n\n## License\n\nMIT - Dylan Murzello\n\n## Acknowledgments\n\nBuilt for production e-commerce at scale. Open-sourced for the Vendure community.\n\nInspired by Elasticsearch's [search_after documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#search-after).\n","readmeFilename":"README.md"}