@better-auth/cli 1.4.0-beta.20 → 1.4.0-beta.21

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.
Files changed (2) hide show
  1. package/dist/index.mjs +88 -17
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -11,6 +11,7 @@ import path from "path";
11
11
  import prompts from "prompts";
12
12
  import yoctoSpinner from "yocto-spinner";
13
13
  import * as z from "zod/v4";
14
+ import { initGetFieldName, initGetModelName } from "better-auth/adapters";
14
15
  import prettier, { format } from "prettier";
15
16
  import { produceSchema } from "@mrleebo/prisma-ast";
16
17
  import babelPresetReact from "@babel/preset-react";
@@ -45,9 +46,17 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
45
46
  tables,
46
47
  options
47
48
  });
49
+ const getModelName = initGetModelName({
50
+ schema: tables,
51
+ usePlural: adapter.options?.adapterConfig?.usePlural
52
+ });
53
+ const getFieldName = initGetFieldName({
54
+ schema: tables,
55
+ usePlural: adapter.options?.adapterConfig?.usePlural
56
+ });
48
57
  for (const tableKey in tables) {
49
58
  const table = tables[tableKey];
50
- const modelName = getModelName(table.modelName, adapter.options);
59
+ const modelName = getModelName(tableKey);
51
60
  const fields = table.fields;
52
61
  function getType(name, field) {
53
62
  if (!databaseType) throw new Error(`Database provider type is undefined during Drizzle schema generation. Please define a \`provider\` in the Drizzle adapter config. Read more at https://better-auth.com/docs/adapters/drizzle`);
@@ -112,7 +121,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
112
121
  let id = "";
113
122
  const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
114
123
  if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
115
- else if (useNumberId) if (databaseType === "pg") id = `serial("id").primaryKey()`;
124
+ else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
116
125
  else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
117
126
  else id = `int("id").autoincrement().primaryKey()`;
118
127
  else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
@@ -150,11 +159,61 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
150
159
  if (attr.onUpdate && attr.type === "date") {
151
160
  if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
152
161
  }
153
- return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(tables[attr.references.model]?.modelName || attr.references.model, adapter.options)}.${fields[attr.references.field]?.fieldName || attr.references.field}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
162
+ return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
163
+ model: attr.references.model,
164
+ field: attr.references.field
165
+ })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
154
166
  }).join(",\n ")}
155
167
  }${assignIndexes(indexes)});`;
156
168
  code += `\n${schema}\n`;
157
169
  }
170
+ let relationsString = "";
171
+ for (const tableKey in tables) {
172
+ const table = tables[tableKey];
173
+ const relations = [];
174
+ const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
175
+ for (const [fieldName, field] of foreignFields) {
176
+ const referencedModel = field.references.model;
177
+ relations.push({
178
+ key: getModelName(referencedModel),
179
+ model: getModelName(referencedModel),
180
+ type: "one",
181
+ reference: {
182
+ field: `${getModelName(tableKey)}.${getFieldName({
183
+ model: tableKey,
184
+ field: fieldName
185
+ })}`,
186
+ references: `${getModelName(referencedModel)}.${getFieldName({
187
+ model: referencedModel,
188
+ field: field.references.field || "id"
189
+ })}`
190
+ }
191
+ });
192
+ }
193
+ const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
194
+ for (const [modelName, otherTable] of otherModels) {
195
+ const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
196
+ for (const [fieldName, field] of foreignKeysPointingHere) {
197
+ const isUnique = !!field.unique;
198
+ const relationKey = isUnique ? getModelName(modelName) : `${getModelName(modelName)}s`;
199
+ relations.push({
200
+ key: relationKey,
201
+ model: getModelName(modelName),
202
+ type: isUnique ? "one" : "many"
203
+ });
204
+ }
205
+ }
206
+ const hasOne = relations.some((relation) => relation.type === "one");
207
+ const hasMany = relations.some((relation) => relation.type === "many");
208
+ let tableRelation = `export const ${table.modelName}Relations = relations(${getModelName(table.modelName)}, ({ ${hasOne ? "one" : ""}${hasMany ? `${hasOne ? ", " : ""}many` : ""} }) => ({
209
+ ${relations.map(({ key, type, model, reference }) => ` ${key}: ${type}(${model}${!reference ? "" : `, {
210
+ fields: [${reference.field}],
211
+ references: [${reference.references}],
212
+ }`})`).join(",\n ")}
213
+ }))`;
214
+ if (relations.length > 0) relationsString += `\n${tableRelation}\n`;
215
+ }
216
+ code += `\n${relationsString}`;
158
217
  return {
159
218
  code: await prettier.format(code, { parser: "typescript" }),
160
219
  fileName: filePath,
@@ -162,7 +221,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
162
221
  };
163
222
  };
164
223
  function generateImport({ databaseType, tables, options }) {
165
- const rootImports = [];
224
+ const rootImports = ["relations"];
166
225
  const coreImports = [];
167
226
  let hasBigint = false;
168
227
  let hasJson = false;
@@ -189,10 +248,7 @@ function generateImport({ databaseType, tables, options }) {
189
248
  const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
190
249
  if (hasNonBigintNumber || (options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial") && hasFkToId) coreImports.push("integer");
191
250
  } else coreImports.push("integer");
192
- if (databaseType === "pg") {
193
- if (useNumberId) coreImports.push("serial");
194
- else if (useUUIDs) coreImports.push("uuid");
195
- }
251
+ if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
196
252
  if (hasJson) {
197
253
  if (databaseType === "pg") coreImports.push("jsonb");
198
254
  if (databaseType === "mysql") coreImports.push("json");
@@ -204,9 +260,6 @@ function generateImport({ databaseType, tables, options }) {
204
260
  if (hasUniqueIndexes) coreImports.push("uniqueIndex");
205
261
  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`;
