1import type * as lambda from "aws-lambda";
2import * as Context from "effect/Context";
3import type * as Effect from "effect/Effect";
4import type { RestApi } from "./RestApi.ts";
5
6/**
7 * The event shape a REST API route handler receives — the API Gateway v1
8 * Lambda proxy event (`httpMethod`, `resource`, `pathParameters`, `body`,
9 * `requestContext`, …).
10 */
11export type RestApiEvent = lambda.APIGatewayProxyEvent;
12
13/**
14 * The result a REST API route handler may return. Returning `void` means
15 * `{ statusCode: 200, body: "" }`.
16 */
17export type RestApiResult = lambda.APIGatewayProxyResult | undefined | void;
18
19export interface RestApiRouteProps {
20 /**
21 * The resource path this handler serves, e.g. `/`, `/items`, or a greedy
22 * proxy like `/{proxy+}`. Intermediate path segments are materialized as
23 * `ApiGateway.Resource`s automatically.
24 * @default "/"
25 */
26 path?: string;
27 /**
28 * The HTTP verb to serve (`GET`, `POST`, …) or `ANY` for all verbs.
29 * @default "ANY"
30 */
31 httpMethod?: string;
32}
33
34export type RestApiEventSourceService = <Req = never>(
35 api: RestApi,
36 props: RestApiRouteProps,
37 handler: (event: RestApiEvent) => Effect.Effect<RestApiResult, never, Req>,
38) => Effect.Effect<void, never, never>;
39
40/**
41 * Event source connecting a {@link RestApi} route to the hosting Lambda
42 * function.
43 *
44 * At deploy time the Lambda implementation (`Lambda.RestApiEventSource`)
45 * materializes the path `Resource`s, the `Method` with an `AWS_PROXY`
46 * integration, and the invoke `Permission` for the route — and registers
47 * each of them as bindings on the API so any `Deployment` of the same API
48 * is ordered after them. At runtime it dispatches matching REST proxy
49 * events to the handler. Subscribe routes with {@link onRestApiRoute} and
50 * provide `Lambda.RestApiEventSource` on the hosting function.
51 *
52 * ### Handling REST API routes
53 * **Example:** Serve GET /items from a Lambda
54 * ```typescript
55 * export default MyFunction.make(
56 * { main: import.meta.url },
57 * Effect.gen(function* () {
58 * const api = yield* ApiGateway.RestApi("Api", {
59 * endpointConfiguration: { types: ["REGIONAL"] },
60 * });
61 *
62 * yield* ApiGateway.onRestApiRoute(
63 * api,
64 * { path: "/items", httpMethod: "GET" },
65 * (event) =>
66 * Effect.succeed({
67 * statusCode: 200,
68 * body: JSON.stringify({ items: [] }),
69 * }),
70 * );
71 *
72 * const deployment = yield* ApiGateway.Deployment("Release", {
73 * restApi: api,
74 * });
75 * yield* ApiGateway.Stage("Prod", {
76 * restApi: api,
77 * stageName: "prod",
78 * deploymentId: deployment.deploymentId,
79 * });
80 *
81 * return {};
82 * }).pipe(Effect.provide(Lambda.RestApiEventSource)),
83 * );
84 * ```
85 *
86 * @binding
87 */
88export class RestApiEventSource extends Context.Service<
89 RestApiEventSource,
90 RestApiEventSourceService
91>()("AWS.ApiGateway.RestApiEventSource") {}
92
93/**
94 * Subscribe an Effect handler to a route of an API Gateway v1
95 * {@link RestApi}.
96 *
97 * Provide `Lambda.RestApiEventSource` on the hosting function to satisfy
98 * the requirement.
99 *
100 * @param api The REST API.
101 * @param props The path + verb to serve.
102 * @param handler Invoked once per proxy event for the route.
103 *
104 * @example
105 * ```typescript
106 * yield* ApiGateway.onRestApiRoute(api, { path: "/", httpMethod: "GET" }, () =>
107 * Effect.succeed({ statusCode: 200, body: "ok" }),
108 * );
109 * ```
110 */
111export function onRestApiRoute<Req = never>(
112 api: RestApi,
113 props: RestApiRouteProps,
114 handler: (event: RestApiEvent) => Effect.Effect<RestApiResult, never, Req>,
115): Effect.Effect<void, never, RestApiEventSource> {
116 return RestApiEventSource.use((source) => source(api, props, handler));
117}