Skip to content
Jennifer Programming Language

flatdb API reference

A file-backed JSON document store. Load a JSON file once into a value-semantic handle, query and edit it in memory through JSON Pointer, and write it back with a crash-atomic whole-file replace (temp file + rename). It is deliberately NOT a database engine: crash-atomic snapshotting of small data. Atomicity is whole-file (temp + rename); durability is only as strong as the OS's buffering of the rename. For a real database, use a client over net (e.g. redis), not this. It is a thin file-lifecycle + ergonomics layer over the json write surface, not a re-implementation of it. Runs on both binaries (pure json + fs, no network).

CONCURRENT WRITERS: crash-atomic is not the same as write-safe. Two writers that each open, edit different parts, and save would otherwise silently lose one update (last rename wins). Two guards address that: save fails loudly (a catchable flatdb error) if the file changed since it was opened, so a lost update can never be silent; and update(path, transform) brackets the whole read-modify-write in a cross-process advisory lock, so concurrent writers serialize and no update is lost. Prefer update when more than one writer (separate processes, or spawned tasks) can touch one store.

Import with import "flatdb.j" as flatdb;. See the flatdb guide for prose and examples.

Functions

flatdb.append(db as DB, pointer as string, value as json.Value)

Push value onto the list addressed by pointer, returning a new DB. The list must already exist (create it first with set($db, ptr, json.list())).

Parameters

  • db {DB} - the store to edit
  • pointer {string} - the JSON Pointer to a list
  • value {json.Value} - the value to append

Returns {DB} - a new store with the element appended

Throws

  • {Error} - when pointer does not resolve to an existing list

flatdb.get(db as DB, pointer as string)

Return the sub-document at pointer (the whole document for "").

Parameters

  • db {DB} - the store to read
  • pointer {string} - the JSON Pointer

Returns {json.Value} - the node at pointer

Throws

  • {Error} - when pointer does not resolve

flatdb.has(db as DB, pointer as string)

Report whether pointer resolves to an existing node.

Parameters

  • db {DB} - the store to read
  • pointer {string} - the JSON Pointer

Returns {bool} - true when the node exists

flatdb.keys(db as DB, pointer as string)

List the keys of the object at pointer, in document order.

Parameters

  • db {DB} - the store to read
  • pointer {string} - the JSON Pointer to an object

Returns {list of string} - the object's keys

Throws

  • {Error} - when pointer does not resolve to an object

flatdb.length(db as DB, pointer as string)

Return the element count of a list, or entry count of an object, at pointer.

Parameters

  • db {DB} - the store to read
  • pointer {string} - the JSON Pointer to a list or object

Returns {int} - the element or entry count

Throws

  • {Error} - when pointer does not resolve to a list or object

flatdb.lock(dbPath as string)

Take the advisory write lock for the store backed by dbPath, blocking (with a short poll) until it is free or LOCK_WAIT_MS elapses. Built on fs.writeNew (an atomic exclusive create), so it coordinates across processes, not just spawned tasks. A lock leaked by a crashed writer is broken after LOCK_STALE_SECONDS. Pair with unlock - or, better, use update, which brackets the whole read-modify-write and releases on every path.

Parameters

  • dbPath {string} - the store's backing file path

Returns {Lock} - the held lock (pass to unlock)

Throws

  • {Error} - kind "flatdb" if the lock cannot be taken within LOCK_WAIT_MS, or at once if the lock file cannot be created at all (no holder exists: a missing directory, a read-only mount, no write permission)

flatdb.open(path as string)

Load the JSON document at path into a DB. A missing file yields an empty document (an empty object), so open never fails on a first run.

Parameters

  • path {string} - the backing file path

Returns {DB} - the loaded store

flatdb.openString(text as string)

Load a DB from an in-memory JSON string instead of a file - for a database fetched over the network (http.get(url, {}).body), embedded in the program, or built elsewhere. The returned DB has no backing path, so it is read-only: every reader verb works and the mutating verbs still return a fresh in-memory DB, but save throws (there is nowhere to write). Whitespace-only text yields an empty document, matching open on a missing file. This keeps flatdb transport-agnostic: it never imports http / net, so it stays fs-only and builds on both binaries; the caller brings the bytes.

