@better-auth/prisma-adapter 1.5.0-beta.17 → 1.5.0-beta.19

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/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Better Auth Prisma Adapter
2
+
3
+ Prisma adapter for [Better Auth](https://www.better-auth.com).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @better-auth/prisma-adapter
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ For full documentation, visit [better-auth.com/docs/adapters/prisma](https://www.better-auth.com/docs/adapters/prisma).
14
+
15
+ ## License
16
+
17
+ MIT
package/package.json CHANGED
@@ -1,13 +1,28 @@
1
1
  {
2
2
  "name": "@better-auth/prisma-adapter",
3
- "version": "1.5.0-beta.17",
3
+ "version": "1.5.0-beta.19",
4
4
  "description": "Prisma adapter for Better Auth",
5
5
  "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://www.better-auth.com/docs/adapters/prisma",
6
8
  "repository": {
7
9
  "type": "git",
8
10
  "url": "git+https://github.com/better-auth/better-auth.git",
9
11
  "directory": "packages/prisma-adapter"
10
12
  },
13
+ "keywords": [
14
+ "auth",
15
+ "prisma",
16
+ "adapter",
17
+ "typescript",
18
+ "better-auth"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
11
26
  "main": "./dist/index.mjs",
12
27
  "module": "./dist/index.mjs",
13
28
  "types": "./dist/index.d.mts",
@@ -25,19 +40,21 @@
25
40
  "@better-auth/utils": "^0.3.0",
26
41
  "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0",
27
42
  "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0",
28
- "@better-auth/core": "1.5.0-beta.17"
43
+ "@better-auth/core": "1.5.0-beta.19"
29
44
  },
30
45
  "devDependencies": {
31
46
  "@better-auth/utils": "^0.3.1",
32
47
  "prisma": "^5.0.0",
33
48
  "tsdown": "^0.20.3",
34
49
  "typescript": "^5.9.3",
35
- "@better-auth/core": "1.5.0-beta.17"
50
+ "@better-auth/core": "1.5.0-beta.19"
36
51
  },
37
52
  "scripts": {
38
53
  "build": "tsdown",
39
54
  "dev": "tsdown --watch",
40
- "test": "vitest",
41
- "typecheck": "tsc --noEmit"
55
+ "lint:package": "publint run --strict",
56
+ "lint:types": "attw --profile esm-only --pack .",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest"
42
59
  }
43
60
  }
@@ -1,16 +0,0 @@
1
-
2
- > @better-auth/prisma-adapter@1.5.0-beta.17 build /home/runner/work/better-auth/better-auth/packages/prisma-adapter
3
- > tsdown
4
-
5
- ℹ tsdown v0.20.3 powered by rolldown v1.0.0-rc.3
6
- ℹ config file: /home/runner/work/better-auth/better-auth/packages/prisma-adapter/tsdown.config.ts
7
- ℹ entry: src/index.ts
8
- ℹ tsconfig: tsconfig.json
9
- ℹ Build start
10
- ℹ dist/index.mjs 11.33 kB │ gzip: 2.65 kB
11
- [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugin `rolldown-plugin-dts:generate`. See https://rolldown.rs/options/checks#plugintimings for more details.
12
- ℹ dist/index.mjs.map 27.23 kB │ gzip: 6.47 kB
13
-
14
- ℹ dist/index.d.mts  1.06 kB │ gzip: 0.51 kB
15
- ℹ 3 files, total: 39.63 kB
16
- ✔ Build complete in 8494ms
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./prisma-adapter";
@@ -1,14 +0,0 @@
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
- });
@@ -1,564 +0,0 @@
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, select, 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(select, 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 DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "include": ["./src"],
4
- "references": [
5
- {
6
- "path": "../core/tsconfig.json"
7
- }
8
- ]
9
- }
package/tsdown.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- dts: { build: true, incremental: true },
5
- format: ["esm"],
6
- entry: ["./src/index.ts"],
7
- sourcemap: true,
8
- });
package/vitest.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineProject } from "vitest/config";
2
-
3
- export default defineProject({
4
- test: {
5
- clearMocks: true,
6
- restoreMocks: true,
7
- },
8
- });