auth 1.6.22 → 1.6.24

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/api.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { DBAdapter, DBAdapter as DBAdapter$1 } from "@better-auth/core/db/adapter";
2
2
  import { BetterAuthOptions } from "@better-auth/core";
3
-
4
3
  //#region src/generators/types.d.ts
5
4
  interface SchemaGeneratorResult {
6
5
  code?: string;
package/dist/api.mjs CHANGED
@@ -125,15 +125,10 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
125
125
  name: `${modelName}_${fieldName}_idx`,
126
126
  on: fieldName
127
127
  });
128
- else if (attr.index && attr.unique) indexes.push({
129
- type: "uniqueIndex",
130
- name: `${modelName}_${fieldName}_uidx`,
131
- on: fieldName
132
- });
133
128
  if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
134
129
  if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
135
130
  else type += `.defaultNow()`;
136
- } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
131
+ } else if (typeof attr.defaultValue === "string") type += `.default(${JSON.stringify(attr.defaultValue)})`;
137
132
  else if (Array.isArray(attr.defaultValue)) {
138
133
  const elements = attr.defaultValue.map((value) => JSON.stringify(value)).join(", ");
139
134
  type += `.default([${elements}])`;
@@ -157,11 +152,24 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
157
152
  const modelName = getModelName(tableKey);
158
153
  const oneRelations = [];
159
154
  const manyRelations = [];
160
- const manyRelationsSet = /* @__PURE__ */ new Set();
161
155
  const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
156
+ const foreignFieldCounts = /* @__PURE__ */ new Map();
157
+ for (const [_, field] of foreignFields) {
158
+ const referencedModel = getModelName(field.references.model);
159
+ foreignFieldCounts.set(referencedModel, (foreignFieldCounts.get(referencedModel) ?? 0) + 1);
160
+ }
161
+ const usedOneRelationKeys = /* @__PURE__ */ new Set();
162
162
  for (const [fieldName, field] of foreignFields) {
163
163
  const referencedModel = field.references.model;
164
- const relationKey = getModelName(referencedModel);
164
+ const hasMultipleRelations = (foreignFieldCounts.get(getModelName(referencedModel)) ?? 0) > 1;
165
+ let relationKey = hasMultipleRelations ? fieldName.replace(/Id$/, "") : getModelName(referencedModel);
166
+ if (usedOneRelationKeys.has(relationKey)) relationKey = fieldName;
167
+ if (usedOneRelationKeys.has(relationKey)) {
168
+ let suffix = 2;
169
+ while (usedOneRelationKeys.has(`${relationKey}_${suffix}`)) suffix++;
170
+ relationKey = `${relationKey}_${suffix}`;
171
+ }
172
+ usedOneRelationKeys.add(relationKey);
165
173
  const fieldRef = `${getModelName(tableKey)}.${getFieldName({
166
174
  model: tableKey,
167
175
  field: fieldName
@@ -174,81 +182,49 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
174
182
  key: relationKey,
175
183
  model: getModelName(referencedModel),
176
184
  type: "one",
185
+ relationName: hasMultipleRelations ? `${getModelName(tableKey)}_${fieldName}` : void 0,
177
186
  reference: {
178
187
  field: fieldRef,
179
- references: referenceRef,
180
- fieldName
188
+ references: referenceRef
181
189
  }
182
190
  });
183
191
  }
184
192
  const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
185
- const modelRelationsMap = /* @__PURE__ */ new Map();
186
193
  for (const [modelName, otherTable] of otherModels) {
187
194
  const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
188
195
  if (foreignKeysPointingHere.length === 0) continue;
189
- const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
190
- const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
191
- modelRelationsMap.set(modelName, {
192
- modelName,
193
- hasUnique,
194
- hasMany
195
- });
196
- }
197
- for (const { modelName, hasMany } of modelRelationsMap.values()) {
198
- const relationType = hasMany ? "many" : "one";
199
- let relationKey = getModelName(modelName);
200
- if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
201
- if (!manyRelationsSet.has(relationKey)) {
202
- manyRelationsSet.add(relationKey);
196
+ for (const [fieldName, field] of foreignKeysPointingHere) {
197
+ const relationType = field.unique ? "one" : "many";
198
+ let relationKey = getModelName(modelName);
199
+ if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
200
+ const hasMultipleRelations = foreignKeysPointingHere.length > 1;
201
+ if (hasMultipleRelations) relationKey = `${relationKey}By${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
203
202
  manyRelations.push({
204
203
  key: relationKey,
205
204
  model: getModelName(modelName),
206
- type: relationType
205
+ type: relationType,
206
+ relationName: hasMultipleRelations ? `${getModelName(modelName)}_${fieldName}` : void 0
207
207
  });
208
208
  }
209
209
  }
210
- const relationsByModel = /* @__PURE__ */ new Map();
211
- for (const relation of oneRelations) if (relation.reference) {
212
- const modelKey = relation.key;
213
- if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
214
- relationsByModel.get(modelKey).push(relation);
215
- }
216
- const duplicateRelations = [];
217
- const singleRelations = [];
218
- for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
219
- else singleRelations.push(relations[0]);
220
- for (const relation of duplicateRelations) if (relation.reference) {
221
- const fieldName = relation.reference.fieldName;
222
- const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
223
- ${relation.key}: one(${relation.model}, {
210
+ const hasForwardOne = oneRelations.length > 0;
211
+ const hasReverseOne = manyRelations.some((relation) => relation.type === "one");
212
+ const hasReverseMany = manyRelations.some((relation) => relation.type === "many");
213
+ const hasOne = hasForwardOne || hasReverseOne;
214
+ const hasMany = hasReverseMany;
215
+ const renderOneRelation = (relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
224
216
  fields: [${relation.reference.field}],
225
217
  references: [${relation.reference.references}],
226
- })
227
- }))`;
228
- relationsString += `\n${tableRelation}\n`;
229
- }
230
- const hasOne = singleRelations.length > 0;
231
- const hasMany = manyRelations.length > 0;
232
- if (hasOne && hasMany) {
233
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
234
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
235
- fields: [${relation.reference.field}],
236
- references: [${relation.reference.references}],
237
- })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
238
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
239
- }))`;
240
- relationsString += `\n${tableRelation}\n`;
241
- } else if (hasOne) {
242
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
243
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
244
- fields: [${relation.reference.field}],
245
- references: [${relation.reference.references}],
246
- })` : "").filter((x) => x !== "").join(",\n ")}
247
- }))`;
248
- relationsString += `\n${tableRelation}\n`;
249
- } else if (hasMany) {
250
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
251
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
218
+ ${relation.relationName ? `relationName: "${relation.relationName}",` : ""}
219
+ })` : "";
220
+ const renderReverseRelation = ({ key, model, type, relationName }) => {
221
+ return ` ${key}: ${type === "one" ? "one" : "many"}(${model}${relationName ? `, { relationName: "${relationName}" }` : ""})`;
222
+ };
223
+ if (hasOne || hasMany) {
224
+ const helpers = [hasOne ? "one" : null, hasMany ? "many" : null].filter(Boolean).join(", ");
225
+ const relationEntries = [...oneRelations.map(renderOneRelation).filter((x) => x !== ""), ...manyRelations.map(renderReverseRelation)].join(",\n ");
226
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ ${helpers} }) => ({
227
+ ${relationEntries}
252
228
  }))`;
