{"id":18256893,"url":"https://github.com/mediacomem/orm-query-builder","last_synced_at":"2025-04-08T22:28:56.713Z","repository":{"id":57316319,"uuid":"133655045","full_name":"MediaComem/orm-query-builder","owner":"MediaComem","description":"An extensible ODM/ORM wrapper to make complex queries with filters, pagination and sorting","archived":false,"fork":false,"pushed_at":"2018-05-16T12:06:36.000Z","size":111,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-15T08:16:16.730Z","etag":null,"topics":["bookshelf","orm-library","query-builder"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/MediaComem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-16T11:25:32.000Z","updated_at":"2018-05-16T12:06:37.000Z","dependencies_parsed_at":"2022-08-25T20:40:32.168Z","dependency_job_id":null,"html_url":"https://github.com/MediaComem/orm-query-builder","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Form-query-builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Form-query-builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Form-query-builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MediaComem%2Form-query-builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MediaComem","download_url":"https://codeload.github.com/MediaComem/orm-query-builder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247938548,"owners_count":21021546,"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":["bookshelf","orm-library","query-builder"],"created_at":"2024-11-05T10:24:03.083Z","updated_at":"2025-04-08T22:28:56.691Z","avatar_url":"https://github.com/MediaComem.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ORM Query Builder\n\nAn extensible wrapper around Object Document Mappers (ODMs) or Object Relational Mappers (ORMs) to\nfacilitate making complex database queries with filters, pagination and sorting.\n\n[Bookshelf][bookshelf] is supported out of the box, but you should be able to use any ODM/ORM by\nproviding an adapter that implements basic operations on its queries.\n\n[![npm version](https://badge.fury.io/js/orm-query-builder.svg)](https://badge.fury.io/js/orm-query-builder)\n[![Build Status](https://travis-ci.org/MediaComem/orm-query-builder.svg?branch=master)](https://travis-ci.org/MediaComem/orm-query-builder)\n[![Coverage Status](https://coveralls.io/repos/github/MediaComem/orm-query-builder/badge.svg?branch=master)](https://coveralls.io/github/MediaComem/orm-query-builder?branch=master)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.txt)\n\nDeveloped at the [Media Engineering Institute](http://mei.heig-vd.ch) ([HEIG-VD](https://heig-vd.ch)).\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n\n- [Installation](#installation)\n  - [Requirements](#requirements)\n- [Usage](#usage)\n- [Concepts](#concepts)\n  - [Stages \u0026 query middleware](#stages--query-middleware)\n  - [Result](#result)\n  - [Plugins](#plugins)\n  - [Adapters](#adapters)\n- [Provided plugins](#provided-plugins)\n  - [Pagination](#pagination)\n    - [Options](#options)\n  - [Sorting](#sorting)\n    - [Methods](#methods)\n    - [Options](#options-1)\n    - [Execution options](#execution-options)\n  - [Eager loading](#eager-loading)\n    - [Methods](#methods-1)\n  - [Joining](#joining)\n    - [Methods](#methods-2)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n\n## Installation\n\n```bash\nnpm install orm-query-builder\n```\n\n### Requirements\n\n* [Node.js][node] 8+\n* An ODM/ORM adapter (one is provided for [Bookshelf][bookshelf])\n\n\n\n\n\n## Usage\n\nORM Query Builder is basically a wrapper around your database queries:\n\n```js\nconst { OrmQueryBuilder } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder({ baseQuery: myQuery });\nbuilder.execute().then(result =\u003e {\n  // \"result\" is what you would get by executing \"myQuery\",\n  // which is a query object from your ODM/ORM.\n});\n```\n\n**TL;DR** features:\n\n```js\n// Require ORM Query Builder and plugins.\nconst { eagerLoading, joining, OrmQueryBuilder, pagination, sorting } = require('orm-query-builder');\n\n// Bookshelf domain model.\nconst Address = bookshelf.model('Address', {});\nconst Person = bookshelf.model('Person', {\n  addresses: function() {\n    return this.hasMany('Address');\n  }\n});\n\n// Create a builder starting from a base query.\nconst baseQuery = new Person();\nconst builder = new OrmQueryBuilder({ baseQuery });\n\n// Apply the pagination plugin (will use \"offset\" \u0026 \"limit\" options).\nbuilder.use(pagination());\n// Define available joins from model relations (for SQL-friendly ORMs).\nbuilder.use(joining(Person).relation('addresses'));\n// Define available sorting orders and the defaults (will use \"sort\" option).\nbuilder.use(sorting().sorts('email', 'firstName', 'lastName').defaultSort('lastName', 'firstName'));\n// Define conditional eager loading.\nbuilder.use(eagerLoading().loadWhen(context =\u003e context.options.include.indexOf('addresses') \u003e= 0, 'addresses'));\n\n// Define a filter to apply before pagination.\nbuilder.before('paginate', context =\u003e {\n  if (context.options.email) {\n    // Modify the query to be executed.\n    const currentQuery = context.get('query');\n    context.set('query', currentQuery.where('email', context.options.email));\n  }\n});\n\n// Define another filter.\nbuilder.before('paginate', context =\u003e {\n  if (context.options.city) {\n    // Require a join to be applied.\n    context.requireJoin('addresses');\n    // Modify the query with a condition on the joined table.\n    const currentQuery = context.get('query');\n    context.set('query', currentQuery.where('addresses.city', context.options.city));\n  }\n});\n\n// Execute the query in an Express route.\nconst app = express();\napp.get('/people', (req, res, next) =\u003e {\n  builder.execute({\n    // Return the full execution context, not only the result.\n    result: 'context',\n    // Pass relevant options from the HTTP request.\n    city: req.query.city,\n    email: req.query.email,\n    include: req.query.include || [],\n    limit: req.query.limit,\n    offset: req.query.offset,\n    sort: req.query.sort\n  }).then(context =\u003e {\n    // Use plugin data to modify response.\n    res.set('Pagination-Total', context.get('pagination.total'));\n    res.set('Pagination-Filtered-Total', context.get('pagination.filteredTotal'));\n    // Send the result of the query.\n    const result = context.get('result');\n    res.send(result.toJSON());\n  }).catch(next);\n});\n\napp.listen(process.env.PORT || 3000);\n```\n\nA more detailed explanation of the above example:\n\n```js\nconst Bookshelf = require('bookshelf');\nconst express = require('express');\nconst knex = require('knex');\n\n// Import the builder and various utility plugins.\nconst { eagerLoading, joining, OrmQueryBuilder, pagination, sorting } = require('orm-query-builder');\n\n// Configure the connection to the database (this example uses the Bookshelf ORM and Knex,\n// and assumes that a PostgreSQL database named \"orm-query-builder\" exists on your local machine).\nconst db = knex({ client: 'postgresql', connection: 'postgresql://localhost/orm-query-builder' });\nconst bookshelf = Bookshelf(db);\nbookshelf.plugin('registry');\n\n// Create an Address model.\nconst Address = bookshelf.model('Address', {});\n\n// Create a Person model that has many addresses.\nconst Person = bookshelf.model('Person', {\n  addresses: function() {\n    return this.hasMany('Address');\n  }\n});\n\n// Create a builder starting from a base query.\n// With Bookshelf, `new Person()` creates a model which we can use as a base query.\nconst baseQuery = new Person();\nconst builder = new OrmQueryBuilder({ baseQuery });\n\n// When executed, a query builder goes through stages. There are two stages by default:\n// \"start\" and \"end\". You can plug in query middleware functions before or after any\n// stage. A query middleware function is simply a function that receives an execution\n// context object and does what it wants with it. It can return a promise if it needs\n// to perform asynchronous work.\nbuilder.after('start', context =\u003e {\n  // The database query object (from your ODM/ORM) can be retrieved from the context\n  // after the \"start\" event.\n  const currentQuery = context.get('query');\n  // You could modify \"currentQuery\" here...\n  context.set(currentQuery);\n});\n\n// After the \"end\" event, the query has been executed and the result can also be\n// retrieved from the context.\nbuilder.after('end', context =\u003e {\n  const result = context.get('result');\n  console.log('The result of executing the query is', result);\n  // You could modify \"result\" here...\n  context.set('result', result);\n});\n\n// Query builder plugins are objects that have a \"use\" function.\nconst plugin = {\n  // The \"use\" function is called with the builder.\n  use: function(builder) {\n    // Customize it as needed.\n    builder.after('start', context =\u003e console.log('query is ready to be customized'));\n    builder.before('end', context =\u003e console.log('query is about to be executed'));\n    builder.after('end', context =\u003e console.log('result is available'));\n  }\n};\n\n// Plugins are passed to the query builder's \"use\" method.\nbuilder.use(plugin);\n\n// A few plugins are provided out of the box.\n//\n// The pagination plugin will use the \"offset\" and \"limit\" options passed to the\n// \"execute\" method later to paginate the request, and also count the total number\n// of records before paginating.\n//\n// Additionally, if you apply filters before the pagination is done (see examples\n// below), the plugin will make an additional count request to count the total\n// number of matching elements.\n//\n// This information can later be retrieved and used in the response (e.g. to add a\n// Link header to indicate the previous/next pages in the collection).\nbuilder.use(pagination());\n\n// The pagination plugin adds 2 new stages, \"countTotal\" and \"paginate\". You can\n// register query middleware functions to be executed before these stages to further\n// modify the query.\n//\n// For example, here we implement an e-mail filter. This filter will be applied\n// after the total number of records has been counted, but before the number of\n// matching records has been counted and before the pagination has been applied to\n// the main query.\nbuilder.before('paginate', context =\u003e {\n  if (context.options.email) {\n    const currentQuery = context.get('query');\n    context.set('query', currentQuery.where('email', context.options.email));\n  }\n});\n\n// The joining plugin facilitates the management of table joins for SQL-friendly ORMs.\n// You can tell it to check a model's relations (in this case the \"addresses\" relation\n// of the Bookshelf model \"Person\") for later use.\nbuilder.use(joining(Person).relation('addresses'));\n\n// Here's another filter, this time making use of the joining plugin.\nbuilder.before('paginate', context =\u003e {\n  if (context.options.city) {\n\n    // The joining filter has added the \"requireJoin\" function to the context,\n    // which you can use to indicate that you require this join to be applied to\n    // the query for your filter condition to work.\n    //\n    // The joining plugin will help you avoid applying a join twice, and will\n    // automatically apply the multiple joins that may be needed to access a given\n    // relation (e.g. go through a many-to-many join table).\n    context.requireJoin('addresses');\n\n    // You can now add a condition using the joined table to the query.\n    const currentQuery = context.get('query');\n    context.set('query', currentQuery.where('addresses.city', context.options.city));\n  }\n});\n\n// The sorting plugin helps you define which sorting orders are available for your\n// query, in this case \"email\", \"firstName\" and \"lastName\". You can also define the\n// default sorts you want applied.\n//\n// It will then use the \"sort\" option passed to the \"execute\" method later to determine\n// which sorts to apply.\nbuilder.use(sorting().sorts('email', 'firstName', 'lastName').defaultSort('lastName', 'firstName'));\n\n// The eager loading plugin can be used to tell your ORM to eager load some of your\n// model's relations after the main query has been executed.\n//\n// This example conditionally eager loads the \"addresses\" relation when the \"include\"\n// option contains the string \"addresses\".\nbuilder.use(eagerLoading().loadWhen(context =\u003e context.options.include.indexOf('addresses') \u003e= 0, 'addresses'));\n\n// Here's an Express route to demonstrate how to use the builder now that it's configured.\nconst app = express();\napp.get('/people', (req, res, next) =\u003e {\n\n  // Execute the configured database query.\n  builder.execute({\n\n    // By setting the \"result\" option to \"context\", you can tell the query builder to\n    // return its full execution context, not only the query result (which is what is\n    // returned by default). This is useful, for example, to retrieve the data from the\n    // pagination plugin.\n    result: 'context',\n\n    // Pass relevant options from the HTTP request (in this case from Express's \"req\" object).\n    // They are available to query middleware functions on the context's \"options\" property.\n    city: req.query.city,\n    email: req.query.email,\n    include: req.query.include || [],\n    limit: req.query.limit,\n    offset: req.query.offset,\n    sort: req.query.sort\n  }).then(context =\u003e {\n\n    // The context object may contain various data set by plugins. For example, the\n    // pagination plugin adds a \"pagination\" object with various properties, including\n    // the number of records it has counted before and after filters.\n    res.set('Pagination-Total', context.get('pagination.total'));\n    res.set('Pagination-Filtered-Total', context.get('pagination.filteredTotal'));\n\n    // You can get the query result from the context. In this example, it's a Bookshelf\n    // Collection. It has a toJSON() method which serializes the models within.\n    const result = context.get('result');\n    res.send(result.toJSON());\n  }).catch(next);\n});\n\napp.listen(process.env.PORT || 3000);\n```\n\n\n\n\n\n## Concepts\n\nThe goal of ORM Query Builder is to help manage a typical database query in a complex application,\nincluding the following:\n\n* Filtering\n* Paginating\n* Sorting\n* Eager loading\n\nAdditionally, you might often need to:\n\n* Count the total number of elements, e.g. to provide pagination data to the client.\n* Count the number of matching elements if you have filters, and make sure it's done before the\n  request is paginated.\n* Join additional tables to perform filtering or sorting (on other tables' columns).\n\nAll these operations have to be done in the right order. The total number of elements must be\ncounted before filters are applied. The number of matching filtered elements must be counted before\nthe offset and limit are applied for pagination. The correct table joins must be made for your\nfilters and sorting criteria, and you don't want to make the same join twice. Etc.\n\nThe goal of ORM Query Builder is to facilitate this process.\n\n\n\n### Stages \u0026 query middleware\n\nWhen executed, a query builder goes through stages sequentially, and does something at each stage.\nThere are 2 stages by default:\n\n* `start` - Initialize the query.\n* `end` - Execute the query and retrieve the result.\n\nYou can plug in query middleware function before or after any stage. A query middleware function is\nsimply a function that is passed the **execution context** of the query.\n\n```js\n// The base query you want to execute.\nconst baseQuery = new Person();\n// Create a query builder from that query.\nconst builder = new OrmQueryBuilder({ baseQuery });\n\n// Plug in a query middleware function after the start event\n// (i.e. after the query has been added to the context).\nbuilder.after('start', context =\u003e {\n  // Retrieve the query from the context.\n  const bookshelfQuery = context.get('query');\n  // Log the query's SQL (the `query()` method of a Bookshelf query returns the underlying\n  // Knex query, which can then be converted into a string).\n  console.log('The query is:', bookshelfQuery.query().toString());\n});\n\n// Start execution, i.e. go through each stage and return the result.\nbuilder.execute().then(result =\u003e console.log(`Found ${result.length} people`));\n\n// OUTPUT:\n// The query is: select * from people\n// Found 42 people\n```\n\nYou can **modify the query** in a query middleware function. The following example plugs in a\nmiddleware **before the `end` event**, i.e. before the query builder actually executes the query.\n\n```js\nbuilder.before('end', context =\u003e {\n  const currentQuery = context.get('query');\n  context.set('query', currentQuery.where('email', 'foo@example.com'));\n});\n\nbuilder.execute().then(result =\u003e console.log(`Found ${result.length} people`));\n\n// OUTPUT:\n// The query is: select * from people where email = 'foo@example.com'\n// Found 24 people\n```\n\nYou can **modify the result** in a query middleware function. The following example plugs in a\nmiddleware **after the `end` event**, i.e. after the query has been executed and the result\nretrieved.\n\n```js\nbuilder.after('end', context =\u003e {\n  const result = context.get('result');\n  context.set('result', result.map(person =\u003e person.toJSON()));\n});\n\nbuilder.execute().then(result =\u003e console.log(result));\n\n// OUTPUT:\n// The query is: select * from people where email = 'foo@example.com'\n// [\n//   {\n//     name: \"Foo\",\n//     email: \"foo@example.com\"\n//   },\n//   ...\n// ]\n```\n\nYou can **add new stages** in a query middleware function. The following example plugs in a\nmiddleware after the `start` event and **adds a new `countTotal` stage** to be executed next.\n\n```js\nconst baseQuery = new Person();\nconst builder = new OrmQueryBuilder({ baseQuery });\n\nbuilder.after('start', context =\u003e {\n  console.log('start stage done');\n  context.addStages('countTotal');\n});\n\nbuilder.after('countTotal', () =\u003e console.log('countTotal stage done'));\nbuilder.after('end', () =\u003e console.log('end stage done'));\n\nbuilder.execute().then(() =\u003e console.log('done'));\n\n// OUTPUT:\n// start stage done\n// countTotal stage done\n// end stage done\n// done\n```\n\nNote that query middleware can also be defined as an object with an `execute()` function (e.g.\nclass instances may also be used as query middleware).\n\n```js\nconst queryMiddlewareFunc = context =\u003e console.log('Hello Alice');\nconst queryMiddlewareObject = {\n  execute: context =\u003e console.log('Hello Bob')\n};\n\nbuilder.after('start', queryMiddlewareFunc);\nbuilder.after('start', queryMiddlewareObject);\nbuilder.execute();\n\n// OUTPUT:\n// Hello Alice\n// Hello Bob\n```\n\n\n\n### Result\n\nA query builder's `execute()` method returns the result of the query by default:\n\n```js\nbuilder.execute().then(result =\u003e console.log(result.length)); // 24\n```\n\nSometimes you might want to retrieve the whole execution context, e.g. to retrieve data added by\nsome middleware:\n\n```js\nbuilder.execute({ result: 'context' }).then(context =\u003e {\n  console.log(context.get('result'));  // 24\n  console.log(context.get('foo'));     // \"bar\"\n});\n```\n\n\n\n### Plugins\n\nAn ORM Query Builder **plugin** is simply anything that has a `use()` function. If you pass a plugin\nto a query builder, it will call that plugin's `use()` function with itself.\n\n```js\nconst plugin = {\n  use: function(builder) {\n\n    builder.after('start', context =\u003e {\n      const currentQuery = context.get('query');\n      console.log('The query is:', currentQuery.query().toString());\n    });\n\n    builder.before('end', context =\u003e {\n      const currentQuery = context.get('query');\n      context.set('query', currentQuery.where('email', 'foo@example.com'));\n    });\n  }\n};\n\nconst baseQuery = new Person();\nconst builder = new OrmQueryBuilder({ baseQuery });\n\nbuilder.use(plugin);\nbuilder.execute().then(result =\u003e console.log(`Found ${result.length} people`));\n\n// OUTPUT:\n// The query is: select * from people where email = 'foo@example.com'\n// Found 24 people\n```\n\nPlugins can be used to encapsulate complex functionality like pagination.\n\n\n\n### Adapters\n\nORM Query Builder isn't built around a specific ODM/ORM. To know how to initialize and execute a\nquery, it needs an **adapter**, basically a bag of utility functions to manipulate that ODM/ORM's\nqueries.\n\nAn adapter **must** implement the following functions:\n\n* `createQuery` - Creates a query from an execution context.\n\n  The execution context has an `options` property which contains the options passed to the builder\n  at construction, merged with the options passed to its `execute` method. A barebones\n  implementation could be to simply return a base query that has to be provided in the options:\n\n  ```js\n  function createQuery(context) {\n    return context.options.baseQuery;\n  }\n  ```\n\n  A query may be anything: a Bookshelf model, a plain object, etc. ORM Query Builder only ever\n  manipulates it through the adapter.\n* `executeQuery` - Executes a query.\n\n  ORM Query Builder doesn't know how to execute your ODM/ORM's queries, so you have to do it. This\n  is an example of how to execute a Bookshelf query:\n\n  ```js\n  function executeQuery(query, context) {\n    return query.fetchAll();\n  }\n  ```\n* `executeCountQuery` - Executes a count query based on the current query.\n\n  ORM Query Builder doesn't know how to count either. This is an example of how to do it with\n  Bookshelf:\n\n  ```js\n  function executeCountQuery(query, context) {\n    // With Bookshelf, we have to `clone()` the query first, as all Bookshelf methods\n    // mutate the query without making a copy.\n    return query.clone().count().then(value =\u003e parseInt(value, 10));\n  }\n  ```\n\nAn adapter **may** implement the following functions that are required by some plugins:\n\n* `eagerLoad` - Eager load relations after the query has been executed.\n* `getQueryIdentifier` - Return a value that can be used to identify a query in its current state.\n  The value should be the same if the query is the same, and it should differ on a query that has\n  been modified. This is used by some plugins to determine whether a query has changed between\n  stages.\n* `getTableName` - Return the name of the query's main table (for SQL-friendly ORMs).\n* `orderQueryBy` - Apply sorting to a query.\n* `paginateQuery` - Apply an offset and limit to a query.\n* `getJoinDefinitions` - Return a list of possible table joins based on a model (for SQL-friendly\n  ORMs).\n* `applyJoinDefinition` - Apply a join definition to a query (for SQL-friendly ORMs).\n\nSee the [Bookshelf adapter](lib/adapters/bookshelf.js) for a full example.\n\n\n\n\n\n## Provided plugins\n\nThese plugins are provided out of the box with ORM Query Builder, but you don't have to use them.\n\n---\n\n\n\n### Pagination\n\n**Requirements:** an adapter that supports `getQueryIdentifier` and `paginateQuery`.\n\n```js\nconst { OrmQueryBuilder, pagination } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.use(pagination());\n```\n\nThe pagination plugin adds 2 new stages, `countTotal` and `paginate`, after the `start` stage,\nmaking the stages list as follows on a query builder with no other plugin:\n\n* `start` - Initialize the query.\n* `countTotal` - Count the total number of elements.\n* *(Apply any filters here.)*\n* `paginate` - Count the number of matching elements and apply offset and limit to the query.\n* `end` - Execute the query.\n\nAs indicated, filters should be applied **before the `paginate` stage** in order to be taken into\naccount when the number of matching elements is counted.\n\nThe pagination plugin will add data to the execution context after the `start` stage, which you can\nuse to send information back to the client, e.g. a `Link` header.\n\n```js\nconsole.log(context.get('pagination'));\n// {\n//   filteredTotal: 123,\n//   offset: 0,\n//   limit: 50,\n//   total: 570\n// }\n```\n\n#### Options\n\n* **`getOffset`** - A function that retrieves the offset to apply from the context. (May also be a\n  string path to retrieve from the context using [Lodash's `get` function][lodash-get].)\n\n  ```js\n  pagination({\n    getOffset: context =\u003e context.options.pageSize * (context.options.page - 1)\n  });\n  ```\n\n  By default, the pagination plugin expects an `offset` property in the context's options.\n* **`getLimit`** - A function that retrieves the limit to apply from the context. (May also be a\n  string path to retrieve from the context using [Lodash's `get` function][lodash-get].)\n\n  ```js\n  pagination({\n    getLimit: context =\u003e context.options.pageSize\n  });\n  ```\n\n  By default, the pagination plugin expects a `limit` property in the context's options.\n* **`getDefaultLimit`** - A function that retrieves the default limit to apply if no limit is found\n  in the context.\n\n  ```js\n  pagination({\n    getDefaultLimit: () =\u003e 100\n  });\n  ```\n\n  By default, the pagination plugin returns the `defaultLimit` property of the context's options, or\n  100.\n* **`getMaxLimit`** - A function that retrieves the maximum limit that is allowed for pagination.\n  (If the specified limit exceeds it, the default limit is used instead.)\n\n  ```js\n  pagination({\n    getMaxLimit: () =\u003e 500\n  });\n  ```\n\n  By default, the pagination plugin returns the `maxLimit` property of the context's options, or\n  250.\n\n---\n\n\n\n### Sorting\n\n**Requirements:** an adapter that supports `orderQueryBy`.\n\n```js\nconst { OrmQueryBuilder, sorting } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.use(\n  sorting()\n    .sorts('foo', 'bar') // Simple sorts\n    .sort('name', direction =\u003e query =\u003e { // Complex sort\n      return query.orderBy('last_name', direction).orderBy('first_name', direction);\n    })\n    .defaultSort('foo', 'name') // Sorts to be applied by default\n);\n```\n\nThe sorting plugin dynamically applies sorting criteria to your query before the `end` stage, based\non the execution context's options. It is also a middleware that you can apply at another stage if\nyou wish:\n\n```js\nconst { OrmQueryBuilder, sorting } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.after('myStage', sorting().sorts('foo', 'bar'));\n```\n\nThe idea is to define what sorts are available (e.g. by name, by creation date), and how to apply\nthose sorts if need be. Then the plugin will automatically apply the sorts based on the `sort`\nproperty of the context's options.\n\n#### Methods\n\n* **`sort(name, factory)`** - Defines a custom sort. The `factory(direction, context)` argument is a\n  function that will be called with the sort direction and the execution context. It must return a\n  function that takes the current query and returns a modified version of the query with the sort\n  applied.\n\n  ```js\n  sorting().sort('foo', (direction, context) =\u003e query =\u003e query.orderBy('foo', direction));\n  ```\n* **`sorts(...names)`** - Defines multiple simple sorts. These sorts are simply applied to the query\n  with the adapter's `orderQueryBy(query, name, direction, context)` method, which will be called\n  with the query, sort name and direction.\n\n  You can customize how simple sorts are applied by providing the `createSimpleSort` option as\n  documented below.\n* **`default(...sorts)`** - Defines the default sorts to apply. This is in the same format as\n  the `sort` execution option documented below, e.g. `[ \"foo\", \"bar-desc\" ]` (the object form is\n  also supported).\n\n#### Options\n\n* **`getSort(context)`** - A function that returns the sort criteria to apply from the context. (May\n  also be a string path to retrieve from the context using [Lodash's `get` function][lodash-get].)\n\n  ```js\n  sorting({\n    getSort: context =\u003e context.options.orderBy\n  })\n  ```\n\n  By default, the sorting plugin expects a `sort` property in the context's options.\n* **`createSimpleSort(name)`** - A function that returns a sort factory from a sort name. It will be\n  used to create simple sorts defined with the plugin's `sorts(...names)` method. The returned sort\n  factory must behave the same as one that would be passed to the plugin's `sort(name, factory)`\n  method.\n\n  ```js\n  const inflection = require('inflection');\n\n  sorting({\n    createSimpleSort: name =\u003e (direction, context) =\u003e query =\u003e {\n      // For example, this automatically underscores sort names\n      // into column names (e.g. \"firstName\" =\u003e \"first_name\").\n      return context.adapter.orderQueryBy(query, inflection.underscore(name), direction, context);\n    }\n  })\n  ```\n\n#### Execution options\n\nThe **`sort`** option is expected to have one of the following formats:\n\n* String-based with `-asc` and `-desc` suffixes indicating the direction, ascending by default.\n\n  ```json\n  [ \"foo\", \"bar-asc\", \"baz-desc\" ]\n  ```\n* Object-based with a `name` and optional `direction` property (ascending by default).\n\n  ```js\n  [\n    { name: \"foo\" },\n    { name: \"bar\", direction: \"asc\" },\n    { name: \"baz\", direction: \"desc\" }\n  ]\n  ```\n\nA single sort parameter can also be specified, e.g. just `\"foo\"`. It will automatically be wrapped\ninto an array.\n\n---\n\n\n\n### Eager loading\n\n**Requirements:** an adapter that supports `eagerLoad`.\n\n```js\nconst { eagerLoading, OrmQueryBuilder } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.use(\n  eagerLoading()\n    // Always load this relation.\n    .load('address')\n    // Only load this relation if the context's \"include\" option indicates it.\n    .loadWhen(context =\u003e context.options.include.indexOf('socialAccounts') \u003e= 0, 'socialAccounts')\n);\n```\n\nThe eager loading plugin dynamically eager-loads your model's relations after the `end` stage (once\nthe query has been executed), based on the execution context's options. It is also a middleware that\nyou can apply at another stage if you wish:\n\n```js\nconst { eagerLoading, OrmQueryBuilder } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.after('myStage', eagerLoading().load('address'));\n```\n\nThis depends on your ODM/ORM's ability to eager-load your model's relations, as implemented in the\nadapter. You can load relations all the time or conditionally.\n\n#### Methods\n\n* **`load(relations, options)`** - Defines a relation (or relations) to be eager-loaded after the\n  query has been executed. The adapter's `eagerLoad(result, relations, options, context)` method\n  will be called with the arguments as is, after the `end` stage.\n* **`loadWhen(predicate, relations, options)`** - Defines a relation (or relations) to be\n  eager-loaded only if the specified predicate matches. The predicate will be called with the\n  context after the `end` stage to determine whether to load the relations.\n\n---\n\n\n\n### Joining\n\n**Requirements:** an adapter that supports `getTableName`, `getJoinDefinitions` and\n`applyJoinDefinition`.\n\n```js\nconst { joining, OrmQueryBuilder } = require('orm-query-builder');\n\nconst builder = new OrmQueryBuilder();\nbuilder.use(\n  // Define the base table.\n  joining('people')\n    // Define a join between \"people\" and the \"books_people\" many-to-many join table.\n    .join('books_people', {\n      column: 'people.id',\n      joinColumn: 'books_people.person_id'\n    })\n    // Define a join between \"books_people\" and \"books\".\n    .join('books', {\n      column: 'books_people.book_id',\n      joinColumn: 'books.id',\n      requiredJoin: 'books_people' // It requires the previous join.\n    })\n);\n\n// Specify which joins you require in query middleware (in this example, for a filter).\nbuilder.before('end', context =\u003e {\n  const currentQuery = context.get('query');\n  context.requireJoin('books'); // Require a join (and dependent joins).\n  context.set('query', currentQuery.where('books.title', 'A Tale of Two Cities'));\n});\n\nbuilder.before('end', context =\u003e {\n  console.log(context.get('query').query().toString());\n});\n\nbuilder.execute().then(() =\u003e console.log('done'));\n\n// OUTPUT:\n// select * from people\n//   inner join books_people on people.id = books_people.person_id\n//   inner join books on books_people.book_id = books.id\n//   where books.title = 'A Tale of Two Cities'\n// done\n```\n\nIf your ORM allows you to easily define basic relations (i.e. many to one, one to many, many to\nmany), and the adapter supports `getJoinDefinitions` and `applyJoinDefinition`, you may not have to\ndefine the joins manually:\n\n```js\nconst { joining, OrmQueryBuilder } = require('orm-query-builder');\n\n// A Bookshelf domain model with the same many-to-many relationship.\nconst Book = bookshelf.model('Book', {});\nconst Person = bookshelf.model('Person', {\n  books: function() {\n    return this.belongsToMany('Book');\n  }\n});\n\nconst builder = new OrmQueryBuilder();\nbuilder.use(\n  // Have the adapter deduce the available joins from the relation.\n  joining(Person).relation('books')\n);\n\n// The behavior would be the same as the previous example.\n```\n\nThe joining plugin allows you to define what table joins are available for your query and the\ndependency between those joins.\n\nIt also adds a `requireJoin(name)` method to the execution context, which allows you to individually\nrequire a specific join for a filter or sorting criteria. The plugin will automatically apply all\nthe required joins when needed, and avoid applying any of them twice.\n\n#### Methods\n\n* **`join(name, options)`** - Defines an available join. The join options are:\n  * `joinType` - The type of join (`innerJoin`, `leftOuterJoin`, `rightOuterJoin`, `fullOuterJoin`,\n    `crossJoin`), defaults to `innerJoin`.\n  * `joinTable` - The join table (if different than the join name).\n  * `column` - The join column in the source table.\n  * `joinColumn` - The join column in the target table.\n  * `requiredJoin` - Another join that must be applied for this join to be valid.\n  * `requiredJoins` - Multiple other joins that must be applied in order for this join to be valid.\n* **`relation(name, options)`** - Adds join definitions based on the model's relation.\n* **`relations(...names)`** - Adds join definitions based on multiple model relations.\n\n\n\n[bookshelf]: http://bookshelfjs.org\n[lodash-get]: https://lodash.com/docs/#get\n[node]: https://nodejs.org/en/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Form-query-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmediacomem%2Form-query-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmediacomem%2Form-query-builder/lists"}