diff --git a/.changeset/fifty-radios-heal.md b/.changeset/fifty-radios-heal.md
new file mode 100644
index 00000000..13aecaf7
--- /dev/null
+++ b/.changeset/fifty-radios-heal.md
@@ -0,0 +1,5 @@
+---
+"@effect-aws/lambda": patch
+---
+
+Refactor `fromHttpApi` function in more effectful way
diff --git a/packages/lambda/src/LambdaHandler.ts b/packages/lambda/src/LambdaHandler.ts
index f4718e0f..431bfe49 100644
--- a/packages/lambda/src/LambdaHandler.ts
+++ b/packages/lambda/src/LambdaHandler.ts
@@ -1,11 +1,10 @@
/**
* @since 1.4.0
*/
-import type { HttpApi, HttpRouter } from "@effect/platform";
+import type { HttpApi, HttpRouter, HttpServerError } from "@effect/platform";
import { HttpApiBuilder, HttpApp } from "@effect/platform";
-import type { Context as LambdaContext } from "aws-lambda";
-import type { Context } from "effect";
-import { Effect, Function, Layer } from "effect";
+import type { Cause } from "effect";
+import { Context, Effect, Function, Layer } from "effect";
import { getEventSource } from "./internal/index.js";
import type { EventSource, LambdaEvent, LambdaResult } from "./internal/types.js";
import { encodeBase64, isContentEncodingBinary, isContentTypeBinary } from "./internal/utils.js";
@@ -75,164 +74,67 @@ export const make: {
// Deprecated case
if (globalLayer) {
const runtime = LambdaRuntime.fromLayer(globalLayer);
- return async (event: T, context: LambdaContext) => handlerOrOptions(event, context).pipe(runtime.runPromise);
+ return async (event, context) => handlerOrOptions(event, context).pipe(runtime.runPromise);
}
- return async (event: T, context: LambdaContext) =>
+ return async (event, context) =>
handlerOrOptions(event, context).pipe(Effect.runPromise as (effect: Effect.Effect) => Promise);
}
- const runtime = LambdaRuntime.fromLayer(handlerOrOptions.layer);
- return async (event: T, context: LambdaContext) => handlerOrOptions.handler(event, context).pipe(runtime.runPromise);
+ const runtime = LambdaRuntime.fromLayer(handlerOrOptions.layer, { memoMap: handlerOrOptions.memoMap });
+ return async (event, context) => handlerOrOptions.handler(event, context).pipe(runtime.runPromise);
};
-// const apiHandler = (options?: {
-// readonly middleware?: (
-// httpApp: HttpApp.Default,
-// ) => HttpApp.Default<
-// never,
-// HttpApi.Api | HttpApiBuilder.Router | HttpRouter.HttpRouter.DefaultServices
-// >;
-// }): EffectHandler<
-// LambdaEvent,
-// HttpApi.Api | HttpApiBuilder.Router | HttpApiBuilder.Middleware | HttpRouter.HttpRouter.DefaultServices,
-// HttpServerError.ResponseError,
-// LambdaResult
-// > =>
-// (event) =>
-// Effect.gen(function*() {
-// const eventSource = getEventSource(event) as EventSource;
-// const requestValues = eventSource.getRequest(event);
+interface HttpApiOptions {
+ readonly middleware?: (
+ httpApp: HttpApp.Default,
+ ) => HttpApp.Default<
+ never,
+ HttpApi.Api | HttpApiBuilder.Router | HttpRouter.HttpRouter.DefaultServices
+ >;
+ readonly memoMap?: Layer.MemoMap;
+}
-// const req = new Request(
-// `https://${requestValues.remoteAddress}${requestValues.path}`,
-// {
-// method: requestValues.method,
-// headers: requestValues.headers,
-// body: requestValues.body,
-// },
-// );
-
-// const app = yield* HttpApiBuilder.httpApp;
-
-// const appWithMiddleware = options?.middleware ? options.middleware(app as any) : app;
-
-// const request = HttpServerRequest.fromWeb(req);
-// const response = yield* appWithMiddleware.pipe(
-// Effect.provideService(HttpServerRequest.HttpServerRequest, request),
-// );
-
-// const handler = yield* FiberRef.get(HttpApp.currentPreResponseHandlers);
-
-// const resp = Option.isSome(handler) ? yield* handler.value(request, response) : response;
-
-// const res = HttpServerResponse.toWeb(resp, { runtime: yield* Effect.runtime() });
-
-// const contentType = res.headers.get("content-type");
-// let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
-
-// if (!isBase64Encoded) {
-// const contentEncoding = res.headers.get("content-encoding");
-// isBase64Encoded = isContentEncodingBinary(contentEncoding);
-// }
-
-// const body = isBase64Encoded
-// ? encodeBase64(yield* Effect.promise(() => res.arrayBuffer()))
-// : yield* Effect.promise(() => res.text());
-
-// const headers: Record = {};
-
-// if (res.headers.has("set-cookie")) {
-// const cookies = res.headers.getSetCookie
-// ? res.headers.getSetCookie()
-// : Array.from((res.headers as any).entries())
-// .filter(([k]: any) => k === "set-cookie")
-// .map(([, v]: any) => v);
-
-// if (Array.isArray(cookies)) {
-// headers["set-cookie"] = cookies.join(", ");
-// res.headers.delete("set-cookie");
-// }
-// }
-
-// res.headers.forEach((value, key) => {
-// headers[key] = value;
-// });
-
-// return eventSource.getResponse({
-// event,
-// statusCode: res.status,
-// body,
-// headers,
-// isBase64Encoded,
-// });
-// });
+type WebHandler = ReturnType;
+const WebHandler = Context.GenericTag("@effect-aws/lambda/WebHandler");
/**
- * Construct a lambda handler from an `HttpApi` instance.
- *
- * @example
- * ```ts
- * import { LambdaHandler } from "@effect-aws/lambda"
- * import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
- * import { Layer } from "effect"
- *
- * class MyApi extends HttpApi.make("api") {}
- *
- * const MyApiLive = HttpApiBuilder.api(MyApi)
- *
- * export const handler = LambdaHandler.fromHttpApi(
- * Layer.mergeAll(
- * MyApiLive,
- * // you could also use NodeHttpServer.layerContext, depending on your
- * // server's platform
- * HttpServer.layerContext
- * )
- * )
- * ```
+ * Construct an `WebHandler` from an `HttpApi` instance.
*
* @since 1.4.0
* @category constructors
*/
-export const fromHttpApi = (
- layer: Layer.Layer,
- options?: {
- readonly middleware?: (
- httpApp: HttpApp.Default,
- ) => HttpApp.Default<
- never,
- HttpApi.Api | HttpApiBuilder.Router | HttpRouter.HttpRouter.DefaultServices
- >;
- readonly memoMap?: Layer.MemoMap;
- },
-): Handler => {
- const runtime = LambdaRuntime.fromLayer(
- Layer.mergeAll(layer, HttpApiBuilder.Router.Live, HttpApiBuilder.Middleware.layer),
- options,
- );
- // // Alternative implementation (I keep it commented here to understand the differences)
- // const handler = apiHandler(options);
- // return async (event: LambdaEvent, context: LambdaContext) => handler(event, context).pipe(runtime.runPromise);
- let handlerCached:
- | ((request: Request, context?: Context.Context | undefined) => Promise)
- | undefined;
-
- const handlerPromise = Effect.gen(function*() {
+export const makeWebHandler = (options?: Pick): Effect.Effect<
+ WebHandler,
+ never,
+ HttpApiBuilder.Router | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices | HttpApiBuilder.Middleware
+> =>
+ Effect.gen(function*() {
const app = yield* HttpApiBuilder.httpApp;
- const rt = yield* runtime.runtimeEffect;
- const handler = HttpApp.toWebHandlerRuntime(rt)(
+ const rt = yield* Effect.runtime();
+ return HttpApp.toWebHandlerRuntime(rt)(
options?.middleware ? options.middleware(app as any) as any : app,
);
- handlerCached = handler;
- return handler;
- }).pipe(runtime.runPromise);
+ });
- async function handler(event: LambdaEvent) {
+/**
+ * Construct an `EffectHandler` from an `HttpApi` instance.
+ *
+ * @since 1.4.0
+ * @category constructors
+ */
+export const httpApiHandler: EffectHandler<
+ LambdaEvent,
+ WebHandler,
+ HttpServerError.ResponseError | Cause.UnknownException,
+ LambdaResult
+> = (event) =>
+ Effect.gen(function*() {
const eventSource = getEventSource(event) as EventSource;
const requestValues = eventSource.getRequest(event);
- const request = new Request(
- `http://${requestValues.remoteAddress}${requestValues.path}`,
+ const req = new Request(
+ `https://${requestValues.remoteAddress}${requestValues.path}`,
{
method: requestValues.method,
headers: requestValues.headers,
@@ -240,9 +142,7 @@ export const fromHttpApi = (
},
);
- const res = handlerCached !== undefined
- ? await handlerCached(request)
- : await handlerPromise.then((handler) => handler(request));
+ const res = yield* WebHandler.pipe(Effect.andThen((handler) => handler(req)));
const contentType = res.headers.get("content-type");
let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
@@ -252,7 +152,9 @@ export const fromHttpApi = (
isBase64Encoded = isContentEncodingBinary(contentEncoding);
}
- const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text();
+ const body = isBase64Encoded
+ ? encodeBase64(yield* Effect.promise(() => res.arrayBuffer()))
+ : yield* Effect.promise(() => res.text());
const headers: Record = {};
@@ -280,7 +182,40 @@ export const fromHttpApi = (
headers,
isBase64Encoded,
});
- }
+ });
- return handler;
+/**
+ * Construct a lambda handler from an `HttpApi` instance.
+ *
+ * @example
+ * ```ts
+ * import { LambdaHandler } from "@effect-aws/lambda"
+ * import { HttpApi, HttpApiBuilder, HttpServer } from "@effect/platform"
+ * import { Layer } from "effect"
+ *
+ * class MyApi extends HttpApi.make("api") {}
+ *
+ * const MyApiLive = HttpApiBuilder.api(MyApi)
+ *
+ * export const handler = LambdaHandler.fromHttpApi(
+ * Layer.mergeAll(
+ * MyApiLive,
+ * // you could also use NodeHttpServer.layerContext, depending on your
+ * // server's platform
+ * HttpServer.layerContext
+ * )
+ * )
+ * ```
+ *
+ * @since 1.4.0
+ * @category constructors
+ */
+export const fromHttpApi = (
+ layer: Layer.Layer,
+ options?: HttpApiOptions,
+): Handler => {
+ const httpApiLayer = Layer.effect(WebHandler, makeWebHandler(options)).pipe(
+ Layer.provide(Layer.mergeAll(layer, HttpApiBuilder.Router.Live, HttpApiBuilder.Middleware.layer)),
+ );
+ return make({ handler: httpApiHandler, layer: httpApiLayer, memoMap: options?.memoMap });
};
diff --git a/packages/lambda/src/Types.ts b/packages/lambda/src/Types.ts
index 93d91c1d..70372d8f 100644
--- a/packages/lambda/src/Types.ts
+++ b/packages/lambda/src/Types.ts
@@ -37,6 +37,7 @@ export type EffectHandler = (
* @category model
*/
export type EffectHandlerWithLayer = {
- handler: EffectHandler;
- layer: Layer.Layer;
+ readonly handler: EffectHandler;
+ readonly layer: Layer.Layer;
+ readonly memoMap?: Layer.MemoMap;
};