@better-auth/drizzle-adapter 1.7.0 → 1.7.2

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/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { a as insensitiveNe, i as insensitiveInArray, n as insensitiveEq, o as insensitiveNotInArray, r as insensitiveIlike } from "./query-builders-D2eE7gbx.mjs";
1
+ import { a as insensitiveNe, c as getOneToOneRelationKey, i as insensitiveInArray, n as insensitiveEq, o as insensitiveNotInArray, r as insensitiveIlike, s as buildRelationKeysByModel } from "./query-builders-CBLMSM7v.mjs";
2
2
  import { createAdapterFactory } from "@better-auth/core/db/adapter";
3
3
  import { logger } from "@better-auth/core/env";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
5
- import { and, asc, count, desc, eq, gt, gte, inArray, isNotNull, isNull, like, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
5
+ import { Column, and, asc, count, desc, eq, gt, gte, inArray, is, isNotNull, isNull, like, lt, lte, ne, notInArray, or, sql } from "drizzle-orm";
6
6
  //#region src/drizzle-adapter.ts
7
7
  /**
8
8
  * Derive the number of affected rows from a Drizzle write result.
@@ -48,6 +48,7 @@ function hasDriverRowCount(result) {
48
48
  const drizzleAdapter = (db, config) => {
49
49
  let lazyOptions = null;
50
50
  let mysqlNoIdWarned = false;
51
+ const relationKeysByModel = buildRelationKeysByModel(db._?.schema);
51
52
  const createCustomAdapter = (db, inTransaction = false) => ({ getFieldName, getDefaultFieldName, getDefaultModelName, options, schema: baSchema }) => {
52
53
  if (config.provider === "mysql" && options.advanced?.database?.generateId === false && !mysqlNoIdWarned) {
53
54
  mysqlNoIdWarned = true;
@@ -115,15 +116,19 @@ const drizzleAdapter = (db, config) => {
115
116
  };
116
117
  function convertWhereClause(where, model) {
117
118
  const schemaModel = getSchema(model);
119
+ const resolveFieldName = (where) => {
120
+ const field = getFieldName({
121
+ model,
122
+ field: where.field
123
+ });
124
+ if (!is(schemaModel[field], Column)) throw new BetterAuthError(`The field "${where.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
125
+ return field;
126
+ };
118
127
  if (!where) return [];
119
128
  if (where.length === 1) {
120
129
  const w = where[0];
121
130
  if (!w) return [];
122
- const field = getFieldName({
123
- model,
124
- field: w.field
125
- });
126
- if (!schemaModel[field]) throw new BetterAuthError(`The field "${w.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
131
+ const field = resolveFieldName(w);
127
132
  const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
128
133
  if (w.operator === "in") {
129
134
  if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
@@ -163,10 +168,7 @@ const drizzleAdapter = (db, config) => {
163
168
  const andGroup = where.filter((w) => w.connector === "AND" || !w.connector);
164
169
  const orGroup = where.filter((w) => w.connector === "OR");
165
170
  const andClause = and(...andGroup.map((w) => {
166
- const field = getFieldName({
167
- model,
168
- field: w.field
169
- });
171
+ const field = resolveFieldName(w);
170
172
  const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
171
173
  if (w.operator === "in") {
172
174
  if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
@@ -204,11 +206,7 @@ const drizzleAdapter = (db, config) => {
204
206
  return eq(schemaModel[field], w.value);
205
207
  }));
206
208
  const orClause = or(...orGroup.map((w) => {
207
- const field = getFieldName({
208
- model,
209
- field: w.field
210
- });
211
- if (!schemaModel[field]) throw new BetterAuthError(`The field "${w.field}" does not exist in the schema for the model "${model}". Please update your schema.`);
209
+ const field = resolveFieldName(w);
212
210
  const isInsensitive = (w.mode ?? "sensitive") === "insensitive" && (typeof w.value === "string" || Array.isArray(w.value) && w.value.every((v) => typeof v === "string"));
213
211
  if (w.operator === "in") {
214
212
  if (!Array.isArray(w.value)) throw new BetterAuthError(`The value for the field "${w.field}" must be an array when using the "in" operator.`);
@@ -295,6 +293,16 @@ const drizzleAdapter = (db, config) => {
295
293
  }
296
294
  return null;
297
295
  }
296
+ function getJoinRelationKey(baseModel, joinModel, relationKeys, isUnique) {
297
+ if (isUnique) return getOneToOneRelationKey({
298
+ baseModel,
299
+ joinModel,
300
+ relationKeys,
301
+ schema: baSchema,
302
+ getDefaultModelName
303
+ });
304
+ return config.usePlural ? joinModel : `${joinModel}s`;
305
+ }
298
306
  return {
299
307
  async create({ model, data: values }) {
300
308
  const schemaModel = getSchema(model);
@@ -311,15 +319,19 @@ const drizzleAdapter = (db, config) => {
311
319
  logger.info("Falling back to regular query");
312
320
  } else {
313
321
  let includes;
314
- const pluralJoinResults = [];
322
+ const renamedJoinResults = [];
323
+ const relationKeys = relationKeysByModel.get(queryModel);
315
324
  includes = {};
316
325
  const joinEntries = Object.entries(join);
317
- for (const [model, joinAttr] of joinEntries) {
326
+ for (const [joinModel, joinAttr] of joinEntries) {
318
327
  const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
319
328
  const isUnique = joinAttr.relation === "one-to-one";
320
- const pluralSuffix = isUnique || config.usePlural ? "" : "s";
321
- includes[`${model}${pluralSuffix}`] = isUnique ? true : { limit };
322
- if (!isUnique) pluralJoinResults.push(`${model}${pluralSuffix}`);
329
+ const relationKey = getJoinRelationKey(model, joinModel, relationKeys, isUnique);
330
+ includes[relationKey] = isUnique ? true : { limit };
331
+ if (relationKey !== joinModel) renamedJoinResults.push({
332
+ key: relationKey,
333
+ target: joinModel
334
+ });
323
335
  }
324
336
  const res = await db.query[queryModel].findFirst({
325
337
  where: clause[0],
@@ -332,10 +344,9 @@ const drizzleAdapter = (db, config) => {
332
344
  }, {}) : void 0,
333
345
  with: includes
334
346
  });
335
- if (res) for (const pluralJoinResult of pluralJoinResults) {
336
- const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
337
- res[singularKey] = res[pluralJoinResult];
338
- if (pluralJoinResult !== singularKey) delete res[pluralJoinResult];
347
+ if (res) for (const { key, target } of renamedJoinResults) {
348
+ res[target] = res[key];
349
+ delete res[key];
339
350
  }
340
351
  return res;
341
352
  }
@@ -364,15 +375,19 @@ const drizzleAdapter = (db, config) => {
364
375
  logger.info("Falling back to regular query");
365
376
  } else {
366
377
  let includes;
367
- const pluralJoinResults = [];
378
+ const renamedJoinResults = [];
379
+ const relationKeys = relationKeysByModel.get(queryModel);
368
380
  includes = {};
369
381
  const joinEntries = Object.entries(join);
370
- for (const [model, joinAttr] of joinEntries) {
382
+ for (const [joinModel, joinAttr] of joinEntries) {
371
383
  const isUnique = joinAttr.relation === "one-to-one";
372
384
  const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
373
- const pluralSuffix = isUnique || config.usePlural ? "" : "s";
374
- includes[`${model}${pluralSuffix}`] = isUnique ? true : { limit };
375
- if (!isUnique) pluralJoinResults.push(`${model}${pluralSuffix}`);
385
+ const relationKey = getJoinRelationKey(model, joinModel, relationKeys, isUnique);
386
+ includes[relationKey] = isUnique ? true : { limit };
387
+ if (relationKey !== joinModel) renamedJoinResults.push({
388
+ key: relationKey,
389
+ target: joinModel
390
+ });
376
391
  }
377
392
  let orderBy = void 0;
378
393
  if (sortBy?.field) orderBy = [sortFn(schemaModel[getFieldName({
@@ -393,11 +408,9 @@ const drizzleAdapter = (db, config) => {
393
408
  offset: offset ?? 0,
394
409
  orderBy
395
410
  });
396
- if (res) for (const item of res) for (const pluralJoinResult of pluralJoinResults) {
397
- const singularKey = !config.usePlural ? pluralJoinResult.slice(0, -1) : pluralJoinResult;
398
- if (singularKey === pluralJoinResult) continue;
399
- item[singularKey] = item[pluralJoinResult];
400
- delete item[pluralJoinResult];
411
+ if (res) for (const item of res) for (const { key, target } of renamedJoinResults) {
412
+ item[target] = item[key];
413
+ delete item[key];
401
414
  }
402
415
  return res;
403
416
  }
@@ -1,4 +1,30 @@
1
1
  import { ilike, sql } from "drizzle-orm";
2
+ //#region src/join-relation-key.ts
3
+ /**
4
+ * Reads the relation registry used to build Drizzle relational queries.
5
+ *
6
+ * - Drizzle 0.x (Relations v1): `db._.schema`
7
+ * - Drizzle 1.x (Relations v2): `db._.relations`
8
+ */
9
+ function buildRelationKeysByModel(relationRegistry) {
10
+ const relationKeysByModel = /* @__PURE__ */ new Map();
11
+ for (const [model, tableMetadata] of Object.entries(relationRegistry ?? {})) {
12
+ if (!tableMetadata.relations) continue;
13
+ relationKeysByModel.set(model, new Set(Object.keys(tableMetadata.relations)));
14
+ }
15
+ return relationKeysByModel;
16
+ }
17
+ function getOneToOneRelationKey({ baseModel, joinModel, relationKeys, schema, getDefaultModelName }) {
18
+ const defaultBaseModelName = getDefaultModelName(baseModel);
19
+ const defaultJoinModelName = getDefaultModelName(joinModel);
20
+ const joinModelFields = schema[defaultJoinModelName]?.fields ?? {};
21
+ const generatedRelationKey = Object.values(joinModelFields).some((field) => field.references && getDefaultModelName(field.references.model) === defaultBaseModelName) ? joinModel : schema[defaultJoinModelName]?.modelName ?? defaultJoinModelName;
22
+ if (!relationKeys?.size) return joinModel;
23
+ if (relationKeys.has(generatedRelationKey)) return generatedRelationKey;
24
+ if (relationKeys.has(joinModel)) return joinModel;
25
+ return generatedRelationKey;
26
+ }
27
+ //#endregion
2
28
  //#region src/query-builders.ts
3
29
  /**
4
30
  * Case-insensitive LIKE/ILIKE for pattern matching.
@@ -49,4 +75,4 @@ function insensitiveNe(column, value) {
49
75
  return sql`LOWER(${column}) <> LOWER(${value})`;
50
76
  }
51
77
  //#endregion
52
- export { insensitiveNe as a, insensitiveInArray as i, insensitiveEq as n, insensitiveNotInArray as o, insensitiveIlike as r, escapedLike as t };
78
+ export { insensitiveNe as a, getOneToOneRelationKey as c, insensitiveInArray as i, insensitiveEq as n, insensitiveNotInArray as o, insensitiveIlike as r, buildRelationKeysByModel as s, escapedLike as t };
@@ -1,4 +1,4 @@
1
- import { a as insensitiveNe, i as insensitiveInArray, n as insensitiveEq, o as insensitiveNotInArray, t as escapedLike } from "../query-builders-D2eE7gbx.mjs";
1
+ import { a as insensitiveNe, c as getOneToOneRelationKey, i as insensitiveInArray, n as insensitiveEq, o as insensitiveNotInArray, s as buildRelationKeysByModel, t as escapedLike } from "../query-builders-CBLMSM7v.mjs";
2
2
  import { createAdapterFactory } from "@better-auth/core/db/adapter";
3
3
  import { logger } from "@better-auth/core/env";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
@@ -72,15 +72,14 @@ function applyWhereOperator(column, w, fieldLabel, provider) {
72
72
  const drizzleAdapter = (db, config) => {
73
73
  let lazyOptions = null;
74
74
  let mysqlNoIdWarned = false;
75
+ const relationKeysByModel = buildRelationKeysByModel(db._?.relations);
75
76
  const createCustomAdapter = (db, inTransaction = false) => ({ getFieldName, getDefaultModelName, options, schema: baSchema }) => {
76
77
  if (config.provider === "mysql" && options.advanced?.database?.generateId === false && !mysqlNoIdWarned) {
77
78
  mysqlNoIdWarned = true;
78
79
  logger.warn("[Drizzle Adapter] MySQL does not support INSERT...RETURNING. With generateId set to false, the adapter uses best-effort fallback strategies (unique columns, full-field match) to retrieve inserted rows. For reliable behavior, use Better Auth's default ID generation, a custom generateId function, or generateId: \"serial\" for auto-increment.");
79
80
  }
80
81
  function getSchema(model) {
81
- const schema = config.schema || db._.fullSchema;
82
- if (!schema) throw new BetterAuthError("Drizzle adapter failed to initialize. Schema not found. Please provide a schema object in the adapter options object.");
83
- const schemaModel = schema[model];
82
+ const schemaModel = config.schema?.[model] ?? db._?.relations?.[model]?.table ?? db._?.fullSchema?.[model];
84
83
  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.`);
85
84
  return schemaModel;
86
85
  }
@@ -109,14 +108,16 @@ const drizzleAdapter = (db, config) => {
109
108
  }
110
109
  return null;
111
110
  }
112
- /**
113
- * Mirror the schema generator's relation-key naming. One-to-one keeps
114
- * the singular model name. One-to-many is pluralized unless the model
115
- * already ends in "s" or `usePlural` keeps the schema keys as-is.
116
- */
117
- function getJoinRelationKey(model, isUnique) {
118
- if (isUnique || config.usePlural || model.endsWith("s")) return model;
119
- return `${model}s`;
111
+ function getJoinRelationKey(baseModel, joinModel, relationKeys, isUnique) {
112
+ if (isUnique) return getOneToOneRelationKey({
113
+ baseModel,
114
+ joinModel,
115
+ relationKeys,
116
+ schema: baSchema,
117
+ getDefaultModelName
118
+ });
119
+ if (config.usePlural || joinModel.endsWith("s")) return joinModel;
120
+ return `${joinModel}s`;
120
121
  }
121
122
  const withReturning = async (model, builder, data, where) => {
122
123
  if (config.provider !== "mysql") return (await builder.returning())[0];
@@ -290,17 +291,18 @@ const drizzleAdapter = (db, config) => {
290
291
  logger.info("Falling back to regular query");
291
292
  } else {
292
293
  let includes;
293
- const pluralJoinResults = [];
294
+ const renamedJoinResults = [];
295
+ const relationKeys = relationKeysByModel.get(queryModel);
294
296
  includes = {};
295
297
  const joinEntries = Object.entries(join);
296
- for (const [model, joinAttr] of joinEntries) {
298
+ for (const [joinModel, joinAttr] of joinEntries) {
297
299
  const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
298
300
  const isUnique = joinAttr.relation === "one-to-one";
299
- const relationKey = getJoinRelationKey(model, isUnique);
301
+ const relationKey = getJoinRelationKey(model, joinModel, relationKeys, isUnique);
300
302
  includes[relationKey] = isUnique ? true : { limit };
301
- if (!isUnique) pluralJoinResults.push({
303
+ if (relationKey !== joinModel) renamedJoinResults.push({
302
304
  key: relationKey,
303
- target: model
305
+ target: joinModel
304
306
  });
305
307
  }
306
308
  const clause = convertNewWhereClause(where, model);
@@ -315,8 +317,7 @@ const drizzleAdapter = (db, config) => {
315
317
  }, {}) : void 0,
316
318
  with: includes
317
319
  });
318
- if (res) for (const { key, target } of pluralJoinResults) {
319
- if (key === target) continue;
320
+ if (res) for (const { key, target } of renamedJoinResults) {
320
321
  res[target] = res[key];
321
322
  delete res[key];
322
323
  }
@@ -347,17 +348,18 @@ const drizzleAdapter = (db, config) => {
347
348
  logger.info("Falling back to regular query");
348
349
  } else {
349
350
  let includes;
350
- const pluralJoinResults = [];
351
+ const renamedJoinResults = [];
352
+ const relationKeys = relationKeysByModel.get(queryModel);
351
353
  includes = {};
352
354
  const joinEntries = Object.entries(join);
353
- for (const [model, joinAttr] of joinEntries) {
355
+ for (const [joinModel, joinAttr] of joinEntries) {
354
356
  const isUnique = joinAttr.relation === "one-to-one";
355
357
  const limit = joinAttr.limit ?? options.advanced?.database?.defaultFindManyLimit ?? 100;
356
- const relationKey = getJoinRelationKey(model, isUnique);
358
+ const relationKey = getJoinRelationKey(model, joinModel, relationKeys, isUnique);
357
359
  includes[relationKey] = isUnique ? true : { limit };
358
- if (!isUnique) pluralJoinResults.push({
360
+ if (relationKey !== joinModel) renamedJoinResults.push({
359
361
  key: relationKey,
360
- target: model
362
+ target: joinModel
361
363
  });
362
364
  }
363
365
  let orderBy = void 0;
@@ -379,8 +381,7 @@ const drizzleAdapter = (db, config) => {
379
381
  offset: offset ?? 0,
380
382
  orderBy
381
383
  });
382
- if (res) for (const item of res) for (const { key, target } of pluralJoinResults) {
383
- if (key === target) continue;
384
+ if (res) for (const item of res) for (const { key, target } of renamedJoinResults) {
384
385
  item[target] = item[key];
385
386
  delete item[key];
386
387
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/drizzle-adapter",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "bugs": {
5
5
  "url": "https://github.com/better-auth/better-auth/issues"
6
6
  },
@@ -45,7 +45,7 @@
45
45
  "peerDependencies": {
46
46
  "@better-auth/utils": "0.4.2",
47
47
  "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0",
48
- "@better-auth/core": "^1.7.0"
48
+ "@better-auth/core": "^1.7.2"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "drizzle-orm": {
@@ -57,7 +57,7 @@
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "tsdown": "0.21.10",
59
59
  "typescript": "^6.0.3",
60
- "@better-auth/core": "1.7.0"
60
+ "@better-auth/core": "1.7.2"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsdown",