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 what changed and answers true. A stored row is guarded by its _version: if the document changed since the row was read, save() throws ConflictError and writes nothing (reload() and assign 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 Does
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() the field exists, its type name, its field names
dirty() there are unsaved changes
toJSON() the stored document (JSON.stringify(row))

The methods change the row in memory; save() writes them. A method name wins over a stored field of the same name (read such a field through toJSON()). No method is named type: a stored field named type reads and writes as 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 fields. 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() answer an Array of rows with these extra methods:

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 Answers
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: a rollback takes them back.
  • A row is a snapshot for the call that read it; do not keep rows in module state.