253
229
  relationsString += `\n${tableRelation}\n`;
254
230
  }
@@ -294,10 +270,7 @@ function generateImport({ databaseType, tables, options }) {
294
270
  if (databaseType === "mysql") coreImports.push("json");
295
271
  }
296
272
  if (databaseType === "sqlite" && Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.type === "date" && field.defaultValue && typeof field.defaultValue === "function" && field.defaultValue.toString().includes("new Date()")))) rootImports.push("sql");
297
- const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
298
- const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
299
- if (hasIndexes) coreImports.push("index");
300
- if (hasUniqueIndexes) coreImports.push("uniqueIndex");
273
+ if (Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique))) coreImports.push("index");
301
274
  return `${rootImports.length > 0 ? `import { ${rootImports.join(", ")} } from "drizzle-orm";\n` : ""}import { ${coreImports.map((x) => x.trim()).filter((x) => x !== "").join(", ")} } from "drizzle-orm/${databaseType}-core";\n`;
302
275
  }
303
276
  //#endregion
@@ -333,7 +306,8 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
333
306
  const provider = adapter.options?.provider || "postgresql";
334
307
  const tables = getAuthTables(options);
335
308
  const filePath = file || "./prisma/schema.prisma";
336
- const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
309
+ const resolvedFilePath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
310
+ const schemaPrismaExist = existsSync(resolvedFilePath);
337
311
  const getModelName = initGetModelName({
338
312
  schema: getAuthTables(options),
339
313
  usePlural: adapter.options?.adapterConfig?.usePlural
@@ -343,7 +317,7 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
343
317
  usePlural: false
344
318
  });
345
319
  let schemaPrisma = "";
346
- if (schemaPrismaExist) schemaPrisma = await fs.readFile(path.join(process.cwd(), filePath), "utf-8");
320
+ if (schemaPrismaExist) schemaPrisma = await fs.readFile(resolvedFilePath, "utf-8");
347
321
  else schemaPrisma = getNewPrisma(provider, process.cwd());
348
322
  const prismaVersion = getPrismaVersion(process.cwd());
349
323
  if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
@@ -0,0 +1,5 @@
1
+ import "dotenv/config";
2
+ //#region src/index.d.ts
3
+ declare let cliVersion: string;
4
+ //#endregion
5
+ export { cliVersion };
package/dist/index.mjs CHANGED
@@ -695,15 +695,10 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
695
695
  name: `${modelName}_${fieldName}_idx`,
