Go to file
2026-07-20 21:43:05 +01:00
dist Update .gitignore, add dist directory 2026-07-20 21:43:05 +01:00
src Updates 2026-07-14 09:34:10 +01:00
.gitignore Update .gitignore, add dist directory 2026-07-20 21:43:05 +01:00
.npmrc First Commit. Based off bun-sqlite 2026-06-21 06:18:34 +01:00
bun.lock updates 2026-07-14 09:10:30 +01:00
CLAUDE.md First Commit. Based off bun-sqlite 2026-06-21 06:18:34 +01:00
package.json Updates 2026-07-14 09:34:25 +01:00
publish.sh Updates 2026-07-14 09:13:07 +01:00
README.md Updates 2026-07-14 09:34:10 +01:00
tsconfig.json updates 2026-07-14 09:10:30 +01:00

Bun MariaDB

@moduletrace/bun-mariadb

A schema-driven MariaDB manager for Bun, featuring automatic schema synchronization, type-safe CRUD, native VECTOR support, HTML sanitization, and TypeScript type definition generation.


Table of Contents


Features

  • Schema-first design — define your database in TypeScript; the library syncs MariaDB to match
  • Automatic migrations — adds/modifies/drops columns, manages indexes & foreign keys, drops removed tables
  • Type-safe CRUD — generic select, insert, update, delete, and raw sql
  • Rich query DSL — filtering, ordering, pagination, joins, grouping, full-text search
  • Native VECTOR columns — MariaDB VECTOR(n) types and VECTOR INDEX support
  • HTML sanitization — fields with html: true are sanitized on insert/update
  • TypeScript codegen — generate .ts type definitions from your schema
  • Zero-config defaultsid, created_at, and updated_at are added to every table automatically

Prerequisites

  • Bun installed
  • A running MariaDB server (11.7+ recommended for native VECTOR support)

Installation

Via the private npm registry

@moduletrace/bun-mariadb is published to a private Gitea npm registry. Configure the @moduletrace scope first:

# .npmrc
@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/
bun add @moduletrace/bun-mariadb

Via Git

bun add github:moduletrace/bun-mariadb

Environment Variables

Connection settings are read from the environment (not the config file):

Variable Required Description
BUN_MARIADB_SERVER_HOST Yes MariaDB host
BUN_MARIADB_SERVER_USERNAME Yes Database user
BUN_MARIADB_SERVER_PASSWORD Yes Database password
BUN_MARIADB_SERVER_PORT No Port (server default if omitted)
BUN_MARIADB_SERVER_SSL_KEY_PATH No Optional SSL key path (legacy/env)

Example .env:

BUN_MARIADB_SERVER_HOST=127.0.0.1
BUN_MARIADB_SERVER_PORT=3306
BUN_MARIADB_SERVER_USERNAME=root
BUN_MARIADB_SERVER_PASSWORD=secret

Quick Start

1. Create the config file

Create bun-mariadb.config.ts at your project root:

import type { BunMariaDBConfig } from "@moduletrace/bun-mariadb";

const config: BunMariaDBConfig = {
    db_name: "my_app",
    db_dir: "./db",
    typedef_file_path: "./db/types/db.ts",
    // ssl_ca: "./db/ca-cert.pem",
};

export default config;

2. Define your schema

Create ./db/schema.ts (filename is fixed as schema.ts inside db_dir):

import type { BUN_MARIADB_DatabaseSchemaType } from "@moduletrace/bun-mariadb";

const schema: BUN_MARIADB_DatabaseSchemaType = {
    tables: [
        {
            tableName: "users",
            fields: [
                { fieldName: "first_name", dataType: "VARCHAR", integerLength: 255 },
                { fieldName: "last_name", dataType: "VARCHAR", integerLength: 255 },
                { fieldName: "email", dataType: "VARCHAR", integerLength: 255, unique: true },
                { fieldName: "bio", dataType: "LONGTEXT", html: true },
            ],
        },
    ],
};

export default schema;

3. Sync the schema to MariaDB

bunx bun-mariadb schema

This creates/updates all tables, indexes, and foreign keys to match your schema.

4. Use the CRUD API

import BunMariaDB from "@moduletrace/bun-mariadb";

// Insert
await BunMariaDB.insert({
    table: "users",
    data: [{ first_name: "Alice", email: "alice@example.com" }],
});

