{"id":28605949,"url":"https://github.com/alexberriman/newtondb","last_synced_at":"2025-08-24T10:13:26.505Z","repository":{"id":46512252,"uuid":"511721521","full_name":"alexberriman/newtondb","owner":"alexberriman","description":"A simple, easy to use and extendible JSON database.","archived":false,"fork":false,"pushed_at":"2023-04-24T07:23:32.000Z","size":632,"stargazers_count":17,"open_issues_count":11,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-29T15:55:58.313Z","etag":null,"topics":["database","document-database","in-memory-database","javascript","json","json-database","json-db","local-database","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/alexberriman/newtondb","language":"TypeScript","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/alexberriman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-08T01:14:32.000Z","updated_at":"2024-02-26T02:25:58.000Z","dependencies_parsed_at":"2023-01-22T14:00:10.144Z","dependency_job_id":null,"html_url":"https://github.com/alexberriman/newtondb","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/alexberriman/newtondb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Fnewtondb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Fnewtondb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Fnewtondb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Fnewtondb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexberriman","download_url":"https://codeload.github.com/alexberriman/newtondb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Fnewtondb/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259322243,"owners_count":22840363,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","document-database","in-memory-database","javascript","json","json-database","json-db","local-database","typescript"],"created_at":"2025-06-11T19:11:55.084Z","updated_at":"2025-06-11T19:11:56.721Z","avatar_url":"https://github.com/alexberriman.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\" id=\"top\"\u003e\n  \u003ca href=\"https://github.com/alexberriman/newtondb\"\u003e\u003cimg src=\"https://github.com/alexberriman/newtondb/raw/main/static/logo.png\" alt=\"newtondb\" height=\"190\"\u003e\u003c/a\u003e\n  \u003cbr\u003e\n  Newton\n  \u003cbr\u003e\n\u003c/h1\u003e\n\n\u003e :warning: **This package is under active development**: Compatibility and APIs may change.\n\n\u003ch4 align=\"center\"\u003eA simple, easy to use and extendible JSON database.\u003c/h4\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Build status](https://github.com/alexberriman/newtondb/actions/workflows/build-test-deploy.yml/badge.svg)](https://github.com/alexberriman/newtondb/actions) [![Version](https://img.shields.io/npm/v/newtondb?label=version)](https://www.npmjs.com/package/newtondb/) [![Minzipped Size](https://img.shields.io/bundlephobia/minzip/newtondb)](https://www.npmjs.com/package/newtondb/) [![License](https://img.shields.io/npm/l/newtondb)](https://github.com/alexberriman/newtondb/blob/main/LICENSE)\n\n[![twitter](https://img.shields.io/badge/Twitter-1DA1F2?logo=twitter\u0026logoColor=white)](https://twitter.com/bezz) [![github](https://img.shields.io/badge/GitHub-100000?logo=github\u0026logoColor=white)](https://github.com/alexberriman/) [![youtube](https://res.cloudinary.com/practicaldev/image/fetch/s--cumRvkw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/badge/YouTube-FF0000%3Flogo%3Dyoutube%26logoColor%3Dwhite)](https://www.youtube.com/channel/UCji7mkyJ6T5X_D9qlWlPczw) [![linkedin](https://img.shields.io/badge/LinkedIn-0077B5?logo=linkedin\u0026logoColor=white)](https://www.linkedin.com/in/alex-berriman/)\n\n\u003c/div\u003e\n\n## Table of contents\n\n- [Introduction](#introduction)\n  - [Key features](#key-features)\n- [Installation](#installation)\n- [Basic usage](#basic-usage)\n- [Basic principles](#basic-principles)\n  - [Adapters](#adapters)\n  - [Collections](#collections)\n  - [Indexing](#indexing)\n  - [Chaining](#chaining)\n  - [Committing mutations](#committing-mutations)\n- [Adapters](#adapters-1)\n  - [MemoryAdapter](#memoryadapter)\n  - [FileAdapter](#fileadapter)\n- [Database](#database)\n  - [`new Database(options)`](#new-databaseadapter-options)\n  - [.read()](#read)\n  - [.write()](#write)\n  - [.$](#dollar)\n  - [.data](#data)\n  - [.observe()](#observe)\n  - [.unobserve()](#unobserve)\n- [Collections](#collections-1)\n  - [`new Collection(options)`](#new-collectionoptions)\n  - [.get()](#get)\n  - [.find()](#find)\n  - [.data](#data-1)\n  - [.count](#count)\n  - [.exists](#exists)\n  - [.select()](#select)\n  - [.insert()](#insert)\n  - [.set()](#set)\n  - [.replace()](#replace)\n  - [.or](#or)\n    - [Upserting data](#upsert)\n  - [.delete()](#delete)\n  - [.orderBy()](#orderby)\n  - [.limit()](#limit)\n  - [.offset()](#offset)\n  - [.commit()](#commit)\n  - [.assert()](#assert)\n  - [.observe()](#observe-1)\n  - [.unobserve()](#unobserve-1)\n- [Querying](#querying)\n  - [By primary key](#by-primary-key)\n  - [By function](#by-function)\n  - [Basic conditions](#by-basic-condition)\n  - [Advanced conditions](#by-advanced-condition)\n    - [`every` and `some`](#every-and-some)\n    - [`not`](#not)\n    - [Operators](#operators)\n      - [equal](#equal)\n      - [notEqual](#notequal)\n      - [lessThan](#lessthan)\n      - [lessThanInclusive](#lessthaninclusive)\n      - [greaterThan](#greaterthan)\n      - [greaterThanInclusive](#greaterthaninclusive)\n      - [in](#in)\n      - [notIn](#notin)\n      - [contains](#contains)\n      - [doesNotContain](#doesnotcontain)\n      - [startsWith](#startswith)\n      - [endsWith](#endswith)\n      - [matchesRegex](#matchesregex)\n      - [doesNotMatchRegex](#doesnotmatchregex)\n    - [Preprocessors](#preprocessors)\n      - [toUpper](#toupper)\n      - [toLower](#tolower)\n      - [toString](#tostring)\n      - [toNumber](#tonumber)\n      - [toLength](#tolength)\n      - [substring](#substring)\n- [Guides and concepts](#guides-and-concepts)\n  - [Type inference](#type-inference)\n- [License](#license)\n\n## Introduction\n\nJSON is central to Javascript and Typescript development. It's commonly used when you need to transfer data between one medium and another, such as when you're consuming and sending data to and from APIs and when persisting and hydrating application data to a remote store (be it the file system, an S3 bucket, session storage, local storage, etc.)\n\nMost of the time, Javascript's Object and Array prototype methods are sufficient when interfacing with your data, however there are times when you may need to interface with a JSON data source as you might a more traditional database. You might find yourself needing to:\n\n- Performantly query large data sets (of which arrays are notoriously poor for).\n- Safely execute serializable queries (e.g. user defined queries).\n- Safely execute data transformations.\n- Set up observers to listen to changes in your data.\n- Automatically hydrate from and persist changes to a remote store (e.g. file system, local storage, s3 bucket, etc.)\n\nThat's where Newton can help out.\n\n### Key features\n\nAlthough Newton doesn't aim to replace a traditional database, it does borrow on common features to let you interact with your data more effectively. It does this by providing:\n\n- A serializable query language to query your data.\n- Adapters to read and write to commonly used stores (filesystem, s3, local/session storage, etc.)\n- Indices - primary, secondary and sort indexes to improve the efficiency of reads.\n- Serializable data transformations.\n- Query caching, eager/lazy loading.\n- Transactions.\n- Observers (hooks).\n\nand more. Above everything else, Newton's mission is to allow you to interface with your data while optimizing for **performance** and **extendability**.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Installation\n\nUsing npm:\n\n```bash\n$ npm install newtondb\n```\n\nOr with yarn:\n\n```bash\n$ yarn add newtondb\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Basic usage\n\n#### Using a single collection:\n\n```ts\nimport { Database } from \"newtondb\";\n\nconst scientists = [\n  { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" },\n  { name: \"Albert Einstein\", born: \"1879-03-14T12:00:00.000Z\" },\n];\nconst db = new Database(scientists);\n\ndb.$.get({ name: \"Isaac Newton\" }).data;\n\n// =\u003e { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" }\n```\n\n#### Using multiple collections:\n\n```ts\nimport { Database } from \"newtondb\";\n\nconst db = {\n  scientists: [\n    { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" },\n    { name: \"Albert Einstein\", born: \"1879-03-14T12:00:00.000Z\" }\n  ],\n  universities: [\n    { name: \"University of Zurich\", location: \"Zurich, Switzerland\" }\n  ]\n];\nconst db = new Database(db);\n\ndb.$.universities.get({ location: \"Zurich, Switzerland\" }).data;\n\n// =\u003e { name: \"University of Zurich\", location: \"Zurich, Switzerland\" }\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Basic principles\n\n### Adapters\n\nAn Adapter is what Newton uses to read and write to a data source. In simple terms, an Adapter is merely an instance of class with both a `read` and `write` method to be able to read from and write changes to your data source.\n\nWhen instantiating Newton, you can either pass through an explicit instance of an Adapter, or you can pass through your data directly and Newton will attempt to infer and instantiate an adapter on your behalf using the following rules:\n\n- If an array of objects, or an object whose properties are all arrays is passed through, Newton will instantiate a new `MemoryAdapter` instance.\n- If a file path is passed through, Newton will instantiate a new `FileAdapter` instance.\n\nYou can extend Newton by creating your own Adapters and passing instances of those adapters through when you instantiate Newton.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Collections\n\nWhen thinking of data sources expressed in JSON, you will often have arrays/lists of data objects of a given type:\n\n```json\n[\n  { \"name\": \"Isaac Newton\", \"born\": \"1643-01-04T12:00:00.000Z\" },\n  { \"name\": \"Albert Einstein\", \"born\": \"1879-03-14T12:00:00.000Z\" }\n]\n```\n\nWe define this as a `Collection`, where a Collection can have a type (in the above example we define a Collection of type `Scientist`).\n\nOne might also have a JSON data structure that defines various arrays of data of different types:\n\n```json\n{\n  \"scientists\": [\n    { \"name\": \"Isaac Newton\", \"born\": \"1643-01-04T12:00:00.000Z\" },\n    { \"name\": \"Albert Einstein\", \"born\": \"1879-03-14T12:00:00.000Z\" }\n  ],\n  \"universities\": [\n    { \"name\": \"University of Zurich\", \"location\": \"Zurich, Switzerland\" }\n  ]\n}\n```\n\nWe define this as a `Database` which contains two collections:\n\n1. `scientists` of type `Scientist`\n1. `universities` or type `University`\n\nNewton will take as input either a single `Collection` or a `Database` with one or more collections.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Indexing\n\nNewton operates on arrays/lists of data. However, there are performance implications when operating on arrays that starts to become more troublesome as the size of your dataset grows. Namely, when given a query or a predicate, internally you have to iterate over the entire list to determine which objects match your predicate.\n\nNewton solves this by internally maintaining both a [linked list](https://en.wikipedia.org/wiki/Doubly_linked_list) and a set of [hash maps](https://en.wikipedia.org/wiki/Hash_table) to efficiently query your data.\n\nFor example, most data sources will often have a primary key composed of one or more attributes that uniquely identifies the item:\n\n```json\n[\n  { \"code\": \"isa\", \"name\": \"Isaac Newton\", \"university\": \"berlin\" },\n  { \"code\": \"alb\", \"name\": \"Albert Einstein\", \"university\": \"cambridge\" }\n]\n```\n\nWhen instantiating newton, if you set the `primaryKey` configuration option to `[\"code\"]`, a hash map would be created internally with `code` as the key, so that when you were to query it, newton could return the record from a single map lookup rather than iterating over the entire list:\n\n```ts\n$.get(\"isa\").data;\n\n// =\u003e { \"code\": \"isa\", \"name\": \"Isaac Newton\", \"university\": \"berlin\" }\n```\n\nYou can configure one or more secondary indexes to maintain hashmaps for attributes that are commonly queried. For example, if you had a data source of 20,000 scientists, and you often queried against universities, you may want to create a secondary index for the `university` attribute. When you then executed the following query:\n\n```ts\n$.find({ university: \"berlin\", isAlive: true });\n```\n\nRather than iterating over all 20,000 records, newton would instead iterate over the records in the hashmap with `university` as the hash (in which there might only be 100 records). You can set up multiple secondary indexes to increase performance even more as your dataset grows.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Chaining\n\nNewton functions using a concept of operation chaining, where the data output from one operation feeds in as input to the subsequent operation. For example, when updating a record, Newton's update function doesn't take as input a query of records to update against. Rather, if you wanted to update a set of records that matched a particular query, you would first `find` those records and then call `set`:\n\n```ts\n// update all records where \"university\" = 'berlin' to \"university\" = 'University of Berlin'\n$.find({ university: \"berlin\" }).set({ university: \"University of Berlin\" });\n```\n\nThis allows you to set up complex chains and transformations on your data.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Committing mutations\n\nMutations are only persisted to the original data source when `.commit` is called on your chain.\n\n```ts\n$.scientists\n  .find({ university: \"berlin\" })\n  .set({ university: \"University of Berlin\" }).data;\n\n// =\u003e [ { \"code\": \"isa\", \"name\": \"Isaac Newton\", \"university\": \"University of Berlin\" } ]\n```\n\nIn the above example, the `university` attribute for all scientists studying at the `\"berlin\"` university is set to `\"University of Berlin\"`, and you can access that data through the `.data` property. However, if you were to then query for scientists attending the `\"University of Berlin\"` you would receive an empty result:\n\n```ts\n$.scientists.find({ university: \"University of Berlin\" }).data;\n\n// =\u003e []\n```\n\nIn order to persist mutations within your chain to the original data source, you must call `.commit`:\n\n```ts\n$.scientists\n  .find({ university: \"berlin\" })\n  .set({ university: \"University of Berlin\" })\n  .commit(); // commits the mutations defined in the chain\n```\n\nYou can then query against the updated items:\n\n```ts\n$.scientists.find({ university: \"University of Berlin\" }).count;\n\n// =\u003e 1\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Adapters\n\n### MemoryAdapter\n\nReads an object directly from memory.\n\n**Usage:**\n\n```ts\nimport { Database } from \"newtondb\";\nimport { MemoryAdapter } from \"newtondb/adapters/memory-adapter\";\n\nconst adapter = new MemoryAdapter({\n  scientists: [\n    { code: \"isa\", name: \"Isaac Newton\", university: \"berlin\" },\n    { code: \"alb\", name: \"Albert Einstein\", university: \"cambridge\" },\n  ],\n  universities: [\n    { id: \"berlin\", name: \"University of Berlin\" },\n    { id: \"cambridge\", name: \"University of Cambridge\" },\n  ],\n});\n\nconst db = new Database(adapter);\nawait db.read();\n\ndb.$.scientists.find({ code: \"isa\" });\n\n// =\u003e { code: \"isa\", name: \"Isaac Newton\", university: \"berlin\" }\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### FileAdapter\n\nReads a JSON file from the local filesystem.\n\n**Usage:**\n\n```ts\nimport { Database } from \"newtondb\";\nimport { FileAdapter } from \"newtondb/adapters/file-adapter\";\n\nconst adapter = new FileAdapter(\"./db.json\");\nconst db = new Database(adapter);\nawait db.read();\n\ndb.$.scientists.find({ code: \"isa\" });\n\n// =\u003e { code: \"isa\", name: \"Isaac Newton\", university: \"berlin\" }\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Database\n\n### `new Database(adapter, options)`\n\nInstantiates a new collection newton instance. The first argument takes either an `Adapter` instance, or a data object (in which case newton will instantiate a `MemoryAdapter` on your behalf).\n\n**Instantiating with an adapter:**\n\n```ts\nimport { Database } from \"newtondb\";\nimport { FileAdapter } from \"newtondb/adapters/file-adapter\";\n\nconst adapter = new FileAdapter(\"./db.json\");\nconst db = new Database(adapter);\nawait db.load();\n```\n\n**Instantiating with a data object:**\n\n```ts\nimport { Database } from \"newton\";\n\nconst db = new Database({\n  scientists: [\n    // ...\n  ]\n  universities: [\n    // ...\n  ]\n});\n```\n\nNewton can be instantiated with either a single collection, or multiple collections. A single collection is defined by an array of objects of the same type, whereas multiple collections is defined as an object whose properties each contain an array of the same type. See: [using multiple collections](#using-multiple-collections).\n\n#### Options\n\nThe following options can be passed through to Newton:\n\n| Option          | Type      | Required | Default value | Description                                                                                                                                                                                         |\n| --------------- | --------- | -------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `writeOnCommit` | `boolean` | `false`  | `true`        | If `true`, Newton will call the `write()` method on your adapter after each commit, persisting data mutations to your data source. **Note:** this option is ignored when using the `MemoryAdapter`. |\n| `collection`    | `object`  | `false`  | `{}`          | Can be used to configure each collection. See below.                                                                                                                                                |\n\n#### DatabaseCollectionOptions\n\nWhen Newton is instantiated with a single collection, the `DatabaseCollectionOptions` object is a single instance of the [`CollectionOptions`](#new-collectionoptions) object. For example:\n\n**Setting `collection` options for a single collection:**\n\n```ts\nconst scientists = [\n  { code: \"isa\", name: \"Isaac Newton\", university: \"berlin\" },\n  { code: \"alb\", name: \"Albert Einstein\", university: \"cambridge\" },\n];\n\nconst db = new Database(scientists, {\n  collection: {\n    primaryKey: \"code\",\n  },\n});\n```\n\nWhen instantiating Newton with multiple collections, the `collection` option takes the shape of an object whose properties are the same as your database shape, where each value is an instance of [`CollectionOptions`](#new-collectionoptions) object. For example:\n\n**Setting `collection` options when using multiple collections:**\n\n```ts\nconst scientists = [\n  { code: \"isa\", name: \"Isaac Newton\", university: \"berlin\" },\n  { code: \"alb\", name: \"Albert Einstein\", university: \"cambridge\" },\n];\n\nconst universities = [\n  { name: \"University of Zurich\", location: \"Zurich, Switzerland\" },\n];\n\nconst db = new Database(scientists, {\n  collection: {\n    scientists: {\n      primaryKey: \"code\",\n    },\n  },\n});\n```\n\nYou can configure as many collections as you like. When omitted from your options, each collection uses the default settings (`{}`):\n\n```ts\nconst db = new Database(scientists, {\n  collection: {\n    scientists: {\n      primaryKey: \"code\",\n    },\n    universities: {\n      primaryKey: [\"name\", \"location\"],\n    },\n  },\n});\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.read()`\n\nWhen reading your data from any source other than memory, you must call `.read()` before you can interact with your database. `read()` is an asynchronous function that returns a `Promise` when complete:\n\n```ts\nimport { Database } from \"newtondb\";\nimport { FileAdapter } from \"newtondb/adapters/file-adapter\";\n\nconst db = new Database(new FileAdapter(\"./db.json\"));\nawait db.read();\n\n// can now interact with your db\n```\n\nIn addition to loading your data, `read()` triggers some basic bootstrapping of your collections. If you try to interact with your database prior to calling read, a `NotReadyError` exception will be thrown:\n\n```ts\nimport { Database } from \"newtondb\";\nimport { FileAdapter } from \"newtondb/adapters/file-adapter\";\n\nconst db = new Database(new FileAdapter(\"./db.json\"));\ndb.$.find({ name: \"isaac newton\" }); // will throw a NotReadyError exception\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.write()`\n\nWill write the current state of your database to its source by triggering the `write()` method in the `Adapter` you instantiated the database with. Returns a `Promise` which will resolve to `true` when the write operation was successful and `false` when it was unsuccessful.\n\n```ts\nconst db = new Database(new FileAdapter(\"./db.json\"));\nawait db.read();\n\ndb.find({ name: \"isaac newton\" }).set({ alive: false }).commit();\nawait db.write();\n```\n\nWhen newton is instantiated with `writeOnCommit` set to `true` (the default option), commits will automatically be written:\n\n```ts\nconst db = new Database(new FileAdapter(\"./db.json\"), { writeOnCommit: true });\nawait db.read();\n\ndb.find({ name: \"isaac newton\" }).set({ alive: false }).commit();\n\n// .write() is not necessary as the changes would have already been written\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n\u003cspan id=\"dollar\"\u003e\u003c/span\u003e\n\n### `.$`\n\nWhen newton is instantiated with a single collection, `$` will return that collection instance:\n\n**Instantiating with a single collection**:\n\n```ts\nconst db = new Database(scientists);\ndb.$.find({ name: \"isaac newton\" });\n```\n\nWhen instantiated with multiple collections, `$` will return an object whose values are collection instances:\n\n```ts\nconst db = new Database({ scientists, universities });\n\ndb.$.scientists.find({ name: \"isaac newton\" });\ndb.$.universities.find({ name: \"university of berlin\" });\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.data`\n\nReturns the data of the entire database.\n\n```ts\nconst scientists = [\n  { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" },\n  { name: \"Albert Einstein\", born: \"1879-03-14T12:00:00.000Z\" },\n];\n\nconst db = new Database(scientists);\ndb.$.find({ name: \"Isaac Newton\" })\n  .set({ name: \"Isaac Newton (deceased)\" })\n  .commit();\n\ndb.data;\n```\n\nWhich returns:\n\n```json\n[\n  { \"name\": \"Isaac Newton (deceased)\", \"born\": \"1643-01-04T12:00:00.000Z\" },\n  { \"name\": \"Albert Einstein\", \"born\": \"1879-03-14T12:00:00.000Z\" }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.observe()`\n\nSets up an observer which is triggered whenever CRUD operations occur on the database.\n\n\u003e :warning: **Note:** when configuring an observer at the database level, it is triggered each time **any** collection is updated. You can also configure observers on [individual collections](#observe-1).\n\nWhen instantiating a database with a single collection, the `observe()` method expects a function with a single argument of type `MutationEvent`:\n\n**MutationEvent**:\n\n```ts\ntype MutationEvent\u003cT\u003e = InsertEvent\u003cT\u003e | DeleteEvent\u003cT\u003e | UpdateEvent\u003cT\u003e;\n```\n\n`MutationEvent` is either an instance of `InsertEvent`, `DeleteEvent` or `UpdateEvent`:\n\n```ts\ntype InsertEvent\u003cT\u003e = { event: \"insert\"; data: T };\ntype DeleteEvent\u003cT\u003e = { event: \"delete\"; data: T };\ntype UpdateEvent\u003cT\u003e = { event: \"updated\"; data: { old: T; new: T } };\n```\n\nWhen using typescript, you can narrow in on the data using the event name:\n\n```ts\nconst db = new Database(scientists);\ndb.observe(({ event, data }) =\u003e {\n  if (event === \"insert\") {\n    // data will be of type `T` (`Scientist` in our case)\n  }\n});\n```\n\nWhen instantiating a database with multiple collections, `observe()` expects a function with two arguments, the first being the collection name and the second a `MutationEvent` argument:\n\n```ts\nconst db = new Database({ scientists, universities });\n\ndb.observe((collection, event) =\u003e {\n  console.log(`collection ${collection} triggered an event`);\n});\n```\n\n#### Return\n\n`observe()` returns a numeric ID of the observer. You can pass this ID to [`unobserve()`](#unobserve) to cancel the observer.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.unobserve()`\n\nTakes as input a numeric ID (the output from [`observe()`](#observe)) and cancels an observer.\n\n```ts\nconst db = new Database({ scientists, universities });\n\nconst observer = db.observe((collection, event) =\u003e {\n  console.log(`collection ${collection} triggered an event`);\n\n  db.unobserve(observer); // cancel after the first event\n});\n```\n\nThrows an `ObserverError` exception when the observer is not found.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Collections\n\n### new Collection(options)\n\nInstantiates a new collection instance. The following options are supported:\n\n| Option       | Type                 | Required | Default value | Description                                                                                                                                                                                                                                                     |\n| ------------ | -------------------- | -------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `primaryKey` | `string \\| string[]` | `false`  | `undefined`   | A single property (or an array of properties for a composite key) that is used to uniquely identify a record. (`id` is commonly used). Not required, but will dramatically speed up read operations when querying by primary key.                               |\n| `copy`       | `boolean`            | `false`  | `false`       | When mutations are committed using `commit`, the original data object will be updated. This can sometimes lead to unintended side effects (when using the `MemoryAdapter`). Set `copy` to `true` to create a deep copy of the collection data on instantiation. |\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.get()`\n\nReturns a single record. Most commonly used when querying your collection by a unique identifier:\n\n```ts\n$.get({ code: \"isa\" }).data;\n\n// =\u003e { \"code\": \"isa\", \"name\": \"Isaac Newton\", \"university\": \"berlin\" }\n```\n\nWhen your collection has been instantiated with a primary key, and your primary key is a single property whose value is a scalar (e.g. a `string` or a `number`), you can call `.get` with that scalar value and Newton will infer the fact that you're querying against your primary key:\n\n```ts\n$.get(\"isa\").data;\n\n// =\u003e { \"code\": \"isa\", \"name\": \"Isaac Newton\", \"university\": \"berlin\" }\n```\n\nYou can query using a [primary key](#by-primary-key), a [basic condition](#by-basic-condition), an [advanced condition](#by-advanced-condition) or a [function](#by-function).\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.find()`\n\nReturns multiple records:\n\n```ts\n$.find({ university: \"cambridge\" }).data;\n\n// =\u003e [ { \"code\": \"alb\", \"name\": \"Albert Einstein\", \"university\": \"cambridge\" } ]\n```\n\nWill return an empty array when no results are found.\n\nYou can query using a [primary key](#by-primary-key), a [basic condition](#by-basic-condition), an [advanced condition](#by-advanced-condition) or a [function](#by-function).\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.data`\n\nThe `data` property returns an array of data as it currently exists within your chain. For example, referencing `.data` on the root collection will return an array of all data in your collection:\n\n```ts\n$.data;\n\n// =\u003e [ { \"name\": \"Isaac Newton\", \"born\": \"1643-01-04T12:00:00.000Z\" }, ... ]\n```\n\nWhen you start chaining operations, `.data` will return an array of data as it currently exists within your chain:\n\n```ts\n$.find({ name: \"Isaac Newton\" }).data;\n\n// =\u003e [ { \"name\": \"Isaac Newton\", \"born\": \"1643-01-04T12:00:00.000Z\" } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.count`\n\nThe `count` property returns the amount of records currently within your chain. When executed from the base collection, it will return the total amount of records in your collection:\n\n```ts\n$.count;\n\n// =\u003e 100\n```\n\nWhen you start chaining operations, `.count` will return the amount of records that currently exist within your chain:\n\n```ts\n$.find({ name: \"Isaac Newton\" }).count;\n\n// =\u003e 1\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.exists`\n\nThe `exists` property is a shorthand for `.count \u003e 0` and simply returns `true` or `false` if there is a non-zero amount of items currently within your chain:\n\n```ts\n$.get(\"isa\").exists;\n\n// =\u003e true\n```\n\nOr when it doesn't exist:\n\n```ts\n$.get(\"not isaac newton\").exists;\n\n// =\u003e false\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.select()`\n\nBy default, when a query returns records, the result includes all of those records' attributes. To only return a subset of an object's properties, call `.select` with an array of properties to return:\n\n```ts\n$.get({ name: \"Isaac Newton\" }).select([\"university\"]).data;\n\n// =\u003e { university: \"Cambridge\" }\n```\n\nGiven the result of one operation is fed into another, the order of `select` doesn't matter. The above will produce the same output as:\n\n```ts\n$.select([\"university\"]).get({ name: \"Isaac Newton\" }).data;\n\n// =\u003e { university: \"Cambridge\" }\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.insert()`\n\nInserts one or more records into the database.\n\nInserting a single record:\n\n```ts\n$.insert({\n  name: \"Nicolaus Copernicus\",\n  born: \"1473-02-19T12:00:00.000Z\",\n}).commit();\n```\n\nYou can insert multiple records by passing through an array of objects to insert:\n\n```ts\n$.insert([\n  { name: \"Nicolaus Copernicus\", born: \"1473-02-19T12:00:00.000Z\" },\n  { name: \"Edwin Hubble\", born: \"1989-11-10T12:00:00.000Z\" },\n]).commit();\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.set()`\n\nUpdates a set of attributes on one or more records.\n\n```ts\n// update isaac newton's college to \"n/a\" and set isAlive to false\n$.find({ name: \"Isaac Newton\" })\n  .set({ college: \"n/a\", isAlive: false })\n  .commit();\n```\n\n`set` can also take as input a function whose first argument is the current value of the record, and which must return a subset of the record to update:\n\n```ts\n// uppercase all universities using .set\n$.set(({ university }) =\u003e ({\n  university: university.toUpperCase(),\n})).commit();\n```\n\n\u003e :warning: this differs from `replace()` in that it will only update/set the attributes passed through, whereas `replace()` will replace the entire document.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.replace()`\n\nReplaces an entire document with a new document:\n\n```ts\nconst newNewton = {\n  name: \"Isaac Newton\",\n  isAlive: false,\n  diedOn: \"1727-03-31T12:00:00.000Z\",\n};\n\n$.get(\"Isaac Newton\").replace(newNewton).commit();\n```\n\n`replace` can also take as input a function whose first argument is the current value of the record, and which must return a complete new record:\n\n```ts\n// uppercase all universities using .replace\n$.replace((record) =\u003e ({\n  ...record,\n  university: university.toUpperCase(),\n})).commit();\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.or`\n\nWhen a `.get()` or `.find()` operation doesn't return any data, the `.or` property can be used to conditionally execute methods on chain:\n\n```ts\n// will throw an Error if Isaac Newton can not be found\nconst isaac = $.get(\"Isaac Newton\").or.throw();\n```\n\n#### Upsert\n\nImportantly, `or` doesn't have to be used immediately after the find/get operation - this allows you to perform conditional operations such as updating an existing record **or** inserting a new record (upserting):\n\n```ts\nconst isaac = $.get(\"Isaac Newton\")\n  .set({ university: \"Trinity College\" })\n  .or.insert({\n    id: 100,\n    name: \"Isaac Newton\",\n    university: \"Trinity College\",\n  })\n  .commit();\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.delete()`\n\nDeletes one or more records from the collection.\n\n`delete()` doesn't take any arguments. Rather, it deletes the records that currently exist within the chain at the time that it's called. For example:\n\n```ts\n// delete all records from a collection\n$.delete().commit();\n\n// delete all scientists from cambridge university\n$.find({ university: \"cambridge\" }).delete().commit();\n\n// delete a single record\n$.get(\"isaac newton\").delete().commit();\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.orderBy()`\n\n`orderBy` can be used to sort records by one or more properties. It takes as input a single object whose properties are a key of your collection's properties, and whose value is either `asc` (for ascending) or `desc` (for descending).\n\nFor example, using the below dataset:\n\n```ts\nconst students = [\n  { name: \"roger galilei\", university: \"mit\" },\n  { name: \"kip tesla\", university: \"harvard\" },\n  { name: \"rosalind faraday\", university: \"harvard\" },\n  { name: \"thomas franklin\", university: \"mit\" },\n  { name: \"albert currie\", university: \"harvard\" },\n];\n```\n\nTo sort by university in descending order and name in ascending order:\n\n```ts\n$.orderBy({ university: \"desc\", name: \"asc\" }).data;\n```\n\nThis will produce the following:\n\n```json\n[\n  { \"name\": \"roger galilei\", \"university\": \"mit\" },\n  { \"name\": \"thomas franklin\", \"university\": \"mit\" },\n  { \"name\": \"albert currie\", \"university\": \"harvard\" },\n  { \"name\": \"kip tesla\", \"university\": \"harvard\" },\n  { \"name\": \"rosalind faraday\", \"university\": \"harvard\" }\n]\n```\n\nGiven the order by which you sort is important, `orderBy()` will adhere to the order of the properties in the object passed through.\n\nFor example, in the above example, `{ university: \"desc\", name: \"asc\" }` was passed through. `orderBy` would first sort by `university` in `descending` order, and then by `name` in ascending order.\n\nIf you were to instead pass through `{ name: \"asc\", university: \"desc\" }`, `orderBy` would first sort by name in `ascending` order and then by `university` in `descending` order. This would produce a different result:\n\n```json\n[\n  { \"name\": \"albert currie\", \"university\": \"harvard\" },\n  { \"name\": \"kip tesla\", \"university\": \"harvard\" },\n  { \"name\": \"roger galilei\", \"university\": \"mit\" },\n  { \"name\": \"rosalind faraday\", \"university\": \"harvard\" },\n  { \"name\": \"thomas franklin\", \"university\": \"mit\" }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.limit()`\n\nYou can use `limit` to only return the first `n` amount of records within your chain:\n\n```ts\n$.find({ university: \"cambridge\" }).limit(5).data;\n```\n\nWill return the first 5 records with `university` set to `\"cambridge\"`.\n\nYou can use `limit` with `offset` to implement an offset based pagination on your data.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.offset()`\n\n`offset` will skip the first `n` records from your query. For example, to skip the first 5 records:\n\n```ts\n$.find({ university: \"cambridge\" }).offset(5).data;\n```\n\n`offset` can be used with `limit` to implement an offset based pagination:\n\n```ts\nconst pageSize = 10;\nconst currentPage = 3;\n\n$.find()\n  .limit(pageSize)\n  .offset((currentPage - 1) * pageSize).data;\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.commit()`\n\nThe following operations can mutate (change) your data:\n\n- [set()](#set)\n- [replace()](#replace)\n- [delete()](#delete)\n- [insert()](#insert)\n\nMutations will only be persisted/committed to your collection when `.commit()` is called. This is useful as it allows you to:\n\n1. Perform temporary transformations on your data, and\n1. Create complex chains\n\nWhat's more, by requiring a call to `commit` Newton confirms your intent to mutate the original data source, reducing the risk for unintended side effects throughout your application.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.assert()`\n\nRuns an `assertion` on your chain, and continues the chain execution if the assertion passes and raises an `AssertionError` when it fails.\n\nTakes as input a function whose single argument is the chain instance and which returns a `boolean`:\n\n```ts\nimport { AssertionError } from \"newtondb\";\n\ntry {\n  $.get({ name: \"isaac newton\" })\n    .assert(({ exists }) =\u003e exists)\n    .set({ university: \"unknown\" })\n    .commit();\n} catch (e: unknown) {\n  if (e instanceof AssertionError) {\n    // record does not exist\n  }\n}\n```\n\nYou can optionally pass through a `string` as the first argument and a `function` as the second to describe your assertion:\n\n```ts\nimport { AssertionError } from \"newtondb\";\n\ntry {\n  $.get({ name: \"isaac newton\" })\n    .assert(\n      \"the record the user is attempting to update exists\",\n      ({ exists }) =\u003e exists\n    )\n    .set({ university: \"unknown\" })\n    .commit();\n} catch (e: unknown) {\n  if (e instanceof AssertionError) {\n    // record does not exist\n  }\n}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.observe()`\n\nWhen mutations to the data source are committed, one or more of the following events will be raised:\n\n- [insert](#insert): raised when a record is inserted into the collection\n- [delete](#delete): raised when a record is deleted from the collection\n- [updated](#updated): raised when a record is updated\n\nYou can pass callbacks to the `observe` method that will be triggered when these events occur.\n\nOn insert:\n\n```ts\nconst onInsert = $.observe(\"insert\", (record) =\u003e {\n  //\n});\n```\n\nOn delete:\n\n```ts\nconst onDelete = $.observe(\"delete\", (record) =\u003e {\n  //\n});\n```\n\nOn update:\n\n```ts\nconst onUpdate = $.observe(\"updated\", (record, historical) =\u003e {\n  // historical.old = item before update\n  // historical.new = item after update\n});\n```\n\nYou can also pass through a wildcard observer which will be triggered on every event:\n\n```ts\nconst wildcardObserver = $.observe((event, data) =\u003e {\n  // event: \"insert\" | \"delete\" | \"updated\"\n  // data: event data\n});\n```\n\nCalls to `.observe()` will return an numeric id of the observer. This id should be passed to `unobserve()` to cancel the observer.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### `.unobserve()`\n\nCancels an observer set with the `.observe()` method. Takes as input a numeric ID (which should correspond to the output of the original `.observe` call).\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Querying\n\nNewton allows you to query your data using through the following mechanisms:\n\n- [By primary key](#by-primary-key)\n- [By function](#by-function)\n- [By a simple condition](#simple-condition)\n- [By an advanced condition](#advanced-condition)\n\nThe examples in this section will use the following dataset:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false },\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### By primary key\n\nWhen you instantiate Newton you can optionally define a primary key:\n\n```ts\nconst db = new Database(scientists, { primaryKey: \"id\" });\n```\n\n\u003e :warning: **Performance warning**: while `primaryKey` is optional, it is highly recommended you set this when you instantiate Newton in order to optimize read performance.\n\nIf the value of your primary key is a scalar value (`string` or `number`), you can query your collection by the value directly:\n\n```ts\n$.get(2).data;\n\n// =\u003e  { id: 3, name: 'galileo galilei', born: 1564, alive: false }\n```\n\nIf you are using a composite primary key, you'll have to pass through an object:\n\n```ts\nconst $ = new Collection(scientists, { primaryKey: [\"name\", \"born\"] });\n\n$.get({ name: \"albert einstein\", born: 1879 }).data;\n\n// =\u003e { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false }\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### By function\n\nA function predicate can be passed to `get()` and `find()`, which takes as input a single argument with the record, and should return `true` if the record passes the predicate and `false` if not.\n\nFor example, to return scientists who are currently alive:\n\n```ts\n$.find((record) =\u003e record.alive).data;\n```\n\nThis will return the following:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003e :warning: **Optimization warning**: Newton will have to iterate over each item in your collection to test whether or not the predicate is truthy. Where possible, you should try and use a [basic](#basic-condition) or [advanced](#advanced-condition) condition with [secondary indexes](#secondary-indexes) to optimize read operations.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### By basic condition\n\nYou can pass a simple key-value query to perform an exact match on items in your collection:\n\n```ts\n$.find({ alive: true }).data;\n```\n\nWhich returns:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\nYou can pass multiple properties through:\n\n```ts\n$.find({ alive: true, born: 1920 }).data;\n\n// =\u003e [ { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### By advanced condition\n\nAn advanced condition is an object with contains a `property`, an `operator` and a `value`:\n\n```ts\n$.find({\n  property: \"born\",\n  operator: \"greaterThan\",\n  value: 1900,\n}).select([\"name\", \"born\"]).data;\n\n// =\u003e [ {\"name\":\"roger penrose\",\"born\":1931}, {\"name\":\"rosalind franklin\",\"born\":1920} ]\n```\n\n#### `every` and `some`\n\nYou can create complex conditions by using a combination of `some` and `every`. Both properties accept an array of conditions. `some` will evaluate as `true` if **any** condition within the array evaluates as `true`, whereas `every` will evaluate to `true` only when **all** conditions within the array evaluate as `true`.\n\n##### `every`\n\nYou can use `every` to return all records that meet **all** of the conditions:\n\n```ts\n$.find({\n  every: [\n    { property: \"born\", operator: \"greaterThan\", value: 1800 },\n    { property: \"name\", operator: \"startsWith\", value: \"r\" },\n  ],\n}).select([\"name\", \"born\"]).data;\n```\n\nThis query will return all scientists who were born after the year 1800 and whose name starts with the letter r:\n\n```json\n[\n  { \"name\": \"roger penrose\", \"born\": 1931 },\n  { \"name\": \"rosalind franklin\", \"born\": 1920 }\n]\n```\n\n##### `some`\n\nYou can use `some` to return all records that meet **any** of the conditions:\n\n```ts\n$.find({\n  some: [\n    { property: \"born\", operator: \"greaterThan\", value: 1800 },\n    { property: \"name\", operator: \"startsWith\", value: \"a\" },\n  ],\n}).select([\"name\", \"born\"]).data;\n```\n\nThis query will return all scientists who were born after the year 1800 or whose name starts with the letter a:\n\n```json\n[\n  { \"name\": \"albert einstein\", \"born\": 1879 },\n  { \"name\": \"marie curie\", \"born\": 1867 },\n  { \"name\": \"roger penrose\", \"born\": 1931 },\n  { \"name\": \"rosalind franklin\", \"born\": 1920 }\n]\n```\n\n#### `not`\n\n`not` can be used to return the _reverse_ of the condition. Similar to standard Javascript, `!false` would return `true` and `!true` would return `false`.\n\nThe following returns all scientists who are alive:\n\n```ts\n$.find({\n  not: {\n    { property: \"alive\", operator: \"equal\", value: false }\n  },\n}).data;\n```\n\nReturned value:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n##### Nesting conditions\n\nYou can nest conditions to create complex rules:\n\n```ts\n$.find({\n  some: [\n    {\n      every: [\n        { property: \"born\", operator: \"greaterThan\", value: 1800 },\n        { property: \"alive\", operator: \"equal\", value: true },\n      ],\n    },\n    {\n      some: [\n        { property: \"name\", operator: \"startsWith\", value: \"albert\" },\n        { property: \"name\", operator: \"endsWith\", value: \"newton\" },\n      ],\n    },\n  ],\n}).select([\"name\", \"born\"]).data;\n```\n\nThis query will return all scientists where:\n\n1. They were born after the year 1800 and are alive, or\n1. Whose first name starts with \"albert\" or ends with \"newton\":\n\n```json\n[\n  { \"name\": \"isaac newton\", \"born\": 1643 },\n  { \"name\": \"albert einstein\", \"born\": 1879 },\n  { \"name\": \"roger penrose\", \"born\": 1931 },\n  { \"name\": \"rosalind franklin\", \"born\": 1920 }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n#### Operators\n\nConditions require one of the following operators:\n\n##### `equal`\n\nPerforms a strict equality (`===`) match:\n\n```ts\n$.find({ property: \"born\", operator: \"equal\", value: 1643 }).data;\n```\n\nReturns the following:\n\n```json\n[{ \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false }]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `notEqual`\n\nPerforms a strict inequality (`!==`) match:\n\n```ts\n$.find({ property: \"alive\", operator: \"notEqual\", value: true }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `startsWith`\n\nChecks if a string starts with a given value.\n\n```ts\n$.find({ property: \"name\", operator: \"startsWith\", value: \"ro\" }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `endsWith`\n\nChecks if a string ends with a given value.\n\n```ts\n$.find({ property: \"name\", operator: \"endsWith\", value: \"n\" }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `greaterThan`\n\nChecks if a **numeric value** is greater than a given value:\n\n```ts\n$.find({ property: \"born\", operator: \"greaterThan\", value: 1879 }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `greaterThanInclusive`\n\nChecks if a **numeric value** is greater than or equal to a given value:\n\n```ts\n$.find({ property: \"born\", operator: \"greaterThanInclusive\", value: 1879 })\n  .data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `lessThan`\n\nChecks if a **numeric value** is less than than a given value:\n\n```ts\n$.find({\n  property: \"born\",\n  operator: \"lessThan\",\n  value: 1867,\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `lessThanInclusive`\n\nChecks if a **numeric value** is less than than or equal to a given value:\n\n```ts\n$.find({\n  property: \"born\",\n  operator: \"lessThanInclusive\",\n  value: 1867,\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `in`\n\nChecks if a value exists within an array of allowed values:\n\n```ts\n$.find({ property: \"born\", operator: \"in\", value: [1867, 1920] }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `notIn`\n\nChecks if a value does not exist within an array of values:\n\n```ts\n$.find({ property: \"born\", operator: \"notIn\", value: [1867, 1920] }).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `contains`\n\nWe'll use the following dataset for this example (as well as the examples in `doesNotContain`):\n\n```json\n[\n  {\n    \"name\": \"lise meitner\",\n    \"awards\": [\"leibniz medal\", \"liebenn prize\", \"ellen richards prize\"]\n  },\n  {\n    \"name\": \"vera rubin\",\n    \"awards\": [\n      \"gruber international cosmology prize\",\n      \"richtmyer memorial award\"\n    ]\n  },\n  {\n    \"name\": \"chien-shiung wu\",\n    \"awards\": [\"john price wetherill medal\"]\n  }\n]\n```\n\nChecks if an array or string **contains** a value:\n\n```ts\n$.find({ property: \"name\", operator: \"contains\", value: \"-\" }).data;\n```\n\nReturns the following:\n\n```json\n[{ \"name\": \"chien-shiung wu\", \"awards\": [\"john price wetherill medal\"] }]\n```\n\n`contains` can also be used to check if an array contains a given value:\n\n```ts\n$.find({\n  property: \"awards\",\n  operator: \"contains\",\n  value: \"richtmyer memorial award\",\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  {\n    \"name\": \"vera rubin\",\n    \"awards\": [\n      \"gruber international cosmology prize\",\n      \"richtmyer memorial award\"\n    ]\n  }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `doesNotContain`\n\nChecks if an array or string **does not contain** a value:\n\n```ts\n$.find({ property: \"name\", operator: \"doesNotContain\", value: \"r\" }).data;\n```\n\nReturns the following:\n\n```json\n[{ \"name\": \"chien-shiung wu\", \"awards\": [\"john price wetherill medal\"] }]\n```\n\n`doesNotContain` can also be used to check if an array does not contain a given value:\n\n```ts\n$.find({\n  property: \"awards\",\n  operator: \"doesNotContain\",\n  value: \"richtmyer memorial award\",\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  {\n    \"name\": \"lise meitner\",\n    \"awards\": [\"leibniz medal\", \"liebenn prize\", \"ellen richards prize\"]\n  },\n  { \"name\": \"chien-shiung wu\", \"awards\": [\"john price wetherill medal\"] }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `matchesRegex`\n\nChecks if a string matches a regular expression:\n\n```ts\n$.find({\n  property: \"name\",\n  operator: \"matchesRegex\",\n  value: \"^ro(g|s)\",\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `doesNotMatchRegex`\n\nChecks if a string does not match a regular expression:\n\n```ts\n$.find({\n  property: \"name\",\n  operator: \"doesNotMatchRegex\",\n  value: \"^ro(g|s)\",\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n#### Preprocessors\n\nPreprocessor functions can optionally be applied to the values you're evaluating against in your condition. They can be used to check for:\n\n- case insensitivity\n- empty/non-empty checks\n- type coercion\n\nTo apply a preprocessor to a property, instead of passing a `property` through as a `string`, pass an `object` through with a `name` and `preProcess` property:\n\n```ts\n$.find({\n  property: { name: \"name\", preProcess: [\"toUpper\"] },\n  operator: \"contains\",\n  value: \"ISAAC\",\n}).data;\n\n// =\u003e [ { id: 1, name: 'isaac newton', born: 1643, alive: false } ]\n```\n\n`preProcess` is an array which can contain one or more preprocessors. When a preprocessor doesn't require any arguments (`toUpper`, `toLower`, `toString`, `toNumber`, `toLength`) you can pass the preprocessor through as a string (as shown in the above example). For functions that require one or more arguments (`substring`, `concat`), pass through an `object` where `fn` is the name of the preprocessor and `args` is an array of arguments:\n\n```ts\n$.find({\n  property: {\n    name: \"name\",\n    preProcess: [\"toUpper\", { fn: \"substring\", args: [0, 3] }],\n  },\n  operator: \"equal\",\n  value: \"ISA\",\n}).data;\n\n// =\u003e [ { id: 1, name: 'isaac newton', born: 1643, alive: false } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `toUpper`\n\nConverts the property to all uppercase before evaluating the condition.\n\n```ts\n$.find({\n  property: { name: \"name\", preProcess: [\"toUpper\"] },\n  operator: \"equal\",\n  value: \"ISAAC\",\n}).data;\n\n// =\u003e [ { id: 1, name: 'isaac newton', born: 1643, alive: false } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `toLower`\n\nConverts the property to all lowercase before evaluating the condition.\n\n```ts\n$.find({\n  property: { name: \"name\", preProcess: [\"toLower\"] },\n  operator: \"equal\",\n  value: \"isaac\",\n}).data;\n\n// =\u003e [ { id: 1, name: 'isaac newton', born: 1643, alive: false } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `toString`\n\nConverts the property to a string before evaluating the condition.\n\nThe below example won't return any data since `born` is of type `number` on the original object and we are doing a comparison of type `string` (remembering that the `equal` operators perform a strict equality (`===`) check):\n\n```ts\n$.find({ property: \"born\", operator: \"equal\", value: \"1867\" }).data;\n\n// =\u003e []\n```\n\nIf you want to compare a `number` against a `string` value, you can coerce the original value to a `string` using the `toString` preprocessor:\n\n```ts\n$.find({\n  property: { name: \"born\", preProcess: [\"toString\"] },\n  operator: \"equal\",\n  value: \"1867\",\n}).data;\n\n// =\u003e [ { id: 4, name: 'marie curie', born: 1867, alive: false } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `toNumber`\n\nConverts the property to a number before evaluating the condition.\n\nUsing the following dataset:\n\n```json\n[\n  { \"element\": \"hydrogen\", \"atomicNumber\": \"1\" },\n  { \"element\": \"helium\", \"atomicNumber\": \"2\" },\n  { \"element\": \"lithium\", \"atomicNumber\": \"3\" },\n  { \"element\": \"beryllium\", \"atomicNumber\": \"4\" },\n  { \"element\": \"boron\", \"atomicNumber\": \"5\" }\n]\n```\n\nExecuting the following query will return an empty result, as we are trying to perform an `equal` operation (`===`) on data of type `string` with a `number`:\n\n```ts\n$.find({ property: \"atomicNumber\", operator: \"equal\", value: 2 }).data;\n\n// =\u003e []\n```\n\nIf we want to perform an equality match on different data types, we can first coerce the value to a number:\n\nIf you want to compare a `number` against a `string` value, you can coerce the original value to a `string` using the `toString` preprocessor:\n\n```ts\n$.find({\n  property: { name: \"atomicNumber\", preProcess: [\"toNumber\"] },\n  operator: \"equal\",\n  value: 2,\n}).data;\n\n// =\u003e [ { element: 'helium', atomicNumber: '2' } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `toLength`\n\nReturns the length of a string or the amount of items in an array. Can be used to check for non-empty values:\n\n```ts\n$.find({\n  property: { name: \"name\", preProcess: [\"toLength\"] },\n  operator: \"greaterThan\",\n  value: 0,\n}).data;\n```\n\nReturns the following:\n\n```json\n[\n  { \"id\": 1, \"name\": \"isaac newton\", \"born\": 1643, \"alive\": false },\n  { \"id\": 2, \"name\": \"albert einstein\", \"born\": 1879, \"alive\": false },\n  { \"id\": 3, \"name\": \"galileo galilei\", \"born\": 1564, \"alive\": false },\n  { \"id\": 4, \"name\": \"marie curie\", \"born\": 1867, \"alive\": false },\n  { \"id\": 5, \"name\": \"roger penrose\", \"born\": 1931, \"alive\": true },\n  { \"id\": 6, \"name\": \"rosalind franklin\", \"born\": 1920, \"alive\": true }\n]\n```\n\nCan also be used on arrays:\n\n```ts\nconst schedule = new Database([\n  { department: \"it\", subjects: [\"data structures and algorithms\"] },\n  { department: \"physics\", subjects: [\"newtonian mechanics\"] },\n  { department: \"maths\", subjects: [] },\n]);\n\nschedule.$.find({\n  property: { name: \"subjects\", preProcess: [\"toLength\"] },\n  operator: \"greaterThan\",\n  value: 0,\n}).data;\n```\n\nReturns records with a non-empty `subjects` property:\n\n```json\n[\n  { \"department\": \"it\", \"subjects\": [\"data structures and algorithms\"] },\n  { \"department\": \"physics\", \"subjects\": [\"newtonian mechanics\"] }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n##### `substring`\n\nReturns the part of the string between the `start` and `end` indexes, or to the end of the string:\n\n```ts\n$.find({\n  property: {\n    name: \"name\",\n    preProcess: [{ fn: \"substring\", args: [1, 4] }],\n  },\n  operator: \"equal\",\n  value: \"oge\",\n}).data;\n\n// =\u003e [ { id: 5, name: 'roger penrose', born: 1931, alive: true } ]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Guides and concepts\n\n### Type inference\n\nWhen using the `MemoryAdapter`, newton will automatically infer the shape of your data based on the value passed in:\n\n**Using a single collection:**\n\n```ts\nconst db = new Database([\n  { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" },\n  { name: \"Albert Einstein\", born: \"1879-03-14T12:00:00.000Z\" },\n]);\n```\n\n\u003cimg src=\"https://github.com/alexberriman/newtondb/raw/main/static/01-type-inference-single-collection.png\" alt=\"type inference using a single collection\" /\u003e\n\n**Using multiple collections:**\n\n```ts\nconst db = new Database({\n  scientists: [\n    { name: \"Isaac Newton\", born: \"1643-01-04T12:00:00.000Z\" },\n    { name: \"Albert Einstein\", born: \"1879-03-14T12:00:00.000Z\" },\n  ],\n  universities: [\n    { name: \"University of Zurich\", location: \"Zurich, Switzerland\" },\n  ],\n});\n```\n\n\u003cimg src=\"https://github.com/alexberriman/newtondb/raw/main/static/02-type-inference-multiple-collections.png\" alt=\"type inference using a single collection\" /\u003e\n\nWhen the shape of the data can't be inferred automatically, you can pass through the shape of the data when instantiating your database:\n\n**Instantiating with the shape of your database:**\n\n```ts\nconst adapter = new FileAdapter(\"./db.json\");\nconst db = new Database\u003c{\n  scientists: Scientist[];\n  universities: University[];\n}\u003e(adapter);\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## License\n\n[MIT](https://tldrlegal.com/license/mit-license)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexberriman%2Fnewtondb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexberriman%2Fnewtondb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexberriman%2Fnewtondb/lists"}