Configuration
What forgedb.toml is, how it is discovered, the precedence rules, and the binding-time tier model that governs every knob.
ForgeDB reads one project config file, forgedb.toml, at generate time. It holds
schema-blind knobs: deployment and behavior settings that are not part of your .forge
schema. The schema describes your data shape; forgedb.toml describes how the generated
code and the running process behave.
Config is schema-blind
Nothing in forgedb.toml is per-model or field-aware. If a setting depended on a
specific model or field, it would be a .forge directive, not a config key. Two apps
with entirely different schemas could share the same forgedb.toml verbatim.
Discovery#
A config is found by walking up from the schema, not from where you are standing.
ForgeDB takes the nearest forgedb.toml at or above the schema's directory; with none
anywhere, built-in defaults apply silently.
forgedb generate all # nearest config at or above ./schema.forge
forgedb generate all --schema apps/api/schema.forge # nearest config at or above apps/api/
forgedb generate all -c prod.toml # explicit path (also --config)Starting at the schema is what lets one config govern several apps, and it is why cd
never changes the answer: which config applies is a fact about the tree, not about the
invocation.
The walk stops at the first of a repository root, your home directory, or the
filesystem root — so a stray ~/forgedb.toml cannot quietly capture every project on the
machine.
An explicit -c/--config path is an outright override of the knobs: no walk, and a path
that is missing or contains invalid TOML is a hard error. A missing forgedb.toml is not
(defaults apply). It does not override which project a schema belongs to — see
[project].
Relative paths resolve against the schema
A relative output directory is resolved against the schema's directory, not the
config's — the built-in generated default included. Under one shared root config,
output = "generated" therefore means apps/api/generated and apps/web/generated — a
per-app pattern, rather than one directory both apps overwrite each other in. An explicit
--output flag is the invocation's own word and is used verbatim.
Unknown keys are errors#
An unrecognized table, or an unrecognized key inside a known table, fails with the line it
appears on. This is deliberately not forward-compatible: a knob an older CLI does not
know would otherwise read as applied and silently not be, so fsync = "never" would stay
always and you would find out from the generated bytes.
error: forgedb.toml:
TOML parse error at line 4, column 2
|
4 | [projekt]
| ^^^^^^^
unknown field `projekt`, expected one of `project`, `generate`, `tenant`, `auth`, `runtime`, `storage`, `transaction`, `server`, `wasm`, `placement`Precedence#
For any setting, the highest-priority source wins:
- An explicit CLI flag (
--output,--schema, …) - The value in the loaded
forgedb.toml - The built-in default
Binding-time tiers#
Every runtime/storage knob binds at one of three times. The tier tells you when a change takes effect and how baked-in it is.
| Tier | Name | Mechanism | When it binds |
|---|---|---|---|
| A | Generate-time specialization | Code is emitted or omitted (e.g. the replication broker, the auto-compaction trigger). The compiler optimizes around code that is not there. | forgedb generate |
| B | Baked const | Same code, a tailored number baked as a const (e.g. checkpoint interval, cascade depth, the fsync policy value). | forgedb generate |
| C | Process-start env | Read once at process start from a FORGEDB_* environment variable (host, port, data dir, JWT settings). | Server launch |
Tiers A and B bake into database.rs at forgedb generate — change them and regenerate.
Tier C is read fresh at each process start, so you change it per deployment without
regenerating. The [runtime] and [storage] tables are Tiers A/B; the [auth] table
bridges to Tier-C FORGEDB_* env at runtime (see tenant &
auth).
fsync is Tier B today
[storage].fsync = "never" bakes the FsyncPolicy value as a const, but the substrate
still matches it per-write at runtime — so the barrier syscall is still present in the
binary (a dead, value-known branch), not removed. True Tier-A elimination of the barrier
is a documented follow-up. See [storage].
The tables#
These are all ten tables forgedb.toml accepts. Any other is an error, so this list is
also the complete vocabulary.
| Table | Purpose | Page |
|---|---|---|
[project] | Project id, isolation, and how derived names are built. | [generate] & [project] |
[generate] | Output dir and which targets to emit. A schema names itself — there is no schema key. | [generate] & [project] |
[runtime] | Replication, changefeed capacity, cascade depth (Tier A/B). | [runtime] |
[storage] | fsync policy, WAL checkpoint, compaction (Tier A/B). | [storage] |
[transaction] | Optimistic-concurrency retry knobs (Tier B). | — |
[server] | Generated REST list-page limits and the /metrics endpoint (Tier A/B). | — |
[wasm] | Browser read-replica auto-commit debounce and frame ceiling (Tier B). | — |
[tenant] | The per-tenant data root. | [tenant] & [auth] |
[auth] | Verify-only JWT tenant guard settings (bridges to FORGEDB_*). | [tenant] & [auth] |
[placement] | Where an already-generated artifact lands. Changes no generated byte. | [placement] |
[toolchain] | Where the interpreter a non-Rust migration transform runs on lives, and which version will do. Location and version only — the language is derived from [generate].targets. | Migrations |
Defaults are byte-identical#
Generated database.rs from an empty [runtime]/[storage] config is byte-identical
to the fully-defaulted output, with one deliberate exception: [runtime].replication
defaults to off. An unused durable replication broker would pay a second F_FULLFSYNC
barrier per write for nothing, so it is omitted unless you opt in. See
[runtime].