/
kysely.ts
116 lines (97 loc) 路 3.38 KB
/
kysely.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { SqliteAdapter, type Kysely, MysqlAdapter } from 'kysely'
import { DatabaseDriver } from '../database.js'
import type { CreateDriverResult, DatabaseAdapter, KyselyConfig } from '../../../types/main.js'
/**
* Create a kysely driver
* You will need to install the underlying database package (mysql2, pg, sqlite3, etc)
*/
export function kyselyDriver(options: KyselyConfig): CreateDriverResult<DatabaseDriver> {
return {
options,
factory: (config: KyselyConfig) => {
const adapter = new KyselyAdapter(config)
return new DatabaseDriver(adapter, config)
},
}
}
/**
* Kysely adapter for the DatabaseDriver
*/
export class KyselyAdapter implements DatabaseAdapter {
#dialect: 'mysql' | 'pg' | 'sqlite'
#tableName!: string
#connection: Kysely<any>
constructor(config: KyselyConfig) {
this.#connection = config.connection
const adapter = this.#connection.getExecutor().adapter
if (adapter instanceof SqliteAdapter) {
this.#dialect = 'sqlite'
} else if (adapter instanceof MysqlAdapter) {
this.#dialect = 'mysql'
} else {
this.#dialect = 'pg'
}
}
setTableName(tableName: string): void {
this.#tableName = tableName
}
async get(key: string): Promise<{ value: any; expiresAt: number | null } | undefined> {
const result = await this.#connection
.selectFrom(this.#tableName)
.select(['value', 'expires_at'])
.where('key', '=', key)
.executeTakeFirst()
if (!result) return
return { value: result.value, expiresAt: result.expires_at }
}
async delete(key: string): Promise<boolean> {
const result = await this.#connection
.deleteFrom(this.#tableName)
.where('key', '=', key)
.executeTakeFirst()
return result.numDeletedRows > 0
}
async deleteMany(keys: string[]): Promise<number> {
const result = await this.#connection
.deleteFrom(this.#tableName)
.where('key', 'in', keys)
.executeTakeFirst()
return +result.numDeletedRows.toString()
}
async disconnect(): Promise<void> {
await this.#connection.destroy()
}
async createTableIfNotExists(): Promise<void> {
await this.#connection.schema
.createTable(this.#tableName)
.addColumn('key', 'varchar(255)', (col) => col.primaryKey().notNull())
.addColumn('value', 'text')
.addColumn('expires_at', 'bigint')
.ifNotExists()
.execute()
}
async pruneExpiredEntries(): Promise<void> {
await this.#connection
.deleteFrom(this.#tableName)
.where('expires_at', '<', Date.now())
.execute()
}
async clear(prefix: string): Promise<void> {
await this.#connection.deleteFrom(this.#tableName).where('key', 'like', `${prefix}%`).execute()
}
async set(row: { value: any; key: string; expiresAt: Date | null }): Promise<void> {
const expiresAt = this.#dialect === 'sqlite' ? row.expiresAt?.getTime() : row.expiresAt
await this.#connection
.insertInto(this.#tableName)
.values({ key: row.key, value: row.value, expires_at: expiresAt ?? null })
.$if(this.#dialect === 'mysql', (query) =>
query.onDuplicateKeyUpdate({ value: row.value, expires_at: expiresAt }),
)
.$if(this.#dialect !== 'mysql', (query) => {
return query.onConflict((conflict) => {
return conflict.columns(['key']).doUpdateSet({ value: row.value, expires_at: expiresAt })
})
})
.execute()
}
}