@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.
- package/.turbo/turbo-build.log +16 -0
- package/LICENSE.md +20 -0
- package/dist/bun-sqlite-dialect-deBGXcrd.mjs +155 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.mjs +364 -0
- package/dist/node-sqlite-dialect.d.mts +28 -0
- package/dist/node-sqlite-dialect.mjs +155 -0
- package/package.json +44 -0
- package/src/bun-sqlite-dialect.ts +300 -0
- package/src/dialect.ts +148 -0
- package/src/index.ts +5 -0
- package/src/kysely-adapter.test.ts +24 -0
- package/src/kysely-adapter.ts +639 -0
- package/src/node-sqlite-dialect.ts +302 -0
- package/src/types.ts +1 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +7 -0
- package/vitest.config.ts +3 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import type { BetterAuthOptions } from "@better-auth/core";
|
|
2
|
+
import type {
|
|
3
|
+
AdapterFactoryCustomizeAdapterCreator,
|
|
4
|
+
AdapterFactoryOptions,
|
|
5
|
+
DBAdapter,
|
|
6
|
+
DBAdapterDebugLogOption,
|
|
7
|
+
JoinConfig,
|
|
8
|
+
Where,
|
|
9
|
+
} from "@better-auth/core/db/adapter";
|
|
10
|
+
import { createAdapterFactory } from "@better-auth/core/db/adapter";
|
|
11
|
+
import type {
|
|
12
|
+
InsertQueryBuilder,
|
|
13
|
+
Kysely,
|
|
14
|
+
RawBuilder,
|
|
15
|
+
UpdateQueryBuilder,
|
|
16
|
+
} from "kysely";
|
|
17
|
+
import { sql } from "kysely";
|
|
18
|
+
import type { KyselyDatabaseType } from "./types";
|
|
19
|
+
|
|
20
|
+
interface KyselyAdapterConfig {
|
|
21
|
+
/**
|
|
22
|
+
* Database type.
|
|
23
|
+
*/
|
|
24
|
+
type?: KyselyDatabaseType | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Enable debug logs for the adapter
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
debugLogs?: DBAdapterDebugLogOption | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Use plural for table names.
|
|
33
|
+
*
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
usePlural?: boolean | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to execute multiple operations in a transaction.
|
|
39
|
+
*
|
|
40
|
+
* If the database doesn't support transactions,
|
|
41
|
+
* set this to `false` and operations will be executed sequentially.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
transaction?: boolean | undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const kyselyAdapter = (
|
|
48
|
+
db: Kysely<any>,
|
|
49
|
+
config?: KyselyAdapterConfig | undefined,
|
|
50
|
+
) => {
|
|
51
|
+
let lazyOptions: BetterAuthOptions | null = null;
|
|
52
|
+
const createCustomAdapter = (
|
|
53
|
+
db: Kysely<any>,
|
|
54
|
+
): AdapterFactoryCustomizeAdapterCreator => {
|
|
55
|
+
return ({
|
|
56
|
+
getFieldName,
|
|
57
|
+
schema,
|
|
58
|
+
getDefaultFieldName,
|
|
59
|
+
getDefaultModelName,
|
|
60
|
+
getFieldAttributes,
|
|
61
|
+
getModelName,
|
|
62
|
+
}) => {
|
|
63
|
+
const selectAllJoins = (join: JoinConfig | undefined) => {
|
|
64
|
+
// Use selectAll which will handle column naming appropriately
|
|
65
|
+
const allSelects: RawBuilder<unknown>[] = [];
|
|
66
|
+
const allSelectsStr: {
|
|
67
|
+
joinModel: string;
|
|
68
|
+
joinModelRef: string;
|
|
69
|
+
fieldName: string;
|
|
70
|
+
}[] = [];
|
|
71
|
+
if (join) {
|
|
72
|
+
for (const [joinModel, _] of Object.entries(join)) {
|
|
73
|
+
const fields = schema[getDefaultModelName(joinModel)]?.fields;
|
|
74
|
+
const [_joinModelSchema, joinModelName] = joinModel.includes(".")
|
|
75
|
+
? joinModel.split(".")
|
|
76
|
+
: [undefined, joinModel];
|
|
77
|
+
|
|
78
|
+
if (!fields) continue;
|
|
79
|
+
fields.id = { type: "string" }; // make sure there is at least an id field
|
|
80
|
+
for (const [field, fieldAttr] of Object.entries(fields)) {
|
|
81
|
+
allSelects.push(
|
|
82
|
+
sql`${sql.ref(`join_${joinModelName}`)}.${sql.ref(fieldAttr.fieldName || field)} as ${sql.ref(`_joined_${joinModelName}_${fieldAttr.fieldName || field}`)}`,
|
|
83
|
+
);
|
|
84
|
+
allSelectsStr.push({
|
|
85
|
+
joinModel: joinModel,
|
|
86
|
+
joinModelRef: joinModelName,
|
|
87
|
+
fieldName: fieldAttr.fieldName || field,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { allSelectsStr, allSelects };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const withReturning = async (
|
|
96
|
+
values: Record<string, any>,
|
|
97
|
+
builder:
|
|
98
|
+
| InsertQueryBuilder<any, any, any>
|
|
99
|
+
| UpdateQueryBuilder<any, string, string, any>,
|
|
100
|
+
model: string,
|
|
101
|
+
where: Where[],
|
|
102
|
+
) => {
|
|
103
|
+
let res: any;
|
|
104
|
+
if (config?.type === "mysql") {
|
|
105
|
+
// This isn't good, but kysely doesn't support returning in mysql and it doesn't return the inserted id.
|
|
106
|
+
// Change this if there is a better way.
|
|
107
|
+
await builder.execute();
|
|
108
|
+
const field = values.id
|
|
109
|
+
? "id"
|
|
110
|
+
: where.length > 0 && where[0]?.field
|
|
111
|
+
? where[0].field
|
|
112
|
+
: "id";
|
|
113
|
+
|
|
114
|
+
if (!values.id && where.length === 0) {
|
|
115
|
+
res = await db
|
|
116
|
+
.selectFrom(model)
|
|
117
|
+
.selectAll()
|
|
118
|
+
.orderBy(getFieldName({ model, field }), "desc")
|
|
119
|
+
.limit(1)
|
|
120
|
+
.executeTakeFirst();
|
|
121
|
+
return res;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const value = values[field] || where[0]?.value;
|
|
125
|
+
res = await db
|
|
126
|
+
.selectFrom(model)
|
|
127
|
+
.selectAll()
|
|
128
|
+
.orderBy(getFieldName({ model, field }), "desc")
|
|
129
|
+
.where(getFieldName({ model, field }), "=", value)
|
|
130
|
+
.limit(1)
|
|
131
|
+
.executeTakeFirst();
|
|
132
|
+
return res;
|
|
133
|
+
}
|
|
134
|
+
if (config?.type === "mssql") {
|
|
135
|
+
res = await builder.outputAll("inserted").executeTakeFirst();
|
|
136
|
+
return res;
|
|
137
|
+
}
|
|
138
|
+
res = await builder.returningAll().executeTakeFirst();
|
|
139
|
+
return res;
|
|
140
|
+
};
|
|
141
|
+
function convertWhereClause(model: string, w?: Where[] | undefined) {
|
|
142
|
+
if (!w)
|
|
143
|
+
return {
|
|
144
|
+
and: null,
|
|
145
|
+
or: null,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const conditions = {
|
|
149
|
+
and: [] as any[],
|
|
150
|
+
or: [] as any[],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
w.forEach((condition) => {
|
|
154
|
+
const {
|
|
155
|
+
field: _field,
|
|
156
|
+
value: _value,
|
|
157
|
+
operator = "=",
|
|
158
|
+
connector = "AND",
|
|
159
|
+
} = condition;
|
|
160
|
+
const value: any = _value;
|
|
161
|
+
const field: string | any = getFieldName({
|
|
162
|
+
model,
|
|
163
|
+
field: _field,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const expr = (eb: any) => {
|
|
167
|
+
const f = `${model}.${field}`;
|
|
168
|
+
if (operator.toLowerCase() === "in") {
|
|
169
|
+
return eb(f, "in", Array.isArray(value) ? value : [value]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (operator.toLowerCase() === "not_in") {
|
|
173
|
+
return eb(f, "not in", Array.isArray(value) ? value : [value]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (operator === "contains") {
|
|
177
|
+
return eb(f, "like", `%${value}%`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (operator === "starts_with") {
|
|
181
|
+
return eb(f, "like", `${value}%`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (operator === "ends_with") {
|
|
185
|
+
return eb(f, "like", `%${value}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (operator === "eq") {
|
|
189
|
+
return eb(f, "=", value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (operator === "ne") {
|
|
193
|
+
return eb(f, "<>", value);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (operator === "gt") {
|
|
197
|
+
return eb(f, ">", value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (operator === "gte") {
|
|
201
|
+
return eb(f, ">=", value);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (operator === "lt") {
|
|
205
|
+
return eb(f, "<", value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (operator === "lte") {
|
|
209
|
+
return eb(f, "<=", value);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return eb(f, operator, value);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (connector === "OR") {
|
|
216
|
+
conditions.or.push(expr);
|
|
217
|
+
} else {
|
|
218
|
+
conditions.and.push(expr);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
and: conditions.and.length ? conditions.and : null,
|
|
224
|
+
or: conditions.or.length ? conditions.or : null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function processJoinedResults(
|
|
229
|
+
rows: any[],
|
|
230
|
+
joinConfig: JoinConfig | undefined,
|
|
231
|
+
allSelectsStr: {
|
|
232
|
+
joinModel: string;
|
|
233
|
+
joinModelRef: string;
|
|
234
|
+
fieldName: string;
|
|
235
|
+
}[],
|
|
236
|
+
) {
|
|
237
|
+
if (!joinConfig || !rows.length) {
|
|
238
|
+
return rows;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Group rows by main model ID
|
|
242
|
+
const groupedByMainId = new Map<string, any>();
|
|
243
|
+
|
|
244
|
+
for (const currentRow of rows) {
|
|
245
|
+
// Separate main model columns from joined columns
|
|
246
|
+
const mainModelFields: Record<string, any> = {};
|
|
247
|
+
const joinedModelFields: Record<string, Record<string, any>> = {};
|
|
248
|
+
|
|
249
|
+
// Initialize joined model fields map
|
|
250
|
+
for (const [joinModel] of Object.entries(joinConfig)) {
|
|
251
|
+
joinedModelFields[getModelName(joinModel)] = {};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Distribute all columns - collect complete objects per model
|
|
255
|
+
for (const [key, value] of Object.entries(currentRow)) {
|
|
256
|
+
const keyStr = String(key);
|
|
257
|
+
let assigned = false;
|
|
258
|
+
|
|
259
|
+
// Check if this is a joined column
|
|
260
|
+
for (const {
|
|
261
|
+
joinModel,
|
|
262
|
+
fieldName,
|
|
263
|
+
joinModelRef,
|
|
264
|
+
} of allSelectsStr) {
|
|
265
|
+
if (keyStr === `_joined_${joinModelRef}_${fieldName}`) {
|
|
266
|
+
joinedModelFields[getModelName(joinModel)]![
|
|
267
|
+
getFieldName({
|
|
268
|
+
model: joinModel,
|
|
269
|
+
field: fieldName,
|
|
270
|
+
})
|
|
271
|
+
] = value;
|
|
272
|
+
assigned = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!assigned) {
|
|
278
|
+
mainModelFields[key] = value;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const mainId = mainModelFields.id;
|
|
283
|
+
if (!mainId) continue;
|
|
284
|
+
|
|
285
|
+
// Initialize or get existing entry for this main model
|
|
286
|
+
if (!groupedByMainId.has(mainId)) {
|
|
287
|
+
const entry: Record<string, any> = { ...mainModelFields };
|
|
288
|
+
|
|
289
|
+
// Initialize joined models based on uniqueness
|
|
290
|
+
for (const [joinModel, joinAttr] of Object.entries(joinConfig)) {
|
|
291
|
+
entry[getModelName(joinModel)] =
|
|
292
|
+
joinAttr.relation === "one-to-one" ? null : [];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
groupedByMainId.set(mainId, entry);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const entry = groupedByMainId.get(mainId)!;
|
|
299
|
+
|
|
300
|
+
// Add joined records to the entry
|
|
301
|
+
for (const [joinModel, joinAttr] of Object.entries(joinConfig)) {
|
|
302
|
+
const isUnique = joinAttr.relation === "one-to-one";
|
|
303
|
+
const limit = joinAttr.limit ?? 100;
|
|
304
|
+
|
|
305
|
+
const joinedObj = joinedModelFields[getModelName(joinModel)];
|
|
306
|
+
|
|
307
|
+
const hasData =
|
|
308
|
+
joinedObj &&
|
|
309
|
+
Object.keys(joinedObj).length > 0 &&
|
|
310
|
+
Object.values(joinedObj).some(
|
|
311
|
+
(value) => value !== null && value !== undefined,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (isUnique) {
|
|
315
|
+
entry[getModelName(joinModel)] = hasData ? joinedObj : null;
|
|
316
|
+
} else {
|
|
317
|
+
// For arrays, append if not already there (deduplicate by id) and respect limit
|
|
318
|
+
const joinModelName = getModelName(joinModel);
|
|
319
|
+
if (Array.isArray(entry[joinModelName]) && hasData) {
|
|
320
|
+
// Check if we've reached the limit before processing
|
|
321
|
+
if (entry[joinModelName].length >= limit) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Get the id field name using getFieldName to ensure correct transformation
|
|
326
|
+
const idFieldName = getFieldName({
|
|
327
|
+
model: joinModel,
|
|
328
|
+
field: "id",
|
|
329
|
+
});
|
|
330
|
+
const joinedId = joinedObj[idFieldName];
|
|
331
|
+
|
|
332
|
+
// Only deduplicate if we have an id field
|
|
333
|
+
if (joinedId) {
|
|
334
|
+
const exists = entry[joinModelName].some(
|
|
335
|
+
(item: any) => item[idFieldName] === joinedId,
|
|
336
|
+
);
|
|
337
|
+
if (!exists && entry[joinModelName].length < limit) {
|
|
338
|
+
entry[joinModelName].push(joinedObj);
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
// If no id field, still add the object if it has data and limit not reached
|
|
342
|
+
if (entry[joinModelName].length < limit) {
|
|
343
|
+
entry[joinModelName].push(joinedObj);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const result = Array.from(groupedByMainId.values());
|
|
352
|
+
|
|
353
|
+
// Apply final limit to non-unique join arrays as a safety measure
|
|
354
|
+
for (const entry of result) {
|
|
355
|
+
for (const [joinModel, joinAttr] of Object.entries(joinConfig)) {
|
|
356
|
+
if (joinAttr.relation !== "one-to-one") {
|
|
357
|
+
const joinModelName = getModelName(joinModel);
|
|
358
|
+
if (Array.isArray(entry[joinModelName])) {
|
|
359
|
+
const limit = joinAttr.limit ?? 100;
|
|
360
|
+
if (entry[joinModelName].length > limit) {
|
|
361
|
+
entry[joinModelName] = entry[joinModelName].slice(0, limit);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
async create({ data, model }) {
|
|
373
|
+
const builder = db.insertInto(model).values(data);
|
|
374
|
+
const returned = await withReturning(data, builder, model, []);
|
|
375
|
+
return returned;
|
|
376
|
+
},
|
|
377
|
+
async findOne({ model, where, select, join }) {
|
|
378
|
+
const { and, or } = convertWhereClause(model, where);
|
|
379
|
+
let query: any = db
|
|
380
|
+
.selectFrom((eb) => {
|
|
381
|
+
let b = eb.selectFrom(model);
|
|
382
|
+
if (and) {
|
|
383
|
+
b = b.where((eb: any) =>
|
|
384
|
+
eb.and(and.map((expr: any) => expr(eb))),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (or) {
|
|
388
|
+
b = b.where((eb: any) =>
|
|
389
|
+
eb.or(or.map((expr: any) => expr(eb))),
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return b.selectAll().as("primary");
|
|
393
|
+
})
|
|
394
|
+
.selectAll("primary");
|
|
395
|
+
|
|
396
|
+
if (join) {
|
|
397
|
+
for (const [joinModel, joinAttr] of Object.entries(join)) {
|
|
398
|
+
const [_joinModelSchema, joinModelName] = joinModel.includes(".")
|
|
399
|
+
? joinModel.split(".")
|
|
400
|
+
: [undefined, joinModel];
|
|
401
|
+
|
|
402
|
+
query = query.leftJoin(
|
|
403
|
+
`${joinModel} as join_${joinModelName}`,
|
|
404
|
+
(join: any) =>
|
|
405
|
+
join.onRef(
|
|
406
|
+
`join_${joinModelName}.${joinAttr.on.to}`,
|
|
407
|
+
"=",
|
|
408
|
+
`primary.${joinAttr.on.from}`,
|
|
409
|
+
),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const { allSelectsStr, allSelects } = selectAllJoins(join);
|
|
415
|
+
query = query.select(allSelects);
|
|
416
|
+
|
|
417
|
+
const res = await query.execute();
|
|
418
|
+
if (!res || !Array.isArray(res) || res.length === 0) return null;
|
|
419
|
+
|
|
420
|
+
// Get the first row from the result array
|
|
421
|
+
const row = res[0];
|
|
422
|
+
|
|
423
|
+
if (join) {
|
|
424
|
+
const processedRows = processJoinedResults(
|
|
425
|
+
res,
|
|
426
|
+
join,
|
|
427
|
+
allSelectsStr,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
return processedRows[0] as any;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return row as any;
|
|
434
|
+
},
|
|
435
|
+
async findMany({ model, where, limit, offset, sortBy, join }) {
|
|
436
|
+
const { and, or } = convertWhereClause(model, where);
|
|
437
|
+
let query: any = db
|
|
438
|
+
.selectFrom((eb) => {
|
|
439
|
+
let b = eb.selectFrom(model);
|
|
440
|
+
|
|
441
|
+
if (config?.type === "mssql") {
|
|
442
|
+
if (offset !== undefined) {
|
|
443
|
+
if (!sortBy) {
|
|
444
|
+
b = b.orderBy(getFieldName({ model, field: "id" }));
|
|
445
|
+
}
|
|
446
|
+
b = b.offset(offset).fetch(limit || 100);
|
|
447
|
+
} else if (limit !== undefined) {
|
|
448
|
+
b = b.top(limit);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
if (limit !== undefined) {
|
|
452
|
+
b = b.limit(limit);
|
|
453
|
+
}
|
|
454
|
+
if (offset !== undefined) {
|
|
455
|
+
b = b.offset(offset);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (sortBy?.field) {
|
|
460
|
+
b = b.orderBy(
|
|
461
|
+
`${getFieldName({ model, field: sortBy.field })}`,
|
|
462
|
+
sortBy.direction,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (and) {
|
|
467
|
+
b = b.where((eb: any) =>
|
|
468
|
+
eb.and(and.map((expr: any) => expr(eb))),
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (or) {
|
|
473
|
+
b = b.where((eb: any) =>
|
|
474
|
+
eb.or(or.map((expr: any) => expr(eb))),
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return b.selectAll().as("primary");
|
|
479
|
+
})
|
|
480
|
+
.selectAll("primary");
|
|
481
|
+
|
|
482
|
+
if (join) {
|
|
483
|
+
for (const [joinModel, joinAttr] of Object.entries(join)) {
|
|
484
|
+
// it's possible users provide a schema name in the model name (`<schema>.<model>`)
|
|
485
|
+
const [_joinModelSchema, joinModelName] = joinModel.includes(".")
|
|
486
|
+
? joinModel.split(".")
|
|
487
|
+
: [undefined, joinModel];
|
|
488
|
+
|
|
489
|
+
query = query.leftJoin(
|
|
490
|
+
`${joinModel} as join_${joinModelName}`,
|
|
491
|
+
(join: any) =>
|
|
492
|
+
join.onRef(
|
|
493
|
+
`join_${joinModelName}.${joinAttr.on.to}`,
|
|
494
|
+
"=",
|
|
495
|
+
`primary.${joinAttr.on.from}`,
|
|
496
|
+
),
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const { allSelectsStr, allSelects } = selectAllJoins(join);
|
|
502
|
+
|
|
503
|
+
query = query.select(allSelects);
|
|
504
|
+
|
|
505
|
+
if (sortBy?.field) {
|
|
506
|
+
query = query.orderBy(
|
|
507
|
+
`${getFieldName({ model, field: sortBy.field })}`,
|
|
508
|
+
sortBy.direction,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const res = await query.execute();
|
|
513
|
+
|
|
514
|
+
if (!res) return [];
|
|
515
|
+
if (join) return processJoinedResults(res, join, allSelectsStr);
|
|
516
|
+
return res;
|
|
517
|
+
},
|
|
518
|
+
async update({ model, where, update: values }) {
|
|
519
|
+
const { and, or } = convertWhereClause(model, where);
|
|
520
|
+
|
|
521
|
+
let query = db.updateTable(model).set(values as any);
|
|
522
|
+
if (and) {
|
|
523
|
+
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
524
|
+
}
|
|
525
|
+
if (or) {
|
|
526
|
+
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
527
|
+
}
|
|
528
|
+
return await withReturning(values as any, query, model, where);
|
|
529
|
+
},
|
|
530
|
+
async updateMany({ model, where, update: values }) {
|
|
531
|
+
const { and, or } = convertWhereClause(model, where);
|
|
532
|
+
let query = db.updateTable(model).set(values as any);
|
|
533
|
+
if (and) {
|
|
534
|
+
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
535
|
+
}
|
|
536
|
+
if (or) {
|
|
537
|
+
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
538
|
+
}
|
|
539
|
+
const res = (await query.executeTakeFirst()).numUpdatedRows;
|
|
540
|
+
return res > Number.MAX_SAFE_INTEGER
|
|
541
|
+
? Number.MAX_SAFE_INTEGER
|
|
542
|
+
: Number(res);
|
|
543
|
+
},
|
|
544
|
+
async count({ model, where }) {
|
|
545
|
+
const { and, or } = convertWhereClause(model, where);
|
|
546
|
+
let query = db
|
|
547
|
+
.selectFrom(model)
|
|
548
|
+
// a temporal solution for counting other than "*" - see more - https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted
|
|
549
|
+
.select(db.fn.count("id").as("count"));
|
|
550
|
+
if (and) {
|
|
551
|
+
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
552
|
+
}
|
|
553
|
+
if (or) {
|
|
554
|
+
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
555
|
+
}
|
|
556
|
+
const res = await query.execute();
|
|
557
|
+
if (typeof res[0]!.count === "number") {
|
|
558
|
+
return res[0]!.count;
|
|
559
|
+
}
|
|
560
|
+
if (typeof res[0]!.count === "bigint") {
|
|
561
|
+
return Number(res[0]!.count);
|
|
562
|
+
}
|
|
563
|
+
return parseInt(res[0]!.count);
|
|
564
|
+
},
|
|
565
|
+
async delete({ model, where }) {
|
|
566
|
+
const { and, or } = convertWhereClause(model, where);
|
|
567
|
+
let query = db.deleteFrom(model);
|
|
568
|
+
if (and) {
|
|
569
|
+
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (or) {
|
|
573
|
+
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
574
|
+
}
|
|
575
|
+
await query.execute();
|
|
576
|
+
},
|
|
577
|
+
async deleteMany({ model, where }) {
|
|
578
|
+
const { and, or } = convertWhereClause(model, where);
|
|
579
|
+
let query = db.deleteFrom(model);
|
|
580
|
+
if (and) {
|
|
581
|
+
query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
|
|
582
|
+
}
|
|
583
|
+
if (or) {
|
|
584
|
+
query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
|
|
585
|
+
}
|
|
586
|
+
const res = (await query.executeTakeFirst()).numDeletedRows;
|
|
587
|
+
return res > Number.MAX_SAFE_INTEGER
|
|
588
|
+
? Number.MAX_SAFE_INTEGER
|
|
589
|
+
: Number(res);
|
|
590
|
+
},
|
|
591
|
+
options: config,
|
|
592
|
+
};
|
|
593
|
+
};
|
|
594
|
+
};
|
|
595
|
+
let adapterOptions: AdapterFactoryOptions | null = null;
|
|
596
|
+
adapterOptions = {
|
|
597
|
+
config: {
|
|
598
|
+
adapterId: "kysely",
|
|
599
|
+
adapterName: "Kysely Adapter",
|
|
600
|
+
usePlural: config?.usePlural,
|
|
601
|
+
debugLogs: config?.debugLogs,
|
|
602
|
+
supportsBooleans:
|
|
603
|
+
config?.type === "sqlite" ||
|
|
604
|
+
config?.type === "mssql" ||
|
|
605
|
+
config?.type === "mysql" ||
|
|
606
|
+
!config?.type
|
|
607
|
+
? false
|
|
608
|
+
: true,
|
|
609
|
+
supportsDates:
|
|
610
|
+
config?.type === "sqlite" || config?.type === "mssql" || !config?.type
|
|
611
|
+
? false
|
|
612
|
+
: true,
|
|
613
|
+
supportsJSON:
|
|
614
|
+
config?.type === "postgres"
|
|
615
|
+
? true // even if there is JSON support, only pg supports passing direct json, all others must stringify
|
|
616
|
+
: false,
|
|
617
|
+
supportsArrays: false, // Even if field supports JSON, we must pass stringified arrays to the database.
|
|
618
|
+
supportsUUIDs: config?.type === "postgres" ? true : false,
|
|
619
|
+
transaction: config?.transaction
|
|
620
|
+
? (cb) =>
|
|
621
|
+
db.transaction().execute((trx) => {
|
|
622
|
+
const adapter = createAdapterFactory({
|
|
623
|
+
config: adapterOptions!.config,
|
|
624
|
+
adapter: createCustomAdapter(trx),
|
|
625
|
+
})(lazyOptions!);
|
|
626
|
+
return cb(adapter);
|
|
627
|
+
})
|
|
628
|
+
: false,
|
|
629
|
+
},
|
|
630
|
+
adapter: createCustomAdapter(db),
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const adapter = createAdapterFactory(adapterOptions);
|
|
634
|
+
|
|
635
|
+
return (options: BetterAuthOptions): DBAdapter<BetterAuthOptions> => {
|
|
636
|
+
lazyOptions = options;
|
|
637
|
+
return adapter(options);
|
|
638
|
+
};
|
|
639
|
+
};
|