696
696
  on: fieldName
697
697
  });
698
- else if (attr.index && attr.unique) indexes.push({
699
- type: "uniqueIndex",
700
- name: `${modelName}_${fieldName}_uidx`,
701
- on: fieldName
702
- });
703
698
  if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
704
699
  if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
705
700
  else type += `.defaultNow()`;
706
- } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
701
+ } else if (typeof attr.defaultValue === "string") type += `.default(${JSON.stringify(attr.defaultValue)})`;
707
702
  else if (Array.isArray(attr.defaultValue)) {
708
703
  const elements = attr.defaultValue.map((value) => JSON.stringify(value)).join(", ");
709
704
  type += `.default([${elements}])`;
@@ -727,11 +722,24 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
727
722
  const modelName = getModelName(tableKey);
728
723
  const oneRelations = [];
729
724
  const manyRelations = [];
730
- const manyRelationsSet = /* @__PURE__ */ new Set();
731
725
  const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
726
+ const foreignFieldCounts = /* @__PURE__ */ new Map();
727
+ for (const [_, field] of foreignFields) {
728
+ const referencedModel = getModelName(field.references.model);
729
+ foreignFieldCounts.set(referencedModel, (foreignFieldCounts.get(referencedModel) ?? 0) + 1);
730
+ }
731
+ const usedOneRelationKeys = /* @__PURE__ */ new Set();
732
732
  for (const [fieldName, field] of foreignFields) {
733
733
  const referencedModel = field.references.model;
734
- const relationKey = getModelName(referencedModel);
734
+ const hasMultipleRelations = (foreignFieldCounts.get(getModelName(referencedModel)) ?? 0) > 1;
735
+ let relationKey = hasMultipleRelations ? fieldName.replace(/Id$/, "") : getModelName(referencedModel);
736
+ if (usedOneRelationKeys.has(relationKey)) relationKey = fieldName;
737
+ if (usedOneRelationKeys.has(relationKey)) {
738
+ let suffix = 2;
739
+ while (usedOneRelationKeys.has(`${relationKey}_${suffix}`)) suffix++;
740
+ relationKey = `${relationKey}_${suffix}`;
741
+ }
742
+ usedOneRelationKeys.add(relationKey);
735
743
  const fieldRef = `${getModelName(tableKey)}.${getFieldName({
736
744
  model: tableKey,
737
745
  field: fieldName
@@ -744,81 +752,49 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
744
752
  key: relationKey,
745
753
  model: getModelName(referencedModel),
746
754
  type: "one",
755
+ relationName: hasMultipleRelations ? `${getModelName(tableKey)}_${fieldName}` : void 0,
747
756
  reference: {
748
757
  field: fieldRef,
749
- references: referenceRef,
750
- fieldName
758
+ references: referenceRef
751
759
  }
752
760
  });
753
761
  }
754
762
  const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
755
- const modelRelationsMap = /* @__PURE__ */ new Map();
756
763
  for (const [modelName, otherTable] of otherModels) {
757
764
  const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
758
765
  if (foreignKeysPointingHere.length === 0) continue;
759
- const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
760
- const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
761
- modelRelationsMap.set(modelName, {
762
- modelName,
763
- hasUnique,
764
- hasMany
765
- });
766
- }
767
- for (const { modelName, hasMany } of modelRelationsMap.values()) {
768
- const relationType = hasMany ? "many" : "one";
769
- let relationKey = getModelName(modelName);
770
- if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
771
- if (!manyRelationsSet.has(relationKey)) {
772
- manyRelationsSet.add(relationKey);
766
+ for (const [fieldName, field] of foreignKeysPointingHere) {
767
+ const relationType = field.unique ? "one" : "many";
768
+ let relationKey = getModelName(modelName);
769
+ if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
770
+ const hasMultipleRelations = foreignKeysPointingHere.length > 1;
771
+ if (hasMultipleRelations) relationKey = `${relationKey}By${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
773
772
  manyRelations.push({
774
773
  key: relationKey,
775
774
  model: getModelName(modelName),
776
- type: relationType
775
+ type: relationType,
776
+ relationName: hasMultipleRelations ? `${getModelName(modelName)}_${fieldName}` : void 0
777
777
  });
778
778
  }
779
779
  }
780
- const relationsByModel = /* @__PURE__ */ new Map();
781
- for (const relation of oneRelations) if (relation.reference) {
782
- const modelKey = relation.key;
783
- if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
784
- relationsByModel.get(modelKey).push(relation);
785
- }
786
- const duplicateRelations = [];
787
- const singleRelations = [];
788
- for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
789
- else singleRelations.push(relations[0]);
790
- for (const relation of duplicateRelations) if (relation.reference) {
791
- const fieldName = relation.reference.fieldName;
792
- const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
793
- ${relation.key}: one(${relation.model}, {
780
+ const hasForwardOne = oneRelations.length > 0;
781
+ const hasReverseOne = manyRelations.some((relation) => relation.type === "one");
782
+ const hasReverseMany = manyRelations.some((relation) => relation.type === "many");
783
+ const hasOne = hasForwardOne || hasReverseOne;
784
+ const hasMany = hasReverseMany;
785
+ const renderOneRelation = (relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
794
786
  fields: [${relation.reference.field}],
795
787
  references: [${relation.reference.references}],
796
- })
797
- }))`;
798
- relationsString += `\n${tableRelation}\n`;
799
- }
800
- const hasOne = singleRelations.length > 0;
801
- const hasMany = manyRelations.length > 0;
802
- if (hasOne && hasMany) {
803
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
804
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
805
- fields: [${relation.reference.field}],
806
- references: [${relation.reference.references}],
807
- })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
808
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
809
- }))`;
810
- relationsString += `\n${tableRelation}\n`;
811
- } else if (hasOne) {
812
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
813
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
814
- fields: [${relation.reference.field}],
815
- references: [${relation.reference.references}],
816
- })` : "").filter((x) => x !== "").join(",\n ")}
817
- }))`;
818
- relationsString += `\n${tableRelation}\n`;
819
- } else if (hasMany) {
820
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
821
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
788
+ ${relation.relationName ? `relationName: "${relation.relationName}",` : ""}
789
+ })` : "";
790
+ const renderReverseRelation = ({ key, model, type, relationName }) => {
791
+ return ` ${key}: ${type === "one" ? "one" : "many"}(${model}${relationName ? `, { relationName: "${relationName}" }` : ""})`;
792
+ };
793
+ if (hasOne || hasMany) {
794
+ const helpers = [hasOne ? "one" : null, hasMany ? "many" : null].filter(Boolean).join(", ");
795
+ const relationEntries = [...oneRelations.map(renderOneRelation).filter((x) => x !== ""), ...manyRelations.map(renderReverseRelation)].join(",\n ");
796
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ ${helpers} }) => ({
797
+ ${relationEntries}
822
798
  }))`;
