@better-auth/prisma-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 +13 -0
- package/LICENSE.md +20 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.mjs +285 -0
- package/package.json +43 -0
- package/src/index.ts +1 -0
- package/src/prisma-adapter.test.ts +14 -0
- package/src/prisma-adapter.ts +564 -0
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +7 -0
- package/vitest.config.ts +3 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
> @better-auth/prisma-adapter@1.5.0-beta.9 build /home/runner/work/better-auth/better-auth/packages/prisma-adapter
|
|
3
|
+
> tsdown
|
|
4
|
+
|
|
5
|
+
[34mℹ[39m tsdown [2mv0.19.0[22m powered by rolldown [2mv1.0.0-beta.59[22m
|
|
6
|
+
[34mℹ[39m config file: [4m/home/runner/work/better-auth/better-auth/packages/prisma-adapter/tsdown.config.ts[24m
|
|
7
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
8
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
|
+
[34mℹ[39m Build start
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m11.33 kB[22m [2m│ gzip: 2.66 kB[22m
|
|
11
|
+
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m 1.03 kB[22m [2m│ gzip: 0.48 kB[22m
|
|
12
|
+
[34mℹ[39m 2 files, total: 12.36 kB
|
|
13
|
+
[32m✔[39m Build complete in [32m8214ms[39m
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright (c) 2024 - present, Bereket Engida
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
6
|
+
the Software without restriction, including without limitation the rights to
|
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
8
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
9
|
+
subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
16
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
19
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
20
|
+
DEALINGS IN THE SOFTWARE.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DBAdapter, DBAdapterDebugLogOption } from "@better-auth/core/db/adapter";
|
|
2
|
+
import { BetterAuthOptions } from "@better-auth/core";
|
|
3
|
+
|
|
4
|
+
//#region src/prisma-adapter.d.ts
|
|
5
|
+
interface PrismaConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Database provider.
|
|
8
|
+
*/
|
|
9
|
+
provider: "sqlite" | "cockroachdb" | "mysql" | "postgresql" | "sqlserver" | "mongodb";
|
|
10
|
+
/**
|
|
11
|
+
* Enable debug logs for the adapter
|
|
12
|
+
*
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
debugLogs?: DBAdapterDebugLogOption | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Use plural table names
|
|
18
|
+
*
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
usePlural?: boolean | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Whether to execute multiple operations in a transaction.
|
|
24
|
+
*
|
|
25
|
+
* If the database doesn't support transactions,
|
|
26
|
+
* set this to `false` and operations will be executed sequentially.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
transaction?: boolean | undefined;
|
|
30
|
+
}
|
|
31
|
+
interface PrismaClient {}
|
|
32
|
+
declare const prismaAdapter: (prisma: PrismaClient, config: PrismaConfig) => (options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { PrismaConfig, prismaAdapter };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { createAdapterFactory } from "@better-auth/core/db/adapter";
|
|
2
|
+
import { BetterAuthError } from "@better-auth/core/error";
|
|
3
|
+
|
|
4
|
+
//#region src/prisma-adapter.ts
|
|
5
|
+
const prismaAdapter = (prisma, config) => {
|
|
6
|
+
let lazyOptions = null;
|
|
7
|
+
const createCustomAdapter = (prisma$1) => ({ getFieldName, getModelName, getFieldAttributes, getDefaultModelName, schema }) => {
|
|
8
|
+
const db = prisma$1;
|
|
9
|
+
const convertSelect = (select, model, join) => {
|
|
10
|
+
if (!select && !join) return void 0;
|
|
11
|
+
const result = {};
|
|
12
|
+
if (select) for (const field of select) result[getFieldName({
|
|
13
|
+
model,
|
|
14
|
+
field
|
|
15
|
+
})] = true;
|
|
16
|
+
if (join) {
|
|
17
|
+
if (!select) {
|
|
18
|
+
const fields = schema[getDefaultModelName(model)]?.fields || {};
|
|
19
|
+
fields.id = { type: "string" };
|
|
20
|
+
for (const field of Object.keys(fields)) result[getFieldName({
|
|
21
|
+
model,
|
|
22
|
+
field
|
|
23
|
+
})] = true;
|
|
24
|
+
}
|
|
25
|
+
for (const [joinModel, joinAttr] of Object.entries(join)) {
|
|
26
|
+
const key = getJoinKeyName(model, getModelName(joinModel), schema);
|
|
27
|
+
if (joinAttr.relation === "one-to-one") result[key] = true;
|
|
28
|
+
else result[key] = { take: joinAttr.limit };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Build the join key name based on whether the foreign field is unique or not.
|
|
35
|
+
* If unique, use singular. Otherwise, pluralize (add 's').
|
|
36
|
+
*/
|
|
37
|
+
const getJoinKeyName = (baseModel, joinedModel, schema$1) => {
|
|
38
|
+
try {
|
|
39
|
+
const defaultBaseModelName = getDefaultModelName(baseModel);
|
|
40
|
+
const defaultJoinedModelName = getDefaultModelName(joinedModel);
|
|
41
|
+
const key = getModelName(joinedModel).toLowerCase();
|
|
42
|
+
let foreignKeys = Object.entries(schema$1[defaultJoinedModelName]?.fields || {}).filter(([_field, fieldAttributes]) => fieldAttributes.references && getDefaultModelName(fieldAttributes.references.model) === defaultBaseModelName);
|
|
43
|
+
if (foreignKeys.length > 0) {
|
|
44
|
+
const [_foreignKey, foreignKeyAttributes] = foreignKeys[0];
|
|
45
|
+
return foreignKeyAttributes?.unique === true || config.usePlural === true ? key : `${key}s`;
|
|
46
|
+
}
|
|
47
|
+
foreignKeys = Object.entries(schema$1[defaultBaseModelName]?.fields || {}).filter(([_field, fieldAttributes]) => fieldAttributes.references && getDefaultModelName(fieldAttributes.references.model) === defaultJoinedModelName);
|
|
48
|
+
if (foreignKeys.length > 0) return key;
|
|
49
|
+
} catch {}
|
|
50
|
+
return `${getModelName(joinedModel).toLowerCase()}s`;
|
|
51
|
+
};
|
|
52
|
+
function operatorToPrismaOperator(operator) {
|
|
53
|
+
switch (operator) {
|
|
54
|
+
case "starts_with": return "startsWith";
|
|
55
|
+
case "ends_with": return "endsWith";
|
|
56
|
+
case "ne": return "not";
|
|
57
|
+
case "not_in": return "notIn";
|
|
58
|
+
default: return operator;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const convertWhereClause = ({ action, model, where }) => {
|
|
62
|
+
if (!where || !where.length) return {};
|
|
63
|
+
const buildSingleCondition = (w) => {
|
|
64
|
+
const fieldName = getFieldName({
|
|
65
|
+
model,
|
|
66
|
+
field: w.field
|
|
67
|
+
});
|
|
68
|
+
if (w.operator === "ne" && w.value === null) return getFieldAttributes({
|
|
69
|
+
model,
|
|
70
|
+
field: w.field
|
|
71
|
+
})?.required !== true ? { [fieldName]: { not: null } } : {};
|
|
72
|
+
if ((w.operator === "in" || w.operator === "not_in") && Array.isArray(w.value)) {
|
|
73
|
+
const filtered = w.value.filter((v) => v != null);
|
|
74
|
+
if (filtered.length === 0) if (w.operator === "in") return { AND: [{ [fieldName]: { equals: "__never__" } }, { [fieldName]: { not: "__never__" } }] };
|
|
75
|
+
else return {};
|
|
76
|
+
const prismaOp = operatorToPrismaOperator(w.operator);
|
|
77
|
+
return { [fieldName]: { [prismaOp]: filtered } };
|
|
78
|
+
}
|
|
79
|
+
if (w.operator === "eq" || !w.operator) return { [fieldName]: w.value };
|
|
80
|
+
return { [fieldName]: { [operatorToPrismaOperator(w.operator)]: w.value } };
|
|
81
|
+
};
|
|
82
|
+
if (action === "update") {
|
|
83
|
+
const and$1 = where.filter((w) => w.connector === "AND" || !w.connector);
|
|
84
|
+
const or$1 = where.filter((w) => w.connector === "OR");
|
|
85
|
+
const andSimple = and$1.filter((w) => w.operator === "eq" || !w.operator);
|
|
86
|
+
const andComplex = and$1.filter((w) => w.operator !== "eq" && w.operator !== void 0);
|
|
87
|
+
const andSimpleClause = andSimple.map((w) => buildSingleCondition(w));
|
|
88
|
+
const andComplexClause = andComplex.map((w) => buildSingleCondition(w));
|
|
89
|
+
const orClause$1 = or$1.map((w) => buildSingleCondition(w));
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const clause of andSimpleClause) Object.assign(result, clause);
|
|
92
|
+
if (andComplexClause.length > 0) result.AND = andComplexClause;
|
|
93
|
+
if (orClause$1.length > 0) result.OR = orClause$1;
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
if (action === "delete") {
|
|
97
|
+
const idCondition = where.find((w) => w.field === "id");
|
|
98
|
+
if (idCondition) {
|
|
99
|
+
const idFieldName = getFieldName({
|
|
100
|
+
model,
|
|
101
|
+
field: "id"
|
|
102
|
+
});
|
|
103
|
+
const idClause = buildSingleCondition(idCondition);
|
|
104
|
+
const remainingWhere = where.filter((w) => w.field !== "id");
|
|
105
|
+
if (remainingWhere.length === 0) return idClause;
|
|
106
|
+
const and$1 = remainingWhere.filter((w) => w.connector === "AND" || !w.connector);
|
|
107
|
+
const or$1 = remainingWhere.filter((w) => w.connector === "OR");
|
|
108
|
+
const andClause$1 = and$1.map((w) => buildSingleCondition(w));
|
|
109
|
+
const orClause$1 = or$1.map((w) => buildSingleCondition(w));
|
|
110
|
+
const result = {};
|
|
111
|
+
if (idFieldName in idClause) result[idFieldName] = idClause[idFieldName];
|
|
112
|
+
else Object.assign(result, idClause);
|
|
113
|
+
if (andClause$1.length > 0) result.AND = andClause$1;
|
|
114
|
+
if (orClause$1.length > 0) result.OR = orClause$1;
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (where.length === 1) {
|
|
119
|
+
const w = where[0];
|
|
120
|
+
if (!w) return;
|
|
121
|
+
return buildSingleCondition(w);
|
|
122
|
+
}
|
|
123
|
+
const and = where.filter((w) => w.connector === "AND" || !w.connector);
|
|
124
|
+
const or = where.filter((w) => w.connector === "OR");
|
|
125
|
+
const andClause = and.map((w) => buildSingleCondition(w));
|
|
126
|
+
const orClause = or.map((w) => buildSingleCondition(w));
|
|
127
|
+
return {
|
|
128
|
+
...andClause.length ? { AND: andClause } : {},
|
|
129
|
+
...orClause.length ? { OR: orClause } : {}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
async create({ model, data: values, select }) {
|
|
134
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
135
|
+
return await db[model].create({
|
|
136
|
+
data: values,
|
|
137
|
+
select: convertSelect(select, model)
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
async findOne({ model, where, select, join }) {
|
|
141
|
+
const whereClause = convertWhereClause({
|
|
142
|
+
model,
|
|
143
|
+
where,
|
|
144
|
+
action: "findOne"
|
|
145
|
+
});
|
|
146
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
147
|
+
const map = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const joinModel of Object.keys(join ?? {})) {
|
|
149
|
+
const key = getJoinKeyName(model, joinModel, schema);
|
|
150
|
+
map.set(key, getModelName(joinModel));
|
|
151
|
+
}
|
|
152
|
+
const selects = convertSelect(select, model, join);
|
|
153
|
+
const result = await db[model].findFirst({
|
|
154
|
+
where: whereClause,
|
|
155
|
+
select: selects
|
|
156
|
+
});
|
|
157
|
+
if (join && result) for (const [includeKey, originalKey] of map.entries()) {
|
|
158
|
+
if (includeKey === originalKey) continue;
|
|
159
|
+
if (includeKey in result) {
|
|
160
|
+
result[originalKey] = result[includeKey];
|
|
161
|
+
delete result[includeKey];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
},
|
|
166
|
+
async findMany({ model, where, limit, offset, sortBy, join }) {
|
|
167
|
+
const whereClause = convertWhereClause({
|
|
168
|
+
model,
|
|
169
|
+
where,
|
|
170
|
+
action: "findMany"
|
|
171
|
+
});
|
|
172
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
173
|
+
const map = /* @__PURE__ */ new Map();
|
|
174
|
+
if (join) for (const [joinModel, _value] of Object.entries(join)) {
|
|
175
|
+
const key = getJoinKeyName(model, joinModel, schema);
|
|
176
|
+
map.set(key, getModelName(joinModel));
|
|
177
|
+
}
|
|
178
|
+
const selects = convertSelect(void 0, model, join);
|
|
179
|
+
const result = await db[model].findMany({
|
|
180
|
+
where: whereClause,
|
|
181
|
+
take: limit || 100,
|
|
182
|
+
skip: offset || 0,
|
|
183
|
+
...sortBy?.field ? { orderBy: { [getFieldName({
|
|
184
|
+
model,
|
|
185
|
+
field: sortBy.field
|
|
186
|
+
})]: sortBy.direction === "desc" ? "desc" : "asc" } } : {},
|
|
187
|
+
select: selects
|
|
188
|
+
});
|
|
189
|
+
if (join && Array.isArray(result)) for (const item of result) for (const [includeKey, originalKey] of map.entries()) {
|
|
190
|
+
if (includeKey === originalKey) continue;
|
|
191
|
+
if (includeKey in item) {
|
|
192
|
+
item[originalKey] = item[includeKey];
|
|
193
|
+
delete item[includeKey];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
},
|
|
198
|
+
async count({ model, where }) {
|
|
199
|
+
const whereClause = convertWhereClause({
|
|
200
|
+
model,
|
|
201
|
+
where,
|
|
202
|
+
action: "count"
|
|
203
|
+
});
|
|
204
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
205
|
+
return await db[model].count({ where: whereClause });
|
|
206
|
+
},
|
|
207
|
+
async update({ model, where, update }) {
|
|
208
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
209
|
+
const whereClause = convertWhereClause({
|
|
210
|
+
model,
|
|
211
|
+
where,
|
|
212
|
+
action: "update"
|
|
213
|
+
});
|
|
214
|
+
return await db[model].update({
|
|
215
|
+
where: whereClause,
|
|
216
|
+
data: update
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
async updateMany({ model, where, update }) {
|
|
220
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
221
|
+
const whereClause = convertWhereClause({
|
|
222
|
+
model,
|
|
223
|
+
where,
|
|
224
|
+
action: "updateMany"
|
|
225
|
+
});
|
|
226
|
+
const result = await db[model].updateMany({
|
|
227
|
+
where: whereClause,
|
|
228
|
+
data: update
|
|
229
|
+
});
|
|
230
|
+
return result ? result.count : 0;
|
|
231
|
+
},
|
|
232
|
+
async delete({ model, where }) {
|
|
233
|
+
if (!db[model]) throw new BetterAuthError(`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`);
|
|
234
|
+
const whereClause = convertWhereClause({
|
|
235
|
+
model,
|
|
236
|
+
where,
|
|
237
|
+
action: "delete"
|
|
238
|
+
});
|
|
239
|
+
try {
|
|
240
|
+
await db[model].delete({ where: whereClause });
|
|
241
|
+
} catch (e) {
|
|
242
|
+
if (e?.meta?.cause === "Record to delete does not exist.") return;
|
|
243
|
+
if (e?.code === "P2025") return;
|
|
244
|
+
console.log(e);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
async deleteMany({ model, where }) {
|
|
248
|
+
const whereClause = convertWhereClause({
|
|
249
|
+
model,
|
|
250
|
+
where,
|
|
251
|
+
action: "deleteMany"
|
|
252
|
+
});
|
|
253
|
+
const result = await db[model].deleteMany({ where: whereClause });
|
|
254
|
+
return result ? result.count : 0;
|
|
255
|
+
},
|
|
256
|
+
options: config
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
let adapterOptions = null;
|
|
260
|
+
adapterOptions = {
|
|
261
|
+
config: {
|
|
262
|
+
adapterId: "prisma",
|
|
263
|
+
adapterName: "Prisma Adapter",
|
|
264
|
+
usePlural: config.usePlural ?? false,
|
|
265
|
+
debugLogs: config.debugLogs ?? false,
|
|
266
|
+
supportsUUIDs: config.provider === "postgresql" ? true : false,
|
|
267
|
+
supportsArrays: config.provider === "postgresql" || config.provider === "mongodb" ? true : false,
|
|
268
|
+
transaction: config.transaction ?? false ? (cb) => prisma.$transaction((tx) => {
|
|
269
|
+
return cb(createAdapterFactory({
|
|
270
|
+
config: adapterOptions.config,
|
|
271
|
+
adapter: createCustomAdapter(tx)
|
|
272
|
+
})(lazyOptions));
|
|
273
|
+
}) : false
|
|
274
|
+
},
|
|
275
|
+
adapter: createCustomAdapter(prisma)
|
|
276
|
+
};
|
|
277
|
+
const adapter = createAdapterFactory(adapterOptions);
|
|
278
|
+
return (options) => {
|
|
279
|
+
lazyOptions = options;
|
|
280
|
+
return adapter(options);
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
export { prismaAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-auth/prisma-adapter",
|
|
3
|
+
"version": "1.5.0-beta.9",
|
|
4
|
+
"description": "Prisma adapter for Better Auth",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/better-auth/better-auth.git",
|
|
9
|
+
"directory": "packages/prisma-adapter"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.mjs",
|
|
12
|
+
"module": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"dev-source": "./src/index.ts",
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@prisma/client": "^5.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
26
|
+
"@better-auth/utils": "^0.3.0",
|
|
27
|
+
"prisma": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
28
|
+
"@better-auth/core": "1.5.0-beta.9"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@better-auth/utils": "^0.3.0",
|
|
32
|
+
"prisma": "^5.0.0",
|
|
33
|
+
"tsdown": "^0.19.0",
|
|
34
|
+
"typescript": "^5.9.3",
|
|
35
|
+
"@better-auth/core": "1.5.0-beta.9"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"dev": "tsdown --watch",
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./prisma-adapter";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { prismaAdapter } from "./prisma-adapter";
|
|
3
|
+
|
|
4
|
+
describe("prisma-adapter", () => {
|
|
5
|
+
it("should create prisma adapter", () => {
|
|
6
|
+
const prisma = {
|
|
7
|
+
$transaction: vi.fn(),
|
|
8
|
+
} as any;
|
|
9
|
+
const adapter = prismaAdapter(prisma, {
|
|
10
|
+
provider: "sqlite",
|
|
11
|
+
});
|
|
12
|
+
expect(adapter).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import type { Awaitable, 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 { BetterAuthError } from "@better-auth/core/error";
|
|
12
|
+
|
|
13
|
+
export interface PrismaConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Database provider.
|
|
16
|
+
*/
|
|
17
|
+
provider:
|
|
18
|
+
| "sqlite"
|
|
19
|
+
| "cockroachdb"
|
|
20
|
+
| "mysql"
|
|
21
|
+
| "postgresql"
|
|
22
|
+
| "sqlserver"
|
|
23
|
+
| "mongodb";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enable debug logs for the adapter
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
debugLogs?: DBAdapterDebugLogOption | undefined;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Use plural table names
|
|
34
|
+
*
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
usePlural?: boolean | undefined;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether to execute multiple operations in a transaction.
|
|
41
|
+
*
|
|
42
|
+
* If the database doesn't support transactions,
|
|
43
|
+
* set this to `false` and operations will be executed sequentially.
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
transaction?: boolean | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface PrismaClient {}
|
|
50
|
+
|
|
51
|
+
type PrismaClientInternal = {
|
|
52
|
+
$transaction: (
|
|
53
|
+
callback: (db: PrismaClient) => Awaitable<any>,
|
|
54
|
+
) => Promise<any>;
|
|
55
|
+
} & {
|
|
56
|
+
[model: string]: {
|
|
57
|
+
create: (data: any) => Promise<any>;
|
|
58
|
+
findFirst: (data: any) => Promise<any>;
|
|
59
|
+
findMany: (data: any) => Promise<any>;
|
|
60
|
+
update: (data: any) => Promise<any>;
|
|
61
|
+
updateMany: (data: any) => Promise<any>;
|
|
62
|
+
delete: (data: any) => Promise<any>;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const prismaAdapter = (prisma: PrismaClient, config: PrismaConfig) => {
|
|
68
|
+
let lazyOptions: BetterAuthOptions | null = null;
|
|
69
|
+
const createCustomAdapter =
|
|
70
|
+
(prisma: PrismaClient): AdapterFactoryCustomizeAdapterCreator =>
|
|
71
|
+
({
|
|
72
|
+
getFieldName,
|
|
73
|
+
getModelName,
|
|
74
|
+
getFieldAttributes,
|
|
75
|
+
getDefaultModelName,
|
|
76
|
+
schema,
|
|
77
|
+
}) => {
|
|
78
|
+
const db = prisma as PrismaClientInternal;
|
|
79
|
+
|
|
80
|
+
const convertSelect = (
|
|
81
|
+
select: string[] | undefined,
|
|
82
|
+
model: string,
|
|
83
|
+
join?: JoinConfig | undefined,
|
|
84
|
+
) => {
|
|
85
|
+
if (!select && !join) return undefined;
|
|
86
|
+
|
|
87
|
+
const result: Record<string, Record<string, any> | boolean> = {};
|
|
88
|
+
|
|
89
|
+
if (select) {
|
|
90
|
+
for (const field of select) {
|
|
91
|
+
result[getFieldName({ model, field })] = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (join) {
|
|
96
|
+
// when joining that has a limit, we need to use Prisma's `select` syntax to append the limit to the field
|
|
97
|
+
// because of such, it also means we need to select all base-model fields as well
|
|
98
|
+
// should check if `select` is not provided, because then we should select all base-model fields
|
|
99
|
+
if (!select) {
|
|
100
|
+
const fields = schema[getDefaultModelName(model)]?.fields || {};
|
|
101
|
+
fields.id = { type: "string" }; // make sure there is at least an id field
|
|
102
|
+
for (const field of Object.keys(fields)) {
|
|
103
|
+
result[getFieldName({ model, field })] = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const [joinModel, joinAttr] of Object.entries(join)) {
|
|
108
|
+
const key = getJoinKeyName(model, getModelName(joinModel), schema);
|
|
109
|
+
if (joinAttr.relation === "one-to-one") {
|
|
110
|
+
result[key] = true;
|
|
111
|
+
} else {
|
|
112
|
+
result[key] = { take: joinAttr.limit };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Build the join key name based on whether the foreign field is unique or not.
|
|
122
|
+
* If unique, use singular. Otherwise, pluralize (add 's').
|
|
123
|
+
*/
|
|
124
|
+
const getJoinKeyName = (
|
|
125
|
+
baseModel: string,
|
|
126
|
+
joinedModel: string,
|
|
127
|
+
schema: any,
|
|
128
|
+
): string => {
|
|
129
|
+
try {
|
|
130
|
+
const defaultBaseModelName = getDefaultModelName(baseModel);
|
|
131
|
+
const defaultJoinedModelName = getDefaultModelName(joinedModel);
|
|
132
|
+
const key = getModelName(joinedModel).toLowerCase();
|
|
133
|
+
|
|
134
|
+
// First, check if the joined model has FKs to the base model (forward join)
|
|
135
|
+
let foreignKeys = Object.entries(
|
|
136
|
+
schema[defaultJoinedModelName]?.fields || {},
|
|
137
|
+
).filter(
|
|
138
|
+
([_field, fieldAttributes]: any) =>
|
|
139
|
+
fieldAttributes.references &&
|
|
140
|
+
getDefaultModelName(fieldAttributes.references.model) ===
|
|
141
|
+
defaultBaseModelName,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (foreignKeys.length > 0) {
|
|
145
|
+
// Forward join: joined model has FK to base model
|
|
146
|
+
// This is typically a one-to-many relationship (plural)
|
|
147
|
+
// Unless the FK is unique, then it's one-to-one (singular)
|
|
148
|
+
const [_foreignKey, foreignKeyAttributes] = foreignKeys[0] as any;
|
|
149
|
+
// Only check if field is explicitly marked as unique
|
|
150
|
+
const isUnique = foreignKeyAttributes?.unique === true;
|
|
151
|
+
return isUnique || config.usePlural === true ? key : `${key}s`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check backwards: does the base model have FKs to the joined model?
|
|
155
|
+
foreignKeys = Object.entries(
|
|
156
|
+
schema[defaultBaseModelName]?.fields || {},
|
|
157
|
+
).filter(
|
|
158
|
+
([_field, fieldAttributes]: any) =>
|
|
159
|
+
fieldAttributes.references &&
|
|
160
|
+
getDefaultModelName(fieldAttributes.references.model) ===
|
|
161
|
+
defaultJoinedModelName,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (foreignKeys.length > 0) {
|
|
165
|
+
return key;
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
// Fallback to pluralizing if we can't determine uniqueness
|
|
169
|
+
}
|
|
170
|
+
return `${getModelName(joinedModel).toLowerCase()}s`;
|
|
171
|
+
};
|
|
172
|
+
function operatorToPrismaOperator(operator: string) {
|
|
173
|
+
switch (operator) {
|
|
174
|
+
case "starts_with":
|
|
175
|
+
return "startsWith";
|
|
176
|
+
case "ends_with":
|
|
177
|
+
return "endsWith";
|
|
178
|
+
case "ne":
|
|
179
|
+
return "not";
|
|
180
|
+
case "not_in":
|
|
181
|
+
return "notIn";
|
|
182
|
+
default:
|
|
183
|
+
return operator;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const convertWhereClause = ({
|
|
187
|
+
action,
|
|
188
|
+
model,
|
|
189
|
+
where,
|
|
190
|
+
}: {
|
|
191
|
+
model: string;
|
|
192
|
+
where?: Where[] | undefined;
|
|
193
|
+
action:
|
|
194
|
+
| "create"
|
|
195
|
+
| "update"
|
|
196
|
+
| "delete"
|
|
197
|
+
| "findOne"
|
|
198
|
+
| "findMany"
|
|
199
|
+
| "count"
|
|
200
|
+
| "updateMany"
|
|
201
|
+
| "deleteMany";
|
|
202
|
+
}) => {
|
|
203
|
+
if (!where || !where.length) return {};
|
|
204
|
+
const buildSingleCondition = (w: Where) => {
|
|
205
|
+
const fieldName = getFieldName({ model, field: w.field });
|
|
206
|
+
// Special handling for Prisma null semantics, for non-nullable fields this is a tautology. Skip condition.
|
|
207
|
+
if (w.operator === "ne" && w.value === null) {
|
|
208
|
+
const fieldAttributes = getFieldAttributes({
|
|
209
|
+
model,
|
|
210
|
+
field: w.field,
|
|
211
|
+
});
|
|
212
|
+
const isNullable = fieldAttributes?.required !== true;
|
|
213
|
+
return isNullable ? { [fieldName]: { not: null } } : {};
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
(w.operator === "in" || w.operator === "not_in") &&
|
|
217
|
+
Array.isArray(w.value)
|
|
218
|
+
) {
|
|
219
|
+
const filtered = w.value.filter((v) => v != null);
|
|
220
|
+
if (filtered.length === 0) {
|
|
221
|
+
if (w.operator === "in") {
|
|
222
|
+
return {
|
|
223
|
+
AND: [
|
|
224
|
+
{ [fieldName]: { equals: "__never__" } },
|
|
225
|
+
{ [fieldName]: { not: "__never__" } },
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
} else {
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const prismaOp = operatorToPrismaOperator(w.operator);
|
|
233
|
+
return { [fieldName]: { [prismaOp]: filtered } };
|
|
234
|
+
}
|
|
235
|
+
if (w.operator === "eq" || !w.operator) {
|
|
236
|
+
return { [fieldName]: w.value };
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
[fieldName]: {
|
|
240
|
+
[operatorToPrismaOperator(w.operator)]: w.value,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Special handling for update actions: extract AND conditions with eq operator to root level
|
|
246
|
+
// Prisma requires unique fields to be at root level, not nested in AND arrays
|
|
247
|
+
// Only simple equality conditions can be at root level; complex operators must stay in AND array
|
|
248
|
+
if (action === "update") {
|
|
249
|
+
const and = where.filter(
|
|
250
|
+
(w) => w.connector === "AND" || !w.connector,
|
|
251
|
+
);
|
|
252
|
+
const or = where.filter((w) => w.connector === "OR");
|
|
253
|
+
|
|
254
|
+
// Separate AND conditions into simple eq (can extract) and complex (must stay in AND)
|
|
255
|
+
const andSimple = and.filter(
|
|
256
|
+
(w) => w.operator === "eq" || !w.operator,
|
|
257
|
+
);
|
|
258
|
+
const andComplex = and.filter(
|
|
259
|
+
(w) => w.operator !== "eq" && w.operator !== undefined,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const andSimpleClause = andSimple.map((w) => buildSingleCondition(w));
|
|
263
|
+
const andComplexClause = andComplex.map((w) =>
|
|
264
|
+
buildSingleCondition(w),
|
|
265
|
+
);
|
|
266
|
+
const orClause = or.map((w) => buildSingleCondition(w));
|
|
267
|
+
|
|
268
|
+
// Extract simple equality AND conditions to root level
|
|
269
|
+
const result: Record<string, any> = {};
|
|
270
|
+
for (const clause of andSimpleClause) {
|
|
271
|
+
Object.assign(result, clause);
|
|
272
|
+
}
|
|
273
|
+
// Keep complex AND conditions in AND array
|
|
274
|
+
if (andComplexClause.length > 0) {
|
|
275
|
+
result.AND = andComplexClause;
|
|
276
|
+
}
|
|
277
|
+
if (orClause.length > 0) {
|
|
278
|
+
result.OR = orClause;
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Special handling for delete actions: extract id to root level
|
|
284
|
+
if (action === "delete") {
|
|
285
|
+
const idCondition = where.find((w) => w.field === "id");
|
|
286
|
+
if (idCondition) {
|
|
287
|
+
const idFieldName = getFieldName({ model, field: "id" });
|
|
288
|
+
const idClause = buildSingleCondition(idCondition);
|
|
289
|
+
const remainingWhere = where.filter((w) => w.field !== "id");
|
|
290
|
+
|
|
291
|
+
if (remainingWhere.length === 0) {
|
|
292
|
+
return idClause;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const and = remainingWhere.filter(
|
|
296
|
+
(w) => w.connector === "AND" || !w.connector,
|
|
297
|
+
);
|
|
298
|
+
const or = remainingWhere.filter((w) => w.connector === "OR");
|
|
299
|
+
const andClause = and.map((w) => buildSingleCondition(w));
|
|
300
|
+
const orClause = or.map((w) => buildSingleCondition(w));
|
|
301
|
+
|
|
302
|
+
// Extract id to root level, put other conditions in AND array
|
|
303
|
+
const result: Record<string, any> = {};
|
|
304
|
+
if (idFieldName in idClause) {
|
|
305
|
+
result[idFieldName] = (idClause as Record<string, any>)[
|
|
306
|
+
idFieldName
|
|
307
|
+
];
|
|
308
|
+
} else {
|
|
309
|
+
// Handle edge case where idClause might have special structure
|
|
310
|
+
Object.assign(result, idClause);
|
|
311
|
+
}
|
|
312
|
+
if (andClause.length > 0) {
|
|
313
|
+
result.AND = andClause;
|
|
314
|
+
}
|
|
315
|
+
if (orClause.length > 0) {
|
|
316
|
+
result.OR = orClause;
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (where.length === 1) {
|
|
323
|
+
const w = where[0]!;
|
|
324
|
+
if (!w) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
return buildSingleCondition(w);
|
|
328
|
+
}
|
|
329
|
+
const and = where.filter((w) => w.connector === "AND" || !w.connector);
|
|
330
|
+
const or = where.filter((w) => w.connector === "OR");
|
|
331
|
+
const andClause = and.map((w) => buildSingleCondition(w));
|
|
332
|
+
const orClause = or.map((w) => buildSingleCondition(w));
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
...(andClause.length ? { AND: andClause } : {}),
|
|
336
|
+
...(orClause.length ? { OR: orClause } : {}),
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
async create({ model, data: values, select }) {
|
|
342
|
+
if (!db[model]) {
|
|
343
|
+
throw new BetterAuthError(
|
|
344
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const result = await db[model]!.create({
|
|
348
|
+
data: values,
|
|
349
|
+
select: convertSelect(select, model),
|
|
350
|
+
});
|
|
351
|
+
return result;
|
|
352
|
+
},
|
|
353
|
+
async findOne({ model, where, select, join }) {
|
|
354
|
+
// this is just "JoinOption" type because we disabled join transformation in adapter config
|
|
355
|
+
const whereClause = convertWhereClause({
|
|
356
|
+
model,
|
|
357
|
+
where,
|
|
358
|
+
action: "findOne",
|
|
359
|
+
});
|
|
360
|
+
if (!db[model]) {
|
|
361
|
+
throw new BetterAuthError(
|
|
362
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// transform join keys to use Prisma expected field names
|
|
367
|
+
const map = new Map<string, string>();
|
|
368
|
+
for (const joinModel of Object.keys(join ?? {})) {
|
|
369
|
+
const key = getJoinKeyName(model, joinModel, schema);
|
|
370
|
+
map.set(key, getModelName(joinModel));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const selects = convertSelect(select, model, join);
|
|
374
|
+
|
|
375
|
+
const result = await db[model]!.findFirst({
|
|
376
|
+
where: whereClause,
|
|
377
|
+
select: selects,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// transform the resulting `include` items to use better-auth expected field names
|
|
381
|
+
if (join && result) {
|
|
382
|
+
for (const [includeKey, originalKey] of map.entries()) {
|
|
383
|
+
if (includeKey === originalKey) continue;
|
|
384
|
+
if (includeKey in result) {
|
|
385
|
+
result[originalKey] = result[includeKey];
|
|
386
|
+
delete result[includeKey];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return result;
|
|
391
|
+
},
|
|
392
|
+
async findMany({ model, where, limit, offset, sortBy, join }) {
|
|
393
|
+
// this is just "JoinOption" type because we disabled join transformation in adapter config
|
|
394
|
+
const whereClause = convertWhereClause({
|
|
395
|
+
model,
|
|
396
|
+
where,
|
|
397
|
+
action: "findMany",
|
|
398
|
+
});
|
|
399
|
+
if (!db[model]) {
|
|
400
|
+
throw new BetterAuthError(
|
|
401
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
// transform join keys to use Prisma expected field names
|
|
405
|
+
const map = new Map<string, string>();
|
|
406
|
+
if (join) {
|
|
407
|
+
for (const [joinModel, _value] of Object.entries(join)) {
|
|
408
|
+
const key = getJoinKeyName(model, joinModel, schema);
|
|
409
|
+
map.set(key, getModelName(joinModel));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const selects = convertSelect(undefined, model, join);
|
|
414
|
+
|
|
415
|
+
const result = await db[model]!.findMany({
|
|
416
|
+
where: whereClause,
|
|
417
|
+
take: limit || 100,
|
|
418
|
+
skip: offset || 0,
|
|
419
|
+
...(sortBy?.field
|
|
420
|
+
? {
|
|
421
|
+
orderBy: {
|
|
422
|
+
[getFieldName({ model, field: sortBy.field })]:
|
|
423
|
+
sortBy.direction === "desc" ? "desc" : "asc",
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
: {}),
|
|
427
|
+
select: selects,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// transform the resulting join items to use better-auth expected field names
|
|
431
|
+
if (join && Array.isArray(result)) {
|
|
432
|
+
for (const item of result) {
|
|
433
|
+
for (const [includeKey, originalKey] of map.entries()) {
|
|
434
|
+
if (includeKey === originalKey) continue;
|
|
435
|
+
if (includeKey in item) {
|
|
436
|
+
item[originalKey] = item[includeKey];
|
|
437
|
+
delete item[includeKey];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return result;
|
|
444
|
+
},
|
|
445
|
+
async count({ model, where }) {
|
|
446
|
+
const whereClause = convertWhereClause({
|
|
447
|
+
model,
|
|
448
|
+
where,
|
|
449
|
+
action: "count",
|
|
450
|
+
});
|
|
451
|
+
if (!db[model]) {
|
|
452
|
+
throw new BetterAuthError(
|
|
453
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return await db[model]!.count({
|
|
457
|
+
where: whereClause,
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
async update({ model, where, update }) {
|
|
461
|
+
if (!db[model]) {
|
|
462
|
+
throw new BetterAuthError(
|
|
463
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
const whereClause = convertWhereClause({
|
|
467
|
+
model,
|
|
468
|
+
where,
|
|
469
|
+
action: "update",
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return await db[model]!.update({
|
|
473
|
+
where: whereClause,
|
|
474
|
+
data: update,
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
async updateMany({ model, where, update }) {
|
|
478
|
+
if (!db[model]) {
|
|
479
|
+
throw new BetterAuthError(
|
|
480
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const whereClause = convertWhereClause({
|
|
484
|
+
model,
|
|
485
|
+
where,
|
|
486
|
+
action: "updateMany",
|
|
487
|
+
});
|
|
488
|
+
const result = await db[model]!.updateMany({
|
|
489
|
+
where: whereClause,
|
|
490
|
+
data: update,
|
|
491
|
+
});
|
|
492
|
+
return result ? (result.count as number) : 0;
|
|
493
|
+
},
|
|
494
|
+
async delete({ model, where }) {
|
|
495
|
+
if (!db[model]) {
|
|
496
|
+
throw new BetterAuthError(
|
|
497
|
+
`Model ${model} does not exist in the database. If you haven't generated the Prisma client, you need to run 'npx prisma generate'`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
const whereClause = convertWhereClause({
|
|
501
|
+
model,
|
|
502
|
+
where,
|
|
503
|
+
action: "delete",
|
|
504
|
+
});
|
|
505
|
+
try {
|
|
506
|
+
await db[model]!.delete({
|
|
507
|
+
where: whereClause,
|
|
508
|
+
});
|
|
509
|
+
} catch (e: any) {
|
|
510
|
+
// If the record doesn't exist, we don't want to throw an error
|
|
511
|
+
if (e?.meta?.cause === "Record to delete does not exist.") return;
|
|
512
|
+
if (e?.code === "P2025") return; // Prisma 7+
|
|
513
|
+
// otherwise if it's an unknown error, we want to just log it for debugging.
|
|
514
|
+
console.log(e);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
async deleteMany({ model, where }) {
|
|
518
|
+
const whereClause = convertWhereClause({
|
|
519
|
+
model,
|
|
520
|
+
where,
|
|
521
|
+
action: "deleteMany",
|
|
522
|
+
});
|
|
523
|
+
const result = await db[model]!.deleteMany({
|
|
524
|
+
where: whereClause,
|
|
525
|
+
});
|
|
526
|
+
return result ? (result.count as number) : 0;
|
|
527
|
+
},
|
|
528
|
+
options: config,
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
let adapterOptions: AdapterFactoryOptions | null = null;
|
|
533
|
+
adapterOptions = {
|
|
534
|
+
config: {
|
|
535
|
+
adapterId: "prisma",
|
|
536
|
+
adapterName: "Prisma Adapter",
|
|
537
|
+
usePlural: config.usePlural ?? false,
|
|
538
|
+
debugLogs: config.debugLogs ?? false,
|
|
539
|
+
supportsUUIDs: config.provider === "postgresql" ? true : false,
|
|
540
|
+
supportsArrays:
|
|
541
|
+
config.provider === "postgresql" || config.provider === "mongodb"
|
|
542
|
+
? true
|
|
543
|
+
: false,
|
|
544
|
+
transaction:
|
|
545
|
+
(config.transaction ?? false)
|
|
546
|
+
? (cb) =>
|
|
547
|
+
(prisma as PrismaClientInternal).$transaction((tx) => {
|
|
548
|
+
const adapter = createAdapterFactory({
|
|
549
|
+
config: adapterOptions!.config,
|
|
550
|
+
adapter: createCustomAdapter(tx),
|
|
551
|
+
})(lazyOptions!);
|
|
552
|
+
return cb(adapter);
|
|
553
|
+
})
|
|
554
|
+
: false,
|
|
555
|
+
},
|
|
556
|
+
adapter: createCustomAdapter(prisma),
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const adapter = createAdapterFactory(adapterOptions);
|
|
560
|
+
return (options: BetterAuthOptions): DBAdapter<BetterAuthOptions> => {
|
|
561
|
+
lazyOptions = options;
|
|
562
|
+
return adapter(options);
|
|
563
|
+
};
|
|
564
|
+
};
|
package/tsconfig.json
ADDED
package/tsdown.config.ts
ADDED
package/vitest.config.ts
ADDED