1import * as Lambda from "alchemy/AWS/Lambda";
2import * as Neon from "alchemy/Neon";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import path from "pathe";
9import { BetterAuth } from "../../../src/index.ts";
10import { Neon as NeonDatabase } from "../../../src/Neon.ts";
11
12const main = path.resolve(import.meta.dirname, "auth-handler.ts");
13
14export const AuthDb = Neon.Project("BetterAuthLambdaPg");
15
16// connectionUri is an Output: bound into the Lambda environment at deploy,
17// read back at runtime; the same source drives the deploy-time migration
18// Action. The serverless driver is the optimal Lambda -> Neon pairing —
19// WebSocket-based, no `pg` install into the artifact needed.
20const AuthDatabase = Layer.unwrap(
21 Effect.map(AuthDb, (project) => NeonDatabase(project.connectionUri)),
22);
23
24export class AuthFunction extends Lambda.Function<Lambda.Function>()(
25 "BetterAuthFunction",
26) {}
27
28export default AuthFunction.make(
29 {
30 main,
31 url: true,
32 // password hashing (scrypt) + the auth bundle need more than the
33 // 128 MB default, and cold start + Neon round-trips more than the
34 // 3 s default timeout
35 memorySize: 512,
36 timeout: Duration.seconds(30),
37 },
38 Effect.gen(function* () {
39 const auth = yield* BetterAuth({
40 basePath: "/auth",
41 emailAndPassword: { enabled: true },
42 });
43
44 return {
45 fetch: Effect.gen(function* () {
46 const request = yield* HttpServerRequest;
47 // On Lambda the request url is absolute — route on the pathname.
48 const pathname = new URL(request.url, "http://lambda").pathname;
49 if (pathname.startsWith("/auth")) {
50 return yield* auth.fetch;
51 }
52 if (pathname.startsWith("/me")) {
53 const session = yield* auth
54 .getSession()
55 .pipe(
56 Effect.catchTag("BetterAuthApiError", () => Effect.succeed(null)),
57 );
58 return yield* HttpServerResponse.json({
59 email: session?.user.email ?? null,
60 });
61 }
62 return HttpServerResponse.text("ok");
63 }),
64 };
65 }).pipe(Effect.provide(AuthDatabase)),
66);