Skip to content

Commit 8d2347e

Browse files
committed
fix(build): use lazy initialization for build compatibility
- Make drizzle db connection lazy (defer DATABASE_URL check) - Make DI container initialize on first access - Make ResendEmailService validate env vars lazily - Make BetterAuth instance initialize on first access Fixes build failures when env vars are not available at build time.
1 parent a57e2ec commit 8d2347e

4 files changed

Lines changed: 114 additions & 65 deletions

File tree

apps/nextjs/common/auth.ts

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,60 @@ import { betterAuth } from "better-auth";
44
import { drizzleAdapter } from "better-auth/adapters/drizzle";
55
import { nextCookies } from "better-auth/next-js";
66

7-
export const auth = betterAuth({
8-
database: drizzleAdapter(db, {
9-
provider: "pg",
10-
schema: {
11-
user: schema.user,
12-
session: schema.session,
13-
account: schema.account,
14-
verification: schema.verification,
7+
type AuthInstance = ReturnType<typeof betterAuth>;
8+
9+
let _auth: AuthInstance | null = null;
10+
11+
function createAuth(): AuthInstance {
12+
return betterAuth({
13+
database: drizzleAdapter(db, {
14+
provider: "pg",
15+
schema: {
16+
user: schema.user,
17+
session: schema.session,
18+
account: schema.account,
19+
verification: schema.verification,
20+
},
21+
}),
22+
emailAndPassword: {
23+
enabled: true,
24+
minPasswordLength: 8,
1525
},
16-
}),
17-
emailAndPassword: {
18-
enabled: true,
19-
minPasswordLength: 8,
20-
},
21-
socialProviders: {
22-
google: {
23-
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
24-
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
26+
socialProviders: {
27+
google: {
28+
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
29+
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
30+
},
31+
github: {
32+
clientId: process.env.GITHUB_CLIENT_ID ?? "",
33+
clientSecret: process.env.GITHUB_CLIENT_SECRET ?? "",
34+
},
2535
},
26-
github: {
27-
clientId: process.env.GITHUB_CLIENT_ID ?? "",
28-
clientSecret: process.env.GITHUB_CLIENT_SECRET ?? "",
29-
},
30-
},
31-
session: {
32-
expiresIn: 60 * 60 * 24 * 7, // 7 days
33-
updateAge: 60 * 60 * 24, // 1 day
34-
cookieCache: {
35-
enabled: true,
36-
maxAge: 5 * 60, // 5 minutes
36+
session: {
37+
expiresIn: 60 * 60 * 24 * 7, // 7 days
38+
updateAge: 60 * 60 * 24, // 1 day
39+
cookieCache: {
40+
enabled: true,
41+
maxAge: 5 * 60, // 5 minutes
42+
},
3743
},
44+
plugins: [nextCookies()],
45+
});
46+
}
47+
48+
function getAuth(): AuthInstance {
49+
if (!_auth) {
50+
_auth = createAuth();
51+
}
52+
return _auth;
53+
}
54+
55+
export const auth = new Proxy({} as AuthInstance, {
56+
get(_target, prop) {
57+
return Reflect.get(getAuth(), prop);
3858
},
39-
plugins: [nextCookies()],
4059
});
4160

42-
export type BetterAuthSession = typeof auth.$Infer.Session;
61+
export type BetterAuthSession = AuthInstance["$Infer"]["Session"];
4362
export type BetterAuthUser = BetterAuthSession["user"];
4463
export type BetterAuthSessionData = BetterAuthSession["session"];

apps/nextjs/common/di/container.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContainer } from "@evyweb/ioctopus";
1+
import { type Container, createContainer } from "@evyweb/ioctopus";
22
import type { InMemoryEventDispatcher } from "@/adapters/events/in-memory-event-dispatcher";
33
import { createAuthModule } from "./modules/auth.module";
44
import { createBillingModule } from "./modules/billing.module";
@@ -9,24 +9,36 @@ import {
99
} from "./modules/events.module";
1010
import { type DI_RETURN_TYPES, DI_SYMBOLS } from "./types";
1111

12-
const ApplicationContainer = createContainer();
12+
let _container: Container | null = null;
13+
let _initialized = false;
1314

14-
ApplicationContainer.load(Symbol("EventsModule"), createEventsModule());
15-
ApplicationContainer.load(Symbol("AuthModule"), createAuthModule());
16-
ApplicationContainer.load(Symbol("BillingModule"), createBillingModule());
17-
ApplicationContainer.load(Symbol("EmailModule"), createEmailModule());
15+
function getContainer(): Container {
16+
if (!_container) {
17+
_container = createContainer();
1818

19-
const dispatcher = ApplicationContainer.get(
20-
DI_SYMBOLS.IEventDispatcher,
21-
) as InMemoryEventDispatcher;
19+
_container.load(Symbol("EventsModule"), createEventsModule());
20+
_container.load(Symbol("AuthModule"), createAuthModule());
21+
_container.load(Symbol("BillingModule"), createBillingModule());
22+
_container.load(Symbol("EmailModule"), createEmailModule());
23+
}
2224

23-
const emailService = ApplicationContainer.get(
24-
DI_SYMBOLS.IEmailService,
25-
) as DI_RETURN_TYPES["IEmailService"];
26-
registerEventHandlers(dispatcher, emailService);
25+
if (!_initialized) {
26+
_initialized = true;
27+
const dispatcher = _container.get(
28+
DI_SYMBOLS.IEventDispatcher,
29+
) as InMemoryEventDispatcher;
30+
31+
const emailService = _container.get(
32+
DI_SYMBOLS.IEmailService,
33+
) as DI_RETURN_TYPES["IEmailService"];
34+
registerEventHandlers(dispatcher, emailService);
35+
}
36+
37+
return _container;
38+
}
2739

2840
export function getInjection<K extends keyof typeof DI_SYMBOLS>(
2941
symbol: K,
3042
): DI_RETURN_TYPES[K] {
31-
return ApplicationContainer.get(DI_SYMBOLS[symbol]);
43+
return getContainer().get(DI_SYMBOLS[symbol]);
3244
}

apps/nextjs/src/adapters/email/resend-email.service.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,29 @@ import type {
66
} from "@/application/ports/email.service.port";
77

88
export class ResendEmailService implements IEmailService {
9-
private resend: Resend;
10-
private fromAddress: string;
11-
12-
constructor() {
13-
const apiKey = process.env.RESEND_API_KEY;
14-
if (!apiKey) {
15-
throw new Error("RESEND_API_KEY is not configured");
9+
private _resend: Resend | null = null;
10+
private _fromAddress: string | null = null;
11+
12+
private getResend(): Resend {
13+
if (!this._resend) {
14+
const apiKey = process.env.RESEND_API_KEY;
15+
if (!apiKey) {
16+
throw new Error("RESEND_API_KEY is not configured");
17+
}
18+
this._resend = new Resend(apiKey);
1619
}
20+
return this._resend;
21+
}
1722

18-
const fromAddress = process.env.EMAIL_FROM;
19-
if (!fromAddress) {
20-
throw new Error("EMAIL_FROM is not configured");
23+
private getFromAddress(): string {
24+
if (!this._fromAddress) {
25+
const fromAddress = process.env.EMAIL_FROM;
26+
if (!fromAddress) {
27+
throw new Error("EMAIL_FROM is not configured");
28+
}
29+
this._fromAddress = fromAddress;
2130
}
22-
23-
this.resend = new Resend(apiKey);
24-
this.fromAddress = fromAddress;
31+
return this._fromAddress;
2532
}
2633

2734
async send(params: SendEmailParams): Promise<Result<void>> {
@@ -30,8 +37,8 @@ export class ResendEmailService implements IEmailService {
3037
return Result.fail("Email must have either html or text content");
3138
}
3239

33-
const { error } = await this.resend.emails.send({
34-
from: this.fromAddress,
40+
const { error } = await this.getResend().emails.send({
41+
from: this.getFromAddress(),
3542
to: params.to,
3643
subject: params.subject,
3744
html: params.html ?? "",

packages/drizzle/src/config.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@ import type { ExtractTablesWithRelations } from "drizzle-orm/relations";
44
import { Pool } from "pg";
55
import * as schema from "./schema";
66

7-
const connectionString = process.env.DATABASE_URL;
7+
let _db: ReturnType<typeof drizzle<typeof schema>> | null = null;
88

9-
if (!connectionString) {
10-
throw new Error("DATABASE_URL is not set");
9+
function getDb(): ReturnType<typeof drizzle<typeof schema>> {
10+
if (!_db) {
11+
const connectionString = process.env.DATABASE_URL;
12+
if (!connectionString) {
13+
throw new Error("DATABASE_URL is not set");
14+
}
15+
const pool = new Pool({ connectionString });
16+
_db = drizzle(pool, { schema });
17+
}
18+
return _db;
1119
}
1220

13-
const pool = new Pool({ connectionString });
14-
export const db = drizzle(pool, { schema });
21+
export const db = new Proxy({} as ReturnType<typeof drizzle<typeof schema>>, {
22+
get(_target, prop) {
23+
return Reflect.get(getDb(), prop);
24+
},
25+
});
1526

16-
export type DbClient = typeof db;
27+
export type DbClient = ReturnType<typeof drizzle<typeof schema>>;
1728

1829
export type Transaction = NodePgTransaction<
1930
typeof schema,

0 commit comments

Comments
 (0)