MongoDB query language for in-memory objects
$ npm install mingo
<array>.<index>
and <document>.<field>
.$$ROOT
, $$CURRENT
, $$DESCEND
, $$PRUNE
, $$KEEP
, $$REMOVE
, $$NOW
toString
when implemented.For more documentation on how to use operators see mongodb.
The package provides 3 distributions on NPM.
6.6.0
for browser targets. See esm.run or unpkg CDNs.require
.import
.Important
Supporting both CJS and ESM modules makes this library subject to the dual package hazard. In backend environments, be consistent with the module loading format to avoid surprises and subtle errors, and use the bundle instead of modules when loading over the network.
// Use as ESM module
import mingo from "mingo";
// or CommonJS
const mingo = require("mingo");
The public API exports interfacs suitable for most use cases. By default the Query
and Aggregator
objects load their basic operators in the context. These include all query predicates and projection operators, plus $project, $match, and $sort for Aggregator
. All other operators must be explicitly added through a custom Context
.
To use more operators load them into a Context
object and pass with your options. This enables tree-shaking in ESM environments during bundling.
As mentioned previously, some operators are always included in a context.
NB: To prevent surprises, operators loaded into a context cannot be replaced. Registering an operator with an already existing name for its type, is a no-op and does not throw an error. To ensure a custom versions of operators are used, add them first to your Context
instance.
The gloabal operator functions
useOperators
andgetOperator
are deprecated and will be removed in7.0.0
.
import { aggregate, Context } from "mingo";
import { $count } from "mingo/operators/pipeline";
// creates a context with "$count" stage operator
// can also use `Context.init({ pipeline: { $count } })`.
const context = Context.init().addPipelineOps({ $count });
const results = aggregate(
[
{ _id: 1, score: 10 },
{ _id: 2, score: 60 },
{ _id: 3, score: 100 }
],
[
// $match is included by default.
{ $match: { score: { $gt: 80 } } },
{ $count: "passing_scores" }
],
{ context } // pass context in options
);
A fully loaded Context
with every operator is provided through the module mingo/init/context
.
import fullContext from "mingo/init/context";
// include every operator in the context.
const context = fullContext();
// use in options for queries (find) and aggregation (aggregate)
import { Query } from "mingo";
// create a query with criteria
// find all grades for homework with score >= 50
let query = new Query({
type: "homework",
score: { $gte: 50 }
});
// test if an object matches query
query.test(doc);
import { Query } from "mingo";
// input is either an Array or any iterable source (i.e Object{next:Function}) including ES6 generators.
let criteria = { score: { $gt: 10 } };
let query = new Query(criteria);
// filter collection with find()
let cursor = query.find(collection);
// alternatively use shorthand
// cursor = mingo.find(collection, criteria)
// sort, skip and limit by chaining
cursor.sort({ student_id: 1, score: -1 }).skip(100).limit(100);
// count matches. exhausts cursor
cursor.count();
// classic cursor iterator (old school)
while (cursor.hasNext()) {
console.log(cursor.next());
}
// ES6 iterators (new cool)
for (let value of cursor) {
console.log(value);
}
// all() to retrieve matched objects. exhausts cursor
cursor.all();
To use the $jsonSchema
operator, you must register your own JsonSchemaValidator
in the options.
No default implementation is provided out of the box so users can use a library with their preferred schema format.
The example below uses Ajv to implement schema validation.
import * as mingo from "mingo"
import type { AnyObject } from "mingo/types"
import type { JsonSchemaValidator } from "mingo/core"
import Ajv, { Schema } from "ajv"
const jsonSchemaValidator: JsonSchemaValidator = (s: AnyObject) => {
const ajv = new Ajv();
const v = ajv.compile(s as Schema);
return (o: AnyObject) => (v(o) ? true : false);
};
const schema = {
type: "object",
required: ["item", "qty", "instock"],
properties: {
item: { type: "string" },
qty: { type: "integer" },
size: {
type: "object",
required: ["uom"],
properties: {
uom: { type: "string" },
h: { type: "number" },
w: { type: "number" },
},
},
instock: { type: "boolean" },
},
};
// queries documents using schema validation
mingo.find(docs, { $jsonSchema: schema }, {}, { jsonSchemaValidator }).all();
Note: An error is thrown when the $jsonSchema
operator is used without a the jsonSchemaValidator
configured.
import { Aggregator, Context } from "mingo";
import { $group } from "mingo/operators/pipeline";
import { $min } from "mingo/operators/accumulator";
// ensure the required operators are preloaded prior to using them.
const context = Context.init({
pipeline: { $group },
accumulator: { $min }
});
let agg = new Aggregator(
[
{ $match: { type: "homework" } },
{ $group: { _id: "$student_id", score: { $min: "$score" } } },
{ $sort: { _id: 1, score: 1 } }
],
{ context }
);
// return an iterator for streaming results
let stream = agg.stream(collection);
// return all results. same as `stream.all()`
let result = agg.run(collection);
Query and aggregation operations can be configured with options to enabled different features or customize how documents are processed. Some options are only relevant to specific operators and need not be specified if not required.
Name | Default | Description | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
collation | none | Collation specification for string sorting operations. See Intl.Collator | ||||||||||
collectionResolver | none | Function to resolve strings to arrays for use with operators that reference other collections such as; (string) => AnyObject[] . |
||||||||||
context | none | An object that defines which operators should be used. This option allow users to load only desired operators or register custom operators which need not be available globally. |
||||||||||
hashFunction | default | Custom hash function to replace the default based on "Effective Java" hashCode. Expects:(Any) => number . |
||||||||||
idKey | "_id" |
The key that is used to lookup the ID value of a document. |
||||||||||
jsonSchemaValidator | none | JSON schema validator to use for the (schema: AnyObject) => (document: AnyObject) => boolean .The $jsonSchema operation would fail if a validator is not provided. |
||||||||||
processingMode | CLONE_OFF | Specifies the degree of mutation for inputs and outputs. By default the input collection is modified as needed and returned output objects may share references. Immutable intermediate results may be collected in a pipeline using the$out operator. |
||||||||||
scriptEnabled | true |
Enable or disable using custom script execution. When disabled, operators that execute custom code are disallowed such as;$where , $accumulator , and $function . |
||||||||||
useGlobalContext | true |
Fallback to the global context if an operator is missing from the user-supplied context. This is provided to allow users to strictly enforce which operators may be used. |
||||||||||
useStrictMode | true |
Enforces strict MongoDB compatibility. When disabled the behaviour changes as follows.
|
||||||||||
variables | {} |
Global variables to pass to all operators |
Custom operators can be registered using a Context
object via the context
option which is the recommended way since 6.4.2
. Context
provides a container for operators, that the execution engine will use to process queries. To register an operator globally, the useOperators(...) function is available. Globally registered operators cannot be overwritten whereas a new context may be created and used at anytime.
NB: Note that the execution engine will first try to find the operator in the context and fallback to the global context when not found if the useGlobalContext
option is true
.
Custom operators must conform to the signatures of their types.
To define custom operators, the following imports are useful.
const mingo = require("mingo");
const util = require("mingo/util");
// this example creates a query operator that checks is a value is between a boundary.
const $between = (selector, args, options) => {
return obj => {
const value = util.resolve(obj, selector, { unwrapArray: true });
return value >= args[0] && value <= args[1];
};
};
// a test collection
const collection = [
{ a: 1, b: 1 },
{ a: 7, b: 1 },
{ a: 10, b: 6 },
{ a: 20, b: 10 }
];
The custom operator is registered with a user-provided context object that is passed an option to the query. The context will be searched for operators used in a query and fallback to the global context when not found.
const context = mingo.Context.init().addQueryOps({ $between });
// must specify context option to make operator available
const result = mingo
.find(collection, { a: { $between: [5, 10] } }, {}, { context })
.all();
console.log(result); // output => [ { a: 7, b: 1 }, { a: 10, b: 6 } ]
An update operation can be performed using the update
function from the mingo/updater
module. Unlike other operations in the library, this only works on a single object.
The query and aggregation operators are powerful enough to use for transforming arrays of documents and should be preferred when dealing with multiple objects.
update
returns an array of all paths that were updated. It also supports arrayFilters for applicable operators. To detect whether a change occurred you can check the length of the returned array.
All operators as of MongoDB 5.0 are supported except the positional array operator $
.
import { update } from "mingo";
// all update operators are automatically loaded.
const obj = {
firstName: "John",
lastName: "Wick",
age: 40,
friends: ["Scooby", "Shagy", "Fred"]
};
// returns array of modified paths if value changed.
update(obj, { $set: { firstName: "Bob", lastName: "Doe" } }); // ["firstName", "lastName"]
// update nested values.
update(obj, { $pop: { friends: 1 } }); // ["friends"] => friends: ["Scooby", "Shagy"]
// update nested value path
update(obj, { $unset: { "friends.1": "" } }); // ["friends.1"] => friends: ["Scooby", null]
// update with condition
update(obj, { $set: { "friends.$[e]": "Velma" } }, [{ e: null }]); // ["friends"] => friends: ["Scooby", "Velma"]
// empty array returned if value has not changed.
update(obj, { $set: { fristName: "Bob" } }); // [] => no change to object.
You can also create a preconfigured updater function.
import { createUpdater } from "mingo/updater";
// configure updater to deep clone passed values. clone mode defaults to "copy".
const updateState = createUpdater({ cloneMode: "deep" });
const state = { people: ["Fred", "John"] };
const newPeople = ["Amy", "Mark"];
console.log(state.people); // ["Fred", "John"]
updateState(state, { $set: { people: newPeople } });
newPeople.push("Jason");
console.log(state.people); // ["Amy", "Mark"]
console.log(newPeople); // ["Amy", "Mark", "Jason"]
Below is a description of how this library differs from the full MongoDB query engine.
number
."minKey"
, "maxKey"
, "timestamp"
, or "binData"
.$collStat
, $planCacheStats
, $listSessions
.$comment
, $meta
, $text
.$
.$toObjectId
, $binarySize
, bsonSize
.$merge
enforces unique constraint on the lookup field during input processing.$where
, $function
, and $accumulator
, do not accept strings as the function body.scriptEnabled
option.merge
option.$jsonSchema
operator requires the user to register their own validator using the jsonSchemaValidator
configuration.npm test
to build and run unit tests.To validate correct behaviour and semantics of operators, you may also test against mongoplayground.net. Credit to the author @feliix.
A big thank you to all users and CONTRIBUTORS of this library.
MIT