{"id":16410716,"url":"https://github.com/tamino-martinius/node-next-model","last_synced_at":"2025-03-21T03:31:31.081Z","repository":{"id":17628400,"uuid":"81219098","full_name":"tamino-martinius/node-next-model","owner":"tamino-martinius","description":"Rails like models using ES6.","archived":false,"fork":false,"pushed_at":"2023-01-03T15:15:23.000Z","size":1492,"stargazers_count":4,"open_issues_count":27,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-15T07:27:38.584Z","etag":null,"topics":["database","model","node","node-js","node-module"],"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/tamino-martinius.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","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":"2017-02-07T14:50:19.000Z","updated_at":"2020-01-06T09:56:17.000Z","dependencies_parsed_at":"2023-01-11T20:26:59.976Z","dependency_job_id":null,"html_url":"https://github.com/tamino-martinius/node-next-model","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamino-martinius%2Fnode-next-model","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamino-martinius%2Fnode-next-model/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamino-martinius%2Fnode-next-model/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamino-martinius%2Fnode-next-model/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tamino-martinius","download_url":"https://codeload.github.com/tamino-martinius/node-next-model/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244733598,"owners_count":20501011,"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":["database","model","node","node-js","node-module"],"created_at":"2024-10-11T06:43:41.885Z","updated_at":"2025-03-21T03:31:30.743Z","avatar_url":"https://github.com/tamino-martinius.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NextModel\n\nWrite scoped models using **TypeScript**. [![Build Status](https://travis-ci.org/tamino-martinius/node-next-model.svg?branch=master)](https://travis-ci.org/tamino-martinius/node-next-model)\n\nNextModel gives you the ability to:\n\n- Represent **models** and their data.\n- Represent **inheritance** hierarchies through related models.\n- Perform database operations in an **object-oriented** fashion.\n- Uses **Promises** for database queries.\n\n## Roadmap / Where can i contribute\n\nSee [GitHub](https://github.com/tamino-martinius/node-next-model/projects/1) project for current progress/tasks\n\n- Fix **typos**\n- Add **associations** between models\n- Improve **documentation**\n- Implement **order** in `Connector`\n- `createdAt` and `updatedAt` **timestamps**\n- Add **callbacks**\n- Predefined **validations**\n- Improve **schema** with eg. default values, limits\n- Improve **associations** eg. cascading deletions\n- Add more packages for eg. **versioning** and **soft deleting**\n- Help to improve **tests** and the test **coverage**.\n- Add more connectors for eg. **graphQL** and **dynamoDB**\n- `includes` prefetches relations with two db queries _(fetch records =\u003e pluck ids =\u003e fetch related records by ids)_ instead of one query per related model.\n\n  `User.includes({address: {}})`, `Profile.includes({user: {address: {}}})`\n\n- Add a solution to create **Migrations**\n\n## TOC\n\n- [NextModel](#nextmodel)\n  - [Roadmap / Where can i contribute](#roadmap--where-can-i-contribute)\n  - [TOC](#toc)\n  - [Example](#example)\n  - [Model Instances](#model-instances)\n    - [build](#build)\n    - [create](#create)\n    - [From Scopes and queries](#from-scopes-and-queries)\n  - [Relations WIP - Definitions will change](#relations-wip---definitions-will-change)\n    - [belongsTo WIP - Definitions will change](#belongsto-wip---definitions-will-change)\n    - [hasMany WIP - Definitions will change](#hasmany-wip---definitions-will-change)\n    - [hasOne WIP - Definitions will change](#hasone-wip---definitions-will-change)\n  - [Queries](#queries)\n    - [filterBy](#filterby)\n    - [orderBy](#orderby)\n    - [skipBy](#skipby)\n    - [limitBy](#limitby)\n  - [Scopes](#scopes)\n    - [Build from scope](#build-from-scope)\n    - [Scope chaining](#scope-chaining)\n  - [Fetching](#fetching)\n    - [all](#all)\n    - [first](#first)\n    - [count](#count)\n  - [Batches WIP - Definitions will change](#batches-wip---definitions-will-change)\n    - [updateaAll WIP - Definitions will change](#updateaall-wip---definitions-will-change)\n    - [deleteAll WIP - Definitions will change](#deleteall-wip---definitions-will-change)\n  - [Model Parameters WIP - Definitions will change](#model-parameters-wip---definitions-will-change)\n    - [connector WIP - Definitions will change](#connector-wip---definitions-will-change)\n    - [keys WIP - Definitions will change](#keys-wip---definitions-will-change)\n    - [filter WIP - Definitions will change](#filter-wip---definitions-will-change)\n    - [order WIP - Definitions will change](#order-wip---definitions-will-change)\n    - [skip WIP - Definitions will change](#skip-wip---definitions-will-change)\n    - [limit WIP - Definitions will change](#limit-wip---definitions-will-change)\n  - [Instance Attributes](#instance-attributes)\n    - [isNew](#isnew)\n    - [isPersistent](#ispersistent)\n    - [attributes](#attributes)\n    - [isChanged WIP - Definitions will change](#ischanged-wip---definitions-will-change)\n    - [changes WIP - Definitions will change](#changes-wip---definitions-will-change)\n    - [Custom Attributes WIP - Definitions will change](#custom-attributes-wip---definitions-will-change)\n  - [Instance Actions](#instance-actions)\n    - [assign](#assign)\n    - [save](#save)\n    - [delete](#delete)\n    - [reload](#reload)\n    - [revertCachanges](#revertcachanges)\n    - [isValid](#isvalid)\n  - [Changelog](#changelog)\n\n## Example\n\n```ts\n// import\nimport { Model } from '@next-model/core';\n\nclass User extends Model({\n  tableName: 'users',\n  init: (props: { firstName?: string; lastName?: string; gender?: string }) =\u003e props,\n}) {\n  static get males() {\n    return this.filterBy({ gender: 'male' });\n  }\n\n  static get females() {\n    return this.filterBy({ gender: 'female' });\n  }\n\n  static withFirstName(firstName: string) {\n    return this.filterBy({ firstName });\n  }\n\n  get addresses() {\n    return Address.filterBy({ userId: this.attributes.id });\n  }\n\n  get name(): string {\n    return `${this.attributes.firstName} ${this.attributes.lastName}`;\n  }\n}\n\nclass Address extends Model({\n  tableName: 'addresses',\n  init: (props: { street: string; userId: number }) =\u003e props,\n}) {\n  get user() {\n    return User.filterBy({ id: this.attributes.userId }).first;\n  }\n}\n\n// Creating\nuser = User.build({\n  firstName: 'John',\n  lastName: 'Doe',\n  gender: 'male',\n});\nuser.name === 'John Doe';\nuser = await user.save();\n\nuser = User.males.buildScoped({ firstName: 'John', lastName: 'Doe' });\nuser.gender === 'male';\n\nuser = await User.create({\n  firstName: 'John',\n  lastName: 'Doe',\n  gender: 'male',\n});\n\naddress = await user.addresses.createScoped({\n  street: 'Bakerstr.',\n});\naddress.userId === user.id;\n\n// Searching\nusers = await User.males.all();\nuser = await User.withFirstName('John').first();\naddresses = await user.addresses.all();\nusers = await User.filterBy({ lastName: 'Doe' }).all();\nusers = await User.males()\n  .order({ key: 'lastName' })\n  .all();\n```\n\n## Model Instances\n\n### build\n\nInitializes new record without saving it to the database.\n\n```ts\nuser = User.build({ firstName: 'John', lastName: 'Doe' });\nuser.isNew === true;\nuser.name === 'John Doe';\n```\n\n### create\n\nReturns a `Promise` which returns the created record on success or the initialized if sth. goes wrong.\n\n```ts\nuser = await User.create({\n  firstName: 'John',\n  lastName: 'Doe',\n});\n```\n\n### From Scopes and queries\n\nAn record can be `buildScoped` or `createScoped` from filters. These records are created with scope values as default.\n\n```ts\naddress = user.addresses.buildScoped();\naddress.userId === user.id;\n\nuser = User.males.buildScoped();\nuser.gender === 'male';\n\nuser = User.withFirstName('John').buildScoped();\nuser.firstName === 'John';\n\nuser = User.withFirstName('John')\n  .filterBy({ lastName: 'Doe' })\n  .buildScoped();\nuser.name === 'John Doe';\n\nuser = User.filterBy({ gender: 'male' }).buildScoped();\nuser.gender === 'male';\n```\n\n## Relations _WIP - Definitions will change_\n\nDefine the Model associations. Describe the relation between models to get predefined scopes and constructors.\n\n### belongsTo _WIP - Definitions will change_\n\nA `.belongsTo` association sets up a one-to-one connection with another model, such that each instance of the declaring model \"belongs to\" one instance of the other model.\n\nFor example, if your application includes users and addresses, and each user can be assigned to exactly one address, you'd declare the user model this way:\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  get address() {\n    return this.belongsTo(Address);\n  }\n}\n\nuser = await User.create({ addressId: id });\naddress = await user.address;\naddress.id === id;\n\nuser = User.build();\nuser.address = address;\nuser.addressId === address.id;\n```\n\n### hasMany _WIP - Definitions will change_\n\nA `.hasMany` association indicates a one-to-many connection with another model. You'll often find this association on the \"other side\" of a [belongsTo](#belongsto) association. This association indicates that each instance of the model has zero or more instances of another model.\n\nFor example, in an application containing users and addresses, the author model could be declared like this:\n\n```ts\nclass Address extends NextModel\u003cAddressSchema\u003e() {\n  get users() {\n    return this.hasMany(User);\n  }\n};\n\nusers = await address.users.all;\nuser = await address.users.create({ ... });\n```\n\n### hasOne _WIP - Definitions will change_\n\nA `.hasOne` association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model.\n\nFor example, if each address in your application has only one user, you'd declare the user model like this:\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  get address() {\n    return this.hasOne(Address);\n  }\n}\n\nclass Address extends NextModel\u003cAddressSchema\u003e() {\n  get user() {\n    return this.belongsTo(User);\n  }\n}\n\naddress = await user.address;\n```\n\n## Queries\n\n### filterBy\n\nSpecial filter syntax is dependent on used connector. But all connectors and the cache supports basic attribute filtering and the special queries $and, $or and $now. All special queries start with an leading $. The filter can be completely cleared by calling `.unfiltered`\n\n```ts\nUser.filterBy({ gender: 'male' });\nUser.filterBy({ age: 21 });\nUser.filterBy({ name: 'John', gender: 'male' });\nUser.filterBy({ $or: [{ firstName: 'John' }, { firstName: 'Foo' }] });\nUser.filterBy({ $and: [{ firstName: 'John' }, { lastName: 'Doe' }] });\nUser.filterBy({ $not: [{ gender: 'male' }, { gender: 'female' }] });\nUser.males.filterBy({ name: 'John' });\nUser.males.unfiltered().females;\n```\n\n### orderBy\n\nThe fetched data can be sorted before fetching then. The `orderBy` function takes an object with property names as keys and the sort direction as value. Valid values are `asc` and `desc`. The order can be resetted by calling `.unordered`.\n\n```ts\nUser.orderBy({ key: 'name' });\nUser.orderBy({ key: 'name', dir: SortDirection.Desc });\nUser.orderBy([{ key: 'name' }, { key: 'age', dir: SortDirection.Desc }]);\nUser.males.orderBy({ key: 'name' dir: SortDirection.Asc });\nUser.orderBy({ key: 'name' }).unordered;\n```\n\n### skipBy\n\nAn defined amont of matching records can be skipped with `.skipBy(amount)` and be resetted with `.unskipped`. The current skipped amount of records can be fetched with `.skip`.\n\n_Please note:_ `.skipBy(amount)` and `.unskipped` will return a scoped model and will not modify the existing one.\n\nDefault value is `0`.\n\n```ts\nUser.count(); //=\u003e 10\nUser.skipBy(3).count(); //=\u003e 7\nUser.count(); //=\u003e 10 - creates new instance and does not modify existing\nUser.skipBy(15).count(); //=\u003e 10\nUser.skipBy(5).unskipped.count(); //=\u003e 10\n```\n\n### limitBy\n\nThe resultset can be limited with `.limitBy(amount)` and be resetted with `.unlimited`. The current limit can be fetched with `.limit`.\n\n_Please note:_ `.limitBy(amount)` and `.unlimited` will return a scoped model and will not modify the existing one.\n\nDefault value is `Number.MAX_SAFE_INTEGER`.\n\n```ts\nUser.count(); //=\u003e 10\nUser.limitBy(3).count(); //=\u003e 3\nUser.count(); //=\u003e 10 - creates new instance and does not modify existing\nUser.limitBy(15).count(); //=\u003e 10\nUser.limitBy(5).unlimited.count(); //=\u003e 10\n```\n\n## Scopes\n\nScopes are predefined queries on a Model. For example filters, orders and limitations.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get males() {\n    return this.filterBy({ gender: 'male' });\n  }\n\n  static get females() {\n    return this.filterBy({ gender: 'female' });\n  }\n\n  static withFirstName(firstName) {\n    return this.filterBy({ firstName });\n  }\n}\n```\n\nNow you can use these scopes to search/filter records.\n\n```ts\nUser.males;\nUser.withFirstName('John');\n```\n\nScopes can be chained with other scopes or search queries.\n\n```ts\nUser.males.witFirsthName('John');\nUser.withFirstName('John').filterBy({ gender: 'transgender' });\n```\n\n### Build from scope\n\n```ts\nprofile = User.males.buildScoped();\nprofile.gender === 'male';\n```\n\n### Scope chaining\n\n```ts\nUser.males.young;\nUser.males.young.filterBy({ ... });\n```\n\n## Fetching\n\nIf you want to read the data of the samples of the [previous section](#fetching) you can fetch if with the following functions. Each fetching function will return a `Promise` to read the data.\n\n### all\n\nReturns all data of the query. Results can be limited by [skipBy](#skipby) and [limitBy](#limitby).\n\n```ts\nusers = await User.all();\nusers = await User.males.all();\nusers = await User.filterBy({ firstName: 'John' }).all();\n```\n\n### first\n\nReturns the first record which matches the query. Use **orderBy** to sort matching records before fetching the first one.\n\n```ts\nuser = await User.first();\nuser = await User.males.first();\nuser = await User.filterBy({ firstName: 'John' }).first();\nuser = await User.orderBy({ lastName: 'asc' }).first();\n```\n\n### count\n\nReturns the count of the matching records. Ignores [orderBy](#orderBy), [skip](#skipby) and [limit](#limitby) and always returns complete count of matching records.\n\n```ts\ncount = await User.count();\ncount = await User.males.count();\ncount = await User.filterBy({ name: 'John' }).count();\n```\n\n## Batches _WIP - Definitions will change_\n\nWhen there is a need to change/delete multiple records at once its recommended to use the following methods if possible. They provide a much better performance compared to do it record by record.\n\n### updateaAll _WIP - Definitions will change_\n\n`.updateAll(attrs)` updates all matching records with the passed attributes.\n\n```ts\nusers = await User.filterBy({ firstName: 'John' }).updateAll({ gender: 'male' });\nusers = await User.updateAll({ encryptedPassword: undefined });\n```\n\n### deleteAll _WIP - Definitions will change_\n\nDeletes and returns all matching records..\n\n```ts\ndeletedUsers = await User.deleteAll();\ndeletedUsers = await User.query({ firstName: 'John', lastName: 'Doe' }).deleteAll();\n```\n\n## Model Parameters _WIP - Definitions will change_\n\nClass Properties are static getters which can be defined with the class. Some of them can be modified by [Queries](#queries) which creates a new Class.\n\n### connector _WIP - Definitions will change_\n\nA connector is the bridge between models and the database. NextModel comes with an DefaultConnector which reads and writes on an simpe js object.\n\nAvailable connectors:\n\n- WIP [knex](https://github.com/tamino-martinius/node-next-model-knex-connector.git) (mySQL, postgres, sqlite3, ...)\n- WIP [local-storage](https://github.com/tamino-martinius/node-next-model-local-storage-connector.git) (Client side for Browser usage)\n\n```ts\nconst Connector = require('next-model-knex-connector');\nconst connector = new Connector(options);\n\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get connector() {\n    return connector;\n  }\n}\n```\n\nDefine an base model with connector to prevent adding connector to all Models.\n\n_Please note:_ In this case its better to call the `@Model` Decorator just on the final models and not on the base model, else you need to define the [modelName](#modelname) on each model because its reflected from the base model.\n\n```ts\nclass BaseModel\u003cS extends Identifiable\u003e extends NextModel\u003cS\u003e() {\n  static get connector() {\n    return connector;\n  }\n};\n\nclass User extends BaseModel\u003cUserSchema\u003e {\n  ...\n};\n\nclass Address extends BaseModel\u003cAddressSchema\u003e {\n  ...\n};\n```\n\n### keys _WIP - Definitions will change_\n\nThe `.keys` will return all possible attributes you can pass to build new model Instances. The keys depend on [schema](#schema).\n\n```ts\nclass Foo extends NextModel {\n  static get schema(): Schema {\n    return {\n      bar: { type: 'string' },\n    };\n  }\n}\nFoo.keys; //=\u003e ['bar']\n```\n\n### filter _WIP - Definitions will change_\n\nA default scope can be defined by adding a getter for `.filter`. You need call [unfiltered](#query) to search without this scope.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get filter() {\n    return {\n      deletedAt: null,\n    };\n  }\n};\n\nuser = await User.first;\nUser.unqueried.where( ... );\n```\n\n### order _WIP - Definitions will change_\n\nAdds an default Order to all queries unless its overwritten.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get order() {\n    return [\n      {\n        name: 'asc',\n      },\n    ];\n  }\n}\n```\n\n### skip _WIP - Definitions will change_\n\nAdds an default amount of skipped records on every query. This can be changed by [skipBy](#skipby) and removed by `.unskipped`.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get limit(): number {\n    return 10;\n  }\n}\n```\n\n### limit _WIP - Definitions will change_\n\nLimits all queries made to this model to an specific amount. This can be changed by [limitBy](#limitby) and removed by `.unlimited`.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get limit(): number {\n    return 100;\n  }\n}\n```\n\n## Instance Attributes\n\n### isNew\n\nAn record is new unless the record is saved to the database. NextModel checks if the identifier property is set for this attribute.\n\n```ts\naddress = Address.build();\naddress.isNew === true;\naddress = await address.save();\naddress.isNew === false;\n```\n\n### isPersistent\n\nThe opposite of [isNew](#isnew). Returns false unless the record is not saved to the database.\n\n```ts\naddress = Address.build();\naddress.isPersistent === false;\naddress = await address.save();\naddress.isPersistent === true;\n```\n\n### attributes\n\nReturns an object which contains all properties defined by schema.\n\n```ts\nuser = User.build({\n  firstName: 'John',\n  lastName: 'Doe',\n  gender: 'male',\n});\n\nuser.attributes() ===\n  {\n    id: 1,\n    firstName: 'John',\n    lastName: 'Doe',\n    gender: 'male',\n  };\n```\n\n### isChanged _WIP - Definitions will change_\n\nWhen you change a fresh build or created Class instance this property changes to true.\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.isChanged === false;\naddress.street = '2nd street';\naddress.isChanged === true;\n```\n\nThis property does not change when the value is same after assignment.\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.isChanged === false;\naddress.street = '1st street';\naddress.isChanged === false;\n```\n\n### changes _WIP - Definitions will change_\n\nThe `changes` property contains an `object` of changes per property which has changed. Each entry contains an `from` and `to` property. Just the last value is saved at the `to` property if the property is changed multiple times. The changes are cleared once its set again to its initial value, or if the record got saved.\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.changes === {};\naddress.street = '2nd street';\naddress.changes ===\n  {\n    street: { from: '1st street', to: '2nd street' },\n  };\naddress.street = '3rd street';\naddress.changes ===\n  {\n    street: { from: '1st street', to: '3nd street' },\n  };\naddress.street = '1st street';\naddress.changes === {};\n```\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.changes === {};\naddress.street = '2nd street';\naddress.changes ===\n  {\n    street: { from: '1st street', to: '2nd street' },\n  };\naddress = await address.save();\naddress.changes === {};\n```\n\n### Custom Attributes _WIP - Definitions will change_\n\nCustom attributes can be defined as on every other js class.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get schema() {\n    return {\n      firstname: { type: 'string' },\n      lastname: { type: 'string' },\n    };\n  }\n\n  get name() {\n    return `${this.firstName} ${this.lastName}`;\n  }\n}\n\nuser = User.build({\n  firstname: 'Foo',\n  lastname: 'Bar',\n});\nuser.name === 'Foo Bar';\n```\n\n## Instance Actions\n\n### assign\n\nYou can assign a new value to an [schema](#schema) defined property. This does **not** automatically save the data to the database. All assigned attributes will be tracked by [changes](#changes)\n\n```ts\naddress.assign({\n  street: '1st Street',\n  city: 'New York',\n});\n```\n\n### save\n\nSaves the record to database. Returns a `Promise` with the created record including its newly created id. An already existing record gets updated.\n\n```ts\naddress = Address.build({ street: '1st street' });\naddress = await address.save();\naddress.isNew === false;\n```\n\n```ts\naddress.street = 'changed';\naddress = await address.save();\n```\n\n### delete\n\nRemoves the record from database. Returns a `Promise` with the deleted record.\n\n```ts\naddress.isNew === false;\naddress = await address.delete();\naddress.isNew === true;\n```\n\n### reload\n\nRefetches the record from database. All temporary attributes and changes will be lost. Returns a `Promise` with the reloaded record.\n\n```ts\naddress.isNew === false;\naddress.street = 'changed';\naddress.notAnDatabaseColumn = 'foo';\naddress = address.reload();\naddress.name === '1st Street';\naddress.notAnDatabaseColumn === undefined;\n```\n\n### revertCachanges\n\nReverts an unsaved change with `#revertChange(key)` or reverts all unsaved changed with `#revertChanges()`.\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.changes === {};\naddress.street = '2nd street';\naddress.changes ===\n  {\n    street: { from: '1st street', to: '2nd street' },\n  };\naddress.revertChange('street');\naddress.changes === {};\n```\n\n```ts\naddress = Address.build({\n  street: '1st street',\n  city: 'New York',\n});\naddress.changes === {};\naddress.street = '2nd street';\n(address.city = 'San Francisco'),\n  address.changes ===\n    {\n      street: { from: '1st street', to: '2nd street' },\n      street: { from: 'New York', to: 'San Francisco' },\n    };\naddress.revertChanges();\naddress.changes === {};\n```\n\n### isValid\n\nChecks if the current instance is valid. Promises to return boolean value.\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static get validators(): Validators {\n    return {\n      ageCheck: user =\u003e Promise.resolve(user.age \u003e 0),\n    };\n  }\n}\n\nisValid = await new User({ age: 28 }).isValid; //=\u003e true\nisValid = await new User({ age: -1 }).isValid; //=\u003e flase\n\nUncheckedUser = User.skipValidator('ageCheck');\nisValid = await new User({ age: -1 }).isValid; //=\u003e true\n```\n\n```ts\nclass User extends NextModel\u003cUserSchema\u003e() {\n  static ageCheck(user): Promise\u003cboolean\u003e {\n    return Promise.resolve(user.age \u003e 0);\n  }\n\n  static get validators(): Validators {\n    return {\n      ageCheck: this.ageCheck,\n    };\n  }\n}\n```\n\n## Changelog\n\nSee [history](HISTORY.md) for more details.\n\n- `1.0.0` **2019-xx-xx** Complete rewrite in typescript\n- `0.4.1` **2017-04-05** Bugfix: before and after callback\n- `0.4.0` **2017-02-28** Added platform specific callbacks\n- `0.3.0` **2017-02-27** Tracked property changes\n- `0.2.0` **2017-02-25** Improved browser compatibility\n- `0.1.0` **2017-02-23** Added Browser compatibility\n- `0.0.4` **2017-02-16** Added callbacks for `build`, `create`, `save` and `delete`\n- `0.0.3` **2017-02-12** Added CI\n- `0.0.2` **2017-02-05** Published [knex connector](https://github.com/tamino-martinius/node-next-model-knex-connector.git)\n- `0.0.1` **2017-01-23** Initial commit with query and scoping functions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftamino-martinius%2Fnode-next-model","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftamino-martinius%2Fnode-next-model","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftamino-martinius%2Fnode-next-model/lists"}