{"id":15728592,"url":"https://github.com/vitaminjs/vitamin","last_synced_at":"2025-03-13T03:30:38.266Z","repository":{"id":57393040,"uuid":"50835428","full_name":"vitaminjs/vitamin","owner":"vitaminjs","description":"Data Mapper library for Node.js applications","archived":false,"fork":false,"pushed_at":"2020-04-30T23:31:05.000Z","size":331,"stargazers_count":4,"open_issues_count":7,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-24T15:09:37.814Z","etag":null,"topics":["database","datamapper","knex","polymorphic-relations","vitamin"],"latest_commit_sha":null,"homepage":"http://vitaminjs.github.io/vitamin","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vitaminjs.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-01T11:34:46.000Z","updated_at":"2021-07-09T13:47:22.000Z","dependencies_parsed_at":"2022-08-31T09:20:29.927Z","dependency_job_id":null,"html_url":"https://github.com/vitaminjs/vitamin","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaminjs%2Fvitamin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaminjs%2Fvitamin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaminjs%2Fvitamin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitaminjs%2Fvitamin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vitaminjs","download_url":"https://codeload.github.com/vitaminjs/vitamin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243335021,"owners_count":20274895,"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","datamapper","knex","polymorphic-relations","vitamin"],"created_at":"2024-10-03T23:04:15.670Z","updated_at":"2025-03-13T03:30:38.020Z","avatar_url":"https://github.com/vitaminjs.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e This library is under developments, so please help improving by testing and submitting issues. Many thanks\n\nVitamin provides a simple and easy to use **Data Mapper** implementation to work with your relational database.\n\nBased on [knex](//knexjs.org), it supports **Postgres**, **MySQL**, **MariaDB**, **SQLite3**, and **Oracle** databases, \nfeaturing both promise based and traditional callback interfaces, providing lazy and eager relationships loading, \nand support for one-to-one, one-to-many, and many-to-many relations.\n\n- [Installation](#installation)\n- [Defining models](#defining-models)\n- [CRUD operations](#crud-operations)\n  - [Create](#create)\n  - [Read](#read)\n  - [Update](#update)\n  - [Delete](#delete)\n- [Events](#events)\n- [Collections](#collections)\n- [Associations](#associations)\n  - [Defining relations](#defining-relations)\n    - [One To One](#one-to-one)\n    - [One To Many](#one-to-many)\n    - [Many To Many](#many-to-many)\n    - [Polymorphic relations](#polymorphic-relations)\n  - [Querying relations](#querying-relations)\n    - [Lazy loading](#lazy-loading)\n    - [Eager loading](#eager-loading)\n    - [Saving related models](#saving-related-models)\n      - [create](#create-and-createmany)\n      - [save](#save-and-savemany)\n      - [associate](#associate-and-dissociate)\n      - [attach](#attach-detach-and-updatepivot)\n      - [sync](#sync)\n      - [toggle](#toggle)\n\n***\n\n## Installation\n\n```bash\n$ npm install --save vitamin\n\n# Then add one of the supported database drivers\n$ npm install pg\n$ npm install mysql\n$ npm install mysql2\n$ npm install oracle\n$ npm install sqlite3\n$ npm install mariasql\n$ npm install strong-oracle\n```\n\nVitamin is initialized by passing an initialized Knex client instance.\nThe [knex documentation](//knexjs.org/#Installation) provides a number of examples for different use cases.\n\n```js\n// for example the database configuration is located at \"app/config/database.js\"\nvar knex = require('knex')({\n  client: 'mysql',\n  connection: {\n    host     : '127.0.0.1',\n    user     : 'your_database_user',\n    password : 'your_database_password',\n    database : 'your_database_name',\n    charset  : 'utf8'\n  }\n})\n\n// exports\nexport default require('vitamin')(knex)\n```\n\n***\n\n## Defining models\n\nTo get started, let's define a user model by specifying both, the `primaryKey` name, and the `tableName`\n\n```js\n// using the previous initialized vitamin object,\n// we define a model called `user` using `model` method of vitamin object\nexport default vitamin.model('User', {\n  \n  // the primary key, default to `id`\n  primaryKey: 'user_id',\n  \n  // the table name\n  tableName: 'users',\n  \n  // set the default values of the model attributes\n  defaults: {\n    active: true,\n    verified: false\n  }\n  \n})\n```\n\n\u003e `vitamin.model()` also accepts a custom mapper instance passed as a second argument instead of a config object.\n\n## CRUD operations\n\n\u003e You can use the standard Node.js style callbacks by calling `.asCallback(function (error, result) {})` on any promise method\n\n### Create\n\nTo create a new record in the database, simply create a new model instance, set its attributes, then call the `save` method\n\n```js\n// access the model defined earlier\nvar User = vitamin.model('User')\n\n// we create a new instance of User model with `make()`\nvar user = User.make({ name: \"John\", occupation: \"Developer\" })\n// or simply with the new operator\nvar user = new User({ name: \"John\", occupation: \"Developer\" })\n\n// then we save it\nuser.save().then(result =\u003e {\n  assert.deepEqual(result, user)\n  assert.ok(result instanceof User)\n})\n\n// or using the callbacks\nuser.save().asCallback((error, result) =\u003e {\n  if ( error ) throw error\n  \n  assert.deepEqual(result, user)\n  assert.ok(result instanceof User)\n})\n```\n\nAnother shorthand to create and save a new user is the static method `create()`\n\n```js\nUser\n  .create({ name: \"John\", occupation: \"Developer\" })\n  .catch(error =\u003e { ... })\n  .then(model =\u003e {\n    assert.ok(model instanceof User)\n  })\n```\n\n### Read\n\nBelow a few examples of different data access methods provided by vitamin\n\n* Retrieving multiple models\n\n```js\n// get a collection of all users\nUser.query().fetch().then(result =\u003e {\n  assert.ok(result instanceof Collection)\n})\n```\nThe `fetch()` method will return all the rows in the `users` table as a [collection](#collections) of `User` models.\n\nIf you may also add constraints to queries, you can use the `where()` methods in the `query builder` object returned by `query()`\n\n```js\nUser.query().where('role', \"guest\").offset(10).limit(15).fetch(['column1', 'column2', '...']).then(result =\u003e {\n  assert.ok(result instanceof Collection)\n  assert.ok(result.first() instanceof User)\n})\n```\n\nThe `findMany()` query method return a [collection](#collections) of models by their primary keys\n\n```js\nUser.query().findMany([1, 2, 3]).then(result =\u003e {\n  assert.ok(result instanceof Collection)\n  assert.equal(result.length, 3) // we expect getting 3 models\n})\n```\n\n* Retrieving single model\n\nOf course, in addition to retrieve all records of a given table, you may also retrieve a single record using `find()` or `first()`\nInstead of returning a collection of models, these methods return only a single model instance\n\n```js\n// find a user by its primary key\nUser.query().find(123).then(result =\u003e {\n  assert.ok(result instanceof User)\n  assert.equal(result.getId(), 123)\n})\n\n// fetch the `id` and `email` of the first admin user\nUser.query().where('is_admin', true).first('id', 'email').then(result =\u003e {\n  assert.ok(result instanceof User)\n  assert.ok(result.get('is_admin'))\n})\n```\n\u003e `findOrFail()`, `findOrNew()`, `firstOrCreate()`, `firstOrNew()`, `firstOrFail()` query methods are also available\n\n\u003e `findOrFail()` and `firstOrFail()` throw a `ModelNotFoundError` if no result found\n\n### Update\n\nThe `save()` model method may be used to update a single model that already exists in the database.\nTo update a model, you should retrieve it, change any attributes you wish to update, and then call the `save()`\n\n```js\n// post model is retrieved from `posts` table\n// then we modify the status attribute and save it\nvar Post = vitamin.model('post')\n\nPost.query().find(1).then(post =\u003e post.set('status', \"draft\").save())\n```\n\nIn case you have many attributes to edit, you may use the `update()` method directly\n\n```js\nvar data = { 'status': \"published\", 'published_at': new Date }\n\nPost.query().find(1).then(post =\u003e post.update(data)).then(result =\u003e {\n  assert.deepEqual(result, post)\n  assert.equal(result.get('status'), 'published')\n})\n```\n\n### Delete\n\nLikewise, once retrieved, a model can be destroyed which removes it from the database.\nTo delete a model, call `destroy()` on an existing model instance\n\n```js\nPost.make({ id : 45 }).destroy().then(result =\u003e {\n  assert.deepEqual(result, post)\n})\n```\n\nOf course, you may also run a delete query on a set of models.\n\n```js\n// we will delete all posts that are marked as draft\nPost.query().where('status', 'draft').destroy().then(...)\n```\n\n## Events\n\nModel events allow you to attach code to certain events in the lifecycle of yours models. \nThis enables you to add behaviors to your models when those built-in events `creating`, `created`, `saving`, `saved`, `updating`, `updated`, `deleting` or `deleted` occur.\n\nEvents can be defined when you register the model using the `events` config property\n\n```js\nvitamin.model('user', {\n  \n  ...\n  \n  events: {\n    \n    'creating': handlerFn,\n    \n    'saved': [\n      handler1,\n      handler2,\n    ]\n  }\n  \n})\n```\n\nOr, later with the static method `on()`\n\n```js\n// attach a listener for `created` event\nUser.on('created', function (user) {\n  assert.ok(user instanceof User)\n})\n\n// Events `saving - creating - created - saved` are fired in order when we create a new model\n\u003e\u003e\u003e User.create({ name: \"John\", occupation: \"Developer\" })\n```\n\nYou can also attach the same handler for many events separated by a white space\n\n```js\nPost.on('creating updating', updateTimestamps)\n```\n\nThe built-in events are fired automatically by the mapper, but you can trigger manually those events, or any custom ones with `emit()`\n\n```js\nPost.make().emit('saving')\n\norderModel.emit('purchased', ...arguments)\n```\n\n***\n\n## Collections\n\nAll multi-result methods, like `fetch()` or `findMany()`, return instances of the `Collection` class instead of simple arrays.\n\nHowever, collections are much more powerful than arrays and expose a variety of operations, including saving of emiting events, that may be chained using an intuitive interface.\n\n```js\n// retrieve the draft posts and make a delete query to remove those without 'publish_date'\nPost\n  .query()\n  .where('status', 'draft')\n  .fetch()\n  .then(posts =\u003e {\n    assert.ok(posts instanceof Collection)\n\n    // destroy \n    return posts.filter(model =\u003e model.get('publish_date') != null).destroy()\n  })\n  .then(deleted_posts =\u003e {\n    assert.ok(deleted_posts instanceof Collection)\n  })\n```\n\n***\n\n## Associations\n\nVitamin makes managing and working with relationships easy, and supports several types of relations:\n\n* One To One\n* One To Many\n* Many To Many\n* Polymorphic relations\n\n### Defining relations\n\n#### One to One\n\nLet's define a relation one to one between `Person` and `Phone`.\n```js\nvar Phone = vitamin.model('phone', {\n  \n  tableName: 'phones',\n  \n  relations: {\n    \n    owner: function () {\n      // BelongsTo is the inverse relation of HasOne and HasMany\n      // we refer to`Person` model by its name\n      // the `owner_id` is the foreign key in `phones` table\n      // the `id` is the primary key of the people table\n      return this.belongsTo('person', 'owner_id', 'id')\n    }\n    \n  }\n  \n})\n\nvar Person = vitamin.model('person', {\n  \n  tableName: 'people',\n  \n  relations: {\n    \n    phone: function () {\n      // the first argument is the target model name\n      // the second is the foreign key in phones table\n      // the third parameter is optional, it corresponds to the primary key of person model\n      return this.hasOne('phone', 'owner_id', 'id')\n    }\n    \n  }\n  \n})\n```\n\n#### One To Many\n\nAn example for this type, is the relation between blog `Post` and its `Author`\n```js\nvar User = vitamin.model('user', {\n  \n  tableName: 'users',\n  \n  relations: {\n    \n    posts: function () {\n      // if the foreign key is not provided, \n      // vitamin will use the parent model name suffixed by '_id',\n      // as a foreign key in the `posts` table, in this case `author_id`\n      return this.hasMany('post')\n    }\n    \n  }\n  \n})\n\nvar Post = vitamin.model('post', {\n  \n  tableName: 'posts',\n  \n  relations: {\n    \n    author: function () {\n      return this.belongsTo('user', 'author_id')\n    }\n    \n  }\n  \n})\n```\n\n#### Many To Many\n\nThis relation is more complicated than the previous. \nAn example of that is the relation between `Product` and `Category`, when a product has many categories, and the same category is assigned to many products. \nA pivot table `product_categories` is used and contains the relative keys `product_id` and `category_id`\n```js\nvitamin.model('product', {\n  \n  tableName: 'products',\n  \n  relations: {\n    \n    categories: function () {\n      return belongsToMany('category', 'product_categories', 'category_id', 'product_id')\n    }\n    \n  }\n  \n})\n\nvitamin.model('category', {\n  \n  tableName: 'categories',\n  \n  relations: {\n    \n    products: function () {\n      return belongsToMany('product', 'product_categories', 'product_id', 'category_id')\n    }\n    \n  }\n  \n})\n```\n\n#### Polymorphic relations\n\nPolymorphic relations allow a model to belong to more than one other model on a single association.\nFor example, the users of the application can like both comments and posts.\nUsing polymorphic relationships, you can use a single `Like` model for the both scenarios.\n\nHere is the schema  of this example database:\n```\nposts (id, title, body)\ncomments (id, post_id, body)\nlikes (id, likeable_id, likeable_type)\n```\nThe `likeable_id` column will contain the ID of the post or the comment, while the `likeable_type` will contain the name of the owning model.\n\n```js\nvar Post = vitamin.model('post', {\n  \n  relations: {\n    \n    likes: function () {\n      return this.morphMany('like', 'likeable')\n    }\n    \n  }\n  \n})\n\nvar comment = vitamin.model('', {\n  \n  relations: {\n    \n    likes: function () {\n      return this.morphMany('like', 'likeable')\n    }\n    \n  }\n  \n})\n\nvar Like = vitamin.model('like', {\n  \n  relations: {\n    \n    likeable: function () {\n      // the inverse relation of `morphOne` and `morphMany`\n      return this.morphTo('likeable')\n    }\n    \n  }\n  \n})\n```\n\nIn addition to those associations, you can also define many-to-many polymorphic relations.\nFor example, a `Post` and `Video` models could share both a relation to `Tag` model.\nUsing a polymorphic many-to-many relation, you can use a single list of unique tags that are shared across blog posts and videos, or any other model.\n\n```\ntags (id, name)\nposts (id, title, body)\nvideos (id, name, filename)\ntaggables (tag_id, taggable_id, taggable_type)\n```\n```js\nvar Post = vitamin.model('post', {\n  \n  relations: {\n    \n    tags: function () {\n      return this.morphToMany('tag', 'taggables', 'taggable')\n    }\n    \n  }\n  \n})\n\n// `taggables` is the pivot table name.\n// `taggable` will be used as a prefix for morph columns `taggable_id` and `taggable_type`\n\nvar Tag = vitamin.model('tag', {\n  \n  relations: {\n    \n    posts: function () {\n      return this.morphedByMany('post', 'taggables', 'taggable')\n    },\n    \n    videos: function () {\n      return this.morphedByMany('video', 'taggables', 'taggable')\n    }\n    \n  }\n  \n})\n```\n\n### Querying relations\n\n#### Lazy loading\n\nWe will use the relations defined below, to lazy load the related models\n```js\n// load the related phone model of the person with the id 123\n// we access the relation via `phone()` which return a HasOne relation instance\nvar person = Person.make({ id: 123 })\n\nperson.load(['posts']).then(function (model) {\n  assert.equal(model, person)\n  assert.instanceOf(model.getRelated('posts'), Collection)\n})\n```\n\n#### Eager loading\n\nTo load a model and its relationships in one call, you can use the query method `withRelated`\n```js\n// fetch the first article and its author\nPost.query().withRelated('author').first().then(function (post) {\n  assert.instanceOf(post.getRelated('author'), User)\n})\n\n// load all authors with their posts\nUser.query().withRelated('posts').fetch().asCallback((error, authors) =\u003e {\n  assert.instanceOf(authors, Collection)\n  \n  authors.forEach(function (author) {\n    assert.instanceOf(author.getRelated('posts'), Collection)\n    assert.instanceOf(author.getRelated('posts').first(), Post)\n  })\n})\n```\n\n### Saving related models\n\nInstead of manually setting the foreign keys, Vitamin provides many methods to save the related models.\n\n#### `create()` and `createMany()`\n\nIn addition to the `save` and `saveMany` methods, you may also use the `create` method, \nwhich accepts an array of attributes, creates a model, and inserts it into the database.\n\n```js\n// create and attach a post comment\npost.comments().create({ body: \"Hello World !!\" }).then(function (error, model) {\n  assert.instanceOf(model, Comment)\n})\n\n// create and attach the post comments\npost.comments().createMany([\n  { body: \"first comment\" }, { body: \"second comment\" }\n]).then(function (result) {\n  assert.instanceOf(result, Collection)\n  assert.instanceOf(result.first(), Comment)\n})\n```\n\n#### `save()` and `saveMany()`\n\n```js\nvar comment = Comment.make({ body: \"Hello World !!\" })\n\n// saving and attach one comment\npost.comments().save(comment).then(\n  function (model) {\n    assert.equal(model, comment)\n  },\n  function (error) {\n    ...\n  }\n)\n\n// saving many events\npost.comments().saveMany([\n  Comment.make({ body: \"first comment\" }),\n  Comment.make({ body: \"second comment\" })\n]).then(function (result) {\n  assert.instanceOf(result, Collection)\n  assert.instanceOf(result.first(), Comment)\n})\n```\n\n#### `associate()` and `dissociate()`\n\nWhen updating a `belongsTo` or `morphTo` relationship, you may use the `associate` method.\n\n```js\nvar john = Person.make({ id: 123 })\n\n// set the foreign key `owner_id` and save the phone model\nphone.owner().associate(john).save().then(...)\n\n// unset the foreign key, then save\nlike.likeable().dissociate().save().then(...)\n```\n\n#### `attach()`, `detach()` and `updatePivot()`\n\nWhen working with many-to-many relationships, Vitamin provides a few additional helper methods to make working with related models more convenient.\n\n```js\n// to attach a role to a user by inserting a record in the joining table\nuser.roles().attach(roleId).then(\n  function (result) {\n    ...\n  },\n  function (error) {\n    ...\n  }\n)\n```\n\nTo remove a many-to-many relationship record, use the detach method.\n\n```js\n// detach all roles of the loaded user\nuser.roles().detach().asCallback(function (error, result) {\n  ...\n})\n\n// detach only the role with the given id\nuser.roles().detach(roleId).then(...)\n\n// detach all roles with the given ids\nuser.roles().detach([1, 2, 3]).then(...)\n```\n\nIf you need to update an existing row in your pivot table, you may use `updatePivot` method\n\n```js\nuser.roles().updatePivot(roleId, pivotAttributes).then(...)\n```\n\n#### `sync()`\n\nYou may also use the `sync` method to construct many-to-many associations. \nThe sync method accepts an array of IDs to place on the intermediate table. \nAny IDs that are not in the given array will be removed from the intermediate table. \nSo, after this operation is complete, only the IDs in the array will exist in the intermediate table:\n\n```js\n// using callbacks\nuser.roles().sync([1, 2, 3], function (error, result) {\n  ...\n})\n\n// using promises\nuser.roles().sync([1, 2, 3]).then(...)\n```\nYou may also pass additional intermediate table values with the IDs:\n```js\nuser.roles().sync([[1, { 'expires': true }], 2]).then(...)\n```\n\n#### `toggle`\n\nThis method allows you toggle relationships without pain.\nIt accepts a model, an ID or an array of IDs, and sync _only_ those ids without affecting the others.\nUseful for `likes`, `stars` or `following` relationships\n\n```js\n// grant or revoke the admin role to a specific user\nuser.roles().toggle(adminRole).then(...)\n\n// add or remove a like on a project\nuser.likes().toggle(someProjectId).then(...)\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitaminjs%2Fvitamin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvitaminjs%2Fvitamin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitaminjs%2Fvitamin/lists"}