Database
Database
Database — Database documentation for BCP Framework.
Last updated: 2026-08-29T17:13:55
Database
BCP 0.2.3 completes Database Platform v2 behind the server-only bcp/database entrypoint.
The public database facade is provider-neutral and currently includes built-in adapters for:
- MySQL,
- PostgreSQL,
- SQLite.
Applications can keep the same db.query(), db.execute(), db.transaction() and lifecycle API while selecting the provider through environment or explicit options.
MySQL
Install the optional driver:
npm install mysql2Environment:
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=bcp_app
DB_CONNECTION_LIMIT=10MySQL remains the default when no provider can be inferred.
Parameterized query:
import {
db,
} from "bcp/database";
const users =
await db.query(
"SELECT id, email FROM users WHERE active = ?",
[
1,
]
);PostgreSQL
Install the optional driver:
npm install pgBCP can infer PostgreSQL from a connection URL:
DATABASE_URL=postgresql://postgres:password@localhost:5432/bcp_appor use explicit fields:
DB_DRIVER=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=bcp_appPostgreSQL parameters use $1, $2, and later placeholders:
const users =
await db.query(
"SELECT id, email FROM users WHERE active = $1",
[
true,
]
);postgres and pg are accepted as environment aliases for the PostgreSQL driver and are normalized to postgresql.
SQLite
Install the optional driver:
npm install better-sqlite3Recommended environment:
DB_DRIVER=sqlite
DATABASE_URL=./data/bcp.sqliteBCP also recognizes :memory:, sqlite: / file: URLs and common .sqlite, .sqlite3, and .db file names.
Example:
import {
createDatabase,
} from "bcp/database";
const database =
createDatabase({
driver:
"sqlite",
database:
"./data/app.sqlite",
});SQLite operations are serialized by the built-in adapter so async transaction callbacks cannot interleave unrelated queries on the same native connection.
Query and execute
query<T>() is intended for row-returning statements:
interface UserRow {
id: number;
email: string;
}
const users =
await db.query<UserRow[]>(
"SELECT id, email FROM users"
);execute<T>() is intended for mutations and DDL:
const result =
await db.execute(
"DELETE FROM sessions WHERE expired_at < ?",
[
new Date(),
]
);Use the placeholder syntax of the active database provider. BCP does not rewrite application SQL between dialects.
Transactions
The transaction callback receives a provider-scoped TransactionDatabase:
await db.transaction(
async (transaction) => {
await transaction.execute(
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
[
100,
1,
]
);
await transaction.execute(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
[
100,
2,
]
);
}
);The example above uses MySQL/SQLite placeholders. PostgreSQL migrations and application statements should use $1, $2, and so on.
BCP commits when the callback resolves and rolls back when it throws.
Connection lifecycle
Database instances remain lazy by default. Importing bcp/database does not connect to a provider.
BCP 0.2.3 adds explicit lifecycle methods:
await db.connect();
// application work
await db.disconnect();close() remains available as the backward-compatible shutdown method and is equivalent to disconnect():
await db.close();After disconnecting, the next operation or connect() call creates a fresh adapter connection/pool.
If adapter initialization fails, BCP resets the pending lifecycle state so a later attempt can retry cleanly. Calling close() after a failed initialization is safe.
Custom database instance
import {
createDatabase,
} from "bcp/database";
export const reportingDb =
createDatabase({
driver:
"postgresql",
host:
"reporting-db.internal",
database:
"analytics",
connectionLimit:
5,
});Explicit options override environment values for that database instance.
Database adapter contract
Provider implementations use the shared adapter contract:
import type {
DatabaseAdapter,
TransactionDatabase,
} from "bcp/database";
const adapter: DatabaseAdapter = {
driver: "custom",
async connect() {},
async query<T>(sql, parameters) {
throw new Error("Not implemented");
},
async execute<T>(sql, parameters) {
throw new Error("Not implemented");
},
async transaction<T>(
callback: (
database: TransactionDatabase
) => Promise<T>
) {
throw new Error("Not implemented");
},
async disconnect() {},
};Inject an adapter directly or through a lazy factory:
export const customDb =
createDatabase({
adapter: () => adapter,
});The contract lets future providers integrate without changing application-facing database calls.
Migrations
Framework migration bookkeeping supports MySQL, PostgreSQL and SQLite in 0.2.3.
bcp db create create_users
bcp db migrate
bcp db status
bcp db rollbackBCP selects the internal migration-table dialect from the same active database environment. Migration files themselves remain normal provider SQL and are not automatically translated between SQL dialects.
Read more: Database Migrations
Server-only boundary
bcp/database is a server-only package export. Importing it into a browser bundle is blocked by the framework browser export boundary.
Provider drivers are optional and loaded lazily. Projects only need to install the driver they actually use.