// Select
const result = await BunMariaDB.select({ table: "users" });
console.log(result.payload);

// Update
await BunMariaDB.update({
    table: "users",
    targetId: 1,
    data: { first_name: "Alicia" },
});

// Delete
await BunMariaDB.delete({ table: "users", targetId: 1 });

Configuration

The config file must be named bun-mariadb.config.ts and placed at the project root.

Field Type Required Description
db_name string Yes MariaDB database name
db_dir string Yes Directory for schema, types, and local artifacts (relative to project root)
db_backup_dir string No Backup directory name, relative to db_dir (default: .backups)
max_backups number No Max backup files to keep (default: 10)
max_exports number No Max export archives to keep (default: 10)
typedef_file_path string No Output path for generated TypeScript types (relative to project root)
db_config object No Extra options passed to Bun's SQL MariaDB adapter
charset string No Database charset (default: utf8mb4)
connection_timeout number No Connection timeout in ms (default: 10000)
ssl_ca string No Path to SSL CA certificate (relative to project root)
html_sanitize object No Extra HTML sanitizer allowlists (see HTML Sanitization)

Schema file path is always: {db_dir}/schema.ts.


Schema Definition

Database Schema

interface BUN_MARIADB_DatabaseSchemaType {
    tables: BUN_MARIADB_TableSchemaType[];
    collation?: "utf8mb4_bin" | "utf8mb4_unicode_520_ci";
}

Table Schema

interface BUN_MARIADB_TableSchemaType {
    tableName: string;
    tableDescription?: string;
    fields: BUN_MARIADB_FieldSchemaType[];
    indexes?: BUN_MARIADB_IndexSchemaType[];
    uniqueConstraints?: BUN_MARIADB_UniqueConstraintSchemaType[];
    parentTableName?: string; // inherit / merge fields from another table
    tableNameOld?: string;    // rename: old name triggers ALTER TABLE RENAME
    collation?: "utf8mb4_bin" | "utf8mb4_unicode_520_ci";
    isVector?: boolean;       // mark as vector-oriented table
}

Field Schema

type BUN_MARIADB_FieldSchemaType = {
    fieldName?: string;
    dataType:
        | "CHAR" | "VARCHAR" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT"
        | "TINYINT" | "SMALLINT" | "MEDIUMINT" | "INT" | "BIGINT"
        | "FLOAT" | "DOUBLE" | "DECIMAL"
        | "BINARY" | "VARBINARY" | "BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB"
        | "DATE" | "TIME" | "DATETIME" | "TIMESTAMP" | "YEAR"
        | "BOOLEAN" | "UUID" | "JSON" | "INET6" | "ENUM" | "SET" | "VECTOR";
    primaryKey?: boolean;
    autoIncrement?: boolean;
    notNullValue?: boolean;
    unique?: boolean;
    defaultValue?: string | number;
    defaultValueLiteral?: string; // raw SQL, e.g. "CURRENT_TIMESTAMP"
    onUpdate?: string;
    onUpdateLiteral?: string;
    foreignKey?: BUN_MARIADB_ForeignKeyType;
    integerLength?: string | number; // e.g. VARCHAR length
    decimals?: string | number;      // DECIMAL scale
    options?: (string | number)[];   // ENUM / SET values
    isVector?: boolean;              // native VECTOR column
    vectorSize?: number;             // dimensions (default: 1536)
    // Content mode flags (editor metadata); only `html: true` affects runtime:
    html?: boolean;
    markdown?: boolean;
    plain?: boolean;
    // ...
};

Foreign Key

interface BUN_MARIADB_ForeignKeyType {
    foreignKeyName?: string;
    destinationTableName?: string;
    destinationTableColumnName?: string;
    cascadeDelete?: boolean;
    cascadeUpdate?: boolean;
}

Index

interface BUN_MARIADB_IndexSchemaType {
    indexName?: string;
    indexTableFields?: string[];
    indexType?: "BTREE" | "HASH" | "FULLTEXT" | "SPATIAL" | "VECTOR";
    vectorDistanceMetric?: "euclidean" | "cosine"; // VECTOR indexes only
    comment?: string;
}

Unique Constraint

interface BUN_MARIADB_UniqueConstraintSchemaType {
    constraintName?: string;
    constraintTableFields?: { value: string }[];
}

CLI Commands

CLI binary: bun-mariadb.

schema — Sync database to schema

