{"id":14383991,"url":"https://github.com/snd/mesa","last_synced_at":"2025-08-23T16:31:54.801Z","repository":{"id":4663912,"uuid":"5809901","full_name":"snd/mesa","owner":"snd","description":"simple elegant sql for nodejs","archived":true,"fork":false,"pushed_at":"2017-05-31T10:25:32.000Z","size":217,"stargazers_count":11,"open_issues_count":3,"forks_count":5,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-09T10:48:14.166Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"CoffeeScript","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/snd.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":"2012-09-14T13:54:10.000Z","updated_at":"2023-01-28T18:27:48.000Z","dependencies_parsed_at":"2022-08-25T20:00:37.307Z","dependency_job_id":null,"html_url":"https://github.com/snd/mesa","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/snd/mesa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snd%2Fmesa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snd%2Fmesa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snd%2Fmesa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snd%2Fmesa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/snd","download_url":"https://codeload.github.com/snd/mesa/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snd%2Fmesa/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271755685,"owners_count":24815459,"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","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-08-28T18:01:04.899Z","updated_at":"2025-08-23T16:31:54.516Z","avatar_url":"https://github.com/snd.png","language":"CoffeeScript","readme":"# mesa\n\n[![NPM Package](https://img.shields.io/npm/v/mesa.svg?style=flat)](https://www.npmjs.org/package/mesa)\n[![Build Status](https://travis-ci.org/snd/mesa.svg?branch=master)](https://travis-ci.org/snd/mesa/branches)\n[![codecov.io](http://codecov.io/github/snd/mesa/coverage.svg?branch=master)](http://codecov.io/github/snd/mesa?branch=master)\n[![Dependencies](https://david-dm.org/snd/mesa.svg)](https://david-dm.org/snd/mesa)\n\n\u003e simply elegant sql for nodejs\n\n**this documentation targets the upcoming `mesa@1.0.0` release\ncurrently in alpha and available on npm as `mesa@1.0.0-alpha.*`.**\n**it's already used in production, is extremely useful, well tested\nand quite stable !**\n\n**mesa is a moving target. we are using it in production and it\ngrows and changes with the challenges it helps us solve.**\n\n**`mesa@1.0.0` will be released when it's done !**\n\nthis documentation does not yet represent everything that is possible with mesa.\nfeel free to [look at the code](src/mesa.coffee). it's just around 600 lines.\n\n[click here for documentation and code of `mesa@0.7.1` which will see no further development.](https://github.com/snd/mesa/tree/0.7.1)\n\n## install\n\ninstall latest:\n\n```\nnpm install --save mesa\n```\n\nmesa needs [node-postgres](https://github.com/brianc/node-postgres):\n\n```\nnpm install --save pg\n```\n\nrequire both:\n\n``` js\nvar mesa = require('mesa');\nvar pg = require('pg');\n```\n\n## connections\n\nlet's tell mesa how to get a database connection for a query:\n\n``` js\nvar database = mesa\n  .setConnection(function(cb) {\n    pg.connect('postgres://localhost/your-database', cb);\n  });\n```\n\na call to `setConnection` is the (only) thing\ntying/coupling the `database` mesa-object\nto the node-postgres library and to the specific database.\n\n## core ideas and configuration\n\ncalling `setConnection(callbackOrConnection)` has returned a new object.\nthe original mesa-object is not modified:\n\n``` js\nassert(database !== mesa);\n```\n\n**mesa embraces functional programming:\nno method call on a mesa-object modifies that object.\nmesa configuration methods are [pure](https://en.wikipedia.org/wiki/Pure_function):\nthey create a NEW mesa-object copy all OWN properties over to it,\nset some property and return it.**\n\nthis has no effect:\n\n``` js\nmesa\n  .setConnection(function(cb) {\n    pg.connect('postgres://localhost/your-database', cb);\n  });\n```\nit creates a new object that is not used anywhere and eventually gets garbage collected.\n\nlet's configure some tables:\n\n``` js\nvar movieTable = database.table('movie');\n\nvar personTable = database.table('person');\n```\n\nthere are no special database-objects, table-objects or query-objects in mesa.\nonly mesa-objects that all have the same methods.\norder of configuration method calls does not matter.\nyou can change anything at any time:\n\n``` js\nvar personTableInOtherDatabase = personTable\n  .setConnection(function(cb) {\n    pg.connect('postgres://localhost/your-other-database', cb);\n  });\n```\n\n**it naturally follows that method calls on mesa-objects are chainable !**\n\n``` js\nvar rRatedMoviesOfThe2000s = movieTable\n  // `where` accepts raw sql and optional parameter bindings\n  .where('year BETWEEN ? AND ?', 2000, 2009)\n  // repeated calls to where are 'anded' together\n  // `where` accepts objects that describe conditions\n  .where({rating: 'R'});\n```\n\n### criterion\n\nthe `.where()` and `.having()` methods take **exactly** the same\narguments as criterion...\n\nwe can always get the SQL and parameter bindings of a mesa-object:\n\n``` js\nrRatedMoviesOfThe2000s.sql();\n// -\u003e 'SELECT * FROM \"movie\" WHERE (year BETWEEN ? AND ?) AND (rating = ?)'\nrRatedMoviesOfThe2000s.params();\n// -\u003e [2000, 2009, 'R']\n```\n\n### query\n\n### mohair\n\nmesa uses mohair to generate sql which it then sends to the database.\nin addition to it's own methods every mesa-object has the entire interface\nof a mohair-object.\nfor this reason the mohair methods are not documented in this readme.\nconsult the mohair documentation as well to get the full picture.\n\nmesa supports all methods supported by mohair with some additions.\nlook into mohairs documentation to get the full picture of what's possible with mesa.\n\n**mohair powers mesa's `.where`\n\n### criterion\n\nmesa's `.where` method is one such method that is implemented by mohair\nmohair uses criterion\n\nfor this reason the criterion methods are not documented in this readme.\n\n**criterion powers/documents mesa's `.where` and `.having`\n\nwe can refine:\n\n``` js\nvar top10GrossingRRatedMoviesOfThe2000s = rRatedMoviesOfThe2000s\n  .order('box_office_gross_total DESC')\n  .limit(10);\n```\n\n**because every mesa-object gets a copy of all\na method added to a mesa-object\nis available on all mesa-objects down the chain.**\n\nthis makes it very easy to extend the chainable interface...\n\n``` js\nmovieTable.betweenYears = function(from, to) {\n  return this\n    .where('year BETWEEN ? AND ?', from to);\n};\n\nmovieTable.page = function(page, perPage) {\n  perPage = perPage ? perPage : 10;\n  return this\n    .limit(perPage)\n    .offset(page * perPage);\n};\n\nvar paginatedTopGrossingPG13RatedMoviesOfThe90s = movieTable\n  // we can freely chain and mix build-in and custom methods !\n  .order('box_office_gross_total DESC')\n  .page(2)\n  .where({rating: 'PG13'})\n  .betweenYears(1990, 1999);\n```\n\n**we see how pure functions and immutability lead to simplicity, reusability\nand [composability](#composability) !**\n\n## select queries\n\nwe can run a select query on a mesa object and return all results:\n\n``` js\ntop10GrossingRRatedMoviesOfThe2000s\n  // run a select query and return all results\n  .find()\n  // running a query always returns a promise\n  .then(function(top10Movies) {\n  });\n```\n\n**running a query always returns a promise !**\n\nwe can run a select query on a mesa object and return only the first result:\n\n``` js\ntop10GrossingRRatedMoviesOfThe2000s\n  // run a select query and return only the first result\n  // `first` automatically calls `.limit(1)` to be as efficient as possible\n  .first()\n  // running a query always returns a promise\n  .then(function(topMovie) {\n\n  });\n```\n\nwe can also simply check whether a query returns any records:\n\n``` js\nmovieTable\n  .where({name: 'Moon'})\n  .exists()\n  // running a query always returns a promise\n  .then(function(exists) {\n\n  });\n```\n\n## insert queries\n\nwe can run an insert query on a mesa object:\n\n``` js\nmovieTable\n  // whitelist some properties to prevent mass assignment\n  .allow('name')\n  .insert({name: 'Moon'})\n  // running a query always returns a promise\n  // if insert is called with a single object only the first inserted object is returned\n  .then(function(insertedMovie) {\n  })\n```\n\nbefore running insert queries\n\nif you have control over the properties of the inserted objects\nand can ensure that no properties\ncan disable this by calling `.unsafe()`.\nyou can reenable it by calling `.unsafe(false)`.\n\nyou can insert multiple records by passing multiple arguments and/or arrays\nto insert:\n\n``` js\nmovieTable\n  // disable mass-assignment protection\n  .unsafe()\n  // running a query always returns a promise\n  .insert(\n    {name: ''}\n    [\n      {name: ''}\n      {name: ''}\n    ]\n    {name: ''}\n  )\n  .then(function(insertedMovies) {\n  })\n```\n\nyou see that mesa returns the inserted records by default\n\n## update queries\n\nThis part is coming soon.\n\n## delete queries\n\nThis part is coming soon.\n\n## sql fragments\n\nan sql fragment is any object with the following properties:\n\nan sql([escape]) method which returns an sql string\ntakes an optional parameter escape which is a function to be used to escape column and table names in the resulting sql\na params() method which returns an array\n\nat the heart of mesa is the query method\n\nif you pass mesa (which is an sql fragment) into the query function...\n\n## connections revisited\n\nThis part is coming soon.\n\n``` js\n```\n\n`setConnection` either accepts\n\n`wrapInConnection`\n\nall of sql\n\ndown to the metal\n\n## debugging\n\n``` js\nmesaWithDebug = mesa.debug(function( , detail, state, verboseState, instance)\n```\n\nonly on refined versions\n\nintermediary results\n\ndebugging per table, per query, ...\n\ndirectly before a query debug will just for that specific query\n\njust display sql\n\n``` js\nmesa = mesa.debug(function(topic, query, data)\n  if (topic === 'query' \u0026\u0026 event === 'before') {\n    console.log('QUERY', data.sql, data.params);\n  }\n});\n```\n\nthe topics are `connection`, `query`, `transaction`, `find`, `embed`\n\nthat function will be called with five arguments\n\nthe first argument is \n\nthe fifth argument is the instance\n\nthe fourth argument contains ALL additional local state that is `connection, arguments`\n\nhere is a quick overview:\n\nlook into the source to see exactly which \n\n\n\n## queueing\n\noften you want to for all tables, a specific table\na specific \ndo something to the records\n\nyou can `configure` mesa instances.\n\nyou can add functions to the queues with the following ways\n\nhooks either run on a the array of all items or item\n\narray queues are run before \n\nfunctions in queues are run in the order they were added.\n\nthere are the following queues:\n\n- `queueBeforeInsert` run before insert on array of items\n- there is no `queueBeforeUpdate` because update always operates on a single item. use `queueBeforeEachUpdate`\n- `queueBeforeEachInsert` run before insert on each item\n- `queueBeforeEachUpdate` run before update on each item\n- `queueBeforeEach` run before update or insert on each item\n\n- `queueAfterSelect` run after find or first on array of items\n- `queueAfterInsert` run after insert on array of items\n- `queueAfterUpdate` run after update on array of items\n- `queueAfterDelete` run after delete on array of items\n- `queueAfter` run after find, first, insert, update and delete on array of items\n\n- `queueAfterEachSelect` run after find or first on each item\n- `queueAfterEachInsert` run after insert on each item\n- `queueAfterEachUpdate` run after update on each item\n- `queueAfterEachDelete` run after delete on each item\n- `queueAfterEach` run after find, first, insert, update and delete on each item\n\n### nice things you can to with queueing:\n\n#### omit password property when a user is returned\n\n``` js\nvar _ = require('lodash');\n\nuserTable\n  .queueAfterEachSelect(_.omit, 'password')\n  .where({id: 3})\n  .first(function(user) {\n  });\n```\n\n#### hash password before user is inserted or updated\n\n``` js\nvar Promise = require('bluebird');\nvar bcrypt = Promise.promisifyAll(require('brypt'));\n\nvar hashPassword = function(record) {\n  if (record.password) {\n    bcrypt.genSaltAsync(10).then(function(salt) {\n      return bcrypt.hashAsync(password, salt);\n    });\n  } else {\n    return Promise.resolve(null);\n  };\n}\n\nuserTable = userTable.queueBeforeEach(hashPassword);\n```\n\n#### convert property names between camelCase and snake_case\n\n[see example/active-record.coffee](example/active-record.coffee)\n\n#### set columns like `created_at` and `updated_at` automatically\n\n``` js\nuserTable = userTable\n  .queueBeforeEach(function(record) {\n    record.updated_at = new Date();\n    return record;\n  })\n  .queueBeforeInsert(function(record) {\n    record.created_at = new Date();\n    return record;\n  });\n```\n\n#### fetch associated data\n\n[see includes](#includes)\n\n#### protect from mass assignment\n\nmesa comes with a very powerful mechanism to manipulate\nrecords before they are sent to the database or after they were received\nfrom the database and before returning them.\n\nif you are familiar with the active record pattern\nprefer a more object-oriented style\nhere is how you would use mesa to implement it\nas the foundation\nas the building blocks\n\n\nif you want to use camelcased property names in your program\nand underscored in your database you can automate the translation\n\n```\n\n\n```\n\nadd them to the mesa instance and have it work for all your tables\n\nby setting the order you ensure that the other hooks see\ncamelcased properties !!!\n\n## includes\n\n**includes are a NEW feature and may not be as stable as the rest**\n\nin any rows in different tables are linked via foreign keys.\n\nincludes make it easy to fetch those linked rows and add them to our data:\n\nlets assume, for a moment, the following tables and relationships:\n\n- `user` with columns `id`, `name`. has one `address`, has many `orders`\n- `address` with columns `id`, `street`, `city`, `user_id`. belongs to `user` via foreign key `user_id` -\u003e `user.id`\n- `order` with columns `id`, `status`. belongs to `user`\n\n``` js\nuserTable = database.table('user');\naddressTable = database.table('address');\norderTable = database.table('order');\n```\n\nwe can now find some users and include the orders in each of them:\n\n### has many relationship\n\n``` js\nuserTable\n  .include(orderTable)\n  .find(function(users) {\n\n  })\n```\n\na lot is happening here. let's break it down:\n\ninclude has no side-effects and does not fetch any data.\ninstead it [queues](#queueing) a function to be executed\non all results (if any) of `first`, `find`, `insert`, `delete` and `update`\nqueries further down the chain.\n\nin this case that function will\nwill run a query on `orderTable` to fetch all\norders where `order.user_id` is in the list of all `id` values in `users`.\nit will then for every user add as property `orders` the list of all\norders where `user.id === order.user_id`.\n\n**by default include queues a fetch of a has-many relationship**\n\nthe above code snippet is equivalent to this:\n\n``` js\nuserTable\n  .include({\n    left: 'id',\n    right: 'user_id',\n    forward: true,\n    first: false,\n    as: 'orders'\n  }, orderTable)\n  .find(function(users) {\n\n  })\n```\n\nthe first argument to\n\nin case that link-object is missing or any properties are missing (and only those fields)\nmesa will autocomplete it from table names , primary keys set with `.primaryKey(key)`\n\n### belongs to relationship\n\n``` js\norderTable\n  .include({forward: false, first: true}, userTable)\n  .find(function(users) {\n\n  })\n```\n\n### has many through\n\nyou can add as many additional link\n\n\n\nyou can modify, add conditions\n\n\n\nyou can nest\n\n\n\n\n\n\nusing an explicit link object:\n\n\n**you get the idea**\n\nincludes are intentionally very flexible.\nthey work with any two tables where the values in \nwhose values match up.\n\nif you are using primary keys other than `id`\n\nfetch a one-to-one association (in a single additional query)\n\nthe implementation uses the hooks\nits surprisingly simple\n\nusing the same connection as the\n\nuse one additional query to fetch all \nand then associate them with the records\n\norder and conditions and limits on the other tables have their full effects\n\n## conditional\n\nusing mesa you'll often find yourself calling methods only\nwhen certain conditions are met:\n\n``` js\nvar dontFindDeleted = true;\nvar pagination = {page: 4, perPage: 10};\n\nvar tmp = userTable;\n\nif (dontFindDeleted) {\n  tmp = userTable.where({is_deleted: false});\n}\n\nif (pagination) {\n  tmp = tmp\n    .limit(pagination.perPage)\n    .offset(pagination.page * pagination.perPage);\n}\n\ntmp.find(function(users) {\n});\n```\n\nall those temporary objects are not very nice.\n\nfortunately there is another way:\n\n``` js\nuserTable\n  .when(dontFindDeleted, userTable.where, {is_deleted: false})\n  .when(pagination, function() {\n    return this\n      .limit(pagination.perPage)\n      .offset(pagination.page * pagination.perPage);\n  })\n  .find(function(users) {\n  });\n```\n\n## mesa by example\n\n## contribution\n\n**TL;DR: bugfixes, issues and discussion are always welcome.\nask me before implementing new features.**\n\ni will happily merge pull requests that fix bugs with reasonable code.\n\ni will only merge pull requests that modify/add functionality\nif the changes align with my goals for this package\nand only if the changes are well written, documented and tested.\n\n**communicate:** write an issue to start a discussion\nbefore writing code that may or may not get merged.\n\n## [license: MIT](LICENSE)\n","funding_links":[],"categories":["CoffeeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnd%2Fmesa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnd%2Fmesa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnd%2Fmesa/lists"}