823
799
  relationsString += `\n${tableRelation}\n`;
824
800
  }
@@ -864,10 +840,7 @@ function generateImport({ databaseType, tables, options }) {
864
840
  if (databaseType === "mysql") coreImports.push("json");
865
841
  }
866
842
  if (databaseType === "sqlite" && Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.type === "date" && field.defaultValue && typeof field.defaultValue === "function" && field.defaultValue.toString().includes("new Date()")))) rootImports.push("sql");
867
- const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
868
- const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
869
- if (hasIndexes) coreImports.push("index");
870
- if (hasUniqueIndexes) coreImports.push("uniqueIndex");
843
+ if (Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique))) coreImports.push("index");
871
844
  return `${rootImports.length > 0 ? `import { ${rootImports.join(", ")} } from "drizzle-orm";\n` : ""}import { ${coreImports.map((x) => x.trim()).filter((x) => x !== "").join(", ")} } from "drizzle-orm/${databaseType}-core";\n`;
872
845
  }
873
846
  //#endregion
@@ -988,7 +961,8 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
988
961
  const provider = adapter.options?.provider || "postgresql";
989
962
  const tables = getAuthTables(options);
990
963
  const filePath = file || "./prisma/schema.prisma";
991
- const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
964
+ const resolvedFilePath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
965
+ const schemaPrismaExist = existsSync(resolvedFilePath);
992
966
  const getModelName = initGetModelName({
993
967
  schema: getAuthTables(options),
994
968
  usePlural: adapter.options?.adapterConfig?.usePlural
@@ -998,7 +972,7 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
998
972
  usePlural: false
999
973
  });
1000
974
  let schemaPrisma = "";
1001
- if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
975
+ if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(resolvedFilePath, "utf-8");
1002
976
  else schemaPrisma = getNewPrisma(provider, process.cwd());
1003
977
  const prismaVersion = getPrismaVersion(process.cwd());
1004
978
  if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
