@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 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 type { Database } from 'bun:sqlite';
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));
@@ -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.8",
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.86"
27
+ "@pikku/core": "^0.12.105"
28
28
  },
29
29
  "devDependencies": {
30
- "@pikku/core": "^0.12.86",
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 { BunSqliteDatabase } from './bun-sqlite-adapter.js'
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 type { Database, Statement } from 'bun:sqlite'
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,4 +1,7 @@
1
- export { BunSqliteDatabase } from './bun-sqlite-adapter.js'
1
+ export {
2
+ BunSqliteDatabase,
3
+ openBunSqliteDatabase,
4
+ } from './bun-sqlite-adapter.js'
2
5
  export {
3
6
  createBunSqliteKysely,
4
7
  type CreateBunSqliteKyselyOptions,
@@ -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
- }