Skip to content

Commit 5b180c0

Browse files
committed
feat(builder): support soft delete
1 parent dcb1a76 commit 5b180c0

File tree

9 files changed

+232
-74
lines changed

9 files changed

+232
-74
lines changed

packages/sqlite-builder/src/builder.ts

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { CompiledQuery, Kysely } from 'kysely'
1111
import { SerializePlugin, defaultSerializer } from 'kysely-plugin-serialize'
1212
import {
1313
type QueryBuilderOutput,
14+
type SqliteExecutor,
15+
basicExecutorFn,
1416
checkPrecompileKey,
1517
createKyselyLogger,
1618
getPrecompileParam,
@@ -50,25 +52,31 @@ type TransactionOptions<T> = {
5052
*/
5153
onRollback?: (err: unknown) => Promisable<void>
5254
}
53-
54-
export class SqliteBuilder<DB extends Record<string, any>> {
55-
public kysely: Kysely<DB>
55+
// ({select, ..., kysely})=>
56+
export class SqliteBuilder<DB extends Record<string, any>, Extra extends Record<string, any> = {}> {
57+
private _kysely: Kysely<DB>
5658
public trxCount = 0
5759
private trx?: Transaction<DB>
5860
private logger?: DBLogger
61+
private executor: SqliteExecutor<DB, Extra>
5962
private serializer = defaultSerializer
6063

64+
public get kysely() {
65+
return this.trx || this._kysely
66+
}
67+
6168
/**
6269
* sqlite builder
6370
* @param options options
6471
*/
65-
constructor(options: SqliteBuilderOptions) {
72+
constructor(options: SqliteBuilderOptions<DB, Extra>) {
6673
const {
6774
dialect,
6875
logger,
6976
onQuery,
7077
plugins = [],
7178
serializerPluginOptions,
79+
executorFn = basicExecutorFn<DB>,
7280
} = options
7381
this.logger = logger
7482

@@ -87,7 +95,8 @@ export class SqliteBuilder<DB extends Record<string, any>> {
8795
log = createKyselyLogger(onQuery)
8896
}
8997

90-
this.kysely = new Kysely<DB>({ dialect, log, plugins })
98+
this._kysely = new Kysely<DB>({ dialect, log, plugins })
99+
this.executor = executorFn(() => this.kysely) as any
91100
}
92101

93102
/**
@@ -138,11 +147,11 @@ export class SqliteBuilder<DB extends Record<string, any>> {
138147
*/
139148
public async syncDB(updater: TableUpdater, checkIntegrity?: boolean): Promise<StatusResult> {
140149
try {
141-
if (checkIntegrity && !(await runCheckIntegrity(this.kysely))) {
150+
if (checkIntegrity && !(await runCheckIntegrity(this._kysely))) {
142151
this.logger?.error('integrity check fail')
143152
return { ready: false, error: new IntegrityError() }
144153
}
145-
const result = await updater(this.kysely, this.logger)
154+
const result = await updater(this._kysely, this.logger)
146155
this.logger?.info('table updated')
147156
return result
148157
} catch (error) {
@@ -154,13 +163,6 @@ export class SqliteBuilder<DB extends Record<string, any>> {
154163
}
155164
}
156165

157-
/**
158-
* get current db instance, auto detect transaction
159-
*/
160-
private getDB() {
161-
return this.trx || this.kysely
162-
}
163-
164166
private logError(e: unknown, errorMsg?: string) {
165167
errorMsg && this.logger?.error(errorMsg, e instanceof Error ? e : undefined)
166168
}
@@ -173,7 +175,7 @@ export class SqliteBuilder<DB extends Record<string, any>> {
173175
options: TransactionOptions<O> = {},
174176
): Promise<O | undefined> {
175177
if (!this.trx) {
176-
return await this.kysely.transaction()
178+
return await this._kysely.transaction()
177179
.execute(async (trx) => {
178180
this.trx = trx
179181
this.logger?.debug('run in transaction')
@@ -213,14 +215,14 @@ export class SqliteBuilder<DB extends Record<string, any>> {
213215
query: CompiledQuery<O>,
214216
): Promise<QueryResult<O>>
215217
public async execute<O>(
216-
fn: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
218+
fn: (db: SqliteExecutor<DB, Extra>) => AvailableBuilder<DB, O>,
217219
): Promise<Simplify<O>[] | undefined>
218220
public async execute<O>(
219-
data: CompiledQuery<O> | ((db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>),
221+
data: CompiledQuery<O> | ((db: SqliteExecutor<DB, Extra>) => AvailableBuilder<DB, O>),
220222
): Promise<QueryResult<O> | Simplify<O>[] | undefined> {
221223
return typeof data === 'function'
222-
? await data(this.getDB()).execute()
223-
: await this.getDB().executeQuery(data)
224+
? await data(this.executor).execute()
225+
: await this.kysely.executeQuery(data)
224226
}
225227

226228
/**
@@ -229,13 +231,13 @@ export class SqliteBuilder<DB extends Record<string, any>> {
229231
* if is `select`, auto append `.limit(1)`
230232
*/
231233
public async executeTakeFirst<O>(
232-
fn: (db: Kysely<DB> | Transaction<DB>) => AvailableBuilder<DB, O>,
234+
fn: (db: SqliteExecutor<DB, Extra>) => AvailableBuilder<DB, O>,
233235
): Promise<Simplify<O> | undefined> {
234-
let _sql = fn(this.getDB())
236+
let _sql = fn(this.executor)
235237
if (isSelectQueryBuilder(_sql)) {
236238
_sql = _sql.limit(1)
237239
}
238-
return await _sql.executeTakeFirstOrThrow()
240+
return await _sql.executeTakeFirst()
239241
}
240242

241243
/**
@@ -270,7 +272,7 @@ export class SqliteBuilder<DB extends Record<string, any>> {
270272
* @returns function to {@link CompileFn compile}
271273
*/
272274
build: <O>(
273-
queryBuilder: (db: Kysely<DB>, param: <K extends keyof T>(name: K) => T[K]) => Compilable<O>,
275+
queryBuilder: (db: SqliteExecutor<DB>, param: <K extends keyof T>(name: K) => T[K]) => Compilable<O>,
274276
) => {
275277
let compiled: CompiledQuery<Compilable<O>> | null
276278
const dispose = () => compiled = null
@@ -279,7 +281,7 @@ export class SqliteBuilder<DB extends Record<string, any>> {
279281
dispose,
280282
compile: (param: T) => {
281283
if (!compiled) {
282-
const { parameters, sql, query } = queryBuilder(this.kysely, getPrecompileParam).compile()
284+
const { parameters, sql, query } = queryBuilder(this.executor, getPrecompileParam).compile()
283285
compiled = {
284286
sql,
285287
query: processRootOperatorNode(query) as any,
@@ -314,8 +316,8 @@ export class SqliteBuilder<DB extends Record<string, any>> {
314316
parameters?: unknown[],
315317
): Promise<QueryResult<O | unknown>> {
316318
return typeof rawSql === 'string'
317-
? await this.getDB().executeQuery(CompiledQuery.raw(rawSql, parameters))
318-
: await rawSql.execute(this.getDB())
319+
? await this.kysely.executeQuery(CompiledQuery.raw(rawSql, parameters))
320+
: await rawSql.execute(this.kysely)
319321
}
320322

321323
/**
@@ -333,7 +335,7 @@ export class SqliteBuilder<DB extends Record<string, any>> {
333335
*/
334336
public async destroy() {
335337
this.logger?.info('destroyed')
336-
await this.kysely.destroy()
338+
await this._kysely.destroy()
337339
this.trx = undefined
338340
}
339341
}

packages/sqlite-builder/src/schema/define.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,42 @@ export function defineTable<
3636
T extends Columns,
3737
C extends string | true | null = null,
3838
U extends string | true | null = null,
39+
D extends string | true | null = null,
3940
>(
4041
columns: T,
41-
property?: Omit<TableProperty<T>, 'timeTrigger'> & {
42+
property?: Omit<TableProperty<T>, 'timeTrigger' | 'softDelete'> & {
4243
timeTrigger?: TimeTriggerOptions<C, U>
44+
softDelete?: D
4345
},
44-
): Table<T, C, U> {
46+
): Table<T, C, U, D> {
4547
const { create, update } = property?.timeTrigger || {}
46-
const options = { type: 'date', defaultTo: TGR }
48+
const triggerOptions = { type: 'date', defaultTo: TGR }
4749
if (create === true) {
4850
// @ts-expect-error assign
49-
columns.createAt = options
51+
columns.createAt = triggerOptions
5052
} else if (create) {
5153
// @ts-expect-error assign
52-
columns[create] = options
54+
columns[create] = triggerOptions
5355
}
5456
if (update === true) {
55-
// @ts-expect-error assign #hack
56-
columns.updateAt = { ...options, notNull: 0 }
57+
// #hack if `notNull === true` and `defaultTo === TGR`, the column is updateAt
58+
// @ts-expect-error assign
59+
columns.updateAt = { ...triggerOptions, notNull: true }
5760
} else if (update) {
58-
// @ts-expect-error assign #hack
59-
columns[update] = { ...options, notNull: 0 }
61+
// #hack if `notNull === true` and `defaultTo === TGR`, the column is updateAt
62+
// @ts-expect-error assign
63+
columns[update] = { ...triggerOptions, notNull: true }
64+
}
65+
const softDelete = property?.softDelete
66+
const softDeleteOptions = { type: 'int', defaultTo: 0 }
67+
if (softDelete === true) {
68+
// @ts-expect-error assign
69+
columns.isDeleted = softDeleteOptions
70+
} else if (softDelete) {
71+
// @ts-expect-error assign
72+
columns[softDelete] = softDeleteOptions
6073
}
74+
6175
return {
6276
columns: columns as unknown as ColumnsWithErrorInfo<T>,
6377
...property,

packages/sqlite-builder/src/schema/run.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ export async function runCreateTable(
121121
// time trigger column is default with TGR
122122
if (defaultTo === TGR) {
123123
// update trigger column is not null
124-
// @ts-expect-error #hack to detect update column
125-
if (_triggerOptions && notNull === 0) {
124+
// #hack to detect update column
125+
if (_triggerOptions && notNull) {
126126
_triggerOptions.update = columnName
127127
}
128128
// default with current_timestamp

packages/sqlite-builder/src/schema/types.ts

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ export type TableProperty<
5454
Cols extends Columns,
5555
Create extends string | true | null = null,
5656
Update extends string | true | null = null,
57+
Delete extends string | true | null = null,
5758
> = {
5859
primary?: Arrayable<keyof Cols & string>
5960
unique?: Arrayable<keyof Cols & string>[]
6061
index?: Arrayable<keyof Cols & string>[]
6162
timeTrigger?: TimeTriggerOptions<Create, Update>
63+
softDelete?: Delete
6264
}
6365

6466
export type Columns = Record<string, ColumnProperty>
@@ -88,37 +90,51 @@ export type Table<
8890
Cols extends Columns = any,
8991
Create extends string | true | null = null,
9092
Update extends string | true | null = null,
93+
Delete extends string | true | null = null,
9194
> = {
9295
columns: ColumnsWithErrorInfo<Cols>
93-
} & TableProperty<Cols, Create, Update>
96+
} & TableProperty<Cols, Create, Update, Delete>
9497

95-
export type Schema = Record<string, Table<any, any, any>>
98+
export type Schema = Record<string, Table<any, any, any, any>>
9699

97-
export type FilterGenerated<
98-
Table extends object,
99-
EscapeKeys extends string = never,
100-
> = {
101-
[K in keyof Table]: K extends EscapeKeys
102-
? Table[K]
103-
: InferGenereated<Table[K]>
104-
}
100+
// export type FilterGenerated<
101+
// Table extends object,
102+
// EscapeKeys extends string = never,
103+
// > = {
104+
// [K in keyof Table]: K extends EscapeKeys
105+
// ? Table[K]
106+
// : InferGenereated<Table[K]>
107+
// }
105108

106109
type ERROR_INFO = 'HAVE_TYPE_ERROR_IN_DEFINITION'
107110

108111
type TriggerKey<A, B> =
109112
| (A extends true ? 'createAt' : A extends string ? A : never)
110113
| (B extends true ? 'updateAt' : B extends string ? B : never)
111114

112-
type ParseTableWithTrigger<
115+
type ExtraColumnsKey<
116+
TriggerKey extends string,
117+
Delete extends string | true | undefined,
118+
> = Delete extends string
119+
? (TriggerKey | Delete)
120+
: Delete extends true
121+
? (TriggerKey | 'isDeleted')
122+
: TriggerKey
123+
124+
export type ParseTableWithExtraColumns<
113125
T extends Columns,
114126
P extends TimeTriggerOptions<any, any> | undefined,
115-
> = P extends TimeTriggerOptions<infer A, infer B> ? Omit<T, TriggerKey<A, B>> & ({
116-
[K in TriggerKey<A, B>]: {
117-
type: 'increments' // #hack to ensure Generated
118-
defaultTo: Generated<Date> | null
119-
notNull: null
120-
}
121-
}) : never
127+
Delete extends string | true | undefined,
128+
> = P extends TimeTriggerOptions<infer A, infer B>
129+
// eslint-disable-next-line style/indent-binary-ops
130+
? Omit<T, ExtraColumnsKey<TriggerKey<A, B>, Delete>> & ({
131+
[K in ExtraColumnsKey<TriggerKey<A, B>, Delete>]: {
132+
type: 'increments' // #hack to ensure Generated
133+
defaultTo: Generated<K extends TriggerKey<A, B> ? Date : number> | null
134+
notNull: null
135+
}
136+
})
137+
: never
122138

123139
/**
124140
* util type for infering type of table
@@ -127,22 +143,25 @@ export type InferTable<
127143
T extends {
128144
columns: Columns
129145
timeTrigger?: TimeTriggerOptions<any, any>
146+
softDelete?: any
130147
},
131-
P = ParseTableWithTrigger<T['columns'], T['timeTrigger']>,
148+
P = ParseTableWithExtraColumns<T['columns'], T['timeTrigger'], T['softDelete']>,
132149
> = Prettify<{
133-
[K in keyof P]: P[K] extends ColumnProperty ? IsNotNull<P[K]['notNull']> extends true // if not null
134-
// return required defaultTo
135-
? Exclude<P[K]['defaultTo'], null>
136-
// if type is "increments"
137-
: P[K]['type'] extends 'increments'
138-
// return "Generated<...>"
150+
[K in keyof P]: P[K] extends ColumnProperty
151+
// if not null
152+
? IsNotNull<P[K]['notNull']> extends true
153+
// return required defaultTo
139154
? Exclude<P[K]['defaultTo'], null>
140-
// if defaultTo is required
141-
: IsNotNull<P[K]['defaultTo']> extends true
142-
// return Generated
143-
? Generated<Exclude<P[K]['defaultTo'], null>>
144-
// return optional
145-
: P[K]['defaultTo'] | null
155+
// if type is "increments"
156+
: P[K]['type'] extends 'increments'
157+
// return "Generated<...>"
158+
? Exclude<P[K]['defaultTo'], null>
159+
// if defaultTo is required
160+
: IsNotNull<P[K]['defaultTo']> extends true
161+
// return Generated
162+
? Generated<Exclude<P[K]['defaultTo'], null>>
163+
// return optional
164+
: P[K]['defaultTo'] | null
146165
// return error info
147166
: ERROR_INFO
148167
}>
@@ -159,5 +178,6 @@ export type InferDatabase<T extends Schema> = Prettify<{
159178
[K in keyof T]: T[K] extends {
160179
columns: Columns
161180
timeTrigger?: TimeTriggerOptions<any, any>
181+
softDelete?: any
162182
} ? InferTable<T[K]> : ERROR_INFO
163183
}>

0 commit comments

Comments
 (0)