@@ -1387,6 +1361,11 @@ function addCloudflareVirtualModules(aliases) {
1387
1361
  * modules the real files depend on are not, which is why we stub `$app/*`
1388
1362
  * directly rather than resolving SvelteKit's on-disk files.
1389
1363
  *
1364
+ * The one exception is `$app/env/{private,public}` (explicit environment
1365
+ * variables): their exports are arbitrary names declared in the project's
1366
+ * `src/env.ts`, so they cannot be enumerated. They are stubbed with a Proxy
1367
+ * default export instead — see `createExplicitEnvModule`.
1368
+ *
1390
1369
  * The authoritative export surfaces this mirrors:
1391
1370
  *
1392
1371
  * @see https://github.com/sveltejs/kit/tree/main/packages/kit/src/runtime/app
@@ -1397,6 +1376,9 @@ function addSvelteKitVirtualModules(aliases) {
1397
1376
  aliases["$env/dynamic/public"] = createStubModule(createDynamicEnvModule("public"));
1398
1377
  aliases["$env/static/private"] = createStubModule(createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")));
1399
1378
  aliases["$env/static/public"] = createStubModule(createStaticEnvModule(filterPublicEnv("PUBLIC_", "")));
1379
+ const explicitEnvStub = createStubModule(createExplicitEnvModule());
1380
+ aliases["$app/env/private"] = explicitEnvStub;
1381
+ aliases["$app/env/public"] = explicitEnvStub;
1400
1382
  for (const [id, body] of Object.entries(appModuleStubs)) aliases[id] = createStubModule(body);
1401
1383
  }
1402
1384
  /**
@@ -1508,6 +1490,31 @@ export const env = new Proxy(
1508
1490
  },
1509
1491
  );`;
1510
1492
  }
1493
+ /**
1494
+ * Body for the explicit `$app/env/{private,public}` modules. Their exports are
1495
+ * named after the vars declared in `src/env.ts`, which the CLI cannot know, so
1496
+ * unlike the other stubs this cannot enumerate them. A Proxy exported as the
1497
+ * *default* sidesteps that: jiti compiles `import { FOO } from "..."` to a
1498
+ * member access on the (interop) default, which the Proxy answers from
1499
+ * process.env. No prefix filtering — the public/private split is a `src/env.ts`
1500
+ * config concern that does not affect schema generation.
1501
+ */
1502
+ function createExplicitEnvModule() {
1503
+ return `
1504
+ export default new Proxy(
1505
+ {},
1506
+ {
1507
+ get: (_, key) =>
1508
+ typeof key === "string" ? process.env[key] : undefined,
1509
+ has: (_, key) => typeof key === "string" && key in process.env,
1510
+ ownKeys: () => Object.keys(process.env),
1511
+ getOwnPropertyDescriptor: (_, key) =>
1512
+ typeof key === "string" && key in process.env
1513
+ ? { value: process.env[key], enumerable: true, configurable: true }
1514
+ : undefined,
1515
+ },
1516
+ );`;
1517
+ }
1511
1518
  function filterPrivateEnv(publicPrefix, privatePrefix) {
1512
1519
  return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(privatePrefix) && (publicPrefix === "" || !k.startsWith(publicPrefix))));
1513
1520
  }
@@ -1515,7 +1522,7 @@ function filterPublicEnv(publicPrefix, privatePrefix) {
1515
1522
  return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(publicPrefix) && (privatePrefix === "" || !k.startsWith(privatePrefix))));
1516
1523
  }
1517
1524
  const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1518
