{"id":41829787,"url":"https://github.com/emdgroup/dynamodb-paginator","last_synced_at":"2026-01-25T08:35:01.242Z","repository":{"id":57111356,"uuid":"384563590","full_name":"emdgroup/dynamodb-paginator","owner":"emdgroup","description":"Paginate through DynamoDB items with ease and enable client pagination using encrypted tokens.","archived":false,"fork":false,"pushed_at":"2023-09-27T01:10:14.000Z","size":87,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-30T00:51:33.239Z","etag":null,"topics":["aws","dynamodb","pagination"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emdgroup.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-09T22:28:23.000Z","updated_at":"2024-09-14T21:58:41.000Z","dependencies_parsed_at":"2022-08-21T11:00:21.337Z","dependency_job_id":null,"html_url":"https://github.com/emdgroup/dynamodb-paginator","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/emdgroup/dynamodb-paginator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emdgroup%2Fdynamodb-paginator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emdgroup%2Fdynamodb-paginator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emdgroup%2Fdynamodb-paginator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emdgroup%2Fdynamodb-paginator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emdgroup","download_url":"https://codeload.github.com/emdgroup/dynamodb-paginator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emdgroup%2Fdynamodb-paginator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28749468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T08:31:04.260Z","status":"ssl_error","status_checked_at":"2026-01-25T08:30:28.859Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["aws","dynamodb","pagination"],"created_at":"2026-01-25T08:35:00.563Z","updated_at":"2026-01-25T08:35:01.235Z","avatar_url":"https://github.com/emdgroup.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Apache License](https://img.shields.io/github/license/emdgroup/dynamodb-paginator.svg?style=flat-square)](https://github.com/emdgroup/dynamodb-paginator/blob/master/LICENSE)\n[![Sponsored by EMD Group](https://img.shields.io/badge/sponsored%20by-emdgroup.com-ff55aa.svg?style=flat-square)](http://emdgroup.com)\n[![Downloads](https://img.shields.io/npm/dw/@emdgroup/dynamodb-paginator.svg?style=flat-square)](https://www.npmjs.com/package/@emdgroup/dynamodb-paginator)\n\n# DynamoDB Paginator\n\nFeatures:\n  * Supports Binary and String key types\n  * Generates AES256 encrypted and authenticated pagination tokens\n  * Works with TypeScript type guards natively\n  * Ensures a minimum number of items when using a `FilterExpression`\n  * Compatible with AWS SDK v2 and v3\n  * Supports pagination over segmented [parallel scans](#parallel-scans)\n\nPagination in NoSQL stores such as DynamoDB can be challenging. This\nlibrary provides a developer friendly interface around the DynamoDB `Query` and `Scan` APIs.\nIt generates and encrypted and authenticated pagination token that can be shared with an untrustworthy\nclient (like the browser or a mobile app) without disclosing potentially sensitive data and protecting\nthe integrity of the token.\n\n**Why is the pagination token encrypted?**\n\nWhen researching pagination with DynamoDB, you will come across blog posts and libraries that recommend\nto JSON-encode the `LastEvaluatedKey` attribute (or even the whole query command). **This is dangerous!**\n\nThe token is sent to a client which can happily decode the token, look at the values for the\npartition and sort key and even modify the token, making the application vulnerable to NoSQL injections.\n\n**How is the pagination token encrypted?**\n\nThe encryption key passed to the paginator is used to derive an encryption and a signing key using an HMAC.\n\nThe `LastEvaluatedKey` attribute is first flattened by length-encoding its datatypes and values. The\nencoded key is then encrypted with the encryption key using AES-256 in CBC mode with a randomly generated IV.\n\nThe additional authenticated data (AAD), the IV, the ciphertext and an int64 of the length of the AAD are\nconcatenated to form the *message* to be signed.\n\nThe encrypted and signed pagination token is then returned by concatenating the IV, ciphertext and the\nfirst 16 bytes of the HMAC-SHA256 of the *message* using the signing key.\n\n\u003e \"Dance like nobody is watching. Encrypt like everyone is.\"\n\u003e -- Werner Vogels\n\n## Usage\n\n```typescript\nimport { Paginator } from '@emdgroup/dynamodb-paginator';\nimport { DynamoDB } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';\n\nimport * as crypto from 'crypto';\n\nconst client = DynamoDBDocument.from(new DynamoDB({}));\n// persist the key in the SSM parameter store or similar\nconst key = crypto.randomBytes(32);\n\nconst paginateQuery = Paginator.createQuery({\n  key,\n  client,\n});\n\nconst paginator = paginateQuery({\n  TableName: 'MyTable',\n  KeyConditionExpression: 'PK = :pk',\n  ExpressionAttributeValues: {\n      ':pk': 'U#ABC',\n  },\n});\n```\n\nUse `for await...of` syntax:\n\n```typescript\nfor await (const item of paginator) {\n  // do something with item\n\n  // only work on the first 50 items,\n  // then generate a pagination token and break.\n  if (paginator.count === 50) {\n    console.log(paginator.nextToken);\n    break;\n  }\n}\n\nitems.requestCount; // number of requests to DynamoDB\n```\n\nUse `await all()` syntax:\n\n```typescript\nconst items = await paginator.limit(50).all();\npaginator.nextToken;\n\nconst nextPaginator = paginator.from(paginator.nextToken);\nnextPaginator.all(); // up to 50 more items\n```\n\nUse TypeScript guards to filter for items:\n\n```typescript\ninterface User {\n  PK: string;\n  SK: string;\n}\n\nfunction isUser(arg: Record\u003cstring, unknown\u003e): args is User {\n  return typeof arg.PK === 'string' \u0026\u0026 \n    typeof arg.SK === 'string' \u0026\u0026\n    arg.PK.startsWith('U#');\n}\n\nfor await (const user of paginator.filter(isUser)) {\n  // user is of type User\n}\n```\n\n## Paginator\n\nThe `Paginator` class is a factory for the [`PaginationResponse`](#PaginationResponse) object. This class\nis instantiated with a 32-byte key and the DynamoDB document client (versions\n2 and 3 of the AWS SDK are supported).\n\n```typescript\nconst paginateQuery = Paginator.createQuery({\n  key: () =\u003e Promise.resolve(crypto.randomBytes(32)),\n  client: documentClient,\n});\n```\n\nTo create a paginator over a scan operation, use `createScan`.\n\n```typescript\nconst paginateScan = Paginator.createScan({\n  key: () =\u003e Promise.resolve(crypto.randomBytes(32)),\n  client: documentClient,\n});\n```\n\n### Parallel Scans\n\nThis library also supports pagination over segmented parallel scans. This is useful when you have a large\ntable and want to parallelize the scan operation to reduce the time it takes to scan the whole table.\n\nTo create a paginator over a segmented scan operation, use `createParallelScan`.\n\n```typescript\nconst paginateParallelScan = Paginator.createParallelScan({\n  key: () =\u003e Promise.resolve(crypto.randomBytes(32)),\n  client: documentClient,\n});\n```\n\nThen, create a paginator and pass the `segments` parameter.\n\n```ts\nconst paginator = paginateParallelScan({\n  TableName: 'MyTable',\n  Limit: 250,\n}, { segments: 10 });\n\nawait paginator.all();\n```\n\nThe scan will be executed in parallel over 10 segments. The paginator will return the items in the order\nthey are returned by DynamoDB which might deliver items from different segments out of order. Refer to the\nfollowing waterfall diagram for an example. The parallel scan was executed over a high-latency connection\nto better illustrate the variability in the requests and responses. Even though the `Limit` is set to 250,\nDynamoDB will return on occasion less than 250 items per segment. The paginator will continue to request\nitems until all segments have been exhausted.\n\n![parallel scan](img/waterfall.svg)\n\n## Constructors\n\n### constructor\n\n• **new Paginator**(`args`)\n\nUse the static factory function [`create()`](#create) instead of the constructor.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `args` | [`PaginatorOptions`](#PaginatorOptions) |\n\n## Methods\n\n### createParallelScan\n\n▸ `Static` **createParallelScan**(`args`): \u003cT\\\u003e(`scan`: `ScanCommandInput`, `opts`: [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e \u0026 { `segments`: `number`  }) =\u003e `ParallelPaginationResponse`\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Scan command and return an instance of `PaginationResponse`.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `args` | [`PaginatorOptions`](#PaginatorOptions) |\n\n#### Returns\n\n`fn`\n\n▸ \u003c`T`\\\u003e(`scan`, `opts`): `ParallelPaginationResponse`\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Scan command and return an instance of `PaginationResponse`.\n\n##### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `AttributeMap` |\n\n##### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `scan` | `ScanCommandInput` |\n| `opts` | [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e \u0026 { `segments`: `number`  } |\n\n##### Returns\n\n`ParallelPaginationResponse`\u003c`T`\\\u003e\n\n___\n\n### createQuery\n\n▸ `Static` **createQuery**(`args`): \u003cT\\\u003e(`query`: `QueryCommandInput`, `opts?`: [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e) =\u003e [`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Query command and return an instance of `PaginationResponse`.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `args` | [`PaginatorOptions`](#PaginatorOptions) |\n\n#### Returns\n\n`fn`\n\n▸ \u003c`T`\\\u003e(`query`, `opts?`): [`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Query command and return an instance of `PaginationResponse`.\n\n##### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `AttributeMap` |\n\n##### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `query` | `QueryCommandInput` |\n| `opts?` | [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e |\n\n##### Returns\n\n[`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\n___\n\n### createScan\n\n▸ `Static` **createScan**(`args`): \u003cT\\\u003e(`scan`: `ScanCommandInput`, `opts?`: [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e) =\u003e [`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Scan command and return an instance of `PaginationResponse`.\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `args` | [`PaginatorOptions`](#PaginatorOptions) |\n\n#### Returns\n\n`fn`\n\n▸ \u003c`T`\\\u003e(`scan`, `opts?`): [`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\nReturns a function that accepts a DynamoDB Scan command and return an instance of `PaginationResponse`.\n\n##### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `AttributeMap` |\n\n##### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `scan` | `ScanCommandInput` |\n| `opts?` | [`PaginateQueryOptions`](#PaginateQueryOptions)\u003c`T`\\\u003e |\n\n##### Returns\n\n[`PaginationResponse`](#PaginationResponse)\u003c`T`\\\u003e\n\n\n## PaginatorOptions\n\n## Properties\n\n### client\n\n• **client**: `DynamoDBDocumentClientV2` \\| `DynamoDBDocumentClient`\n\nAWS SDK v2 or v3 DynamoDB Document Client.\n\n___\n\n### indexes\n\n• `Optional` **indexes**: `Record`\u003c`string`, [partitionKey: string, sortKey?: string]\\\u003e \\| (`index`: `string`) =\u003e [partitionKey: string, sortKey?: string]\n\nObject that resolves an index name to the partition and sort key for that index.\nAlso accepts a function that builds the names based on the index name.\n\nDefaults to ```(index) =\u003e [`${index}PK`, `${index}SK`]```.\n\n___\n\n### key\n\n• **key**: `CipherKey` \\| `Promise`\u003c`CipherKey`\\\u003e \\| () =\u003e `CipherKey` \\| `Promise`\u003c`CipherKey`\\\u003e\n\nA 32-byte encryption key (e.g. `crypto.randomBytes(32)`). The `key` parameter also\naccepts a Promise that resolves to a key or a function that resolves to a Promise of a key.\n\nIf a function is passed, that function is lazily called only once. The function is called concurrently\nwith the first query request to DynamoDB to reduce the overall latency for the first query. The\nkey is cached and the function is not called again.\n\n___\n\n### schema\n\n• `Optional` **schema**: [partitionKey: string, sortKey?: string]\n\nNames for the partition and sort key of the table. Defaults to `['PK', 'SK']`.\n\n\n## PaginationResponse\n\nThe `PaginationResponse` class implements the query result iterator. It has a number of\nutility functions such as [`peek()`](#peek) and [`all()`](#all) to simplify common usage patterns.\n\nThe iterator can be interrupted and resumed at any time. The iterator will stop to produce\nitems after the end of the query is reached or the provided [`limit`](#limit) parameter is exceeded.\n\n## Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `AttributeMap` = `AttributeMap` |\n\n## Properties\n\n### count\n\n• **count**: `number`\n\nNumber of items yielded\n\n## Accessors\n\n### consumedCapacity\n\n• `get` **consumedCapacity**(): `number`\n\nTotal consumed capacity for query\n\n#### Returns\n\n`number`\n\n___\n\n### finished\n\n• `get` **finished**(): `boolean`\n\nReturns true if all items for this query have been returned from DynamoDB.\n\n#### Returns\n\n`boolean`\n\n___\n\n### nextToken\n\n• `get` **nextToken**(): `undefined` \\| `string`\n\nToken to resume query operation from the current position. The token is generated from the `LastEvaluatedKey`\nattribute provided by DynamoDB and then AES256 encrypted such that it can safely be provided to an\nuntrustworthy client (such as a user browser or mobile app). The token is Base64 URL encoded which means that\nit only contains URL safe characters and does not require further encoding.\n\nThe encryption is necessary to\nprevent leaking sensitive information that can be included in the `LastEvaluatedKey` provided\nby DynamoDB. It also prevents a client from modifying the token and therefore manipulating the query\nexecution (NoSQL injection).\n\nThe length of the token depends on the length of the values for the partition and sort key of the table\nor index that you are querying. The token length is at least 42 characters.\n\n#### Returns\n\n`undefined` \\| `string`\n\n___\n\n### requestCount\n\n• `get` **requestCount**(): `number`\n\nNumber of requests made to DynamoDB\n\n#### Returns\n\n`number`\n\n___\n\n### scannedCount\n\n• `get` **scannedCount**(): `number`\n\nNumber of items scanned by DynamoDB\n\n#### Returns\n\n`number`\n\n## Methods\n\n### [asyncIterator]\n\n▸ **[asyncIterator]**(): `AsyncGenerator`\u003c`T`, `void`, `void`\\\u003e\n\n```typescript\nfor await (const item of items) {\n  // work with item\n}\n```\n\n#### Returns\n\n`AsyncGenerator`\u003c`T`, `void`, `void`\\\u003e\n\n___\n\n### all\n\n▸ **all**(): `Promise`\u003c`T`[]\\\u003e\n\nReturn all items from the query (up to `limit` items). This is potentially dangerous and expensive\nas it this query will keep making requests to DynamoDB until there are no more items. It is recommended\nto pair `all()` with a `limit()` to prevent a runaway query execution.\n\n#### Returns\n\n`Promise`\u003c`T`[]\\\u003e\n\n___\n\n### filter\n\n▸ **filter**\u003c`K`\\\u003e(`predicate`): [`PaginationResponse`](#PaginationResponse)\u003c`K`\\\u003e\n\nFilter results by a predicate function\n\n#### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `K` | extends `AttributeMap` |\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `predicate` | (`arg`: `AttributeMap`) =\u003e arg is K |\n\n#### Returns\n\n[`PaginationResponse`](#PaginationResponse)\u003c`K`\\\u003e\n\n___\n\n### from\n\n▸ **from**\u003c`L`\\\u003e(`nextToken`): `L`\n\nStart returning results starting from `nextToken`\n\n#### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `L` | extends [`PaginationResponse`](#PaginationResponse)\u003c`T`, `L`\\\u003e |\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `nextToken` | `undefined` \\| `string` |\n\n#### Returns\n\n`L`\n\n___\n\n### limit\n\n▸ **limit**\u003c`L`\\\u003e(`limit`): `L`\n\nLimit the number of results to `limit`. Will return at least `limit` results even when using FilterExpressions.\n\n#### Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `L` | extends [`PaginationResponse`](#PaginationResponse)\u003c`T`, `L`\\\u003e |\n\n#### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `limit` | `number` |\n\n#### Returns\n\n`L`\n\n___\n\n### peek\n\n▸ **peek**(): `Promise`\u003c`undefined` \\| `T`\\\u003e\n\nReturns the first item in the query without advancing the iterator. `peek()` can\nalso be used to \"prime\" the iterator. It will immediately make a request to DynamoDB\nand fill the iterators cache with the first page of results. This can be useful if\nyou have other concurrent asynchronous requests:\n\n```typescript\nconst items = paginateQuery(...);\n\nawait Promise.all([\n  items.peek(),\n  doSomethingElse(),\n]);\n\nfor await (const item of items) {\n  // the first page of items has already been pre-fetched so they are available immediately\n}\n```\n\n`peek` can be invoked inside a `for await` loop. `peek` returns `undefined` if there are no\nmore items returned or if the `limit` has been reached.\n\n```typescript\nfor await (const item of items) {\n  const next = await items.peek();\n  if (!next) {\n    // we've reached the last item\n  }\n}\n```\n\n`peek()` does not increment the `count` attribute.\n\n#### Returns\n\n`Promise`\u003c`undefined` \\| `T`\\\u003e\n\n\n## PaginateQueryOptions\n\n## Type parameters\n\n| Name | Type |\n| :------ | :------ |\n| `T` | extends `AttributeMap` |\n\n## Properties\n\n### context\n\n• `Optional` **context**: `string` \\| `Buffer`\n\nThe context defines the additional authenticated data (AAD) that is used to generate the signature\nfor the pagination token. It is optional but recommended because it adds an additional layer of\nauthentication to the pagination token. Pagination token will be tied to the context and replaying\nthem in other contexts will fail. Good examples for the context are a user ID or a session ID concatenated\nwith the purpose of the query, such as `ListPets`. The context cannot be extracted from the pagination\ntoken and can therefore contain sensitive data.\n\n___\n\n### filter\n\n• `Optional` **filter**: (`arg`: `AttributeMap`) =\u003e arg is T\n\n#### Type declaration\n\n▸ (`arg`): arg is T\n\nFilter results by a predicate function\n\n##### Parameters\n\n| Name | Type |\n| :------ | :------ |\n| `arg` | `AttributeMap` |\n\n##### Returns\n\narg is T\n\n___\n\n### from\n\n• `Optional` **from**: `string`\n\nStart returning results starting from `nextToken`\n\n___\n\n### limit\n\n• `Optional` **limit**: `number`\n\nLimit the number of results to `limit`. Will return at least `limit` results even when using FilterExpressions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femdgroup%2Fdynamodb-paginator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femdgroup%2Fdynamodb-paginator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femdgroup%2Fdynamodb-paginator/lists"}