@pikku/kysely-node-sqlite 0.12.4 → 0.12.7

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,85 @@
1
1
  # @pikku/kysely-node-sqlite
2
2
 
3
+ ## 0.12.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 746ed6a: fix: one coercion plugin, not three
8
+
9
+ The Kysely coercion plugin existed in three copies — the CLI's local database,
10
+ `@pikku/kysely-node-sqlite` and `@pikku/kysely-bun-sqlite` — and all three had
11
+ drifted apart. Only the CLI's resolved a column against the tables the query
12
+ actually named, so two tables that disagree on the kind of a same-named column
13
+ coerced correctly in local development and silently did not at runtime; only
14
+ bun's dropped a genuinely ambiguous column instead of letting the last table
15
+ processed win.
16
+
17
+ The single implementation now lives in `@pikku/kysely`, which all three already
18
+ depended on, and keeps both behaviours: table-qualified resolution first, an
19
+ ambiguity-safe bare-name fallback second.
20
+
21
+ `ColumnKind` — the value type of the generated `coercion.gen.ts` — is
22
+ `'date' | 'bool' | 'json'`. The CLI's fourth member, `uuid`, was never a
23
+ coercion kind: the codegen excludes it from the map by construction, because a
24
+ UUID is a string in both Postgres and SQLite. It is now `AnnotationKind` in the
25
+ CLI, the union a column may declare in `db/annotations.ts`, of which
26
+ `ColumnKind` is the coercible subset.
27
+
28
+ `@pikku/cli` also drops its unused dependency on the Node-only
29
+ `@pikku/kysely-node-sqlite`.
30
+
31
+ - Updated dependencies [5a1a962]
32
+ - Updated dependencies [746ed6a]
33
+ - @pikku/core@0.12.86
34
+ - @pikku/kysely@0.13.18
35
+
36
+ ## 0.12.6
37
+
38
+ ### Patch Changes
39
+
40
+ - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
41
+ - Updated dependencies [7722ceb]
42
+ - Updated dependencies [375c1ff]
43
+ - Updated dependencies [02a70cd]
44
+ - Updated dependencies [aeef159]
45
+ - Updated dependencies [a281de6]
46
+ - Updated dependencies [266e3bc]
47
+ - Updated dependencies [02a70cd]
48
+ - Updated dependencies [786dae5]
49
+ - Updated dependencies [6eef0a0]
50
+ - Updated dependencies [3561d67]
51
+ - Updated dependencies [a91c433]
52
+ - Updated dependencies [02a70cd]
53
+ - Updated dependencies [9537f74]
54
+ - Updated dependencies [2b57ca8]
55
+ - Updated dependencies [266e3bc]
56
+ - Updated dependencies [9fce0f1]
57
+ - Updated dependencies [83683a0]
58
+ - Updated dependencies [456c88b]
59
+ - Updated dependencies [456c88b]
60
+ - Updated dependencies [c127273]
61
+ - @pikku/core@0.12.85
62
+ - @pikku/kysely@0.13.17
63
+ - @pikku/kysely-sqlite@0.12.13
64
+
65
+ ## 0.12.5
66
+
67
+ ### Patch Changes
68
+
69
+ - 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.
70
+ - Updated dependencies [063f43a]
71
+ - Updated dependencies [ce66bf8]
72
+ - Updated dependencies [d0307a8]
73
+ - Updated dependencies [ce66bf8]
74
+ - Updated dependencies [3ad2131]
75
+ - Updated dependencies [b930dca]
76
+ - Updated dependencies [b95e77d]
77
+ - Updated dependencies [fd9d834]
78
+ - Updated dependencies [8978fbd]
79
+ - @pikku/core@0.12.82
80
+ - @pikku/kysely@0.13.15
81
+ - @pikku/kysely-sqlite@0.12.11
82
+
3
83
  ## 0.12.4
4
84
 
5
85
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  export { NodeSqliteDatabase } from './node-sqlite-adapter.js';
2
2
  export { createNodeSqliteKysely, type CreateNodeSqliteKyselyOptions, } from './create-node-sqlite-kysely.js';
3
3
  export { registerSqliteFunctions } from './register-functions.js';
4
- export { SqliteFunctionsUnsupportedError, type SqliteFunction, type SqliteFunctionMap, } from '@pikku/kysely-sqlite';
5
- export { createCoercionPlugin, type CoercionMap, type ColumnKind, type CreateCoercionPluginOptions, } from './coercion-plugin.js';
4
+ export { SqliteFunctionsUnsupportedError, type SqliteFunctionMap, } from '@pikku/kysely-sqlite';
5
+ export { createCoercionPlugin, type ColumnKind, type CoercionMap, type CreateCoercionPluginOptions, } from '@pikku/kysely';
package/dist/src/index.js CHANGED
@@ -2,4 +2,4 @@ export { NodeSqliteDatabase } from './node-sqlite-adapter.js';
2
2
  export { createNodeSqliteKysely, } from './create-node-sqlite-kysely.js';
3
3
  export { registerSqliteFunctions } from './register-functions.js';
4
4
  export { SqliteFunctionsUnsupportedError, } from '@pikku/kysely-sqlite';
5
- export { createCoercionPlugin, } from './coercion-plugin.js';
5
+ export { createCoercionPlugin, } from '@pikku/kysely';
@@ -1 +1 @@
1
- {"root":["../src/coercion-plugin.ts","../src/create-node-sqlite-kysely.ts","../src/index.ts","../src/node-sqlite-adapter.ts","../src/register-functions.ts"],"version":"6.0.3"}
1
+ {"root":["../src/create-node-sqlite-kysely.ts","../src/index.ts","../src/node-sqlite-adapter.ts","../src/register-functions.ts"],"version":"6.0.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely-node-sqlite",
3
- "version": "0.12.4",
3
+ "version": "0.12.7",
4
4
  "description": "Kysely driver for Pikku backed by the built-in node:sqlite module",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -20,20 +20,20 @@
20
20
  "node": ">=22.10"
21
21
  },
