Skip to content

Latest commit

 

History

History
261 lines (197 loc) · 16.5 KB

File metadata and controls

261 lines (197 loc) · 16.5 KB

API Reference

new SencilloDB(config)

Every field is optional.

Option Type Default Meaning
file string "./sencillo.json" Single file mode. Parent folders are created if needed.
folder string Folder mode: one file per collection, loaded on demand.
sharding boolean false Folder mode only: one file per index bucket.
compression boolean false gzip the persisted files.
aof boolean false Append writes to a log instead of rewriting the store.
appendfsync "always" | "everysec" | "no" "everysec" How aggressively the log is flushed to disk.
maxCacheSize number 0 Collections/shards kept in memory. 0 means no limit.
clone boolean true Return deep copies instead of live references.
lock boolean | { staleMs, timeoutMs, retryMs } false Advisory cross process lock file.
loadHook () => Promise<string> Single file mode: load the store from somewhere else.
saveHook (json: string) => Promise<void> Single file mode: save the store somewhere else.
debug boolean false Also print internal warnings to stderr.
autoCompact false | { maxBytes?, maxEffects? } false Compact the AOF after either positive threshold.

Rejected combinations throw a ValidationError: sharding without folder, file together with folder, and hooks together with folder.

Database methods

db.transaction(callback)

Runs callback(tx) and commits when it returns. Resolves to the callback's return value. If the callback throws, every change is discarded and the error is re-thrown.

db.compact()

Folds the append only log back into the store and deletes the log. Also purges expired documents. Safe to call in any mode.

db.close()

Writes anything still pending and drops the in-memory cache. The instance stays usable — the next transaction reloads from disk.

db.export()

Loads every collection (and every shard) and resolves to a plain object copy of the whole store.

db.import(data, options?)

Replaces the store with data. With { merge: true }, existing collections are kept and merged rather than removed. The result is written straight through and the log is reset.

db.snapshot(path)

Writes export() to path. Gzipped when the path ends in .gz.

db.migrate(migrations)

Runs any migration whose version has not been recorded yet, in ascending version order, each inside its own transaction. Applied versions are stored in the __migrations collection. Resolves to the list of versions applied by this call.

await db.migrate([
  {
    version: 1,
    name: "seed",
    up: async (tx) => {
      await tx.create({ collection: "users", data: { name: "root" } });
    },
  },
  {
    version: 2,
    name: "add-role",
    up: async (tx) => {
      await tx.updateMany({
        collection: "users",
        filter: {},
        $set: { role: "member" },
      });
    },
  },
]);

A migration that throws is not recorded, so it runs again next time.

db.collection(name)

Returns a collection-scoped handle whose methods each open their own transaction. With new SencilloDB<AppSchema>(), collection names, input documents and returned documents are typed. The handle exposes create, createMany, update, updateMany, destroy, destroyMany, find, findMany, count, page, stream, ensureIndex and ensureTTL.

db.stats()

Returns live document totals, per-collection bucket/index counts, cache entries and AOF bytes.

db.explain(instructions)

Returns the selected query strategy (id, secondary-index, bucket, or collection-scan) and its estimated candidate count. It does not execute the query.

db.validate() / db.repair()

validate() checks IDs, totals, ID maps and secondary index entries. repair() transactionally rebuilds stats, ID maps and indexes, then returns a fresh integrity report. Back up damaged stores before repair; irrecoverably malformed documents and duplicate IDs require manual review.

SencilloDB extends EventEmitter; see Events.

Transaction methods

All are asynchronous and take a single instructions object.

Method Returns Notes
tx.create the created document Needs data.
tx.createMany array of created documents data must be an array. index may be a function.
tx.update the updated document Needs _id or filter, and data or an update operator.
tx.updateMany array of updated documents Applies the same patch to every match.
tx.destroy the removed document Needs _id or filter.
tx.destroyMany array of removed documents Removes every match.
tx.find the first match or undefined
tx.findMany array of matches Honours sort, skip, limit, populate.
tx.count number Same matching rules as findMany.
tx.dropCollection void Deletes the collection and its files.
tx.dropIndex void Deletes one index bucket, its documents and (in sharded mode) its file.
tx.rewriteCollection void Rebuilds the collection: renumbers ids, re-sorts, re-buckets. Secondary indexes and TTL rules are preserved.
tx.ensureIndex void { collection, field, unique? }. Builds the index over existing documents.
tx.dropSecondaryIndex void { collection, field }.
tx.ensureTTL void { collection, field, seconds }.

