Skip to content

Commit 37e4ee1

Browse files
committed
feat(builder): destroy(), setParam() parameter change, type & jsdoc improve
1 parent 2920df6 commit 37e4ee1

File tree

7 files changed

+166
-66
lines changed

7 files changed

+166
-66
lines changed

packages/sqlite-builder/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"kysely-plugin-serialize": "workspace:*"
4242
},
4343
"peerDependencies": {
44-
"kysely": "^0.26.1"
44+
"kysely": "^0.26.3"
4545
}
4646
}

packages/sqlite-builder/src/builder.ts

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
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'
5-
import { parseTableMap, preCompile, runCreateTable } from './utils'
6-
import type { AvailableBuilder, BuilderResult, Logger, QueryBuilderOutput, SqliteBuilderOption, Table } from './types'
4+
import type { Simplify } from 'kysely/dist/cjs/util/type-utils'
5+
import { parseTableMap, precompileQuery, runCreateTable } from './utils'
6+
import type { AvailableBuilder, Logger, QueryBuilderOutput, QueryBuilderResult, SqliteBuilderOption, Table } from './types'
77
import { Stack } from './stack'
88

99
type DBStatus =
10-
| 'needDrop'
11-
| 'noNeedDrop'
12-
| 'ready'
10+
| 'needDrop'
11+
| 'noNeedDrop'
12+
| 'ready'
13+
| 'destroy'
1314