bunx bun-mariadb schema [options]
Option Description
-t, --typedef Also generate TypeScript type definitions after syncing

Examples:

# Sync schema only
bunx bun-mariadb schema

# Sync schema and regenerate types
bunx bun-mariadb schema --typedef

What it does:

  1. Ensures the internal __db_schema_manager__ tracking table exists
  2. Orders tables by foreign-key dependencies
  3. Creates missing tables / surgically updates existing ones
  4. Syncs indexes (including VECTOR INDEX)
  5. Drops tables removed from the schema (skips tables still referenced by FKs)
  6. Optionally writes TypeScript types and a live schema snapshot

typedef — Generate TypeScript types only

bunx bun-mariadb typedef

Writes types to typedef_file_path from config.

backup — SQL dump backup

bunx bun-mariadb backup

Runs mariadb-dump (or mysqldump) against the configured database and writes a timestamped .sql file into db_backup_dir. Old dumps are pruned to max_backups.

Requires mariadb-dump or mysqldump on PATH.

restore — Restore from an SQL dump

bunx bun-mariadb restore

Interactive picker of available .sql backups (newest first). Imports the selected dump via mariadb / mysql into the configured database.

Requires mariadb or mysql on PATH.

export — Portable SQL + schema archive

bunx bun-mariadb export [options]
Option Description
-o, --output Output archive path (.tar.gz or .zip). Defaults to {db_dir}/.exports
-f, --format When --output is omitted: tar.gz (default) or zip

Creates a portable archive containing:

  1. dump.sql — full SQL dump (same options as backup)
  2. schema.ts — the projects TypeScript schema file from db_dir

Archives are written to {db_dir}/.exports (sibling of the backups directory). Old exports are pruned using max_exports.

Examples:

# Write {db_name}-{timestamp}.tar.gz into {db_dir}/.exports
bunx bun-mariadb export

# Explicit path / format
bunx bun-mariadb export -o ./my-app-export.tar.gz
bunx bun-mariadb export -f zip
bunx bun-mariadb export -o ./my-app-export.zip

Requires mariadb-dump or mysqldump on PATH. Zip output also requires zip.

import — SQL or full export archive

bunx bun-mariadb import [file] [options]
Option Description
[file] Path to a .sql dump or export archive (.tar.gz / .tar / .zip)
--sql-only Archive only: restore SQL, do not overwrite schema.ts
--schema-only Archive only: write schema.ts, do not restore SQL

Behavior:

  • .sql file — restores SQL into the configured database (same as restore, but non-interactive)
  • Export archive — restores dump.sql and writes schema.ts into db_dir
  • No file argument — interactive picker of archives in {db_dir}/.exports

Examples:

# Plain SQL dump
bunx bun-mariadb import ./db/.backups/my_app-1710000000000.sql

# Full export (SQL + schema.ts)
bunx bun-mariadb import ./db/exports/my_app-1710000000000.tar.gz

# Archive: SQL only / schema only
bunx bun-mariadb import ./export.tar.gz --sql-only
bunx bun-mariadb import ./export.tar.gz --schema-only

# Interactive picker (from {db_dir}/.exports)
bunx bun-mariadb import

Requires mariadb or mysql on PATH. Zip archives also require unzip.

admin — Interactive admin tools

bunx bun-mariadb admin

Interactive menu to list tables / inspect data and run SQL.


CRUD API

import BunMariaDB from "@moduletrace/bun-mariadb";

All methods return a DBResponseObject<T>:

{
    success: boolean;
    payload?: T[];           // rows (select / returning)
    single_res?: T;          // first row
    count?: number;
    insert_return?: {
        count?: number;
        last_insert_id?: number;
        affected_rows?: number;
    };
    error?: any;
    msg?: string;
    debug?: any;
}

Select

BunMariaDB.select<T>({ table, query?, count?, targetId? })
Parameter Type Description
table string Table name
query ServerQueryParam<T> Query/filter options (see Query API)
count boolean Return row count instead of rows
targetId string | number Shorthand to filter by id

Examples:

const res = await BunMariaDB.select({ table: "users" });

const res = await BunMariaDB.select({ table: "users", targetId: 42 });

const res = await BunMariaDB.select({
    table: "users",
    query: {
        query: {
            first_name: { value: "Ali", equality: "LIKE" },
        },
    },
});

