Skip to content

Commit 05259c1

Browse files
committed
fix(builder): fix compiled return type infer
1 parent b1b46a0 commit 05259c1

File tree

4 files changed

+69
-38
lines changed

4 files changed

+69
-38
lines changed

packages/sqlite-builder/src/builder.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Compilable, CompiledQuery, KyselyPlugin, LogEvent, QueryResult, RawBuilder, Sql, Transaction } from 'kysely'
22
import { Kysely, sql } from 'kysely'
33
import { SqliteSerializePlugin } from 'kysely-plugin-serialize'
4+
import type { SimplifySingleResult } from 'kysely/dist/cjs/util/type-utils'
45
import { parseTableMap, preCompile, runCreateTable } from './utils'
5-
import type { AvailableBuilder, Logger, SqliteBuilderOption, Table } from './types'
6+
import type { AvailableBuilder, BuilderResult, Logger, QueryBuilderOutput, SqliteBuilderOption, Table } from './types'
67
import { Stack } from './stack'
7-
import type { QueryBuilderOutput } from './utils'
88

99
const enum DBStatus {
1010
'needDrop',
@@ -96,15 +96,15 @@ export class SqliteBuilder<DB extends Record<string, any>> {
9696
public async execOne<O>(
9797
cb: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
9898
errorMsg?: string,
99-
): Promise<O | undefined> {
99+
): Promise<SimplifySingleResult<O> | undefined> {
100100
const resultList = await this.execList(cb, errorMsg)
101101
return resultList?.length ? resultList[0] : undefined
102102
}
103103

104104
public async execList<O>(
105105
cb: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
106106
errorMsg?: string,
107-
): Promise<O[] | undefined> {
107+
): Promise<Exclude<SimplifySingleResult<O>, undefined>[] | undefined> {
108108
if (await this.isEmptyTable()) {
109109
return undefined
110110
}
@@ -113,36 +113,35 @@ export class SqliteBuilder<DB extends Record<string, any>> {
113113
.catch((err) => {
114114
errorMsg && this.logger?.error(errorMsg, err)
115115
return undefined
116-
})
116+
}) as any
117117
}
118118

119119
public preCompile<O>(
120120
queryBuilder: (db: Kysely<DB> | Transaction<DB>) => QueryBuilderOutput<Compilable<O>>,
121-
): ReturnType<typeof preCompile<DB, O>> {
122-
return preCompile(this.kysely, queryBuilder)
121+
): ReturnType<typeof preCompile<O>> {
122+
return preCompile(queryBuilder(this.kysely))
123123
}
124124

125-
public async execCompiledOne<O>(
125+
public async execCompiledRows<O>(
126126
query: CompiledQuery<O>,
127127
errorMsg?: string,
128-
): Promise<O | undefined> {
129-
const resultList = await this.execCompiledList(query, errorMsg)
130-
return resultList?.length ? resultList[0] : undefined
128+
): Promise<BuilderResult<O>['rows'] | undefined> {
129+
const result = await this.execCompiled(query, errorMsg)
130+
return result?.rows ?? undefined
131131
}
132132

133-
public async execCompiledList<O>(
133+
public async execCompiled<O>(
134134
query: CompiledQuery<O>,
135135
errorMsg?: string,
136-
): Promise<O[] | undefined> {
136+
): Promise<BuilderResult<O> | undefined> {
137137
if (await this.isEmptyTable()) {
138138
return undefined
139139
}
140140
return this.getDB().executeQuery(query)
141-
.then(result => result.rows)
142141
.catch((err) => {
143142
errorMsg && this.logger?.error(errorMsg, err)
144143
return undefined
145-
})
144+
}) as any
146145
}
147146

148147
public async toSQL<T extends Compilable>(cb: (db: Kysely<DB>) => T): Promise<CompiledQuery<unknown>> {

packages/sqlite-builder/src/types.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DeleteQueryBuilder, Dialect, Generated, InsertQueryBuilder, KyselyPlugin, SelectQueryBuilder, Sql, UpdateQueryBuilder } from 'kysely'
1+
import type { Compilable, DeleteQueryBuilder, Dialect, Generated, InsertQueryBuilder, KyselyPlugin, QueryResult, SelectQueryBuilder, Sql, UpdateQueryBuilder } from 'kysely'
22
import type { CompiledQuery } from 'kysely/dist/cjs/query-compiler/compiled-query'
33

44
export type TriggerEvent = 'insert' | 'update' | 'delete'
@@ -54,7 +54,11 @@ export type Logger = {
5454
}
5555

5656
export type AvailableBuilder<DB, O> =
57-
| SelectQueryBuilder<DB, keyof DB, O>
58-
| UpdateQueryBuilder<DB, keyof DB, keyof DB, O>
59-
| InsertQueryBuilder<DB, keyof DB, O>
60-
| DeleteQueryBuilder<DB, keyof DB, O>
57+
| SelectQueryBuilder<DB, any, O>
58+
| UpdateQueryBuilder<DB, any, any, O>
59+
| InsertQueryBuilder<DB, any, O>
60+
| DeleteQueryBuilder<DB, any, O>
61+
62+
export type QueryBuilderOutput<QB> = QB extends Compilable<infer O> ? O : never
63+
64+
export type BuilderResult<T> = T extends SelectQueryBuilder<any, any, infer P> ? QueryResult<P> : Omit<QueryResult<any>, 'rows'> & { rows: [] }

packages/sqlite-builder/src/utils.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { Compilable, CompiledQuery, Kysely, Transaction } from 'kysely'
1+
import type { Compilable, CompiledQuery, Kysely, RootOperationNode } from 'kysely'
22
import { sql } from 'kysely'
33
import type { DataTypeExpression } from 'kysely/dist/cjs/parser/data-type-parser'
44
import { defaultSerializer } from 'kysely-plugin-serialize'
5-
import type { ColumeOption, Table, Tables, TriggerEvent } from './types'
5+
import type { ColumeOption, QueryBuilderOutput, Table, Tables, TriggerEvent } from './types'
66

77
export function isString(value: any): value is string {
88
return typeof value === 'string'
@@ -143,11 +143,8 @@ export async function runCreateTable<T>(
143143
}
144144
}
145145

146-
export type QueryBuilderOutput<QB> = QB extends Compilable<infer O> ? O : never
147-
148-
export function preCompile<DB, O>(
149-
db: Kysely<DB> | Transaction<DB>,
150-
queryBuilder: (db: Kysely<DB> | Transaction<DB>) => QueryBuilderOutput<Compilable<O>>,
146+
export function preCompile<O>(
147+
queryBuilder: QueryBuilderOutput<Compilable<O>>,
151148
) {
152149
function getParam<P extends Record<string, any>>(name: keyof P): P[keyof P] {
153150
return `__precomile_${name as string}` as unknown as P[keyof P]
@@ -160,12 +157,12 @@ export function preCompile<DB, O>(
160157
) => Compilable<O>,
161158
) {
162159
let compiled: CompiledQuery<Compilable<O>>
163-
return (param: P) => {
160+
return (param: P, processRootOperatorNode?: (node: RootOperationNode) => RootOperationNode): CompiledQuery<O> => {
164161
if (!compiled) {
165-
const { parameters, sql, query } = paramBuilder(queryBuilder(db), getParam).compile()
162+
const { parameters, sql, query } = paramBuilder(queryBuilder, getParam).compile()
166163
compiled = {
167164
sql,
168-
query: { kind: query.kind } as any,
165+
query: processRootOperatorNode?.(query) || { kind: query.kind } as any,
169166
parameters,
170167
}
171168
}

test/builder.test.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SqliteBuilder } from '../packages/sqlite-builder/src'
66

77
interface DB {
88
test: TestTable
9+
pet: PetTable
910
}
1011
interface TestTable {
1112
id: Generated<number>
@@ -15,6 +16,13 @@ interface TestTable {
1516
updateAt: Date | null
1617
buffer: ArrayBuffer | null
1718
}
19+
interface PetTable {
20+
id: Generated<string>
21+
name: string
22+
owner_id: string
23+
species: 'cat' | 'dog'
24+
is_favorite: boolean
25+
}
1826
describe('test builder', async () => {
1927
const db = new SqliteBuilder<DB>({
2028
dialect: new SqliteDialect({
@@ -36,18 +44,30 @@ describe('test builder', async () => {
3644
timestamp: true,
3745
},
3846
},
47+
pet: {
48+
columns: {
49+
id: { type: 'increments' },
50+
is_favorite: { type: 'boolean' },
51+
name: { type: 'string' },
52+
owner_id: { type: 'string' },
53+
species: { type: 'string' },
54+
},
55+
},
3956
},
4057
dropTableBeforeInit: true,
4158
logger: console,
42-
onQuery: (queryInfo, time) => console.log(`${time}ms`, queryInfo.sql, queryInfo.parameters),
59+
// onQuery: (queryInfo, time) => console.log(`${time}ms`, queryInfo.sql, queryInfo.parameters),
4360
})
4461
beforeAll(async () => {
4562
// manually generate table
4663
await db.init(true)
4764
})
4865
test('insert', async () => {
4966
// auto generate table
50-
await db.transaction(trx => trx.insertInto('test').values({ gender: false }).execute())
67+
console.log(await db.transaction((trx) => {
68+
trx.insertInto('test').values([{ gender: false }, { gender: true }]).execute()
69+
return trx.updateTable('test').set({ gender: true }).where('id', '=', 2).execute()
70+
}))
5171
const result = await db.execList(d => d.selectFrom('test').selectAll())
5272
expect(result).toBeInstanceOf(Array)
5373
expect(result![0].person).toStrictEqual({ name: 'test' })
@@ -62,31 +82,42 @@ describe('test builder', async () => {
6282
expect(result2!.updateAt).toBeInstanceOf(Date)
6383
})
6484
test('raw', async () => {
65-
const { sql, parameters } = await db.toSQL(d => d
85+
const query = await db.toSQL(d => d
6686
.selectFrom('test')
6787
.where('person', '=', { name: '1' })
6888
.selectAll(),
6989
)
70-
expect(sql).toBe('select * from "test" where "person" = ?')
71-
expect(parameters).toStrictEqual(['{"name":"1"}'])
90+
expect(query.sql).toBe('select * from "test" where "person" = ?')
91+
expect(query.parameters).toStrictEqual(['{"name":"1"}'])
92+
const result = await db.execCompiled(query)
93+
console.log(result)
7294
})
7395
test('preCompile', async () => {
74-
const fn = db.preCompile(db => db.selectFrom('test').selectAll())
96+
const select = db.preCompile(db => db.selectFrom('test').selectAll())
7597
.setParam<{ person: { name: string } }>((qb, param) => qb.where('person', '=', param('person')))
98+
const insert = db.preCompile(db => db.insertInto('test'))
99+
.setParam<{ gender: boolean }>((qb, param) => qb.values({ gender: param('gender') }))
100+
const update = db.preCompile(db => db.updateTable('test'))
101+
.setParam<{ gender: boolean }>((qb, param) => qb.set({ gender: param('gender') }).where('id', '=', 1))
76102

77103
const start = performance.now()
78104

79-
const { parameters, sql } = fn({ person: { name: '1' } })
105+
const { parameters, sql } = select({ person: { name: '1' } })
80106
expect(sql).toBe('select * from "test" where "person" = ?')
81107
expect(parameters).toStrictEqual(['{"name":"1"}'])
82108

83109
const start2 = performance.now()
84110
console.log('no compiled:', `${(start2 - start).toFixed(2)}ms`)
85111

86-
const { parameters: p1, sql: s1 } = fn({ person: { name: 'test' } })
112+
const { parameters: p1, sql: s1 } = select({ person: { name: 'test' } })
87113
expect(s1).toBe('select * from "test" where "person" = ?')
88114
expect(p1).toStrictEqual(['{"name":"test"}'])
89115

90116
console.log(' compiled:', `${(performance.now() - start2).toFixed(2)}ms`)
117+
118+
const result = await db.execCompiled(insert({ gender: true }))
119+
expect(result!.rows).toStrictEqual([])
120+
const result2 = await db.execCompiled(update({ gender: false }))
121+
expect(result2!.rows).toStrictEqual([])
91122
})
92123
})

0 commit comments

Comments
 (0)