Docs/Database/Database

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:

Bash
npm install mysql2

Environment:

env
DB_DRIVER=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=bcp_app
DB_CONNECTION_LIMIT=10

MySQL remains the default when no provider can be inferred.

Parameterized query:

TypeScript
import {
    db,
} from "bcp/database";

const users =
    await db.query(
        "SELECT id, email FROM users WHERE active = ?",
        [
            1,
        ]
    );

PostgreSQL

Install the optional driver:

Bash
npm install pg

BCP can infer PostgreSQL from a connection URL:

env
DATABASE_URL=postgresql://postgres:password@localhost:5432/bcp_app

or use explicit fields:

env
DB_DRIVER=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=bcp_app

PostgreSQL parameters use $1, $2, and later placeholders:

TypeScript
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:

Bash
npm install better-sqlite3

Recommended environment:

env
DB_DRIVER=sqlite
DATABASE_URL=./data/bcp.sqlite

BCP also recognizes :memory:, sqlite: / file: URLs and common .sqlite, .sqlite3, and .db file names.

Example:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
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:

TypeScript
await db.connect();

// application work

await db.disconnect();

close() remains available as the backward-compatible shutdown method and is equivalent to disconnect():

TypeScript
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

TypeScript
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:

TypeScript
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:

TypeScript
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.

Bash
bcp db create create_users
bcp db migrate
bcp db status
bcp db rollback

BCP 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.