# Bun MariaDB `@moduletrace/bun-mariadb` A schema-driven MariaDB manager for [Bun](https://bun.sh), featuring automatic schema synchronization, type-safe CRUD, native `VECTOR` support, HTML sanitization, and TypeScript type definition generation. --- ## Table of Contents - [Features](#features) - [Prerequisites](#prerequisites) - [Installation](#installation) - [Environment Variables](#environment-variables) - [Quick Start](#quick-start) - [Configuration](#configuration) - [Schema Definition](#schema-definition) - [CLI Commands](#cli-commands) - [`schema`](#schema--sync-database-to-schema) - [`typedef`](#typedef--generate-typescript-types-only) - [`backup`](#backup--sql-dump-backup) - [`restore`](#restore--restore-from-an-sql-dump) - [`admin`](#admin--interactive-admin-tools) - [CRUD API](#crud-api) - [Select](#select) - [Insert](#insert) - [Update](#update) - [Delete](#delete) - [Raw SQL](#raw-sql) - [Query API Reference](#query-api-reference) - [Vector Support](#vector-support) - [HTML Sanitization](#html-sanitization) - [TypeScript Type Generation](#typescript-type-generation) - [Default Fields](#default-fields) - [Project Structure](#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 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 defaults** — `id`, `created_at`, and `updated_at` are added to every table automatically --- ## Prerequisites - [Bun](https://bun.sh) 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: ```ini # .npmrc @moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/ ``` ```bash bun add @moduletrace/bun-mariadb ``` ### Via Git ```bash 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`: ```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: ```ts 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`): ```ts 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 ```bash bunx bun-mariadb schema ``` This creates/updates all tables, indexes, and foreign keys to match your schema. ### 4. Use the CRUD API ```ts 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](#html-sanitization)) | Schema file path is always: `{db_dir}/schema.ts`. --- ## Schema Definition ### Database Schema ```ts interface BUN_MARIADB_DatabaseSchemaType { tables: BUN_MARIADB_TableSchemaType[]; collation?: "utf8mb4_bin" | "utf8mb4_unicode_520_ci"; } ``` ### Table Schema ```ts 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 ```ts 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 ```ts interface BUN_MARIADB_ForeignKeyType { foreignKeyName?: string; destinationTableName?: string; destinationTableColumnName?: string; cascadeDelete?: boolean; cascadeUpdate?: boolean; } ``` ### Index ```ts interface BUN_MARIADB_IndexSchemaType { indexName?: string; indexTableFields?: string[]; indexType?: "BTREE" | "HASH" | "FULLTEXT" | "SPATIAL" | "VECTOR"; vectorDistanceMetric?: "euclidean" | "cosine"; // VECTOR indexes only comment?: string; } ``` ### Unique Constraint ```ts interface BUN_MARIADB_UniqueConstraintSchemaType { constraintName?: string; constraintTableFields?: { value: string }[]; } ``` --- ## CLI Commands CLI binary: `bun-mariadb`. ### `schema` — Sync database to schema ```bash bunx bun-mariadb schema [options] ``` | Option | Description | | ----------------- | ------------------------------------------------------- | | `-t`, `--typedef` | Also generate TypeScript type definitions after syncing | **Examples:** ```bash # 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 ```bash bunx bun-mariadb typedef ``` Writes types to `typedef_file_path` from config. ### `backup` — SQL dump backup ```bash 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 ```bash 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 ```bash bunx bun-mariadb admin ``` Interactive menu to list tables / inspect data and run SQL. --- ## CRUD API ```ts import BunMariaDB from "@moduletrace/bun-mariadb"; ``` All methods return a `DBResponseObject`: ```ts { 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 ```ts BunMariaDB.select({ table, query?, count?, targetId? }) ``` | Parameter | Type | Description | | ---------- | --------------------- | ------------------------------------------------------------ | | `table` | `string` | Table name | | `query` | `ServerQueryParam` | Query/filter options (see [Query API](#query-api-reference)) | | `count` | `boolean` | Return row count instead of rows | | `targetId` | `string \| number` | Shorthand to filter by `id` | **Examples:** ```ts 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 ```ts BunMariaDB.insert({ 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. ```ts 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 ```ts BunMariaDB.update({ table, data, query?, targetId? }) ``` | Parameter | Type | Description | | ---------- | --------------------- | --------------------------- | | `table` | `string` | Table name | | `data` | `Partial` | Fields to update | | `query` | `ServerQueryParam` | 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. ```ts 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 ```ts BunMariaDB.delete({ table, query?, targetId? }) ``` A WHERE clause is required. ```ts await BunMariaDB.delete({ table: "users", targetId: 1 }); await BunMariaDB.delete({ table: "users", query: { query: { first_name: { value: "Ben", equality: "LIKE" }, }, }, }); ``` --- ### Raw SQL ```ts BunMariaDB.sql({ sql, values? }) ``` ```ts 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`: ```ts type ServerQueryParam = { 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 | ```ts const res = await BunMariaDB.select({ table: "users", query: { query: { email: { equality: "IS NOT NULL" }, }, order: { field: "created_at", strategy: "DESC" }, limit: 20, }, }); ``` ### JOIN ```ts 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 ```ts { 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 ```bash bunx bun-mariadb schema ``` --- ## HTML Sanitization Fields with **explicit** `html: true` are sanitized on **insert** and **update** only. ```ts { fieldName: "body", dataType: "LONGTEXT", html: true } ``` Other fields (including text that happens to contain HTML tags) are **not** sanitized. ### Extend the allowlist ```ts // 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 ```bash bunx bun-mariadb typedef # or bunx bun-mariadb schema --typedef ``` Generates: - A `const` array of table names - A type per table (`BUN_MARIADB__`) - A union of all table types Example: ```ts 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; ``` ```ts import BunMariaDB from "@moduletrace/bun-mariadb"; import type { BUN_MARIADB_MY_APP_USERS } from "./db/types/db"; const res = await BunMariaDB.select({ 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