-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathindex.ts
162 lines (135 loc) · 4.8 KB
/
index.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import {cast, Config, connect, Connection, Field} from '@planetscale/database'
import {parseJSON} from 'date-fns'
import {
CompiledQuery,
DatabaseConnection,
DatabaseIntrospector,
Dialect,
Driver,
Kysely,
MysqlAdapter,
MysqlIntrospector,
MysqlQueryCompiler,
QueryCompiler,
QueryResult,
} from 'kysely'
/**
* Config for the PlanetScale dialect. It extends {@link Config} from `@planetscale/database`,
* so you can pass any of those options to the constructor.
*
* @see https://github.com/planetscale/database-js#usage
*/
export interface PlanetScaleDialectConfig extends Config {}
/**
* PlanetScale dialect that uses the [PlanetScale Serverless Driver for JavaScript][0].
* The constructor takes an instance of {@link Config} from `@planetscale/database`.
*
* ```typescript
* new PlanetScaleDialect({
* host: '<host>',
* username: '<username>',
* password: '<password>',
* })
*
* // or with a connection URL
*
* new PlanetScaleDialect({
* url: process.env.DATABASE_URL ?? 'mysql://<username>:<password>@<host>/<database>'
* })
* ```
*
* See the [`@planetscale/database` documentation][1] for more information.
*
* [0]: https://github.com/planetscale/database-js
* [1]: https://github.com/planetscale/database-js#readme
*/
export class PlanetScaleDialect implements Dialect {
#config: PlanetScaleDialectConfig
constructor(config: PlanetScaleDialectConfig) {
this.#config = config
}
createAdapter() {
return new MysqlAdapter()
}
createDriver(): Driver {
return new PlanetScaleDriver(this.#config)
}
createQueryCompiler(): QueryCompiler {
return new MysqlQueryCompiler()
}
createIntrospector(db: Kysely<any>): DatabaseIntrospector {
return new MysqlIntrospector(db)
}
}
class PlanetScaleDriver implements Driver {
#config: PlanetScaleDialectConfig
constructor(config: PlanetScaleDialectConfig) {
this.#config = config
}
async init(): Promise<void> {}
async acquireConnection(): Promise<DatabaseConnection> {
return new PlanetScaleConnection(this.#config)
}
async beginTransaction(conn: PlanetScaleConnection): Promise<void> {
return await conn.beginTransaction()
}
async commitTransaction(conn: PlanetScaleConnection): Promise<void> {
return await conn.commitTransaction()
}
async rollbackTransaction(conn: PlanetScaleConnection): Promise<void> {
return await conn.rollbackTransaction()
}
async releaseConnection(_conn: PlanetScaleConnection): Promise<void> {}
async destroy(): Promise<void> {}
}
class PlanetScaleConnection implements DatabaseConnection {
#config: PlanetScaleDialectConfig
#conn: Connection
#transactionClient?: PlanetScaleConnection
constructor(config: PlanetScaleDialectConfig) {
this.#config = config
this.#conn = connect({cast: inflateDates, ...config})
}
async executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
if (this.#transactionClient) return this.#transactionClient.executeQuery(compiledQuery)
// If no custom formatter is provided, format dates as DB date strings
const parameters = this.#config.format
? compiledQuery.parameters
: compiledQuery.parameters.map((param) => (param instanceof Date ? formatDate(param) : param))
const results = await this.#conn.execute(compiledQuery.sql, parameters)
return {
rows: results.rows as O[],
numUpdatedOrDeletedRows: results.rowsAffected == null ? undefined : BigInt(results.rowsAffected),
}
}
async beginTransaction() {
this.#transactionClient = this.#transactionClient ?? new PlanetScaleConnection(this.#config)
this.#transactionClient.#conn.execute('BEGIN')
}
async commitTransaction() {
if (!this.#transactionClient) throw new Error('No transaction to commit')
this.#transactionClient.#conn.execute('COMMIT')
this.#transactionClient = undefined
}
async rollbackTransaction() {
if (!this.#transactionClient) throw new Error('No transaction to rollback')
this.#transactionClient.#conn.execute('ROLLBACK')
this.#transactionClient = undefined
}
async *streamQuery<O>(_compiledQuery: CompiledQuery, _chunkSize: number): AsyncIterableIterator<QueryResult<O>> {
throw new Error('PlanetScale Serverless Driver does not support streaming')
}
}
/**
* Converts dates returned from the database to JavaScript Date objects. This is the default
* `cast` function passed to the `@planetscale/database` library, but you can override it by
* passing your own alternative `cast` function to {@link Config}.
*/
export function inflateDates(field: Field, value: string | null) {
if (field.type === 'DATETIME' && value) return parseJSON(value)
if (field.type === 'TIMESTAMP' && value) return parseJSON(value)
return cast(field, value)
}
function formatDate(date: Date): string {
return date.toISOString().replace(/[TZ]/g, ' ').trim()
}