feat: add @default and @updatedAt for prisma generator (#4375)

This commit is contained in:
Alex Yang
2025-09-02 21:22:08 -07:00
committed by GitHub
parent a4e0ede7e3
commit c84b37faaf
21 changed files with 146 additions and 117 deletions

View File

@@ -283,7 +283,7 @@ exports[`init > should match config 1`] = `
"unique": true,
},
"emailVerified": {
"defaultValue": [Function],
"defaultValue": false,
"fieldName": "emailVerified",
"required": true,
"type": "boolean",
@@ -315,7 +315,7 @@ exports[`init > should match config 1`] = `
"createdAt": {
"defaultValue": [Function],
"fieldName": "createdAt",
"required": false,
"required": true,
"type": "date",
},
"expiresAt": {
@@ -332,7 +332,7 @@ exports[`init > should match config 1`] = `
"defaultValue": [Function],
"fieldName": "updatedAt",
"onUpdate": [Function],
"required": false,
"required": true,
"type": "date",
},
"value": {

View File

@@ -352,7 +352,7 @@ export const createAdapter =
newMappedKeys[field] || fields[field].fieldName || field;
if (
value === undefined &&
((!fieldAttributes.defaultValue &&
((fieldAttributes.defaultValue === undefined &&
!fieldAttributes.transform?.input &&
!(action === "update" && fieldAttributes.onUpdate)) ||
(action === "update" && !fieldAttributes.onUpdate))

View File

@@ -144,7 +144,7 @@ export const getAuthTables = (
},
emailVerified: {
type: "boolean",
defaultValue: () => false,
defaultValue: false,
required: true,
fieldName: options.user?.fields?.emailVerified || "emailVerified",
},
@@ -272,13 +272,13 @@ export const getAuthTables = (
},
createdAt: {
type: "date",
required: false,
required: true,
defaultValue: () => new Date(),
fieldName: options.verification?.fields?.createdAt || "createdAt",
},
updatedAt: {
type: "date",
required: false,
required: true,
defaultValue: () => new Date(),
onUpdate: () => new Date(),
fieldName: options.verification?.fields?.updatedAt || "updatedAt",

View File

@@ -36,13 +36,14 @@ export const createInternalAdapter = (
return {
createOAuthUser: async (
user: Omit<User, "id" | "createdAt" | "updatedAt"> & Partial<User>,
user: Omit<User, "id" | "createdAt" | "updatedAt">,
account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> &
Partial<Account>,
context?: GenericEndpointContext,
) => {
const createdUser = await createWithHooks(
{
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
...user,
@@ -54,7 +55,8 @@ export const createInternalAdapter = (
const createdAccount = await createWithHooks(
{
...account,
userId: createdUser!.id || user.id,
userId: createdUser!.id,
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
},
@@ -75,9 +77,9 @@ export const createInternalAdapter = (
) => {
const createdUser = await createWithHooks(
{
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
emailVerified: false,
...user,
email: user.email?.toLowerCase(),
},
@@ -95,6 +97,7 @@ export const createInternalAdapter = (
) => {
const createdAccount = await createWithHooks(
{
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
...account,
@@ -238,6 +241,7 @@ export const createInternalAdapter = (
: getDate(sessionExpiration, "sec"),
userId,
token: generateId(32),
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
...(overrideAll ? rest : {}),
@@ -712,9 +716,10 @@ export const createInternalAdapter = (
) => {
const _account = await createWithHooks(
{
...account,
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
...account,
},
"account",
undefined,
@@ -892,6 +897,7 @@ export const createInternalAdapter = (
) => {
const verification = await createWithHooks(
{
// todo: we should remove auto setting createdAt and updatedAt in the next major release, since the db generators already handle that
createdAt: new Date(),
updatedAt: new Date(),
...data,

View File

@@ -5,8 +5,13 @@ import type { BetterAuthOptions } from "../types/options";
import { APIError } from "better-call";
import type { Account, Session, User } from "../types";
export const accountSchema = z.object({
export const coreSchema = z.object({
id: z.string(),
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
});
export const accountSchema = coreSchema.extend({
providerId: z.string(),
accountId: z.string(),
userId: z.coerce.string(),
@@ -29,36 +34,25 @@ export const accountSchema = z.object({
* Password is only stored in the credential provider
*/
password: z.string().nullish(),
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
});
export const userSchema = z.object({
id: z.string(),
export const userSchema = coreSchema.extend({
email: z.string().transform((val) => val.toLowerCase()),
emailVerified: z.boolean().default(false),
name: z.string(),
image: z.string().nullish(),
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
});
export const sessionSchema = z.object({
id: z.string(),
export const sessionSchema = coreSchema.extend({
userId: z.coerce.string(),
expiresAt: z.date(),
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
token: z.string(),
ipAddress: z.string().nullish(),
userAgent: z.string().nullish(),
});
export const verificationSchema = z.object({
id: z.string(),
export const verificationSchema = coreSchema.extend({
value: z.string(),
createdAt: z.date().default(() => new Date()),
updatedAt: z.date().default(() => new Date()),
expiresAt: z.date(),
identifier: z.string(),
});

View File

@@ -72,7 +72,7 @@ describe("two factor", async () => {
},
],
});
expect(dbUser?.twoFactorEnabled).toBe(null);
expect(dbUser?.twoFactorEnabled).toBe(false);
expect(twoFactor?.secret).toBeDefined();
expect(twoFactor?.backupCodes).toBeDefined();
});

View File

@@ -1,4 +1,5 @@
import type { BetterAuthOptions } from "./options";
import type { AdapterConfig, CustomAdapter } from "../adapters";
/**
* Adapter where clause
@@ -77,7 +78,9 @@ export type Adapter = {
options: BetterAuthOptions,
file?: string,
) => Promise<AdapterSchemaCreation>;
options?: Record<string, any>;
options?: {
adapterConfig: AdapterConfig;
} & CustomAdapter["options"];
};
export type AdapterSchemaCreation = {

View File

@@ -162,6 +162,34 @@ export const generatePrismaSchema: SchemaGenerator = async ({
if (attr.unique) {
builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
}
if (attr.defaultValue !== undefined) {
if (field === "createdAt") {
fieldBuilder.attribute("default(now())");
} else if (typeof attr.defaultValue === "boolean") {
fieldBuilder.attribute(`default(${attr.defaultValue})`);
} else if (typeof attr.defaultValue === "function") {
// For other function-based defaults, we'll need to check what they return
const defaultVal = attr.defaultValue();
if (defaultVal instanceof Date) {
fieldBuilder.attribute("default(now())");
} else {
console.warn(
`Warning: Unsupported default function for field ${fieldName} in model ${modelName}. Please adjust manually.`,
);
}
}
}
// This is a special handling for updatedAt fields
if (field === "updatedAt" && attr.onUpdate) {
fieldBuilder.attribute("updatedAt");
} else if (attr.onUpdate) {
console.warn(
`Warning: 'onUpdate' is only supported on 'updatedAt' fields. Please adjust manually for field ${fieldName} in model ${modelName}.`,
);
}
if (attr.references) {
const referencedOriginalModelName = attr.references.model;
const referencedCustomModelName =

View File

@@ -11,9 +11,7 @@ export const custom_user = mysqlTable("custom_user", {
id: int("id").autoincrement().primaryKey(),
name: text("name").notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
emailVerified: boolean("email_verified")
.$defaultFn(() => false)
.notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
@@ -67,12 +65,13 @@ export const custom_verification = mysqlTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: timestamp("updated_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = mysqlTable("two_factor", {

View File

@@ -10,9 +10,7 @@ export const custom_user = mysqlTable("custom_user", {
id: varchar("id", { length: 36 }).primaryKey(),
name: text("name").notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
emailVerified: boolean("email_verified")
.$defaultFn(() => false)
.notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
@@ -66,12 +64,13 @@ export const custom_verification = mysqlTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: timestamp("updated_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = mysqlTable("two_factor", {

View File

@@ -11,9 +11,7 @@ export const custom_user = pgTable("custom_user", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified")
.$defaultFn(() => false)
.notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
@@ -67,12 +65,13 @@ export const custom_verification = pgTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: timestamp("updated_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = pgTable("two_factor", {

View File

@@ -5,7 +5,7 @@ export const custom_user = sqliteTable("custom_user", {
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" })
.$defaultFn(() => false)
.default(false)
.notNull(),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp" })
@@ -66,12 +66,13 @@ export const custom_verification = sqliteTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: integer("created_at", { mode: "timestamp" })
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" })
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = sqliteTable("two_factor", {

View File

@@ -5,7 +5,7 @@ export const custom_user = sqliteTable("custom_user", {
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" })
.$defaultFn(() => false)
.default(false)
.notNull(),
image: text("image"),
createdAt: integer("created_at", { mode: "timestamp" })
@@ -66,12 +66,13 @@ export const custom_verification = sqliteTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: integer("created_at", { mode: "timestamp" })
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" })
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = sqliteTable("two_factor", {

View File

@@ -4,9 +4,7 @@ export const custom_user = pgTable("custom_user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified")
.$defaultFn(() => false)
.notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
@@ -60,12 +58,13 @@ export const custom_verification = pgTable("custom_verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").$defaultFn(
() => /* @__PURE__ */ new Date(),
),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.notNull(),
updatedAt: timestamp("updated_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
.$onUpdate(() => /* @__PURE__ */ new Date()),
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const twoFactor = pgTable("two_factor", {

View File

@@ -4,4 +4,4 @@ create table "session" ("id" text not null primary key, "expiresAt" date not nul
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id") on delete cascade, "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" date, "refreshTokenExpiresAt" date, "scope" text, "password" text, "createdAt" date not null, "updatedAt" date not null);
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date, "updatedAt" date);
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date not null, "updatedAt" date not null);

View File

@@ -12,11 +12,11 @@ model User {
id String @id @map("_id")
name String
email String
emailVerified Boolean
emailVerified Boolean @default(false)
image String?
createdAt DateTime
updatedAt DateTime
twoFactorEnabled Boolean?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
twoFactorEnabled Boolean? @default(false)
username String?
displayUsername String?
sessions Session[]
@@ -33,7 +33,7 @@ model Session {
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId String
@@ -57,18 +57,18 @@ model Account {
scope String?
password String?
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
@@map("account")
}
model Verification {
id String @id @map("_id")
id String @id @map("_id")
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("verification")
}

View File

@@ -12,11 +12,11 @@ model User {
id String @id
name String @db.Text
email String
emailVerified Boolean
emailVerified Boolean @default(false)
image String? @db.Text
createdAt DateTime
updatedAt DateTime
twoFactorEnabled Boolean?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
twoFactorEnabled Boolean? @default(false)
username String?
displayUsername String? @db.Text
sessions Session[]
@@ -35,7 +35,7 @@ model Session {
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
ipAddress String? @db.Text
userAgent String? @db.Text
userId String
@@ -60,18 +60,18 @@ model Account {
scope String? @db.Text
password String? @db.Text
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
@@map("account")
}
model Verification {
id String @id
identifier String @db.Text
value String @db.Text
id String @id
identifier String @db.Text
value String @db.Text
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("verification")
}

View File

@@ -12,11 +12,11 @@ model User {
id String @id
name String @db.Text
email String
emailVerified Boolean
emailVerified Boolean @default(false)
image String? @db.Text
createdAt DateTime
updatedAt DateTime
twoFactorEnabled Boolean?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
twoFactorEnabled Boolean? @default(false)
username String?
displayUsername String? @db.Text
sessions Session[]
@@ -33,7 +33,7 @@ model Session {
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
ipAddress String? @db.Text
userAgent String? @db.Text
userId String
@@ -57,18 +57,18 @@ model Account {
scope String? @db.Text
password String? @db.Text
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
@@map("account")
}
model Verification {
id String @id
identifier String @db.Text
value String @db.Text
id String @id
identifier String @db.Text
value String @db.Text
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("verification")
}

View File

@@ -12,11 +12,11 @@ model User {
id Int @id @default(autoincrement())
name String
email String
emailVerified Boolean
emailVerified Boolean @default(false)
image String?
createdAt DateTime
updatedAt DateTime
twoFactorEnabled Boolean?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
twoFactorEnabled Boolean? @default(false)
username String?
displayUsername String?
sessions Session[]
@@ -33,7 +33,7 @@ model Session {
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId Int
@@ -57,18 +57,18 @@ model Account {
scope String?
password String?
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
@@map("account")
}
model Verification {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("verification")
}

View File

@@ -12,11 +12,11 @@ model User {
id String @id
name String
email String
emailVerified Boolean
emailVerified Boolean @default(false)
image String?
createdAt DateTime
updatedAt DateTime
twoFactorEnabled Boolean?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
twoFactorEnabled Boolean? @default(false)
username String?
displayUsername String?
sessions Session[]
@@ -33,7 +33,7 @@ model Session {
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId String
@@ -57,18 +57,18 @@ model Account {
scope String?
password String?
createdAt DateTime
updatedAt DateTime
updatedAt DateTime @updatedAt
@@map("account")
}
model Verification {
id String @id
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("verification")
}

View File

@@ -180,7 +180,7 @@ describe("stripe", async () => {
stripeCustomerId: expect.any(String),
status: "incomplete",
periodStart: undefined,
cancelAtPeriodEnd: undefined,
cancelAtPeriodEnd: false,
trialStart: undefined,
trialEnd: undefined,
});