create-croissant 0.1.11 β 0.1.13
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/package.json +1 -1
- package/template/README.md +33 -4
- package/template/apps/web/package.json +0 -1
- package/template/apps/web/node_modules/@better-auth/drizzle-adapter/LICENSE.md +0 -20
- package/template/apps/web/node_modules/@better-auth/drizzle-adapter/README.md +0 -17
- package/template/apps/web/node_modules/@better-auth/drizzle-adapter/dist/index.d.mts +0 -47
- package/template/apps/web/node_modules/@better-auth/drizzle-adapter/dist/index.mjs +0 -458
- package/template/apps/web/node_modules/@better-auth/drizzle-adapter/package.json +0 -62
package/package.json
CHANGED
package/template/README.md
CHANGED
|
@@ -19,9 +19,19 @@ npx create-croissant@latest
|
|
|
19
19
|
- **API**: [oRPC](https://orpc.sh/) with a modular, namespaced router for end-to-end type-safety.
|
|
20
20
|
- **Database**: [Drizzle ORM](https://orm.drizzle.team/) with PostgreSQL and Docker Compose setup.
|
|
21
21
|
- **Styling**: [shadcn/ui](https://ui.shadcn.com/) components with Tailwind CSS.
|
|
22
|
-
- **Monorepo**:
|
|
22
|
+
- **Monorepo**: Powered by [Turborepo](https://turbo.build/) for lightning-fast builds and smart task orchestration.
|
|
23
23
|
- **Developer Experience**: Path aliases (`@/`), strict TypeScript, and automated linting/formatting.
|
|
24
24
|
|
|
25
|
+
## π Monorepo Management with Turborepo
|
|
26
|
+
|
|
27
|
+
This project uses **Turborepo** to manage the monorepo efficiently. Turbo understands the dependency graph between our packages and optimizes our workflow in several ways:
|
|
28
|
+
|
|
29
|
+
- **Smart Caching**: Tasks like `build` and `lint` are cached. If the code hasn't changed, Turbo will replay the logs and output instantly.
|
|
30
|
+
- **Parallel Execution**: Run tasks across multiple packages simultaneously without stepping on each other's toes.
|
|
31
|
+
- **Task Pipelines**: Defines the relationship between tasks (e.g., "don't build the app until its dependencies are built").
|
|
32
|
+
|
|
33
|
+
You can see the configuration in `turbo.json`.
|
|
34
|
+
|
|
25
35
|
## π Project Structure
|
|
26
36
|
|
|
27
37
|
- `apps/web`: The main TanStack Start application. Uses `@/` path alias for clean imports.
|
|
@@ -83,16 +93,35 @@ The application will be available at `http://localhost:3000`.
|
|
|
83
93
|
|
|
84
94
|
## π¦ Scripts
|
|
85
95
|
|
|
96
|
+
All scripts are orchestrated by Turborepo. You can run them from the root directory:
|
|
97
|
+
|
|
86
98
|
- `npm run dev`: Start all applications in development mode.
|
|
87
99
|
- `npm run build`: Build all applications for production.
|
|
100
|
+
- `npm run lint`: Lint all packages (uses Turbo's caching).
|
|
101
|
+
- `npm run format`: Format all packages using Prettier.
|
|
102
|
+
- `npm run typecheck`: Run TypeScript type checking across the workspace.
|
|
103
|
+
|
|
104
|
+
### ποΈ Database Scripts
|
|
105
|
+
|
|
106
|
+
These handle Docker and Drizzle operations:
|
|
107
|
+
|
|
88
108
|
- `npm run db:up`: Start the PostgreSQL Docker container.
|
|
89
109
|
- `npm run db:down`: Stop and remove the database container.
|
|
90
110
|
- `npm run db:logs`: Tail logs from the database container.
|
|
91
111
|
- `npm run db:push --filter @workspace/db`: Push Drizzle schema to the database.
|
|
92
112
|
- `npm run db:studio --filter @workspace/db`: Open Drizzle Studio to explore your data.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
|
|
114
|
+
### π― Filtering Tasks
|
|
115
|
+
|
|
116
|
+
Turbo allows you to run tasks for specific packages using the `--filter` flag:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Only lint the web app
|
|
120
|
+
npm run lint -- --filter web
|
|
121
|
+
|
|
122
|
+
# Build the db package and everything that depends on it
|
|
123
|
+
npm run build -- --filter @workspace/db...
|
|
124
|
+
```
|
|
96
125
|
|
|
97
126
|
## π oRPC & Type Safety
|
|
98
127
|
|
|
@@ -1,20 +0,0 @@
|
|
|
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.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Better Auth Drizzle Adapter
|
|
2
|
-
|
|
3
|
-
Drizzle ORM adapter for [Better Auth](https://www.better-auth.com).
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @better-auth/drizzle-adapter
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Documentation
|
|
12
|
-
|
|
13
|
-
For full documentation, visit [better-auth.com/docs/adapters/drizzle](https://www.better-auth.com/docs/adapters/drizzle).
|
|
14
|
-
|
|
15
|
-
## License
|
|
16
|
-
|
|
17
|
-
MIT
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { DBAdapter, DBAdapterDebugLogOption } from "@better-auth/core/db/adapter";
|
|
2
|
-
import { BetterAuthOptions } from "@better-auth/core";
|
|
3
|
-
|
|
4
|
-
//#region src/drizzle-adapter.d.ts
|
|
5
|
-
interface DB {
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
}
|
|
8
|
-
interface DrizzleAdapterConfig {
|
|
9
|
-
/**
|
|
10
|
-
* The schema object that defines the tables and fields
|
|
11
|
-
*/
|
|
12
|
-
schema?: Record<string, any> | undefined;
|
|
13
|
-
/**
|
|
14
|
-
* The database provider
|
|
15
|
-
*/
|
|
16
|
-
provider: "pg" | "mysql" | "sqlite";
|
|
17
|
-
/**
|
|
18
|
-
* If the table names in the schema are plural
|
|
19
|
-
* set this to true. For example, if the schema
|
|
20
|
-
* has an object with a key "users" instead of "user"
|
|
21
|
-
*/
|
|
22
|
-
usePlural?: boolean | undefined;
|
|
23
|
-
/**
|
|
24
|
-
* Enable debug logs for the adapter
|
|
25
|
-
*
|
|
26
|
-
* @default false
|
|
27
|
-
*/
|
|
28
|
-
debugLogs?: DBAdapterDebugLogOption | undefined;
|
|
29
|
-
/**
|
|
30
|
-
* By default snake case is used for table and field names
|
|
31
|
-
* when the CLI is used to generate the schema. If you want
|
|
32
|
-
* to use camel case, set this to true.
|
|
33
|
-
* @default false
|
|
34
|
-
*/
|
|
35
|
-
camelCase?: boolean | undefined;
|
|
36
|
-
/**
|
|
37
|
-
* Whether to execute multiple operations in a transaction.
|
|
38
|
-
*
|
|
39
|
-
* If the database doesn't support transactions,
|
|
40
|
-
* set this to `false` and operations will be executed sequentially.
|
|
41
|
-
* @default false
|
|
42
|
-
*/
|
|
43
|
-
transaction?: boolean | undefined;
|
|
44
|
-
}
|
|
45
|
-
declare const drizzleAdapter: (db: DB, config: DrizzleAdapterConfig) => (options: BetterAuthOptions) => DBAdapter<BetterAuthOptions>;
|
|
46
|
-
//#endregion
|
|
47
|
-
export { DB, DrizzleAdapterConfig, drizzleAdapter };
|
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import { createAdapterFactory } from "@better-auth/core/db/adapter";
|
|
2
|
-
import { logger } from "@better-auth/core/env";
|
|
3
|
-
import { BetterAuthError } from "@better-auth/core/error";
|
|
4
|
-
import { and, asc, count, desc, eq, gt, gte, ilike, inArray, isNotNull, isNull, like, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
|
|
5
|
-
//#region src/query-builders.ts
|
|
6
|
-
/**
|
|
7
|
-
* Case-insensitive LIKE/ILIKE for pattern matching.
|
|
8
|
-
* Uses ILIKE on PostgreSQL, LOWER()+LIKE on MySQL/SQLite.
|
|
9
|
-
*/
|
|
10
|
-
function insensitiveIlike(column, pattern, provider) {
|
|
11
|
-
return provider === "pg" ? ilike(column, pattern) : sql`LOWER(${column}) LIKE LOWER(${pattern})`;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Case-insensitive IN for string arrays.
|
|
15
|
-
*/
|
|
16
|
-
function insensitiveInArray(column, values) {
|
|
17
|
-
if (values.length === 0) return sql`false`;
|
|
18
|
-
return sql`LOWER(${column}) IN (${sql.join(values.map((v) => sql`LOWER(${v})`), sql`, `)})`;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Case-insensitive NOT IN for string arrays.
|
|
22
|
-
*/
|
|
23
|
-
function insensitiveNotInArray(column, values) {
|
|
24
|
-
if (values.length === 0) return sql`true`;
|
|
25
|
-
return sql`LOWER(${column}) NOT IN (${sql.join(values.map((v) => sql`LOWER(${v})`), sql`, `)})`;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Case-insensitive equality for strings.
|
|
29
|
-
*/
|
|
30
|
-
function insensitiveEq(column, value) {
|
|
31
|
-
return sql`LOWER(${column}) = LOWER(${value})`;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Case-insensitive inequality for strings.
|
|
35
|
-
*/
|
|
36
|
-
function insensitiveNe(column, value) {
|
|
37
|
-
return sql`LOWER(${column}) <> LOWER(${value})`;
|
|
38
|
-
}
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/drizzle-adapter.ts
|
|
41
|
-
const drizzleAdapter = (db, config) => {
|
|
42
|
-
let lazyOptions = null;
|
|
43
|
-
const createCustomAdapter = (db) => ({ getFieldName, getDefaultFieldName, options }) => {
|
|
44
|
-
function getSchema(model) {
|
|
45
|
-
const schema = config.schema || db._.fullSchema;
|
|
46
|
-
if (!schema) throw new BetterAuthError("Drizzle adapter failed to initialize. Schema not found. Please provide a schema object in the adapter options object.");
|
|
47
|
-
const schemaModel = schema[model];
|
|
48
|
-
if (!schemaModel) throw new BetterAuthError(`[# Drizzle Adapter]: The model "${model}" was not found in the schema object. Please pass the schema directly to the adapter options.`);
|
|
49
|
-
return schemaModel;
|
|
50
|
-
}
|
|
51
|
-
const withReturning = async (model, builder, data, where) => {
|
|
52
|
-
if (config.provider !== "mysql") return (await builder.returning())[0];
|
|
53
|
-
await builder.execute();
|
|
54
|
-
const schemaModel = getSchema(model);
|
|
55
|
-
const builderVal = builder.config?.values;
|
|
56
|
-
if (where?.length) {
|
|
57
|
-
const clause = convertWhereClause(where.map((w) => {
|
|
58
|
-
if (data[w.field] !== void 0) return {
|
|
59
|
-
...w,
|
|
60
|
-
value: data[w.field]
|
|
61
|
-
};
|
|
62
|
-
return w;
|
|
63
|
-
}), model);
|
|
64
|
-
return (await db.select().from(schemaModel).where(...clause))[0];
|
|
65
|
-
} else if (builderVal && builderVal[0]?.id?.value) {
|
|
66
|
-
let tId = builderVal[0]?.id?.value;
|
|
67
|
-
if (!tId) tId = (await db.select({ id: sql`LAST_INSERT_ID()` }).from(schemaModel).orderBy(desc(schemaModel.id)).limit(1))[0].id;
|
|
68
|
-
return (await db.select().from(schemaModel).where(eq(schemaModel.id, tId)).limit(1).execute())[0];
|
|
69
|
-
} else if (data.id) return (await db.select().from(schemaModel).where(eq(schemaModel.id, data.id)).limit(1).execute())[0];
|
|
70
|
-
else {
|
|
71
|
-
if (!("id" in schemaModel)) throw new BetterAuthError(`The model "${model}" does not have an "id" field. Please use the "id" field as your primary key.`);
|
|
72
|
-
return (await db.select().from(schemaModel).orderBy(desc(schemaModel.id)).limit(1).execute())[0];
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
function convertWhereClause(where, model) {
|
|
76
|
-
const schemaModel = getSchema(model);
|
|
77
|
-
if (!where) return [];
|
|
78
|
-
if (where.length === 1) {
|
|
79
|
-
const w = where[0];
|
|
80
|
-
if (!w) return [];
|
|
81
|
-
const field = getFieldName({
|
|
82
|
-
model,
|
|
83
|
-
field: w.field
|
|
84
|
-
});
|
|
85
|
-
if (!schemaModel[field]) throw new BetterAuthError(`The field "${w.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
|
|
86
|
-
const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
|
|
87
|
-
if (w.operator === "in") {
|
|
88
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
|
|
89
|
-
if (isInsensitive) return [insensitiveInArray(schemaModel[field], w.value)];
|
|
90
|
-
return [inArray(schemaModel[field], w.value)];
|
|
91
|
-
}
|
|
92
|
-
if (w.operator === "not_in") {
|
|
93
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
|
|
94
|
-
if (isInsensitive) return [insensitiveNotInArray(schemaModel[field], w.value)];
|
|
95
|
-
return [notInArray(schemaModel[field], w.value)];
|
|
96
|
-
}
|
|
97
|
-
if (w.operator === "contains") {
|
|
98
|
-
if (isInsensitive && typeof w.value === "string") return [insensitiveIlike(schemaModel[field], `%${w.value}%`, config.provider)];
|
|
99
|
-
return [like(schemaModel[field], `%${w.value}%`)];
|
|
100
|
-
}
|
|
101
|
-
if (w.operator === "starts_with") {
|
|
102
|
-
if (isInsensitive && typeof w.value === "string") return [insensitiveIlike(schemaModel[field], `${w.value}%`, config.provider)];
|
|
103
|
-
return [like(schemaModel[field], `${w.value}%`)];
|
|
104
|
-
}
|
|
105
|
-
if (w.operator === "ends_with") {
|
|
106
|
-
if (isInsensitive && typeof w.value === "string") return [insensitiveIlike(schemaModel[field], `%${w.value}`, config.provider)];
|
|
107
|
-
return [like(schemaModel[field], `%${w.value}`)];
|
|
108
|
-
}
|
|
109
|
-
if (w.operator === "lt") return [lt(schemaModel[field], w.value)];
|
|
110
|
-
if (w.operator === "lte") return [lte(schemaModel[field], w.value)];
|
|
111
|
-
if (w.operator === "ne") {
|
|
112
|
-
if (w.value === null) return [isNotNull(schemaModel[field])];
|
|
113
|
-
if (isInsensitive && typeof w.value === "string") return [insensitiveNe(schemaModel[field], w.value)];
|
|
114
|
-
return [ne(schemaModel[field], w.value)];
|
|
115
|
-
}
|
|
116
|
-
if (w.operator === "gt") return [gt(schemaModel[field], w.value)];
|
|
117
|
-
if (w.operator === "gte") return [gte(schemaModel[field], w.value)];
|
|
118
|
-
if (w.value === null) return [isNull(schemaModel[field])];
|
|
119
|
-
if (isInsensitive && typeof w.value === "string") return [insensitiveEq(schemaModel[field], w.value)];
|
|
120
|
-
return [eq(schemaModel[field], w.value)];
|
|
121
|
-
}
|
|
122
|
-
const andGroup = where.filter((w) => w.connector === "AND" || !w.connector);
|
|
123
|
-
const orGroup = where.filter((w) => w.connector === "OR");
|
|
124
|
-
const andClause = and(...andGroup.map((w) => {
|
|
125
|
-
const field = getFieldName({
|
|
126
|
-
model,
|
|
127
|
-
field: w.field
|
|
128
|
-
});
|
|
129
|
-
const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
|
|
130
|
-
if (w.operator === "in") {
|
|
131
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
|
|
132
|
-
if (isInsensitive) return insensitiveInArray(schemaModel[field], w.value);
|
|
133
|
-
return inArray(schemaModel[field], w.value);
|
|
134
|
-
}
|
|
135
|
-
if (w.operator === "not_in") {
|
|
136
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
|
|
137
|
-
if (isInsensitive) return insensitiveNotInArray(schemaModel[field], w.value);
|
|
138
|
-
return notInArray(schemaModel[field], w.value);
|
|
139
|
-
}
|
|
140
|
-
if (w.operator === "contains") {
|
|
141
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `%${w.value}%`, config.provider);
|
|
142
|
-
return like(schemaModel[field], `%${w.value}%`);
|
|
143
|
-
}
|
|
144
|
-
if (w.operator === "starts_with") {
|
|
145
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `${w.value}%`, config.provider);
|
|
146
|
-
return like(schemaModel[field], `${w.value}%`);
|
|
147
|
-
}
|
|
148
|
-
if (w.operator === "ends_with") {
|
|
149
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `%${w.value}`, config.provider);
|
|
150
|
-
return like(schemaModel[field], `%${w.value}`);
|
|
151
|
-
}
|
|
152
|
-
if (w.operator === "lt") return lt(schemaModel[field], w.value);
|
|
153
|
-
if (w.operator === "lte") return lte(schemaModel[field], w.value);
|
|
154
|
-
if (w.operator === "gt") return gt(schemaModel[field], w.value);
|
|
155
|
-
if (w.operator === "gte") return gte(schemaModel[field], w.value);
|
|
156
|
-
if (w.operator === "ne") {
|
|
157
|
-
if (w.value === null) return isNotNull(schemaModel[field]);
|
|
158
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveNe(schemaModel[field], w.value);
|
|
159
|
-
return ne(schemaModel[field], w.value);
|
|
160
|
-
}
|
|
161
|
-
if (w.value === null) return isNull(schemaModel[field]);
|
|
162
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveEq(schemaModel[field], w.value);
|
|
163
|
-
return eq(schemaModel[field], w.value);
|
|
164
|
-
}));
|
|
165
|
-
const orClause = or(...orGroup.map((w) => {
|
|
166
|
-
const field = getFieldName({
|
|
167
|
-
model,
|
|
168
|
-
field: w.field
|
|
169
|
-
});
|
|
170
|
-
if (!schemaModel[field]) throw new BetterAuthError(`The field "${w.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
|
|
171
|
-
const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
|
|
172
|
-
if (w.operator === "in") {
|
|
173
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
|
|
174
|
-
if (isInsensitive) return insensitiveInArray(schemaModel[field], w.value);
|
|
175
|
-
return inArray(schemaModel[field], w.value);
|
|
176
|
-
}
|
|
177
|
-
if (w.operator === "not_in") {
|
|
178
|
-
if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "not_in" operator.`);
|
|
179
|
-
if (isInsensitive) return insensitiveNotInArray(schemaModel[field], w.value);
|
|
180
|
-
return notInArray(schemaModel[field], w.value);
|
|
181
|
-
}
|
|
182
|
-
if (w.operator === "contains") {
|
|
183
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `%${w.value}%`, config.provider);
|
|
184
|
-
return like(schemaModel[field], `%${w.value}%`);
|
|
185
|
-
}
|
|
186
|
-
if (w.operator === "starts_with") {
|
|
187
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `${w.value}%`, config.provider);
|
|
188
|
-
return like(schemaModel[field], `${w.value}%`);
|
|
189
|
-
}
|
|
190
|
-
if (w.operator === "ends_with") {
|
|
191
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveIlike(schemaModel[field], `%${w.value}`, config.provider);
|
|
192
|
-
return like(schemaModel[field], `%${w.value}`);
|
|
193
|
-
}
|
|
194
|
-
if (w.operator === "lt") return lt(schemaModel[field], w.value);
|
|
195
|
-
if (w.operator === "lte") return lte(schemaModel[field], w.value);
|
|
196
|
-
if (w.operator === "gt") return gt(schemaModel[field], w.value);
|
|
197
|
-
if (w.operator === "gte") return gte(schemaModel[field], w.value);
|
|
198
|
-
if (w.operator === "ne") {
|
|
199
|
-
if (w.value === null) return isNotNull(schemaModel[field]);
|
|
200
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveNe(schemaModel[field], w.value);
|
|
201
|
-
return ne(schemaModel[field], w.value);
|
|
202
|
-
}
|
|
203
|
-
if (w.value === null) return isNull(schemaModel[field]);
|
|
204
|
-
if (isInsensitive && typeof w.value === "string") return insensitiveEq(schemaModel[field], w.value);
|
|
205
|
-
return eq(schemaModel[field], w.value);
|
|
206
|
-
}));
|
|
207
|
-
const clause = [];
|
|
208
|
-
if (andGroup.length) clause.push(andClause);
|
|
209
|
-
if (orGroup.length) clause.push(orClause);
|
|
210
|
-
return clause;
|
|
211
|
-
}
|
|
212
|
-
function checkMissingFields(schema, model, values) {
|
|
213
|
-
if (!schema) throw new BetterAuthError("Drizzle adapter failed to initialize. Drizzle Schema not found. Please provide a schema object in the adapter options object.");
|
|
214
|
-
for (const key in values) {
|
|
215
|
-
let fieldName;
|
|
216
|
-
try {
|
|
217
|
-
fieldName = getFieldName({
|
|
218
|
-
model,
|
|
219
|
-
field: key
|
|
220
|
-
});
|
|
221
|
-
} catch {
|
|
222
|
-
fieldName = key;
|
|
223
|
-
}
|
|
224
|
-
if (!schema[fieldName]) throw new BetterAuthError(`The field "${key}" does not exist in the "${model}" Drizzle schema. Please update your drizzle schema or re-generate using "npx auth@latest generate".`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Resolve the db.query key for a model.
|
|
229
|
-
*
|
|
230
|
-
* When `usePlural` is false (default), Better Auth uses singular model
|
|
231
|
-
* names like "user", but Drizzle's db.query is keyed by the schema
|
|
232
|
-
* export names (often plural like "users"). This function:
|
|
233
|
-
*
|
|
234
|
-
* 1. Tries the model name directly (works when schema keys match)
|
|
235
|
-
* 2. If usePlural is set, tries appending "s"
|
|
236
|
-
* 3. Falls back to scanning config.schema to find which db.query key
|
|
237
|
-
* corresponds to the same table object
|
|
238
|
-
*/
|
|
239
|
-
function getQueryModel(model) {
|
|
240
|
-
if (db.query[model]) return model;
|
|
241
|
-
if (config.usePlural) {
|
|
242
|
-
const plural = `${model}s`;
|
|
243
|
-
if (db.query[plural]) return plural;
|
|
244
|
-
}
|
|
245
|
-
if (config.schema) {
|
|
246
|
-
const targetTable = config.schema[model];
|
|
247
|
-
if (targetTable) {
|
|
248
|
-
const fullSchema = db._.fullSchema;
|
|
249
|
-
if (fullSchema) {
|
|
250
|
-
for (const key of Object.keys(db.query)) if (fullSchema[key] === targetTable) return key;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
async create({ model, data: values }) {
|
|
258
|
-
const schemaModel = getSchema(model);
|
|
259
|
-
checkMissingFields(schemaModel, model, values);
|
|
260
|
-
return await withReturning(model, db.insert(schemaModel).values(values), values);
|
|
261
|
-
},
|
|
262
|
-
async findOne({ model, where, select, join }) {
|
|
263
|
-
const schemaModel = getSchema(model);
|
|
264
|
-
const clause = convertWhereClause(where, model);
|
|
265
|
-
if (options.experimental?.joins) {
|
|
266
|
-
const queryModel = getQueryModel(model);
|
|
267
|
-
if (!db.query || !queryModel) {
|
|
268
|
-
logger.error(`[# Drizzle Adapter]: The model "${model}" was not found in the query object. Please update your Drizzle schema to include relations or re-generate using "npx auth@latest generate".`);
|
|
269
|
-
logger.info("Falling back to regular query");
|
|
270
|
-
} else {
|
|
271
|
-
let includes;
|
|
272
|
-
const pluralJoinResults = [];
|
|
273
|
-
if (join) {
|
|
274
|
-
includes = {};
|
|
275
|
-
const joinEntries = Object.entries(join);
|
|
276
|
-
for (const [model, joinAttr] of joinEntries) {
|
|
277
|
-
const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
|
|
278
|
-
const isUnique = joinAttr.relation === "one-to-one";
|
|
279
|
-
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
|
280
|
-
includes[`${model}${pluralSuffix}`] = isUnique ? true : { limit };
|
|
281
|
-
if (!isUnique) pluralJoinResults.push(`${model}${pluralSuffix}`);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
const res = await db.query[queryModel].findFirst({
|
|
285
|
-
where: clause[0],
|
|
286
|
-
columns: select?.length && select.length > 0 ? select.reduce((acc, field) => {
|
|
287
|
-
acc[getFieldName({
|
|
288
|
-
model,
|
|
289
|
-
field
|
|
290
|
-
})] = true;
|
|
291
|
-
return acc;
|
|
292
|
-
}, {}) : void 0,
|
|
293
|
-
with: includes
|
|
294
|
-
});
|
|
295
|
-
if (res) for (const pluralJoinResult of pluralJoinResults) {
|
|
296
|
-
const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
|
|
297
|
-
res[singularKey] = res[pluralJoinResult];
|
|
298
|
-
if (pluralJoinResult !== singularKey) delete res[pluralJoinResult];
|
|
299
|
-
}
|
|
300
|
-
return res;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
const res = await db.select(select?.length && select.length > 0 ? select.reduce((acc, field) => {
|
|
304
|
-
const fieldName = getFieldName({
|
|
305
|
-
model,
|
|
306
|
-
field
|
|
307
|
-
});
|
|
308
|
-
return {
|
|
309
|
-
...acc,
|
|
310
|
-
[fieldName]: schemaModel[fieldName]
|
|
311
|
-
};
|
|
312
|
-
}, {}) : void 0).from(schemaModel).where(...clause);
|
|
313
|
-
if (!res.length) return null;
|
|
314
|
-
return res[0];
|
|
315
|
-
},
|
|
316
|
-
async findMany({ model, where, sortBy, limit, select, offset, join }) {
|
|
317
|
-
const schemaModel = getSchema(model);
|
|
318
|
-
const clause = where ? convertWhereClause(where, model) : [];
|
|
319
|
-
const sortFn = sortBy?.direction === "desc" ? desc : asc;
|
|
320
|
-
if (options.experimental?.joins) {
|
|
321
|
-
const queryModel = getQueryModel(model);
|
|
322
|
-
if (!queryModel) {
|
|
323
|
-
logger.error(`[# Drizzle Adapter]: The model "${model}" was not found in the query object. Please update your Drizzle schema to include relations or re-generate using "npx auth@latest generate".`);
|
|
324
|
-
logger.info("Falling back to regular query");
|
|
325
|
-
} else {
|
|
326
|
-
let includes;
|
|
327
|
-
const pluralJoinResults = [];
|
|
328
|
-
if (join) {
|
|
329
|
-
includes = {};
|
|
330
|
-
const joinEntries = Object.entries(join);
|
|
331
|
-
for (const [model, joinAttr] of joinEntries) {
|
|
332
|
-
const isUnique = joinAttr.relation === "one-to-one";
|
|
333
|
-
const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
|
|
334
|
-
const pluralSuffix = isUnique || config.usePlural ? "" : "s";
|
|
335
|
-
includes[`${model}${pluralSuffix}`] = isUnique ? true : { limit };
|
|
336
|
-
if (!isUnique) pluralJoinResults.push(`${model}${pluralSuffix}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
let orderBy = void 0;
|
|
340
|
-
if (sortBy?.field) orderBy = [sortFn(schemaModel[getFieldName({
|
|
341
|
-
model,
|
|
342
|
-
field: sortBy?.field
|
|
343
|
-
})])];
|
|
344
|
-
const res = await db.query[queryModel].findMany({
|
|
345
|
-
where: clause[0],
|
|
346
|
-
with: includes,
|
|
347
|
-
columns: select?.length && select.length > 0 ? select.reduce((acc, field) => {
|
|
348
|
-
acc[getFieldName({
|
|
349
|
-
model,
|
|
350
|
-
field
|
|
351
|
-
})] = true;
|
|
352
|
-
return acc;
|
|
353
|
-
}, {}) : void 0,
|
|
354
|
-
limit: limit ?? 100,
|
|
355
|
-
offset: offset ?? 0,
|
|
356
|
-
orderBy
|
|
357
|
-
});
|
|
358
|
-
if (res) for (const item of res) for (const pluralJoinResult of pluralJoinResults) {
|
|
359
|
-
const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
|
|
360
|
-
if (singularKey === pluralJoinResult) continue;
|
|
361
|
-
item[singularKey] = item[pluralJoinResult];
|
|
362
|
-
delete item[pluralJoinResult];
|
|
363
|
-
}
|
|
364
|
-
return res;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
let builder = db.select(select?.length && select.length > 0 ? select.reduce((acc, field) => {
|
|
368
|
-
const fieldName = getFieldName({
|
|
369
|
-
model,
|
|
370
|
-
field
|
|
371
|
-
});
|
|
372
|
-
return {
|
|
373
|
-
...acc,
|
|
374
|
-
[fieldName]: schemaModel[fieldName]
|
|
375
|
-
};
|
|
376
|
-
}, {}) : void 0).from(schemaModel);
|
|
377
|
-
const effectiveLimit = limit;
|
|
378
|
-
const effectiveOffset = offset;
|
|
379
|
-
if (typeof effectiveLimit !== "undefined") builder = builder.limit(effectiveLimit);
|
|
380
|
-
if (typeof effectiveOffset !== "undefined") builder = builder.offset(effectiveOffset);
|
|
381
|
-
if (sortBy?.field) builder = builder.orderBy(sortFn(schemaModel[getFieldName({
|
|
382
|
-
model,
|
|
383
|
-
field: sortBy?.field
|
|
384
|
-
})]));
|
|
385
|
-
return await builder.where(...clause);
|
|
386
|
-
},
|
|
387
|
-
async count({ model, where }) {
|
|
388
|
-
const schemaModel = getSchema(model);
|
|
389
|
-
const clause = where ? convertWhereClause(where, model) : [];
|
|
390
|
-
return (await db.select({ count: count() }).from(schemaModel).where(...clause))[0].count;
|
|
391
|
-
},
|
|
392
|
-
async update({ model, where, update: values }) {
|
|
393
|
-
const schemaModel = getSchema(model);
|
|
394
|
-
const clause = convertWhereClause(where, model);
|
|
395
|
-
return await withReturning(model, db.update(schemaModel).set(values).where(...clause), values, where);
|
|
396
|
-
},
|
|
397
|
-
async updateMany({ model, where, update: values }) {
|
|
398
|
-
const schemaModel = getSchema(model);
|
|
399
|
-
const clause = convertWhereClause(where, model);
|
|
400
|
-
return await db.update(schemaModel).set(values).where(...clause);
|
|
401
|
-
},
|
|
402
|
-
async delete({ model, where }) {
|
|
403
|
-
const schemaModel = getSchema(model);
|
|
404
|
-
const clause = convertWhereClause(where, model);
|
|
405
|
-
return await db.delete(schemaModel).where(...clause);
|
|
406
|
-
},
|
|
407
|
-
async deleteMany({ model, where }) {
|
|
408
|
-
const schemaModel = getSchema(model);
|
|
409
|
-
const clause = convertWhereClause(where, model);
|
|
410
|
-
const res = await db.delete(schemaModel).where(...clause);
|
|
411
|
-
let count = 0;
|
|
412
|
-
if (res && "rowCount" in res) count = res.rowCount;
|
|
413
|
-
else if (Array.isArray(res)) count = res.length;
|
|
414
|
-
else if (res && ("affectedRows" in res || "rowsAffected" in res || "changes" in res)) count = res.affectedRows ?? res.rowsAffected ?? res.changes;
|
|
415
|
-
if (typeof count !== "number") logger.error("[Drizzle Adapter] The result of the deleteMany operation is not a number. This is likely a bug in the adapter. Please report this issue to the Better Auth team.", {
|
|
416
|
-
res,
|
|
417
|
-
model,
|
|
418
|
-
where
|
|
419
|
-
});
|
|
420
|
-
return count;
|
|
421
|
-
},
|
|
422
|
-
options: config
|
|
423
|
-
};
|
|
424
|
-
};
|
|
425
|
-
let adapterOptions = null;
|
|
426
|
-
adapterOptions = {
|
|
427
|
-
config: {
|
|
428
|
-
adapterId: "drizzle",
|
|
429
|
-
adapterName: "Drizzle Adapter",
|
|
430
|
-
usePlural: config.usePlural ?? false,
|
|
431
|
-
debugLogs: config.debugLogs ?? false,
|
|
432
|
-
supportsUUIDs: config.provider === "pg" ? true : false,
|
|
433
|
-
supportsJSON: config.provider === "pg" ? true : false,
|
|
434
|
-
supportsArrays: config.provider === "pg" ? true : false,
|
|
435
|
-
customTransformOutput: ({ data, fieldAttributes }) => {
|
|
436
|
-
if (fieldAttributes.type === "date") {
|
|
437
|
-
if (data === null || data === void 0) return data;
|
|
438
|
-
return new Date(data);
|
|
439
|
-
}
|
|
440
|
-
return data;
|
|
441
|
-
},
|
|
442
|
-
transaction: config.transaction ?? false ? (cb) => db.transaction((tx) => {
|
|
443
|
-
return cb(createAdapterFactory({
|
|
444
|
-
config: adapterOptions.config,
|
|
445
|
-
adapter: createCustomAdapter(tx)
|
|
446
|
-
})(lazyOptions));
|
|
447
|
-
}) : false
|
|
448
|
-
},
|
|
449
|
-
adapter: createCustomAdapter(db)
|
|
450
|
-
};
|
|
451
|
-
const adapter = createAdapterFactory(adapterOptions);
|
|
452
|
-
return (options) => {
|
|
453
|
-
lazyOptions = options;
|
|
454
|
-
return adapter(options);
|
|
455
|
-
};
|
|
456
|
-
};
|
|
457
|
-
//#endregion
|
|
458
|
-
export { drizzleAdapter };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@better-auth/drizzle-adapter",
|
|
3
|
-
"version": "1.6.7",
|
|
4
|
-
"description": "Drizzle adapter for Better Auth",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"homepage": "https://www.better-auth.com/docs/adapters/drizzle",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/better-auth/better-auth.git",
|
|
11
|
-
"directory": "packages/drizzle-adapter"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"auth",
|
|
15
|
-
"drizzle",
|
|
16
|
-
"adapter",
|
|
17
|
-
"typescript",
|
|
18
|
-
"better-auth"
|
|
19
|
-
],
|
|
20
|
-
"publishConfig": {
|
|
21
|
-
"access": "public"
|
|
22
|
-
},
|
|
23
|
-
"sideEffects": false,
|
|
24
|
-
"files": [
|
|
25
|
-
"dist"
|
|
26
|
-
],
|
|
27
|
-
"main": "./dist/index.mjs",
|
|
28
|
-
"module": "./dist/index.mjs",
|
|
29
|
-
"types": "./dist/index.d.mts",
|
|
30
|
-
"exports": {
|
|
31
|
-
".": {
|
|
32
|
-
"dev-source": "./src/index.ts",
|
|
33
|
-
"types": "./dist/index.d.mts",
|
|
34
|
-
"default": "./dist/index.mjs"
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"peerDependencies": {
|
|
38
|
-
"@better-auth/utils": "0.4.0",
|
|
39
|
-
"drizzle-orm": "^0.45.2",
|
|
40
|
-
"@better-auth/core": "^1.6.7"
|
|
41
|
-
},
|
|
42
|
-
"peerDependenciesMeta": {
|
|
43
|
-
"drizzle-orm": {
|
|
44
|
-
"optional": true
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@better-auth/utils": "0.4.0",
|
|
49
|
-
"drizzle-orm": "^0.45.2",
|
|
50
|
-
"tsdown": "0.21.1",
|
|
51
|
-
"typescript": "^5.9.3",
|
|
52
|
-
"@better-auth/core": "1.6.7"
|
|
53
|
-
},
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "tsdown",
|
|
56
|
-
"dev": "tsdown --watch",
|
|
57
|
-
"lint:package": "publint run --strict --pack false",
|
|
58
|
-
"lint:types": "attw --profile esm-only --pack .",
|
|
59
|
-
"typecheck": "tsc --noEmit",
|
|
60
|
-
"test": "vitest"
|
|
61
|
-
}
|
|
62
|
-
}
|