Configuration
Configure Stabilize ORM for your database
Database Configuration
Stabilize supports PostgreSQL, MySQL, MariaDB, SQLite, and SQL Server, plus a MongoDB document backend. Create a DBConfig object:
import { DBType, type DBConfig } from "stabilize-orm";
// PostgreSQLconst pgConfig: DBConfig = { type: DBType.Postgres, connectionString: process.env.DATABASE_URL || "postgresql://user:password@localhost:5432/mydb", retryAttempts: 3, retryDelay: 1000,};
// MySQL / MariaDBconst mysqlConfig: DBConfig = { type: DBType.MySQL, connectionString: process.env.DATABASE_URL || "mysql://user:password@localhost:3306/mydb", retryAttempts: 3, retryDelay: 1000,};
// SQL Serverconst mssqlConfig: DBConfig = { type: DBType.MSSQL, connectionString: "Server=localhost,1433;Database=mydb;User Id=sa;Password=Your_password123;TrustServerCertificate=true", retryAttempts: 3, retryDelay: 1000,};
// SQLite (great for development and testing)const sqliteConfig: DBConfig = { type: DBType.SQLite, connectionString: "./data/app.db", retryAttempts: 3, retryDelay: 1000,};DBType is a string enum, so the literal works wherever the enum does — type: "postgres" is the same as type: DBType.Postgres. Only type and connectionString are required; the retry fields all have defaults.
The SQL Server pool is opened on the first query rather than in the constructor, because mssql's ConnectionPool.connect() is asynchronous and the constructor is not. See SQL Server for what else the dialect changes.
ORM Initialization
Create a Stabilize instance with database, cache, and logger configuration:
import { Stabilize, type CacheConfig, type LoggerConfig, LogLevel } from "stabilize-orm";import dbConfig from "../config/database";
const cacheConfig: CacheConfig = { enabled: false, // Turn caching on ttl: 60, // Cache TTL in seconds redisUrl: process.env.REDIS_URL, // Omit to cache in process instead cachePrefix: "myapp:", // Key prefix for namespacing strategy: "cache-aside", // "cache-aside" or "write-through" maxEntries: 1000, // In-process bound; ignored with redisUrl};
const loggerConfig: LoggerConfig = { level: LogLevel.Info, // Debug, Info, Warn, or Error filePath: "logs/stabilize.log", // omit to log to the console only maxFileSize: 5 * 1024 * 1024, // 5MB (default 1MB) maxFiles: 3, // default 3};
export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);All three arguments after config are optional. The default cacheConfig is { enabled: false, ttl: 60 }, so caching is off unless you turn it on.
redisUrl chooses the backend rather than enabling one: with it, entries are cached in Redis and shared by every process pointing at the same server. Without it they are cached in the process itself, which needs no server to run but is not shared between processes and is lost on restart — see Caching. Either way, turning caching on now always caches something.
LogLevel is a numeric enum, not a string one — Debug = 0, Info = 1, Warn = 2, Error = 3. A message is logged when its level is less than or equal to the configured level, so LogLevel.Info keeps info, warn and error and drops debug.
Sharing an Existing Client
A fourth argument accepts a DBClient you built yourself, and the ORM uses it instead of opening its own:
import { Stabilize, DBClient } from "stabilize-orm";import dbConfig from "../config/database";
// One pool for the whole process, shared by several Stabilize instances.const client = new DBClient(dbConfig);
export const orm = new Stabilize(dbConfig, { enabled: false, ttl: 60 }, {}, client);export const reporting = new Stabilize(dbConfig, { enabled: false, ttl: 60 }, {}, client);Passing a client disables caching
When existingClient is supplied, the cacheConfig argument is ignored entirely and the cache is set to null — even if enabled: true. Sharing a connection pool across instances would otherwise mean sharing a cache namespace with no way to invalidate one instance's writes without flushing the other's. If you need caching, let the ORM own its client.
close() closes the shared client
orm.close() calls close() on whatever client it holds, including one you passed in. Nothing counts how many instances are using it, so in the example above orm.close() shuts the pool down for reporting as well. Close the shared client exactly once, at process shutdown, rather than closing each instance.
Environment Variables
Store sensitive data in environment variables. Never commit credentials:
DATABASE_URL=postgresql://user:password@localhost:5432/mydbREDIS_URL=redis://localhost:6379CACHE_ENABLED=falseThree variables are read by the ORM itself rather than by your config: ORM_ENCRYPTION_KEY supplies the key for column encryption, ORM_ENCRYPTION_KEY_FILE names the file to read that key from instead (default .stabilize/encryption.key), and ORM_ENCRYPTION_KEYS_OLD lists retired keys that still decrypt. If no key is configured at all, one is generated into the key file on first use and a warning is emitted — so keep that file out of version control rather than committing it by accident.
DBConfig Options
type- Database type:DBType.Postgres,DBType.MySQL,DBType.SQLite,DBType.MSSQL, orDBType.MongoDB. The underlying string values are"postgres","mysql","sqlite","mssql"and"mongodb". The first four are SQL dialects; see MongoDB for where the document backend differs.connectionString- Connection string, or a file path for SQLiteretryAttempts- Number of retry attempts on query failure (default: 3)retryDelay- Base delay between retries in ms (default: 1000)maxJitter- Maximum random jitter added to retry delay in ms (default: 100)
Retries apply to read-only statements only — a statement whose text begins with SELECT, PRAGMA, SHOW, EXPLAIN or VALUES. A failed INSERT or UPDATE is reported immediately: it may already have been applied, so replaying it is not safe in general. See Retry and Pooling.
LoggerConfig Options
level-LogLevel.Debug,LogLevel.Info,LogLevel.WarnorLogLevel.Error(default:Info)filePath- Where to write the log file. When it is omitted there is no file logging at all; messages go to the console.maxFileSize- Size in bytes at which the file is rotated (default: 1MB, i.e.1 * 1024 * 1024)maxFiles- How many rotated files to keep (default: 3)
Rotation is size-triggered, not time-triggered: the file is checked before each write and rolled over once it exceeds maxFileSize. The oldest file is deleted, so total disk use is bounded by roughly maxFileSize * (maxFiles + 1).