{"id":16254300,"url":"https://github.com/celer/okapi","last_synced_at":"2025-09-06T04:34:59.998Z","repository":{"id":8315320,"uuid":"9863395","full_name":"celer/okapi","owner":"celer","description":"Okapi is an not ORM! It fills the void between an ORM and a SQL toolkit","archived":false,"fork":false,"pushed_at":"2014-10-08T05:23:04.000Z","size":398,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-22T21:24:22.877Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/celer.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2013-05-05T03:02:01.000Z","updated_at":"2015-04-09T13:06:02.000Z","dependencies_parsed_at":"2022-07-19T18:05:04.439Z","dependency_job_id":null,"html_url":"https://github.com/celer/okapi","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/celer/okapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fokapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fokapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fokapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fokapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/celer","download_url":"https://codeload.github.com/celer/okapi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fokapi/sbom","scorecard":{"id":270482,"data":{"date":"2025-08-11","repo":{"name":"github.com/celer/okapi","commit":"f080ffebd70be410345f03a5291c3b7dfcffb963"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-17T13:12:28.877Z","repository_id":8315320,"created_at":"2025-08-17T13:12:28.878Z","updated_at":"2025-08-17T13:12:28.878Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273858753,"owners_count":25180766,"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-09-06T02:00:13.247Z","response_time":2576,"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-10-10T15:21:02.000Z","updated_at":"2025-09-06T04:34:59.964Z","avatar_url":"https://github.com/celer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n![Okapi Logo](https://raw.github.com/celer/okapi/master/media/okapi.png)\n\n[![Build Status](https://travis-ci.org/celer/okapi.png)](https://travis-ci.org/celer/okapi)\n[![Depdendency Status](https://david-dm.org/celer/okapi.png)](https://david-dm.org/celer/okapi);\n\nOkapi is not an ORM, it is better!\n==================================\n\nOkapi aims at making SQL easier to use within Node.JS but isn't an ORM. \n\nOkapi: \n  \n * Doesn't do anything magical, it simply makes it easier to write SQL statements\n * Provides a single interface for Postgres, MySQL and SQLite\n  * It is super easy to support other databases with Okapi, look at lib/pg.js, lib/mysql.js, lib/sqlite.js to see how to implement a dialect\n * Works with regular JavaScript Objects - it purposefully doesn't provide any magical relationship mapping\n * Attempts to make it easy to write SQL when needed\n * Designed to work with async\n * Has an integrated assertion framework\n * Is well unit tested (click on the build status above for details)\n\n## Getting Started\n\nTo get started with Okapi simply:\n\n### 1 Install the npm module for the database of your choosing\n\nMySQL\n```shell\n  npm install mysql\n```\n\nPostgres\n```shell\n  npm install pg\n```\n\nSqlite\n```shell\n  npm install sqlite3\n```\n  \n### 2 Install okapi\n\n```shell\n  npm install okapi\n```\n\n### 3 Create a dialect for the DB of your choosing\n\n```javascript\nvar Okapi = require('okapi');\n\n//For MySQL\nvar dialect = new Okapi.MySQLDialect({  host:\"localhost\", user:\"root\", database:\"dao\" });\n\n//For Postgres\nvar dialect = new Okapi.PGSQLDialect({  host:\"localhost\", user:\"postgres\", database:\"okapi\", password:\"\" });\n\n//For SQLite\nvar dialect = new Okapi.SQLiteDialect(new sqlite.Database(\":memory:\"));\n\n```\n\n## Using Okapi\n\n### Defining Objects\n\nTo use Okapi first we need to define the schema for the object we want to use:\n\n```javascript\n\n  //Let's create a new person table, called 'person'\n  Person = new Okapi.Object(dialect,\"person\");\n  \n  //Now let's add some columns\n  Person.column(\"id\",{type: Okapi.ID });\n  Person.column(\"name\",{type: Okapi.String, unique: true});\n  Person.column(\"email\",{type: Okapi.String });\n\n  Profile = new Okapi.Object(dialect,\"profile\");\n\n  //This column refers to the id column defined by Person above\n  Profile.column(\"userId\",{type:Okapi.IDRef, ref: { dao: Person, column: \"id\" }});\n  Profile.column(\"gender\",{type:Okapi.String});\n\n  Person.insert({ name:\"Bob\", email:\"bob@bob.com\"}).done(function(err,insertedPerson){\n  \n    console.log(\"I inserted this person\",insertedPerson);\n  \n    Profile.insert({ userId: insertedPerson.id, gender:\"male\"}).done(function(err,profile){\n  \n      Person.find().join(Profile).done(function(err,results){\n        console.log(\"I found these people\",results);\n      });\n\n    });\n\n  });\n\n\n\n```\n\n#### Supported Types\n\n - Okapi.ID - denotes that the column is the primary id for the table\n - Okapu.IDRef - denotes a column that references another id in another table (may be null)\n - Okapi.String - a string column (short in length)\n - Okapi.Text - a text column (long in length)\n - Okapi.Number - a number column\n - Okapi.Float - a floating point column\n - Okapi.Boolean - a boolean column (1 or 0)\n - Okapi.Date - a date column\n  \n#### Column Modifiers \n\n - type - one of the Okapi types defined above\n - pk (boolean) - this column is a primary key\n - unique (boolean) - this column is unique\n - notNull (boolean) - this column may not be null\n - default - the default value for this column\n - ref     - the information associated with this reference\n  - ref.dao - the day this reference referes to \n  - ref.column - the column in the refered dao that joins to this one\n\n\n#### Indexes\n\nOkapi supports single and multi key indexes:\n\n```javascript\n  \n  //And create an index on name\n  Person.index(\"name\",[\"name\"]);\n\n  //Create a unique index on name and email\n  Person.index(\"name_email\",[\"name\",\"email\"], { unique: true });\n\n\n```\n\n#### Calling methodology\n\nEach statement within Okapi is chainable, and is designed to be utilized\nwith the standard callback method for node or with the popular async npm module. \n\nHere is now it would look if your not using async and works the same way\nall the other callbacks in NodeJS work.\n\n```javascript\n\n  //This will return all people in the database\n  Person.find().done(function(err,result){\n    //Returns an array of people\n  });\n\n  // .last() and .each() are also available\n  Person.find().first(function(err,firstPerson){\n    //Returns the first result\n  });\n  \n  //This will return the first 5 people\n  Person.find().limit(5).offset(0).done(function(err,result){\n\n  });\n  \n  Person.insert({ name: \"Bob\"}).done(function(err,result){\n    //Returns the person object with the id of it in the object\n  });\n  \n  Person.update({ id: 1, name: \"Bob\"}).done(function(err,result){\n    //Returns the number of changed rows\n  });\n\n  Person.update({ email:\"email@email.com\"}).where({name:\"Bob\"}).done(function(err,result){\n    //Returns the number of rows\n  });\n\n  Person.delete({ id:1 }).done(function(err,result){\n    \n  });\n\n  \n```\n\nTo make it easier to use with the async npm module you can alternatively return\na call back so that statements can be executed easily in series by using the .async()\nmethod. This method will return a callback that works well with async.\n\n```javascript\n\n  async.series([\n  \n    //Each one of these statements will be called in sequence\n    Person.insert({ name: \"Bob\"}).async(),\n    Person.find().async(),\n    Person.update({ id: 1, name: \"Sally\"}).async(),\n    Person.delete({ id: 1 }).async()\n\n  ],function(err,result){\n    //This will be called when were done!\n  });\n\n\n```\n\nLastly you can also use Okapis native assertion framework, which \nwas designed to work well with async. Too see how to use the\nassertion framework in detail checkout the /test directory.\n\n```javascript\n\n  async.series([\n  \n    //Each one of these statements will be called in sequence and tested\n    // against the assertions\n    Person.insert({ name: \"Bob\"}).assert(\"A user was created\",function(a){\n      a.contains({ name: \"Bob\", id: 1 });\n    }),\n    \n    Person.find().assert(\"The person we just created is in the database\",function(a){\n      a.containsRow({ name: \"Bob\", id: 1 });\n      a.rowsReturned(1);\n    }),\n\n  ],function(err,result){\n    //This will be called when were done!\n  });\n\n\n```\n\n#### Getting to SQL!\n\nFirst you can simply use SQL via your dialect, simply put '?' around the variables you want to \nsubstitute in the SQL statement, and then include the data in the second argument. \n\n\nThe third argument tells Okapi to to prepare the data, simply specify 'select','update','insert' here. \n\n```javascript\n  \n  dialect.sqlQuery(\"select * from person where name=?name?\",{name:\"bob\"},\"select\",function(err,results){\n\n  });\n\n  person.sqlQuery(\"select average(height) from person\",{},function(err,result){\n\n  });\n\n  //You can also specify different SQL to use for different databases:\n  dialect.sqlQuery({ sqlite: \"select date('now')\", mysql: \"select now()\", pg: \"select now\" },...);\n\n```\n\nOkapi crafts SQL statements by using a small template language, if you check out lib/dialect.js you'll see this list of templates. \n\nSo for example the update statement looks like so:\n```jsp\n    update \u003c%tableName()%\u003e set \u003c%sets()%\u003e \u003c%setExp()%\u003e \u003c? where \u003c%where()%\u003e \u003c%whereExp()%\u003e ?\u003e\n```\n\nOkapi uses this statement to generate the update call and it can be overriden by the various database dialects (see lib/sqlite.js for an example), but it\nalso makes it easy to customize a statement in a way that is exteremly flexible. Anywhere within the SQL templates where something ends in 'Exp' such as \nwhereExp() raw SQL can be inserted here. \n\nSo for example, would insert the SQL statement below in the update. \n```javascript\n  // update person set namesdx=soundex(name)\n  Person.update().setExp(\"namesdx=soundex(name)\").done(...);\n```\n\nWhile this is nice you can also use it to support multiple different databases at once, in the example\nbelow we've inserted custom SQL for each one of the different databases we want to support.\n\n```javascript\n  Person.find().columnExp({ \n                            mysql:\",TIMESTAMPDIFF(YEAR,birthdate,now()) as age\",\n                            pg:\",date_part('year',age(now(),birthdate)) as age\",\n                            sqlite:\",(?now?-birthdate)/(1000*3600*24*365) as age\",\n                          },{ now: Date.now() }\n                          ).join(Profile).done(...);\n```\n\n\n\n#### Where Queries\n\nAnywhere a where block is specified a query can be specified as:\n\n * An object which implys that each column must match the suplied value\n * A lambda which can be used to construct a complex query\n * A string which will be matched against the primary key column\n\n#### An object based query\n\n\n```javascript\n  // name=\"bob\" and email=\"bob@bob.com\"\n  Person.find().where({ name:\"bob\", email:\"bob@bob.com\"})...\n\n\n```\n\n#### A Lambda based query\n\n```javascript\n  // name=\"bob\" or email=\"bob@bob.com\"\n  Person.find().where(function(q){\n    q.and(function(q){\n      q.eq(\"name\",\"bob\");\n      q.eq(\"email\",\"bob@bob.com\");\n    });\n  }).async(),\n\n  //Or\n  Person.update({ name: \"Elvis\"}).where(function(q){\n    q.not(function(q){\n      q.or(function(q){\n        q.like(\"name\",\"The King\");\n        q.startsWith(\"name\",\"The\");\n        q.endsWith(\"name\",\"King\");\n      });\n    });\n  }).async(),\n\n```\n\n#### A Primary Key based query\n\n```javascript\n  // id = 1\n  Person.find().where(1).async();\n```\n\n## Statements\n\n\n### Creating and deleting tables\n\nOkapi provides two functions for creating and deleting tables:\n\n```javascript\n\n  //We can drop and create a table like so:\n  Person.dropTable().done(function(err,result){\n    Person.createTable().done(function(err,result){\n\n    });\n  });\n\n  //Or like so:\n  Okapi.dropTables(Profile,Person,function(err,result){\n  \n    //Or like so:\n    Okapi.createTables(Person,Profile,function(err,result){\n\n    });\n\n  });\n\n```\n\nCreate Table template:\n```jsp\n    create table if not exists \u003c%tableName()%\u003e (\n      \u003c% eachColumn(function(columnName,column){ return '\\\\t'+columnName+' '+columnType(columnName)+' '+columnModifiers(columnName); },',\\\\n')%\u003e\n      \u003c?,\\n\u003c%eachColumn(function(name,column){ var c = columnUniqueConstraint(name); if(c) return '\\\\t'+c; },',\\\\n')%\u003e?\u003e\n      \u003c?,\\n\u003c%eachColumn(function(name,column){ var c = columnFKConstraint(name); if(c) return '\\\\t'+c; },',\\\\n')%\u003e?\u003e\n    \u003c%createExp()%\u003e) \n    \u003c%postCreateExp()%\u003e\n```\n\nThis makes it easy to deal with MySQL specific table types!\n\n```javascript\n  Person.createTable().postCreateExp({mysql:\"ENGINE=InnoDB\"}).done(...);\n```\n\n### Inserting data\n\nTo insert data simply provide a javascript object with variables matching the names \nof the columns specified in the table definition. A new object with the id associated\nwith the object will be returned\n\n\n```javascript\n\n  Person.insert({name: \"bob\", email:\"bob@bob.com\").done(function(err,res){\n    console.log(\"The person was inserted!\",res);\n  });\n\n\n```\n\nInsert template:\n```jsp  \n  insert into \u003c%tableName()%\u003e (\u003c%columns()%\u003e \u003c%columnExp()%\u003e) values(\u003c%values()%\u003e \u003c%valueExp()%\u003e)\n```\n\n### Upating data\n\nThe update function will use the provided data to update the rows\nselected via the where query\n\n\n```javascript\n  //This will update the suplied data by using the primary key for the object, in this case it will use 'id'\n  // update person set name='bob' where id=3\n  Person.update({ name:\"Bob\", id:3 }).done(...)\n      \n  //You can also use a 'where' query to specify what to update\n  // update person set name='bob' where email='bob@bob.com' \n  Person.update({name:\"bob\"}).where({email: 'bob@bob.com'}).done(err,res){\n    console.log(err,res);\n  });  \n```\n\nUpdate template:\n```jsp  \n    update \u003c%tableName()%\u003e set \u003c%sets()%\u003e \u003c%setExp()%\u003e \u003c? where \u003c%where()%\u003e \u003c%whereExp()%\u003e ?\u003e\n```\n\n### Upserts\n\nUpserts are essentially statements that insert or update the row depending upon a primary key conflict. Essentially create or update a row, which is more efficient then\ndoing and individual insert and update. \n\n```javascript\n  Person.upsert({ id: 1, name:\"Bob\"}).done(....);\n```\n\nUpsert templates:\n  \n```jsp\n  //mysql\n  insert into \u003c%tableName()%\u003e (\u003c%columns()%\u003e \u003c%columnExp()%\u003e) values(\u003c%values()%\u003e \u003c%valueExp()%\u003e) on duplicate key update \u003c%sets({ noPK: true })%\u003e \u003c%setExp()%\u003e\n  //sqlite \n  insert or replace into \u003c%tableName()%\u003e (\u003c%columns()%\u003e \u003c%columnExp()%\u003e) values(\u003c%values()%\u003e \u003c%valueExp()%\u003e)\n  //pg - update followed by insert\n```\n\n### Delete data\n\n```javascript\n  //Delete everything!\n  // delete from person\n  Person.delete().done(...);\n\n  //Delete only some people\n  // delete from person where name='bob'\n  Person.delete().where({name:\"bob\").async();\n```\n\nDelete template:\n```jsp\n    delete from \u003c%tableName()%\u003e \u003c? where (\u003c%where()%\u003e\u003c%whereExp()%\u003e) ?\u003e\n```\n\n\n### Finding Data\n\n```javascript\n  //Return everything that has a name of celer\n  Person.find({name:\"celer\"}).async(),\n  \n  //Return the item with an primary key of 1 (id=1)\n  Person.find(1).async();\n\n  //Find an individual using a more advanced query\n  Person.find(function(q){\n    q.or(function(q){\n      q.eq(\"name\",\"celer\");\n      q.eq(\"name\",\"bob\");\n    });\n  }).async(),\n\n  //This will perform a find and only return the specified columns\n  Profile.find().columns(\"age\",\"gender\").async(),\n\n  \n  //This will join across user and profile\n  Profile.find({gender:\"male\"}).join(user,\"userId\").async(),\n\n  //Or we could join from user:\n  Person.find().join(Profile,\"id\",{gender:\"male\"}).async(),\n\n  //Get the first page of data\n  Person.find().page(1).done(...)\n\n  //Order the results  \n  Person.find().orderBy(\"name\",\"asc\").done(...)\n\n```\n\nSelect template:\n```jsp\n    select \u003c%columns()%\u003e\u003c%columnExp()%\u003e from \u003c%tableName()%\u003e \u003c%joins()%\u003e \u003c? where \u003c%where()%\u003e\u003c%whereExp()%\u003e?\u003e\u003c?order by \u003c%orderBy()%\u003e \u003c%orderByExp()%\u003e?\u003e\u003c?limit \u003c%limit()%\u003e ?\u003e \u003c? offset \u003c%offset()%\u003e ?\u003e\n```\n\n#### Joins\n\nOkapi doesn't have an explicit understanding of relationships, hence why it is not an ORM! Instead it lets you join things:\n\n```javascript\n  //If it can figure out how to join things it will just do it (as an inner join)\n  Person.find().join(Profile).done(...).done(...)\n\n  //You can give it more details if it gets confused, the second paramter after the DAO being the column to use. \n  Person.find().join(Profile,\"userId\").done(...);\n\n  //You can tell also give it a query to use for your join:\n  Person.find().join(Profile,\"userId\",function(q){\n    q.eq(\"gender\",\"male\");\n  });\n\n  //You can even tell it what type of join to use:\n  Person.find().join(Profile,\"userId\",null,{ type:\"left\"}).done(...)\n  \n  //Or you can even tell it what columns to return:\n  Person.find().join(Profile,\"userId\",null,{ columns:[\"age\",\"gender\"]}).done(...)\n\n  //And suppose you have multiple joins on the same table:\n  Vehicle.find().join(Person,\"ownerId\", null,{ type: \"left, as:\"owner\"}).join(Person,\"driverId\",null,{type:\"left\",as:\"driver\"}).done(...)\n\n\t//Or you want to do a join across linked tables\n  Vehicle.find().join(\"ownerID\",person,{name:\"b\"},{ as:\"owner\"},profile,{as:\"ownerProfile\"}).join(\"driverID\",person,{as:\"driver\"},profile, { as: \"driverProfile\"}).done(...)\n```\n\nThe join function expects a sequence like so:\n\n```javascript\n\t(dao,column?,query?,options?)*\n```\n\nWhere the only required parameter is the DAO, and everything else is optional - Okapi will figure out the rest if it can. With the first DAO being \nimplied by what it was chained upon, so for example:\n\n```javascript\n\tVehicle.find().join(\"ownerID\",person,{name:\"b\"},{ as:\"owner\"},profile,{as:\"ownerProfile\"})\n```\n\nis interpreted as:\n\n```javascript\n\t//inner join Vehicle.ownerId to\n\tjoin(Vehicle,\"ownerID\",null,null) \n\t//inner join person (Okapi will resolve the correct id column) where name==\"B\" as \"owner\" to \n\tjoin(person,null,{name:\"b\"},{as:\"owner\"})\n\t//inner join profile (Okapi will resolve the correct id column) as \"ownerProfile\"\n\tjoin(profile,null,null,{as:\"ownerProfile\"})\n```\n\nThe options have two possible values:\n\t\n * options\n  * as - what to name the resulting object\n  * type - the type of join to do (left, inner, etc)\n\nJoins may also use filtered DAOs as described below\n\n### Transactions\n\n```javascript\n    Okapi.tx(dialect,function(err,tx){\n      var v = tx.use(vehicle);  \n\n      async.series([\n        \n        v.insert({ make:\"make\", model:\"model\"}).async(),\n\n        v.update({ make:\"make2\", id: 0}).async(),\n\n      ],function(err,res){\n        if(err){\n          tx.rollback(done);\n        } else {\n          tx.commit(done);\n        }\n      });\n  });\n\n```\n### Filtered DAOs\n\nSo to make it easy for you to filter a DAO, for example to restrict a view you can simply clone an existing DAO an apply a filter, and these filtered DAOs will even retain their filtering in joins!\n\n```javascript\n  var vehicle = new Okapi.Object(dialect,\"vehicle\");\n  vehicle.column(\"id\",{type: Okapi.ID });\n  vehicle.column(\"make\",{ type:Okapi.String });\n  vehicle.column(\"model\",{ type:Okapi.String });\n\n  /* \n    We will clone the object and make a new one\n    that is limited to dealing only with mazda\n  */\n  var mazdaOnly = vehicle.clone();\n  mazdaOnly.filter(function(q){\n    q.eq(\"make\",\"Mazda\");\n  });\n    \n\tmazdaOnly.find().assert(\"Only contains mazda\",function(q){\n\t\tq.containsRow({ make:\"Mazda\", model:\"Miata\"});\n\t\tq.rowsReturned(1);\n\t}),\n```\n### Prepared statements\n\nFor the most part Okapi constructs the statement or query at the time it is executed, but is possible to ask it to pre-construct a query that you want to run a bunch, allowing you\nto overwrite variables as needed. \n\n```javascript\n\t//This will create a prepared insert statement with a variable 'year' that can be overridden as needed:\n\tvar insert = vehicle.insert({ make:\"mazda\", model:\"Miata\",year:Okapi.$(\"year\") }).prepare();\n\n\tinsert.exec({year:1997},function(err,result){  ... }), \n\n\tvar find = vehicle.find(function(q){\n\t\tq.eqVar(\"make\",\"make\");\n\t}).prepare();\n\n\tfind.exec({ make:\"mazda\"},function(err,result){ ... }),\n```\n\nThe exec statements will return an async function if no call back is provided for use with the async module.\n\n### Notes\n\nWe don't expect Okapi to solve every SQL problem, hence why we endorse dropping to SQL when you need it.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceler%2Fokapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceler%2Fokapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceler%2Fokapi/lists"}