tx.update in detail

// full replacement (fields not present are dropped)
await tx.update({
  collection: "users",
  _id: 1,
  data: { name: "Alice", age: 31 },
});

// partial patch
await tx.update({ collection: "users", _id: 1, $set: { age: 31 } });
await tx.update({ collection: "users", _id: 1, $inc: { visits: 1 } });
await tx.update({ collection: "users", _id: 1, $unset: ["temporary"] });

// by filter, creating the document when nothing matches
await tx.update({
  collection: "settings",
  filter: { key: "theme" },
  $set: { value: "dark" },
  upsert: true,
});

$set and $unset accept dot paths ("profile.theme"). Update operators are applied on top of data when both are given.

The instructions object

Field Used by Meaning
collection all Collection name. Defaults to "default".
index all Index bucket. A string, a (document) => string function (create/createMany/update), or { current, new } to move a document. For reads it restricts the search to one bucket.
data create, createMany, update The document (or array of documents).
_id update, destroy Target document id.
filter reads, update, destroy, *Many Query object — see Querying.
callback reads (document) => boolean, combined with filter via AND.
sort findMany, rewriteCollection Array.sort comparator. Defaults to ascending _id.
limit / skip / cursor findMany Offset or ascending _id cursor pagination. Values must be non-negative integers.
populate find, findMany [{ field, collection, targetField? }]. targetField defaults to _id.
$set / $inc / $unset update, updateMany Partial update operators.
upsert update Create the document when nothing matches.

Unknown query operators, invalid regular expressions, unsafe prototype paths and invalid pagination values throw ValidationError.

Typed collection API

type AppSchema = { users: { name: string; active: boolean } };
const db = new SencilloDB<AppSchema>({ file: "./app.json" });
const users = db.collection("users");

await users.create({ data: { name: "Alice", active: true } });
const first = await users.page({ limit: 50 });
const second = await users.page({ limit: 50, cursor: first.nextCursor });

for await (const user of users.stream({ limit: 100 })) {
  console.log(user._id, user.name);
}

page and stream use ascending _id order and reject a custom sort. Their limit is the page/batch size.

Stored value model

Documents must be JSON-compatible objects. Date values are normalized to ISO strings. Circular references, undefined, functions, symbols, bigint, NaN, infinities, arrays as top-level documents, and prototype-control keys are rejected instead of being silently altered by serialization.

quickTx(db)

Wraps a single operation in its own transaction:

import { quickTx } from "sencillodb";

const qtx = quickTx(db);
const user = await qtx("create", {
  collection: "users",
  data: { name: "Alice" },
});

createResourceManager(config)

A schema-validating, hook-aware wrapper around one collection. See Schemas and Resource Managers.

const User = createResourceManager({
  db,
  collection: "users",
  index: (user) => user.country,
  schema: { name: String, age: { type: Number, required: false, default: 0 } },
  hooks: {
    beforeCreate: (data) => ({ ...data, createdAt: new Date().toISOString() }),
  },
});

await User.create({ data: { name: "Alice" } });
await User.findMany({ filter: { age: { $gte: 18 } } });

Returned object: schema, validate(document), errors(document), withDefaults(document), execute(operation, instructions) and shorthands create, createMany, update, updateMany, destroy, destroyMany, find, findMany, count.

Errors

All extend SencilloDBError, which extends Error.

Error Thrown when
ValidationError Bad instructions, bad config, or a schema violation.
CollectionNotFoundError The collection does not exist.
IndexNotFoundError The index bucket does not exist.
DocumentNotFoundError No document matched the _id or filter.
UniqueConstraintError A write would duplicate a value on a unique index.
CorruptDataError A store file exists but could not be read.
LockError The advisory lock could not be acquired before the timeout.
DatabaseNotLoadedError An operation was used outside a transaction before any load.

Events

db.on("create", ({ collection, index, _id, doc }) => {
  /* … */
});
Event Payload Fires
create, update, destroy { collection, index, _id, doc } After the transaction commits
dropCollection, dropIndex, rewriteCollection { collection, index? } After commit
ensureIndex, dropSecondaryIndex, ensureTTL { collection } After commit
expire { collection, index, _id, doc } During compact()
commit array of the events just emitted After commit
rollback { error } After a failed transaction
warning { message, detail } On a recoverable problem, e.g. an unreadable log line