1415
export class SqliteBuilder<DB extends Record<string, any>> {
1516
public kysely: Kysely<DB>
@@ -45,20 +46,27 @@ export class SqliteBuilder<DB extends Record<string, any>> {
4546
return this
4647
}
4748

48-
private async isEmptyTable(): Promise<boolean> {
49+
private async isFailToInitDB(): Promise<boolean> {
50+
if (this.status === 'destroy') {
51+
this.logger?.error('DB have been destroyed')
52+
return true
53+
}
4954
this.status !== 'ready' && await this.init()
5055
if (this.status === 'ready') {
5156
return false
5257
}
53-
this.logger?.error('fail to init table')
58+
this.logger?.error('fail to init DB')
5459
return true
5560
}
5661

62+
/**
63+
* execute sql in transaction, support nest transactions
64+
*/
5765
public async transaction<T>(
5866
cb: (trx: Transaction<DB>) => Promise<T>,
5967
errorMsg?: string,
6068
): Promise<T | undefined> {
61-
if (await this.isEmptyTable()) {
69+
if (await this.isFailToInitDB()) {
6270
return undefined
6371
}
6472
return await this.kysely.transaction()
@@ -79,11 +87,14 @@ export class SqliteBuilder<DB extends Record<string, any>> {
7987
return this.trxs.isEmpty() ? this.kysely : this.trxs.peek()
8088
}
8189

90+
/**
91+
* execute query manually, auto detect transaction
92+
*/
8293
public async exec<O>(
8394
cb: (db: Kysely<DB> | Transaction<DB>) => Promise<O>,
8495
errorMsg?: string,
8596
): Promise<O | undefined> {
86-
if (await this.isEmptyTable()) {
97+
if (await this.isFailToInitDB()) {
8798
return undefined
8899
}
89100
return cb(this.getDB())
@@ -93,48 +104,63 @@ export class SqliteBuilder<DB extends Record<string, any>> {
93104
})
94105
}
95106

107+
/**
108+
* execute query and auto return first result, auto detect transaction
109+
*/
96110
public async execOne<O>(
97111
cb: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
98112
errorMsg?: string,
99-
): Promise<SimplifySingleResult<O> | undefined> {
113+
): Promise<Simplify<O> | undefined> {
100114
const resultList = await this.execList(cb, errorMsg)
101115
return resultList?.length ? resultList[0] : undefined
102116
}
103117

118+
/**
119+
* execute query and auto return results, auto detect transaction
120+
*/
104121
public async execList<O>(
105122
cb: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
106123
errorMsg?: string,
107-
): Promise<Exclude<SimplifySingleResult<O>, undefined>[] | undefined> {
108-
if (await this.isEmptyTable()) {
124+
): Promise<Simplify<O>[] | undefined> {
125+
if (await this.isFailToInitDB()) {
109126
return undefined
110127
}
111128
return cb(this.getDB())
112129
.execute()
113130
.catch((err) => {
114131
errorMsg && this.logger?.error(errorMsg, err)
115132
return undefined
116-
}) as any
133+
})
117134
}
118135

119-
public preCompile<O>(
136+
/**
137+
* see {@link precompileQuery}
138+
*/
139+
public precompile<O>(
120140
queryBuilder: (db: Kysely<DB> | Transaction<DB>) => QueryBuilderOutput<Compilable<O>>,
121-
): ReturnType<typeof preCompile<O>> {
122-
return preCompile(queryBuilder(this.kysely))
141+
): ReturnType<typeof precompileQuery<O>> {
142+
return precompileQuery(queryBuilder(this.kysely))
123143
}
124144

145+
/**
146+
* exec compiled query, and return rows in result, auto detect transaction, useful for select
147+
*/
125148
public async execCompiledRows<O>(
126149
query: CompiledQuery<O>,
127150
errorMsg?: string,
128-
): Promise<BuilderResult<O>['rows'] | undefined> {
151+
): Promise<QueryBuilderResult<O>['rows'] | undefined> {
129152
const result = await this.execCompiled(query, errorMsg)
130-
return result?.rows ?? undefined
153+
return result?.rows?.length ? result.rows : undefined
131154
}
132155

156+
/**
157+
* exec compiled query, return result, auto detect transaction
158+
*/
133159
public async execCompiled<O>(
134160
query: CompiledQuery<O>,
135161
errorMsg?: string,
136-
): Promise<BuilderResult<O> | undefined> {
137-
if (await this.isEmptyTable()) {
162+
): Promise<QueryBuilderResult<O> | undefined> {
163+
if (await this.isFailToInitDB()) {
138164
return undefined
139165
}
140166
return this.getDB().executeQuery(query)
@@ -144,14 +170,31 @@ export class SqliteBuilder<DB extends Record<string, any>> {
144170
}) as any
145171
}
146172

173+
/**
174+
* compile query builder
175+
*/
147176
public async toSQL<T extends Compilable>(cb: (db: Kysely<DB>) => T): Promise<CompiledQuery<unknown>> {
148-
return cb(this.getDB()).compile()
177+
return cb(this.kysely).compile()
149178
}
150179

151-
public async raw<T = any>(rawSql: (s: Sql) => RawBuilder<T>): Promise<QueryResult<T> | undefined> {
152-
if (await this.isEmptyTable()) {
180+
/**
181+
* execute raw sql, auto detect transaction
182+
* @see {@link Sql}
183+
*/
184+
public async raw<O = any>(rawSql: (s: Sql) => RawBuilder<O>): Promise<QueryResult<O> | undefined> {
185+
if (await this.isFailToInitDB()) {
153186
return undefined
154187
}
155188
return rawSql(sql).execute(this.getDB())
156189
}
190+
191+
/**
192+
* destroy db connection
193+
*/
194+
public async destroy() {
195+
await this.kysely.destroy()
196+
this.status = 'destroy'
197+
this.tableMap.clear()
198+
this.trxs.clear()
199+
}
157200
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export * from './builder'
22
export * from './types'
3-
export { createTimeTrigger, parseTableMap, runCreateTable, preCompile } from './utils'
3+
export { createTimeTrigger, parseTableMap, runCreateTable, precompileQuery } from './utils'

packages/sqlite-builder/src/stack.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ export class Stack<T> {
2424
size() {
2525
return this.items.length
2626
}
27+
28+
clear() {
29+
while (this.size()) {
30+
this.pop()
31+
}
32+
}
2733
}

packages/sqlite-builder/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,7 @@ export type AvailableBuilder<DB, O> =
6161

6262
export type QueryBuilderOutput<QB> = QB extends Compilable<infer O> ? O : never
6363

64-
export type BuilderResult<T> = T extends SelectQueryBuilder<any, any, infer P> ? QueryResult<P> : Omit<QueryResult<any>, 'rows'> & { rows: [] }
64+
export type QueryBuilderResult<T> =
65+
T extends SelectQueryBuilder<any, any, infer P>
66+
? QueryResult<P>
67+
: Omit<QueryResult<any>, 'rows'> & { rows: [] }

packages/sqlite-builder/src/utils.ts

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,38 +145,77 @@ export async function runCreateTable<T>(
145145
}
146146
}
147147

148-
export function preCompile<O>(
148+
/**
149+
* create precompiled query, inspired by {@link https://github.com/jtlapp/kysely-params kysely-params}
150+
* @example
151+
* ```ts
152+
* const query = precompileQuery(
153+
* db.selectFrom('test').selectAll(),
154+
* ).setParam<{ name: string }>((qb, param) =>
155+
* qb.where('name', '=', param('name')),
156+
* )
157+
* const compiledQuery = query({ name: 'test' })
158+
* // {
159+
* // sql: 'select * from "test" where "name" = ?',
160+
* // parameters: ['test'],
161+
* // query: { kind: 'SelectQueryNode' }
162+
* // }
163+
* ```
164+
*/
165+
export function precompileQuery<O>(
149166
queryBuilder: QueryBuilderOutput<Compilable<O>>,
150167
) {
151-
function getParam<P extends Record<string, any>>(name: keyof P): P[keyof P] {
152-
return `__precomile_${name as string}` as unknown as P[keyof P]
153-
}
154-
return {
155-
setParam<P extends Record<string, any>>(
156-
paramBuilder: (
157-
queryBuilder: QueryBuilderOutput<Compilable<O>>,
158-
param: typeof getParam<P>
159-
) => Compilable<O>,
160-
) {
161-
let compiled: CompiledQuery<Compilable<O>>
162-
return (param: P, processRootOperatorNode?: (node: RootOperationNode) => RootOperationNode): CompiledQuery<O> => {
163-
if (!compiled) {
164-
const { parameters, sql, query } = paramBuilder(queryBuilder, getParam).compile()
165-
compiled = {
166-
sql,
167-
query: processRootOperatorNode?.(query) || { kind: query.kind } as any,
168-
parameters,
169-
}
170-
}
171-
return {
172-
...compiled,
173-
parameters: compiled.parameters.map(p =>
174-
(typeof p === 'string' && p.startsWith('__precomile_'))
175-
? defaultSerializer(param[p.slice(12)])
176-
: p,
177-
),
178-
}
168+
const getParam = <T extends Record<string, any>>(
169+
name: keyof T,
170+
): T[keyof T] => `__precomile_${name as string}` as unknown as T[keyof T]
171+
type SetParam<T extends Record<string, any>> = {
172+
/**
173+
* query builder for setup params
174+
*/
175+
qb: QueryBuilderOutput<Compilable<O>>
176+
/**
177+
* param builder
178+
*/
179+
param: typeof getParam<T>
179180
}
180-
},
181-
}
181+
/**
182+
* @param param custom params
183+
* @param processRootOperatorNode process `query` in {@link CompiledQuery}
184+
* @default (node) => ({ kind: node.kind })
185+
*/
186+
type CompileFn<T extends Record<string, any>> = (
187+
param: T,
188+
processRootOperatorNode?: ((node: RootOperationNode) => RootOperationNode)
189+
) => CompiledQuery<O>
190+
191+
return {
192+
/**
193+
* setup params
194+
* @param paramBuilder param builder
195+
* @returns function to {@link CompileFn compile}
196+
*/
197+
setParam: <T extends Record<string, any>>(
198+
paramBuilder: ({ param, qb }: SetParam<T>) => Compilable<O>,
199+
): CompileFn<T> => {
200+
let compiled: CompiledQuery<Compilable<O>>
201+
return ((param, processRootOperatorNode) => {
202+
if (!compiled) {
203+
const { parameters, sql, query } = paramBuilder({ qb: queryBuilder, param: getParam }).compile()
204+
compiled = {
205+
sql,
206+
query: processRootOperatorNode?.(query) || { kind: query.kind } as any,
207+
parameters,
208+
}
209+
}
210+
return {
211+
...compiled,
212+
parameters: compiled.parameters.map(p =>
213+
(typeof p === 'string' && p.startsWith('__precomile_'))
214+
? defaultSerializer(param[p.slice(12)])
215+
: p,
216+
),
217+
}
218+
}) satisfies CompileFn<T>
219+
},
220+
}
182221
}

test/builder.test.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('test builder', async () => {
7575
expect(result![0].createAt).toBeInstanceOf(Date)
7676
expect(result![0].updateAt).toBeInstanceOf(Date)
7777
const result2 = await db.execOne(d => d.selectFrom('test').selectAll())
78-
expect(result).toBeInstanceOf(Object)
78+
expect(result2).toBeInstanceOf(Object)
7979
expect(result2!.person).toStrictEqual({ name: 'test' })
8080
expect(result2!.gender).toStrictEqual(false)
8181
expect(result2!.createAt).toBeInstanceOf(Date)
@@ -90,13 +90,22 @@ describe('test builder', async () => {
9090
expect(query.sql).toBe('select * from "test" where "person" = ?')
9191
expect(query.parameters).toStrictEqual(['{"name":"1"}'])
9292
})
93-
test('preCompile', async () => {
94-
const select = db.preCompile(db => db.selectFrom('test').selectAll())
95-
.setParam<{ person: { name: string } }>((qb, param) => qb.where('person', '=', param('person')))
96-
const insert = db.preCompile(db => db.insertInto('test'))
97-
.setParam<{ gender: boolean }>((qb, param) => qb.values({ gender: param('gender') }))
98-
const update = db.preCompile(db => db.updateTable('test'))
99-
.setParam<{ gender: boolean }>((qb, param) => qb.set({ gender: param('gender') }).where('id', '=', 1))
93+
test('precompile', async () => {
94+
const select = db.precompile(
95+
db => db.selectFrom('test').selectAll(),
96+
).setParam<{ person: { name: string } }>(({ qb, param }) =>
97+
qb.where('person', '=', param('person')),
98+
)
99+
const insert = db.precompile(
100+
db => db.insertInto('test'),
101+
).setParam<{ gender: boolean }>(({ qb, param }) =>
102+
qb.values({ gender: param('gender') }),
103+
)
104+
const update = db.precompile(
105+
db => db.updateTable('test'),
106+
).setParam<{ gender: boolean }>(({ qb, param }) =>
107+
qb.set({ gender: param('gender') }).where('id', '=', 1),
108+
)
100109

101110
const start = performance.now()
102111

0 commit comments

Comments
 (0)