const res = await BunMariaDB.select({ table: "users", count: true });
console.log(res.count);

const res = await BunMariaDB.select({
    table: "users",
    query: { limit: 10, page: 2 },
});

Insert

BunMariaDB.insert<T>({ table, data, update_on_duplicate? })
Parameter Type Description
table string Table name
data T[] Array of row objects to insert
update_on_duplicate boolean Use ON DUPLICATE KEY UPDATE when unique exists

created_at and updated_at are set automatically to Date.now().

Fields with html: true are sanitized before insert.

const res = await BunMariaDB.insert({
    table: "users",
    data: [
        { first_name: "Alice", last_name: "Smith", email: "alice@example.com" },
        { first_name: "Bob", last_name: "Jones", email: "bob@example.com" },
    ],
});

console.log(res.insert_return?.last_insert_id);

Update

BunMariaDB.update<T>({ table, data, query?, targetId? })
Parameter Type Description
table string Table name
data Partial<T> Fields to update
query ServerQueryParam<T> WHERE clause conditions
targetId string | number Shorthand to filter by id

A WHERE clause is required. updated_at is set automatically. HTML fields are sanitized.

await BunMariaDB.update({
    table: "users",
    targetId: 1,
    data: { first_name: "Alicia" },
});

await BunMariaDB.update({
    table: "users",
    data: { last_name: "Doe" },
    query: {
        query: {
            email: { value: "alice@example.com", equality: "EQUAL" },
        },
    },
});

Delete

BunMariaDB.delete<T>({ table, query?, targetId? })

A WHERE clause is required.

await BunMariaDB.delete({ table: "users", targetId: 1 });

await BunMariaDB.delete({
    table: "users",
    query: {
        query: {
            first_name: { value: "Ben", equality: "LIKE" },
        },
    },
});

Raw SQL

BunMariaDB.sql<T>({ sql, values? })
const res = await BunMariaDB.sql({
    sql: "SELECT * FROM users WHERE id = ?",
    values: [1],
});
console.log(res.payload);

Query API Reference

query on select / update / delete accepts ServerQueryParam<T>:

type ServerQueryParam<T> = {
    query?: { [key in keyof T]?: ServerQueryObject };
    selectFields?: (keyof T | { fieldName: keyof T; alias?: string })[];
    omitFields?: (keyof T)[];
    limit?: number;
    page?: number;
    offset?: number;
    order?:
        | { field: keyof T; strategy: "ASC" | "DESC" }
        | { field: keyof T; strategy: "ASC" | "DESC" }[];
    searchOperator?: "AND" | "OR";
    join?: ServerQueryParamsJoin[];
    group?: keyof T | { field: keyof T; table?: string } | Array<...>;
    countSubQueries?: ServerQueryParamsCount[];
    fullTextSearch?: {
        fields: (keyof T)[];
        searchTerm: string;
        scoreAlias: string;
    };
};

Equality Operators

Equality SQL Equivalent
EQUAL (default) =
NOT EQUAL !=
LIKE LIKE '%value%'
LIKE_RAW LIKE 'value'
LIKE_LOWER LOWER(field) LIKE '%value%'
NOT LIKE NOT LIKE '%value%'
GREATER THAN >
GREATER THAN OR EQUAL >=
LESS THAN <
LESS THAN OR EQUAL <=
IN IN (...)
NOT IN NOT IN (...)
BETWEEN BETWEEN a AND b
IS NULL IS NULL
IS NOT NULL IS NOT NULL
REGEXP REGEXP
FULLTEXT full-text match
MATCH vector / specialized match
const res = await BunMariaDB.select({
    table: "users",
    query: {
        query: {
            email: { equality: "IS NOT NULL" },
        },
        order: { field: "created_at", strategy: "DESC" },
        limit: 20,
    },
});

JOIN

const res = await BunMariaDB.select({
    table: "posts",
    query: {
        join: [
            {
                joinType: "LEFT JOIN",
                tableName: "users",
                match: { source: "user_id", target: "id" },
                selectFields: ["first_name", "email"],
            },
        ],
    },
});

Vector Support

MariaDB native VECTOR(n) columns and VECTOR INDEX are supported (MariaDB 11.7+).

Schema

