Documentation
¶
Overview ¶
Package fado is the public Go API for embedding FadoDB.
Nothing is implemented yet. The intended surface is small: open a database, define classes, create and change documents, request transitions, query the document tables, and subscribe to changes. See docs/architecture.md.
Package fado is the Go API of FadoDB: open a database file; define schemas, classes and explicit tables; create, change, read and delete documents as commits with recalculation, typed history and an event log; attach files; filter documents with a formula and run read-only SQL over the views; redefine a class as a new version that touches no document, and migrate live documents on purpose (docs/architecture.md, "Go API").
The embedder supplies the principal on every call (ADR-0027), may give every mutating call an idempotency key and a business date (ADR-0026), and can run a change, or a draft class, through the commit pipeline without writing anything (ADR-0025). A document moves through its class's statechart with Transition, and Transitions says which moves are open (ADR-0040); a preview, a listing or a read may be asked at a moment the caller chooses with At, and a preview reports the moment it ran under (ADR-0042). The principal decides what a call may set and sees: the rights table and the per-transition rules are enforced on every call, a refusal being forbidden and a hidden variable absent (ADR-0041). Not here yet: rules and QUERY (Phase 4), the class-level access rules compiled to row predicates (Phase 5) and the server (Phase 6).
Example ¶
The invoice of docs/architecture.md, as far as this build takes it: a class with defaults, objects, lines and auto formulas; a document created, changed, previewed and read back with its history.
package main
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/MojoHand70/fadodb/fado"
)
func main() {
tmp, err := os.MkdirTemp("", "fado-example")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmp)
db, err := fado.Open(filepath.Join(tmp, "example.fado"), fado.Options{
Now: func() time.Time { return time.Date(2026, 11, 8, 12, 0, 0, 0, time.UTC) },
})
if err != nil {
panic(err)
}
defer db.Close()
ctx := context.Background()
_, err = db.DefineClass(ctx, []byte(`{
"name": "Invoice",
"title": "\"INV-\" & number",
"variables": {
"number": {"type": "text", "required": true},
"customer": {"type": "ref", "class": "Customer"},
"issued": {"type": "date", "default": "TODAY()"},
"due": {"type": "date", "default": "ADDDAYS(issued, 30)"},
"owner": {"type": "text", "default": "USER()"},
"discount": {"type": "decimal", "scale": 2, "default": "0"},
"net": {"type": "money", "scale": 2},
"shipping": {"type": "object", "schema": "Address"},
"lines": {"type": "rows", "schema": {"sku": "text", "qty": "number", "price": "decimal(2)"}},
"total": {"formula": "ROUND(net * (1 - discount), 2)"},
"ship_city": {"formula": "shipping.city"}
},
"states": {"initial": "draft", "list": ["draft", "approved", "paid"]}
}`))
if err != nil {
panic(err)
}
alice := fado.Principal{ID: "alice", Groups: []string{"sales"}}
values := fado.Values{
"number": "2026-0042",
"customer": "Customer:42",
"net": "100.00 EUR",
"shipping": fado.Values{"line1": "Długa 1", "city": "Gdańsk", "country": "PL"},
"lines": []fado.Values{{"sku": "A1", "qty": 2, "price": "9.50"}},
}
doc, err := db.Create(ctx, "Invoice", values, fado.As(alice), fado.Key("ksef-2026-0042"), fado.WithID("inv_42"))
if err != nil {
panic(err)
}
fmt.Println(doc.Title, doc.State, doc.Version, doc.Values["issued"], doc.Values["due"], doc.Values["owner"], doc.Values["total"], doc.Values["ship_city"])
// The same command with the same key is the same commit, not a second
// document.
again, err := db.Create(ctx, "Invoice", values, fado.As(alice), fado.Key("ksef-2026-0042"), fado.WithID("inv_42"))
fmt.Println("replayed:", err == nil && again.Version == doc.Version)
// A change recalculates what depends on it and is a new version.
doc, _ = db.Set(ctx, "Invoice", "inv_42", fado.Values{"discount": "0.10"}, fado.As(alice))
fmt.Println(doc.Version, doc.Values["total"])
// A preview computes and writes nothing.
preview, _ := db.Preview(ctx, "Invoice", "inv_42", fado.Values{"discount": "0.50"}, "", fado.As(alice))
current, _ := db.Get(ctx, "Invoice", "inv_42")
fmt.Println("preview:", preview.Document.Values["total"], "stored:", current.Values["total"])
// A refusal names the variable and a stable code.
_, err = db.Set(ctx, "Invoice", "inv_42", fado.Values{"total": "1.00 EUR"})
var e *fado.Error
if errors.As(err, &e) {
fmt.Println(e.Code, e.Variable)
}
// Every version is in the history, with who committed it.
history, _ := db.History(ctx, "Invoice", "inv_42")
for _, h := range history {
fmt.Println("version", h.Version, h.Values["total"], h.ActorID, h.ValidTo == 0)
}
// A filter is a formula over the class's variables; SQL runs over the
// views.
open, _ := db.Query(ctx, fado.Filter{Class: "Invoice", Where: "state = \"draft\" AND total > 50.00", Fields: []string{"number", "total"}})
fmt.Println("filtered:", len(open), open[0].Values["number"], open[0].Values["total"])
rows, _ := db.SQL(ctx, `SELECT count(*) AS n, sum(total) AS cents FROM Invoice`, nil)
fmt.Println("sql:", rows.Columns, rows.Rows[0])
// A definition's problems come back at once, each with a place.
_, err = db.DefineClass(ctx, []byte(`{"name": "Receipt", "variables": {"amount": {"type": "money", "default": "1"}, "paid": {"type": "bool", "formula": "amount"}}}`))
var diags fado.Diagnostics
if errors.As(err, &diags) {
for _, d := range diags {
fmt.Println(d.Path, d.Code)
}
}
}
Output: INV-2026-0042 draft 1 2026-11-08 2026-12-08 alice 100.00 EUR Gdańsk replayed: true 2 90.00 EUR preview: 50.00 EUR stored: 90.00 EUR read_only total version 1 100.00 EUR alice false version 2 90.00 EUR alice true filtered: 1 2026-0042 90.00 EUR sql: [n cents] [1 9000] /variables/amount/default type_mismatch /variables/paid/formula type_mismatch
Index ¶
- type BlobStore
- type Class
- type DB
- func (db *DB) CheckBlobs(ctx context.Context) (mismatched []string, checked int, err error)
- func (db *DB) Class(name string) (*Class, bool)
- func (db *DB) Classes() []string
- func (db *DB) Close() error
- func (db *DB) Count(ctx context.Context, f Filter, opts ...Option) (int64, error)
- func (db *DB) Create(ctx context.Context, class string, values Values, opts ...Option) (*Document, error)
- func (db *DB) CreateMany(ctx context.Context, class string, docs []Values, opts ...Option) ([]*Document, error)
- func (db *DB) DefineClass(ctx context.Context, definition []byte) (*Class, error)
- func (db *DB) DefineSchema(ctx context.Context, definition []byte) (*Schema, error)
- func (db *DB) DefineTable(ctx context.Context, definition []byte) (*Table, error)
- func (db *DB) Delete(ctx context.Context, class, id string, opts ...Option) error
- func (db *DB) Events(ctx context.Context, class, id string, opts ...Option) ([]*Event, error)
- func (db *DB) Get(ctx context.Context, class, id string, opts ...Option) (*Document, error)
- func (db *DB) History(ctx context.Context, class, id string, opts ...Option) ([]*HistoryRow, error)
- func (db *DB) ImportRows(ctx context.Context, table string, rows []Values, opts ...Option) (int, error)
- func (db *DB) Migrate(ctx context.Context, class string, opts ...Option) (int, error)
- func (db *DB) OpenBlob(ctx context.Context, hash string, opts ...Option) (io.ReadCloser, int64, error)
- func (db *DB) OpenFile(ctx context.Context, class, id, hash string, opts ...Option) (io.ReadCloser, *FileInfo, error)
- func (db *DB) Preview(ctx context.Context, class, id string, values Values, transition string, ...) (*Preview, error)
- func (db *DB) PreviewDraft(ctx context.Context, definition []byte, id string, values Values, ...) (*Preview, error)
- func (db *DB) PutBlob(ctx context.Context, name, mediaType string, r io.Reader, opts ...Option) (Values, error)
- func (db *DB) Query(ctx context.Context, f Filter, opts ...Option) ([]*Document, error)
- func (db *DB) SQL(ctx context.Context, statement string, args []any, opts ...Option) (*Rows, error)
- func (db *DB) Set(ctx context.Context, class, id string, values Values, opts ...Option) (*Document, error)
- func (db *DB) Table(name string) (*Table, bool)
- func (db *DB) TableRows(ctx context.Context, table string) ([]Values, error)
- func (db *DB) Tables() []string
- func (db *DB) Transition(ctx context.Context, class, id, name string, values Values, opts ...Option) (*Document, error)
- func (db *DB) Transitions(ctx context.Context, class, id string, opts ...Option) ([]TransitionInfo, error)
- func (db *DB) ValidateClass(definition []byte) Diagnostics
- func (db *DB) Version(ctx context.Context, class, id string, version int64, opts ...Option) (*HistoryRow, error)
- type Diagnostic
- type Diagnostics
- type Document
- type Error
- type Event
- type FileInfo
- type Filter
- type HistoryRow
- type Option
- type Options
- type Preview
- type Principal
- type Rows
- type Schema
- type Table
- type TransitionInfo
- type Values
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BlobStore ¶
BlobStore holds the bytes of attachments by content hash (ADR-0033); an embedder may supply its own in Options.Blobs.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is an open database.
func (*DB) CheckBlobs ¶
CheckBlobs recomputes the hash of every held blob and returns the ones whose bytes do not match, and how many it checked.
func (*DB) Create ¶
func (db *DB) Create(ctx context.Context, class string, values Values, opts ...Option) (*Document, error)
Create makes a document with the values given, the defaults of the inputs not given and every auto variable computed, as version 1.
func (*DB) CreateMany ¶
func (db *DB) CreateMany(ctx context.Context, class string, docs []Values, opts ...Option) ([]*Document, error)
CreateMany makes documents in one transaction, one commit each, all or none: the bulk load path (ADR-0035). A batch takes no key and no id.
func (*DB) DefineClass ¶
DefineClass compiles a class definition in JSON, creates its tables and views and stores it. The error is Diagnostics when the definition is refused.
func (*DB) DefineSchema ¶
DefineSchema compiles a named schema in JSON and stores it.
func (*DB) DefineTable ¶
DefineTable compiles an explicit table definition in JSON, creates the table and its view and stores it; formulas read it through LOOKUP.
func (*DB) Delete ¶
Delete removes a document's current row and appends the event; its history stays.
func (*DB) ImportRows ¶
func (db *DB) ImportRows(ctx context.Context, table string, rows []Values, opts ...Option) (int, error)
ImportRows writes rows into a table, each a map of column to value, replacing a row whose key exists; with Replace, the table is emptied first. It returns the number of rows written.
func (*DB) Migrate ¶
Migrate brings every live document of a class that an older version computed to the current version, as commits in batches (ADR-0039). It returns how many documents it moved; terminal documents stay.
func (*DB) OpenBlob ¶
func (db *DB) OpenBlob(ctx context.Context, hash string, opts ...Option) (io.ReadCloser, int64, error)
OpenBlob opens a blob by hash alone, for an administrator.
func (*DB) OpenFile ¶
func (db *DB) OpenFile(ctx context.Context, class, id, hash string, opts ...Option) (io.ReadCloser, *FileInfo, error)
OpenFile opens a file a document holds, by the document and the hash, at the current version or the one AtVersion names.
func (*DB) Preview ¶
func (db *DB) Preview(ctx context.Context, class, id string, values Values, transition string, opts ...Option) (*Preview, error)
Preview runs a create (empty id), a set or a transition through the commit pipeline and writes nothing (ADR-0025): the document as it would be, the states the command would visit and the transitions open from the last of them.
func (*DB) PreviewDraft ¶ added in v0.3.0
func (db *DB) PreviewDraft(ctx context.Context, definition []byte, id string, values Values, transition string, opts ...Option) (*Preview, error)
PreviewDraft compiles a class definition for this call alone and runs a create (empty id) or a set of an existing document of the stored class of the same name through the commit pipeline under it, writing and storing nothing (ADR-0025): a designer tries a draft on real documents, terminal ones included.
func (*DB) PutBlob ¶
func (db *DB) PutBlob(ctx context.Context, name, mediaType string, r io.Reader, opts ...Option) (Values, error)
PutBlob stores the bytes of a file and returns the File fact to commit in an object<File> variable or a rows<File> element (ADR-0033).
func (*DB) Query ¶
Query reads the documents of a class that match a filter, in its order, at most its limit, with the fields it names.
func (*DB) SQL ¶
SQL runs one read-only SELECT over the views with positional parameters; at most 10,000 rows come back.
func (*DB) Set ¶
func (db *DB) Set(ctx context.Context, class, id string, values Values, opts ...Option) (*Document, error)
Set assigns values to a document and recalculates what depends on them, as a new version; a set that changes nothing commits nothing.
func (*DB) Transition ¶ added in v0.3.0
func (db *DB) Transition(ctx context.Context, class, id, name string, values Values, opts ...Option) (*Document, error)
Transition moves a document along the named transition of its class, with the assignments in values applied in the same commit (ADR-0040); values may be nil. Every eventless transition that then fires is a further commit in the same transaction. A transition that cannot happen is refused with unknown_transition, wrong_state, guard_false, too_many_transitions or terminal, before anything is written.
func (*DB) Transitions ¶ added in v0.3.0
func (db *DB) Transitions(ctx context.Context, class, id string, opts ...Option) ([]TransitionInfo, error)
Transitions lists the named transitions leaving the document's state, each with whether the principal may take it now (ADR-0040). It writes nothing.
func (*DB) ValidateClass ¶
func (db *DB) ValidateClass(definition []byte) Diagnostics
ValidateClass reports what DefineClass would refuse, storing nothing.
type Diagnostic ¶
type Diagnostic = model.Diagnostic
Diagnostic is one finding: a code, the JSON pointer of the element, the position in a formula, a message and a hint.
type Diagnostics ¶
type Diagnostics = model.Diagnostics
Diagnostics are the findings of a class or schema definition; the error DefineClass, DefineSchema and ValidateClass return.
type Filter ¶
Filter is a query over a class: a where formula, an order, a limit, an offset and the fields to return (ADR-0038).
type HistoryRow ¶
type HistoryRow = store.HistoryRow
HistoryRow is one committed version with its validity and actor.
type Option ¶
Option configures one call: As, Key, BusinessDate, WithID.
func At ¶ added in v0.3.0
At runs a call that reads or previews at the moment (ADR-0042): NOW() is t and TODAY() its UTC day in every formula the call evaluates, and a preview reports it as Now. A zero t is the database's clock. A command that writes refuses it with not_allowed: a change is previewed at a moment, or made in a copy of the database opened with Options.Now set.
func BusinessDate ¶
BusinessDate gives the commit's event its business date (ADR-0026), a calendar day.
type Preview ¶
Preview is what a commit would do (ADR-0025): the document, the errors per variable, the states visited and the transitions open.
type TransitionInfo ¶ added in v0.3.0
type TransitionInfo = store.TransitionInfo
TransitionInfo is one named transition leaving a document's state and whether it may be taken now (ADR-0040).