@ryangarber/better-auth-adapter-prisma 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan Garber
4
+ Copyright (c) 2024 - present, Bereket Engida (original Prisma adapter)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,4 @@
1
+ Adapter behavior is based on Better Auth's MIT-licensed Prisma adapter:
2
+ https://github.com/better-auth/better-auth/tree/main/packages/prisma-adapter
3
+ Copyright (c) 2024 - present, Bereket Engida. See its MIT license:
4
+ https://github.com/better-auth/better-auth/blob/main/LICENSE.md
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # Better Auth adapter for Prisma 8
2
+
3
+ A PostgreSQL adapter for **Prisma 8.0.0-rc.8** and **Better Auth 1.7.4**, based on the behavior of Better Auth's Prisma 7 adapter. Uses Prisma 8's contract-driven ORM, including extension codecs, instead of the legacy Prisma Client API.
4
+
5
+ Supports CRUD, filtering, selections, pagination, ordering, transactions, joins, numeric/UUID identifiers, and additional fields. PostgreSQL is the supported target; this package does not implement the separate MongoDB API or claim SQLite/MySQL support.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add @ryangarber/better-auth-adapter-prisma better-auth@1.7.4 @better-auth/core@1.7.4 @prisma/orm-postgres@8.0.0-rc.8 temporal-polyfill
11
+ ```
12
+
13
+ Prisma RC versions are pinned because their query and generated-type APIs change between releases. The adapter does not create a database connection or close your client; pass your application's existing client with its runtime extensions registered.
14
+
15
+ ```ts
16
+ // auth.ts
17
+ import "temporal-polyfill/global"; // Required by Prisma DateTime codecs on runtimes without Temporal.
18
+ import { betterAuth } from "better-auth";
19
+ import { prismaAdapter } from "@ryangarber/better-auth-adapter-prisma";
20
+ import { db } from "./prisma/db";
21
+
22
+ export const auth = betterAuth({
23
+ database: prismaAdapter(db),
24
+ emailAndPassword: { enabled: true },
25
+ });
26
+ ```
27
+
28
+ Both `prismaAdapter` and `prisma8Adapter` name the same factory. Author and emit your `contract.prisma`, then provision it using Prisma's normal database/migration workflow. This package does not implement Better Auth CLI schema generation. A complete test contract containing the four authentication models is in [test/fixtures/contract.prisma](test/fixtures/contract.prisma); remove its example additional fields if you do not need them.
29
+
30
+ ## Additional fields and extension codecs
31
+
32
+ Declare additional fields in both the Prisma contract and Better Auth. The adapter passes their values through Prisma's ORM so the configured codec handles encoding and decoding. It does not JSON-stringify, clone nested values, or deserialize extension fields itself. Nullable fields, arrays, enums, branded values, and structured JSON retain their Prisma codec behavior.
33
+
34
+ For example, using [the Zod extension](https://github.com/ryangarber/prisma-orm-extension-zod):
35
+
36
+ ```sh
37
+ pnpm add @ryangarber/prisma-orm-extension-zod@0.1.2 zod
38
+ pnpm add -D @prisma/orm-toolchain@8.0.0-rc.8
39
+ ```
40
+
41
+ ```ts
42
+ // prisma/schemas.ts — colocated with the emitted contract.d.ts
43
+ import { z } from "zod";
44
+ import type { CodecTypes } from "@ryangarber/prisma-orm-extension-zod/codec-types";
45
+ import { createZodExtension, defineZodSchema } from "@ryangarber/prisma-orm-extension-zod/column-types";
46
+
47
+ export const Profile = z.object({
48
+ name: z.string(),
49
+ age: z.string().transform(Number),
50
+ });
51
+ const schemas = { Profile: defineZodSchema(Profile) };
52
+ export type SchemaTypes = CodecTypes<typeof schemas>;
53
+ export const profileExtension: ReturnType<typeof createZodExtension> = createZodExtension(schemas, {
54
+ module: "./schemas",
55
+ export: "SchemaTypes",
56
+ });
57
+ ```
58
+
59
+ ```ts
60
+ // prisma.config.ts
61
+ import { defineConfig } from "@prisma/orm-postgres/config";
62
+ import { profileExtension } from "./prisma/schemas";
63
+
64
+ export default defineConfig({
65
+ contract: "./prisma/contract.prisma",
66
+ extensions: [profileExtension.control],
67
+ });
68
+ ```
69
+
70
+ Add this field to your `User` model:
71
+
72
+ ```prisma
73
+ profile zod.Json("Profile")
74
+ ```
75
+
76
+ Emit the contract with `pnpm exec prisma contract emit`. Register the matching runtime extension:
77
+
78
+ ```ts
79
+ // prisma/db.ts
80
+ import "temporal-polyfill/global";
81
+ import postgres from "@prisma/orm-postgres/runtime";
82
+ import type { Contract } from "./contract";
83
+ import contractJson from "./contract.json" with { type: "json" };
84
+ import { profileExtension } from "./schemas";
85
+
86
+ export const db = postgres<Contract>({
87
+ contractJson,
88
+ url: process.env.DATABASE_URL,
89
+ extensions: [profileExtension.runtime],
90
+ });
91
+ ```
92
+
93
+ ### Infer custom user types
94
+
95
+ Better Auth 1.7.4 infers `type: "json"` as a generic record and does not derive additional-field types from validators or database adapters. Use `prismaUserFields` to bridge the emitted Prisma types into your direct server API:
96
+
97
+ ```ts
98
+ import { betterAuth } from "better-auth";
99
+ import { prismaAdapter, prismaUserFields } from "@ryangarber/better-auth-adapter-prisma";
100
+ import { db } from "./prisma/db";
101
+
102
+ const userFields = prismaUserFields(db.orm.public.User)({
103
+ profile: { type: "json", required: true },
104
+ });
105
+
106
+ export const auth = userFields.inferAuth(betterAuth({
107
+ database: prismaAdapter(db),
108
+ emailAndPassword: { enabled: true },
109
+ user: { additionalFields: userFields.additionalFields },
110
+ }));
111
+
112
+ const result = await auth.api.signUpEmail({
113
+ body: {
114
+ email: "ada@example.com",
115
+ name: "Ada",
116
+ password: "a sufficiently long password",
117
+ profile: { name: "Ada", age: "36" }, // Write input: string.
118
+ },
119
+ });
120
+ result.user.profile.age; // Read output: number.
121
+
122
+ type User = typeof auth.$Infer.Session.user;
123
+ // User["profile"] is { name: string; age: number }.
124
+ ```
125
+
126
+ Use the actual namespace and an unprojected collection. The helper validates field names at compile time, including a field's optional `fieldName` mapping. It reads the contract's **input** type map separately from the collection's output type: Prisma RC.8's create signatures alone are insufficient for codecs with different input/output types.
127
+
128
+ The helper preserves `required`, `input: false`, `returned: false`, and defaulted input optionality. It types `signUpEmail` and `updateUser` bodies, full user objects returned by server endpoints, and `auth.$Infer.Session.user`. Standard response/header/status options remain available. It returns the same auth object at runtime, and checks that its `additionalFields` object was installed on that instance.
129
+
130
+ Declare custom structured values as `type: "json"`. Use `type: "date"` for ordinary JavaScript `Date` fields. Keep any Better Auth `transform` or transforming `validator.input` consistent with the codec's input/output: those run independently of Prisma, and the helper does not infer their effects. In particular, passing the transforming `Profile` schema above as a Better Auth input validator would convert `age` before Prisma receives it; let the Prisma codec validate it instead.
131
+
132
+ **Scope of inference:** this helper supplies a server API type view. It does not change Better Auth's hook/context types, plugin-defined input bodies, or the built-in `inferAdditionalFields` client plugin. Standard JSON HTTP responses also have different semantics from decoded database values: JSON turns dates into strings, cannot serialize `bigint`, and does not preserve `Map`/`Set` instances. Use JSON-compatible public fields or an explicit serializer/client type layer for those values; the adapter and helper do not install an HTTP serializer. Cookie caching and other serialized stores have the same transport considerations.
133
+
134
+ ## Names and defaults
135
+
136
+ | Setting | Default | Behavior |
137
+ | --- | --- | --- |
138
+ | `provider` | `"postgresql"` | PostgreSQL contract required. |
139
+ | `usePlural` | `false` | Same as the original adapter. When enabled, Better Auth appends `s`, including to custom `modelName` values. |
140
+ | Model lookup | Exact, then lower-first alias | `user` resolves to contract model `user` or `User`, matching Prisma 7 delegate naming. Exact matches take precedence. No general case folding or English pluralization. |
141
+ | `namespace` | Unspecified | Search contract namespaces; reject ambiguous model names. |
142
+ | `models` | Unspecified | Map a resolved Better Auth name to an exact contract model name or `{ namespace, model }`. |
143
+ | `transaction` | `false` | Same as the original adapter. Enable to bind Better Auth transaction callbacks to Prisma transactions. |
144
+ | `debugLogs` | `false` | Better Auth adapter logging. |
145
+
146
+ `user.modelName`, `session.modelName`, plugin model names, built-in `fields` mappings, and additional-field `fieldName` mappings remain Better Auth options. They refer to **Prisma model and model-field names**. Prisma itself resolves storage names from `@@map` and `@map`; SQL table capitalization is not guessed by the adapter.
147
+
148
+ ```ts
149
+ betterAuth({
150
+ database: prismaAdapter(db, {
151
+ namespace: "auth",
152
+ usePlural: true,
153
+ transaction: true,
154
+ models: {
155
+ persons: { namespace: "auth", model: "AuthPerson" },
156
+ sessions: "LoginSession",
157
+ },
158
+ }),
159
+ user: {
160
+ modelName: "person", // becomes "persons" with usePlural
161
+ fields: { name: "displayName" },
162
+ additionalFields: {
163
+ profile: { type: "json", fieldName: "profileData" },
164
+ },
165
+ },
166
+ });
167
+ ```
168
+
169
+ Explicit `models` mappings are exact: a misspelling fails rather than silently falling back. The same resolution applies inside transactions and joins. Joins use Better Auth's foreign-key metadata and separate ORM queries, so they do not require guessed Prisma relation-property names. They are not single-statement SQL joins; use a suitable transaction/isolation strategy if your application needs a consistent snapshot across those reads.
170
+
171
+ ## Query behavior
172
+
173
+ - Equality, inequality, comparisons, `in`/`not_in`, and string contains/prefix/suffix filters are supported. Case-insensitive string filters use database `lower(...)`; LIKE wildcard characters are escaped as literals.
174
+ - AND conditions are combined with the OR group, matching the original adapter. Null list members are removed; an empty `in` matches nothing and an empty `not_in` matches everything.
175
+ - Operations use Prisma's codec-aware predicates. A codec must advertise the comparison/order traits needed by a query. Codec, database, and constraint errors propagate.
176
+ - Single-row updates/deletes affect at most one match. Missing updates return `null`; missing deletes are no-ops. Better Auth guards empty single-row mutation predicates.
177
+ - Bulk updates/deletes use affected-row counts. Better Auth's built-in compare-and-swap fallbacks implement guarded increments and single-use consumption using these atomic operations. Prisma's single-row read-then-write methods are not advertised as atomic guarded operations.
178
+ - Built-in PostgreSQL `DateTime`/Temporal and date-string codecs are adapted on Better Auth `date` fields to/from JavaScript `Date`, including filters. Timestamp-without-time-zone values are treated as UTC. Custom extension codecs are not converted.
179
+
180
+ ## Development and verification
181
+
182
+ ```sh
183
+ pnpm install
184
+ pnpm typecheck
185
+ pnpm typecheck:contract # Generated Prisma declarations, with skipLibCheck disabled.
186
+ pnpm lint
187
+ pnpm build
188
+ pnpm test
189
+
190
+ # Explicit opt-in; creates a unique ba_test_* schema and drops it in finally/afterAll.
191
+ ADAPTER_TEST_DATABASE_URL=postgresql://localhost:5432/postgres pnpm test
192
+ ```
193
+
194
+ Live tests require PostgreSQL and `psql`. They emit the fixture contract, provision isolated tables, exercise real codecs and Better Auth, and clean up their schema. They cover storage names, mappings, rich values, different input/output types, date conversion, filtering, pagination, joins, rollback, concurrent consumption/increments, and signup/session/update. They do not test Prisma migrations or production contract signing; the isolated fixture runtime disables marker verification.
195
+
196
+ The ordinary project check retains the scaffold's `skipLibCheck` setting because Better Auth's optional platform declarations have unrelated compatibility issues. The separate contract check validates all generated Prisma field maps and their dependencies without that setting, and the type tests assert concrete input/output types and rejected invalid inputs.
197
+
198
+ Regenerate the committed type fixture with `node test/fixtures/emit.ts`. Generated files should not be edited manually.
@@ -0,0 +1,133 @@
1
+ import { DBAdapter, DBAdapterDebugLogOption, Where } from "@better-auth/core/db/adapter";
2
+ import { Collection } from "@prisma/orm-postgres/orm-client";
3
+ import { AnyExpression } from "@prisma/orm-postgres/relational-core/ast";
4
+ import { BetterAuthOptions } from "@better-auth/core";
5
+ import { Contract } from "@prisma/orm-postgres/contract/types";
6
+ import { ExtractFieldInputTypes, SqlStorage } from "@prisma/orm-postgres/family-contract/types";
7
+ import { PostgresClient } from "@prisma/orm-postgres/runtime";
8
+ import { DBFieldAttribute } from "@better-auth/core/db";
9
+ //#region src/user-fields.d.ts
10
+ type CollectionShape = {
11
+ first: (...args: never[]) => Promise<unknown>;
12
+ create: (...args: never[]) => Promise<unknown>;
13
+ };
14
+ type Definitions = Record<string, DBFieldAttribute>;
15
+ type Output<C extends CollectionShape> = NonNullable<Awaited<ReturnType<C["first"]>>>;
16
+ type Input<C extends CollectionShape> = C extends Collection<infer CT, infer M, infer _Row, infer State> ? State["nsId"] extends keyof ExtractFieldInputTypes<CT> ? M extends keyof ExtractFieldInputTypes<CT>[State["nsId"]] ? ExtractFieldInputTypes<CT>[State["nsId"]][M] : never : never : never;
17
+ type StorageKey<D, K> = D extends {
18
+ fieldName: infer F extends string;
19
+ } ? F : K;
20
+ type StoredField<Shape, D, K> = StorageKey<D, K> extends keyof Shape ? Shape[StorageKey<D, K>] : never;
21
+ type FieldCodec<C extends CollectionShape, D, K> = C extends Collection<infer CT, infer M, infer _Row, infer State> ? CT["domain"]["namespaces"][State["nsId"]]["models"][M] extends {
22
+ fields: infer Fields;
23
+ } ? StorageKey<D, K> extends keyof Fields ? Fields[StorageKey<D, K>] extends {
24
+ type: {
25
+ codecId: infer Codec;
26
+ };
27
+ } ? Codec : never : never : never : never;
28
+ type BuiltinDateCodec = `pg/${"timestamptz" | "timestamp" | "date"}-${"temporal" | "string"}@1`;
29
+ type FieldValue<Shape, C extends CollectionShape, D, K> = D extends {
30
+ type: "date";
31
+ } ? [FieldCodec<C, D, K>] extends [never] ? StoredField<Shape, D, K> : FieldCodec<C, D, K> extends BuiltinDateCodec ? Date | Extract<StoredField<Shape, D, K>, null | undefined> : StoredField<Shape, D, K> : StoredField<Shape, D, K>;
32
+ type RequiredInput<D> = D extends {
33
+ required: false;
34
+ } | {
35
+ defaultValue: unknown;
36
+ } ? false : true;
37
+ type UserInput<C extends CollectionShape, D extends Definitions> = { -readonly [K in keyof D as D[K] extends {
38
+ input: false;
39
+ } ? never : RequiredInput<D[K]> extends true ? K : never]: FieldValue<Input<C>, C, D[K], K>; } & { -readonly [K in keyof D as D[K] extends {
40
+ input: false;
41
+ } ? never : RequiredInput<D[K]> extends false ? K : never]?: FieldValue<Input<C>, C, D[K], K>; };
42
+ type UserOutput<C extends CollectionShape, D extends Definitions> = { -readonly [K in keyof D as D[K] extends {
43
+ returned: false;
44
+ } ? never : D[K] extends {
45
+ required: false;
46
+ } ? never : K]: FieldValue<Output<C>, C, D[K], K>; } & { -readonly [K in keyof D as D[K] extends {
47
+ returned: false;
48
+ } ? never : D[K] extends {
49
+ required: false;
50
+ } ? K : never]?: FieldValue<Output<C>, C, D[K], K> | null; };
51
+ type ReplaceUser<T, C extends CollectionShape, D extends Definitions> = T extends {
52
+ id: string;
53
+ email: string;
54
+ emailVerified: boolean;
55
+ name: string;
56
+ } ? Omit<T, keyof D> & UserOutput<C, D> : T extends Date | Response | Headers | Map<unknown, unknown> | Set<unknown> ? T : T extends readonly unknown[] ? { [K in keyof T]: ReplaceUser<T[K], C, D>; } : T extends object ? { [K in keyof T]: ReplaceUser<T[K], C, D>; } : T;
57
+ type Endpoint = (...args: never[]) => unknown;
58
+ type Context<E extends Endpoint> = NonNullable<Parameters<E>[0]>;
59
+ type BodyContext<E extends Endpoint, K, C extends CollectionShape, D extends Definitions> = K extends "signUpEmail" | "updateUser" ? Omit<Context<E>, "body"> & {
60
+ body: Omit<Context<E> extends {
61
+ body?: infer B;
62
+ } ? B : object, keyof D> & (K extends "updateUser" ? Partial<UserInput<C, D>> : UserInput<C, D>);
63
+ } : Context<E>;
64
+ type Flags = "asResponse" | "returnHeaders" | "returnStatus";
65
+ type EndpointResult<T, R extends boolean, H extends boolean, S extends boolean> = R extends true ? Response : H extends true ? S extends true ? {
66
+ headers: Headers;
67
+ status: number;
68
+ response: T;
69
+ } : {
70
+ headers: Headers;
71
+ response: T;
72
+ } : S extends true ? {
73
+ status: number;
74
+ response: T;
75
+ } : T;
76
+ type Call<E extends Endpoint, B, T> = undefined extends Parameters<E>[0] ? <R extends boolean = false, H extends boolean = false, S extends boolean = false>(context?: Omit<B, Flags> & {
77
+ asResponse?: R | undefined;
78
+ returnHeaders?: H | undefined;
79
+ returnStatus?: ("returnStatus" extends keyof B ? S : never) | undefined;
80
+ }) => Promise<EndpointResult<T, R, H, S>> : <R extends boolean = false, H extends boolean = false, S extends boolean = false>(context: Omit<B, Flags> & {
81
+ asResponse?: R | undefined;
82
+ returnHeaders?: H | undefined;
83
+ returnStatus?: ("returnStatus" extends keyof B ? S : never) | undefined;
84
+ }) => Promise<EndpointResult<T, R, H, S>>;
85
+ type AuthShape = {
86
+ api: object;
87
+ $Infer: {
88
+ Session: unknown;
89
+ };
90
+ options: {
91
+ user?: {
92
+ additionalFields?: Definitions;
93
+ };
94
+ };
95
+ };
96
+ /** A server-side type view. Runtime calls and Better Auth's HTTP handler are unchanged. */
97
+ type TypedPrismaAuth<A extends AuthShape, C extends CollectionShape, D extends Definitions> = Omit<A, "api" | "$Infer"> & {
98
+ $Infer: ReplaceUser<A["$Infer"], C, D>;
99
+ api: { [K in keyof A["api"]]: A["api"][K] extends Endpoint ? Pick<A["api"][K], keyof A["api"][K]> & Call<A["api"][K], BodyContext<A["api"][K], K, C, D>, K extends "getSession" ? ReplaceUser<A["$Infer"]["Session"], C, D> | null : ReplaceUser<Awaited<ReturnType<A["api"][K]>>, C, D>> : A["api"][K]; };
100
+ };
101
+ interface PrismaUserFields<C extends CollectionShape, D extends Definitions> {
102
+ additionalFields: D;
103
+ /** Apply after betterAuth({ user: { additionalFields } }). For direct server calls, not HTTP deserialization. */
104
+ inferAuth<A extends AuthShape>(auth: A): TypedPrismaAuth<A, C, D>;
105
+ }
106
+ /** Derive field input/output types from an unprojected Prisma User collection, including extension codecs. */
107
+ export declare function prismaUserFields<C extends CollectionShape>(_collection: C): <const D extends Definitions>(definitions: D & { [K in keyof D]: StorageKey<D[K], K> extends keyof Output<C> & keyof Input<C> ? D[K] : never; }) => PrismaUserFields<C, D>;
108
+ //#endregion
109
+ //#region src/index.d.ts
110
+ export interface Prisma8Config {
111
+ /** PostgreSQL is the supported Prisma 8 target. */
112
+ provider?: "postgresql";
113
+ /** Same default as the original Prisma adapter: false. */
114
+ usePlural?: boolean;
115
+ /** Same default as the original Prisma adapter: false. */
116
+ transaction?: boolean;
117
+ debugLogs?: DBAdapterDebugLogOption;
118
+ /** Restrict model lookup to this contract namespace. Otherwise names must be unambiguous. */
119
+ namespace?: string;
120
+ /** Keys are Better Auth model names after modelName/usePlural; values are exact contract coordinates. */
121
+ models?: Record<string, string | {
122
+ namespace: string;
123
+ model: string;
124
+ }>;
125
+ }
126
+ type Field = Record<string, ((value?: unknown) => AnyExpression) | undefined>;
127
+ type Fields$1 = Record<string, Field | undefined>;
128
+ /** Convert Better Auth's AND-group AND (OR-group) semantics, as in the Prisma 7 adapter. */
129
+ export declare function prismaWhere(fields: Fields$1, where?: readonly Where[]): AnyExpression;
130
+ export declare function prismaAdapter<C extends Contract<SqlStorage>>(db: Pick<PostgresClient<C>, "orm" | "contract" | "transaction">, config?: Prisma8Config): (options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>;
131
+ //#endregion
132
+ export { type PrismaUserFields, type TypedPrismaAuth, prismaAdapter as prisma8Adapter };
133
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/user-fields.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,208 @@
1
+ import { createAdapterFactory } from "@better-auth/core/db/adapter";
2
+ import { BetterAuthError } from "@better-auth/core/error";
3
+ import { and, or } from "@prisma/orm-postgres/orm-client";
4
+ import { BinaryExpr, FunctionCallExpr } from "@prisma/orm-postgres/relational-core/ast";
5
+ import { Temporal } from "temporal-polyfill";
6
+ //#region src/dates.ts
7
+ /** Only adapt built-in PostgreSQL temporal codecs on Better Auth `date` fields. */
8
+ function dateInput(codec, value) {
9
+ if (Array.isArray(value)) return value.map((item) => dateInput(codec, item));
10
+ if (!(value instanceof Date)) return value;
11
+ switch (codec) {
12
+ case "pg/timestamptz-temporal@1": return Temporal.Instant.fromEpochMilliseconds(value.getTime());
13
+ case "pg/timestamp-temporal@1": return Temporal.Instant.fromEpochMilliseconds(value.getTime()).toZonedDateTimeISO("UTC").toPlainDateTime();
14
+ case "pg/date-temporal@1": return Temporal.Instant.fromEpochMilliseconds(value.getTime()).toZonedDateTimeISO("UTC").toPlainDate();
15
+ case "pg/timestamptz-string@1": return value.toISOString();
16
+ case "pg/timestamp-string@1": return value.toISOString().slice(0, -1);
17
+ case "pg/date-string@1": return value.toISOString().slice(0, 10);
18
+ default: return value;
19
+ }
20
+ }
21
+ function dateOutput(codec, value) {
22
+ if (value == null) return value;
23
+ switch (codec) {
24
+ case "pg/timestamptz-temporal@1":
25
+ case "pg/timestamptz-string@1": return new Date(String(value));
26
+ case "pg/timestamp-temporal@1":
27
+ case "pg/timestamp-string@1": return /* @__PURE__ */ new Date(`${String(value).replace(" ", "T")}Z`);
28
+ case "pg/date-temporal@1":
29
+ case "pg/date-string@1": return /* @__PURE__ */ new Date(`${String(value)}T00:00:00Z`);
30
+ default: return value;
31
+ }
32
+ }
33
+ //#endregion
34
+ //#region src/user-fields.ts
35
+ /** Derive field input/output types from an unprojected Prisma User collection, including extension codecs. */
36
+ function prismaUserFields(_collection) {
37
+ return (definitions) => ({
38
+ additionalFields: definitions,
39
+ inferAuth(auth) {
40
+ if (auth.options.user?.additionalFields !== definitions) throw new Error("Pass this helper's additionalFields to betterAuth({ user: { additionalFields } }) before inferAuth().");
41
+ return auth;
42
+ }
43
+ });
44
+ }
45
+ //#endregion
46
+ //#region src/index.ts
47
+ function call(field, method, value) {
48
+ const fn = field?.[method];
49
+ if (!fn) throw new BetterAuthError(`Prisma 8 field does not support ${method}; check the field name and codec traits.`);
50
+ return fn(value);
51
+ }
52
+ const escapeLike = (value) => value.replace(/[\\%_]/g, "\\$&");
53
+ /** Convert Better Auth's AND-group AND (OR-group) semantics, as in the Prisma 7 adapter. */
54
+ function prismaWhere(fields, where = []) {
55
+ function condition(w) {
56
+ const field = fields[w.field];
57
+ const op = w.operator ?? "eq";
58
+ if (w.value === null && (op === "eq" || op === "ne")) return call(field, op === "eq" ? "isNull" : "isNotNull");
59
+ const insensitive = w.mode === "insensitive";
60
+ if (op === "in" || op === "not_in") {
61
+ if (!Array.isArray(w.value)) throw new BetterAuthError(`${op} requires an array`);
62
+ const values = w.value.filter((value) => value != null);
63
+ if (!values.length) return op === "in" ? or() : and();
64
+ if (insensitive && values.every((value) => typeof value === "string")) {
65
+ const expr = or(...values.map((value) => condition({
66
+ ...w,
67
+ operator: "eq",
68
+ value
69
+ })));
70
+ return op === "not_in" ? expr.not() : expr;
71
+ }
72
+ return call(field, op === "in" ? "in" : "notIn", values);
73
+ }
74
+ const method = op === "ne" ? "neq" : op;
75
+ let expr;
76
+ if (op === "contains" || op === "starts_with" || op === "ends_with") {
77
+ if (typeof w.value !== "string") throw new BetterAuthError(`${op} requires a string`);
78
+ expr = call(field, "like", `${op === "starts_with" ? "" : "%"}${escapeLike(w.value)}${op === "ends_with" ? "" : "%"}`);
79
+ } else expr = call(field, method, w.value);
80
+ if (insensitive && typeof w.value === "string") {
81
+ if (!(expr instanceof BinaryExpr)) throw new BetterAuthError("Expected a Prisma binary predicate");
82
+ expr = new BinaryExpr(expr.op, FunctionCallExpr.of("lower", [expr.left]), FunctionCallExpr.of("lower", [expr.right]));
83
+ }
84
+ return expr;
85
+ }
86
+ const conjunction = where.filter((w) => w.connector !== "OR").map(condition);
87
+ const disjunction = where.filter((w) => w.connector === "OR").map(condition);
88
+ return and(...conjunction, ...disjunction.length ? [or(...disjunction)] : []);
89
+ }
90
+ function prismaAdapter(db, config = {}) {
91
+ if (db.contract.target !== "postgres") throw new BetterAuthError("This adapter requires a Prisma 8 PostgreSQL contract");
92
+ const resolve = (name) => {
93
+ const mapped = config.models?.[name];
94
+ const model = typeof mapped === "string" ? mapped : mapped?.model ?? name;
95
+ const namespace = typeof mapped === "object" ? mapped.namespace : config.namespace;
96
+ const candidates = Object.entries(db.contract.domain.namespaces).flatMap(([ns, domain]) => namespace !== void 0 && ns !== namespace ? [] : Object.keys(domain.models ?? {}).map((key) => ({
97
+ namespace: ns,
98
+ model: key
99
+ })));
100
+ const exact = candidates.filter((c) => c.model === model);
101
+ const matches = exact.length ? exact : mapped === void 0 ? candidates.filter((c) => c.model[0]?.toLowerCase() + c.model.slice(1) === model) : [];
102
+ if (matches.length !== 1) throw new BetterAuthError(`Prisma 8 model '${name}' ${matches.length ? "is ambiguous" : "was not found"}. Configure namespace/models using contract model names (not storage table names).`);
103
+ const match = matches[0];
104
+ if (!match) throw new BetterAuthError("Prisma model resolution failed");
105
+ return match;
106
+ };
107
+ return (options) => {
108
+ const build = (orm, inTransaction) => {
109
+ const creator = ({ getFieldName, schema, getDefaultModelName }) => {
110
+ const dateCodec = (model, field) => {
111
+ if (!Object.entries(schema[getDefaultModelName(model)]?.fields ?? {}).some(([key, attr]) => (attr.fieldName ?? key) === field && attr.type === "date")) return void 0;
112
+ const coordinate = resolve(model);
113
+ const type = db.contract.domain.namespaces[coordinate.namespace]?.models[coordinate.model]?.fields?.[field]?.type;
114
+ return type?.kind === "scalar" ? type.codecId : void 0;
115
+ };
116
+ const inputData = (model, data) => Object.fromEntries(Object.entries(data).map(([field, value]) => [field, dateInput(dateCodec(model, field), value)]));
117
+ const outputRow = (model, row) => Object.fromEntries(Object.entries(row).map(([field, value]) => [field, dateOutput(dateCodec(model, field), value)]));
118
+ const query = (model, where) => {
119
+ const coordinate = resolve(model);
120
+ const collection = orm[coordinate.namespace]?.[coordinate.model];
121
+ if (!collection) throw new BetterAuthError(`Prisma 8 collection ${coordinate.namespace}.${coordinate.model} is unavailable`);
122
+ return where === void 0 ? collection : collection.where((fields) => prismaWhere(fields, where.map((w) => ({
123
+ ...w,
124
+ value: dateInput(dateCodec(model, w.field), w.value)
125
+ }))));
126
+ };
127
+ const selectQuery = (q, model, select) => select?.length ? q.select(...select.map((field) => getFieldName({
128
+ model,
129
+ field
130
+ }))) : q;
131
+ const joinRows = async (rows, model, join) => {
132
+ rows = rows.map((row) => outputRow(model, row));
133
+ if (!join) return rows;
134
+ return Promise.all(rows.map(async (row) => {
135
+ const result = { ...row };
136
+ for (const [joinedModel, attr] of Object.entries(join)) {
137
+ const value = row[getFieldName({
138
+ model,
139
+ field: attr.on.from
140
+ })];
141
+ let joined = [];
142
+ if (value != null) joined = await query(joinedModel, [{
143
+ field: getFieldName({
144
+ model: joinedModel,
145
+ field: attr.on.to
146
+ }),
147
+ value
148
+ }]).limit(attr.relation === "one-to-one" ? 1 : attr.limit ?? 100).all();
149
+ joined = joined.map((row) => outputRow(joinedModel, row));
150
+ result[joinedModel] = attr.relation === "one-to-one" ? joined[0] ?? null : joined;
151
+ }
152
+ return result;
153
+ }));
154
+ };
155
+ return {
156
+ async create({ model, data, select }) {
157
+ return outputRow(model, await selectQuery(query(model), model, select).create(inputData(model, data)));
158
+ },
159
+ async findOne({ model, where, select, join }) {
160
+ const row = await selectQuery(query(model, where), model, select).first();
161
+ return row ? (await joinRows([row], model, join))[0] : null;
162
+ },
163
+ async findMany({ model, where, select, limit, offset, sortBy, join }) {
164
+ let q = selectQuery(query(model, where), model, select).limit(limit);
165
+ if (offset !== void 0) q = q.offset(offset);
166
+ if (sortBy) q = q.orderBy((fields) => call(fields[getFieldName({
167
+ model,
168
+ field: sortBy.field
169
+ })], sortBy.direction));
170
+ return await joinRows(await q.all(), model, join);
171
+ },
172
+ count: async ({ model, where }) => (await query(model, where).aggregate((a) => ({ total: a.count() }))).total,
173
+ async update({ model, where, update }) {
174
+ const row = await query(model, where).update(inputData(model, update));
175
+ return row ? outputRow(model, row) : null;
176
+ },
177
+ updateMany: ({ model, where, update }) => query(model, where).updateAndCount(inputData(model, update)),
178
+ async delete({ model, where }) {
179
+ await query(model, where).delete();
180
+ },
181
+ deleteMany: ({ model, where }) => query(model, where).deleteAndCount(),
182
+ options: config
183
+ };
184
+ };
185
+ return createAdapterFactory({
186
+ config: {
187
+ adapterId: "prisma-8",
188
+ adapterName: "Prisma 8 Adapter",
189
+ usePlural: config.usePlural ?? false,
190
+ debugLogs: config.debugLogs ?? false,
191
+ supportsJSON: true,
192
+ supportsArrays: true,
193
+ supportsDates: true,
194
+ supportsBooleans: true,
195
+ supportsUUIDs: true,
196
+ supportsNumericIds: true,
197
+ transaction: config.transaction && !inTransaction ? (callback) => db.transaction((tx) => callback(build(tx.orm, true))) : false
198
+ },
199
+ adapter: creator
200
+ })(options);
201
+ };
202
+ return build(db.orm, false);
203
+ };
204
+ }
205
+ //#endregion
206
+ export { prismaAdapter as prisma8Adapter, prismaAdapter, prismaUserFields, prismaWhere };
207
+
208
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["surface"],"sources":["../src/dates.ts","../src/user-fields.ts","../src/index.ts"],"sourcesContent":["import { Temporal } from \"temporal-polyfill\";\n\n/** Only adapt built-in PostgreSQL temporal codecs on Better Auth `date` fields. */\nexport function dateInput(codec: string | undefined, value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map((item) => dateInput(codec, item));\n\tif (!(value instanceof Date)) return value;\n\tswitch (codec) {\n\t\tcase \"pg/timestamptz-temporal@1\":\n\t\t\treturn Temporal.Instant.fromEpochMilliseconds(value.getTime());\n\t\tcase \"pg/timestamp-temporal@1\":\n\t\t\treturn Temporal.Instant.fromEpochMilliseconds(value.getTime())\n\t\t\t\t.toZonedDateTimeISO(\"UTC\")\n\t\t\t\t.toPlainDateTime();\n\t\tcase \"pg/date-temporal@1\":\n\t\t\treturn Temporal.Instant.fromEpochMilliseconds(value.getTime())\n\t\t\t\t.toZonedDateTimeISO(\"UTC\")\n\t\t\t\t.toPlainDate();\n\t\tcase \"pg/timestamptz-string@1\":\n\t\t\treturn value.toISOString();\n\t\tcase \"pg/timestamp-string@1\":\n\t\t\treturn value.toISOString().slice(0, -1);\n\t\tcase \"pg/date-string@1\":\n\t\t\treturn value.toISOString().slice(0, 10);\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n\nexport function dateOutput(codec: string | undefined, value: unknown): unknown {\n\tif (value == null) return value;\n\tswitch (codec) {\n\t\tcase \"pg/timestamptz-temporal@1\":\n\t\tcase \"pg/timestamptz-string@1\":\n\t\t\treturn new Date(String(value));\n\t\tcase \"pg/timestamp-temporal@1\":\n\t\tcase \"pg/timestamp-string@1\":\n\t\t\treturn new Date(`${String(value).replace(\" \", \"T\")}Z`);\n\t\tcase \"pg/date-temporal@1\":\n\t\tcase \"pg/date-string@1\":\n\t\t\treturn new Date(`${String(value)}T00:00:00Z`);\n\t\tdefault:\n\t\t\treturn value;\n\t}\n}\n","import type { DBFieldAttribute } from \"@better-auth/core/db\";\nimport type { ExtractFieldInputTypes } from \"@prisma/orm-postgres/family-contract/types\";\nimport type { Collection } from \"@prisma/orm-postgres/orm-client\";\n\ntype CollectionShape = {\n\tfirst: (...args: never[]) => Promise<unknown>;\n\tcreate: (...args: never[]) => Promise<unknown>;\n};\ntype Definitions = Record<string, DBFieldAttribute>;\ntype Output<C extends CollectionShape> = NonNullable<\n\tAwaited<ReturnType<C[\"first\"]>>\n>;\ntype Input<C extends CollectionShape> =\n\tC extends Collection<infer CT, infer M, infer _Row, infer State>\n\t\t? State[\"nsId\"] extends keyof ExtractFieldInputTypes<CT>\n\t\t\t? M extends keyof ExtractFieldInputTypes<CT>[State[\"nsId\"]]\n\t\t\t\t? ExtractFieldInputTypes<CT>[State[\"nsId\"]][M]\n\t\t\t\t: never\n\t\t\t: never\n\t\t: never;\ntype StorageKey<D, K> = D extends { fieldName: infer F extends string } ? F : K;\ntype StoredField<Shape, D, K> =\n\tStorageKey<D, K> extends keyof Shape ? Shape[StorageKey<D, K>] : never;\ntype FieldCodec<C extends CollectionShape, D, K> =\n\tC extends Collection<infer CT, infer M, infer _Row, infer State>\n\t\t? CT[\"domain\"][\"namespaces\"][State[\"nsId\"]][\"models\"][M] extends {\n\t\t\t\tfields: infer Fields;\n\t\t\t}\n\t\t\t? StorageKey<D, K> extends keyof Fields\n\t\t\t\t? Fields[StorageKey<D, K>] extends { type: { codecId: infer Codec } }\n\t\t\t\t\t? Codec\n\t\t\t\t\t: never\n\t\t\t\t: never\n\t\t\t: never\n\t\t: never;\ntype BuiltinDateCodec =\n\t`pg/${\"timestamptz\" | \"timestamp\" | \"date\"}-${\"temporal\" | \"string\"}@1`;\ntype FieldValue<Shape, C extends CollectionShape, D, K> = D extends {\n\ttype: \"date\";\n}\n\t? [FieldCodec<C, D, K>] extends [never]\n\t\t? StoredField<Shape, D, K>\n\t\t: FieldCodec<C, D, K> extends BuiltinDateCodec\n\t\t\t? Date | Extract<StoredField<Shape, D, K>, null | undefined>\n\t\t\t: StoredField<Shape, D, K>\n\t: StoredField<Shape, D, K>;\ntype RequiredInput<D> = D extends\n\t| { required: false }\n\t| { defaultValue: unknown }\n\t? false\n\t: true;\n\ntype UserInput<C extends CollectionShape, D extends Definitions> = {\n\t-readonly [K in keyof D as D[K] extends { input: false }\n\t\t? never\n\t\t: RequiredInput<D[K]> extends true\n\t\t\t? K\n\t\t\t: never]: FieldValue<Input<C>, C, D[K], K>;\n} & {\n\t-readonly [K in keyof D as D[K] extends { input: false }\n\t\t? never\n\t\t: RequiredInput<D[K]> extends false\n\t\t\t? K\n\t\t\t: never]?: FieldValue<Input<C>, C, D[K], K>;\n};\ntype UserOutput<C extends CollectionShape, D extends Definitions> = {\n\t-readonly [K in keyof D as D[K] extends { returned: false }\n\t\t? never\n\t\t: D[K] extends { required: false }\n\t\t\t? never\n\t\t\t: K]: FieldValue<Output<C>, C, D[K], K>;\n} & {\n\t-readonly [K in keyof D as D[K] extends { returned: false }\n\t\t? never\n\t\t: D[K] extends { required: false }\n\t\t\t? K\n\t\t\t: never]?: FieldValue<Output<C>, C, D[K], K> | null;\n};\n\ntype ReplaceUser<\n\tT,\n\tC extends CollectionShape,\n\tD extends Definitions,\n> = T extends {\n\tid: string;\n\temail: string;\n\temailVerified: boolean;\n\tname: string;\n}\n\t? Omit<T, keyof D> & UserOutput<C, D>\n\t: T extends Date | Response | Headers | Map<unknown, unknown> | Set<unknown>\n\t\t? T\n\t\t: T extends readonly unknown[]\n\t\t\t? { [K in keyof T]: ReplaceUser<T[K], C, D> }\n\t\t\t: T extends object\n\t\t\t\t? { [K in keyof T]: ReplaceUser<T[K], C, D> }\n\t\t\t\t: T;\n\ntype Endpoint = (...args: never[]) => unknown;\ntype Context<E extends Endpoint> = NonNullable<Parameters<E>[0]>;\ntype BodyContext<\n\tE extends Endpoint,\n\tK,\n\tC extends CollectionShape,\n\tD extends Definitions,\n> = K extends \"signUpEmail\" | \"updateUser\"\n\t? Omit<Context<E>, \"body\"> & {\n\t\t\tbody: Omit<Context<E> extends { body?: infer B } ? B : object, keyof D> &\n\t\t\t\t(K extends \"updateUser\" ? Partial<UserInput<C, D>> : UserInput<C, D>);\n\t\t}\n\t: Context<E>;\ntype Flags = \"asResponse\" | \"returnHeaders\" | \"returnStatus\";\ntype EndpointResult<\n\tT,\n\tR extends boolean,\n\tH extends boolean,\n\tS extends boolean,\n> = R extends true\n\t? Response\n\t: H extends true\n\t\t? S extends true\n\t\t\t? { headers: Headers; status: number; response: T }\n\t\t\t: { headers: Headers; response: T }\n\t\t: S extends true\n\t\t\t? { status: number; response: T }\n\t\t\t: T;\ntype Call<E extends Endpoint, B, T> = undefined extends Parameters<E>[0]\n\t? <\n\t\t\tR extends boolean = false,\n\t\t\tH extends boolean = false,\n\t\t\tS extends boolean = false,\n\t\t>(\n\t\t\tcontext?: Omit<B, Flags> & {\n\t\t\t\tasResponse?: R | undefined;\n\t\t\t\treturnHeaders?: H | undefined;\n\t\t\t\treturnStatus?: (\"returnStatus\" extends keyof B ? S : never) | undefined;\n\t\t\t},\n\t\t) => Promise<EndpointResult<T, R, H, S>>\n\t: <\n\t\t\tR extends boolean = false,\n\t\t\tH extends boolean = false,\n\t\t\tS extends boolean = false,\n\t\t>(\n\t\t\tcontext: Omit<B, Flags> & {\n\t\t\t\tasResponse?: R | undefined;\n\t\t\t\treturnHeaders?: H | undefined;\n\t\t\t\treturnStatus?: (\"returnStatus\" extends keyof B ? S : never) | undefined;\n\t\t\t},\n\t\t) => Promise<EndpointResult<T, R, H, S>>;\n\ntype AuthShape = {\n\tapi: object;\n\t$Infer: { Session: unknown };\n\toptions: { user?: { additionalFields?: Definitions } };\n};\n\n/** A server-side type view. Runtime calls and Better Auth's HTTP handler are unchanged. */\nexport type TypedPrismaAuth<\n\tA extends AuthShape,\n\tC extends CollectionShape,\n\tD extends Definitions,\n> = Omit<A, \"api\" | \"$Infer\"> & {\n\t$Infer: ReplaceUser<A[\"$Infer\"], C, D>;\n\tapi: {\n\t\t[K in keyof A[\"api\"]]: A[\"api\"][K] extends Endpoint\n\t\t\t? Pick<A[\"api\"][K], keyof A[\"api\"][K]> &\n\t\t\t\t\tCall<\n\t\t\t\t\t\tA[\"api\"][K],\n\t\t\t\t\t\tBodyContext<A[\"api\"][K], K, C, D>,\n\t\t\t\t\t\tK extends \"getSession\"\n\t\t\t\t\t\t\t? ReplaceUser<A[\"$Infer\"][\"Session\"], C, D> | null\n\t\t\t\t\t\t\t: ReplaceUser<Awaited<ReturnType<A[\"api\"][K]>>, C, D>\n\t\t\t\t\t>\n\t\t\t: A[\"api\"][K];\n\t};\n};\n\nexport interface PrismaUserFields<\n\tC extends CollectionShape,\n\tD extends Definitions,\n> {\n\tadditionalFields: D;\n\t/** Apply after betterAuth({ user: { additionalFields } }). For direct server calls, not HTTP deserialization. */\n\tinferAuth<A extends AuthShape>(auth: A): TypedPrismaAuth<A, C, D>;\n}\n\n/** Derive field input/output types from an unprojected Prisma User collection, including extension codecs. */\nexport function prismaUserFields<C extends CollectionShape>(_collection: C) {\n\treturn <const D extends Definitions>(\n\t\tdefinitions: D & {\n\t\t\t[K in keyof D]: StorageKey<D[K], K> extends keyof Output<C> &\n\t\t\t\tkeyof Input<C>\n\t\t\t\t? D[K]\n\t\t\t\t: never;\n\t\t},\n\t): PrismaUserFields<C, D> => ({\n\t\tadditionalFields: definitions,\n\t\tinferAuth(auth) {\n\t\t\tif (auth.options.user?.additionalFields !== definitions) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Pass this helper's additionalFields to betterAuth({ user: { additionalFields } }) before inferAuth().\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn auth as unknown as TypedPrismaAuth<typeof auth, C, D>;\n\t\t},\n\t});\n}\n","import type { BetterAuthOptions } from \"@better-auth/core\";\nimport type {\n\tAdapterFactoryCustomizeAdapterCreator,\n\tDBAdapter,\n\tDBAdapterDebugLogOption,\n\tJoinConfig,\n\tWhere,\n} from \"@better-auth/core/db/adapter\";\nimport { createAdapterFactory } from \"@better-auth/core/db/adapter\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport type { Contract } from \"@prisma/orm-postgres/contract/types\";\nimport type { SqlStorage } from \"@prisma/orm-postgres/family-contract/types\";\nimport { and, or } from \"@prisma/orm-postgres/orm-client\";\nimport {\n\ttype AnyExpression,\n\tBinaryExpr,\n\tFunctionCallExpr,\n} from \"@prisma/orm-postgres/relational-core/ast\";\nimport type { PostgresClient } from \"@prisma/orm-postgres/runtime\";\nimport { dateInput, dateOutput } from \"./dates\";\n\nexport type { PrismaUserFields, TypedPrismaAuth } from \"./user-fields\";\nexport { prismaUserFields } from \"./user-fields\";\n\nexport interface Prisma8Config {\n\t/** PostgreSQL is the supported Prisma 8 target. */\n\tprovider?: \"postgresql\";\n\t/** Same default as the original Prisma adapter: false. */\n\tusePlural?: boolean;\n\t/** Same default as the original Prisma adapter: false. */\n\ttransaction?: boolean;\n\tdebugLogs?: DBAdapterDebugLogOption;\n\t/** Restrict model lookup to this contract namespace. Otherwise names must be unambiguous. */\n\tnamespace?: string;\n\t/** Keys are Better Auth model names after modelName/usePlural; values are exact contract coordinates. */\n\tmodels?: Record<string, string | { namespace: string; model: string }>;\n}\n\ntype Row = Record<string, unknown>;\ntype Field = Record<string, ((value?: unknown) => AnyExpression) | undefined>;\ntype Fields = Record<string, Field | undefined>;\n// Better Auth dispatches model and field names dynamically. Keep that erased boundary here;\n// the public client argument remains checked against Prisma's real contract/client types.\ninterface Query {\n\twhere(predicate: (fields: Fields) => AnyExpression): Query;\n\tselect(...fields: string[]): Query;\n\torderBy(predicate: (fields: Fields) => AnyExpression): Query;\n\tlimit(value: number): Query;\n\toffset(value: number): Query;\n\tcreate(data: unknown): Promise<Row>;\n\tfirst(): Promise<Row | null>;\n\tall(): PromiseLike<Row[]>;\n\taggregate(\n\t\tbuild: (aggregate: { count(): unknown }) => { total: unknown },\n\t): Promise<{ total: number }>;\n\tupdate(data: unknown): Promise<Row | null>;\n\tupdateAndCount(data: unknown): Promise<number>;\n\tdelete(): Promise<Row | null>;\n\tdeleteAndCount(): Promise<number>;\n}\n\nfunction call(\n\tfield: Field | undefined,\n\tmethod: string,\n\tvalue?: unknown,\n): AnyExpression {\n\tconst fn = field?.[method];\n\tif (!fn)\n\t\tthrow new BetterAuthError(\n\t\t\t`Prisma 8 field does not support ${method}; check the field name and codec traits.`,\n\t\t);\n\treturn fn(value);\n}\n\nconst escapeLike = (value: string) => value.replace(/[\\\\%_]/g, \"\\\\$&\");\n\n/** Convert Better Auth's AND-group AND (OR-group) semantics, as in the Prisma 7 adapter. */\nexport function prismaWhere(\n\tfields: Fields,\n\twhere: readonly Where[] = [],\n): AnyExpression {\n\tfunction condition(w: Where): AnyExpression {\n\t\tconst field = fields[w.field];\n\t\tconst op = w.operator ?? \"eq\";\n\t\tif (w.value === null && (op === \"eq\" || op === \"ne\")) {\n\t\t\treturn call(field, op === \"eq\" ? \"isNull\" : \"isNotNull\");\n\t\t}\n\t\tconst insensitive = w.mode === \"insensitive\";\n\t\tif (op === \"in\" || op === \"not_in\") {\n\t\t\tif (!Array.isArray(w.value))\n\t\t\t\tthrow new BetterAuthError(`${op} requires an array`);\n\t\t\tconst values = w.value.filter((value) => value != null);\n\t\t\tif (!values.length) return op === \"in\" ? or() : and();\n\t\t\tif (insensitive && values.every((value) => typeof value === \"string\")) {\n\t\t\t\tconst expr = or(\n\t\t\t\t\t...values.map((value) => condition({ ...w, operator: \"eq\", value })),\n\t\t\t\t);\n\t\t\t\treturn op === \"not_in\" ? expr.not() : expr;\n\t\t\t}\n\t\t\treturn call(field, op === \"in\" ? \"in\" : \"notIn\", values);\n\t\t}\n\t\tconst method = op === \"ne\" ? \"neq\" : op;\n\t\tlet expr: AnyExpression;\n\t\tif (op === \"contains\" || op === \"starts_with\" || op === \"ends_with\") {\n\t\t\tif (typeof w.value !== \"string\")\n\t\t\t\tthrow new BetterAuthError(`${op} requires a string`);\n\t\t\tconst pattern = `${op === \"starts_with\" ? \"\" : \"%\"}${escapeLike(w.value)}${op === \"ends_with\" ? \"\" : \"%\"}`;\n\t\t\texpr = call(field, \"like\", pattern);\n\t\t} else {\n\t\t\texpr = call(field, method, w.value);\n\t\t}\n\t\tif (insensitive && typeof w.value === \"string\") {\n\t\t\t// Build from the codec-aware ORM predicate: never interpolate identifiers or values.\n\t\t\tif (!(expr instanceof BinaryExpr))\n\t\t\t\tthrow new BetterAuthError(\"Expected a Prisma binary predicate\");\n\t\t\texpr = new BinaryExpr(\n\t\t\t\texpr.op,\n\t\t\t\tFunctionCallExpr.of(\"lower\", [expr.left]),\n\t\t\t\tFunctionCallExpr.of(\"lower\", [expr.right]),\n\t\t\t);\n\t\t}\n\t\treturn expr;\n\t}\n\tconst conjunction = where.filter((w) => w.connector !== \"OR\").map(condition);\n\tconst disjunction = where.filter((w) => w.connector === \"OR\").map(condition);\n\treturn and(\n\t\t...conjunction,\n\t\t...(disjunction.length ? [or(...disjunction)] : []),\n\t);\n}\n\nexport function prismaAdapter<C extends Contract<SqlStorage>>(\n\tdb: Pick<PostgresClient<C>, \"orm\" | \"contract\" | \"transaction\">,\n\tconfig: Prisma8Config = {},\n): (options: BetterAuthOptions) => DBAdapter<BetterAuthOptions> {\n\tif (db.contract.target !== \"postgres\")\n\t\tthrow new BetterAuthError(\n\t\t\t\"This adapter requires a Prisma 8 PostgreSQL contract\",\n\t\t);\n\tconst resolve = (name: string): { namespace: string; model: string } => {\n\t\tconst mapped = config.models?.[name];\n\t\tconst model = typeof mapped === \"string\" ? mapped : (mapped?.model ?? name);\n\t\tconst namespace =\n\t\t\ttypeof mapped === \"object\" ? mapped.namespace : config.namespace;\n\t\tconst candidates = Object.entries(db.contract.domain.namespaces).flatMap(\n\t\t\t([ns, domain]) =>\n\t\t\t\tnamespace !== undefined && ns !== namespace\n\t\t\t\t\t? []\n\t\t\t\t\t: Object.keys(domain.models ?? {}).map((key) => ({\n\t\t\t\t\t\t\tnamespace: ns,\n\t\t\t\t\t\t\tmodel: key,\n\t\t\t\t\t\t})),\n\t\t);\n\t\tconst exact = candidates.filter((c) => c.model === model);\n\t\tconst matches = exact.length\n\t\t\t? exact\n\t\t\t: mapped === undefined\n\t\t\t\t? candidates.filter(\n\t\t\t\t\t\t(c) => c.model[0]?.toLowerCase() + c.model.slice(1) === model,\n\t\t\t\t\t)\n\t\t\t\t: [];\n\t\tif (matches.length !== 1)\n\t\t\tthrow new BetterAuthError(\n\t\t\t\t`Prisma 8 model '${name}' ${matches.length ? \"is ambiguous\" : \"was not found\"}. Configure namespace/models using contract model names (not storage table names).`,\n\t\t\t);\n\t\tconst match = matches[0];\n\t\tif (!match) throw new BetterAuthError(\"Prisma model resolution failed\");\n\t\treturn match;\n\t};\n\n\treturn (options) => {\n\t\tconst build = (\n\t\t\torm: unknown,\n\t\t\tinTransaction: boolean,\n\t\t): DBAdapter<BetterAuthOptions> => {\n\t\t\tconst creator: AdapterFactoryCustomizeAdapterCreator = ({\n\t\t\t\tgetFieldName,\n\t\t\t\tschema,\n\t\t\t\tgetDefaultModelName,\n\t\t\t}) => {\n\t\t\t\tconst dateCodec = (model: string, field: string) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!Object.entries(\n\t\t\t\t\t\t\tschema[getDefaultModelName(model)]?.fields ?? {},\n\t\t\t\t\t\t).some(\n\t\t\t\t\t\t\t([key, attr]) =>\n\t\t\t\t\t\t\t\t(attr.fieldName ?? key) === field && attr.type === \"date\",\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\tconst coordinate = resolve(model);\n\t\t\t\t\tconst type =\n\t\t\t\t\t\tdb.contract.domain.namespaces[coordinate.namespace]?.models[\n\t\t\t\t\t\t\tcoordinate.model\n\t\t\t\t\t\t]?.fields?.[field]?.type;\n\t\t\t\t\treturn type?.kind === \"scalar\" ? type.codecId : undefined;\n\t\t\t\t};\n\t\t\t\tconst inputData = (model: string, data: unknown) =>\n\t\t\t\t\tObject.fromEntries(\n\t\t\t\t\t\tObject.entries(data as Row).map(([field, value]) => [\n\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\tdateInput(dateCodec(model, field), value),\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tconst outputRow = (model: string, row: Row): Row =>\n\t\t\t\t\tObject.fromEntries(\n\t\t\t\t\t\tObject.entries(row).map(([field, value]) => [\n\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\tdateOutput(dateCodec(model, field), value),\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\tconst query = (model: string, where?: readonly Where[]) => {\n\t\t\t\t\tconst coordinate = resolve(model);\n\t\t\t\t\tconst surface = orm as Record<string, Record<string, Query>>;\n\t\t\t\t\tconst collection = surface[coordinate.namespace]?.[coordinate.model];\n\t\t\t\t\tif (!collection)\n\t\t\t\t\t\tthrow new BetterAuthError(\n\t\t\t\t\t\t\t`Prisma 8 collection ${coordinate.namespace}.${coordinate.model} is unavailable`,\n\t\t\t\t\t\t);\n\t\t\t\t\treturn where === undefined\n\t\t\t\t\t\t? collection\n\t\t\t\t\t\t: collection.where((fields) =>\n\t\t\t\t\t\t\t\tprismaWhere(\n\t\t\t\t\t\t\t\t\tfields,\n\t\t\t\t\t\t\t\t\twhere.map((w) => ({\n\t\t\t\t\t\t\t\t\t\t...w,\n\t\t\t\t\t\t\t\t\t\tvalue: dateInput(\n\t\t\t\t\t\t\t\t\t\t\tdateCodec(model, w.field),\n\t\t\t\t\t\t\t\t\t\t\tw.value,\n\t\t\t\t\t\t\t\t\t\t) as Where[\"value\"],\n\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\tconst selectQuery = (q: Query, model: string, select?: string[]) =>\n\t\t\t\t\tselect?.length\n\t\t\t\t\t\t? q.select(...select.map((field) => getFieldName({ model, field })))\n\t\t\t\t\t\t: q;\n\t\t\t\t// Join using Better Auth's resolved FK metadata, so relation-property naming is irrelevant.\n\t\t\t\tconst joinRows = async (\n\t\t\t\t\trows: Row[],\n\t\t\t\t\tmodel: string,\n\t\t\t\t\tjoin?: JoinConfig,\n\t\t\t\t) => {\n\t\t\t\t\trows = rows.map((row) => outputRow(model, row));\n\t\t\t\t\tif (!join) return rows;\n\t\t\t\t\treturn Promise.all(\n\t\t\t\t\t\trows.map(async (row) => {\n\t\t\t\t\t\t\tconst result = { ...row };\n\t\t\t\t\t\t\tfor (const [joinedModel, attr] of Object.entries(join)) {\n\t\t\t\t\t\t\t\tconst value = row[getFieldName({ model, field: attr.on.from })];\n\t\t\t\t\t\t\t\tlet joined: Row[] = [];\n\t\t\t\t\t\t\t\tif (value != null) {\n\t\t\t\t\t\t\t\t\tjoined = await query(joinedModel, [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tfield: getFieldName({\n\t\t\t\t\t\t\t\t\t\t\t\tmodel: joinedModel,\n\t\t\t\t\t\t\t\t\t\t\t\tfield: attr.on.to,\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\tvalue: value as Where[\"value\"],\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t\t.limit(\n\t\t\t\t\t\t\t\t\t\t\tattr.relation === \"one-to-one\" ? 1 : (attr.limit ?? 100),\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t.all();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tjoined = joined.map((row) => outputRow(joinedModel, row));\n\t\t\t\t\t\t\t\tresult[joinedModel] =\n\t\t\t\t\t\t\t\t\tattr.relation === \"one-to-one\" ? (joined[0] ?? null) : joined;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn result;\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\tasync create({ model, data, select }) {\n\t\t\t\t\t\treturn outputRow(\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tawait selectQuery(query(model), model, select).create(\n\t\t\t\t\t\t\t\tinputData(model, data),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t) as typeof data;\n\t\t\t\t\t},\n\t\t\t\t\tasync findOne<T>({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t\tselect,\n\t\t\t\t\t\tjoin,\n\t\t\t\t\t}: {\n\t\t\t\t\t\tmodel: string;\n\t\t\t\t\t\twhere: Where[];\n\t\t\t\t\t\tselect?: string[] | undefined;\n\t\t\t\t\t\tjoin?: JoinConfig | undefined;\n\t\t\t\t\t}) {\n\t\t\t\t\t\tconst row = await selectQuery(\n\t\t\t\t\t\t\tquery(model, where),\n\t\t\t\t\t\t\tmodel,\n\t\t\t\t\t\t\tselect,\n\t\t\t\t\t\t).first();\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\trow ? (await joinRows([row], model, join))[0] : null\n\t\t\t\t\t\t) as T | null;\n\t\t\t\t\t},\n\t\t\t\t\tasync findMany<T>({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t\tselect,\n\t\t\t\t\t\tlimit,\n\t\t\t\t\t\toffset,\n\t\t\t\t\t\tsortBy,\n\t\t\t\t\t\tjoin,\n\t\t\t\t\t}: {\n\t\t\t\t\t\tmodel: string;\n\t\t\t\t\t\twhere?: Where[] | undefined;\n\t\t\t\t\t\tselect?: string[] | undefined;\n\t\t\t\t\t\tlimit: number;\n\t\t\t\t\t\toffset?: number | undefined;\n\t\t\t\t\t\tsortBy?: { field: string; direction: \"asc\" | \"desc\" } | undefined;\n\t\t\t\t\t\tjoin?: JoinConfig | undefined;\n\t\t\t\t\t}) {\n\t\t\t\t\t\tlet q = selectQuery(query(model, where), model, select).limit(\n\t\t\t\t\t\t\tlimit,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (offset !== undefined) q = q.offset(offset);\n\t\t\t\t\t\tif (sortBy)\n\t\t\t\t\t\t\tq = q.orderBy((fields) =>\n\t\t\t\t\t\t\t\tcall(\n\t\t\t\t\t\t\t\t\tfields[getFieldName({ model, field: sortBy.field })],\n\t\t\t\t\t\t\t\t\tsortBy.direction,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn (await joinRows(await q.all(), model, join)) as T[];\n\t\t\t\t\t},\n\t\t\t\t\tcount: async ({ model, where }) =>\n\t\t\t\t\t\t(await query(model, where).aggregate((a) => ({ total: a.count() })))\n\t\t\t\t\t\t\t.total,\n\t\t\t\t\tasync update<T>({\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t\tupdate,\n\t\t\t\t\t}: {\n\t\t\t\t\t\tmodel: string;\n\t\t\t\t\t\twhere: Where[];\n\t\t\t\t\t\tupdate: T;\n\t\t\t\t\t}) {\n\t\t\t\t\t\tconst row = await query(model, where).update(\n\t\t\t\t\t\t\tinputData(model, update),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn (row ? outputRow(model, row) : null) as T | null;\n\t\t\t\t\t},\n\t\t\t\t\tupdateMany: ({ model, where, update }) =>\n\t\t\t\t\t\tquery(model, where).updateAndCount(inputData(model, update)),\n\t\t\t\t\tasync delete({ model, where }) {\n\t\t\t\t\t\tawait query(model, where).delete();\n\t\t\t\t\t},\n\t\t\t\t\tdeleteMany: ({ model, where }) =>\n\t\t\t\t\t\tquery(model, where).deleteAndCount(),\n\t\t\t\t\t// Better Auth's compare-and-swap fallbacks use atomic updateAndCount/deleteAndCount.\n\t\t\t\t\t// Prisma's single-row update/delete first select an identity; they are not CAS primitives.\n\t\t\t\t\toptions: config,\n\t\t\t\t};\n\t\t\t};\n\t\t\treturn createAdapterFactory({\n\t\t\t\tconfig: {\n\t\t\t\t\tadapterId: \"prisma-8\",\n\t\t\t\t\tadapterName: \"Prisma 8 Adapter\",\n\t\t\t\t\tusePlural: config.usePlural ?? false,\n\t\t\t\t\tdebugLogs: config.debugLogs ?? false,\n\t\t\t\t\tsupportsJSON: true,\n\t\t\t\t\tsupportsArrays: true,\n\t\t\t\t\tsupportsDates: true,\n\t\t\t\t\tsupportsBooleans: true,\n\t\t\t\t\tsupportsUUIDs: true,\n\t\t\t\t\tsupportsNumericIds: true,\n\t\t\t\t\ttransaction:\n\t\t\t\t\t\tconfig.transaction && !inTransaction\n\t\t\t\t\t\t\t? (callback) =>\n\t\t\t\t\t\t\t\t\tdb.transaction((tx) => callback(build(tx.orm, true)))\n\t\t\t\t\t\t\t: false,\n\t\t\t\t},\n\t\t\t\tadapter: creator,\n\t\t\t})(options);\n\t\t};\n\t\treturn build(db.orm, false);\n\t};\n}\n\nexport { prismaAdapter as prisma8Adapter };\n"],"mappings":";;;;;;;AAGA,SAAgB,UAAU,OAA2B,OAAyB;CAC7E,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,SAAS,UAAU,OAAO,IAAI,CAAC;CAC3E,IAAI,EAAE,iBAAiB,OAAO,OAAO;CACrC,QAAQ,OAAR;EACC,KAAK,6BACJ,OAAO,SAAS,QAAQ,sBAAsB,MAAM,QAAQ,CAAC;EAC9D,KAAK,2BACJ,OAAO,SAAS,QAAQ,sBAAsB,MAAM,QAAQ,CAAC,CAAC,CAC5D,mBAAmB,KAAK,CAAC,CACzB,gBAAgB;EACnB,KAAK,sBACJ,OAAO,SAAS,QAAQ,sBAAsB,MAAM,QAAQ,CAAC,CAAC,CAC5D,mBAAmB,KAAK,CAAC,CACzB,YAAY;EACf,KAAK,2BACJ,OAAO,MAAM,YAAY;EAC1B,KAAK,yBACJ,OAAO,MAAM,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;EACvC,KAAK,oBACJ,OAAO,MAAM,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;EACvC,SACC,OAAO;CACT;AACD;AAEA,SAAgB,WAAW,OAA2B,OAAyB;CAC9E,IAAI,SAAS,MAAM,OAAO;CAC1B,QAAQ,OAAR;EACC,KAAK;EACL,KAAK,2BACJ,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;EAC9B,KAAK;EACL,KAAK,yBACJ,uBAAO,IAAI,KAAK,GAAG,OAAO,KAAK,CAAC,CAAC,QAAQ,KAAK,GAAG,EAAE,EAAE;EACtD,KAAK;EACL,KAAK,oBACJ,uBAAO,IAAI,KAAK,GAAG,OAAO,KAAK,EAAE,WAAW;EAC7C,SACC,OAAO;CACT;AACD;;;;ACgJA,SAAgB,iBAA4C,aAAgB;CAC3E,QACC,iBAM6B;EAC7B,kBAAkB;EAClB,UAAU,MAAM;GACf,IAAI,KAAK,QAAQ,MAAM,qBAAqB,aAC3C,MAAM,IAAI,MACT,uGACD;GAED,OAAO;EACR;CACD;AACD;;;ACjJA,SAAS,KACR,OACA,QACA,OACgB;CAChB,MAAM,KAAK,QAAQ;CACnB,IAAI,CAAC,IACJ,MAAM,IAAI,gBACT,mCAAmC,OAAO,yCAC3C;CACD,OAAO,GAAG,KAAK;AAChB;AAEA,MAAM,cAAc,UAAkB,MAAM,QAAQ,WAAW,MAAM;;AAGrE,SAAgB,YACf,QACA,QAA0B,CAAC,GACX;CAChB,SAAS,UAAU,GAAyB;EAC3C,MAAM,QAAQ,OAAO,EAAE;EACvB,MAAM,KAAK,EAAE,YAAY;EACzB,IAAI,EAAE,UAAU,SAAS,OAAO,QAAQ,OAAO,OAC9C,OAAO,KAAK,OAAO,OAAO,OAAO,WAAW,WAAW;EAExD,MAAM,cAAc,EAAE,SAAS;EAC/B,IAAI,OAAO,QAAQ,OAAO,UAAU;GACnC,IAAI,CAAC,MAAM,QAAQ,EAAE,KAAK,GACzB,MAAM,IAAI,gBAAgB,GAAG,GAAG,mBAAmB;GACpD,MAAM,SAAS,EAAE,MAAM,QAAQ,UAAU,SAAS,IAAI;GACtD,IAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG,IAAI,IAAI;GACpD,IAAI,eAAe,OAAO,OAAO,UAAU,OAAO,UAAU,QAAQ,GAAG;IACtE,MAAM,OAAO,GACZ,GAAG,OAAO,KAAK,UAAU,UAAU;KAAE,GAAG;KAAG,UAAU;KAAM;IAAM,CAAC,CAAC,CACpE;IACA,OAAO,OAAO,WAAW,KAAK,IAAI,IAAI;GACvC;GACA,OAAO,KAAK,OAAO,OAAO,OAAO,OAAO,SAAS,MAAM;EACxD;EACA,MAAM,SAAS,OAAO,OAAO,QAAQ;EACrC,IAAI;EACJ,IAAI,OAAO,cAAc,OAAO,iBAAiB,OAAO,aAAa;GACpE,IAAI,OAAO,EAAE,UAAU,UACtB,MAAM,IAAI,gBAAgB,GAAG,GAAG,mBAAmB;GAEpD,OAAO,KAAK,OAAO,QAAQ,GADR,OAAO,gBAAgB,KAAK,MAAM,WAAW,EAAE,KAAK,IAAI,OAAO,cAAc,KAAK,KACnE;EACnC,OACC,OAAO,KAAK,OAAO,QAAQ,EAAE,KAAK;EAEnC,IAAI,eAAe,OAAO,EAAE,UAAU,UAAU;GAE/C,IAAI,EAAE,gBAAgB,aACrB,MAAM,IAAI,gBAAgB,oCAAoC;GAC/D,OAAO,IAAI,WACV,KAAK,IACL,iBAAiB,GAAG,SAAS,CAAC,KAAK,IAAI,CAAC,GACxC,iBAAiB,GAAG,SAAS,CAAC,KAAK,KAAK,CAAC,CAC1C;EACD;EACA,OAAO;CACR;CACA,MAAM,cAAc,MAAM,QAAQ,MAAM,EAAE,cAAc,IAAI,CAAC,CAAC,IAAI,SAAS;CAC3E,MAAM,cAAc,MAAM,QAAQ,MAAM,EAAE,cAAc,IAAI,CAAC,CAAC,IAAI,SAAS;CAC3E,OAAO,IACN,GAAG,aACH,GAAI,YAAY,SAAS,CAAC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAClD;AACD;AAEA,SAAgB,cACf,IACA,SAAwB,CAAC,GACsC;CAC/D,IAAI,GAAG,SAAS,WAAW,YAC1B,MAAM,IAAI,gBACT,sDACD;CACD,MAAM,WAAW,SAAuD;EACvE,MAAM,SAAS,OAAO,SAAS;EAC/B,MAAM,QAAQ,OAAO,WAAW,WAAW,SAAU,QAAQ,SAAS;EACtE,MAAM,YACL,OAAO,WAAW,WAAW,OAAO,YAAY,OAAO;EACxD,MAAM,aAAa,OAAO,QAAQ,GAAG,SAAS,OAAO,UAAU,CAAC,CAAC,SAC/D,CAAC,IAAI,YACL,cAAc,KAAA,KAAa,OAAO,YAC/B,CAAC,IACD,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;GAC/C,WAAW;GACX,OAAO;EACR,EAAE,CACN;EACA,MAAM,QAAQ,WAAW,QAAQ,MAAM,EAAE,UAAU,KAAK;EACxD,MAAM,UAAU,MAAM,SACnB,QACA,WAAW,KAAA,IACV,WAAW,QACV,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,IAAI,EAAE,MAAM,MAAM,CAAC,MAAM,KACzD,IACC,CAAC;EACL,IAAI,QAAQ,WAAW,GACtB,MAAM,IAAI,gBACT,mBAAmB,KAAK,IAAI,QAAQ,SAAS,iBAAiB,gBAAgB,mFAC/E;EACD,MAAM,QAAQ,QAAQ;EACtB,IAAI,CAAC,OAAO,MAAM,IAAI,gBAAgB,gCAAgC;EACtE,OAAO;CACR;CAEA,QAAQ,YAAY;EACnB,MAAM,SACL,KACA,kBACkC;GAClC,MAAM,WAAkD,EACvD,cACA,QACA,0BACK;IACL,MAAM,aAAa,OAAe,UAAkB;KACnD,IACC,CAAC,OAAO,QACP,OAAO,oBAAoB,KAAK,EAAE,EAAE,UAAU,CAAC,CAChD,CAAC,CAAC,MACA,CAAC,KAAK,WACL,KAAK,aAAa,SAAS,SAAS,KAAK,SAAS,MACrD,GAEA,OAAO,KAAA;KACR,MAAM,aAAa,QAAQ,KAAK;KAChC,MAAM,OACL,GAAG,SAAS,OAAO,WAAW,WAAW,UAAU,EAAE,OACpD,WAAW,MACX,EAAE,SAAS,MAAM,EAAE;KACrB,OAAO,MAAM,SAAS,WAAW,KAAK,UAAU,KAAA;IACjD;IACA,MAAM,aAAa,OAAe,SACjC,OAAO,YACN,OAAO,QAAQ,IAAW,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CACnD,OACA,UAAU,UAAU,OAAO,KAAK,GAAG,KAAK,CACzC,CAAC,CACF;IACD,MAAM,aAAa,OAAe,QACjC,OAAO,YACN,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAC3C,OACA,WAAW,UAAU,OAAO,KAAK,GAAG,KAAK,CAC1C,CAAC,CACF;IACD,MAAM,SAAS,OAAe,UAA6B;KAC1D,MAAM,aAAa,QAAQ,KAAK;KAEhC,MAAM,aAAaA,IAAQ,WAAW,UAAU,GAAG,WAAW;KAC9D,IAAI,CAAC,YACJ,MAAM,IAAI,gBACT,uBAAuB,WAAW,UAAU,GAAG,WAAW,MAAM,gBACjE;KACD,OAAO,UAAU,KAAA,IACd,aACA,WAAW,OAAO,WAClB,YACC,QACA,MAAM,KAAK,OAAO;MACjB,GAAG;MACH,OAAO,UACN,UAAU,OAAO,EAAE,KAAK,GACxB,EAAE,KACH;KACD,EAAE,CACH,CACD;IACH;IACA,MAAM,eAAe,GAAU,OAAe,WAC7C,QAAQ,SACL,EAAE,OAAO,GAAG,OAAO,KAAK,UAAU,aAAa;KAAE;KAAO;IAAM,CAAC,CAAC,CAAC,IACjE;IAEJ,MAAM,WAAW,OAChB,MACA,OACA,SACI;KACJ,OAAO,KAAK,KAAK,QAAQ,UAAU,OAAO,GAAG,CAAC;KAC9C,IAAI,CAAC,MAAM,OAAO;KAClB,OAAO,QAAQ,IACd,KAAK,IAAI,OAAO,QAAQ;MACvB,MAAM,SAAS,EAAE,GAAG,IAAI;MACxB,KAAK,MAAM,CAAC,aAAa,SAAS,OAAO,QAAQ,IAAI,GAAG;OACvD,MAAM,QAAQ,IAAI,aAAa;QAAE;QAAO,OAAO,KAAK,GAAG;OAAK,CAAC;OAC7D,IAAI,SAAgB,CAAC;OACrB,IAAI,SAAS,MACZ,SAAS,MAAM,MAAM,aAAa,CACjC;QACC,OAAO,aAAa;SACnB,OAAO;SACP,OAAO,KAAK,GAAG;QAChB,CAAC;QACM;OACR,CACD,CAAC,CAAC,CACA,MACA,KAAK,aAAa,eAAe,IAAK,KAAK,SAAS,GACrD,CAAC,CACA,IAAI;OAEP,SAAS,OAAO,KAAK,QAAQ,UAAU,aAAa,GAAG,CAAC;OACxD,OAAO,eACN,KAAK,aAAa,eAAgB,OAAO,MAAM,OAAQ;MACzD;MACA,OAAO;KACR,CAAC,CACF;IACD;IACA,OAAO;KACN,MAAM,OAAO,EAAE,OAAO,MAAM,UAAU;MACrC,OAAO,UACN,OACA,MAAM,YAAY,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,OAC9C,UAAU,OAAO,IAAI,CACtB,CACD;KACD;KACA,MAAM,QAAW,EAChB,OACA,OACA,QACA,QAME;MACF,MAAM,MAAM,MAAM,YACjB,MAAM,OAAO,KAAK,GAClB,OACA,MACD,CAAC,CAAC,MAAM;MACR,OACC,OAAO,MAAM,SAAS,CAAC,GAAG,GAAG,OAAO,IAAI,EAAA,CAAG,KAAK;KAElD;KACA,MAAM,SAAY,EACjB,OACA,OACA,QACA,OACA,QACA,QACA,QASE;MACF,IAAI,IAAI,YAAY,MAAM,OAAO,KAAK,GAAG,OAAO,MAAM,CAAC,CAAC,MACvD,KACD;MACA,IAAI,WAAW,KAAA,GAAW,IAAI,EAAE,OAAO,MAAM;MAC7C,IAAI,QACH,IAAI,EAAE,SAAS,WACd,KACC,OAAO,aAAa;OAAE;OAAO,OAAO,OAAO;MAAM,CAAC,IAClD,OAAO,SACR,CACD;MACD,OAAQ,MAAM,SAAS,MAAM,EAAE,IAAI,GAAG,OAAO,IAAI;KAClD;KACA,OAAO,OAAO,EAAE,OAAO,aACrB,MAAM,MAAM,OAAO,KAAK,CAAC,CAAC,WAAW,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAA,CAChE;KACH,MAAM,OAAU,EACf,OACA,OACA,UAKE;MACF,MAAM,MAAM,MAAM,MAAM,OAAO,KAAK,CAAC,CAAC,OACrC,UAAU,OAAO,MAAM,CACxB;MACA,OAAQ,MAAM,UAAU,OAAO,GAAG,IAAI;KACvC;KACA,aAAa,EAAE,OAAO,OAAO,aAC5B,MAAM,OAAO,KAAK,CAAC,CAAC,eAAe,UAAU,OAAO,MAAM,CAAC;KAC5D,MAAM,OAAO,EAAE,OAAO,SAAS;MAC9B,MAAM,MAAM,OAAO,KAAK,CAAC,CAAC,OAAO;KAClC;KACA,aAAa,EAAE,OAAO,YACrB,MAAM,OAAO,KAAK,CAAC,CAAC,eAAe;KAGpC,SAAS;IACV;GACD;GACA,OAAO,qBAAqB;IAC3B,QAAQ;KACP,WAAW;KACX,aAAa;KACb,WAAW,OAAO,aAAa;KAC/B,WAAW,OAAO,aAAa;KAC/B,cAAc;KACd,gBAAgB;KAChB,eAAe;KACf,kBAAkB;KAClB,eAAe;KACf,oBAAoB;KACpB,aACC,OAAO,eAAe,CAAC,iBACnB,aACD,GAAG,aAAa,OAAO,SAAS,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,IACpD;IACL;IACA,SAAS;GACV,CAAC,CAAC,CAAC,OAAO;EACX;EACA,OAAO,MAAM,GAAG,KAAK,KAAK;CAC3B;AACD"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@ryangarber/better-auth-adapter-prisma",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Better Auth adapter for Prisma 8 PostgreSQL with codec-preserving additional user fields",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "import": "./dist/index.mjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "NOTICE"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/ryangarber/better-auth-prisma-8-adapter.git"
24
+ },
25
+ "keywords": [
26
+ "better-auth",
27
+ "prisma",
28
+ "adapter"
29
+ ],
30
+ "author": "Ryan Garber <ryanmichaelgarber@gmail.com>",
31
+ "license": "MIT",
32
+ "devDependencies": {
33
+ "@better-auth/core": "1.7.4",
34
+ "@biomejs/biome": "^2.5.13",
35
+ "@prisma/orm-postgres": "8.0.0-rc.8",
36
+ "@prisma/orm-toolchain": "8.0.0-rc.8",
37
+ "@ryangarber/prisma-orm-extension-zod": "0.1.2",
38
+ "@types/node": "^22.20.2",
39
+ "better-auth": "1.7.4",
40
+ "bumpp": "^12.3.0",
41
+ "temporal-spec": "^1.0.1",
42
+ "tsdown": "^0.23.0",
43
+ "typescript": "^7.0.2",
44
+ "vitest": "^5.0.0",
45
+ "zod": "^4.6.2"
46
+ },
47
+ "types": "./dist/index.d.mts",
48
+ "peerDependencies": {
49
+ "@better-auth/core": "1.7.4",
50
+ "@prisma/orm-postgres": "8.0.0-rc.8"
51
+ },
52
+ "dependencies": {
53
+ "temporal-polyfill": "^1.0.4"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown",
57
+ "lint": "biome check --write",
58
+ "test": "vitest run",
59
+ "typecheck": "tsc --noEmit",
60
+ "release": "bumpp --commit --push --tag && pnpm publish",
61
+ "typecheck:contract": "tsc -p tsconfig.contract.json"
62
+ }
63
+ }