-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathroute-handler.ts
116 lines (111 loc) · 3.76 KB
/
route-handler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { type NextRequest, NextResponse } from "next/server.js";
import type { HandlerConfig } from "./server-action.ts";
import { Array, Cause, Effect, Exit, Layer, Option, Schema as S } from "effect";
import { Next } from "./next-service.ts";
import { RequestContext } from "./request-context.ts";
import { encodingJson, getEncoding, getStatus } from "./annotations.ts";
export type RouteHandlerConfig<
InternalServerError,
InvalidPayloadError,
ProvidedServices,
Responses extends S.Schema.AnyNoContext[],
> =
& HandlerConfig<InternalServerError, InvalidPayloadError, ProvidedServices>
& {
responses: Responses;
};
export const makeRouteHandler = <
InternalServerError,
InvalidPayloadError,
ProvidedServices,
Responses extends S.Schema.AnyNoContext[],
>(
config: RouteHandlerConfig<
InternalServerError,
InvalidPayloadError,
ProvidedServices,
Responses
>,
) => {
const mergedContext = Layer.mergeAll(
config.layer ?? Layer.empty,
Next.Default,
);
return <A, E>(effect: Effect.Effect<A, E, ProvidedServices>) => {
return async (request: NextRequest): Promise<NextResponse<A | E>> => {
// @ts-expect-error: types are correct
const responseExit = await Effect.runPromiseExit(effect.pipe(
Effect.catchTag(
// @ts-expect-error: this can be thrown by Next service, so we need to catch it
"NextUnexpectedError",
(error) => Effect.fail(config.errors.unexpected(Cause.fail(error))),
),
Effect.catchTag(
// @ts-expect-error: this can be thrown by Next service, so we need to catch it
"NextPayloadError",
(payload) => Effect.fail(config.errors.invalidPayload(payload)),
),
Effect.provide(mergedContext),
Effect.provideService(
RequestContext,
RequestContext.of({
rawRequest: request,
type: "route-handler",
requestId: config.generateRequestId?.() ?? crypto.randomUUID(),
}),
),
));
if (Exit.isSuccess(responseExit)) {
const matchingResponseSchema = Array.findFirst(
config.responses,
(schema) => S.is(schema)(responseExit.value),
).pipe(
Option.getOrUndefined,
);
const statusCode = matchingResponseSchema
? getStatus(matchingResponseSchema.ast)
: 200;
const encoding = matchingResponseSchema
? getEncoding(matchingResponseSchema.ast)
: encodingJson;
return new NextResponse(
encoding.kind === "Json"
? JSON.stringify(responseExit.value)
: responseExit.value as BodyInit,
{
status: statusCode,
headers: { "Content-Type": encoding.contentType },
},
);
}
if (Cause.isFailType(responseExit.cause)) {
const matchingResponseSchema = Array.findFirst(
config.responses,
(schema) =>
S.is(schema)((responseExit.cause as Cause.Fail<unknown>).error),
).pipe(
Option.getOrUndefined,
);
const statusCode = matchingResponseSchema
? getStatus(matchingResponseSchema.ast)
: 400;
const encoding = matchingResponseSchema
? getEncoding(matchingResponseSchema.ast)
: encodingJson;
return new NextResponse(
encoding.kind === "Json"
? JSON.stringify(responseExit.cause.error)
: responseExit.cause.error as BodyInit,
{
status: statusCode,
headers: { "Content-Type": encoding.contentType },
},
);
}
// @ts-expect-error: types are correct
return NextResponse.json(config.errors.unexpected(responseExit.cause), {
status: 500,
});
};
};
};