{"id":19256533,"url":"https://github.com/instant-dev/orm","last_synced_at":"2025-07-12T11:03:15.213Z","repository":{"id":194890657,"uuid":"691805713","full_name":"instant-dev/orm","owner":"instant-dev","description":"Instant ORM: JavaScript ORM for Postgres","archived":false,"fork":false,"pushed_at":"2025-04-11T05:48:23.000Z","size":514,"stargazers_count":64,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-07T13:33:20.612Z","etag":null,"topics":["bun","database","deno","javascript","migrations","nodejs","orm","postgres","postgresql"],"latest_commit_sha":null,"homepage":"https://instant.dev","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/instant-dev.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-09-14T23:49:26.000Z","updated_at":"2025-04-11T05:48:27.000Z","dependencies_parsed_at":"2023-09-23T23:04:31.562Z","dependency_job_id":"63780f1a-b737-4208-b60b-c7fb3c39c5b0","html_url":"https://github.com/instant-dev/orm","commit_stats":{"total_commits":261,"total_committers":1,"mean_commits":261.0,"dds":0.0,"last_synced_commit":"53577fa16b5db4d3f94f736f0e00ccbe56011f44"},"previous_names":["instant-dev/orm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/instant-dev/orm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/instant-dev","download_url":"https://codeload.github.com/instant-dev/orm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instant-dev%2Form/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264979765,"owners_count":23692492,"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":["bun","database","deno","javascript","migrations","nodejs","orm","postgres","postgresql"],"created_at":"2024-11-09T19:06:05.188Z","updated_at":"2025-07-12T11:03:15.189Z","avatar_url":"https://github.com/instant-dev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Instant ORM\n![npm version](https://img.shields.io/npm/v/@instant.dev/orm?label=) ![Build Status](https://app.travis-ci.com/instant-dev/orm.svg?branch=main)\n\n## JavaScript ORM for Postgres with built-in Vector Support\n\nThis is the core ORM package for [**`instant.dev`**](https://github.com/instant-dev/instant).\nIt is recommended that you use it with the `instant` command line utility\navailable at [instant-dev/instant](https://github.com/instant-dev/instant) for\neasy migration management, **however, it can be used as a standalone ORM**. By\ndefault, upon connecting to a database, the Instant ORM will introspect your\nDatabase schema and determine appropriate models and relationships.\n\n## Table of Contents\n\n1. [Getting Started](#getting-started)\n1. [Connecting to a Database](#connecting-to-a-database)\n   1. [Connecting to another Database](#connecting-to-another-database)\n   1. [Querying your databases directly](#querying-your-databases-directly)\n   1. [Disconnecting](#disconnecting)\n1. [Loading a Schema](#loading-a-schema)\n1. [Loading custom Model logic](#loading-custom-model-logic)\n1. [Using Models](#using-models)\n   1. [CRUD operations](#crud-operations)\n      1. [Create](#create)\n      1. [Read](#read)\n      1. [Update](#update)\n         1. [Incrementing values and custom SQL](#incrementing-values-and-custom-sql)\n      1. [Destroy](#destroy)\n   1. [Vector fields](#vector-fields)\n      1. [Setting a vector engine](#setting-a-vector-engine)\n      1. [Setting a vector engine globally](#setting-a-vector-engine-globally)\n      1. [Using vector fields](#using-vector-fields)\n   1. [Query composition](#query-composition)\n      1. [Composer instance methods](#composer-instance-methods)\n         1. [Composer#safeWhere](#composersafewhere)\n         1. [Composer#safeJoin](#composersafejoin)\n         1. [Composer#where](#composerwhere)\n            1. [Custom SQL](#custom-sql)\n         1. [Composer#join](#composerjoin)\n            1. [One-to-many](#one-to-many)\n            1. [One-to-one](#one-to-one)\n            1. [Naming conventions](#naming-conventions)\n         1. [Composer#orderBy](#composerorderby)\n         1. [Composer#limit](#composerlimit)\n         1. [Composer#groupBy](#composergroupby)\n         1. [Composer#aggregate](#composeraggregate)\n         1. [Composer#search](#composersearch)\n         1. [Composer#similarity](#composersimilarity)\n   1. [Transactions](#transactions)\n   1. [Input validation](#input-validation)\n   1. [Relationship verification](#relationship-verification)\n   1. [Calculated and hidden fields](#calculated-and-hidden-fields)\n   1. [Lifecycle callbacks](#lifecycle-callbacks)\n1. [Using Migrations, Seeding and Code Generation](#using-migrations-seeding-and-code-generation)\n1. [Acknowledgements](#acknowledgements)\n\n## Getting Started\n\nInstalling the Instant ORM:\n\n```shell\nnpm i @instant.dev/orm@latest --save\n```\n\nInitializing (CommonJS):\n\n```javascript\nconst InstantORM = require('@instant.dev/orm');\nconst Instant = new InstantORM();\n```\n\nInitializing (ESM):\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\nconst Instant = new InstantORM();\n```\n\n## Connecting to a Database\n\nBy default, the Instant ORM will attempt to load database credentials from\n`_instant/db.json[process.env.NODE_ENV][\"main\"]`:\n\n```javascript\nawait Instant.connect(); // connects based on _instant/db.json\n```\n\nHowever, you can also provide custom credentials to any database you'd like\nby passing in a `cfg` configuration object with the credentials in the following\nformat:\n\n```javascript\nconst cfg = {\n  host: 'my.postgres.host',\n  port: 5432,\n  user: 'postgres',\n  password: '',\n  database: 'postgres',\n  ssl: false, // optional: acceptable values are [true, false, \"unauthorized\"]\n  in_vpc: false, // optional: if false, will use provided SSH tunnel when deployed\n  tunnel: { // optional: use this if we need to SSH tunnel into database\n    host: 'my.ssh.host.com',\n    port: 22,\n    user: 'ec2-user',\n    private_key: 'path/to/private_key.pem'\n  }\n};\nawait Instant.connect(cfg); // now connected to custom Database\n```\n\nYou can also opt to provide a `connectionString` instead:\n\n```javascript\nconst cfg = {\n  connectionString: 'postgres://postgres:mypass@my.postgres.host:5432/postgres?sslmode=true',\n  in_vpc: false, // optional: if false, will use provided SSH tunnel when deployed\n  tunnel: { // optional: use this if we need to SSH tunnel into database\n    host: 'my.ssh.host.com',\n    port: 22,\n    user: 'ec2-user',\n    private_key: 'path/to/private_key.pem'\n  }\n};\nawait Instant.connect(cfg); // now connected to custom Database\n```\n\n### Connecting to another database\n\nBy default, the `Instant.connect()` method will assign your initial database\nconnection the alias `\"main\"`. You can access your Database object directly\nvia:\n\n```javascript\nconst db = Instant.database();\nconst mainDb = Instant.database('main');\nconsole.log(db === mainDb); // true, \"main\" is an alias for your main db\n```\n\nTo connect to another database, simply use:\n\n```javascript\n// connect\nInstant.addDatabase(name, cfg);\n// read\nconst otherDb = Instant.database(name);\n```\n\n### Querying your databases directly\n\nQuerying your database directly is easy. To run a standalone query;\n\n```javascript\nconst db = Instant.database();\nconst result = await db.query(`SELECT * FROM my_table WHERE x = $1`, [27]);\n```\n\nTo execute a batched transaction from prepared statements and queries;\n\n```javascript\nconst db = Instant.database();\n// Pass in an array of statements\nconst result = await db.transact([\n  `SELECT * FROM my_table`,\n  `INSERT INTO my_table(field) VALUES((1))`,\n  // Parameterized statements can be passed in as well\n  [`INSERT INTO my_other_table(other_field) VALUES(($1))`, [2]]\n]);\n```\n\nAnd to create a transaction that you want to work with in real-time, potentially\nquerying third party services before deciding whether or not to commit the query:\n\n```javascript\nconst db = Instant.database();\nconst txn = db.createTransaction();\n\nlet result = await txn.query(`SELECT * FROM my_table WHERE x = $1`, [27]);\nlet result2 = await txn.query(`INSERT INTO my_table(field) VALUES(($1))`, [5]);\nlet manyQueries = await txn.transact([\n  `SELECT * FROM my_table`,\n  `INSERT INTO my_table(field) VALUES((1))`,\n]);\n// to commit\nawait txn.commit();\n// to rollback\nawait txn.rollback();\n```\n\n### Disconnecting\n\nTo disconnect from a specific database:\n\n```javascript\nInstant.closeDatabase(name);\n```\n\nAnd to disconnect from all open databases and reset your connection:\n\n```javascript\nawait Instant.disconnect();\n```\n\n## Loading a Schema\n\nWhen you connect to a database, Instant ORM will attempt to determine the\nschema of your database in a few ways.\n\n- First, it will check to see if `_instant/cache/schema.json` exists\n  - If it does, it will load the schema from this file\n- Next, it will check to see if an `_instant_migrations` table exists in your\n  database\n  - This table holds all migrations applied to the database and is generated by\n    the [instant.dev](https://github.com/instant-dev/instant) CLI automatically\n  - If it does exist and has entries, it will load the schema from the latest\n    migration\n- Finally, it will introspect your database structure\n  - All tables, columns, sequences and constraints will be inspected\n  - Foreign keys and uniqueness will be used to determine one-to-one and\n    one-to-many relationships\n\nAdditionally, you can also pass a custom `schema` object to the\n`Instant.connect(cfg)` method as a second argument, but this is\n**not recommended**. It is usually reserved for testing purposes.\n\n## Loading custom Model logic\n\nBy default, the Instant ORM will load models from the `_instant/models`\ndirectory.\n**You do not need a model file for every, or even any, table in your database**.\nThese are only meant to extend models in the case you want to add\n[Lifecycle callbacks](#lifecycle-callbacks), validations, verifications,\ncalculated fields or hide data. Each file should look something like this;\n\nFile: `_instant/models/sample_model.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass SampleModel extends Model {\n\n  static tableName = 'sample_models';\n\n  async beforeSave (txn) {}\n  async afterSave (txn) {}\n  async beforeDestroy (txn) {}\n  async afterDestroy (txn) {}\n\n}\n\nSampleModel.calculates(/* ... */);\nSampleModel.validates(/* ... */);\nSampleModel.verifies(/* ... */);\nSampleModel.hides(/* ... */);\n\nexport default SampleModel;\n```\n\nThe Instant ORM will automatically associate each file with the appropriate\ntable in your database schema, provided `SampleModel.tableName` matches a table\non your Database. You can access your Models using;\n\n```javascript\n// Note that \"SampleModels\", \"samplemodel\", \"sample_models\" etc.\n// will all work as well as long as there's no ambiguity\nInstant.Model('SampleModel');\n```\n\n## Using Models\n\nModels are accessible via the `Instant.Model(modelName)` method. This method\nwill automatically look up the most likely model based on the matching `table`\nin your database schema.\n\n```javascript\nconst User = Instant.Model('User');\n```\n\nThis method would also accept the strings `Users`, `user`, `users`. If your\ntable has pluralization and underscores we recommend using the singular version,\nbut you can access using the table name as well. For example, the table name\n`object_children` could be accessed via:\n\n```javascript\nconst ObjectChild = Instant.Model('ObjectChild'); // recommended\n```\n\nHowever, the following would also work:\n\n```javascript\nInstant.Model('ObjectChildren');\nInstant.Model('object_child');\nInstant.Model('object_children');\n```\n\nIn the case of ambiguity - multiple tables potentially matching the object name -\n`Instant.Model()` will throw an error and ask you to use the specific table.\n\n### CRUD Operations\n\n#### Create\n\nYou can create new model instances and save them to the database with\n`Model.create(data)` or `new Model(data)` and then a subsequent `model.save()`:\n\n```javascript\nconst User = Instant.Model('User');\n\n// Model.create() method creates a user:\nlet user1 = await User.create({email: 'keith@instant.dev', username: 'keith'});\nconsole.log(user1.inStorage()); // true\n\n// Can also use new Model() and then save it\nlet user2 = new User({email: 'scott@instant.dev'});\nuser2.set('username', 'scott'); // can set values independently\nconsole.log(user2.inStorage()); // false\nawait user2.save();\nconsole.log(user2.inStorage()); // true\n```\n\n#### Read\n\nReading model data can be done in a few ways: `Model.find()`, `Model.findBy()`\nor via [Query composition](#query-composition) using the `query.select()`\nmethod.\n\n```javascript\nlet user1 = await User.find(1); // uses id\nlet user2 = await User.findBy('email', 'keith@instant.dev');\nlet user3 = await User.query()\n  .where({email: 'keith@instant.dev'})\n  .first(); // throws error if not found\nlet userList = await User.query()\n  .where({email: 'keith@instant.dev'})\n  .select(); // can return an empty list\nlet userCount = await User.query()\n  .where({email: 'keith@instant.dev'})\n  .count();\n```\n\n#### Update\n\nUpdating model data can be performed by (1) updating and saving individual\nmodels, (2) update and saving ModelArrays, (3) `Model.updateOrCreateBy()` or\n(4) [Query composition](#query-composition) using the `query.update()` method.\n\n**Note:** `query.update()` will bypass model lifecycle methods `beforeSave()`\nand `afterSave()` as well as all validations verifications. Read more in\n[Lifecycle callbacks](#lifecycle-callbacks).\n\n```javascript\nlet user = await user.findBy('username', 'keith');\nuser.set('username', 'keith_h');\nawait user.save();\n\n// Update by reading from data\nuser.read({username: 'keith_h2'});\nawait user.save();\n\n// Save many models at once using ModelArrays\n// Let's make all our moderators superusers\nlet users = await User.query()\n  .where({is_moderator: true})\n  .select();\nusers.setAll('is_superuser', true);\nawait users.saveAll();\n\n// Can also use `readAll`\nusers.readAll({free_credits: 100});\nawait users.saveAll();\n\n// Can update models directly with new data if there's a matching entry\nuser = await User.updateOrCreateBy(\n  'username',\n  {username: 'keith_h2', email: 'keith+new@instant.dev'}\n);\n\n// Bypass lifecycle callbacks, validations and verifications\n// Useful for updating many models at once and batch processing\nusers = await User.query()\n  .where({username: 'keith_h2'})\n  .update({username: 'keith'});\n```\n\n##### Incrementing values and custom SQL\n\nYou can run custom SQL when updating models using the `query.update()` method.\n**This will bypass [Lifecycle callbacks](#lifecycle-callbacks)**. However it is\nthe most efficient way to do things like incrementing values.\n\n```javascript\nconst user = User.findBy('email', 'keith@instant.dev');\nawait User.query()\n  .where({user_id: user.get('id')})\n  .update({post_count: (post_count) =\u003e `${post_count} + 1`});\n```\n\nIn this case, the `post_count` variable will hold the query column reference.\nYou can reference multiple fields by including more fields in the function\narguments:\n\n```javascript\nconst user = User.findBy('email', 'keith@instant.dev');\nawait User.query()\n  .where({user_id: user.get('id')})\n  .update({\n    post_count: (post_count) =\u003e `${post_count} + 1`,\n    karma: (karma, post_count) =\u003e `${karma} + LOG(${post_count})`\n  });\n```\n\nAny valid SQL expression can be returned by these methods.\n\n#### Destroy\n\nWe purposefully **do not** include a `delete` method in\n[Query composition](#query-composition). In most application contexts,\npermanently deleting records is bad practice from a security and monitoring\nperspective. We usually recommend `is_archived` or `is_deleted` flags.\nIn the case you really do need to delete records, there is a `Model.destroy(id)`\nmethod, a `model.destroy()` method and a `modelArray.destroyAll()` method.\nWe also provide `model.destroyCascade()` and `modelArray.destroyCascade()` for\na cascading delete if foreign key constraints prevent deleting a model directly.\n\n```javascript\nawait User.destroy(100); // goodbye User(id=100)!\n\nlet user = await User.findBy('email', 'nouser@instant.dev');\nawait user.destroy();\nlet user2 = await User.findBy('email', 'nouser2@instant.dev');\nawait user2.destroyCascade(); // destroy model + children (useful for foreign keys)\n\n/* ModelArray methods */\nlet bannedUsers = await User.query().where({is_banned: true}).select();\nawait bannedUsers.destroyAll();\n\nlet mutedUsers = await User.query().where({is_muted: true}).select();\nawait mutedUsers.destroyCascade();\n```\n\n### Vector fields\n\nInstant ORM comes with built-in support for [pgvector](https://github.com/pgvector/pgvector) and the\n`vector` field type. To install `pgvector` locally, follow the instructions in the GitHub repo above.\n\n**Note:** In order to use vector fields, `pgvector` will need to be enabled on every database\nyou're working with. To enable `pgvector`, assuming it is installed and you are using the\n[instant.dev CLI](https://github.com/instant-dev/instant), run:\n\n```shell\ninstant db:ext vector --enable # enable for local\ninstant db:ext vector --enable --env staging # enable for staging\ninstant db:ext vector --enable --env production # enable for production ... and so on\n```\n\nOr you can also simple `psql` into your database and run:\n\n```sql\nCREATE EXTENSION vector;\n```\n\nDatabase providers with built-in `pgvector` support include:\n\n- AWS RDS for PostgreSQL (15+) ([announcement](https://aws.amazon.com/about-aws/whats-new/2023/05/amazon-rds-postgresql-pgvector-ml-model-integration/))\n- [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres)\n- [Neon](https://neon.tech)\n- [Supabase](https://supabase.com)\n\n#### Setting a vector engine\n\nInstant ORM uses the [@instant.dev/vectors](https://github.com/instant-dev/vectors) package to\nmake creating vectors a breeze. It will automatically handle batching requests to OpenAI or\nany other third party vector service.\n\nTo set a vector engine, you can use `Instant.Vectors.setEngine()` like so:\n\n```javascript\n// values will automatically be batched appropriately\nInstant.Vectors.setEngine(async (values) =\u003e {\n  const embeddingResult = await openai.embeddings.create({\n    model: 'text-embedding-3-small',\n    input: values,\n  });\n  return embeddingResult.data.map(entry =\u003e entry.embedding);\n});\n```\n\n#### Setting a vector engine globally\n\n**Quickstart:** If you are using the [instant.dev CLI](https://github.com/instant-dev/instant),\nyou can simply run `instant kit vector`. It will set up a plugin automatically.\n\nTo automatically load a vector engine, we will need to add a **plugin**. These are executed\nas part of lifecycle events when using the Instant ORM. You'll need to create a file:\n\nFile `_instant/000_set_vector_engine.mjs`:\n\n```javascript\nimport OpenAI from 'openai';\nconst openai = new OpenAI({apiKey: process.env.OPENAI_API_KEY});\n\nexport const plugin = async (Instant) =\u003e {\n  Instant.Vectors.setEngine(async (values) =\u003e {\n    const embedding = await openai.embeddings.create({\n      model: 'text-embedding-3-small',\n      input: values\n    });\n    return embedding.data.map((entry, i) =\u003e entry.embedding);\n  });\n};\n```\n\nPlugins **must** export a `plugin` function. Plugins are executed in the\nalphabetized order they exist in the filesystem, with directories being loaded first.\n\n#### Using vector fields\n\nUsing vector fields is easy. The vector engine, specified above, will do all the heavy lifting\nof converting strings to vectors and `pgvector` will handle comparisons automatically.\n\nTo automatically populate vector fields when models are saved:\n\nFile: `_instant/models/blog_post.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass BlogPost extends InstantORM.Core.Model {\n\n  static tableName = 'blog_posts';\n\n}\n\n// Stores the `title` and `content` fields together as a vector\n// in the `content_embedding` vector field\nBlogPost.vectorizes(\n  'content_embedding',\n  (title, content) =\u003e `Title: ${title}\\n\\nBody: ${content}`\n);\n\nexport default BlogPost;\n```\n\nAnd then to query vector fields:\n\n```javascript\nconst blogPost = await BlogPost.create({title: `My first post`, content: `some content`});\nconst vector = blogPost.get('content_embedding'); // length 1,536 array\n\n// Find the top 10 blog posts matching \"blog posts about dogs\"\n// Automatically converts query to a vector\nlet searchBlogPosts = await BlogPost.query()\n  .search('content_embedding', 'blog posts about dogs')\n  .limit(10)\n  .select();\n```\n\nYou can read more on vector queries at\n[Composer#search](#composersearch), [Composer#similarity](#composersimilarity) and\n[Composer#classify](#composerclassify).\n\n### Query composition\n\nInstant ORM provides a query composer that enables you to construct complex\nSQL queries with multiple layers of nesting and joins easily. It is heavily\ninspired by the\n[Rails ActiveRecord ORM](https://guides.rubyonrails.org/active_record_querying.html#hash-conditions)\nand the\n[Django ORM](https://docs.djangoproject.com/en/4.2/topics/db/queries/#chaining-filters),\nwhere you can filter using objects and chain multiple queries and statements\ntogether. If you've worked with these ecosystems, querying with the Instant ORM\nwill come naturally to you. Otherwise, it's easy to pick up!\n\nHere's a basic example that;\n- Selects users with an id matching 7, 8, or 9\n- Orders them by their username\n- Retrieves a maximum of 2 results\n\n```javascript\nconst User = Instant.Model('User');\n\n// Basic querying\nlet users = await User.query()\n  .where({id__in: [7, 8, 9]})\n  .orderBy('username', 'ASC')\n  .limit(2)\n  .select();\n```\n\nA couple of things to note here\n- `User.query()` returns an immutable [Composer](core/lib/composer.js) instance\n- Each new chained command, like `.where()`, `.orderBy()` returns\n  a new, immutable [Composer](core/lib/composer.js) instance\n- Any of these instances can individually be queried\n- `.select()` is an async function that executes the actual SQL query\n- As such, **the query is not executed until `.select()` is called**\n\nWe could rewrite this like so:\n\n```javascript\nlet query = User.query();\nlet idQuery = query.where({id__in: [7, 8, 9]});\nlet orderQuery = idQuery.orderBy('username', 'ASC')\nlet limitQuery = orderQuery.limit(2);\nlet users = await limitQuery.select();\n```\n\nEach query could be executed on its own. For readability, we suggest chaining\nqueries as we show in the docs, but for advanced composition and reusability\nyou can cache `Composer` instances.\n\n#### Composer instance methods\n\n##### Composer#safeWhere\n\nAlias for `Composer#where` that prevents querying on fields that the model has\nhidden via `Model.hides('field_name')`. This is useful for querying against\nuser-supplied data, e.g. if you pass in POST data from a web request directly\nto the ORM.\n\n##### Composer#safeJoin\n\nAlias for `Composer#join` that prevents querying on fields that the model has\nhidden via `Model.hides('field_name')`. This is useful for querying against\nuser-supplied data, e.g. if you pass in POST data from a web request directly\nto the ORM.\n\n##### Composer#where\n\n```javascript\n/**\n* Add comparisons to SQL WHERE clause.\n* @param {Object} comparisons Comparisons object. {age__lte: 27}, for example.\n* @return {Nodal.Composer} new Composer instance\n*/\nwhere (comparisonsArray) { ... }\n```\n\nThis method can be passed a `comparisons` object, multiple `comparisons`\nobjects, or an Array of `comparisons` object. If multiple `comparisons` are\npassed to this method (via an Array or as different arguments), they will be\ntreated as an OR clause.\n\nA comparison object follows the\nformat:\n\n```javascript\n{\n  field__comparator: value\n}\n```\n\nWhere `field` is of the format:\n- `field_name`,\n- `joined_model__joined_model_field_name`\n- `joined_model__other_joined_model__other_joined_model_field_name`\n- ... and so on\n\nAnd `comparator` is a comparator from `PostgresAdapter.prototype.comparators` in\n[PostgresAdapter](core/db/adapters/postgres.js). If no comparator is provided,\nthe comparator will default to `is`.\n\n\n```javascript\nPostgresAdapter.prototype.comparators = {\n  is: field =\u003e `${field} = __VAR__`,\n  not: field =\u003e `${field} \u003c\u003e __VAR__`,\n  lt: field =\u003e `${field} \u003c __VAR__`,\n  lte: field =\u003e `${field} \u003c= __VAR__`,\n  gt: field =\u003e `${field} \u003e __VAR__`,\n  gte: field =\u003e `${field} \u003e= __VAR__`,\n  contains: field =\u003e `${field} LIKE '%' || __VAR__ || '%'`,\n  icontains: field =\u003e `${field} ILIKE '%' || __VAR__ || '%'`,\n  startswith: field =\u003e `${field} LIKE __VAR__ || '%'`,\n  istartswith: field =\u003e `${field} ILIKE __VAR__ || '%'`,\n  endswith: field =\u003e `${field} LIKE '%' || __VAR__`,\n  iendswith: field =\u003e `${field} ILIKE '%' || __VAR__`,\n  like: field =\u003e `${field} LIKE __VAR__`,\n  ilike: field =\u003e `${field} ILIKE __VAR__`,\n  is_null: field =\u003e `${field} IS NULL`,\n  is_true: field =\u003e `${field} IS TRUE`,\n  is_false: field =\u003e `${field} IS FALSE`,\n  not_null: field =\u003e `${field} IS NOT NULL`,\n  not_true: field =\u003e `${field} IS NOT TRUE`,\n  not_false: field =\u003e `${field} IS NOT FALSE`,\n  in: field =\u003e `ARRAY[${field}] \u003c@ __VAR__`,\n  not_in: field =\u003e `NOT (ARRAY[${field}] \u003c@ __VAR__)`,\n  array_contains: field =\u003e `${field} @\u003e __VAR__`,\n  not_array_contains: field =\u003e `NOT (${field} @\u003e __VAR__)`,\n  array_contains: field =\u003e `${field} @\u003e __VAR__`,\n  not_array_contains: field =\u003e `NOT (${field} @\u003e __VAR__)`,\n  array_intersects: field =\u003e `${field} \u0026\u0026 __VAR__`,\n  not_array_intersects: field =\u003e `NOT (${field} \u0026\u0026 __VAR__)`\n  json: (field, value) =\u003e {\n    return `${field.replace(/\"/g,\"\")} = __VAR__`;\n  },\n  jsoncontains: (field) =\u003e {\n    return `${field.replace(/\"/g,\"\")} ? __VAR__`;\n  }\n};\n```\n\nSo, if you had a `User` with `BlogPost`s and `Comment`s...\n\n```javascript\n// Select only for users that have comments on their blog posts matching \"lol\"\nlet users = await User.query()\n  .join('blogPosts__comments') // joins in both blogPosts and comments\n  .where({blogPosts__comments__body__contains: 'lol'})\n```\n\n###### Custom SQL\n\nIn your `comparisons` object instead of passing in a raw `value`, you can pass\nin a synchronous function that returns a SQL statement. For example;\n\n```javascript\n// Fetch users with email being equal to [their username]@gmail.com\nlet gmailUsers = await User.query() // || is str_concat in Postgres\n  .where({email: username =\u003e `${username} || '@gmail.com'`})\n  .select();\n```\n\nYou can compare to multiple fields on the model by adding more arguments;\n\n```javascript\n// Fetch users with email being equal to [firstname].[lastname]@gmail.com\nlet gmailUsers = await User.query() // || is str_concat in Postgres\n  .where({\n    email: (first_name, last_name) =\u003e {\n      return `${first_name} || '.' || ${last_name} || '@gmail.com'`\n    }\n  })\n  .select();\n```\n\n**Important:** Field names are aliased by the query composer during query\ngeneration, so please use the above format to make sure the correct column\nreference is used in comparisons. You **must** concatenate these fields when\ntrying to create strings.\n\n##### Composer#join\n\n```javascript\n/**\n* Join in a relationship.\n* @param {string} joinName The name of the joined relationship\n* @param {array} comparisons comparisons to perform on this join, similar to where\n*/\njoin (joinName, comparisons) { ... }\n```\n\nUse `.join()` to join in related models.\nRelated models are determined by foreign keys and column uniqueness. Names are\nautomatically generate based on the field name. You can also join in based on\ncomparisons similar to the `where()` method. For example, to get a user and\njoin in all of their posts from the last 24 hours:\n\n```javascript\nlet posts = await User.query()\n  .join('posts', {created_at__gte: new Date(Date.now() - (24 * 60 * 60 * 1000))})\n  .select();\n```\n\nYou can also pass in an to `comparisons` to create an OR clause between the two\nobjects.\n\n**Note:** Using this method, **all joins are `LEFT JOIN`s**. If you need to\nperform a more complex join we recommend querying the database directly.\n\n###### One-to-many\n\nIf a `User` has many `Post`s:\n\n```\n// pseudocode for SQL relationships\nforeign_key(\"post\".\"user_id\", \"user\".id\")\nNOT unique(\"post\".\"user_id\")\n```\n\nYou would query this with;\n\n```javascript\nlet users = User.query()\n  .join('posts') // plural\n  .select();\n\nusers[0].joined('posts'); // returns ModelArray instance\n```\n\n###### One-to-one\n\nIf a `User` has just one `Profile`:\n\n```\n// pseudocode for SQL relationships\nforeign_key(\"profile\".\"user_id\", \"user\".id\")\nunique(\"profile\".\"user_id\")\n```\n\nYou would query this with;\n\n```javascript\nlet users = User.query()\n  .join('profile') // not plural\n  .select();\n\nusers[0].joined('profile'); // returns Model instance\n```\n\n###### Naming conventions\n\nJoined models will be automatically named in `lowerCamelCase` as either\n`lowerCamelCaseModels` (one-to-many, plural) or `lowerCamelCaseModel` (one-to-one, singular)\nwhen joined. For example;\n\n- `BlogPost` (model) / `blog_posts` (table) =\u003e `blogPosts` (when 1:many)\n- `BlogPost` (model) / `blog_posts` (table) =\u003e `blogPost` (when 1:1)\n- `CalendarEntryChild` (model) / `calendar_entry_children` (table) =\u003e `calendarEntryChildren` (when 1:many)\n- `CalendarEntryChild` (model) / `calendar_entry_children` (table) =\u003e `calendarEntryChild` (when 1:1)\n- `BigBox` (model) / `big_boxes` (table) =\u003e `bigBoxes` (when 1:many)\n- `BigBox` (model) / `big_boxes` (table) =\u003e `bigBox` (when 1:1)\n\n**Don't worry about using the wrong naming convention.** You will receive an error\nexplaining what join relationships are possible if you get it wrong.\n\nAdditionally, child models will primarily be joined in on parents based on the\n`parent_reference` column -- not the table name. So if an `Account` belongs to a\n`User` but uses the field `owner_id` instead of `user_id`:\n\n```\n// pseudocode for SQL relationships\nforeign_key(\"account\".\"owner_id\", \"user\".id\")\nunique(\"account\".\"owner_id\")\n```\n\nYou would query `Account` like this:\n\n```javascript\nlet users = User.query()\n  .join('account')\n  .select();\nusers[0].joined('account');\n```\n\nBut `Account` would be queried like so:\n\n```javascript\nlet accounts = Account.query()\n  .join('owner')\n  .select()\naccounts[0].joined('owner');\n```\n\n##### Composer#orderBy\n\n```javascript\n/**\n* Order by field belonging to the current Composer instance's model\n* @param {string} field Field to order by\n* @param {string} direction Must be 'ASC' or 'DESC'\n*/\norderBy (field, direction) { ... }\n```\n\nOrders the query by a specific field. These can be stacked to change order when\nfields have the same value\n\n##### Composer#limit\n\n```javascript\n/**\n* Limit to an offset and count\n* @param {number} offset The offset at which to set the limit. If this is the only argument provided, it will be the count instead.\n* @param {number} count The number of results to be returned. Can be omitted, and if omitted, first argument is used for count\n*/\nlimit (offset, count) { ... }\n```\n\nLimits the query to a specific number of results. If only the first argument\nis provided it will be used as `count` and `offset` will be 0.\n\n##### Composer#groupBy\n\n```javascript\n\n/**\n* Groups by a specific field, or a transformation on a field\n* @param {String} column The column to group by\n*/\ngroupBy (column) { ... }\n```\n\nCreates a `GROUP BY` statement, aggregating results by a field. Note that\nby default the only column returned in the grouped object response will be\nthe `column` specified here. You must use the `aggregate()` method to add\naggregate columns. `column` can also be a method, if you need to execute\nSQL as part of the aggregation.\n\nHere is an example query that groups `ActivityTimeEntry` entries by day and\nreturns the total entries and sum of the activity time, then orders by the day.\n\n```javascript\nlet activityEntryData = await ActivityTimeEntry.query()\n  .aggregate('total', (id) =\u003e `COUNT(${id})`)\n  .aggregate('total_activity_time', (activity_time) =\u003e `SUM(COALESCE(${activity_time}, 0))`)\n  .groupBy(created_at =\u003e `DATE_TRUNC('day', ${created_at})`)\n  .orderBy(created_at =\u003e `DATE_TRUNC('day', ${created_at})`, 'ASC');\nconsole.log(activityEntryData);\n// [\n//   {\n//     \"created_at\": \"2023-09-01T00:00:00.000Z\"\n//     \"total\": 7,\n//     \"total_activity_time\": 221\n//   },\n//   {\n//     \"created_at\": \"2023-09-02T00:00:00.000Z\"\n//     \"total\": 23,\n//     \"total_activity_time\": 1056\n//   }\n// ]\n```\n\n##### Composer#aggregate\n\n```javascript\n/**\n* Aggregates a field\n* @param {string} alias The alias for the new aggregate field\n* @param {function} transformation The transformation to apply to create the aggregate\n*/\naggregate (alias, transformation) { ... }\n```\n\nUse with `.groupBy()`, example is provided above.\n\n##### Composer#search\n\n```javascript\n/**\n  * Search a vector field by dot product similarity to a string or object\n  * This method is ideal when using normalized vectors, eg using OpenAI embeddings\n  * This is an alias for an orderBy function that orders by dot product similarity\n  * @param {string} field Field to search\n  * @param {string} value Value to search for\n  * @param {?string} direction Orders by dot product, default is ASC (least to most distance)\n  * @returns {Composer} new Composer instance\n  */\n  search (field, value, direction = 'ASC') { ... }\n```\n\nPerforms a vector comparison (dot product) against the specified vector field.\nThis is ideal to use when your vectors are normalized, like OpenAI embeddings.\nOrder by distance (min: `0`, max: `Infinity`), ascending is default.\n\nThis method creates an aliased field, accessible via `model.getMetafield('field_product')`\nrepresenting the dot product where `field` is the vector field name you are searching for.\n\n##### Composer#similarity\n\n```javascript\n/**\n  * Search a vector field by cosine similarity to a string or object\n  * This is an alias for an orderBy function that orders by cosine similarity\n  * @param {string} field Field to search\n  * @param {string} value Value to search for\n  * @param {?string} direction Orders by similarity, default is DESC (most to least similar)\n  * @returns {Composer} new Composer instance\n  */\n  similarity (field, value, direction = 'DESC') { ... }\n```\n\nPerforms a vector comparison (cosine similarity) against the specified vector field.\nThis is ideal to use when your vectors are NOT normalized. For normalized vectors,\nlike OpenAI embeddings, this will return the same result as `search()` but is slightly\nslower. Orders by similarity (min: `0`, max: `1.0`), defaults to `DESC` order (most similar = 1.0).\n\nThis method creates an aliased field, accessible via `model.getMetafield('field_similarity')`\nrepresenting the cosine similarity where `field` is the vector field name you are searching for.\n\n##### Composer#classify\n\n```javascript\n/**\n  * Classifies results based on cosine similarity to provided terms\n  * @param {string} field Field to search\n  * @param {Array\u003cstring\u003e} values Classification values\n  * @returns {Composer} new Composer instance\n  */\n  classify (field, values = []) { ... }\n```\n\nClassifies rows based on their cosine similarity to the terms provided. This method\ncreates an aliased field, accessible via `model.getMetafield('field_classification')` where\n`field` is the vector field name that contains the classified term as well as a map of\ncosine similarity scores for each provided term.\n\n### Transactions\n\nTransactions can be used to ensure integrity of your data and prevent orphaned\nrows from being inserted into your database. For example, if you need to create\na `User` and an `Account` at the same time but run some logic between them:\n\n```javascript\nconst User = Instant.Model('User');\nconst Account = Instant.Model('Account');\n\nconst txn = Instant.database().createTransaction();\n\ntry {\n  const user = await User.create({email: 'keith@instant.dev'}, txn);\n  await sendUserEmail(user.get('email'), `Welcome to our website!`);\n  const account = await Account.create({user_id: user.get('id')}, txn);\n  await txn.commit();\n} catch (e) {\n  // If any step fails, including sending the welcome email,\n  // we can just roll the whole thing back\n  await txn.rollback();\n}\n```\n\nTransactions can also be queried directly:\n\n```javascript\nlet result = await txn.query(`SELECT * FROM my_table WHERE id = $1`, [100]);\n```\n\nAnd support the `.transact()` function to send in multiple statements:\n\n```javascript\nlet result = await txn.transact([\n  `SELECT * FROM my_table`,\n  `INSERT INTO my_table(field) VALUES((1))`,\n  // Parameterized statements can be passed in as well\n  [`INSERT INTO my_other_table(other_field) VALUES(($1))`, [2]]\n]);\n```\n\nFinally, they can be passed in to a number of existing query methods. This\ngives you transaction-level control right in the ORM.\n**When you pass a transaction object into an ORM method, you must remember to commit it to complete the queries.**\n\n```javascript\n// Can pass transactions to the following Class methods\nawait Model.find(id, txn);\nawait Model.findBy(field, value, txn);\nawait Model.create(data, txn);\nawait Model.update(id, data, txn);\nawait Model.updateOrCreateBy(field, data, txn);\nawait Model.query().count(txn);\nawait Model.query().first(txn);\nawait Model.query().select(txn);\nawait Model.query().update(fields, txn);\n// Instance methods\nawait model.save(txn);\nawait model.destroy(txn);\nawait model.destroyCascade(txn);\n// Instance Array methods\nawait modelArray.saveAll(txn);\nawait modelArray.destroyAll(txn);\nawait modelArray.destroyCascade(txn);\n```\n\n### Input validation\n\nValidations allow you to ensure the right data is being added into the database.\nValidations are performed **immediately** and synchronously, right as data\nis being set in the model. You can check validation errors at any time\nwith `model.hasErrors()` and `model.getErrors()`. Validation errors will cause\n`model.save()` to throw an error and prevent writing a row to your database.\n\nYou can use validations by creating a file\nfor your model in the directory `_instant/models`. Note that the\n[`instant` command line utility](https://github.com/instant-dev/instant) can\nautomatically generate these files for you.\n\nFile: `_instant/models/user.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass User extends InstantORM.Core.Model {\n\n  static tableName = 'users';\n\n}\n\n// Validates email and password before .save()\nUser.validates(\n  'email',\n  'must be valid',\n  v =\u003e v \u0026\u0026 (v + '').match(/.+@.+\\.\\w+/i)\n);\nUser.validates(\n  'password',\n  'must be at least 5 characters in length',\n  v =\u003e v \u0026\u0026 v.length \u003e= 5\n);\n\nexport default User;\n```\n\nNow validations can be used;\n\n```javascript\nconst User = Instant.Model('User');\n\ntry {\n  await User.create({email: 'invalid'});\n} catch (e) {\n  // Will catch a validation error\n  console.log(e.details);\n  /*\n    {\n      \"email\": [\"must be valid\"],\n      \"password\": [\"must be at least 5 characters in length\"]\n    }\n  */\n}\n```\n\nYou can also check errors before the model is saved:\n\n```javascript\nconst User = Instant.Model('User');\n\nlet user = new User({email: 'invalid'});\nif (user.hasErrors()) {\n  console.log(user.getErrors());\n  /*\n    {\n      \"email\": [\"must be valid\"],\n      \"password\": [\"must be at least 5 characters in length\"]\n    }\n  */\n}\nawait user.save(); // will throw an error\n```\n\n### Relationship verification\n\nVerifications allow you to validate fields in your model **asynchronously**,\nas opposed to validations which are only synchronous. Unlike validations,\n**verifications are performed at `INSERT` time**, right before a model is saved\nas a new row in its corresponding table.\n\nYou can use verifications by creating a file for your model in the directory\n`_instant/models`. Note that the\n[`instant` command line utility](https://github.com/instant-dev/instant) can\nautomatically generate these files for you.\n\nFile: `_instant/models/user.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass User extends InstantORM.Core.Model {\n\n  static tableName = 'users';\n\n}\n\n// Before saving to the database, asynchronously compare fields to each other\nUser.verifies(\n  'phone_number',\n  'must correspond to country and be valid',\n  async (phone_number, country) =\u003e {\n    let phoneResult = await someAsyncPhoneValidationAPI(phone_number);\n    return (phoneResult.valid === true \u0026\u0026 phoneResult.country === country);\n  }\n);\n\nexport default User;\n```\n\nNow verifications can be used;\n\n```javascript\nconst User = Instant.Model('User');\n\ntry {\n  await User.create({phone_number: '+1-416-555-1234', country: 'SE'});\n} catch (e) {\n  // Will catch a validation error\n  console.log(e.details);\n  /*\n    {\n      \"phone_number\": [\"must correspond to country and be valid\"],\n    }\n  */\n}\n```\n\n### Calculated and hidden fields\n\nCalculated fields will populate your model with fields that do not exist in your\ntable by can be computed **synchronously** at runtime. They are exposed via the\n`model.get(field)` interface or `model.toJSON()`. Hidden fields prevent exposure\nof sensitive data when using `model.toJSON()`; useful for hiding IDs, encrypted\nfields and more when displaying results to a user.\n\nYou can use calculated and hidden fields by adding to your model file:\n\nFile: `_instant/models/user.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass User extends InstantORM.Core.Model {\n\n  static tableName = 'users';\n\n}\n\nUser.calculates(\n  'formatted_name',\n  (first_name, last_name) =\u003e `${first_name} ${last_name}`\n);\nUser.hides('last_name');\n\nexport default User;\n```\n\n```javascript\nconst User = Instant.Model('User');\n\nlet user = await User.create({first_name: 'Steven', last_name: 'Nevets'});\nlet name = user.get('formatted_name') // Steven Nevets\nlet json = user.toJSON();\n/*\n  Last name is hidden from .hides()\n  {\n    first_name: 'Steven',\n    formatted_name: 'Steven Nevets'\n  }\n*/\n```\n\n### Lifecycle callbacks\n\nLifecycle callbacks are used to execute custom logic inside of transaction\nblocks associated with the creation and destruction of models. Four lifecycle\nevents are supported, `beforeSave()`, `afterSave()`, `beforeDestroy()` and\n`afterDestroy()`. Each of these methods receives a transaction associated with\nthe model creation or destruction query and is performed either before or after\nthe associated event.\n**If an error is thrown in a lifecycle callback, the transaction will be rolled back automatically.**\n\nLifecycle callbacks allow you to create multiple codependent resources\nsimultaneously and can help ensure consistency with third-party services. They\nare manage directly inside your model files. Note that the\n[`instant` command line utility](https://github.com/instant-dev/instant) can\nautomatically generate these files for you.\n\nFile: `_instant/models/user.mjs`\n\n```javascript\nimport InstantORM from '@instant.dev/orm';\n\nclass User extends InstantORM.Core.Model {\n\n  static tableName = 'users';\n\n  async beforeSave (txn) {\n    const NameBan = this.getModel('NameBan');\n    const nameBans = NameBan.query()\n      .where({username: this.get('username')})\n      .limit(1)\n      .select(txn);\n    if (nameBans.length) {\n      throw new Error(`Username \"${this.get('username')}\" is not allowed`);\n    }\n  }\n\n  async afterSave (txn) {\n    // Create an account after the user id is set\n    // But only when first creating the user\n    if (this.isCreating()) {\n      const Account = this.getModel('Account');\n      await Account.create({user_id: this.get('id')}, txn);\n    }\n  }\n\n  async beforeDestroy (txn) { /* before we destroy */ }\n  async afterDestroy (txn) { /* after we destroy */ }\n\n}\n\nexport default User;\n```\n\n## Using Migrations, Seeding and Code Generation\n\nMigrations, seeds and code generation can be managed via the\n[instant.dev](https://github.com/instant-dev/instant) CLI.\n\n## Acknowledgements\n\nSpecial thank you to [Scott Gamble](https://x.com/threesided) who helps run all\nof the front-of-house work for instant.dev 💜!\n\n| Destination | Link |\n| ----------- | ---- |\n| Home | [instant.dev](https://instant.dev) |\n| GitHub | [github.com/instant-dev](https://github.com/instant-dev) |\n| Discord | [discord.gg/puVYgA7ZMh](https://discord.gg/puVYgA7ZMh) |\n| X / instant.dev | [x.com/instantdevs](https://x.com/instantdevs) |\n| X / Keith Horwood | [x.com/keithwhor](https://x.com/keithwhor) |\n| X / Scott Gamble | [x.com/threesided](https://x.com/threesided) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstant-dev%2Form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finstant-dev%2Form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstant-dev%2Form/lists"}