Config
Schema-validated environment configuration. Operators read process.env throughout their app; a typo in the key name OR a value in the wrong shape (port="abc", flag="yas") surfaces three days later as a mysterious 500. b.config.create validates env at boot through b.safeSchema so the app refuses to start with broken config — the throw happens at create() time, not at the first request that touches the broken value.
b.config.coerce.number() and b.config.coerce.boolean() wrap schema leaves with the env-friendly preprocessors most operators want (env values are always strings at the source). loadDbBacked composes create with periodic DB-row polling so a row update in _blamejs_config_overrides surfaces without restart, and falls back to the last-good value on validation failure.
b.config.create(opts) #
{
schema: b.safeSchema instance (required; built via b.safeSchema.object({...})),
env: object (env bag; default process.env),
redactKeys: Array (keys masked by `.redacted()` for log output),
}
Validate env against a b.safeSchema shape and return a frozen config handle (value / get / has / redacted / subscribe / reload). Throws ConfigError synchronously when validation fails — the operator sees broken config at boot rather than at the first request that touches the value. The handle's reload(overlay) applies a new env-shaped overlay on top of the validated baseline, notifies subscribers on success, and falls back to the prior value on failure.
var s = b.safeSchema;
var cfg = b.config.create({
schema: s.object({
NODE_ENV: s.enum_(["development", "test", "production"]),
PORT: b.config.coerce.number().default(3000),
}),
env: { NODE_ENV: "production", PORT: "8080" },
redactKeys: [],
});
cfg.value.NODE_ENV; // → "production"
cfg.value.PORT; // → 8080 (Number, not "8080")
cfg.has("PORT"); // → true
b.config.loadDbBacked(opts) #
{
schema: b.safeSchema instance (required),
env: object (env baseline; default process.env),
redactKeys: Array,
fetchRows: async () => Array<{ key: string, value: string }> (required),
intervalMs: number (positive finite poll interval),
transformValue: (row) => string | Promise (optional per-row
transform — receives `{ key, value, ...rest }` so the
row can carry envelope metadata; returns the value
that flows into the schema. Common shape: unseal a
`b.vault`-sealed ciphertext column before validation.
Rows whose transform throws or returns a non-string
are skipped with a `config.reload.failed` audit so a
single bad row never crashes the poller),
audit: boolean (default true; set false to silence the
per-poll config.reload.* audit emissions),
}
Compose b.config.create with a periodic DB-row fetch. Operators keep canonical config values in _blamejs_config_overrides(key TEXT PRIMARY KEY, value TEXT); this helper polls every intervalMs, applies the rows as an overlay via the underlying handle's reload, and re-validates. Reload failures emit a config.reload.failed audit row but DO NOT clobber the previous value — the running app stays on the last-good config.
Returns immediately with a synchronous handle, but kicks off one immediate hydration tick on construction so the first DB read happens at t=0 rather than t=intervalMs. Callers that need to wait for first-data-applied can await handle.hydrated before the app starts serving traffic; the Promise resolves after the first tick settles (success OR audit-on-failure path) and never rejects, so the boot path never deadlocks on a temporarily-unreachable DB.
The returned handle is the same shape as create() plus: - .hydrated — Promise.refresh()— run one tick on demand (save-triggered reload); returns Promise.stop() — halts the poller
Three tiers of precedence (highest wins): the DB-row overlay resolved at each _tick > the opts.env baseline > defaults declared on the schema (s.string().default(...) and friends). The .subscribe(fn) callback registered through create() fires synchronously inside every successful reload — operators reach for it to invalidate caches, recompute derived state, or hot-rebuild middleware that closed over the previous config value.
var s = b.safeSchema;
var cfg = b.config.loadDbBacked({
schema: s.object({
FEATURE_X: b.config.coerce.boolean().default(false),
}),
env: { FEATURE_X: "false" },
fetchRows: async function () {
return [{ key: "FEATURE_X", value: "true" }];
},
intervalMs: 60 * 1000,
});
cfg.value.FEATURE_X; // → false (until first poll tick lands)
cfg.stop(); // halt the poller on shutdown
// Sealed values — column stores `b.vault.seal(plain)` ciphertext.
var cfg = b.config.loadDbBacked({
schema: s.object({ STRIPE_SECRET: s.string() }),
fetchRows: async function () {
return await db.all("SELECT key, sealed FROM _config WHERE sealed IS NOT NULL");
},
transformValue: function (row) {
return b.vault.unseal(row.sealed).toString("utf8");
},
intervalMs: 30 * 1000,
});
// Save-triggered reload — admin UI writes a row, fires refresh()
// so the new value is active immediately without waiting for
// intervalMs. cfg.subscribe(...) sees the change inline.
var cfg = b.config.loadDbBacked({
schema: s.object({ FEATURE_X: b.config.coerce.boolean().default(false) }),
fetchRows: async function () { return await db.all("SELECT key, value FROM _config"); },
intervalMs: 5 * 60 * 1000, // safety-net interval
});
await cfg.hydrated; // boot path waits
cfg.subscribe(function (next) { cache.invalidate(); });
adminApp.post("/settings", async function (req, res) {
await db.run("INSERT OR REPLACE INTO _config(key,value) VALUES (?,?)",
req.body.key, req.body.value);
await cfg.refresh(); // active immediately
res.json({ ok: true });
});
Last updated 2026-08-08T16:39:49.652Z by seeder.