Records and query results

Read a row from a collection as a live record, assign its fields, and write them back with one call.

Docs menu

The record

const Notes = db.collection("notes");
const note = Notes.create({ title: "hi", n: 1, tags: ["a"], done: false });
note.title;                                    // "hi"
note._version;                                 // 1
note.title = "new";
note.dirty();                                  // true
note.save();                                   // true
note.dirty();                                  // false
note._version;                                 // 2
Notes.get(note._id).title;                     // "new"
Notes.get(note._id) instanceof Notes;          // true

save() writes only changed fields and returns true. A stored row is guarded by its _version. If the document changed since the row was read, save() throws ConflictError and writes nothing. Call reload() and assign the fields again.

Methods

const Notes = db.collection("notes2");
const rec = Notes.create({ title: "hi", n: 1, tags: ["a"], done: false, tmp: 1 });
rec.has("title");                              // true
rec.keys().includes("title");                  // true
rec.typeOf("n");                               // "number"
rec.set("title", "x") === rec;                 // true
rec.merge({ n: 5, extra: "e" }) === rec;       // true
rec.inc("n", 2).n;                             // 7
rec.push("tags", "b").tags;                    // ["a", "b"]
rec.pull("tags", "a").tags;                    // ["b"]
rec.toggle("done").done;                       // true
rec.unset("tmp").tmp;                          // undefined
rec.dirty();                                   // true
rec.save();                                    // true
Notes.get(rec._id).tags;                       // ["b"]
rec.title = "oops";
rec.reload().title;                            // "x"
rec.title = "oops";
rec.revert().title;                            // "x"
JSON.parse(JSON.stringify(rec)).title;         // "x"
rec.delete();                                  // true
Notes.get(rec._id);                            // null
Method Behavior
save() writes the changes; true
reload(), revert() re-read the stored document, dropping unsaved changes
delete() delete the document; true when it existed
set(field, value) an unsaved write; chainable
merge(object) set for each field of the object; chainable
unset(field) remove a field; chainable
inc(field, n) exact add; a BigInt past 2^53; TypeError for a non-number, RangeError past int64
toggle(field) flip a boolean
push(field, v), pull(field, v) array edits, saved as $push and $pull
has(field), typeOf(field), keys() return whether the field exists, its type name, and the field names, respectively
dirty() returns whether there are unsaved changes
toJSON() the stored document (JSON.stringify(row))

The methods change the row in memory; save() writes the changes. A method name takes precedence over a stored field of the same name. Read such a field through toJSON(). There is no method named type, so a stored field named type can be read and written like any other field.

Detached rows

const Notes = db.collection("notes3");
const draft = new Notes({ title: "draft" });
"_id" in draft;                                // false
draft.save();                                  // true
draft._version;                                // 1
Notes.count();                                 // 1

new Collection({ ... }) copies the object's own properties. Nothing is stored until save(), which inserts and fills _id and _version on the same object. A failed insert (UniqueError) leaves the row detached: fix it and save again.

Query results

const Notes = db.collection("notes4");
Notes.insertMany([{ title: "a", n: 1 }, { title: "b", n: 2 }, { title: "c", n: 3 }]);
const rows = Notes.gt("n", 0).sort("n").all();
rows.length;                                   // 3
rows.first().title;                            // "a"
rows.last().title;                             // "c"
rows.ids().length;                             // 3
rows.pluck("title");                           // ["a", "b", "c"]
rows.project(["n"]);                           // [{ n: 1 }, { n: 2 }, { n: 3 }]
rows.reduce((sum, r) => sum + r.n, 0);         // 6
rows.setAll("seen", true);                     // 3
rows.incAll("n", 10);                          // 3
Notes.sort("n").pluck("n");                    // [11, 12, 13]
Notes.eq("seen", true).count();                // 3
rows[0].title = "A";
rows[1].title = "B";
rows.saveAll();                                // 2
Notes.sort("n").pluck("title");                // ["A", "B", "c"]
rows.deleteAll();                              // 3
Notes.count();                                 // 0

find and a chain's all() return an Array of rows with the extra methods below. These methods apply to unprojected document results. find(..., { select }) and projection, grouping, or compound chains return plain rows. ids, pluck, and ordinary Array map return ordinary arrays. saveAll counts rows with unsaved changes that were saved. It can surface the first row conflict while still saving other current rows.

Method Result
first(), last() a row or undefined
ids(), pluck(field), project([fields]) id strings, one field of each, plain objects
toArray(), toJSON() a plain Array of rows, of documents
reduce(fn, initial), sort(compare) as Array
setAll(field, v), incAll(field, n) write each row at once; the count
saveAll() save the rows with unsaved changes; the count
deleteAll() delete every row's document; the count

Rules

  • Writes through rows and collections join db.transaction and are undone by a rollback.
  • A row is a snapshot for the call that read it; do not keep rows in module state.