From 36e61cab3f36aacbf376b61981af0f20c30261d1 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 20 Sep 2025 10:16:33 +0200 Subject: [PATCH 1/2] feat: enhance SQLite connection configurations to allow WAL Updated the Bun and Node SQLite connection implementations to support additional configuration options, including `onCreateConnection`. Introduced tests for connection creation to validate database instance types and ensure proper callback execution. Improved type exports for better integration with existing code. --- .../connection/BunSqliteConnection.spec.ts | 19 +++++- .../bun/connection/BunSqliteConnection.ts | 67 +++++++++++-------- .../node/connection/NodeSqliteConnection.ts | 35 ++++++---- .../NodeSqliteConnection.vi-test.ts | 19 +++++- .../sqlite/GenericSqliteConnection.ts | 14 ++-- app/src/index.ts | 1 + .../docs/(documentation)/usage/database.mdx | 46 +++++++++++-- 7 files changed, 147 insertions(+), 54 deletions(-) diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts index 2b242a1..b18fe82 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts @@ -1,8 +1,9 @@ import { connectionTestSuite } from "data/connection/connection-test-suite"; import { bunSqlite } from "./BunSqliteConnection"; import { bunTestRunner } from "adapter/bun/test"; -import { describe } from "bun:test"; +import { describe, test, mock, expect } from "bun:test"; import { Database } from "bun:sqlite"; +import { GenericSqliteConnection } from "data/connection/sqlite/GenericSqliteConnection"; describe("BunSqliteConnection", () => { connectionTestSuite(bunTestRunner, { @@ -12,4 +13,20 @@ describe("BunSqliteConnection", () => { }), rawDialectDetails: [], }); + + test("onCreateConnection", async () => { + const called = mock(() => null); + + const conn = bunSqlite({ + onCreateConnection: (db) => { + expect(db).toBeInstanceOf(Database); + called(); + }, + }); + await conn.ping(); + + expect(conn).toBeInstanceOf(GenericSqliteConnection); + expect(conn.db).toBeInstanceOf(Database); + expect(called).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index 08444c5..39fff00 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -1,40 +1,53 @@ import { Database } from "bun:sqlite"; -import { genericSqlite, type GenericSqliteConnection } from "bknd"; +import { + genericSqlite, + type GenericSqliteConnection, + type GenericSqliteConnectionConfig, +} from "bknd"; +import { omitKeys } from "bknd/utils"; export type BunSqliteConnection = GenericSqliteConnection; -export type BunSqliteConnectionConfig = { - database: Database; -}; +export type BunSqliteConnectionConfig = Omit< + GenericSqliteConnectionConfig, + "name" | "supports" +> & + ({ database?: Database; url?: never } | { url?: string; database?: never }); -export function bunSqlite(config?: BunSqliteConnectionConfig | { url: string }) { - let db: Database; +export function bunSqlite(config?: BunSqliteConnectionConfig) { + let db: Database | undefined; if (config) { - if ("database" in config) { + if ("database" in config && config.database) { db = config.database; - } else { + } else if (config.url) { db = new Database(config.url); } - } else { + } + + if (!db) { db = new Database(":memory:"); } - return genericSqlite("bun-sqlite", db, (utils) => { - //const fn = cache ? "query" : "prepare"; - const getStmt = (sql: string) => db.prepare(sql); + return genericSqlite( + "bun-sqlite", + db, + (utils) => { + const getStmt = (sql: string) => db.prepare(sql); - return { - db, - query: utils.buildQueryFn({ - all: (sql, parameters) => getStmt(sql).all(...(parameters || [])), - run: (sql, parameters) => { - const { changes, lastInsertRowid } = getStmt(sql).run(...(parameters || [])); - return { - insertId: utils.parseBigInt(lastInsertRowid), - numAffectedRows: utils.parseBigInt(changes), - }; - }, - }), - close: () => db.close(), - }; - }); + return { + db, + query: utils.buildQueryFn({ + all: (sql, parameters) => getStmt(sql).all(...(parameters || [])), + run: (sql, parameters) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...(parameters || [])); + return { + insertId: utils.parseBigInt(lastInsertRowid), + numAffectedRows: utils.parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + }; + }, + omitKeys(config ?? ({} as any), ["database", "url", "name", "supports"]), + ); } diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index e215aad..915ab3d 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -1,19 +1,29 @@ -import { genericSqlite } from "bknd"; +import { + genericSqlite, + type GenericSqliteConnection, + type GenericSqliteConnectionConfig, +} from "bknd"; import { DatabaseSync } from "node:sqlite"; +import { omitKeys } from "bknd/utils"; -export type NodeSqliteConnectionConfig = { - database: DatabaseSync; -}; +export type NodeSqliteConnection = GenericSqliteConnection; +export type NodeSqliteConnectionConfig = Omit< + GenericSqliteConnectionConfig, + "name" | "supports" +> & + ({ database?: DatabaseSync; url?: never } | { url?: string; database?: never }); -export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }) { - let db: DatabaseSync; +export function nodeSqlite(config?: NodeSqliteConnectionConfig) { + let db: DatabaseSync | undefined; if (config) { - if ("database" in config) { + if ("database" in config && config.database) { db = config.database; - } else { + } else if (config.url) { db = new DatabaseSync(config.url); } - } else { + } + + if (!db) { db = new DatabaseSync(":memory:"); } @@ -21,11 +31,7 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string } "node-sqlite", db, (utils) => { - const getStmt = (sql: string) => { - const stmt = db.prepare(sql); - //stmt.setReadBigInts(true); - return stmt; - }; + const getStmt = (sql: string) => db.prepare(sql); return { db, @@ -49,6 +55,7 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string } }; }, { + ...omitKeys(config ?? ({} as any), ["database", "url", "name", "supports"]), supports: { batching: false, }, diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts index 205f166..bbf85f5 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts @@ -1,9 +1,10 @@ import { nodeSqlite } from "./NodeSqliteConnection"; import { DatabaseSync } from "node:sqlite"; import { connectionTestSuite } from "data/connection/connection-test-suite"; -import { describe, beforeAll, afterAll } from "vitest"; +import { describe, beforeAll, afterAll, test, expect, vi } from "vitest"; import { viTestRunner } from "../vitest"; import { disableConsoleLog, enableConsoleLog } from "core/utils/test"; +import { GenericSqliteConnection } from "data/connection/sqlite/GenericSqliteConnection"; beforeAll(() => disableConsoleLog()); afterAll(() => enableConsoleLog()); @@ -16,4 +17,20 @@ describe("NodeSqliteConnection", () => { }), rawDialectDetails: [], }); + + test("onCreateConnection", async () => { + const called = vi.fn(() => null); + + const conn = nodeSqlite({ + onCreateConnection: (db) => { + expect(db).toBeInstanceOf(DatabaseSync); + called(); + }, + }); + await conn.ping(); + + expect(conn).toBeInstanceOf(GenericSqliteConnection); + expect(conn.db).toBeInstanceOf(DatabaseSync); + expect(called).toHaveBeenCalledOnce(); + }); }); diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts index 98a584b..0ec5212 100644 --- a/app/src/data/connection/sqlite/GenericSqliteConnection.ts +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -1,7 +1,6 @@ import type { KyselyPlugin, QueryResult } from "kysely"; import { type IGenericSqlite, - type OnCreateConnection, type Promisable, parseBigInt, buildQueryFn, @@ -9,6 +8,7 @@ import { } from "kysely-generic-sqlite"; import { SqliteConnection } from "./SqliteConnection"; import type { ConnQuery, ConnQueryResults, Features } from "../Connection"; +import type { MaybePromise } from "bknd"; export type { IGenericSqlite }; export type TStatement = { sql: string; parameters?: any[] | readonly any[] }; @@ -16,11 +16,11 @@ export interface IGenericCustomSqlite extends IGenericSqlite { batch?: (stmts: TStatement[]) => Promisable[]>; } -export type GenericSqliteConnectionConfig = { +export type GenericSqliteConnectionConfig = { name?: string; additionalPlugins?: KyselyPlugin[]; excludeTables?: string[]; - onCreateConnection?: OnCreateConnection; + onCreateConnection?: (db: Database) => MaybePromise; supports?: Partial; }; @@ -35,7 +35,12 @@ export class GenericSqliteConnection extends SqliteConnection ) { super({ dialect: GenericSqliteDialect, - dialectArgs: [executor, config?.onCreateConnection], + dialectArgs: [ + executor, + config?.onCreateConnection && typeof config.onCreateConnection === "function" + ? (c: any) => config.onCreateConnection?.(c.db.db as any) + : undefined, + ], additionalPlugins: config?.additionalPlugins, excludeTables: config?.excludeTables, }); @@ -61,7 +66,6 @@ export class GenericSqliteConnection extends SqliteConnection override async executeQueries(...qbs: O): Promise> { const executor = await this.getExecutor(); if (!executor.batch) { - //$console.debug("Batching is not supported by this database"); return super.executeQueries(...qbs); } diff --git a/app/src/index.ts b/app/src/index.ts index def9264..ae01151 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -116,6 +116,7 @@ export { genericSqlite, genericSqliteUtils, type GenericSqliteConnection, + type GenericSqliteConnectionConfig, } from "data/connection/sqlite/GenericSqliteConnection"; export { EntityTypescript, diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index 4608d62..b5db41c 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -63,7 +63,7 @@ import type { BkndConfig } from "bknd"; export default { connection: { url: "file:data.db" }, -} as const satisfies BkndConfig; +} satisfies BkndConfig; ``` Throughout the documentation, it is assumed you use `bknd.config.ts` to define your connection. @@ -93,17 +93,51 @@ import type { BkndConfig } from "bknd"; // no connection is required, bknd will use a SQLite database in-memory // this does not work on edge environments! -export default {} as const satisfies BkndConfig; +export default {} satisfies BkndConfig; // or explicitly in-memory export default { connection: { url: ":memory:" }, -} as const satisfies BkndConfig; +} satisfies BkndConfig; // or explicitly as a file export default { connection: { url: "file:" }, -} as const satisfies BkndConfig; +} satisfies BkndConfig; +``` + +### Bun SQLite + +You can explicitly use the Bun SQLite adapter by passing the `bunSqlite` function to the `connection` property. This allows further configuration of the database, e.g. enabling WAL mode. + +```typescript title="bknd.config.ts" +import { bunSqlite, type BunBkndConfig } from "bknd/adapter/bun"; + +export default { + connection: bunSqlite({ + url: "file:", + onCreateConnection: (db) => { + db.run("PRAGMA journal_mode = WAL;"); + }, + }), +} satisfies BunBkndConfig; +``` + +### Node.js SQLite + +To use the Node.js SQLite adapter directly, set the `connection` property to the result of the `nodeSqlite` function. This lets you customize the database connection, such as enabling WAL mode. + +```typescript title="bknd.config.ts" +import { nodeSqlite, type NodeBkndConfig } from "bknd/adapter/node"; + +export default { + connection: nodeSqlite({ + url: "file:", + onCreateConnection: (db) => { + db.exec("PRAGMA journal_mode = WAL;"); + }, + }), +} satisfies NodeBkndConfig; ``` ### LibSQL @@ -118,7 +152,7 @@ export default { url: "libsql://.turso.io", authToken: "", }), -} as const satisfies BkndConfig; +} satisfies BkndConfig; ``` If you wish to use LibSQL as file, in-memory or make use of [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction), you have to pass in the `Client` from `@libsql/client`: @@ -134,7 +168,7 @@ const client = createClient({ export default { connection: libsql(client), -} as const satisfies BkndConfig; +} satisfies BkndConfig; ``` ### Cloudflare D1 From 874293807494b114320dff4dfeb7c197404ee256 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 20 Sep 2025 10:21:08 +0200 Subject: [PATCH 2/2] docs: swap Bun and Node.js SQLite sections --- .../docs/(documentation)/usage/database.mdx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index b5db41c..bb57adc 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -106,6 +106,23 @@ export default { } satisfies BkndConfig; ``` +### Node.js SQLite + +To use the Node.js SQLite adapter directly, use the `nodeSqlite` function as value for the `connection` property. This lets you customize the database connection, such as enabling WAL mode. + +```typescript title="bknd.config.ts" +import { nodeSqlite, type NodeBkndConfig } from "bknd/adapter/node"; + +export default { + connection: nodeSqlite({ + url: "file:", + onCreateConnection: (db) => { + db.exec("PRAGMA journal_mode = WAL;"); + }, + }), +} satisfies NodeBkndConfig; +``` + ### Bun SQLite You can explicitly use the Bun SQLite adapter by passing the `bunSqlite` function to the `connection` property. This allows further configuration of the database, e.g. enabling WAL mode. @@ -123,23 +140,6 @@ export default { } satisfies BunBkndConfig; ``` -### Node.js SQLite - -To use the Node.js SQLite adapter directly, set the `connection` property to the result of the `nodeSqlite` function. This lets you customize the database connection, such as enabling WAL mode. - -```typescript title="bknd.config.ts" -import { nodeSqlite, type NodeBkndConfig } from "bknd/adapter/node"; - -export default { - connection: nodeSqlite({ - url: "file:", - onCreateConnection: (db) => { - db.exec("PRAGMA journal_mode = WAL;"); - }, - }), -} satisfies NodeBkndConfig; -``` - ### LibSQL Turso offers a SQLite-fork called LibSQL that runs a server around your SQLite database. The edge-version of the adapter is included in the bundle (remote only):