Configs should be easy.
Fast, Bun-first configuration SDK for AI agent frameworks, CLI apps, and applications — backed by SQLite with in-memory caching, Zod validation, and optional encryption.
- AI agent frameworks — persistent agent settings, model preferences, API key management, and runtime configuration
- CLI applications — user preferences, saved credentials, and tool configuration that persists across sessions
- Desktop & server apps — application settings with schema validation, safe migrations, and multi-process access
- SQLite-backed — ACID transactions, WAL mode, multi-process safe
- In-memory cache — zero disk I/O on reads, configurable write-behind flush
- Zod validation — built-in schema validation with pluggable validator interface
- Dot-notation access —
config.get("ui.sidebar.width")just works - Async migrations — version-based with full transaction rollback on failure
- Optional encryption — AES-256-GCM via
@wgtechlabs/secrets-engineor bring your own - Change events — watch specific keys or the entire store
- Bun-first, Node.js compatible — uses
bun:sqlitenatively,better-sqlite3on Node.js
bun add @wgtechlabs/config-engineFor Node.js, also install the SQLite driver:
npm install @wgtechlabs/config-engine better-sqlite3import { ConfigEngine } from "@wgtechlabs/config-engine";
const config = await ConfigEngine.open({
projectName: "my-app",
defaults: {
theme: "dark",
fontSize: 14,
sidebar: { width: 300, visible: true },
},
});
// Read (zero disk I/O — reads from in-memory cache)
config.get("theme"); // "dark"
config.get("sidebar.width"); // 300 (dot-notation)
// Write
config.set("fontSize", 16);
config.set({ theme: "light", fontSize: 12 });
config.set("sidebar.visible", false);
// Check & delete
config.has("theme"); // true
config.delete("theme");
// Iterate
for (const [key, value] of config) {
console.log(key, value);
}
// Close when done
config.close();import { ConfigEngine } from "@wgtechlabs/config-engine";
import { z } from "zod";
const config = await ConfigEngine.open({
projectName: "my-app",
defaults: { theme: "dark", fontSize: 14 },
schema: z.object({
theme: z.enum(["light", "dark"]),
fontSize: z.number().min(8).max(72),
}),
});
config.set("fontSize", 16); // OK
config.set("fontSize", 200); // Throws ValidationErrorBring your own validation library (Valibot, AJV, etc.):
import { ConfigEngine, type Validator } from "@wgtechlabs/config-engine";
const myValidator: Validator<MyConfig> = {
validate(data) {
// Your validation logic
if (isValid(data)) {
return { success: true, data: data as MyConfig };
}
return { success: false, errors: ["Validation failed"] };
},
};
const config = await ConfigEngine.open({
projectName: "my-app",
validator: myValidator,
});Robust, async-capable migration system with full SQLite transaction rollback:
const config = await ConfigEngine.open({
projectName: "my-app",
projectVersion: "3.0.0",
migrations: [
{
version: "2.0.0",
up(ctx) {
// Rename a key
const old = ctx.get("oldSetting");
if (old !== undefined) {
ctx.set("newSetting", old);
ctx.delete("oldSetting");
}
},
},
{
version: "3.0.0",
async up(ctx) {
// Async migrations supported natively
const data = ctx.get<string>("rawData");
if (data) {
ctx.set("processedData", data.toUpperCase());
ctx.delete("rawData");
}
},
},
],
beforeEachMigration({ fromVersion, toVersion }) {
console.log(`Migrating ${fromVersion} → ${toVersion}`);
},
});If any migration throws, all changes are rolled back automatically.
Optional encryption using @wgtechlabs/secrets-engine for machine-bound key management:
bun add @wgtechlabs/secrets-engineconst config = await ConfigEngine.open({
projectName: "my-app",
encryptionKey: "my-app.config.key", // Key name in secrets-engine
});
// All values are AES-256-GCM encrypted at rest
config.set("apiToken", "sk-secret-value");Or bring your own encryptor:
import { ConfigEngine, type Encryptor } from "@wgtechlabs/config-engine";
const myEncryptor: Encryptor = {
async encrypt(plaintext) { /* ... */ },
async decrypt(ciphertext) { /* ... */ },
};
const config = await ConfigEngine.open({
projectName: "my-app",
encryptionKey: myEncryptor,
});// Watch a specific key
const unsubscribe = config.onDidChange("theme", (newValue, oldValue) => {
console.log(`Theme changed: ${oldValue} → ${newValue}`);
});
// Watch any change
config.onDidAnyChange((newStore, oldStore) => {
console.log("Config updated");
});
// Stop watching
unsubscribe();Control when writes hit disk:
// Immediate — sync write on every .set() (safest)
const config = await ConfigEngine.open({
projectName: "my-app",
flushStrategy: "immediate",
});
// Batched — debounced microtask flush (default, best perf)
const config = await ConfigEngine.open({
projectName: "my-app",
flushStrategy: "batched",
});
// Manual — you control when to flush
const config = await ConfigEngine.open({
projectName: "my-app",
flushStrategy: "manual",
});
config.set("key", "value");
await config.flush(); // Explicit flushSQLite database files are stored in platform-appropriate directories:
| OS | Default Path |
|---|---|
| macOS | ~/Library/Preferences/<projectName>/config.db |
| Windows | %APPDATA%/<projectName>/config.db |
| Linux | ~/.config/<projectName>/config.db |
Override with cwd:
const config = await ConfigEngine.open({
projectName: "my-app",
cwd: "/custom/path", // → /custom/path/config.db
});Async factory method. Returns Promise<ConfigEngine<T>>.
| Option | Type | Default | Description |
|---|---|---|---|
projectName |
string |
required | App identifier for path resolution |
projectVersion |
string |
— | Required when migrations is set |
cwd |
string |
OS config dir | Override config directory |
configName |
string |
"config" |
Database file name (without .db) |
defaults |
Partial<T> |
— | Default values, merged on first open |
schema |
ZodSchema<T> |
— | Zod schema for validation |
validator |
Validator<T> |
— | Custom validator (overrides schema) |
encryptionKey |
string | Encryptor |
— | Encryption key name or custom encryptor |
migrations |
MigrationDefinition[] |
— | Ordered migration definitions |
beforeEachMigration |
Function |
— | Hook before each migration |
afterEachMigration |
Function |
— | Hook after each migration |
flushStrategy |
FlushStrategy |
"batched" |
Write-behind strategy |
accessPropertiesByDotNotation |
boolean |
true |
Enable dot-notation access |
watch |
boolean |
false |
Watch for external changes |
clearInvalidConfig |
boolean |
false |
Clear store on validation/decrypt failure |
| Method | Description |
|---|---|
.get(key, defaultValue?) |
Get a value (dot-notation supported) |
.set(key, value) |
Set a single value |
.set(object) |
Set multiple values |
.has(key) |
Check if a key exists |
.delete(key) |
Remove a key |
.clear() |
Clear all values, restore defaults |
.reset(...keys) |
Reset specific keys to defaults |
.onDidChange(key, cb) |
Watch a key, returns unsubscribe |
.onDidAnyChange(cb) |
Watch all changes, returns unsubscribe |
.flush() |
Flush pending writes (async) |
.flushSync() |
Flush pending writes (sync) |
.close() |
Close the engine |
| Property | Type | Description |
|---|---|---|
.store |
T |
Get/set the entire config object |
.size |
number |
Number of top-level entries |
.path |
string |
Absolute path to the database file |
MIT © WGTech Labs