@better-auth/kysely-adapter 1.5.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,302 @@
1
+ /**
2
+ * @see {@link https://nodejs.org/api/sqlite.html} - Node.js SQLite API documentation
3
+ */
4
+
5
+ import type { DatabaseSync } from "node:sqlite";
6
+ import type {
7
+ DatabaseConnection,
8
+ DatabaseIntrospector,
9
+ DatabaseMetadata,
10
+ DatabaseMetadataOptions,
11
+ Dialect,
12
+ DialectAdapter,
13
+ DialectAdapterBase,
14
+ Driver,
15
+ Kysely,
16
+ QueryCompiler,
17
+ QueryResult,
18
+ SchemaMetadata,
19
+ TableMetadata,
20
+ } from "kysely";
21
+ import {
22
+ CompiledQuery,
23
+ DEFAULT_MIGRATION_LOCK_TABLE,
24
+ DEFAULT_MIGRATION_TABLE,
25
+ DefaultQueryCompiler,
26
+ sql,
27
+ } from "kysely";
28
+
29
+ class NodeSqliteAdapter implements DialectAdapterBase {
30
+ get supportsCreateIfNotExists(): boolean {
31
+ return true;
32
+ }
33
+
34
+ get supportsTransactionalDdl(): boolean {
35
+ return false;
36
+ }
37
+
38
+ get supportsReturning(): boolean {
39
+ return true;
40
+ }
41
+
42
+ async acquireMigrationLock(): Promise<void> {
43
+ // SQLite only has one connection that's reserved by the migration system
44
+ // for the whole time between acquireMigrationLock and releaseMigrationLock.
45
+ // We don't need to do anything here.
46
+ }
47
+
48
+ async releaseMigrationLock(): Promise<void> {
49
+ // SQLite only has one connection that's reserved by the migration system
50
+ // for the whole time between acquireMigrationLock and releaseMigrationLock.
51
+ // We don't need to do anything here.
52
+ }
53
+ get supportsOutput(): boolean {
54
+ return true;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Config for the SQLite dialect.
60
+ */
61
+ export interface NodeSqliteDialectConfig {
62
+ /**
63
+ * A sqlite DatabaseSync instance or a function that returns one.
64
+ */
65
+ database: DatabaseSync;
66
+
67
+ /**
68
+ * Called once when the first query is executed.
69
+ */
70
+ onCreateConnection?:
71
+ | ((connection: DatabaseConnection) => Promise<void>)
72
+ | undefined;
73
+ }
74
+
75
+ class NodeSqliteDriver implements Driver {
76
+ readonly #config: NodeSqliteDialectConfig;
77
+ readonly #connectionMutex = new ConnectionMutex();
78
+
79
+ #db?: DatabaseSync;
80
+ #connection?: DatabaseConnection;
81
+
82
+ constructor(config: NodeSqliteDialectConfig) {
83
+ this.#config = { ...config };
84
+ }
85
+
86
+ async init(): Promise<void> {
87
+ this.#db = this.#config.database;
88
+
89
+ this.#connection = new NodeSqliteConnection(this.#db);
90
+
91
+ if (this.#config.onCreateConnection) {
92
+ await this.#config.onCreateConnection(this.#connection);
93
+ }
94
+ }
95
+
96
+ async acquireConnection(): Promise<DatabaseConnection> {
97
+ // SQLite only has one single connection. We use a mutex here to wait
98
+ // until the single connection has been released.
99
+ await this.#connectionMutex.lock();
100
+ return this.#connection!;
101
+ }
102
+
103
+ async beginTransaction(connection: DatabaseConnection): Promise<void> {
104
+ await connection.executeQuery(CompiledQuery.raw("begin"));
105
+ }
106
+
107
+ async commitTransaction(connection: DatabaseConnection): Promise<void> {
108
+ await connection.executeQuery(CompiledQuery.raw("commit"));
109
+ }
110
+
111
+ async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
112
+ await connection.executeQuery(CompiledQuery.raw("rollback"));
113
+ }
114
+
115
+ async releaseConnection(): Promise<void> {
116
+ this.#connectionMutex.unlock();
117
+ }
118
+
119
+ async destroy(): Promise<void> {
120
+ this.#db?.close();
121
+ }
122
+ }
123
+
124
+ class NodeSqliteConnection implements DatabaseConnection {
125
+ readonly #db: DatabaseSync;
126
+
127
+ constructor(db: DatabaseSync) {
128
+ this.#db = db;
129
+ }
130
+
131
+ executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
132
+ const { sql, parameters } = compiledQuery;
133
+ const stmt = this.#db.prepare(sql);
134
+
135
+ const rows = stmt.all(...(parameters as any[])) as O[];
136
+
137
+ return Promise.resolve({
138
+ rows,
139
+ });
140
+ }
141
+
142
+ async *streamQuery() {
143
+ throw new Error("Streaming query is not supported by SQLite driver.");
144
+ }
145
+ }
146
+
147
+ class ConnectionMutex {
148
+ #promise?: Promise<void>;
149
+ #resolve?: () => void;
150
+
151
+ async lock(): Promise<void> {
152
+ while (this.#promise !== undefined) {
153
+ await this.#promise;
154
+ }
155
+
156
+ this.#promise = new Promise((resolve) => {
157
+ this.#resolve = resolve;
158
+ });
159
+ }
160
+
161
+ unlock(): void {
162
+ const resolve = this.#resolve;
163
+
164
+ this.#promise = undefined;
165
+ this.#resolve = undefined;
166
+
167
+ resolve?.();
168
+ }
169
+ }
170
+
171
+ class NodeSqliteIntrospector implements DatabaseIntrospector {
172
+ readonly #db: Kysely<unknown>;
173
+
174
+ constructor(db: Kysely<unknown>) {
175
+ this.#db = db;
176
+ }
177
+
178
+ async getSchemas(): Promise<SchemaMetadata[]> {
179
+ // Sqlite doesn't support schemas.
180
+ return [];
181
+ }
182
+
183
+ async getTables(
184
+ options: DatabaseMetadataOptions = { withInternalKyselyTables: false },
185
+ ): Promise<TableMetadata[]> {
186
+ let query = this.#db
187
+ // @ts-expect-error
188
+ .selectFrom("sqlite_schema")
189
+ // @ts-expect-error
190
+ .where("type", "=", "table")
191
+ // @ts-expect-error
192
+ .where("name", "not like", "sqlite_%")
193
+ .select("name")
194
+ .$castTo<{ name: string }>();
195
+
196
+ if (!options.withInternalKyselyTables) {
197
+ query = query
198
+ // @ts-expect-error
199
+ .where("name", "!=", DEFAULT_MIGRATION_TABLE)
200
+ // @ts-expect-error
201
+ .where("name", "!=", DEFAULT_MIGRATION_LOCK_TABLE);
202
+ }
203
+
204
+ const tables = await query.execute();
205
+ return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name)));
206
+ }
207
+
208
+ async getMetadata(
209
+ options?: DatabaseMetadataOptions | undefined,
210
+ ): Promise<DatabaseMetadata> {
211
+ return {
212
+ tables: await this.getTables(options),
213
+ };
214
+ }
215
+
216
+ async #getTableMetadata(table: string): Promise<TableMetadata> {
217
+ const db = this.#db;
218
+
219
+ // Get the SQL that was used to create the table.
220
+ const createSql = await db
221
+ // @ts-expect-error
222
+ .selectFrom("sqlite_master")
223
+ // @ts-expect-error
224
+ .where("name", "=", table)
225
+ .select("sql")
226
+ .$castTo<{ sql: string | undefined }>()
227
+ .execute();
228
+
229
+ // Try to find the name of the column that has `autoincrement` >&
230
+ const autoIncrementCol = createSql[0]?.sql
231
+ ?.split(/[\(\),]/)
232
+ ?.find((it) => it.toLowerCase().includes("autoincrement"))
233
+ ?.split(/\s+/)?.[0]
234
+ ?.replace(/["`]/g, "");
235
+
236
+ const columns = await db
237
+ .selectFrom(
238
+ sql<{
239
+ name: string;
240
+ type: string;
241
+ notnull: 0 | 1;
242
+ dflt_value: any;
243
+ }>`pragma_table_info(${table})`.as("table_info"),
244
+ )
245
+ .select(["name", "type", "notnull", "dflt_value"])
246
+ .execute();
247
+
248
+ return {
249
+ name: table,
250
+ columns: columns.map((col) => ({
251
+ name: col.name,
252
+ dataType: col.type,
253
+ isNullable: !col.notnull,
254
+ isAutoIncrementing: col.name === autoIncrementCol,
255
+ hasDefaultValue: col.dflt_value != null,
256
+ })),
257
+ isView: true,
258
+ };
259
+ }
260
+ }
261
+
262
+ class NodeSqliteQueryCompiler extends DefaultQueryCompiler {
263
+ protected override getCurrentParameterPlaceholder() {
264
+ return "?";
265
+ }
266
+
267
+ protected override getLeftIdentifierWrapper(): string {
268
+ return '"';
269
+ }
270
+
271
+ protected override getRightIdentifierWrapper(): string {
272
+ return '"';
273
+ }
274
+
275
+ protected override getAutoIncrement() {
276
+ return "autoincrement";
277
+ }
278
+ }
279
+
280
+ export class NodeSqliteDialect implements Dialect {
281
+ readonly #config: NodeSqliteDialectConfig;
282
+
283
+ constructor(config: NodeSqliteDialectConfig) {
284
+ this.#config = { ...config };
285
+ }
286
+
287
+ createDriver(): Driver {
288
+ return new NodeSqliteDriver(this.#config);
289
+ }
290
+
291
+ createQueryCompiler(): QueryCompiler {
292
+ return new NodeSqliteQueryCompiler();
293
+ }
294
+
295
+ createAdapter(): DialectAdapter {
296
+ return new NodeSqliteAdapter();
297
+ }
298
+
299
+ createIntrospector(db: Kysely<any>): DatabaseIntrospector {
300
+ return new NodeSqliteIntrospector(db);
301
+ }
302
+ }
package/src/types.ts ADDED
@@ -0,0 +1 @@
1
+ export type KyselyDatabaseType = "postgres" | "mysql" | "sqlite" | "mssql";
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["./src"],
4
+ "references": [
5
+ {
6
+ "path": "../core/tsconfig.json"
7
+ }
8
+ ]
9
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ dts: { build: true, incremental: true },
5
+ format: ["esm"],
6
+ entry: ["./src/index.ts", "./src/node-sqlite-dialect.ts"],
7
+ });
@@ -0,0 +1,3 @@
1
+ import { defineProject } from "vitest/config";
2
+
3
+ export default defineProject({});