@pikku/kysely-bun-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 +80 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -5
- package/src/coercion-plugin.test.ts +74 -79
- package/src/index.ts +2 -1
- package/src/coercion-plugin.ts +0 -106
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# @pikku/kysely-bun-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
|
+
- 0686513: Declare `@pikku/core` as a peer dependency, matching `@pikku/kysely-sqlite`.
|
|
70
|
+
|
|
71
|
+
The package depends on `@pikku/kysely-sqlite`, which requires `@pikku/core` as a
|
|
72
|
+
peer, but never declared that requirement itself — so an install resolved
|
|
73
|
+
without complaint and the missing peer only surfaced at runtime. It is now
|
|
74
|
+
declared the same way its sibling declares it.
|
|
75
|
+
|
|
76
|
+
- Updated dependencies [7406bfe]
|
|
77
|
+
- Updated dependencies [6794681]
|
|
78
|
+
- Updated dependencies [a7fcd2e]
|
|
79
|
+
- @pikku/core@0.12.84
|
|
80
|
+
- @pikku/kysely@0.13.16
|
|
81
|
+
- @pikku/kysely-sqlite@0.12.12
|
|
82
|
+
|
|
3
83
|
## 0.12.4
|
|
4
84
|
|
|
5
85
|
### Patch Changes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { BunSqliteDatabase } 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';
|
|
5
|
-
export { createCoercionPlugin, type ColumnKind, type CreateCoercionPluginOptions, } from '
|
|
5
|
+
export { createCoercionPlugin, type ColumnKind, type CoercionMap, type CreateCoercionPluginOptions, } from '@pikku/kysely';
|
package/dist/src/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { BunSqliteDatabase } 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';
|
|
5
|
-
export { createCoercionPlugin, } from '
|
|
5
|
+
export { createCoercionPlugin, } from '@pikku/kysely';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/bun-sqlite-adapter.ts","../src/
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely-bun-sqlite",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
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",
|
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
"bun": ">=1.0.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@pikku/kysely": "^0.13.
|
|
23
|
-
"@pikku/kysely-sqlite": "^0.12.
|
|
24
|
-
"kysely": "^0.29.
|
|
22
|
+
"@pikku/kysely": "^0.13.18",
|
|
23
|
+
"@pikku/kysely-sqlite": "^0.12.13",
|
|
24
|
+
"kysely": "^0.29.5"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@pikku/core": "^0.12.86"
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
30
|
+
"@pikku/core": "^0.12.86",
|
|
27
31
|
"@types/bun": "latest",
|
|
28
32
|
"typescript": "^6.0.3"
|
|
29
33
|
}
|
|
30
|
-
}
|
|
34
|
+
}
|
|
@@ -1,95 +1,90 @@
|
|
|
1
1
|
import { describe, test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import { createBunSqliteKysely, createCoercionPlugin } from './index.js'
|
|
4
|
+
import type { CoercionMap } from './index.js'
|
|
5
|
+
import { sql, type Kysely } from 'kysely'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
): Promise<UnknownRow[]> => {
|
|
10
|
-
const plugin = createCoercionPlugin({ map })
|
|
11
|
-
const result = { rows } as QueryResult<UnknownRow>
|
|
12
|
-
const out = await plugin.transformResult({
|
|
13
|
-
result,
|
|
14
|
-
queryId: { queryId: 'test' },
|
|
15
|
-
} as Parameters<NonNullable<typeof plugin.transformResult>>[0])
|
|
16
|
-
return out.rows as UnknownRow[]
|
|
7
|
+
interface TestDB {
|
|
8
|
+
users: { id: number; settings: unknown }
|
|
9
|
+
posts: { id: number; settings: unknown }
|
|
17
10
|
}
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
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
|
+
}
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
}
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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)
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
assert.equal(rows[0].is_active, true)
|
|
53
|
-
assert.equal(rows[0].has_pets, false)
|
|
54
|
-
})
|
|
44
|
+
const user = await db
|
|
45
|
+
.selectFrom('users')
|
|
46
|
+
.select('settings')
|
|
47
|
+
.executeTakeFirstOrThrow()
|
|
48
|
+
assert.deepEqual(user.settings, { theme: 'dark' })
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
assert.deepEqual(rows[0].meta, { a: 1 })
|
|
62
|
-
assert.equal(rows[0].broken, '{bad')
|
|
63
|
-
})
|
|
50
|
+
const post = await db
|
|
51
|
+
.selectFrom('posts')
|
|
52
|
+
.select('settings')
|
|
53
|
+
.executeTakeFirstOrThrow()
|
|
54
|
+
assert.equal(post.settings, true)
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
const rows = await runTransform({ users: { created_at: 'date' } }, [
|
|
67
|
-
{ created_at: null, name: 'leave me' },
|
|
68
|
-
])
|
|
69
|
-
assert.equal(rows[0].created_at, null)
|
|
70
|
-
assert.equal(rows[0].name, 'leave me')
|
|
56
|
+
await db.destroy()
|
|
71
57
|
})
|
|
72
58
|
|
|
73
|
-
test('
|
|
74
|
-
const
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
84
75
|
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
test('still coerces columns that agree across tables', async () => {
|
|
89
|
-
const rows = await runTransform(
|
|
90
|
-
{ users: { active: 'bool' }, events: { active: 'bool' } },
|
|
91
|
-
[{ active: 1 }]
|
|
76
|
+
await sql`insert into rows values ('2026-06-26T00:00:00.000Z', 0, '{"a":1}')`.execute(
|
|
77
|
+
db
|
|
92
78
|
)
|
|
93
|
-
|
|
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()
|
|
94
89
|
})
|
|
95
90
|
})
|
package/src/index.ts
CHANGED
package/src/coercion-plugin.ts
DELETED
|
@@ -1,106 +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
|
-
* When two tables disagree on the kind for the same column name, the column
|
|
50
|
-
* is ambiguous (a joined/aliased query could carry either) and is left
|
|
51
|
-
* uncoerced rather than letting whichever table was processed last win.
|
|
52
|
-
*/
|
|
53
|
-
function buildGlobalMap(map: CoercionMap): Record<string, ColumnKind> {
|
|
54
|
-
const out: Record<string, ColumnKind> = {}
|
|
55
|
-
const ambiguous = new Set<string>()
|
|
56
|
-
const assign = (key: string, kind: ColumnKind) => {
|
|
57
|
-
const existing = out[key]
|
|
58
|
-
if (existing !== undefined && existing !== kind) {
|
|
59
|
-
ambiguous.add(key)
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
out[key] = kind
|
|
63
|
-
}
|
|
64
|
-
for (const tbl of Object.values(map)) {
|
|
65
|
-
for (const [col, kind] of Object.entries(tbl)) {
|
|
66
|
-
assign(col, kind)
|
|
67
|
-
assign(snakeToCamel(col), kind)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const key of ambiguous) delete out[key]
|
|
71
|
-
return out
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Convert SQLite-stored values (TEXT/INTEGER) back into the logical types
|
|
76
|
-
* app code expects (Date / boolean / parsed JSON).
|
|
77
|
-
*
|
|
78
|
-
* Write-side coercion (Date → ISO string, boolean → 0/1, object → JSON) is
|
|
79
|
-
* handled in the NodeSqliteDatabase adapter (always-on), so this plugin is
|
|
80
|
-
* read-only.
|
|
81
|
-
*
|
|
82
|
-
* Place AFTER CamelCasePlugin in the plugin array (or it handles both
|
|
83
|
-
* orderings via the dual-keyed global map).
|
|
84
|
-
*/
|
|
85
|
-
export function createCoercionPlugin(
|
|
86
|
-
options: CreateCoercionPluginOptions
|
|
87
|
-
): KyselyPlugin {
|
|
88
|
-
const globalMap = buildGlobalMap(options.map)
|
|
89
|
-
return {
|
|
90
|
-
transformQuery(args) {
|
|
91
|
-
return args.node
|
|
92
|
-
},
|
|
93
|
-
async transformResult(args) {
|
|
94
|
-
const out: UnknownRow[] = []
|
|
95
|
-
for (const row of args.result.rows as UnknownRow[]) {
|
|
96
|
-
const next: UnknownRow = { ...row }
|
|
97
|
-
for (const [col, val] of Object.entries(row)) {
|
|
98
|
-
const kind = globalMap[col]
|
|
99
|
-
if (kind) next[col] = fromDb(val, kind)
|
|
100
|
-
}
|
|
101
|
-
out.push(next)
|
|
102
|
-
}
|
|
103
|
-
return { ...args.result, rows: out }
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
}
|