mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #266 from bknd-io/feat/generic-sqlite-modes
feat: enhance SQLite connection configurations to allow WAL
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { connectionTestSuite } from "data/connection/connection-test-suite";
|
import { connectionTestSuite } from "data/connection/connection-test-suite";
|
||||||
import { bunSqlite } from "./BunSqliteConnection";
|
import { bunSqlite } from "./BunSqliteConnection";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
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 { Database } from "bun:sqlite";
|
||||||
|
import { GenericSqliteConnection } from "data/connection/sqlite/GenericSqliteConnection";
|
||||||
|
|
||||||
describe("BunSqliteConnection", () => {
|
describe("BunSqliteConnection", () => {
|
||||||
connectionTestSuite(bunTestRunner, {
|
connectionTestSuite(bunTestRunner, {
|
||||||
@@ -12,4 +13,20 @@ describe("BunSqliteConnection", () => {
|
|||||||
}),
|
}),
|
||||||
rawDialectDetails: [],
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,36 @@
|
|||||||
import { Database } from "bun:sqlite";
|
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<Database>;
|
export type BunSqliteConnection = GenericSqliteConnection<Database>;
|
||||||
export type BunSqliteConnectionConfig = {
|
export type BunSqliteConnectionConfig = Omit<
|
||||||
database: Database;
|
GenericSqliteConnectionConfig<Database>,
|
||||||
};
|
"name" | "supports"
|
||||||
|
> &
|
||||||
|
({ database?: Database; url?: never } | { url?: string; database?: never });
|
||||||
|
|
||||||
export function bunSqlite(config?: BunSqliteConnectionConfig | { url: string }) {
|
export function bunSqlite(config?: BunSqliteConnectionConfig) {
|
||||||
let db: Database;
|
let db: Database | undefined;
|
||||||
if (config) {
|
if (config) {
|
||||||
if ("database" in config) {
|
if ("database" in config && config.database) {
|
||||||
db = config.database;
|
db = config.database;
|
||||||
} else {
|
} else if (config.url) {
|
||||||
db = new Database(config.url);
|
db = new Database(config.url);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!db) {
|
||||||
db = new Database(":memory:");
|
db = new Database(":memory:");
|
||||||
}
|
}
|
||||||
|
|
||||||
return genericSqlite("bun-sqlite", db, (utils) => {
|
return genericSqlite(
|
||||||
//const fn = cache ? "query" : "prepare";
|
"bun-sqlite",
|
||||||
|
db,
|
||||||
|
(utils) => {
|
||||||
const getStmt = (sql: string) => db.prepare(sql);
|
const getStmt = (sql: string) => db.prepare(sql);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -36,5 +47,7 @@ export function bunSqlite(config?: BunSqliteConnectionConfig | { url: string })
|
|||||||
}),
|
}),
|
||||||
close: () => db.close(),
|
close: () => db.close(),
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
omitKeys(config ?? ({} as any), ["database", "url", "name", "supports"]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
import { genericSqlite } from "bknd";
|
import {
|
||||||
|
genericSqlite,
|
||||||
|
type GenericSqliteConnection,
|
||||||
|
type GenericSqliteConnectionConfig,
|
||||||
|
} from "bknd";
|
||||||
import { DatabaseSync } from "node:sqlite";
|
import { DatabaseSync } from "node:sqlite";
|
||||||
|
import { omitKeys } from "bknd/utils";
|
||||||
|
|
||||||
export type NodeSqliteConnectionConfig = {
|
export type NodeSqliteConnection = GenericSqliteConnection<DatabaseSync>;
|
||||||
database: DatabaseSync;
|
export type NodeSqliteConnectionConfig = Omit<
|
||||||
};
|
GenericSqliteConnectionConfig<DatabaseSync>,
|
||||||
|
"name" | "supports"
|
||||||
|
> &
|
||||||
|
({ database?: DatabaseSync; url?: never } | { url?: string; database?: never });
|
||||||
|
|
||||||
export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }) {
|
export function nodeSqlite(config?: NodeSqliteConnectionConfig) {
|
||||||
let db: DatabaseSync;
|
let db: DatabaseSync | undefined;
|
||||||
if (config) {
|
if (config) {
|
||||||
if ("database" in config) {
|
if ("database" in config && config.database) {
|
||||||
db = config.database;
|
db = config.database;
|
||||||
} else {
|
} else if (config.url) {
|
||||||
db = new DatabaseSync(config.url);
|
db = new DatabaseSync(config.url);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!db) {
|
||||||
db = new DatabaseSync(":memory:");
|
db = new DatabaseSync(":memory:");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,11 +31,7 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }
|
|||||||
"node-sqlite",
|
"node-sqlite",
|
||||||
db,
|
db,
|
||||||
(utils) => {
|
(utils) => {
|
||||||
const getStmt = (sql: string) => {
|
const getStmt = (sql: string) => db.prepare(sql);
|
||||||
const stmt = db.prepare(sql);
|
|
||||||
//stmt.setReadBigInts(true);
|
|
||||||
return stmt;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
db,
|
db,
|
||||||
@@ -49,6 +55,7 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
...omitKeys(config ?? ({} as any), ["database", "url", "name", "supports"]),
|
||||||
supports: {
|
supports: {
|
||||||
batching: false,
|
batching: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { nodeSqlite } from "./NodeSqliteConnection";
|
import { nodeSqlite } from "./NodeSqliteConnection";
|
||||||
import { DatabaseSync } from "node:sqlite";
|
import { DatabaseSync } from "node:sqlite";
|
||||||
import { connectionTestSuite } from "data/connection/connection-test-suite";
|
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 { viTestRunner } from "../vitest";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";
|
||||||
|
import { GenericSqliteConnection } from "data/connection/sqlite/GenericSqliteConnection";
|
||||||
|
|
||||||
beforeAll(() => disableConsoleLog());
|
beforeAll(() => disableConsoleLog());
|
||||||
afterAll(() => enableConsoleLog());
|
afterAll(() => enableConsoleLog());
|
||||||
@@ -16,4 +17,20 @@ describe("NodeSqliteConnection", () => {
|
|||||||
}),
|
}),
|
||||||
rawDialectDetails: [],
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { KyselyPlugin, QueryResult } from "kysely";
|
import type { KyselyPlugin, QueryResult } from "kysely";
|
||||||
import {
|
import {
|
||||||
type IGenericSqlite,
|
type IGenericSqlite,
|
||||||
type OnCreateConnection,
|
|
||||||
type Promisable,
|
type Promisable,
|
||||||
parseBigInt,
|
parseBigInt,
|
||||||
buildQueryFn,
|
buildQueryFn,
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
} from "kysely-generic-sqlite";
|
} from "kysely-generic-sqlite";
|
||||||
import { SqliteConnection } from "./SqliteConnection";
|
import { SqliteConnection } from "./SqliteConnection";
|
||||||
import type { ConnQuery, ConnQueryResults, Features } from "../Connection";
|
import type { ConnQuery, ConnQueryResults, Features } from "../Connection";
|
||||||
|
import type { MaybePromise } from "bknd";
|
||||||
|
|
||||||
export type { IGenericSqlite };
|
export type { IGenericSqlite };
|
||||||
export type TStatement = { sql: string; parameters?: any[] | readonly any[] };
|
export type TStatement = { sql: string; parameters?: any[] | readonly any[] };
|
||||||
@@ -16,11 +16,11 @@ export interface IGenericCustomSqlite<DB = unknown> extends IGenericSqlite<DB> {
|
|||||||
batch?: (stmts: TStatement[]) => Promisable<QueryResult<any>[]>;
|
batch?: (stmts: TStatement[]) => Promisable<QueryResult<any>[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GenericSqliteConnectionConfig = {
|
export type GenericSqliteConnectionConfig<Database = unknown> = {
|
||||||
name?: string;
|
name?: string;
|
||||||
additionalPlugins?: KyselyPlugin[];
|
additionalPlugins?: KyselyPlugin[];
|
||||||
excludeTables?: string[];
|
excludeTables?: string[];
|
||||||
onCreateConnection?: OnCreateConnection;
|
onCreateConnection?: (db: Database) => MaybePromise<void>;
|
||||||
supports?: Partial<Features>;
|
supports?: Partial<Features>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,7 +35,12 @@ export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB>
|
|||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
dialect: GenericSqliteDialect,
|
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,
|
additionalPlugins: config?.additionalPlugins,
|
||||||
excludeTables: config?.excludeTables,
|
excludeTables: config?.excludeTables,
|
||||||
});
|
});
|
||||||
@@ -61,7 +66,6 @@ export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB>
|
|||||||
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
|
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
|
||||||
const executor = await this.getExecutor();
|
const executor = await this.getExecutor();
|
||||||
if (!executor.batch) {
|
if (!executor.batch) {
|
||||||
//$console.debug("Batching is not supported by this database");
|
|
||||||
return super.executeQueries(...qbs);
|
return super.executeQueries(...qbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export {
|
|||||||
genericSqlite,
|
genericSqlite,
|
||||||
genericSqliteUtils,
|
genericSqliteUtils,
|
||||||
type GenericSqliteConnection,
|
type GenericSqliteConnection,
|
||||||
|
type GenericSqliteConnectionConfig,
|
||||||
} from "data/connection/sqlite/GenericSqliteConnection";
|
} from "data/connection/sqlite/GenericSqliteConnection";
|
||||||
export {
|
export {
|
||||||
EntityTypescript,
|
EntityTypescript,
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import type { BkndConfig } from "bknd";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
connection: { url: "file:data.db" },
|
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.
|
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
|
// no connection is required, bknd will use a SQLite database in-memory
|
||||||
// this does not work on edge environments!
|
// this does not work on edge environments!
|
||||||
export default {} as const satisfies BkndConfig;
|
export default {} satisfies BkndConfig;
|
||||||
|
|
||||||
// or explicitly in-memory
|
// or explicitly in-memory
|
||||||
export default {
|
export default {
|
||||||
connection: { url: ":memory:" },
|
connection: { url: ":memory:" },
|
||||||
} as const satisfies BkndConfig;
|
} satisfies BkndConfig;
|
||||||
|
|
||||||
// or explicitly as a file
|
// or explicitly as a file
|
||||||
export default {
|
export default {
|
||||||
connection: { url: "file:<path/to/your/database.db>" },
|
connection: { url: "file:<path/to/your/database.db>" },
|
||||||
} as const satisfies BkndConfig;
|
} 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:<path/to/your/database.db>",
|
||||||
|
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.
|
||||||
|
|
||||||
|
```typescript title="bknd.config.ts"
|
||||||
|
import { bunSqlite, type BunBkndConfig } from "bknd/adapter/bun";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
connection: bunSqlite({
|
||||||
|
url: "file:<path/to/your/database.db>",
|
||||||
|
onCreateConnection: (db) => {
|
||||||
|
db.run("PRAGMA journal_mode = WAL;");
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
} satisfies BunBkndConfig;
|
||||||
```
|
```
|
||||||
|
|
||||||
### LibSQL
|
### LibSQL
|
||||||
@@ -118,7 +152,7 @@ export default {
|
|||||||
url: "libsql://<database>.turso.io",
|
url: "libsql://<database>.turso.io",
|
||||||
authToken: "<auth-token>",
|
authToken: "<auth-token>",
|
||||||
}),
|
}),
|
||||||
} 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`:
|
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 {
|
export default {
|
||||||
connection: libsql(client),
|
connection: libsql(client),
|
||||||
} as const satisfies BkndConfig;
|
} satisfies BkndConfig;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cloudflare D1
|
### Cloudflare D1
|
||||||
|
|||||||
Reference in New Issue
Block a user