From e293205e6684390ced729391878988a5ef8588e0 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Mon, 2 Mar 2026 10:32:57 +0100 Subject: [PATCH] Updates --- README.md | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 + 2 files changed, 732 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fdd55e1..a03b133 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,736 @@ -# bun-sqlite +# Bun SQLite -To install dependencies: +@moduletrace/bun-sqlite -```bash -bun install +A schema-driven SQLite manager for [Bun](https://bun.sh), featuring automatic schema synchronization, type-safe CRUD operations, vector embedding support (via `sqlite-vec`), and TypeScript type definition generation. + +--- + +## Table of Contents + +- [Features](#features) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Schema Definition](#schema-definition) +- [CLI Commands](#cli-commands) +- [CRUD API](#crud-api) + - [Select](#select) + - [Insert](#insert) + - [Update](#update) + - [Delete](#delete) + - [Raw SQL](#raw-sql) +- [Query API Reference](#query-api-reference) +- [Vector Table Support](#vector-table-support) +- [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 your SQLite file to match +- **Automatic migrations** — adds new columns, recreates tables for complex changes, drops removed tables +- **Type-safe CRUD** — fully generic `select`, `insert`, `update`, `delete` functions with TypeScript generics +- **Rich query DSL** — filtering, ordering, pagination, joins, grouping, full-text search, sub-query counts +- **Vector table support** — create and manage `sqlite-vec` virtual tables for AI/ML embeddings +- **TypeScript codegen** — generate `.ts` type definitions from your schema automatically +- **Zero-config defaults** — `id`, `created_at`, and `updated_at` fields are added to every table automatically + +--- + +## Prerequisites + +`@moduletrace/bun-sqlite` is published to a private Gitea npm registry. You must configure your package manager to resolve the `@moduletrace` scope from that registry before installing. + +Add the following to your project's `.npmrc` file (create it at the root of your project if it doesn't exist): + +```ini +@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/ ``` -To run: +This works for both `bun` and `npm`. + +--- + +## Installation ```bash -bun run index.ts +bun add @moduletrace/bun-sqlite ``` -This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. +--- + +## Quick Start + +### 1. Create the config file + +Create `bun-sqlite.config.ts` at your project root: + +```ts +import type { BunSQLiteConfig } from "@moduletrace/bun-sqlite"; + +const config: BunSQLiteConfig = { + db_name: "my-app.db", + db_schema_file_name: "schema.ts", + db_backup_dir: ".backups", + db_dir: "./db", // optional: where to store the db file + typedef_file_path: "./db/types/db.ts", // optional: where to write generated types +}; + +export default config; +``` + +### 2. Define your schema + +Create `./db/schema.ts` (matching `db_schema_file_name` above): + +```ts +import type { BUN_SQLITE_DatabaseSchemaType } from "@moduletrace/bun-sqlite"; + +const schema: BUN_SQLITE_DatabaseSchemaType = { + dbName: "my-app", + tables: [ + { + tableName: "users", + fields: [ + { fieldName: "first_name", dataType: "TEXT" }, + { fieldName: "last_name", dataType: "TEXT" }, + { fieldName: "email", dataType: "TEXT", unique: true }, + ], + }, + ], +}; + +export default schema; +``` + +### 3. Sync the schema to SQLite + +```bash +bunx bun-sqlite schema +``` + +This creates the SQLite database file and creates/updates all tables to match your schema. + +### 4. Use the CRUD API + +```ts +import BunSQLite from "@moduletrace/bun-sqlite"; + +// Insert +await BunSQLite.insert({ + table: "users", + data: [{ first_name: "Alice", email: "alice@example.com" }], +}); + +// Select +const result = await BunSQLite.select({ table: "users" }); +console.log(result.payload); // Alice's row + +// Update +await BunSQLite.update({ + table: "users", + targetId: 1, + data: { first_name: "Alicia" }, +}); + +// Delete +await BunSQLite.delete({ table: "users", targetId: 1 }); +``` + +--- + +## Configuration + +The config file must be named `bun-sqlite.config.ts` and placed at the root of your project. + +| Field | Type | Required | Description | +| --------------------- | -------- | -------- | --------------------------------------------------------------------------------- | +| `db_name` | `string` | Yes | Filename for the SQLite database (e.g. `"app.db"`) | +| `db_schema_file_name` | `string` | Yes | Filename of the schema file relative to `db_dir` (or root if `db_dir` is not set) | +| `db_backup_dir` | `string` | Yes | Directory for database backups, relative to `db_dir` | +| `db_dir` | `string` | No | Root directory for the database file and schema. Defaults to project root | +| `typedef_file_path` | `string` | No | Output path for generated TypeScript types, relative to project root | + +--- + +## Schema Definition + +### Database Schema + +```ts +interface BUN_SQLITE_DatabaseSchemaType { + dbName?: string; + tables: BUN_SQLITE_TableSchemaType[]; +} +``` + +### Table Schema + +```ts +interface BUN_SQLITE_TableSchemaType { + tableName: string; + tableDescription?: string; + fields: BUN_SQLITE_FieldSchemaType[]; + indexes?: BUN_SQLITE_IndexSchemaType[]; + uniqueConstraints?: BUN_SQLITE_UniqueConstraintSchemaType[]; + parentTableName?: string; // inherit fields from another table in the schema + tableNameOld?: string; // rename: set this to the old name to trigger ALTER TABLE RENAME + isVector?: boolean; // mark this as a sqlite-vec virtual table + vectorType?: string; // virtual table type, defaults to "vec0" +} +``` + +### Field Schema + +```ts +type BUN_SQLITE_FieldSchemaType = { + fieldName?: string; + dataType: "TEXT" | "INTEGER"; + primaryKey?: boolean; + autoIncrement?: boolean; + notNullValue?: boolean; + unique?: boolean; + defaultValue?: string | number; + defaultValueLiteral?: string; // raw SQL literal, e.g. "CURRENT_TIMESTAMP" + foreignKey?: BUN_SQLITE_ForeignKeyType; + isVector?: boolean; // vector embedding column + vectorSize?: number; // embedding dimensions (default: 1536) + sideCar?: boolean; // sqlite-vec "+" prefix for side-car columns + updatedField?: boolean; // flag that this field definition has changed +}; +``` + +### Foreign Key + +```ts +interface BUN_SQLITE_ForeignKeyType { + destinationTableName: string; + destinationTableColumnName: string; + cascadeDelete?: boolean; + cascadeUpdate?: boolean; +} +``` + +### Index + +```ts +interface BUN_SQLITE_IndexSchemaType { + indexName?: string; + indexType?: "regular" | "full_text" | "vector"; + indexTableFields?: { value: string; dataType: string }[]; +} +``` + +### Unique Constraint + +```ts +interface BUN_SQLITE_UniqueConstraintSchemaType { + constraintName?: string; + constraintTableFields?: { value: string }[]; +} +``` + +--- + +## CLI Commands + +The package provides a `bun-sqlite` CLI binary. + +### `schema` — Sync database to schema + +```bash +bunx bun-sqlite schema [options] +``` + +| Option | Description | +| ----------------- | ---------------------------------------------------------- | +| `-v`, `--vector` | Drop and recreate all vector (`sqlite-vec`) virtual tables | +| `-t`, `--typedef` | Also generate TypeScript type definitions after syncing | + +**Examples:** + +```bash +# Sync schema only +bunx bun-sqlite schema + +# Sync schema and regenerate types +bunx bun-sqlite schema --typedef + +# Sync schema, recreate vector tables, and regenerate types +bunx bun-sqlite schema --vector --typedef +``` + +### `typedef` — Generate TypeScript types only + +```bash +bunx bun-sqlite typedef +``` + +Reads the schema and writes TypeScript type definitions to the path configured in `typedef_file_path`. + +--- + +## CRUD API + +Import the default export: + +```ts +import BunSQLite from "@moduletrace/bun-sqlite"; +``` + +All methods return an `APIResponseObject`: + +```ts +{ + success: boolean; + payload?: T[]; // array of rows (select) + singleRes?: T; // first row (select) + count?: number; // total count (when count: true) + postInsertReturn?: { + affectedRows?: number; + insertId?: number; + }; + error?: string; + msg?: string; + debug?: any; +} +``` + +--- + +### Select + +```ts +BunSQLite.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 +// Get all users +const res = await BunSQLite.select({ table: "users" }); + +// Get by ID +const res = await BunSQLite.select({ table: "users", targetId: 42 }); + +// Filter with LIKE +const res = await BunSQLite.select({ + table: "users", + query: { + query: { + first_name: { value: "Ali", equality: "LIKE" }, + }, + }, +}); + +// Count rows +const res = await BunSQLite.select({ table: "users", count: true }); +console.log(res.count); + +// Pagination +const res = await BunSQLite.select({ + table: "users", + query: { limit: 10, page: 2 }, +}); +``` + +--- + +### Insert + +```ts +BunSQLite.insert({ table, data }); +``` + +| Parameter | Type | Description | +| --------- | -------- | ------------------------------ | +| `table` | `string` | Table name | +| `data` | `T[]` | Array of row objects to insert | + +`created_at` and `updated_at` are set automatically to `Date.now()`. + +**Example:** + +```ts +const res = await BunSQLite.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.postInsertReturn?.insertId); // last inserted row ID +``` + +--- + +### Update + +```ts +BunSQLite.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. If no condition matches, `success` is `false`. + +`updated_at` is set automatically to `Date.now()`. + +**Examples:** + +```ts +// Update by ID +await BunSQLite.update({ + table: "users", + targetId: 1, + data: { first_name: "Alicia" }, +}); + +// Update with custom query +await BunSQLite.update({ + table: "users", + data: { last_name: "Doe" }, + query: { + query: { + email: { value: "alice@example.com", equality: "EQUAL" }, + }, + }, +}); +``` + +--- + +### Delete + +```ts +BunSQLite.delete({ table, query?, targetId? }) +``` + +| Parameter | Type | Description | +| ---------- | --------------------- | --------------------------- | +| `table` | `string` | Table name | +| `query` | `ServerQueryParam` | WHERE clause conditions | +| `targetId` | `string \| number` | Shorthand to filter by `id` | + +A WHERE clause is required. If no condition is provided, `success` is `false`. + +**Examples:** + +```ts +// Delete by ID +await BunSQLite.delete({ table: "users", targetId: 1 }); + +// Delete with condition +await BunSQLite.delete({ + table: "users", + query: { + query: { + first_name: { value: "Ben", equality: "LIKE" }, + }, + }, +}); +``` + +--- + +### Raw SQL + +```ts +BunSQLite.sql({ sql, values? }) +``` + +| Parameter | Type | Description | +| --------- | ---------------------- | -------------------- | +| `sql` | `string` | Raw SQL statement | +| `values` | `(string \| number)[]` | Parameterized values | + +SELECT statements return rows; all other statements return `postInsertReturn`. + +**Examples:** + +```ts +// SELECT +const res = await BunSQLite.sql({ sql: "SELECT * FROM users" }); +console.log(res.payload); + +// INSERT with params +await BunSQLite.sql({ + sql: "INSERT INTO users (first_name, email) VALUES (?, ?)", + values: ["Charlie", "charlie@example.com"], +}); +``` + +--- + +## Query API Reference + +The `query` parameter on `select`, `update`, and `delete` accepts a `ServerQueryParam` object: + +```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"; // how multiple query fields are joined (default: AND) + join?: ServerQueryParamsJoin[]; + group?: + | keyof T + | { field: keyof T; table?: string } + | (keyof T | { field: keyof T; table?: string })[]; + countSubQueries?: ServerQueryParamsCount[]; + fullTextSearch?: { + fields: (keyof T)[]; + searchTerm: string; + scoreAlias: string; + }; +}; +``` + +### Equality Operators + +Set `equality` on any query field to control the comparison: + +| Equality | SQL Equivalent | +| ----------------------- | ------------------------------------------------------ | +| `EQUAL` (default) | `=` | +| `NOT EQUAL` | `!=` | +| `LIKE` | `LIKE '%value%'` | +| `LIKE_RAW` | `LIKE 'value'` (no auto-wrapping) | +| `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 (val1, val2, ...)` — pass array as value | +| `NOT IN` | `NOT IN (...)` | +| `BETWEEN` | `BETWEEN val1 AND val2` — pass `[val1, val2]` as value | +| `IS NULL` | `IS NULL` | +| `IS NOT NULL` | `IS NOT NULL` | +| `MATCH` | sqlite-vec vector nearest-neighbor search | + +**Example:** + +```ts +// Find users with email NOT NULL, ordered by created_at DESC, limit 20 +const res = await BunSQLite.select({ + table: "users", + query: { + query: { + email: { equality: "IS NOT NULL" }, + }, + order: { field: "created_at", strategy: "DESC" }, + limit: 20, + }, +}); +``` + +### JOIN + +```ts +const res = await BunSQLite.select({ + table: "posts", + query: { + join: [ + { + joinType: "LEFT JOIN", + tableName: "users", + match: { source: "user_id", target: "id" }, + selectFields: ["first_name", "email"], + }, + ], + }, +}); +``` + +--- + +## Vector Table Support + +`@moduletrace/bun-sqlite` integrates with [`sqlite-vec`](https://github.com/asg017/sqlite-vec) for storing and querying vector embeddings. + +### Define a vector table in the schema + +```ts +{ + tableName: "documents", + isVector: true, + vectorType: "vec0", // defaults to "vec0" + fields: [ + { + fieldName: "embedding", + dataType: "TEXT", + isVector: true, + vectorSize: 1536, // embedding dimensions + }, + { + fieldName: "title", + dataType: "TEXT", + sideCar: true, // stored as a side-car column (+title) for efficiency + }, + { + fieldName: "body", + dataType: "TEXT", + sideCar: true, + }, + ], +} +``` + +> **Side-car columns** (`sideCar: true`) use sqlite-vec's `+column` syntax. They are stored separately from the vector index, keeping the index lean and fast while still being queryable alongside vector results. + +### Sync vector tables + +```bash +# Initial sync +bunx bun-sqlite schema + +# Recreate vector tables (e.g. after changing vectorSize) +bunx bun-sqlite schema --vector +``` + +### Query vectors + +```ts +const res = await BunSQLite.select({ + table: "documents", + query: { + query: { + embedding: { + equality: "MATCH", + value: "", + vector: true, + vectorFunction: "vec_f32", + }, + }, + limit: 5, + }, +}); +``` + +--- + +## TypeScript Type Generation + +Run the `typedef` command (or pass `--typedef` to `schema`) to generate a `.ts` file containing: + +- A `const` array of all table names (`BunSQLiteTables`) +- A `type` for each table (named `BUN_SQLITE__`) +- A union type `BUN_SQLITE__ALL_TYPEDEFS` + +**Example output** (`db/types/db.ts`): + +```ts +export const BunSQLiteTables = ["users"] as const; + +export type BUN_SQLITE_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_SQLITE_MY_APP_ALL_TYPEDEFS = BUN_SQLITE_MY_APP_USERS; +``` + +Use the generated types with the CRUD API for full type safety: + +```ts +import BunSQLite from "@moduletrace/bun-sqlite"; +import { BUN_SQLITE_MY_APP_USERS, BunSQLiteTables } from "./db/types/db"; + +const res = await BunSQLite.select({ + table: "users" as (typeof BunSQLiteTables)[number], +}); +``` + +--- + +## Default Fields + +Every table automatically receives the following fields — you do not need to declare them in your schema: + +| Field | Type | Description | +| ------------ | -------------------------------------------- | -------------------------------------- | +| `id` | `INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL` | Unique row identifier | +| `created_at` | `INTEGER` | Unix timestamp set on insert | +| `updated_at` | `INTEGER` | Unix timestamp updated on every update | + +--- + +## Project Structure + +``` +bun-sqlite/ +├── src/ +│ ├── index.ts # Main export (BunSQLite object) +│ ├── commands/ +│ │ ├── index.ts # CLI entry point +│ │ ├── schema.ts # `schema` command +│ │ └── typedef.ts # `typedef` command +│ ├── functions/ +│ │ └── init.ts # Config + schema loader +│ ├── lib/sqlite/ +│ │ ├── index.ts # Database client (bun:sqlite + sqlite-vec) +│ │ ├── db-schema-manager.ts # Schema synchronization engine +│ │ ├── db-select.ts # Select implementation +│ │ ├── db-insert.ts # Insert implementation +│ │ ├── db-update.ts # Update implementation +│ │ ├── db-delete.ts # Delete implementation +│ │ ├── db-sql.ts # Raw SQL implementation +│ │ ├── db-generate-type-defs.ts # Type def generator +│ │ └── schema-to-typedef.ts # Schema-to-TypeScript converter +│ ├── types/ +│ │ └── index.ts # All TypeScript types and interfaces +│ └── utils/ +│ ├── sql-generator.ts # SELECT query builder +│ ├── sql-insert-generator.ts # INSERT query builder +│ ├── sql-gen-operator-gen.ts # Equality operator mapper +│ ├── sql-equality-parser.ts # Equality string parser +│ └── append-default-fields-to-db-schema.ts +└── test/ + └── test-01/ # Example project using the library + ├── bun-sqlite.config.ts + ├── db/ + │ ├── bun-sqlite-schema.ts + │ └── types/bun-sqlite.ts # Generated types + └── src/ + ├── insert.ts + ├── select.ts + ├── delete.ts + └── sql.ts +``` + +--- + +## License + +MIT diff --git a/package.json b/package.json index ea7222e..6aca06e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,10 @@ "README.md", "package.json" ], + "repository": { + "type": "git", + "url": "git+https://git.tben.me/Moduletrace/bun-sqlite.git" + }, "dependencies": { "commander": "^14.0.3", "lodash": "^4.17.23",