206
262
  }
207
- function getModelName(modelName, options) {
208
- return options?.usePlural ? `${modelName}s` : modelName;
209
- }
210
263
 
211
264
  //#endregion
212
265
  //#region src/generators/kysely.ts
@@ -278,9 +331,17 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
278
331
  const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
279
332
  const useUUIDs = options.advanced?.database?.generateId === "uuid";
280
333
  if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
281
- else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("db.Uuid").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))");
334
+ else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
282
335
  else builder.model(modelName).field("id", "String").attribute("id");
283
336
  }
337
+ const getModelName = initGetModelName({
338
+ schema: getAuthTables(options),
339
+ usePlural: adapter.options?.adapterConfig?.usePlural
340
+ });
341
+ const getFieldName = initGetFieldName({
342
+ schema: getAuthTables(options),
343
+ usePlural: false
344
+ });
284
345
  for (const field in fields) {
285
346
  const attr = fields[field];
286
347
  const fieldName = attr.fieldName || field;
@@ -345,25 +406,35 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
345
406
  if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
346
407
  else if (attr.onUpdate) {}
347
408
  if (attr.references) {
348
- if (useUUIDs && provider === "postgresql") fieldBuilder.attribute(`db.Uuid`);
349
- const referencedOriginalModelName = attr.references.model;
409
+ if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
410
+ const referencedOriginalModelName = getModelName(attr.references.model);
350
411
  const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
351
412
  let action = "Cascade";
352
413
  if (attr.references.onDelete === "no action") action = "NoAction";
353
414
  else if (attr.references.onDelete === "set null") action = "SetNull";
354
415
  else if (attr.references.onDelete === "set default") action = "SetDefault";
355
416
  else if (attr.references.onDelete === "restrict") action = "Restrict";
356
- const relationField = `relation(fields: [${fieldName}], references: [${attr.references.field}], onDelete: ${action})`;
417
+ const relationField = `relation(fields: [${getFieldName({
418
+ model: originalTableName,
419
+ field: fieldName
420
+ })}], references: [${getFieldName({
421
+ model: attr.references.model,
422
+ field: attr.references.field
423
+ })}], onDelete: ${action})`;
357
424
  builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${!attr.required ? "?" : ""}`).attribute(relationField);
358
425
  }
359
426
  if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
360
427
  }
361
428
  if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
362
- const fieldName = `${relatedModel.toLowerCase()}s`;
429
+ const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
430
+ const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
431
+ const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
432
+ const isUnique = fkFieldAttr?.unique === true;
433
+ const fieldName = isUnique ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
363
434
  if (!builder.findByType("field", {
364
435
  name: fieldName,
365
436
  within: prismaModel?.properties
366
- })) builder.model(modelName).field(fieldName, `${relatedModel}[]`);
437
+ })) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
367
438
  }
368
439
  const indexedFieldsForModel = indexedFields.get(modelName);
369
440
  if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/cli",
3
- "version": "1.4.0-beta.20",
3
+ "version": "1.4.0-beta.21",
4
4
  "type": "module",
5
5
  "description": "The CLI for Better Auth",
6
6
  "module": "dist/index.mjs",
@@ -34,7 +34,7 @@
34
34
  "tsx": "^4.20.6",
35
35
  "type-fest": "^5.2.0",
36
36
  "typescript": "^5.9.3",
37
- "@better-auth/passkey": "1.4.0-beta.20"
37
+ "@better-auth/passkey": "1.4.0-beta.21"
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/core": "^7.28.4",
@@ -62,8 +62,8 @@
62
62
  "tinyexec": "^0.3.2",
63
63
  "yocto-spinner": "^0.2.3",
64
64
  "zod": "^4.1.12",
65
- "better-auth": "1.4.0-beta.20",
66
- "@better-auth/core": "1.4.0-beta.20"
65
+ "better-auth": "1.4.0-beta.21",
66
+ "@better-auth/core": "1.4.0-beta.21"
67
67
  },
68
68
  "files": [
69
69
  "dist"