{"id":13486836,"url":"https://github.com/peak-ai/jedlik","last_synced_at":"2025-08-20T18:32:36.591Z","repository":{"id":42792803,"uuid":"148829948","full_name":"peak-ai/jedlik","owner":"peak-ai","description":"DynamoDB ODM for Node","archived":false,"fork":false,"pushed_at":"2023-01-09T01:02:35.000Z","size":895,"stargazers_count":105,"open_issues_count":6,"forks_count":4,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2024-12-06T14:50:13.613Z","etag":null,"topics":["aws","dynamodb","javascript","nodejs","odm","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/peak-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-14T18:55:50.000Z","updated_at":"2024-11-18T13:21:02.000Z","dependencies_parsed_at":"2023-02-08T08:00:32.260Z","dependency_job_id":null,"html_url":"https://github.com/peak-ai/jedlik","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peak-ai%2Fjedlik","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peak-ai%2Fjedlik/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peak-ai%2Fjedlik/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/peak-ai%2Fjedlik/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/peak-ai","download_url":"https://codeload.github.com/peak-ai/jedlik/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230445926,"owners_count":18227060,"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","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","javascript","nodejs","odm","typescript"],"created_at":"2024-07-31T18:00:51.863Z","updated_at":"2024-12-19T14:07:09.287Z","avatar_url":"https://github.com/peak-ai.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Jedlik\n\nJedlik is an extensible DynamoDB ODM for Node, written in TypeScript.\n\nJedlik allows you to utilize the power of JavaScript classes to create models of entities in your domain, while introducing behaviour that allows you to interact with DynamoDB in a simple way.\n\n## Install\n\nYarn:\n`yarn add @peak-ai/jedlik`\nNPM:\n`npm i -S @peak-ai/jedlik`\n\n## Usage\n\nYou can use package like [joi](https://www.npmjs.com/package/joi) to validate the schema.\n\n```ts\nimport * as jedlik from '@peak-ai/jedlik';\nimport * as Joi from 'joi';\n\ninterface UserProps {\n  id: number;\n  name: string;\n  type: 'admin' | 'user';\n}\n\nconst schema = Joi.object({\n  id: Joi.number().required(),\n  name: Joi.string().required(),\n  type: Joi.string().allow('admin', 'user').required(),\n});\n\n// the name of the DynamoDB table the model should write to\n// it is assumed this table exists\nconst Users = new jedlik.Model\u003cUserProps\u003e({ table: 'users', schema });\n\nconst user = Users.create({ id: 1, name: 'Fred' }); // create a new document locally\n\nawait user.save(); // write the data to the database\n\nuser.set({ name: 'Ronak' }); // update an attribute locally\n\nconsole.log(user.get('name')); // get an attribute\n\nUsers.on('save', (u) =\u003e {\n  console.log(u.toObject()); // get the attributes as a plain object\n});\n\nawait user.save(); // write the changes to the database\n\nconst user2 = await Users.get({ id: 2 }); // query the database\n\nconsole.log(user2.toObject());\n\n// advanced filtering\nconst adminsCalledRon_ = await Users.scan({\n  filters: {\n    $or: [\n      { key: 'type', operator: '=', value: 'admin' },\n      { key: 'name', operator: 'begins_with', value: 'Ron' },\n    ],\n  },\n});\n```\n\n## API\n\n### `class Model\u003cT\u003e`\n\n#### `constructor(options: ModelOptions, config?: ServiceConfig): Model\u003cT\u003e`\n\nConstructor function that creates a new `Model`.\n\n##### `options.table (String - required)`\n\nName of the DynamoDB table to interact with\n\n##### `options.schema (Schema\u003cT\u003e - required)`\n\n- `schema.validate(item: T): { value: T, error: { name: string, details: { message: string }[] } }`\n\nA function that validates the values in a Document and applies any defaults. This is designed to be used with [Joi](https://hapi.dev/module/joi/).\n\n##### `config`\n\nOptional config that is passed directly to the underlying [`AWS.DynamoDB.DocumentClient` service constructor from the AWS SDK.](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property)\n\n#### `Model.create(item: T): Document\u003cT\u003e`\n\nReturns a new `Document` using the attributes of the given item.\n\n#### `Model.query(key: Key\u003cT\u003e, options?: QueryOptions\u003cT\u003e): Promise\u003cDocument\u003cT\u003e[]\u003e`\n\nRecursively queries the table and resolves with an array of documents which match the given key. Takes an optional options object.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the query\n- `options.index?: IndexName;` - the name of the index to query\n- `options.limit?: number;` - limit the number of documents returned\n- `options.sort?: 'asc' | 'desc';` - sort direction of the results (N.B. this uses DynamoDB's ScanIndexForward to sort)\n\nReturned items are type `Document\u003cT\u003e`.\n\n#### `Model.first(key: Key\u003cT\u003e, options? FirstOptions\u003cT\u003e): Promise\u003cDocument\u003cT\u003e\u003e`\n\nConvenience method which returns the first document found by the `Model.query` method. Throws an error if no items are found.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the query\n- `options.index?: IndexName;` - the name of the index to query\n- `options.sort?: 'asc' | 'desc';` - sort direction of the results (N.B. this uses DynamoDB's ScanIndexForward to sort)\n\n#### `Model.scan(options? ScanOptions\u003cT\u003e): Promise\u003cDocument\u003cT\u003e[]\u003e`\n\nPerforms a scan of the entire table (recursively) and returns all the found documents.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the scan\n- `options.index?: IndexName;` - the name of the index to scan\n- `options.limit?: number;` - limit the number of documents returned\n\n#### `Model.get(key: Key\u003cT\u003e) : Promise\u003cDocument\u003cT\u003e\u003e`\n\nResolves with the document that matches the given key parameter. Throws an error if no document exists.\n\n**N.B.** The key must be the full primary key defined in the table schema. If the table has a composite key, both the partition key and sort key must be provided. You cannot search on a secondary index. If you need to do one of these, use `Model.query` or `Model.first` instead.\n\n#### `Model.delete(key: Key\u003cT\u003e, options? DeleteOptions\u003cT\u003e): Promise\u003cvoid\u003e`\n\nDeletes the document that matches the given key parameter.\n\n- `options.conditions?: Conditions\u003cT\u003e` - conditions to apply to the deletion - if the condition evaluates to false, then the delete will fail\n\n#### `Model.on(eventName: EventName, callback: (document?: Document\u003cT\u003e) =\u003e void): void`\n\nRegisters an event handler to be fired after successful events. Valid event names are `delete` and `save`.\n\n### `class Document\u003cT\u003e`\n\nA `Document` represents a single item in your DynamoDB table.\n\n#### `Document.get(key: keyof T): T[K]`\n\nReturns the value of an attribute on the document.\n\n#### `Document.set(props: Partial\u003cT\u003e): void`\n\nSets the value of an attribute on the document.\n\n#### `Document.save(options? PutOptions\u003cT\u003e): Promise\u003cvoid\u003e`\n\nSaves the Documents attributes to DynamoDB, either overwriting the existing item with the given primary key, or creating a new one.\n\n- `options.conditions?: Conditions\u003cT\u003e` - - conditions to apply to the underlying put request - if the condition evaluates to false, then the request will fail\n\n#### `Document.toObject(): T`\n\nReturns a plain JavaScript object representation of the documents attributes.\n\n### `class Client\u003cT\u003e`\n\nA low-level DynamoDB client. It has all of the main functionality of the AWS DynamoDB document client. `Model` and `Document` classes use this behind the scenes.\n\nThis class is similar to the main `Model` class, but offers support for `put` and `update` requests, and doesn't have extra features such as validation and events, and it returns the data directly, rather than converting it to `Documents`.\n\n#### `constructor(tableName: string, config?: ServiceConfig): Model\u003cT\u003e`\n\nConstructor function that creates a new `Client`.\n\n##### `table (String - required)`\n\nName of the DynamoDB table to interact with\n\n##### `config`\n\nOptional config that is passed directly to the underlying [`AWS.DynamoDB.DocumentClient` service constructor from the AWS SDK.](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#constructor-property)\n\n#### `Client.query(key: Key\u003cT\u003e, options?: QueryOptions\u003cT\u003e): Promise\u003cT[]\u003e`\n\nRecursively queries the table and resolves with an array of documents which match the given key. Takes an optional options object.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the query\n- `options.index?: IndexName;` - the name of the index to query\n- `options.limit?: number;` - limit the number of documents returned\n- `options.sort?: 'asc' | 'desc';` - sort direction of the results (N.B. this uses DynamoDB's ScanIndexForward to sort)\n\nReturned items are type `Document\u003cT\u003e`.\n\n#### `Client.first(key: Key\u003cT\u003e, options? FirstOptions\u003cT\u003e): Promise\u003cT\u003e`\n\nConvenience method which returns the first document found by the `Client.query` method. Throws an error if no items are found.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the query\n- `options.index?: IndexName;` - the name of the index to query\n- `options.sort?: 'asc' | 'desc';` - sort direction of the results (N.B. this uses DynamoDB's ScanIndexForward to sort)\n\n#### `Client.scan(options? ScanOptions\u003cT\u003e): Promise\u003cT[]\u003e`\n\nPerforms a scan of the entire table (recursively) and returns all the found documents.\n\n- `options.filters?: Conditions\u003cT\u003e` - filters to apply to the scan\n- `options.index?: IndexName;` - the name of the index to scan\n- `options.limit?: number;` - limit the number of documents returned\n\n#### `Client.get(key: Key\u003cT\u003e) : Promise\u003cT\u003e`\n\nResolves with the document that matches the given key parameter. Throws an error if no document exists.\n\n**N.B.** The key must be the full primary key defined in the table schema. If the table has a composite key, both the partition key and sort key must be provided. You cannot search on a secondary index. If you need to do one of these, use `Client.query` or `Client.first` instead.\n\n#### `Client.delete(key: Key\u003cT\u003e, options? DeleteOptions\u003cT\u003e): Promise\u003cvoid\u003e`\n\nDeletes the document that matches the given key parameter.\n\n- `options.conditions?: Conditions\u003cT\u003e` - conditions to apply to the deletion - if the condition evaluates to false, then the delete will fail\n\n#### `Client.put(item: T, options: PutOptions\u003cT\u003e = {}): Promise\u003cvoid\u003e`\n\nSaves an item into the database.\n\n- `options.conditions?: Conditions\u003cT\u003e` - conditions to apply to the put request\n\n#### `Client.update(key: Key\u003cT\u003e, updates: UpdateMap\u003cT\u003e ): Promise\u003cT\u003e`\n\nPerforms an update request on an item in the database. Resolves with the newly saved data.\n\n- `options.key: Key\u003cT\u003e` - key of the item to update\n- `options.updates: UpdateMap\u003cT\u003e` - updates to apply\n\n### `Conditions\u003cT\u003e`\n\n- [AWS Documentation - Condition Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)\n\nCondition maps give you a nicer way of writing filter and condition expressions without worrying about `ExpressionAttributeNames` and `ExpressionAttributeValues`.\n\nA simple condition would look like this:\n\n```ts\nconst condition = { key: 'price', operator: '\u003e', value: 10 };\n```\n\nThis says **\"_do this thing if the price is greater than 10_\"** - you could use it filter results in a query, or to prevent an item from being deleted or overwritten.\n\nYou can also get more complex, using logical groups with `$and` `$or` and `$not` groups. `$and` and `$or` must be lists of condition groups. `$not` must be a single condition group:\n\n```ts\nconst complexCondition = {\n  $or: [\n    { key: 'price', operator: '\u003e', value: 10 },\n    {\n      $and: [\n        { key: 'price', operator: '\u003e', value: 5 },\n        { $not: { key: 'name', operator: 'begins_with', value: 'MyItem' } },\n      ],\n    },\n  ],\n};\n```\n\nThis says **\"_do this thing if either a) the price is greater than 10, or b) the price is greater than 5 and the name noes not begin with MyItem_\"** - the resulting ConditionExpression/FilterExpression sent to DynamoDB would look something like `(#price \u003e :price10 OR (#price \u003e :price5 AND NOT begins_with(#name, :name)))`.\n\n#### Supported Operators\n\nSupported operators are:\n\n- `AND` (`$and`)\n- `OR` (`$or`)\n- `NOT` (`$not`)\n- `=`\n- `\u003c\u003e`\n- `\u003c`\n- `\u003c=`\n- `\u003e`\n- `\u003e=`\n- `begins_with`\n- `contains`\n- `attribute_exists` - conditions with this operator do not accept a `value` key\n- `attribute_not_exists` - conditions with this operator do not accept a `value` key\n\n#### Workarounds\n\n`BETWEEN`, `IN`, `attribute_type` and `size` operators are not currently supported.\n\nThese will be added at some point, but you can usually get around the lack of `BETWEEN` and `IN` support using `$and` or `$or` groups. e.g. `price BETWEEN 10 AND 20` can be done as:\n\n```ts\n{\n  $and: [\n    { key: 'price', operator: '\u003e', value: 10 }\n    { key: 'price', operator: '\u003c', value: 20 }\n  ]\n}\n```\n\n### `UpdateMap\u003cT\u003e`\n\n- [Documentation - Update Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)\n\n`UpdateMaps` are used when making update requests using the `jedlik.Client` class and take away some of the complexity of writing and coordinating `UpdateExpressions`, `ExpressionAttributeNames` and `ExpressionAttributeValues`.\n\nYou can perform `set`, `remove`, `add`, and `delete` updates. You can combine different types of update in one expression - they will be applied in the aforementioned order.\n\n#### Set updates\n\n**Set** updates have two parts - normal updates (which are always applied) and conditional updates (which will only be applied if the attribute exists already):\n\n```ts\n{\n  set: [{ id: 10, name: 'Michael' }, { age: 20 }];\n}\n```\n\nThis says **\"_set the id to 10 and the name to Michael, and if the age property exists, set it to 20_\"**\n\nFor setting nested attributes, just pass the partial object that you want to update. For example, if you have this object in your database:\n\n```ts\n{\n  name: 'Michael',\n  address: {\n    street: 'Example Avenue',\n    town: 'Exampleville',\n  },\n};\n```\n\nThe following update:\n\n```ts\nclient.update(key, { set: [{ address: { street: 'Different Street' } }] });\n```\n\nwould only update the `street` property on the address.\n\nIf you wanted to override the whole address property, you can wrap the new value in the `Literal` function.\n\n```ts\nimport { Literal } from '@peak-ai/jedlik';\n\nclient.update(key, {\n  set: [{ address: Literal({ street: 'Different Street' }) }],\n});\n```\n\n#### Remove updates\n\n**Remove** updates remove an attribute from an item. Just pass the path to the value you want to delete, with a value of true\n\n```ts\nclient.update(key, { delete: [{ address: { street: true } }] });\n```\n\nThe above expression would delete the items `name` and `street.address` attributes.\n\n#### Add updates\n\n**Add** updates add values to a DynamoDB set, or to a numeric value. You can only add to sets or numbers. Note that DynamoDB sets are different to Arrays and native JavaScript Sets.\n\nYou can create a DynamoDB `Set` using the `createSet` function.\n\nIf you have the following object in your database:\n\n```ts\n{\n  age: 10,\n  favouriteFoods: createSet(['Pizza', 'Ice Cream']),\n}\n```\n\nThen the following expression:\n\n```ts\nclient.update(key, {\n  add: [{ age: 1, favouriteFoods: createSet(['Chocolate']) }],\n});\n```\n\nThe resulting item would look like:\n\n```ts\n{\n  age: 12,\n  favouriteFoods: createSet(['Pizza', 'Ice Cream', 'Chocolate']), // Chocolate is added to the set\n}\n```\n\n#### Delete updates\n\n**Delete** updates remove items from a DynamoDB set. Note that DynamoDB sets are different to Arrays and native JavaScript Sets.\n\nYou can create a DynamoDB `Set` using the `createSet` function.\n\nIf you have the following object in your database:\n\n```ts\n{\n  age: 10,\n  favouriteFoods: createSet(['Pizza', 'Ice Cream']),\n}\n```\n\nThen the following expression:\n\n```ts\nclient.update(key, {\n  delete: [{ favouriteFoods: createSet(['Pizza']) }],\n});\n```\n\nThe resulting item would look like:\n\n```ts\n{\n  age: 12,\n  favouriteFoods: createSet(['Ice Cream']), // Pizza is no longer in the set\n}\n```\n\n## Roadmap\n\nSome features that I'd still like to add\n\n- Support for more complicated filter types - [the full list is here](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)\n- Ability to add methods to Documents and Models\n- Ability to add \"virtual properties\" to documents - custom getters and setters\n- Timestamps\n- Key validation. For example - You should only be able to pass key properties into `Model.get`. And you shouldn't really be able to update the key properties on a document - if you change the value of a key property of an existing document, a new document will be created. We should make this better, even if it's just by adding better type checking. Currently when you need to give a key, it just uses `Partial\u003cT\u003e` as the type.\n\n## Development\n\n### Getting Started\n\n- `git clone git@github.com:peak-ai/jedlik.git`\n- `yarn`\n\n### Build\n\n- `yarn build` compiles the TypeScript code into a `dist` directory.\n\n### Test\n\n- `yarn test` will run unit and integration tests using Jest. Integration tests run against a Dockerized DynamoDB Local. You'll need the Docker daemon running for this.\n\n### Contributing\n\n- Branch off `develop` please. PR's will get merged to `develop`. Releases will go from `develop` to `master`\n\n### Publish\n\n- Run the tests and linter on the `develop` branch\n- Switch to `master` and merge in `develop`\n- Use `yarn version` to increase the package version and release the new version. This will do the following things:\n  - Run the tests and linter\n  - Increase the version\n  - Add a new git tag for the version\n  - push the new tag to GitHub\n  - publish the package to npm\n  - push the released code to GitHub `master`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeak-ai%2Fjedlik","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeak-ai%2Fjedlik","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeak-ai%2Fjedlik/lists"}