Documentation
Introduction
A document is a plain object: strings, numbers, booleans, null, arrays, nested objects and Date
objects. Every document has an _id.
{
_id: '6f1c…', // generated, or yours
name: 'Coffee',
price: 12.5,
tags: ['hot'],
supplier: { name: 'Fazenda Boa', city: 'Patrocínio' },
createdAt: new Date()
}
Rules for documents
| Rule | What happens otherwise |
|---|---|
The document is an object (not an array, not null) |
Error: Invalid document type |
No key contains $ or ., at any depth |
Error: Invalid document keys ($ starts operators, . separates paths) |
| Values are cloneable: no functions, DOM nodes or class instances with private state | Error: Documents can only contain cloneable values (no functions or DOM nodes) |
_id is unique in the collection |
Error: Duplicate _id "…" |
| The schema, when the collection has one, is satisfied | ValidationError with every problem, see Schema validation |
_id can be any value you like (number, string…). Without one, sorla-db generates a uuid v4 string.
Copies everywhere
sorla-db stores a copy of what you insert, and hands out copies when you read:
const product = { name: 'Coffee', tags: ['hot'] };
const id = products.insertOne(product);
product.tags.push('changed'); // your object: the collection does not see it
const copy = products.findOne({ _id: id });
copy.tags.push('also changed'); // a copy: the collection does not see it either
products.findOne({ _id: id }).tags; // ['hot']
The objects you pass in are never modified (sorla-db does not add _id to them). Stored documents
are never changed in place either: an update stores a new version. That is what makes cursors
consistent snapshots and lets updates share the unchanged parts between versions (see
Performance).
insertOne
const id = products.insertOne({ name: 'Coffee', price: 12.5 });
products.insertOne({ _id: 'tea', name: 'Tea' }, id => console.log('stored', id)); // optional callback
Returns the _id. Schema defaults are filled in before validation (see
Schema validation). Emits an insert event.
insertMany
const ids = products.insertMany([
{ name: 'Tea', price: 8 },
{ name: 'Juice', price: 9.9 }
]);
// ['…', '…']
All or nothing: the whole array is checked first (types, keys, duplicate ids inside the batch and against the collection, schema), so one bad document stores nothing:
try {
products.insertMany([{ _id: 1, name: 'A' }, { _id: 1, name: 'B' }]);
} catch (error) {
error.message; // 'Duplicate _id "1"'
products.count(); // unchanged
}
Emits one insert event for the whole batch. The optional second argument is a callback that
receives the ids. Inserting is linear: 100 000 documents take well under a second.
Need inserts, updates and deletes in one call? Use bulkWrite.
Dates
Date objects are stored as dates (copied), compared by time in queries and sorted after the other
types. Many apps store ISO strings instead ('2026-11-01'): they sort and compare correctly as
text, and they survive JSON.stringify when you send data to a server.
events.insertOne({ title: 'Launch', at: new Date('2026-11-01T10:00:00Z') });
events.find({ at: { $gte: new Date('2026-11-01') } });
Nested objects and arrays
Nested values are reached with dot paths in queries, sorting and updates:
products.insertOne({ name: 'Coffee', supplier: { city: 'Patrocínio' }, tags: ['hot', 'strong'] });
products.find({ 'supplier.city': 'Patrocínio' });
products.find({ tags: { $in: ['strong'] } });
products.updateOne({ name: 'Coffee' }, { $set: { 'supplier.city': 'Franca' } });