Parameters

  • text {string} - the JSON document

Returns {DB} - a read-only store

flatdb.remove(db as DB, pointer as string)

Drop the key or element at pointer, returning a new DB.

Parameters

  • db {DB} - the store to edit
  • pointer {string} - the JSON Pointer to remove

Returns {DB} - a new store with the node removed

Throws

  • {Error} - when pointer does not resolve

flatdb.save(db as DB)

Write the document back to its own backing file, crash-atomically (temp + rename), and return a fresh DB rebound to the just-written file. A read-only DB (from openString, no backing file) has nowhere to write - use saveAs.

Conflict check: if the file changed since this DB was opened (a concurrent writer got there first), save throws a flatdb error instead of silently clobbering their write. Re-open and retry, or - to serialize writers without a possible conflict at all - use update. Because save returns the rebound DB, a program that saves the same handle repeatedly should thread it through ($db = flatdb.save($db);) so the next save checks against the file it wrote.

Parameters

  • db {DB} - the store to persist

Returns {DB} - a fresh DB bound to the written file (its version is the new file's)

Throws

  • {Error} - kind "flatdb" if the DB is read-only, if the document changed since it was opened, or on a filesystem write / rename failure

flatdb.saveAs(db as DB, path as string)

Write the document to path (not necessarily its own) and return a fresh DB bound to that path - the passed-in db is unchanged (value semantics, like set / append / remove). Two uses: the first dump to disk of a read-only openString DB, and copying / forking an on-disk store to a new file (a snapshot or a new version). Which store is "current" for the next save is whichever handle you keep - reassign ($db = flatdb.saveAs($db, path);) to make the new file current, or hold both to keep the original too. Same crash-atomic temp+rename as save.

Parameters

  • db {DB} - the store whose data to write
  • path {string} - the destination file path

Returns {DB} - a new DB bound to path (its data equals db's)

Throws

  • {Error} - kind "flatdb" if path is empty, or on a filesystem failure

flatdb.set(db as DB, pointer as string, value as json.Value)

Write value at pointer (upsert an object key / replace a list index), returning a new DB. Strict: intermediate containers must already exist.

Parameters

  • db {DB} - the store to edit
  • pointer {string} - the JSON Pointer to write
  • value {json.Value} - the value to write (build scalars with json.decode, objects and lists with json.map / json.list)

Returns {DB} - a new store with the write applied

Throws

  • {Error} - when an intermediate container does not exist

flatdb.unlock(lk as Lock)

Release a lock taken by lock, but only if this holder still owns it: if a stale-break already handed the lock to another writer, unlock does nothing (it never removes a lock it no longer holds).

Parameters

  • lk {Lock} - the lock returned by lock

Returns {null}

flatdb.update(path as string, transform as func)

Run a locked read-modify-write against the store at path: take the write lock, open the current document under it, apply transform, save, and release the lock - on every exit path (via defer). This is the safe way to update a store that concurrent writers (separate processes, or spawned tasks) may also write: because the read and the write are one locked span, no update is lost and save cannot conflict. transform is a func value func(db as DB) -> DB - edit the DB it receives with set / append / remove and return the result.

Parameters

  • path {string} - the store's backing file path
  • transform {func} - func(db as DB) -> DB, the edit to apply under the lock

Returns {DB} - the saved DB (its version is the freshly-written file's)

Throws

  • {Error} - kind "flatdb" if the lock cannot be taken, or on a write failure

Structs

flatdb.DB

The value the caller holds: the file path plus the decoded document. A module holds no mutable state and spawn deep-copies scope, so a store cannot be a shared open connection - it is a value. Mutating verbs return a fresh DB; save is the only side effect.

FieldTypeDescription
pathstringthe backing file path (empty for a read-only DB from openString)
datajson.Valuethe decoded in-memory document
versionstringan opaque token of the file's on-disk state at open, so save can detect a concurrent write (empty when there was no backing file)

flatdb.Lock

A held advisory lock, returned by lock and passed to unlock. Carries the lock file path and this holder's unique id, so unlock removes only a lock this holder still owns (never one a stale-break has handed to another writer).

FieldTypeDescription
pathstringthe lock file path
holderstringthis holder's unique id ("<uuid> <unix-seconds>")