@pikku/kysely-bun-sqlite 0.12.9 → 0.12.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,5 +18,13 @@ export interface CreateBunSqliteKyselyOptions {
18
18
  * shows up as an error that explains itself.
19
19
  */
20
20
  functions?: SqliteFunctionMap;
21
+ /**
22
+ * Absolute paths of loadable SQLite extensions (sqlite-vec's `vec0`, say) to
23
+ * load before the database is handed to Kysely. Loaded through the C API;
24
+ * SQL's own `load_extension()` stays refused. On macOS this throws unless
25
+ * the process pointed bun at a SQLite that allows it first
26
+ * (`Database.setCustomSQLite`), since Apple's system one does not.
27
+ */
28
+ extensions?: string[];
21
29
  }
22
30
  export declare function createBunSqliteKysely<DB>(options: CreateBunSqliteKyselyOptions): Kysely<DB>;
@@ -4,6 +4,8 @@ import { BunSqliteDatabase } from './bun-sqlite-adapter.js';
4
4
  import { registerSqliteFunctions } from './register-functions.js';
5
5
  export function createBunSqliteKysely(options) {
6
6
  const db = new Database(options.filename);
7
+ for (const path of options.extensions ?? [])
8
+ db.loadExtension(path);
7
9
  if (options.functions)
8
10
  registerSqliteFunctions(db, options.functions);
9
11
  const plugins = [];
package/package.json CHANGED
@@ -1,34 +1,43 @@
1
1
  {
2
2
  "name": "@pikku/kysely-bun-sqlite",
3
- "version": "0.12.9",
3
+ "version": "0.12.11",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/pikkujs/pikku.git",
7
+ "directory": "packages/services/kysely-bun-sqlite"
8
+ },
4
9
  "description": "Kysely driver for Pikku backed by the built-in bun:sqlite module",
5
10
  "author": "yasser.fadl@gmail.com",
6
11
  "license": "MIT",
7
12
  "module": "dist/src/index.js",
8
13
  "main": "dist/src/index.js",
14
+ "files": [
15
+ "dist",
16
+ "!dist/**/*.tsbuildinfo"
17
+ ],
9
18
  "type": "module",
10
19
  "scripts": {
11
20
  "tsc": "tsc",
12
21
  "ncu": "npx npm-check-updates",
13
- "build": "tsc -b",
22
+ "build": "bun run clean && tsc -b",
14
23
  "test": "bash run-tests.sh",
15
24
  "test:coverage": "bash run-tests.sh --coverage",
16
- "prepublishOnly": "bun run build"
25
+ "prepublishOnly": "bun run build",
26
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
17
27
  },
18
28
  "engines": {
19
29
  "bun": ">=1.4.0"
20
30
  },
21
31
  "dependencies": {
22
- "@pikku/kysely": "^0.13.18",
23
- "@pikku/kysely-sqlite": "^0.12.13",
24
- "kysely": "^0.29.5"
32
+ "@pikku/kysely": "^0.13.39",
33
+ "@pikku/kysely-sqlite": "^0.12.18",
34
+ "kysely": "^0.29.6"
25
35
  },
26
36
  "peerDependencies": {
27
- "@pikku/core": "^0.12.105"
37
+ "@pikku/core": "^0.12.144"
28
38
  },
29
39
  "devDependencies": {
30
- "@pikku/core": "^0.12.105",
31
- "@types/bun": "^1.4.0",
32
- "typescript": "^6.0.3"
40
+ "@pikku/core": "^0.12.144",
41
+ "@types/bun": "^1.4.3"
33
42
  }
34
- }
43
+ }
package/CHANGELOG.md DELETED
@@ -1,160 +0,0 @@
1
- # @pikku/kysely-bun-sqlite
2
-
3
- ## 0.12.9
4
-
5
- ### Patch Changes
6
-
7
- - 1ab831e: Add `openBunSqliteDatabase`, which opens a `bun:sqlite` database as the `SqliteDatabase` Kysely's `SqliteDialect` binds to.
8
-
9
- `createBunSqliteKysely` builds one Kysely over one database, so it cannot express several Kysely instances — differing only in their plugins — sharing a single underlying database. That combination is what lets a plugin-free instance (Better Auth needs unmangled column names) and a CamelCase one address the same tables, and until now an app that needed it had to reach for `better-sqlite3` instead.
10
-
11
- Under Bun that fallback is not survivable: `better-sqlite3` ships a Node-ABI addon which Bun cannot load, and rather than throwing it aborts the process with `NAPI FATAL ERROR: Error::New napi_get_last_error_info`. In a `pikku dev --watch` this killed the dev server after codegen but before it ever listened, with no error naming the driver.
12
-
13
- - Updated dependencies [1ab831e]
14
- - @pikku/core@0.12.105
15
-
16
- ## 0.12.8
17
-
18
- ### Patch Changes
19
-
20
- - 6ff72d3: Raise the supported Bun version to 1.4.
21
-
22
- `@pikku/bun-server` and `@pikku/kysely-bun-sqlite` now declare `engines.bun: >=1.4.0`
23
- and build against `@types/bun@^1.4.0`. `create-pikku` scaffolds
24
- `"packageManager": "bun@1.4.0"`, and the fabric `smoke`/`validate` commands default to
25
- and recommend the same version. CI pins `oven-sh/setup-bun` to 1.4.0 instead of
26
- tracking `latest`.
27
-
28
- ## 0.12.7
29
-
30
- ### Patch Changes
31
-
32
- - 746ed6a: fix: one coercion plugin, not three
33
-
34
- The Kysely coercion plugin existed in three copies — the CLI's local database,
35
- `@pikku/kysely-node-sqlite` and `@pikku/kysely-bun-sqlite` — and all three had
36
- drifted apart. Only the CLI's resolved a column against the tables the query
37
- actually named, so two tables that disagree on the kind of a same-named column
38
- coerced correctly in local development and silently did not at runtime; only
39
- bun's dropped a genuinely ambiguous column instead of letting the last table
40
- processed win.
41
-
42
- The single implementation now lives in `@pikku/kysely`, which all three already
43
- depended on, and keeps both behaviours: table-qualified resolution first, an
44
- ambiguity-safe bare-name fallback second.
45
-
46
- `ColumnKind` — the value type of the generated `coercion.gen.ts` — is
47
- `'date' | 'bool' | 'json'`. The CLI's fourth member, `uuid`, was never a
48
- coercion kind: the codegen excludes it from the map by construction, because a
49
- UUID is a string in both Postgres and SQLite. It is now `AnnotationKind` in the
50
- CLI, the union a column may declare in `db/annotations.ts`, of which
51
- `ColumnKind` is the coercible subset.
52
-
53
- `@pikku/cli` also drops its unused dependency on the Node-only
54
- `@pikku/kysely-node-sqlite`.
55
-
56
- - Updated dependencies [5a1a962]
57
- - Updated dependencies [746ed6a]
58
- - @pikku/core@0.12.86
59
- - @pikku/kysely@0.13.18
60
-
61
- ## 0.12.6
62
-
63
- ### Patch Changes
64
-
65
- - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
66
- - Updated dependencies [7722ceb]
67
- - Updated dependencies [375c1ff]
68
- - Updated dependencies [02a70cd]
69
- - Updated dependencies [aeef159]
70
- - Updated dependencies [a281de6]
71
- - Updated dependencies [266e3bc]
72
- - Updated dependencies [02a70cd]
73
- - Updated dependencies [786dae5]
74
- - Updated dependencies [6eef0a0]
75
- - Updated dependencies [3561d67]
76
- - Updated dependencies [a91c433]
77
- - Updated dependencies [02a70cd]
78
- - Updated dependencies [9537f74]
79
- - Updated dependencies [2b57ca8]
80
- - Updated dependencies [266e3bc]
81
- - Updated dependencies [9fce0f1]
82
- - Updated dependencies [83683a0]
83
- - Updated dependencies [456c88b]
84
- - Updated dependencies [456c88b]
85
- - Updated dependencies [c127273]
86
- - @pikku/core@0.12.85
87
- - @pikku/kysely@0.13.17
88
- - @pikku/kysely-sqlite@0.12.13
89
-
90
- ## 0.12.5
91
-
92
- ### Patch Changes
93
-
94
- - 0686513: Declare `@pikku/core` as a peer dependency, matching `@pikku/kysely-sqlite`.
95
-
96
- The package depends on `@pikku/kysely-sqlite`, which requires `@pikku/core` as a
97
- peer, but never declared that requirement itself — so an install resolved
98
- without complaint and the missing peer only surfaced at runtime. It is now
99
- declared the same way its sibling declares it.
100
-
101
- - Updated dependencies [7406bfe]
102
- - Updated dependencies [6794681]
103
- - Updated dependencies [a7fcd2e]
104
- - @pikku/core@0.12.84
105
- - @pikku/kysely@0.13.16
106
- - @pikku/kysely-sqlite@0.12.12
107
-
108
- ## 0.12.4
109
-
110
- ### Patch Changes
111
-
112
- - fd9d834: Stop publishing internals that only their own package or file used. The declarations stay; only the entrypoint re-export is removed, so nothing that imported a name from where it is declared is affected.
113
- - Updated dependencies [fd9d834]
114
- - @pikku/kysely@0.13.15
115
- - @pikku/kysely-sqlite@0.12.11
116
-
117
- ## 0.12.3
118
-
119
- ### Patch Changes
120
-
121
- - 9d62571: Make user-defined SQL functions an explicit, checked capability of the SQLite drivers.
122
-
123
- `node:sqlite` can register scalar UDFs (`db.function()`); `bun:sqlite` cannot. Until now
124
- neither driver mentioned that, so an app that registered a UDF by reaching for the raw
125
- connection worked on Node and, on bun, failed as `no such function: …` from whichever
126
- query used it — typically one endpoint breaking in production while everything else
127
- looked healthy.
128
-
129
- Both drivers now export `registerSqliteFunctions(db, functions)` and accept a
130
- `functions` option on `createNodeSqliteKysely` / `createBunSqliteKysely`. The Node
131
- implementation registers them as deterministic; the bun implementation throws
132
- `SqliteFunctionsUnsupportedError` — exported from `@pikku/kysely-sqlite`, and naming
133
- every function requested — so the incompatibility surfaces at wiring time with a message
134
- saying what to do about it.
135
-
136
- - Updated dependencies [cabd9dc]
137
- - Updated dependencies [9d62571]
138
- - @pikku/kysely@0.13.8
139
- - @pikku/kysely-sqlite@0.12.10
140
-
141
- ## 0.12.2
142
-
143
- ### Patch Changes
144
-
145
- - 41ce2cb: Upgrade to TypeScript 6 and raise the minimum Node.js version to 22.
146
-
147
- All packages now build against `typescript@^6.0.3` and declare `engines.node >= 22`. Internal tooling (`ts-json-schema-generator`, `zod-to-ts`) was bumped to TypeScript 6-compatible releases.
148
-
149
- - Updated dependencies [241e6cf]
150
- - Updated dependencies [41ce2cb]
151
- - @pikku/kysely@0.13.0
152
- - @pikku/kysely-sqlite@0.12.7
153
-
154
- ## 0.12.1
155
-
156
- ### Patch Changes
157
-
158
- - d5c3c85: feat: bun first-class support — new `@pikku/bun-server` runtime and `@pikku/kysely-bun-sqlite` dialect, bun template, CI matrix with `package-manager: [yarn, bun]`, and bun verifier.
159
- - Updated dependencies [92cd5b1]
160
- - @pikku/kysely@0.12.17
@@ -1 +0,0 @@
1
- {"root":["../src/bun-sqlite-adapter.ts","../src/create-bun-sqlite-kysely.ts","../src/index.ts","../src/register-functions.ts"],"version":"6.0.3"}
package/run-tests.sh DELETED
@@ -1,54 +0,0 @@
1
- #!/bin/bash
2
-
3
- shopt -s nullglob
4
-
5
- watch_mode=false
6
- coverage_mode=false
7
-
8
- while [[ $# -gt 0 ]]; do
9
- case $1 in
10
- --watch)
11
- watch_mode=true
12
- shift
13
- ;;
14
- --coverage)
15
- coverage_mode=true
16
- shift
17
- ;;
18
- *)
19
- echo "Unknown option: $1"
20
- exit 1
21
- ;;
22
- esac
23
- done
24
-
25
- files=()
26
- while IFS= read -r -d '' file; do
27
- files+=("$file")
28
- done < <(find src -type f -name "*.test.ts" -print0)
29
-
30
- if [ ${#files[@]} -eq 0 ]; then
31
- echo "No test files found"
32
- exit 0
33
- fi
34
-
35
- if [ "$coverage_mode" = true ]; then
36
- # Bun writes coverage/lcov.info and instruments imported dist files too.
37
- # Re-emit a package-root lcov.info containing only src/ records so the
38
- # repo-wide unit-coverage merge — which expects <pkg>/lcov.info and prefixes
39
- # its SF paths — maps them correctly, exactly like the node packages'
40
- # --test-reporter=lcov output.
41
- bun test --coverage --coverage-reporter=lcov "${files[@]}"
42
- status=$?
43
- awk '/^SF:/{keep=/^SF:src\//} keep' coverage/lcov.info > lcov.info 2>/dev/null || true
44
- rm -rf coverage
45
- exit $status
46
- fi
47
-
48
- bun_cmd="bun test"
49
-
50
- if [ "$watch_mode" = true ]; then
51
- bun_cmd="$bun_cmd --watch"
52
- fi
53
-
54
- $bun_cmd "${files[@]}"
@@ -1,212 +0,0 @@
1
- import { describe, test, beforeEach, afterEach } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { Database } from 'bun:sqlite'
4
- import { Kysely, SqliteDialect, CamelCasePlugin } from 'kysely'
5
- import {
6
- BunSqliteDatabase,
7
- openBunSqliteDatabase,
8
- } from './bun-sqlite-adapter.js'
9
-
10
- interface TestDB {
11
- items: { id: number; name: string; active: number; score: number | null }
12
- }
13
-
14
- describe('BunSqliteDatabase', () => {
15
- let db: Database
16
- let kysely: Kysely<TestDB>
17
-
18
- beforeEach(() => {
19
- db = new Database(':memory:')
20
- kysely = new Kysely<TestDB>({
21
- dialect: new SqliteDialect({ database: new BunSqliteDatabase(db) }),
22
- })
23
- })
24
-
25
- afterEach(async () => {
26
- await kysely.destroy()
27
- })
28
-
29
- test('create table and insert a row', async () => {
30
- await kysely.schema
31
- .createTable('items')
32
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
33
- .addColumn('name', 'text', (c) => c.notNull())
34
- .addColumn('active', 'integer', (c) => c.notNull())
35
- .addColumn('score', 'real')
36
- .execute()
37
-
38
- await kysely
39
- .insertInto('items')
40
- .values({ name: 'alpha', active: 1, score: 3.14 })
41
- .execute()
42
-
43
- const rows = await kysely.selectFrom('items').selectAll().execute()
44
- assert.equal(rows.length, 1)
45
- assert.equal(rows[0].name, 'alpha')
46
- assert.equal(rows[0].active, 1)
47
- assert.equal(rows[0].score, 3.14)
48
- })
49
-
50
- test('update and delete', async () => {
51
- await kysely.schema
52
- .createTable('items')
53
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
54
- .addColumn('name', 'text', (c) => c.notNull())
55
- .addColumn('active', 'integer', (c) => c.notNull())
56
- .addColumn('score', 'real')
57
- .execute()
58
-
59
- await kysely
60
- .insertInto('items')
61
- .values([
62
- { name: 'a', active: 1, score: null },
63
- { name: 'b', active: 0, score: null },
64
- ])
65
- .execute()
66
-
67
- await kysely
68
- .updateTable('items')
69
- .set({ active: 1 })
70
- .where('name', '=', 'b')
71
- .execute()
72
- const updated = await kysely
73
- .selectFrom('items')
74
- .where('name', '=', 'b')
75
- .selectAll()
76
- .executeTakeFirstOrThrow()
77
- assert.equal(updated.active, 1)
78
-
79
- await kysely.deleteFrom('items').where('name', '=', 'a').execute()
80
- const remaining = await kysely.selectFrom('items').selectAll().execute()
81
- assert.equal(remaining.length, 1)
82
- assert.equal(remaining[0].name, 'b')
83
- })
84
-
85
- test('boolean coercion via coerce function', async () => {
86
- await kysely.schema
87
- .createTable('items')
88
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
89
- .addColumn('name', 'text', (c) => c.notNull())
90
- .addColumn('active', 'integer', (c) => c.notNull())
91
- .addColumn('score', 'real')
92
- .execute()
93
-
94
- await kysely
95
- .insertInto('items')
96
- .values({
97
- name: 'bool-test',
98
- active: true as unknown as number,
99
- score: null,
100
- })
101
- .execute()
102
- const row = await kysely
103
- .selectFrom('items')
104
- .selectAll()
105
- .executeTakeFirstOrThrow()
106
- assert.equal(row.active, 1)
107
- })
108
-
109
- test('null values round-trip', async () => {
110
- await kysely.schema
111
- .createTable('items')
112
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
113
- .addColumn('name', 'text', (c) => c.notNull())
114
- .addColumn('active', 'integer', (c) => c.notNull())
115
- .addColumn('score', 'real')
116
- .execute()
117
-
118
- await kysely
119
- .insertInto('items')
120
- .values({ name: 'nullable', active: 0, score: null })
121
- .execute()
122
- const row = await kysely
123
- .selectFrom('items')
124
- .selectAll()
125
- .executeTakeFirstOrThrow()
126
- assert.equal(row.score, null)
127
- })
128
-
129
- test('iterate returns rows lazily', async () => {
130
- await kysely.schema
131
- .createTable('items')
132
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
133
- .addColumn('name', 'text', (c) => c.notNull())
134
- .addColumn('active', 'integer', (c) => c.notNull())
135
- .addColumn('score', 'real')
136
- .execute()
137
-
138
- await kysely
139
- .insertInto('items')
140
- .values([
141
- { name: 'x', active: 1, score: null },
142
- { name: 'y', active: 1, score: null },
143
- { name: 'z', active: 1, score: null },
144
- ])
145
- .execute()
146
-
147
- const collected: string[] = []
148
- const stmt = new BunSqliteDatabase(db).prepare(
149
- 'SELECT name FROM items ORDER BY id'
150
- )
151
- for (const row of stmt.iterate([])) {
152
- collected.push((row as any).name)
153
- }
154
- assert.deepEqual(collected, ['x', 'y', 'z'])
155
- })
156
-
157
- test('reader is false for writes so run() returns mutation metadata', async () => {
158
- await kysely.schema
159
- .createTable('items')
160
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
161
- .addColumn('name', 'text', (c) => c.notNull())
162
- .addColumn('active', 'integer', (c) => c.notNull())
163
- .addColumn('score', 'real')
164
- .execute()
165
-
166
- const inserted = await kysely
167
- .insertInto('items')
168
- .values({ name: 'meta', active: 1, score: null })
169
- .executeTakeFirstOrThrow()
170
- assert.equal(inserted.insertId, 1n)
171
-
172
- const updated = await kysely
173
- .updateTable('items')
174
- .set({ active: 0 })
175
- .where('name', '=', 'meta')
176
- .executeTakeFirstOrThrow()
177
- assert.equal(updated.numUpdatedRows, 1n)
178
- })
179
- })
180
-
181
- describe('openBunSqliteDatabase', () => {
182
- test('opens a database two Kysely instances can share', async () => {
183
- const database = openBunSqliteDatabase(':memory:')
184
- const plain = new Kysely<TestDB>({ dialect: new SqliteDialect({ database }) })
185
- const camelCase = new Kysely<TestDB>({
186
- dialect: new SqliteDialect({ database }),
187
- plugins: [new CamelCasePlugin()],
188
- })
189
-
190
- await plain.schema
191
- .createTable('items')
192
- .addColumn('id', 'integer', (c) => c.primaryKey().autoIncrement())
193
- .addColumn('name', 'text', (c) => c.notNull())
194
- .addColumn('active', 'integer', (c) => c.notNull())
195
- .addColumn('score', 'real')
196
- .execute()
197
- await plain
198
- .insertInto('items')
199
- .values({ name: 'shared', active: 1, score: null })
200
- .execute()
201
-
202
- // The second instance addresses the same underlying database, which is the
203
- // only reason a plugin-free and a CamelCase Kysely can span one schema.
204
- const row = await camelCase
205
- .selectFrom('items')
206
- .selectAll()
207
- .executeTakeFirstOrThrow()
208
- assert.equal(row.name, 'shared')
209
-
210
- await plain.destroy()
211
- })
212
- })
@@ -1,64 +0,0 @@
1
- import { Database, type Statement } from 'bun:sqlite'
2
- import type { SqliteDatabase, SqliteStatement } from 'kysely'
3
-
4
- function coerce(v: unknown): unknown {
5
- if (v === null || v === undefined) return null
6
- if (typeof v === 'boolean') return v ? 1 : 0
7
- if (v instanceof Date) return v.toISOString()
8
- if (typeof v === 'object') return JSON.stringify(v)
9
- return v
10
- }
11
-
12
- class BunSqliteStatement implements SqliteStatement {
13
- constructor(private readonly stmt: Statement) {}
14
-
15
- get reader(): boolean {
16
- return this.stmt.columnNames.length > 0
17
- }
18
-
19
- all(parameters: ReadonlyArray<unknown>): unknown[] {
20
- return this.stmt.all(...(parameters.map(coerce) as any))
21
- }
22
-
23
- *iterate(parameters: ReadonlyArray<unknown>): IterableIterator<unknown> {
24
- for (const row of this.stmt.iterate(...(parameters.map(coerce) as any))) {
25
- yield row
26
- }
27
- }
28
-
29
- run(parameters: ReadonlyArray<unknown>): {
30
- changes: number | bigint
31
- lastInsertRowid: number | bigint
32
- } {
33
- const result = this.stmt.run(...(parameters.map(coerce) as any))
34
- return {
35
- changes: result.changes,
36
- lastInsertRowid: result.lastInsertRowid,
37
- }
38
- }
39
- }
40
-
41
- export class BunSqliteDatabase implements SqliteDatabase {
42
- constructor(private readonly db: Database) {}
43
-
44
- prepare(sql: string): SqliteStatement {
45
- return new BunSqliteStatement(this.db.prepare(sql))
46
- }
47
-
48
- close(): void {
49
- this.db.close()
50
- }
51
- }
52
-
53
- /**
54
- * Opens a `bun:sqlite` database as the `SqliteDatabase` Kysely's `SqliteDialect`
55
- * binds to.
56
- *
57
- * `createBunSqliteKysely` covers the common case of one database behind one
58
- * Kysely. This is for the case it cannot express: several Kysely instances —
59
- * differing in their plugins — sharing a single underlying database, which is
60
- * what lets a plugin-free instance (Better Auth needs unmangled column names)
61
- * and a CamelCase one address the same tables.
62
- */
63
- export const openBunSqliteDatabase = (filename: string): SqliteDatabase =>
64
- new BunSqliteDatabase(new Database(filename))
@@ -1,90 +0,0 @@
1
- import { describe, test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { createBunSqliteKysely, createCoercionPlugin } from './index.js'
4
- import type { CoercionMap } from './index.js'
5
- import { sql, type Kysely } from 'kysely'
6
-
7
- interface TestDB {
8
- users: { id: number; settings: unknown }
9
- posts: { id: number; settings: unknown }
10
- }
11
-
12
- /**
13
- * The same map the CLI's local database builds from `coercion.gen.ts`: two
14
- * tables that disagree on the kind of a same-named column. Resolving it needs
15
- * the query's tables, which is what kept the CLI's copy and the runtime copies
16
- * from behaving identically.
17
- */
18
- const map: CoercionMap = {
19
- users: { settings: 'json' },
20
- posts: { settings: 'bool' },
21
- }
22
-
23
- const seed = async (db: Kysely<TestDB>) => {
24
- await sql`create table users (id integer primary key, settings text)`.execute(
25
- db
26
- )
27
- await sql`create table posts (id integer primary key, settings integer)`.execute(
28
- db
29
- )
30
- await sql`insert into users (id, settings) values (1, '{"theme":"dark"}')`.execute(
31
- db
32
- )
33
- await sql`insert into posts (id, settings) values (1, 1)`.execute(db)
34
- }
35
-
36
- describe('bun-sqlite coercion plugin', () => {
37
- test('resolves a colliding column against the queried table', async () => {
38
- const db = createBunSqliteKysely<TestDB>({
39
- filename: ':memory:',
40
- plugins: [createCoercionPlugin({ map })],
41
- })
42
- await seed(db)
43
-
44
- const user = await db
45
- .selectFrom('users')
46
- .select('settings')
47
- .executeTakeFirstOrThrow()
48
- assert.deepEqual(user.settings, { theme: 'dark' })
49
-
50
- const post = await db
51
- .selectFrom('posts')
52
- .select('settings')
53
- .executeTakeFirstOrThrow()
54
- assert.equal(post.settings, true)
55
-
56
- await db.destroy()
57
- })
58
-
59
- test('coerces date, bool and json columns read back from SQLite', async () => {
60
- const db = createBunSqliteKysely<{
61
- rows: { created_at: unknown; is_active: unknown; meta: unknown }
62
- }>({
63
- filename: ':memory:',
64
- camelCase: false,
65
- plugins: [
66
- createCoercionPlugin({
67
- map: {
68
- rows: { created_at: 'date', is_active: 'bool', meta: 'json' },
69
- },
70
- }),
71
- ],
72
- })
73
- await sql`create table rows (created_at text, is_active integer, meta text)`.execute(
74
- db
75
- )
76
- await sql`insert into rows values ('2026-06-26T00:00:00.000Z', 0, '{"a":1}')`.execute(
77
- db
78
- )
79
-
80
- const row = await db
81
- .selectFrom('rows')
82
- .selectAll()
83
- .executeTakeFirstOrThrow()
84
- assert.ok(row.created_at instanceof Date)
85
- assert.equal(row.is_active, false)
86
- assert.deepEqual(row.meta, { a: 1 })
87
-
88
- await db.destroy()
89
- })
90
- })
@@ -1,45 +0,0 @@
1
- import { describe, test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { sql } from 'kysely'
4
- import { createBunSqliteKysely } from './create-bun-sqlite-kysely.js'
5
-
6
- interface TestDB {
7
- widgets: { id: number; displayName: string }
8
- }
9
-
10
- describe('createBunSqliteKysely', () => {
11
- test('creates a working in-memory Kysely with camelCase mapping by default', async () => {
12
- const db = createBunSqliteKysely<TestDB>({ filename: ':memory:' })
13
-
14
- await sql`CREATE TABLE widgets (id INTEGER PRIMARY KEY, display_name TEXT)`.execute(
15
- db
16
- )
17
- await db
18
- .insertInto('widgets')
19
- .values({ id: 1, displayName: 'hello' })
20
- .execute()
21
-
22
- const row = await db
23
- .selectFrom('widgets')
24
- .selectAll()
25
- .executeTakeFirstOrThrow()
26
-
27
- assert.equal(row.displayName, 'hello')
28
- await db.destroy()
29
- })
30
-
31
- test('can disable camelCase and layer extra plugins', async () => {
32
- const db = createBunSqliteKysely<{ t: { snake_col: string } }>({
33
- filename: ':memory:',
34
- camelCase: false,
35
- plugins: [],
36
- })
37
-
38
- await sql`CREATE TABLE t (snake_col TEXT)`.execute(db)
39
- await db.insertInto('t').values({ snake_col: 'raw' }).execute()
40
- const row = await db.selectFrom('t').selectAll().executeTakeFirstOrThrow()
41
-
42
- assert.equal(row.snake_col, 'raw')
43
- await db.destroy()
44
- })
45
- })
@@ -1,45 +0,0 @@
1
- import { Database } from 'bun:sqlite'
2
- import {
3
- Kysely,
4
- SqliteDialect,
5
- CamelCasePlugin,
6
- type KyselyPlugin,
7
- } from 'kysely'
8
- import type { SqliteFunctionMap } from '@pikku/kysely-sqlite'
9
- import { BunSqliteDatabase } from './bun-sqlite-adapter.js'
10
- import { registerSqliteFunctions } from './register-functions.js'
11
-
12
- export interface CreateBunSqliteKyselyOptions {
13
- /** Path to the SQLite file. Use ':memory:' for an in-memory DB. */
14
- filename: string
15
- /** Apply CamelCasePlugin so DB columns map to camelCase TS fields. Default true. */
16
- camelCase?: boolean
17
- /** Extra plugins to layer on top. */
18
- plugins?: KyselyPlugin[]
19
- /**
20
- * Accepted only so that setting it fails loudly. bun:sqlite cannot register
21
- * user-defined SQL functions, so passing any throws
22
- * SqliteFunctionsUnsupportedError here rather than leaving the queries that
23
- * call them to fail with "no such function" later.
24
- *
25
- * The option is declared — instead of simply absent — so that code shared
26
- * with `createNodeSqliteKysely` still type-checks and the incompatibility
27
- * shows up as an error that explains itself.
28
- */
29
- functions?: SqliteFunctionMap
30
- }
31
-
32
- export function createBunSqliteKysely<DB>(
33
- options: CreateBunSqliteKyselyOptions
34
- ): Kysely<DB> {
35
- const db = new Database(options.filename)
36
- if (options.functions) registerSqliteFunctions(db, options.functions)
37
- const plugins: KyselyPlugin[] = []
38
- if (options.camelCase ?? true) plugins.push(new CamelCasePlugin())
39
- if (options.plugins) plugins.push(...options.plugins)
40
-
41
- return new Kysely<DB>({
42
- dialect: new SqliteDialect({ database: new BunSqliteDatabase(db) }),
43
- plugins,
44
- })
45
- }
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- export {
2
- BunSqliteDatabase,
3
- openBunSqliteDatabase,
4
- } from './bun-sqlite-adapter.js'
5
- export {
6
- createBunSqliteKysely,
7
- type CreateBunSqliteKyselyOptions,
8
- } from './create-bun-sqlite-kysely.js'
9
- export { registerSqliteFunctions } from './register-functions.js'
10
- export {
11
- SqliteFunctionsUnsupportedError,
12
- type SqliteFunctionMap,
13
- } from '@pikku/kysely-sqlite'
14
- export {
15
- createCoercionPlugin,
16
- type ColumnKind,
17
- type CoercionMap,
18
- type CreateCoercionPluginOptions,
19
- } from '@pikku/kysely'
@@ -1,54 +0,0 @@
1
- import { describe, test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { Database } from 'bun:sqlite'
4
- import { SqliteFunctionsUnsupportedError } from '@pikku/kysely-sqlite'
5
- import { registerSqliteFunctions } from './register-functions.js'
6
- import { createBunSqliteKysely } from './create-bun-sqlite-kysely.js'
7
-
8
- describe('registerSqliteFunctions', () => {
9
- test('throws rather than silently skipping registration', () => {
10
- const db = new Database(':memory:')
11
- assert.throws(
12
- () => registerSqliteFunctions(db, { similarity: () => 0 }),
13
- SqliteFunctionsUnsupportedError
14
- )
15
- db.close()
16
- })
17
-
18
- test('names every requested function, so the error says what to port', () => {
19
- const db = new Database(':memory:')
20
- try {
21
- registerSqliteFunctions(db, {
22
- similarity: () => 0,
23
- levenshtein: () => 0,
24
- })
25
- assert.fail('expected a throw')
26
- } catch (error) {
27
- assert.ok(error instanceof SqliteFunctionsUnsupportedError)
28
- assert.deepEqual(error.functionNames, ['similarity', 'levenshtein'])
29
- assert.match(error.message, /similarity, levenshtein/)
30
- assert.match(error.message, /kysely-node-sqlite/)
31
- }
32
- db.close()
33
- })
34
-
35
- // The point of the whole change: the failure has to happen while the app is
36
- // wiring itself up, not on the first request that reaches a query using one.
37
- test('createBunSqliteKysely rejects `functions` at construction', () => {
38
- assert.throws(
39
- () =>
40
- createBunSqliteKysely({
41
- filename: ':memory:',
42
- functions: { similarity: () => 0 },
43
- }),
44
- SqliteFunctionsUnsupportedError
45
- )
46
- })
47
-
48
- test('omitting `functions` is unaffected', async () => {
49
- const db = createBunSqliteKysely<{ t: { id: number } }>({
50
- filename: ':memory:',
51
- })
52
- await db.destroy()
53
- })
54
- })
@@ -1,30 +0,0 @@
1
- import type { Database } from 'bun:sqlite'
2
- import {
3
- SqliteFunctionsUnsupportedError,
4
- type SqliteFunctionMap,
5
- } from '@pikku/kysely-sqlite'
6
-
7
- /**
8
- * The bun counterpart to `@pikku/kysely-node-sqlite`'s function of the same
9
- * name. It always throws: bun:sqlite exposes `loadExtension` but nothing
10
- * equivalent to node:sqlite's `db.function()`, so scalar UDFs cannot be
11
- * registered at all.
12
- *
13
- * It exists precisely so that the failure happens here. Without it the two
14
- * drivers differ silently — code written against node registers its functions,
15
- * the same code on bun does not, and the difference only surfaces as
16
- * `no such function: …` from whichever query calls one, which in practice means
17
- * one endpoint failing in production while the rest of the app looks healthy.
18
- *
19
- * Throwing at wiring time turns that into a startup error naming every function
20
- * involved.
21
- */
22
- export function registerSqliteFunctions(
23
- _db: Database,
24
- functions: SqliteFunctionMap
25
- ): never {
26
- throw new SqliteFunctionsUnsupportedError(
27
- 'bun:sqlite',
28
- Object.keys(functions)
29
- )
30
- }
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": ".",
5
- "module": "Node18",
6
- "outDir": "dist",
7
- "target": "esnext",
8
- "declaration": true,
9
- "types": ["bun"]
10
- },
11
- "include": ["src/**/*.ts"],
12
- "exclude": ["**/*.test.ts", "node_modules"]
13
- }