{
    tableName: "documents",
    isVector: true,
    fields: [
        {
            fieldName: "embedding",
            dataType: "VECTOR",
            isVector: true,
            vectorSize: 1536,
        },
        {
            fieldName: "title",
            dataType: "VARCHAR",
            integerLength: 255,
        },
    ],
    indexes: [
        {
            indexName: "idx_documents_embedding",
            indexType: "VECTOR",
            indexTableFields: ["embedding"],
            vectorDistanceMetric: "cosine", // or "euclidean" (default)
        },
    ],
}

Notes:

  • isVector: true or dataType: "VECTOR" maps to VECTOR(n) (n defaults to 1536)
  • Vector columns are created as NOT NULL (required for vector indexes)
  • Dimension / storage changes trigger an automatic table recreate and data copy
  • No special CLI flag is required

Sync

bunx bun-mariadb schema

HTML Sanitization

Fields with explicit html: true are sanitized on insert and update only.

{ fieldName: "body", dataType: "LONGTEXT", html: true }

Other fields (including text that happens to contain HTML tags) are not sanitized.

Extend the allowlist

// bun-mariadb.config.ts
const config: BunMariaDBConfig = {
    db_name: "my_app",
    db_dir: "./db",
    html_sanitize: {
        allowed_tags: ["iframe", "video"],
        allowed_attributes: {
            iframe: ["src", "width", "height", "allow", "allowfullscreen"],
            video: ["src", "controls", "width", "height"],
            "*": ["data-id"],
        },
    },
};

Config values are appended to the built-in defaults (safe rich-text tags/attributes).


TypeScript Type Generation

bunx bun-mariadb typedef
# or
bunx bun-mariadb schema --typedef

Generates:

  • A const array of table names
  • A type per table (BUN_MARIADB_<DB>_<TABLE>)
  • A union of all table types

Example:

export const BunMariaDBTables = ["users"] as const;

export type BUN_MARIADB_MY_APP_USERS = {
    /** The unique identifier of the record. */
    id?: number;
    /** The time when the record was created. (Unix Timestamp) */
    created_at?: number;
    /** The time when the record was updated. (Unix Timestamp) */
    updated_at?: number;
    first_name?: string;
    last_name?: string;
    email?: string;
};

export type BUN_MARIADB_MY_APP_ALL_TYPEDEFS = BUN_MARIADB_MY_APP_USERS;
import BunMariaDB from "@moduletrace/bun-mariadb";
import type { BUN_MARIADB_MY_APP_USERS } from "./db/types/db";

const res = await BunMariaDB.select<BUN_MARIADB_MY_APP_USERS>({
    table: "users",
});

Default Fields

Every table automatically receives:

Field Type Description
id BIGINT PRIMARY KEY AUTO_INCREMENT NOT NULL Unique row identifier
created_at BIGINT Unix timestamp set on insert
updated_at BIGINT Unix timestamp updated on every update

You do not need to declare these in your schema.


Project Structure

bun-mariadb/
├── src/
│   ├── index.ts                        # Main export (BunMariaDB object)
│   ├── commands/
│   │   ├── index.ts                    # CLI entry point
│   │   ├── schema.ts                   # `schema` command
│   │   ├── typedef.ts                  # `typedef` command
│   │   ├── backup.ts                   # `backup` command
│   │   ├── restore.ts                  # `restore` command
│   │   └── admin/                      # Interactive admin tools
│   ├── functions/
│   │   ├── init.ts                     # Config + schema + client loader
│   │   ├── grab-mariadb-client.ts      # Bun SQL MariaDB client
│   │   └── live-schema.ts              # Live schema snapshot I/O
│   ├── lib/
│   │   ├── db-handler.ts               # Shared query runner
│   │   ├── mariadb/
│   │   │   ├── db-select.ts
│   │   │   ├── db-insert.ts
│   │   │   ├── db-update.ts
│   │   │   ├── db-delete.ts
│   │   │   ├── db-sql.ts
│   │   │   └── schema-to-typedef.ts
│   │   └── schema/                     # Schema sync engine (modular)
│   │       ├── create-db-schema.ts
│   │       ├── create-table.ts
│   │       ├── update-table.ts
│   │       ├── recreate-table.ts
│   │       ├── sync-indexes.ts
│   │       └── ...
│   ├── types/
│   │   └── index.ts
│   └── utils/
│       ├── sql-generator.ts
│       ├── sql-insert-generator.ts
│       ├── sanitize-html-fields.ts
│       └── ...
└── test/
    └── coderank/                       # Example project

License

MIT