{"id":39764781,"url":"https://github.com/neoxia/dynamodels","last_synced_at":"2026-01-18T11:44:03.063Z","repository":{"id":38175669,"uuid":"205405095","full_name":"neoxia/dynamodels","owner":"neoxia","description":"Typescript overlay to easily interact with DynamoDB. Fluid syntax library offering pagination, auto-escape of reserved words and many more.","archived":false,"fork":false,"pushed_at":"2024-07-15T21:49:05.000Z","size":973,"stargazers_count":11,"open_issues_count":11,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-09-05T11:20:34.616Z","etag":null,"topics":["chainable","dynamodb","helpers","typescript"],"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/neoxia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2019-08-30T15:06:29.000Z","updated_at":"2025-03-17T16:41:47.000Z","dependencies_parsed_at":"2023-02-18T02:45:38.371Z","dependency_job_id":"e199169a-bead-4869-a274-1a76c8da1512","html_url":"https://github.com/neoxia/dynamodels","commit_stats":null,"previous_names":["marioarnt/dynamodels"],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/neoxia/dynamodels","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neoxia%2Fdynamodels","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neoxia%2Fdynamodels/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neoxia%2Fdynamodels/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neoxia%2Fdynamodels/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neoxia","download_url":"https://codeload.github.com/neoxia/dynamodels/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neoxia%2Fdynamodels/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28535177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T10:13:46.436Z","status":"ssl_error","status_checked_at":"2026-01-18T10:13:11.045Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":["chainable","dynamodb","helpers","typescript"],"created_at":"2026-01-18T11:44:02.474Z","updated_at":"2026-01-18T11:44:03.054Z","avatar_url":"https://github.com/neoxia.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dynamodels\n\n[![npm](https://img.shields.io/npm/v/dynamodels)](https://www.npmjs.com/package/dynamodels)\n[![npm bundle size](https://img.shields.io/bundlephobia/minzip/dynamodels)](https://bundlephobia.com/package/dynamodels)\n![npm](https://img.shields.io/npm/dm/dynamodels)\n[![semantic-release](https://img.shields.io/badge/semantic--release-enabled?logo=semantic-release)](https://github.com/semantic-release/semantic-release)\n\n![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/neoxia/dynamodels/publish.yml?branch=main)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=neoxia_dynamodels\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=neoxia_dynamodels)\n\n![Logo](https://raw.githubusercontent.com/neoxia/dynamodels/master/logo.svg?sanitize=true)\n\nDynamodels is a dead simple typescript overlay to easily manage DynamoDB CRUD operations.\n\nIt provides helpers for pagination, filtering, sorting and more !\n\n## Installation\n\nInstall `dynamodels` package from NPM public registry.\n\n- Using npm: `npm i dynamodels`\n\n- Using yarn: `yarn add dynamodels`\n\n## Getting started\n\nThe only thing you need to do is extending base model class `Model\u003cT\u003e` providing a type defintion for your entity, a table name, a hashkey, and optionally, a range key.\n\nHere is an example for table with a composite key:\n\n```typescript\n// Import dynamodels Base Model\nimport Model from 'dynamodels';\n\n// Type definition for your entity\ninterface IAlbum {\n  artist: string;\n  album: string;\n  year?: number;\n  genres?: string[];\n}\n\nexport class Album extends Model\u003cIAlbum\u003e {\n  // DynamoDB table name\n  protected tableName = 'my_albums';\n  // The keys of the table. Here it is a composite key (artist,album)\n  protected hashkey = 'artist';\n  protected rangekey = 'album';\n\n  // Optionally override constructor\n  constructor(item?: IAlbum) {\n    super(item);\n  }\n}\n```\n\nYou have the option to either directly provide the table name, as shown in the previous example, or retrieve it from the AWS Systems Manager (SSM) Parameter Store by specifying the ARN of the SSM parameter. The value will be automatically replaced at runtime.\n\nHere is an example of using table name as SSM Parameter ARN\n\n```typescript\ninterface IAlbum {\n  artist: string;\n  album: string;\n  year?: number;\n  genres?: string[];\n}\n\nexport class Album extends Model\u003cIAlbum\u003e {\n  protected tableName = 'arn:aws:ssm:\u003caws-region\u003e:\u003caws-account-id\u003e:parameter/ParameterName';\n  protected hashkey = 'artist';\n  protected rangekey = 'album';\n\n  constructor(item?: IAlbum) {\n    super(item);\n  }\n}\n```\n\nHere is another example for a table with a simple hashkey:\n\n```typescript\nimport Model from 'dynamodels';\n\ninterface IUser {\n  email: string;\n  // ..other fields\n}\n\nexport class User extends Model\u003cIUser\u003e {\n  protected tableName = 'my_users';\n  protected hashkey = 'email';\n}\n```\n\n## Create an entity\n\nYou can either call the create method or the save method.\n\nThe create method will throw if the hashkey or the hashkey/rangekey pair already exists.\n\nThe save method will overwrite the existing item.\n\n```typescript\nconst classic = new Album({\n  artist: 'Pink Floyd',\n  album: 'Dark Side of the Moon',\n  year: 1973,\n  genre: ['Rock', 'Psychadelic', 'Progressive'],\n});\n\n// Item will be saved\nawait album.save();\n\n// Will throw, as item already exists\ntry {\n  await album.create();\n} catch (e) {\n  if (e.name === 'EALREADYEXISTS') {\n    // Do something\n  }\n  // Do something else\n}\n```\n\nYou can also directly pass in argument the item to save.\n\n```typescript\nconst albums = new Album();\n\nawait album.create({\n  artist: 'Bob Marley \u0026 The Wailers',\n  album: \"Burnin'\",\n  year: 1973,\n  genre: ['Reggea'],\n});\n```\n\n### Model validation\n\nYou can use Joi objects to validate the data to save.\n\nIf object don't pass Joi validation an error is thrown.\n\nDefine your Joi schema in your model.\n\n```typescript\nexport class Album extends Model\u003cIAlbum\u003e {\n  protected tableName = 'my_albums';\n  protected hashkey = 'artist';\n  protected rangekey = 'album';\n\n  protected schema = Joi.object().keys({\n    artist: Joi.string().required(),\n    album: Joi.string().required(),\n    year: Joi.number().required(),\n    genres: Joi.array(Joi.string()).optional(),\n  });\n}\n```\n\nModel validation is automatically enforced when creating/saving entities:\n\n```typescript\n// Will throw as year must be a number\nawait album.save({\n  artist: 'Bob Marley \u0026 The Wailers',\n  album: \"Burnin'\",\n  year: '1973',\n  genre: ['Reggea'],\n});\n```\n\n## Get an item\n\nTable has a simple hashkey:\n\n```typescript\nconst users = new User();\nconst user = await user.get('some-user@domain.com'); // {email: some-user@domain.com, ...}\n```\n\nTable has a composite key:\n\n```typescript\nconst albums = new Album();\nconst nvrmind = await album.get('Nirvana', 'Nevermind'); // {artist: 'Nirvana', album: 'Nevermind', year: 1991...}\n```\n\n_Note:_ For table with a composite key, range key is mandatory. This will throw an exception.\n\n```typescript\nawait album.get('ACDC'); // Bad Request\n```\n\nYou can also just check if an item exists:\n\n```typescript\nconst albums = new Album();\nif (await album.exists('The Fugees', 'The Score')) {\n  return 'Kill me softly';\n}\n```\n\n## Scan table\n\n_Note_: It is not advised to use scan operations as it is time-consuming.\n\nTo retrieve all the entries of a table use a DynamoDB scan operation.\n\n```typescript\nconst albums = new Album();\nconst result = await albums.scan().exec();\n```\n\nThis will return the first 1MB of matching result and the last evaluated key.\n\nIf you want to retrieve all items beyond this 1MB limit, use `execAll`;\n\n```typescript\nconst result = await albums.scan().execAll();\n```\n\n### Paginate\n\nYou can use pagination helpers to get paginated result\n\n```typescript\nconst albums = new Album();\nconst result = await albums.scan().paginate({ size: 50 }).exec();\n```\n\nThis will return the 50 first items and the last evaluated key. To fetch the next page, simply use:\n\n```typescript\nconst nextPage = await albums.scan().paginate(result.nextPage)exec();\n```\n\n### Pagination mode\n\nNatively, dynamoDB performs filter operations after having retrieved a page of result.\n\nThis leads to inconsistent pages size. Let's say you target a page size of 50. DynamoDB fetch the first page which length is 50. After applying filters on this first page you ends up with 13 results, and maybe 32 on the seconds, 7 on the third and so on.\n\nIf you want to force page size to be same despite filtering, you can use `PaginationMode.CONSTANT_PAGE_SIZE` option.\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .scan()\n  .filter({\n    year: 1969, // Summer of love\n  })\n  .paginate({\n    mode: PaginationMode.CONSTANT_PAGE_SIZE,\n    size: 50,\n  })\n  .exec();\n```\n\nUnder the hood, dynamodels will fetch as many pages as it is necessary to fill the 50 results matching filters.\n\n### Filtering scan operations\n\nA filtering helper method is also available.\n\nFor instance to retrieve albums released after 1973 (included), use the following query:\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .scan()\n  .filter({\n    year: ge(1973),\n  })\n  .exec();\n```\n\nThe `filter` accept an object where keys are the fields on which you to filter and value can be either:\n\n1. just the target value of the field, in this case the equal `EQ` operator is used\n2. a call to a filter operator helper method.\n\n_Note_: if you want to filter on a field which is also a [Amazon reserved keyword](https://docs.aws.amazon.com/fr_fr/amazondynamodb/latest/developerguide/ReservedWords.html), dynamodels with automatically escape it :sparkles:\n\n### Filter operators\n\nAvailable filter operators are:\n\n1. Equal: `eq(value: string | number | Buffer)`\n2. Not Equal: `neq(value: string | number | Buffer)`\n3. In: `isIn(values: Array\u003cstring | number | Buffer\u003e)`\n4. Lesser than: `lt(value: string | number | Buffer)`\n5. Lesser or equal than: `le(value: string | number | Buffer)`\n6. Greater than: `gt(value: string | number | Buffer)`\n7. Greater or equal than: `le(value: string | number | Buffer)`\n8. Between boundaries: `between(lower: string | number | Buffer, upper: string | number | Buffer)`\n9. Contains substring `contains(value: string)`\n10. Do not contains substring: `notContains(value: string)`\n11. Begin with: `contains(value: string)`\n12. Is null: `isNull()`\n13. Is not null: `notNull()`\n\nFor string, utf-8 alphabetical order is used.\n\nFor binary, unsigned byte-wise comparison is used.\n\nCheck official DynamoDB documentation for more details.\n\n### Filter Condition Builder\n\nFor complex conditions, i.e. conditions with compositions of AND/OR or NOT clauses, dynamodels provides a fluid synthax to easily write them.\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .scan()\n  .filter(attr('year').lt(1970)\n    .or(attr('year').ge(1980))\n    .and(not(attr('artist').beginsWith('Bob')))\n  .exec();\n```\n\n## Query items\n\nThe library also provides helpers to build dynamoDB queries.\n\nThe synthax is simmilar to `scan` operations.\n\n### Key conditions\n\nKey conditions can be added with the `keys` helpers method.\n\nFor instance to retrieve all the album for a given artist.\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .query()\n  .filter({\n    artist: 'The Rolling Stones',\n  })\n  .exec();\n```\n\nYou can combine key condition if your table has a composite key.\n\nIn this case both condition are applied: it is a `AND` not a `OR`.\n\n### Key condition operators\n\nAvailable key conditions operators are:\n\n1. Equal: `eq(value: string | number | Buffer)`\n2. Lesser than: `lt(value: string | number | Buffer)`\n3. Lesser or equal than: `le(value: string | number | Buffer)`\n4. Greater than: `gt(value: string | number | Buffer)`\n5. Greater or equal than: `le(value: string | number | Buffer)`\n6. Between boundaries: `between(lower: string | number | Buffer, upper: string | number | Buffer)`\n7. Begin with: `contains(value: string)`\n\n### Key condition builder\n\nYou can use, if you prefer, the same fluid sythax than for filter conditions.\n\n```typescript\nconst albums = new Album();\nconst result = await albums.query().keys(attr('artist').eq('Bob Dylan')).exec();\n```\n\nJust be aware that:\n\n1. Key condition on hash key is mandatory.\n2. Only `eq()` operator can be used on hash key.\n3. You can only use `and` between hash key condition and optional range key condition.\n4. Only the operators listed above can be used on range key condition.\n\nOtherwise dynamoDB will throw a ValidationException. Dynamodels will not check these prerequisites for you.\n\n### Using indexes\n\nYou can also specify the index which is used.\n\nLet's say you have the following global secondary index called `year_index` on your albums table: `pk=artist, sk=year`.\n\nYou can retrieve all the albums from Deep Purple release before 1976:\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .query('year_index')\n  .keys({\n    artist: 'Deep Purple',\n    year: lt(1976),\n  })\n  .exec();\n```\n\nThe following synthax using index method is equivalent:\n\n```typescript\nconst albums = new Album();\nconst result = await albums\n  .query()\n  .index('year_index')\n  .filter({\n    artist: 'Deep Purple',\n    year: lt(1976),\n  })\n  .exec();\n```\n\n### Paginate\n\nPagination work the same way than for scan operations.\n\n```typescript\nconst albums = new Album();\nconst result = await albums.query().keys({ artist: 'Dire Straits' }).paginate({ size: 50 }).exec();\n```\n\nThis will return the 50 first items and the last evaluated key. To fetch the next page, simply use:\n\n```typescript\nconst nextPage = albums.query().keys({ artist: 'Dire Straits' }).paginate(result.nextPage)exec();\n```\n\n### Filtering\n\nFiltering process is the same for query and scan operations. [See above](filtering-scan-operations).\n\n### Sorting\n\nYou can use `sort` helpers to sort the result based on the range key.\n\n```typescript\nconst albums = new Album();\n\n// From oldest to newest\nconst result = await albums\n  .query('year_index')\n  .filter({\n    artist: 'James brown',\n  })\n  .sort('asc')\n  .exec();\n\n// From newest to oldest\nconst result = await albums\n  .query('year_index')\n  .filter({\n    artist: 'James brown',\n  })\n  .sort('desc')\n  .exec();\n```\n\n## Batch get\n\nTo perform a batch get operations, simply give the keys in argument:\n\n```typescript\nconst albums = new Album();\nconst result = await albums.batchGet([\n  { artist: 'Janis Joplin', album: \"I Got Dem Ol' Kozmic Blues Again Mama!\" },\n  { artist: 'Creedence Clearwater Revival', album: 'Willy and the Poor Boys' },\n  { artist: 'The Beatles', album: \"Sgt. Pepper's Lonely Hearts Club Band\" },\n  { artist: 'Queen', album: 'A Night at the Opera' },\n  { artist: 'The Clash', album: ' London Calling' },\n]);\n```\n\nBatch get operations are limited to 100 items.\n\nUnder the hood, if you give more than 100 keys or keys pair, dynamodels will split the operation in chunks of 100 items.\n\nFor instance a batchGet operation with 642 keys will be automatically split in 7 batches.\n\n## Update\n\nA wrapper around `putItem` operations is provided.\n\nThe synthax is the following:\n\nIf table has a simple hashkey:\n\n```typescript\nconst users = new User();\nawait user.update('some-user@domain.com', {\n  password: put(hashSync('n3wP4ssW0rd', 10)),\n  additional_details: remove(),\n});\n```\n\nTable has a composite key:\n\n```typescript\nconst albums = new Album();\nawait album.update('Jimi Hendrix', 'Are You Experienced', {\n  year: add(1967),\n  genre: put(['Rock', 'Blues', 'Psychadelic']),\n});\n```\n\nThe three helpers method, `add`, `remove`, and `put`provide convenient synthax to easily build `DoumentClient.UpdateAttributes` objects.\n\n## Delete\n\nTable has a simple hashkey:\n\n```typescript\nconst users = new User();\nawait user.delete('some-user@domain.com');\n```\n\nTable has a composite key:\n\n```typescript\nconst albums = new Album();\nawait album.delete('Nirvana', 'Nevermind');\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneoxia%2Fdynamodels","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneoxia%2Fdynamodels","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneoxia%2Fdynamodels/lists"}