@pikku/kysely-bun-sqlite 0.12.8 → 0.12.9
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.
- package/CHANGELOG.md +13 -0
- package/dist/src/bun-sqlite-adapter.d.ts +12 -1
- package/dist/src/bun-sqlite-adapter.js +12 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/package.json +3 -3
- package/src/bun-sqlite-adapter.test.ts +38 -2
- package/src/bun-sqlite-adapter.ts +14 -1
- package/src/index.ts +4 -1
- package/dist/src/coercion-plugin.d.ts +0 -24
- package/dist/src/coercion-plugin.js +0 -92
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @pikku/kysely-bun-sqlite
|
|
2
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
|
+
|
|
3
16
|
## 0.12.8
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
2
|
import type { SqliteDatabase, SqliteStatement } from 'kysely';
|
|
3
3
|
export declare class BunSqliteDatabase implements SqliteDatabase {
|
|
4
4
|
private readonly db;
|
|
@@ -6,3 +6,14 @@ export declare class BunSqliteDatabase implements SqliteDatabase {
|
|
|
6
6
|
prepare(sql: string): SqliteStatement;
|
|
7
7
|
close(): void;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Opens a `bun:sqlite` database as the `SqliteDatabase` Kysely's `SqliteDialect`
|
|
11
|
+
* binds to.
|
|
12
|
+
*
|
|
13
|
+
* `createBunSqliteKysely` covers the common case of one database behind one
|
|
14
|
+
* Kysely. This is for the case it cannot express: several Kysely instances —
|
|
15
|
+
* differing in their plugins — sharing a single underlying database, which is
|
|
16
|
+
* what lets a plugin-free instance (Better Auth needs unmangled column names)
|
|
17
|
+
* and a CamelCase one address the same tables.
|
|
18
|
+
*/
|
|
19
|
+
export declare const openBunSqliteDatabase: (filename: string) => SqliteDatabase;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
1
2
|
function coerce(v) {
|
|
2
3
|
if (v === null || v === undefined)
|
|
3
4
|
return null;
|
|
@@ -45,3 +46,14 @@ export class BunSqliteDatabase {
|
|
|
45
46
|
this.db.close();
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Opens a `bun:sqlite` database as the `SqliteDatabase` Kysely's `SqliteDialect`
|
|
51
|
+
* binds to.
|
|
52
|
+
*
|
|
53
|
+
* `createBunSqliteKysely` covers the common case of one database behind one
|
|
54
|
+
* Kysely. This is for the case it cannot express: several Kysely instances —
|
|
55
|
+
* differing in their plugins — sharing a single underlying database, which is
|
|
56
|
+
* what lets a plugin-free instance (Better Auth needs unmangled column names)
|
|
57
|
+
* and a CamelCase one address the same tables.
|
|
58
|
+
*/
|
|
59
|
+
export const openBunSqliteDatabase = (filename) => new BunSqliteDatabase(new Database(filename));
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BunSqliteDatabase } from './bun-sqlite-adapter.js';
|
|
1
|
+
export { BunSqliteDatabase, openBunSqliteDatabase, } from './bun-sqlite-adapter.js';
|
|
2
2
|
export { createBunSqliteKysely, type CreateBunSqliteKyselyOptions, } from './create-bun-sqlite-kysely.js';
|
|
3
3
|
export { registerSqliteFunctions } from './register-functions.js';
|
|
4
4
|
export { SqliteFunctionsUnsupportedError, type SqliteFunctionMap, } from '@pikku/kysely-sqlite';
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BunSqliteDatabase } from './bun-sqlite-adapter.js';
|
|
1
|
+
export { BunSqliteDatabase, openBunSqliteDatabase, } from './bun-sqlite-adapter.js';
|
|
2
2
|
export { createBunSqliteKysely, } from './create-bun-sqlite-kysely.js';
|
|
3
3
|
export { registerSqliteFunctions } from './register-functions.js';
|
|
4
4
|
export { SqliteFunctionsUnsupportedError, } from '@pikku/kysely-sqlite';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely-bun-sqlite",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"description": "Kysely driver for Pikku backed by the built-in bun:sqlite module",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"kysely": "^0.29.5"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@pikku/core": "^0.12.
|
|
27
|
+
"@pikku/core": "^0.12.105"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@pikku/core": "^0.12.
|
|
30
|
+
"@pikku/core": "^0.12.105",
|
|
31
31
|
"@types/bun": "^1.4.0",
|
|
32
32
|
"typescript": "^6.0.3"
|
|
33
33
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { describe, test, beforeEach, afterEach } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import { Database } from 'bun:sqlite'
|
|
4
|
-
import { Kysely, SqliteDialect } from 'kysely'
|
|
5
|
-
import {
|
|
4
|
+
import { Kysely, SqliteDialect, CamelCasePlugin } from 'kysely'
|
|
5
|
+
import {
|
|
6
|
+
BunSqliteDatabase,
|
|
7
|
+
openBunSqliteDatabase,
|
|
8
|
+
} from './bun-sqlite-adapter.js'
|
|
6
9
|
|
|
7
10
|
interface TestDB {
|
|
8
11
|
items: { id: number; name: string; active: number; score: number | null }
|
|
@@ -174,3 +177,36 @@ describe('BunSqliteDatabase', () => {
|
|
|
174
177
|
assert.equal(updated.numUpdatedRows, 1n)
|
|
175
178
|
})
|
|
176
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,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Database, type Statement } from 'bun:sqlite'
|
|
2
2
|
import type { SqliteDatabase, SqliteStatement } from 'kysely'
|
|
3
3
|
|
|
4
4
|
function coerce(v: unknown): unknown {
|
|
@@ -49,3 +49,16 @@ export class BunSqliteDatabase implements SqliteDatabase {
|
|
|
49
49
|
this.db.close()
|
|
50
50
|
}
|
|
51
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))
|
package/src/index.ts
CHANGED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { KyselyPlugin } from 'kysely';
|
|
2
|
-
export type ColumnKind = 'date' | 'bool' | 'json';
|
|
3
|
-
/**
|
|
4
|
-
* Per-table column kind map. Keys are snake_case table names matching the
|
|
5
|
-
* physical SQLite tables; inner keys are snake_case column names.
|
|
6
|
-
* Generated by `pikku db migrate` from `-- @date|@bool|@json` annotations
|
|
7
|
-
* and naming conventions (col ends in _at/_on → date; starts with is_/has_/can_ → bool).
|
|
8
|
-
*/
|
|
9
|
-
export type CoercionMap = Record<string, Record<string, ColumnKind>>;
|
|
10
|
-
export interface CreateCoercionPluginOptions {
|
|
11
|
-
map: CoercionMap;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Convert SQLite-stored values (TEXT/INTEGER) back into the logical types
|
|
15
|
-
* app code expects (Date / boolean / parsed JSON).
|
|
16
|
-
*
|
|
17
|
-
* Write-side coercion (Date → ISO string, boolean → 0/1, object → JSON) is
|
|
18
|
-
* handled in the NodeSqliteDatabase adapter (always-on), so this plugin is
|
|
19
|
-
* read-only.
|
|
20
|
-
*
|
|
21
|
-
* Place AFTER CamelCasePlugin in the plugin array (or it handles both
|
|
22
|
-
* orderings via the dual-keyed global map).
|
|
23
|
-
*/
|
|
24
|
-
export declare function createCoercionPlugin(options: CreateCoercionPluginOptions): KyselyPlugin;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
function fromDb(value, kind) {
|
|
2
|
-
if (value == null)
|
|
3
|
-
return value;
|
|
4
|
-
switch (kind) {
|
|
5
|
-
case 'date':
|
|
6
|
-
if (typeof value === 'string') {
|
|
7
|
-
const d = new Date(value);
|
|
8
|
-
return Number.isNaN(d.getTime()) ? value : d;
|
|
9
|
-
}
|
|
10
|
-
return value;
|
|
11
|
-
case 'bool':
|
|
12
|
-
if (typeof value === 'number')
|
|
13
|
-
return value !== 0;
|
|
14
|
-
if (typeof value === 'bigint')
|
|
15
|
-
return value !== 0n;
|
|
16
|
-
return value;
|
|
17
|
-
case 'json':
|
|
18
|
-
if (typeof value !== 'string')
|
|
19
|
-
return value;
|
|
20
|
-
try {
|
|
21
|
-
return JSON.parse(value);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function snakeToCamel(name) {
|
|
29
|
-
return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Flatten the per-table map into a single column-name → kind lookup.
|
|
33
|
-
* Indexed by BOTH snake_case and camelCase so it works regardless of
|
|
34
|
-
* CamelCasePlugin ordering in the plugin array.
|
|
35
|
-
*
|
|
36
|
-
* When two tables disagree on the kind for the same column name, the column
|
|
37
|
-
* is ambiguous (a joined/aliased query could carry either) and is left
|
|
38
|
-
* uncoerced rather than letting whichever table was processed last win.
|
|
39
|
-
*/
|
|
40
|
-
function buildGlobalMap(map) {
|
|
41
|
-
const out = {};
|
|
42
|
-
const ambiguous = new Set();
|
|
43
|
-
const assign = (key, kind) => {
|
|
44
|
-
const existing = out[key];
|
|
45
|
-
if (existing !== undefined && existing !== kind) {
|
|
46
|
-
ambiguous.add(key);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
out[key] = kind;
|
|
50
|
-
};
|
|
51
|
-
for (const tbl of Object.values(map)) {
|
|
52
|
-
for (const [col, kind] of Object.entries(tbl)) {
|
|
53
|
-
assign(col, kind);
|
|
54
|
-
assign(snakeToCamel(col), kind);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
for (const key of ambiguous)
|
|
58
|
-
delete out[key];
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Convert SQLite-stored values (TEXT/INTEGER) back into the logical types
|
|
63
|
-
* app code expects (Date / boolean / parsed JSON).
|
|
64
|
-
*
|
|
65
|
-
* Write-side coercion (Date → ISO string, boolean → 0/1, object → JSON) is
|
|
66
|
-
* handled in the NodeSqliteDatabase adapter (always-on), so this plugin is
|
|
67
|
-
* read-only.
|
|
68
|
-
*
|
|
69
|
-
* Place AFTER CamelCasePlugin in the plugin array (or it handles both
|
|
70
|
-
* orderings via the dual-keyed global map).
|
|
71
|
-
*/
|
|
72
|
-
export function createCoercionPlugin(options) {
|
|
73
|
-
const globalMap = buildGlobalMap(options.map);
|
|
74
|
-
return {
|
|
75
|
-
transformQuery(args) {
|
|
76
|
-
return args.node;
|
|
77
|
-
},
|
|
78
|
-
async transformResult(args) {
|
|
79
|
-
const out = [];
|
|
80
|
-
for (const row of args.result.rows) {
|
|
81
|
-
const next = { ...row };
|
|
82
|
-
for (const [col, val] of Object.entries(row)) {
|
|
83
|
-
const kind = globalMap[col];
|
|
84
|
-
if (kind)
|
|
85
|
-
next[col] = fromDb(val, kind);
|
|
86
|
-
}
|
|
87
|
-
out.push(next);
|
|
88
|
-
}
|
|
89
|
-
return { ...args.result, rows: out };
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|