@better-auth/kysely-adapter 1.7.0-beta.0 → 1.7.0-beta.10
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.
|
@@ -57,7 +57,14 @@ var BunSqliteConnection = class {
|
|
|
57
57
|
executeQuery(compiledQuery) {
|
|
58
58
|
const { sql, parameters } = compiledQuery;
|
|
59
59
|
const stmt = this.#db.prepare(sql);
|
|
60
|
-
|
|
60
|
+
const params = parameters;
|
|
61
|
+
if (stmt.columnNames.length > 0) return Promise.resolve({ rows: stmt.all(...params) });
|
|
62
|
+
const { changes, lastInsertRowid } = stmt.run(...params);
|
|
63
|
+
return Promise.resolve({
|
|
64
|
+
rows: [],
|
|
65
|
+
numAffectedRows: BigInt(changes),
|
|
66
|
+
insertId: typeof lastInsertRowid === "bigint" ? lastInsertRowid : BigInt(lastInsertRowid)
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
async *streamQuery() {
|
|
63
70
|
throw new Error("Streaming query is not supported by SQLite driver.");
|
|
@@ -113,7 +120,7 @@ var BunSqliteIntrospector = class {
|
|
|
113
120
|
isAutoIncrementing: col.name === autoIncrementCol,
|
|
114
121
|
hasDefaultValue: col.dflt_value != null
|
|
115
122
|
})),
|
|
116
|
-
isView:
|
|
123
|
+
isView: false
|
|
117
124
|
};
|
|
118
125
|
}
|
|
119
126
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -21,6 +21,19 @@ declare const createKyselyAdapter: (config: BetterAuthOptions) => Promise<{
|
|
|
21
21
|
interface KyselyAdapterConfig {
|
|
22
22
|
/**
|
|
23
23
|
* Database type.
|
|
24
|
+
*
|
|
25
|
+
* For `"mysql"`, this adapter depends on the driver returning
|
|
26
|
+
* "rows matched" counts from `UPDATE`/`DELETE` operations (in
|
|
27
|
+
* mysql2: `affectedRows`, exposed by Kysely as `numUpdatedRows`).
|
|
28
|
+
* By default, `mysql2` enables this via the `FOUND_ROWS` client
|
|
29
|
+
* flag.
|
|
30
|
+
*
|
|
31
|
+
* Do not disable this flag. If you remove it (e.g. with
|
|
32
|
+
* `flags: '-FOUND_ROWS'` in your pool config), MySQL will report
|
|
33
|
+
* "rows changed" semantics: an idempotent `UPDATE` (where the new
|
|
34
|
+
* value equals the old value) will show zero affected rows, causing
|
|
35
|
+
* adapter methods like `update`, `incrementOne`, or `updateMany` to
|
|
36
|
+
* return `null` or `0` even if a row matched the predicate.
|
|
24
37
|
*/
|
|
25
38
|
type?: KyselyDatabaseType | undefined;
|
|
26
39
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Kysely, MssqlDialect, MysqlDialect, PostgresDialect, SqliteDialect, sql } from "kysely";
|
|
2
2
|
import { createAdapterFactory } from "@better-auth/core/db/adapter";
|
|
3
|
+
import { logger } from "@better-auth/core/env";
|
|
3
4
|
import { capitalizeFirstLetter } from "@better-auth/core/utils/string";
|
|
4
5
|
//#region src/dialect.ts
|
|
5
6
|
function getKyselyDatabaseType(db) {
|
|
@@ -43,7 +44,7 @@ const createKyselyAdapter = async (config) => {
|
|
|
43
44
|
if ("getConnection" in db) dialect = new MysqlDialect(db);
|
|
44
45
|
if ("connect" in db) dialect = new PostgresDialect({ pool: db });
|
|
45
46
|
if ("fileControl" in db) {
|
|
46
|
-
const { BunSqliteDialect } = await import("./bun-sqlite-dialect-
|
|
47
|
+
const { BunSqliteDialect } = await import("./bun-sqlite-dialect-PuE7pPr-.mjs");
|
|
47
48
|
dialect = new BunSqliteDialect({ database: db });
|
|
48
49
|
}
|
|
49
50
|
if ("createSession" in db) {
|
|
@@ -124,8 +125,13 @@ function insensitiveNe(columnRef, value) {
|
|
|
124
125
|
//#region src/kysely-adapter.ts
|
|
125
126
|
const kyselyAdapter = (db, config) => {
|
|
126
127
|
let lazyOptions = null;
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
let mysqlNoIdWarned = false;
|
|
129
|
+
const createCustomAdapter = (db, inTransaction = false) => {
|
|
130
|
+
return ({ getFieldName, schema, getDefaultFieldName, getDefaultModelName, getFieldAttributes, getModelName, options }) => {
|
|
131
|
+
if (config?.type === "mysql" && options.advanced?.database?.generateId === false && !mysqlNoIdWarned) {
|
|
132
|
+
mysqlNoIdWarned = true;
|
|
133
|
+
logger.warn("[Kysely Adapter] MySQL does not support INSERT...RETURNING. With generateId set to false, the adapter uses best-effort fallback strategies (unique columns, full-field match) to retrieve inserted rows. For reliable behavior, use Better Auth's default ID generation, a custom generateId function, or generateId: \"serial\" for auto-increment.");
|
|
134
|
+
}
|
|
129
135
|
const selectAllJoins = (join) => {
|
|
130
136
|
const allSelects = [];
|
|
131
137
|
const allSelectsStr = [];
|
|
@@ -149,33 +155,71 @@ const kyselyAdapter = (db, config) => {
|
|
|
149
155
|
};
|
|
150
156
|
};
|
|
151
157
|
const withReturning = async (values, builder, model, where) => {
|
|
152
|
-
let res;
|
|
153
158
|
if (config?.type === "mysql") {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
if (where.length > 0) {
|
|
160
|
+
const updateResult = await builder.executeTakeFirst();
|
|
161
|
+
if (!updateResult || Number(updateResult.numUpdatedRows ?? 0) === 0) return null;
|
|
162
|
+
const idEqualityWhere = where.find((w) => w.field === "id" && (w.operator === void 0 || w.operator === "eq") && w.connector !== "OR" && w.value !== void 0 && w.value !== null);
|
|
163
|
+
let reselectField;
|
|
164
|
+
let reselectValue;
|
|
165
|
+
if (values.id !== void 0 && values.id !== null) {
|
|
166
|
+
reselectField = "id";
|
|
167
|
+
reselectValue = values.id;
|
|
168
|
+
} else if (idEqualityWhere) {
|
|
169
|
+
reselectField = "id";
|
|
170
|
+
reselectValue = idEqualityWhere.value;
|
|
171
|
+
} else if (where[0]?.field) {
|
|
172
|
+
reselectField = where[0].field;
|
|
173
|
+
reselectValue = values[reselectField] !== void 0 ? values[reselectField] : where[0].value;
|
|
174
|
+
} else return null;
|
|
175
|
+
return await db.selectFrom(model).selectAll().where(getFieldName({
|
|
158
176
|
model,
|
|
159
|
-
field
|
|
160
|
-
}), "
|
|
161
|
-
return res;
|
|
177
|
+
field: reselectField
|
|
178
|
+
}), reselectValue === null ? "is" : "=", reselectValue).limit(1).executeTakeFirst();
|
|
162
179
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
model
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
await builder.execute();
|
|
181
|
+
const fetchInserted = async (trx) => {
|
|
182
|
+
if (values.id) return await trx.selectFrom(model).selectAll().where(getFieldName({
|
|
183
|
+
model,
|
|
184
|
+
field: "id"
|
|
185
|
+
}), "=", values.id).limit(1).executeTakeFirst();
|
|
186
|
+
if (options.advanced?.database?.generateId === "serial") {
|
|
187
|
+
const lastId = (await sql`SELECT LAST_INSERT_ID() as id`.execute(trx)).rows[0]?.id;
|
|
188
|
+
if (lastId) return await trx.selectFrom(model).selectAll().where(getFieldName({
|
|
189
|
+
model,
|
|
190
|
+
field: "id"
|
|
191
|
+
}), "=", lastId).limit(1).executeTakeFirst();
|
|
192
|
+
}
|
|
193
|
+
const modelSchema = schema[getDefaultModelName(model)]?.fields;
|
|
194
|
+
if (modelSchema) for (const [fieldKey, fieldAttr] of Object.entries(modelSchema)) {
|
|
195
|
+
if (!fieldAttr.unique) continue;
|
|
196
|
+
const dbFieldName = getFieldName({
|
|
197
|
+
model,
|
|
198
|
+
field: fieldKey
|
|
199
|
+
});
|
|
200
|
+
const val = values[dbFieldName];
|
|
201
|
+
if (val === void 0 || val === null) continue;
|
|
202
|
+
const row = await trx.selectFrom(model).selectAll().where(dbFieldName, "=", val).limit(1).executeTakeFirst();
|
|
203
|
+
if (row) return row;
|
|
204
|
+
}
|
|
205
|
+
let query = trx.selectFrom(model).selectAll();
|
|
206
|
+
let hasConditions = false;
|
|
207
|
+
for (const [key, val] of Object.entries(values)) {
|
|
208
|
+
if (val === void 0) continue;
|
|
209
|
+
query = query.where(key, val === null ? "is" : "=", val);
|
|
210
|
+
hasConditions = true;
|
|
211
|
+
}
|
|
212
|
+
if (hasConditions) {
|
|
213
|
+
const rows = await query.limit(2).execute();
|
|
214
|
+
if (rows.length === 1) return rows[0];
|
|
215
|
+
}
|
|
216
|
+
logger.warn(`[Kysely Adapter] Unable to safely identify the inserted "${model}" row on MySQL. Enable Better Auth ID generation or use generateId: "serial" for reliable behavior.`);
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
return inTransaction ? fetchInserted(db) : db.transaction().execute(fetchInserted);
|
|
176
220
|
}
|
|
177
|
-
|
|
178
|
-
return
|
|
221
|
+
if (config?.type === "mssql") return await builder.outputAll("inserted").executeTakeFirst();
|
|
222
|
+
return await builder.returningAll().executeTakeFirst();
|
|
179
223
|
};
|
|
180
224
|
function convertWhereClause(model, w) {
|
|
181
225
|
if (!w) return {
|
|
@@ -386,6 +430,7 @@ const kyselyAdapter = (db, config) => {
|
|
|
386
430
|
return res;
|
|
387
431
|
},
|
|
388
432
|
async update({ model, where, update: values }) {
|
|
433
|
+
if (where.length === 0) return null;
|
|
389
434
|
const { and, or } = convertWhereClause(model, where);
|
|
390
435
|
let query = db.updateTable(model).set(values);
|
|
391
436
|
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
@@ -425,6 +470,75 @@ const kyselyAdapter = (db, config) => {
|
|
|
425
470
|
const res = (await query.executeTakeFirst()).numDeletedRows;
|
|
426
471
|
return res > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : Number(res);
|
|
427
472
|
},
|
|
473
|
+
async consumeOne({ model, where }) {
|
|
474
|
+
const { and, or } = convertWhereClause(model, where);
|
|
475
|
+
const applyWhere = (query) => {
|
|
476
|
+
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
477
|
+
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
478
|
+
return query;
|
|
479
|
+
};
|
|
480
|
+
const idField = getFieldName({
|
|
481
|
+
model,
|
|
482
|
+
field: "id"
|
|
483
|
+
});
|
|
484
|
+
const deleteSelectedRow = async (db, row) => {
|
|
485
|
+
const targetId = row[idField] ?? row.id;
|
|
486
|
+
if (targetId === void 0 || targetId === null) return null;
|
|
487
|
+
const query = db.deleteFrom(model).where(`${model}.${idField}`, "=", targetId);
|
|
488
|
+
if (config?.type === "mysql") {
|
|
489
|
+
const result = await query.executeTakeFirst();
|
|
490
|
+
return Number(result.numDeletedRows) > 0 ? row : null;
|
|
491
|
+
}
|
|
492
|
+
if (config?.type === "mssql") return await query.outputAll("deleted").executeTakeFirst() ?? null;
|
|
493
|
+
return await query.returningAll().executeTakeFirst() ?? null;
|
|
494
|
+
};
|
|
495
|
+
const deleteWithReturning = async (query) => {
|
|
496
|
+
if (config?.type === "mssql") return await query.outputAll("deleted").executeTakeFirst() ?? null;
|
|
497
|
+
return await query.returningAll().executeTakeFirst() ?? null;
|
|
498
|
+
};
|
|
499
|
+
if (config?.type === "mysql") {
|
|
500
|
+
const claimFromTransaction = async (trx) => {
|
|
501
|
+
const row = await applyWhere(trx.selectFrom(model).selectAll().forUpdate()).limit(1).executeTakeFirst();
|
|
502
|
+
if (!row) return null;
|
|
503
|
+
return deleteSelectedRow(trx, row);
|
|
504
|
+
};
|
|
505
|
+
return inTransaction ? claimFromTransaction(db) : db.transaction().execute(claimFromTransaction);
|
|
506
|
+
}
|
|
507
|
+
const selectIds = applyWhere(db.selectFrom(model).select(`${model}.${idField}`));
|
|
508
|
+
const targetIds = config?.type === "mssql" ? selectIds.top(1) : selectIds.limit(1);
|
|
509
|
+
return deleteWithReturning(db.deleteFrom(model).where(`${model}.${idField}`, "in", targetIds));
|
|
510
|
+
},
|
|
511
|
+
async incrementOne({ model, where, increment, set }) {
|
|
512
|
+
const { and, or } = convertWhereClause(model, where);
|
|
513
|
+
const applyWhere = (query) => {
|
|
514
|
+
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
515
|
+
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
516
|
+
return query;
|
|
517
|
+
};
|
|
518
|
+
const assignments = { ...set ?? {} };
|
|
519
|
+
for (const [field, delta] of Object.entries(increment)) assignments[field] = sql`${sql.ref(field)} + ${delta}`;
|
|
520
|
+
const idField = getFieldName({
|
|
521
|
+
model,
|
|
522
|
+
field: "id"
|
|
523
|
+
});
|
|
524
|
+
if (config?.type === "mysql") {
|
|
525
|
+
const incrementInTransaction = async (trx) => {
|
|
526
|
+
const target = await applyWhere(trx.selectFrom(model).select(`${model}.${idField}`).forUpdate()).limit(1).executeTakeFirst();
|
|
527
|
+
if (!target) return null;
|
|
528
|
+
const targetId = target[idField] ?? target.id;
|
|
529
|
+
if (targetId === void 0 || targetId === null) return null;
|
|
530
|
+
const updated = await applyWhere(trx.updateTable(model).set(assignments)).where(`${model}.${idField}`, "=", targetId).executeTakeFirst();
|
|
531
|
+
if (Number(updated.numUpdatedRows) === 0) return null;
|
|
532
|
+
return await trx.selectFrom(model).selectAll().where(`${model}.${idField}`, "=", targetId).limit(1).executeTakeFirst() ?? null;
|
|
533
|
+
};
|
|
534
|
+
return inTransaction ? incrementInTransaction(db) : db.transaction().execute(incrementInTransaction);
|
|
535
|
+
}
|
|
536
|
+
const selectIds = applyWhere(db.selectFrom(model).select(`${model}.${idField}`));
|
|
537
|
+
const targetIds = config?.type === "mssql" ? selectIds.top(1) : selectIds.limit(1);
|
|
538
|
+
const updateQuery = applyWhere(db.updateTable(model).set(assignments)).where(`${model}.${idField}`, "in", targetIds);
|
|
539
|
+
if (config?.type === "mssql") return await updateQuery.outputAll("inserted").executeTakeFirst() ?? null;
|
|
540
|
+
return await updateQuery.returningAll().executeTakeFirst() ?? null;
|
|
541
|
+
},
|
|
428
542
|
options: config
|
|
429
543
|
};
|
|
430
544
|
};
|
|
@@ -443,8 +557,11 @@ const kyselyAdapter = (db, config) => {
|
|
|
443
557
|
supportsUUIDs: config?.type === "postgres" ? true : false,
|
|
444
558
|
transaction: config?.transaction ? (cb) => db.transaction().execute((trx) => {
|
|
445
559
|
return cb(createAdapterFactory({
|
|
446
|
-
config:
|
|
447
|
-
|
|
560
|
+
config: {
|
|
561
|
+
...adapterOptions.config,
|
|
562
|
+
transaction: false
|
|
563
|
+
},
|
|
564
|
+
adapter: createCustomAdapter(trx, true)
|
|
448
565
|
})(lazyOptions));
|
|
449
566
|
}) : false
|
|
450
567
|
},
|
|
@@ -56,8 +56,15 @@ var NodeSqliteConnection = class {
|
|
|
56
56
|
}
|
|
57
57
|
executeQuery(compiledQuery) {
|
|
58
58
|
const { sql, parameters } = compiledQuery;
|
|
59
|
-
const
|
|
60
|
-
|
|
59
|
+
const stmt = this.#db.prepare(sql);
|
|
60
|
+
const params = parameters;
|
|
61
|
+
if (stmt.columns().length > 0) return Promise.resolve({ rows: stmt.all(...params) });
|
|
62
|
+
const { changes, lastInsertRowid } = stmt.run(...params);
|
|
63
|
+
return Promise.resolve({
|
|
64
|
+
rows: [],
|
|
65
|
+
numAffectedRows: BigInt(changes),
|
|
66
|
+
insertId: typeof lastInsertRowid === "bigint" ? lastInsertRowid : BigInt(lastInsertRowid)
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
async *streamQuery() {
|
|
63
70
|
throw new Error("Streaming query is not supported by SQLite driver.");
|
|
@@ -113,7 +120,7 @@ var NodeSqliteIntrospector = class {
|
|
|
113
120
|
isAutoIncrementing: col.name === autoIncrementCol,
|
|
114
121
|
hasDefaultValue: col.dflt_value != null
|
|
115
122
|
})),
|
|
116
|
-
isView:
|
|
123
|
+
isView: false
|
|
117
124
|
};
|
|
118
125
|
}
|
|
119
126
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/kysely-adapter",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.10",
|
|
4
4
|
"description": "Kysely adapter for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@better-auth/utils": "0.4.
|
|
44
|
-
"kysely": "^0.
|
|
45
|
-
"@better-auth/core": "^1.7.0-beta.
|
|
43
|
+
"@better-auth/utils": "0.4.2",
|
|
44
|
+
"kysely": "^0.28.17 || ^0.29.0",
|
|
45
|
+
"@better-auth/core": "^1.7.0-beta.10"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|
|
48
48
|
"kysely": {
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
+
"@better-auth/utils": "0.4.2",
|
|
53
54
|
"@cloudflare/workers-types": "^4.20250121.0",
|
|
54
|
-
"
|
|
55
|
-
"kysely": "^0.28.14",
|
|
55
|
+
"kysely": "^0.28.17 || ^0.29.0",
|
|
56
56
|
"tsdown": "0.21.1",
|
|
57
57
|
"typescript": "^5.9.3",
|
|
58
|
-
"@better-auth/core": "1.7.0-beta.
|
|
58
|
+
"@better-auth/core": "1.7.0-beta.10"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|