22
22
  "dependencies": {
23
- "@pikku/kysely": "^0.13.8",
24
- "@pikku/kysely-sqlite": "^0.12.10",
25
- "kysely": "^0.29.0"
23
+ "@pikku/kysely": "^0.13.18",
24
+ "@pikku/kysely-sqlite": "^0.12.13",
25
+ "kysely": "^0.29.5"
26
26
  },
27
27
  "peerDependencies": {
28
- "@pikku/core": "^0.12.75"
28
+ "@pikku/core": "^0.12.86"
29
29
  },
30
30
  "devDependencies": {
31
- "@pikku/core": "^0.12.75",
31
+ "@pikku/core": "^0.12.86",
32
32
  "@types/node": "^22",
33
- "tsx": "^4.19.2",
33
+ "tsx": "^4.23.12",
34
34
  "typescript": "^6.0.3"
35
35
  },
36
36
  "exports": {
37
37
  ".": "./dist/src/index.js"
38
38
  }
39
- }
39
+ }
package/run-tests.sh CHANGED
File without changes
@@ -0,0 +1,90 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { createNodeSqliteKysely, 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('node-sqlite coercion plugin', () => {
37
+ test('resolves a colliding column against the queried table', async () => {
38
+ const db = createNodeSqliteKysely<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 = createNodeSqliteKysely<{
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
+ })
package/src/index.ts CHANGED
@@ -6,12 +6,11 @@ export {
6
6
  export { registerSqliteFunctions } from './register-functions.js'
7
7
  export {
8
8
  SqliteFunctionsUnsupportedError,
9
- type SqliteFunction,
10
9
  type SqliteFunctionMap,
11
10
  } from '@pikku/kysely-sqlite'
12
11
  export {
13
12
  createCoercionPlugin,
14
- type CoercionMap,
15
13
  type ColumnKind,
14
+ type CoercionMap,
16
15
  type CreateCoercionPluginOptions,
17
- } from './coercion-plugin.js'
16
+ } from '@pikku/kysely'
@@ -1,92 +0,0 @@
1
- import type { KyselyPlugin, UnknownRow } from 'kysely'
2
-
3
- export type ColumnKind = 'date' | 'bool' | 'json'
4
-
5
- /**
6
- * Per-table column kind map. Keys are snake_case table names matching the
7
- * physical SQLite tables; inner keys are snake_case column names.
8
- * Generated by `pikku db migrate` from `-- @date|@bool|@json` annotations
9
- * and naming conventions (col ends in _at/_on → date; starts with is_/has_/can_ → bool).
10
- */
11
- export type CoercionMap = Record<string, Record<string, ColumnKind>>
12
-
13
- export interface CreateCoercionPluginOptions {
14
- map: CoercionMap
15
- }
16
-
17
- function fromDb(value: unknown, kind: ColumnKind): unknown {
18
- if (value == null) return value
19
- switch (kind) {
20
- case 'date':
21
- if (typeof value === 'string') {
22
- const d = new Date(value)
23
- return Number.isNaN(d.getTime()) ? value : d
24
- }
25
- return value
26
- case 'bool':
27
- if (typeof value === 'number') return value !== 0
28
- if (typeof value === 'bigint') return value !== 0n
29
- return value
30
- case 'json':
31
- if (typeof value !== 'string') return value
32
- try {
33
- return JSON.parse(value)
34
- } catch {
35
- return value
36
- }
37
- }
38
- }
39
-
40
- function snakeToCamel(name: string): string {
41
- return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
42
- }
43
-
44
- /**
45
- * Flatten the per-table map into a single column-name → kind lookup.
46
- * Indexed by BOTH snake_case and camelCase so it works regardless of
47
- * CamelCasePlugin ordering in the plugin array.
48
- */
49
- function buildGlobalMap(map: CoercionMap): Record<string, ColumnKind> {
50
- const out: Record<string, ColumnKind> = {}
51
- for (const tbl of Object.values(map)) {
52
- for (const [col, kind] of Object.entries(tbl)) {
53
- out[col] = kind
54
- out[snakeToCamel(col)] = kind
55
- }
56
- }
57
- return out
58
- }
59
-
60
- /**
61
- * Convert SQLite-stored values (TEXT/INTEGER) back into the logical types
62
- * app code expects (Date / boolean / parsed JSON).
63
- *
64
- * Write-side coercion (Date → ISO string, boolean → 0/1, object → JSON) is
65
- * handled in the NodeSqliteDatabase adapter (always-on), so this plugin is
66
- * read-only.
67
- *
68
- * Place AFTER CamelCasePlugin in the plugin array (or it handles both
69
- * orderings via the dual-keyed global map).
70
- */
71
- export function createCoercionPlugin(
72
- options: CreateCoercionPluginOptions
73
- ): KyselyPlugin {
74
- const globalMap = buildGlobalMap(options.map)
75
- return {
76
- transformQuery(args) {
77
- return args.node
78
- },
79
- async transformResult(args) {
80
- const out: UnknownRow[] = []
81
- for (const row of args.result.rows as UnknownRow[]) {
82
- const next: UnknownRow = { ...row }
83
- for (const [col, val] of Object.entries(row)) {
84
- const kind = globalMap[col]
85
- if (kind) next[col] = fromDb(val, kind)
86
- }
87
- out.push(next)
88
- }
89
- return { ...args.result, rows: out }
90
- },
91
- }
92
- }