23 KiB
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
- Prerequisites
- Installation
- Environment Variables
- Quick Start
- Configuration
- Schema Definition
- CLI Commands
- CRUD API
- Query API Reference
- Vector Support
- HTML Sanitization
- TypeScript Type Generation
- Default Fields
- Project Structure
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 rawsql - Rich query DSL — filtering, ordering, pagination, joins, grouping, full-text search
- Native VECTOR columns — MariaDB
VECTOR(n)types andVECTOR INDEXsupport - HTML sanitization — fields with
html: trueare sanitized on insert/update - TypeScript codegen — generate
.tstype definitions from your schema - Zero-config defaults —
id,created_at, andupdated_atare added to every table automatically
Prerequisites
- Bun installed
- A running MariaDB server (11.7+ recommended for native
VECTORsupport)
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) |
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:
- Ensures the internal
__db_schema_manager__tracking table exists - Orders tables by foreign-key dependencies
- Creates missing tables / surgically updates existing ones
- Syncs indexes (including
VECTOR INDEX) - Drops tables removed from the schema (skips tables still referenced by FKs)
- 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.
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: trueordataType: "VECTOR"maps toVECTOR(n)(ndefaults to1536)- 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
constarray 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