- const reserved = new Set([
1525
+ const reserved = /* @__PURE__ */ new Set([
1519
1526
  "do",
1520
1527
  "if",
1521
1528
  "in",
@@ -1735,7 +1742,7 @@ function resolveWithMatchers(specifier, matchers) {
1735
1742
  * a jiti-side preprocessor artifact observed in the AST; revisit on jiti
1736
1743
  * major version bumps (the regression suite catches a rename but not the why).
1737
1744
  */
1738
- const LOADER_IDENTIFIERS = new Set([
1745
+ const LOADER_IDENTIFIERS = /* @__PURE__ */ new Set([
1739
1746
  "require",
1740
1747
  "import",
1741
1748
  "jitiImport"
@@ -1809,18 +1816,62 @@ const jitiOptions = (cwd) => {
1809
1816
  const isDefaultExport = (object) => {
1810
1817
  return typeof object === "object" && object !== null && !Array.isArray(object) && Object.keys(object).length > 0 && "options" in object;
1811
1818
  };
1812
- async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
1819
+ /** Strips a source extension so paths can be compared regardless of `.ts`/`.js`/etc. */
1820
+ function withoutSourceExtension(filePath) {
1821
+ const ext = path.extname(filePath);
1822
+ return SOURCE_EXTENSIONS_SET.has(ext) ? filePath.slice(0, -ext.length) : filePath;
1823
+ }
1824
+ /**
1825
+ * Detects the specific first-run failure where a config file imports the
1826
+ * very file `auth generate --output <path>` is about to create (e.g. the
1827
+ * Convex integration guide's `auth.ts` template does `import schema from
1828
+ * "./schema"` before `schema.ts` has ever been generated).
1829
+ *
1830
+ * Deliberately narrow: only relative specifiers (`./`, `../`) are considered
1831
+ * — a bare package name that happens to share a name with the output file
1832
+ * must still fail normally — and the resolved specifier must point at
1833
+ * exactly the resolved `outputPath`, so unrelated missing imports are never
1834
+ * swallowed.
1835
+ *
1836
+ * @see https://github.com/better-auth/better-auth/issues/10136
1837
+ */
1838
+ function resolvesMissingOutputModule(error, configFilePath, resolvedOutputPath) {
1839
+ if (!error || typeof error !== "object" || error.code !== "MODULE_NOT_FOUND") return false;
1840
+ const specifier = ("message" in error && typeof error.message === "string" ? error.message : "").match(/Cannot find module ['"](\.\.?\/[^'"]+)['"]/)?.[1];
1841
+ if (!specifier) return false;
1842
+ const requireStack = "requireStack" in error && Array.isArray(error.requireStack) ? error.requireStack : [];
1843
+ const importedFrom = typeof requireStack[0] === "string" ? requireStack[0] : configFilePath;
1844
+ return withoutSourceExtension(path.resolve(path.dirname(importedFrom), specifier)) === withoutSourceExtension(resolvedOutputPath);
1845
+ }
1846
+ async function getConfig({ cwd, configPath, outputPath, shouldThrowOnError = false }) {
1813
1847
  try {
1814
1848
  let configFile = null;
1815
1849
  if (configPath) {
1816
1850
  let resolvedPath = path.join(cwd, configPath);
1817
1851
  if (existsSync(configPath)) resolvedPath = configPath;
1818
- const { config } = await loadConfig({
1852
+ const resolvedOutputPath = outputPath ? path.resolve(cwd, outputPath) : void 0;
1853
+ const loadOnce = () => loadConfig({
1819
1854
  configFile: resolvedPath,
1820
1855
  dotenv: { fileName: [".env", ".env.local"] },
1821
1856
  jitiOptions: jitiOptions(cwd),
1822
1857
  cwd
1823
1858
  });
1859
+ let loaded;
1860
+ try {
1861
+ loaded = await loadOnce();
1862
+ } catch (e) {
1863
+ if (resolvedOutputPath && !existsSync(resolvedOutputPath) && resolvesMissingOutputModule(e, resolvedPath, resolvedOutputPath)) {
1864
+ await fs.promises.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
1865
+ await fs.promises.writeFile(resolvedOutputPath, "");
1866
+ try {
1867
+ loaded = await loadOnce();
1868
+ } catch (retryError) {
1869
+ await fs.promises.rm(resolvedOutputPath, { force: true }).catch(() => {});
1870
+ throw retryError;
1871
+ }
1872
+ } else throw e;
1873
+ }
1874
+ const { config } = loaded;
1824
1875
  if (!("auth" in config) && !isDefaultExport(config)) {
1825
1876
  if (shouldThrowOnError) throw new Error(`Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
1826
1877
  console.error(`[#better-auth]: Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
@@ -1940,28 +1991,38 @@ async function generateAction(opts) {
1940
1991
  if ((await fs$1.stat(resolvedOutput)).isDirectory()) options.output = path.join(options.output, "auth-schema.ts");
1941
1992
  } catch {}
1942
1993
  }
1994
+ const resolvedOutputPath = options.output ? path.resolve(cwd, options.output) : void 0;
1995
+ const outputExistedBefore = resolvedOutputPath ? existsSync(resolvedOutputPath) : true;
1996
+ const removeGeneratedStub = async () => {
1997
+ if (!resolvedOutputPath || outputExistedBefore) return;
1998
+ await fs$1.rm(resolvedOutputPath, { force: true }).catch(() => {});
1999
+ };
1943
2000
  const config = await getConfig({
1944
2001
  cwd,
1945
- configPath: options.config
2002
+ configPath: options.config,
2003
+ outputPath: options.output
1946
2004
  });
1947
2005
  if (!config) {
2006
+ await removeGeneratedStub();
1948
2007
  console.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");
1949
2008
  return;
1950
2009
  }
1951
2010
  let adapter;
1952
2011
  if (options.adapter) adapter = createMockAdapter$1(options.adapter, options.dialect);
1953
- else adapter = await getAdapter(config).catch((e) => {
2012
+ else adapter = await getAdapter(config).catch(async (e) => {
1954
2013
  console.error(e.message);
2014
+ await removeGeneratedStub();
1955
2015
  process.exit(1);
1956
2016
  });
1957
2017
  const spinner = yoctoSpinner({ text: "preparing schema..." }).start();
1958
2018
  const schema = await generateSchema({
1959
2019
  adapter,
1960
- file: options.output,
2020
+ file: resolvedOutputPath ?? options.output,
1961
2021
  options: config
1962
2022
  });
1963
2023
  spinner.stop();
1964
2024
  if (!schema.code) {
2025
+ await removeGeneratedStub();
1965
2026
  console.log("Your schema is already up to date.");
1966
2027
  try {
1967
2028
  await (await createTelemetry(config)).publish({
@@ -1985,9 +2046,10 @@ async function generateAction(opts) {
1985
2046
  message: `The file ${schema.fileName} already exists. Do you want to ${chalk.yellow(`${schema.overwrite ? "overwrite" : "append"}`)} the schema to the file?`
1986
2047
  })).confirm;
1987
2048
  if (confirm) {
1988
- if (!existsSync(path.join(cwd, schema.fileName))) await fs$1.mkdir(path.dirname(path.join(cwd, schema.fileName)), { recursive: true });
1989
- if (schema.overwrite) await fs$1.writeFile(path.join(cwd, schema.fileName), schema.code);
1990
- else await fs$1.appendFile(path.join(cwd, schema.fileName), schema.code);
2049
+ const schemaPath = path.isAbsolute(schema.fileName) ? schema.fileName : path.join(cwd, schema.fileName);
2050
+ if (!existsSync(schemaPath)) await fs$1.mkdir(path.dirname(schemaPath), { recursive: true });
2051
+ if (schema.overwrite) await fs$1.writeFile(schemaPath, schema.code);
2052
+ else await fs$1.appendFile(schemaPath, schema.code);
1991
2053
  console.log(`🚀 Schema was ${schema.overwrite ? "overwritten" : "appended"} successfully!`);
1992
2054
  try {
1993
2055
  await (await createTelemetry(config)).publish({
@@ -2001,6 +2063,7 @@ async function generateAction(opts) {
2001
2063
  process.exit(0);
2002
2064
  } else {
2003
2065
  console.error("Schema generation aborted.");
2066
+ await removeGeneratedStub();
2004
2067
  try {
2005
2068
  await (await createTelemetry(config)).publish({
2006
2069
  type: "cli_generate",
@@ -2025,6 +2088,7 @@ async function generateAction(opts) {
2025
2088
  })).confirm;
2026
2089
  if (!confirm) {
2027
2090
  console.error("Schema generation aborted.");
2091
+ await removeGeneratedStub();
2028
2092
  try {
2029
2093
  await (await createTelemetry(config)).publish({
2030
2094
  type: "cli_generate",
@@ -2036,10 +2100,9 @@ async function generateAction(opts) {
2036
2100
  } catch {}
2037
2101
  process.exit(1);
2038
2102
  }
2039
- if (!options.output) {
2040
- if (!existsSync(path.dirname(path.join(cwd, schema.fileName)))) await fs$1.mkdir(path.dirname(path.join(cwd, schema.fileName)), { recursive: true });
2041
- }
2042
- await fs$1.writeFile(options.output || path.join(cwd, schema.fileName), schema.code);
2103
+ const writePath = resolvedOutputPath ?? path.join(cwd, schema.fileName);
2104
+ if (!existsSync(path.dirname(writePath))) await fs$1.mkdir(path.dirname(writePath), { recursive: true });
2105
+ await fs$1.writeFile(writePath, schema.code);
2043
2106
  console.log(`🚀 Schema was generated successfully!`);
2044
2107
  try {
2045
2108
  await (await createTelemetry(config)).publish({
@@ -5101,11 +5164,12 @@ const getAuthPluginsCode = async ({ plugins, options = {}, installDependency })
5101
5164
  const getArguments = await getArgumentsPrompt(options, plugins, "auth");
5102
5165
  const pluginsCode = [];
5103
5166
  for (const plugin of plugins) {
5104
- const args = convertArgumentsCodeToStringArray(await buildArgumentsCode(plugin.auth.arguments, getArguments, plugin.auth.function));
5167
+ const argumentsCode = await buildArgumentsCode(plugin.auth.arguments, getArguments, plugin.auth.function);
5168
+ const args = convertArgumentsCodeToStringArray(argumentsCode);
5105
5169
  removeTrailingUndefined(args);
5106
5170
  pluginsCode.push(`${plugin.auth.function}(${args.join(", ")})`);
5107
- const dependencies = new Set([...plugin.dependencies || [], ...plugin.auth.dependencies || []]);
5108
- const devDependencies = new Set([...plugin.devDependencies || [], ...plugin.auth.devDependencies || []]);
5171
+ const dependencies = /* @__PURE__ */ new Set([...plugin.dependencies || [], ...plugin.auth.dependencies || []]);
5172
+ const devDependencies = /* @__PURE__ */ new Set([...plugin.devDependencies || [], ...plugin.auth.devDependencies || []]);
5109
5173
  if (dependencies.size > 0) await installDependency([...dependencies]);
5110
5174
  if (devDependencies.size > 0) await installDependency([...devDependencies], "dev");
5111
5175
  }
@@ -5119,11 +5183,12 @@ const getAuthClientPluginsCode = async ({ plugins, options = {}, installDependen
5119
5183
  const pluginsCode = [];
5120
5184
  for (const plugin of pluginsWithClient) {
5121
5185
  if (!plugin.authClient) continue;
5122
- const args = convertArgumentsCodeToStringArray(await buildArgumentsCode(plugin.authClient.arguments, getArguments, plugin.authClient.function));
5186
+ const argumentsCode = await buildArgumentsCode(plugin.authClient.arguments, getArguments, plugin.authClient.function);
5187
+ const args = convertArgumentsCodeToStringArray(argumentsCode);
5123
5188
  removeTrailingUndefined(args);
5124
5189
  pluginsCode.push(`${plugin.authClient.function}(${args.join(", ")})`);
5125
- const dependencies = new Set([...plugin.dependencies || [], ...plugin.authClient.dependencies || []]);
5126
- const devDependencies = new Set([...plugin.devDependencies || [], ...plugin.authClient.devDependencies || []]);
5190
+ const dependencies = /* @__PURE__ */ new Set([...plugin.dependencies || [], ...plugin.authClient.dependencies || []]);
5191
+ const devDependencies = /* @__PURE__ */ new Set([...plugin.devDependencies || [], ...plugin.authClient.devDependencies || []]);
5127
5192
  if (dependencies.size > 0) await installDependency([...dependencies]);
5128
5193
  if (devDependencies.size > 0) await installDependency([...devDependencies], "dev");
5129
5194
  }
@@ -6296,9 +6361,10 @@ export const auth = betterAuth({
6296
6361
  }
6297
6362
  databaseChoice = dbChoice || null;
6298
6363
  if (databaseChoice === "yes") {
6364
+ const availableORMs = getAvailableORMs();
6299
6365
  const selectedOption = await select({
6300
6366
  message: `Select the database you want to use:`,
6301
- options: getAvailableORMs().map((opt) => ({
6367
+ options: availableORMs.map((opt) => ({
6302
6368
  value: opt.adapter || opt.value,
6303
6369
  label: opt.label
6304
6370
  }))
@@ -6332,9 +6398,10 @@ export const auth = betterAuth({
6332
6398
  database = sqliteVariants;
6333
6399
  } else if (isDirectAdapter(selectedOption)) database = selectedOption;
6334
6400
  else {
6401
+ const availableDialects = getDialectsForORM(selectedOption);
6335
6402
  const selectedDialect = await select({
6336
6403
  message: `Select the database dialect:`,
6337
- options: getDialectsForORM(selectedOption).map((d) => ({
6404
+ options: availableDialects.map((d) => ({
6338
6405
  value: d.adapter,
6339
6406
  label: d.label
6340
6407
  }))
@@ -6352,7 +6419,7 @@ export const auth = betterAuth({
6352
6419
  const { shouldInstallDeps } = await prompts({
6353
6420
  type: "confirm",
6354
6421
  name: "shouldInstallDeps",
6355
- message: `Would you like to install the following dependencies: ${[...new Set([...databaseConfig.dependencies, ...databaseConfig.devDependencies || []])].map((x) => chalk.cyan(x)).join(", ")}?`,
6422
+ message: `Would you like to install the following dependencies: ${[.../* @__PURE__ */ new Set([...databaseConfig.dependencies, ...databaseConfig.devDependencies || []])].map((x) => chalk.cyan(x)).join(", ")}?`,
6356
6423
  initial: true
6357
6424
  });
6358
6425
  if (isCancel(shouldInstallDeps)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth",
3
- "version": "1.6.22",
3
+ "version": "1.6.24",
4
4
  "description": "The CLI for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "@babel/preset-react": "^7.28.5",
48
48
  "@babel/preset-typescript": "^7.28.5",
49
49
  "@better-auth/utils": "0.4.2",
50
- "@clack/prompts": "^0.11.0",
50
+ "@clack/prompts": "^1.6.0",
51
51
  "@mrleebo/prisma-ast": "^0.13.1",
52
52
  "c12": "^3.3.3",
53
53
  "chalk": "^5.6.2",
@@ -60,9 +60,9 @@
60
60
  "semver": "^7.7.4",
61
61
  "yocto-spinner": "^0.2.3",
62
62
  "zod": "^4.3.6",
63
- "@better-auth/core": "1.6.22",
64
- "@better-auth/telemetry": "1.6.22",
65
- "better-auth": "1.6.22"
63
+ "@better-auth/core": "1.6.24",
64
+ "@better-auth/telemetry": "1.6.24",
65
+ "better-auth": "1.6.24"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/better-sqlite3": "^7.6.13",
@@ -71,11 +71,11 @@
71
71
  "better-sqlite3": "^12.6.2",
72
72
  "jiti": "^2.6.1",
73
73
  "memfs": "^4.56.10",
74
- "tsdown": "0.21.1",
74
+ "tsdown": "0.22.7",
75
75
  "tsx": "^4.21.0",
76
76
  "type-fest": "^5.4.4",
77
- "typescript": "^5.9.3",
78
- "@better-auth/passkey": "1.6.22"
77
+ "typescript": "^6.0.3",
78
+ "@better-auth/passkey": "1.6.24"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsdown",