Skip to content

Commit 6dc0b02

Browse files
authored
Refactor/access to lambda event (#157)
* feat: implement LambdaHandler.LambdaHandlerArgs accessor * docs: add changeset * fix: improve lambda types * docs: update changeset doc
1 parent f727e5e commit 6dc0b02

26 files changed

+276
-155
lines changed

.changeset/tasty-rice-trade.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@effect-aws/lambda": patch
3+
---
4+
5+
Implement `LambdaHandler.event()` and `LambdaHandler.context()` to access raw lambda event and context
6+
7+
Just yield them within api handler:
8+
9+
```ts
10+
//...
11+
import { type APIGatewayProxyEvent, LambdaHandler } from "@effect-aws/lambda"
12+
//...
13+
const HelloLive = HttpApiBuilder.group(MyApi, "hello", (handlers) =>
14+
handlers.handle("hello", () =>
15+
Effect.gen(function* () {
16+
const event = yield* LambdaHandler.event<APIGatewayProxyEvent>()
17+
const context = yield* LambdaHandler.context()
18+
19+
yield* Effect.logInfo("Lambda event", { event, context })
20+
21+
return yield* Effect.succeed("Hello, World!")
22+
})
23+
)
24+
)
25+
//...
26+
```

packages/lambda/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ npm install --save @effect-aws/lambda
2020
Without dependencies:
2121

2222
```typescript
23-
import type { SNSEvent } from "aws-lambda"
2423
import { Effect } from "effect"
25-
import { EffectHandler, makeLambda } from "@effect-aws/lambda"
24+
import { EffectHandler, makeLambda, SNSEvent } from "@effect-aws/lambda"
2625

2726
// Define your effect handler
2827
const myEffectHandler: EffectHandler<SNSEvent, never> = (event, context) => {
@@ -37,9 +36,8 @@ export const handler = makeLambda(myEffectHandler)
3736
With dependencies:
3837

3938
```typescript
40-
import { EffectHandler, makeLambda } from "@effect-aws/lambda"
39+
import { EffectHandler, makeLambda, SNSEvent } from "@effect-aws/lambda"
4140
import * as Logger from "@effect-aws/powertools-logger"
42-
import type { SNSEvent } from "aws-lambda"
4341
import { Context, Effect, Layer } from "effect"
4442

4543
interface FooService {

packages/lambda/src/Compatibility.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@ import * as LambdaRuntime from "./LambdaRuntime.js";
99
* The global layer is used to provide a runtime which will gracefully handle lambda termination during down-scaling.
1010
*
1111
* @example
12-
* import { makeLambda } from "@effect-aws/lambda";
13-
* import { Context } from "aws-lambda";
12+
* import { makeLambda, LambdaContext } from "@effect-aws/lambda";
1413
* import { Effect } from "effect";
1514
*
16-
* const effectHandler = (event: unknown, context: Context) => {
15+
* const effectHandler = (event: unknown, context: LambdaContext) => {
1716
* return Effect.logInfo("Hello, world!");
1817
* };
1918
*
2019
* export const handler = makeLambda(effectHandler);
2120
*
2221
* @example
23-
* import { makeLambda } from "@effect-aws/lambda";
24-
* import { Context } from "aws-lambda";
22+
* import { makeLambda, LambdaContext } from "@effect-aws/lambda";
2523
* import { Effect, Logger } from "effect";
2624
*
27-
* const effectHandler = (event: unknown, context: Context) => {
25+
* const effectHandler = (event: unknown, context: LambdaContext) => {
2826
* return Effect.logInfo("Hello, world!");
2927
* };
3028
*
@@ -46,15 +44,14 @@ export const makeLambda = LambdaHandler.make;
4644
* All finalizers will be executed on process termination or interruption.
4745
*
4846
* @example
49-
* import { fromLayer } from "@effect-aws/lambda";
50-
* import { Context } from "aws-lambda";
47+
* import { fromLayer, LambdaContext } from "@effect-aws/lambda";
5148
* import { Effect, Logger } from "effect";
5249
*
5350
* const LambdaLayer = Logger.replace(Logger.defaultLogger, Logger.logfmtLogger);
5451
*
5552
* const lambdaRuntime = fromLayer(LambdaLayer);
5653
*
57-
* export const handler = async (event: unknown, context: Context) => {
54+
* export const handler = async (event: unknown, context: LambdaContext) => {
5855
* return Effect.logInfo("Hello, world!").pipe(lambdaRuntime.runPromise);
5956
* };
6057
*

packages/lambda/src/LambdaHandler.ts

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,107 @@
11
/**
22
* @since 1.4.0
33
*/
4-
import type { HttpApi, HttpRouter, HttpServerError } from "@effect/platform";
4+
import type { HttpApi, HttpRouter } from "@effect/platform";
55
import { HttpApiBuilder, HttpApp } from "@effect/platform";
66
import type { Cause } from "effect";
77
import { Context, Effect, Function, Layer } from "effect";
88
import { getEventSource } from "./internal/index.js";
9-
import type { EventSource, LambdaEvent, LambdaResult } from "./internal/types.js";
9+
import * as internal from "./internal/lambdaHandler.js";
10+
import type { EventSource } from "./internal/types.js";
1011
import { encodeBase64, isContentEncodingBinary, isContentTypeBinary } from "./internal/utils.js";
1112
import * as LambdaRuntime from "./LambdaRuntime.js";
12-
import type { EffectHandler, EffectHandlerWithLayer, Handler } from "./Types.js";
13+
import type {
14+
ALBEvent,
15+
ALBResult,
16+
APIGatewayProxyEvent,
17+
APIGatewayProxyEventV2,
18+
APIGatewayProxyResult,
19+
APIGatewayProxyResultV2,
20+
CloudFrontRequestEvent,
21+
DynamoDBStreamEvent,
22+
EffectHandler,
23+
EffectHandlerWithLayer,
24+
EventBridgeEvent,
25+
Handler,
26+
KinesisStreamEvent,
27+
LambdaContext,
28+
S3Event,
29+
SelfManagedKafkaEvent,
30+
SNSEvent,
31+
SQSEvent,
32+
} from "./Types.js";
33+
34+
/**
35+
* @since 1.4.0
36+
*/
37+
export declare namespace LambdaHandler {
38+
/**
39+
* @since 1.4.0
40+
* @category model
41+
*/
42+
export type Event =
43+
| ALBEvent
44+
| APIGatewayProxyEvent
45+
| APIGatewayProxyEventV2
46+
| EventBridgeEvent<string, unknown>
47+
| DynamoDBStreamEvent
48+
| KinesisStreamEvent
49+
| S3Event
50+
| SelfManagedKafkaEvent
51+
| SNSEvent
52+
| SQSEvent
53+
| CloudFrontRequestEvent;
54+
55+
/**
56+
* @since 1.4.0
57+
* @category model
58+
*/
59+
export type Result =
60+
| ALBResult
61+
| APIGatewayProxyResult
62+
| APIGatewayProxyResultV2
63+
| void;
64+
}
65+
66+
/**
67+
* @since 1.4.0
68+
* @category context
69+
*/
70+
export const event = <T extends LambdaHandler.Event>(): Effect.Effect<T> =>
71+
Effect.map(
72+
Effect.context<never>(),
73+
(context) => Context.unsafeGet(context, internal.lambdaEventTag),
74+
) as Effect.Effect<T>;
75+
76+
/**
77+
* @since 1.4.0
78+
* @category context
79+
*/
80+
export const context = (): Effect.Effect<LambdaContext> =>
81+
Effect.map(
82+
Effect.context<never>(),
83+
(context) => Context.unsafeGet(context, internal.lambdaContextTag),
84+
);
1385

1486
/**
1587
* Makes a lambda handler from the given EffectHandler and optional global layer.
1688
* The global layer is used to provide a runtime which will gracefully handle lambda termination during down-scaling.
1789
*
1890
* @example
19-
* import { LambdaHandler } from "@effect-aws/lambda"
20-
* import { Context } from "aws-lambda";
91+
* import { LambdaHandler, LambdaContext } from "@effect-aws/lambda"
2192
* import { Effect } from "effect";
2293
*
23-
* const effectHandler = (event: unknown, context: Context) => {
94+
* const effectHandler = (event: unknown, context: LambdaContext) => {
2495
* return Effect.logInfo("Hello, world!");
2596
* };
2697
*
2798
* export const handler = LambdaHandler.make(effectHandler);
2899
*
29100
* @example
30-
* import { LambdaHandler } from "@effect-aws/lambda"
31-
* import { Context } from "aws-lambda";
101+
* import { LambdaHandler, LambdaContext } from "@effect-aws/lambda"
32102
* import { Effect, Logger } from "effect";
33103
*
34-
* const effectHandler = (event: unknown, context: Context) => {
104+
* const effectHandler = (event: unknown, context: LambdaContext) => {
35105
* return Effect.logInfo("Hello, world!");
36106
* };
37107
*
@@ -107,11 +177,18 @@ const WebHandler = Context.GenericTag<WebHandler>("@effect-aws/lambda/WebHandler
107177
export const makeWebHandler = (options?: Pick<HttpApiOptions, "middleware">): Effect.Effect<
108178
WebHandler,
109179
never,
110-
HttpApiBuilder.Router | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices | HttpApiBuilder.Middleware
180+
| HttpApiBuilder.Router
181+
| HttpApi.Api
182+
| HttpRouter.HttpRouter.DefaultServices
183+
| HttpApiBuilder.Middleware
184+
| LambdaHandler.Event
185+
| LambdaContext
111186
> =>
112187
Effect.gen(function*() {
113188
const app = yield* HttpApiBuilder.httpApp;
114-
const rt = yield* Effect.runtime<HttpRouter.HttpRouter.DefaultServices>();
189+
const rt = yield* Effect.runtime<
190+
HttpRouter.HttpRouter.DefaultServices | LambdaHandler.Event | LambdaContext
191+
>();
115192
return HttpApp.toWebHandlerRuntime(rt)(
116193
options?.middleware ? options.middleware(app as any) as any : app,
117194
);
@@ -123,14 +200,15 @@ export const makeWebHandler = (options?: Pick<HttpApiOptions, "middleware">): Ef
123200
* @since 1.4.0
124201
* @category constructors
125202
*/
126-
export const httpApiHandler: EffectHandler<
127-
LambdaEvent,
128-
WebHandler,
129-
HttpServerError.ResponseError | Cause.UnknownException,
130-
LambdaResult
131-
> = (event) =>
203+
export const httpApiHandler = (options?: Pick<HttpApiOptions, "middleware">): EffectHandler<
204+
LambdaHandler.Event,
205+
HttpApi.Api | HttpApiBuilder.Router | HttpRouter.HttpRouter.DefaultServices | HttpApiBuilder.Middleware,
206+
Cause.UnknownException,
207+
LambdaHandler.Result
208+
> =>
209+
(event, context) =>
132210
Effect.gen(function*() {
133-
const eventSource = getEventSource(event) as EventSource<LambdaEvent, LambdaResult>;
211+
const eventSource = getEventSource(event) as EventSource<LambdaHandler.Event, LambdaHandler.Result>;
134212
const requestValues = eventSource.getRequest(event);
135213

136214
const req = new Request(
@@ -142,7 +220,11 @@ export const httpApiHandler: EffectHandler<
142220
},
143221
);
144222

145-
const res = yield* WebHandler.pipe(Effect.andThen((handler) => handler(req)));
223+
const res = yield* makeWebHandler(options).pipe(
224+
Effect.provideService(internal.lambdaEventTag, event),
225+
Effect.provideService(internal.lambdaContextTag, context),
226+
Effect.andThen((handler) => handler(req)),
227+
);
146228

147229
const contentType = res.headers.get("content-type");
148230
let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false;
@@ -213,9 +295,7 @@ export const httpApiHandler: EffectHandler<
213295
export const fromHttpApi = <LA, LE>(
214296
layer: Layer.Layer<LA | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices, LE>,
215297
options?: HttpApiOptions,
216-
): Handler<LambdaEvent, LambdaResult> => {
217-
const httpApiLayer = Layer.effect(WebHandler, makeWebHandler(options)).pipe(
218-
Layer.provide(Layer.mergeAll(layer, HttpApiBuilder.Router.Live, HttpApiBuilder.Middleware.layer)),
219-
);
220-
return make({ handler: httpApiHandler, layer: httpApiLayer, memoMap: options?.memoMap });
298+
): Handler<LambdaHandler.Event, LambdaHandler.Result> => {
299+
const httpApiLayer = Layer.mergeAll(layer, HttpApiBuilder.Router.Live, HttpApiBuilder.Middleware.layer);
300+
return make({ handler: httpApiHandler(options), layer: httpApiLayer, memoMap: options?.memoMap });
221301
};

packages/lambda/src/LambdaRuntime.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import { Console, Effect, ManagedRuntime } from "effect";
99
* All finalizers will be executed on process termination or interruption.
1010
*
1111
* @example
12-
* import { LambdaRuntime } from "@effect-aws/lambda";
13-
* import { Context } from "aws-lambda";
12+
* import { LambdaRuntime, LambdaContext } from "@effect-aws/lambda";
1413
* import { Effect, Logger } from "effect";
1514
*
1615
* const LambdaLayer = Logger.replace(Logger.defaultLogger, Logger.logfmtLogger);
1716
*
1817
* const lambdaRuntime = LambdaRuntime.fromLayer(LambdaLayer);
1918
*
20-
* export const handler = async (event: unknown, context: Context) => {
19+
* export const handler = async (event: unknown, context: LambdaContext) => {
2120
* return Effect.logInfo("Hello, world!").pipe(lambdaRuntime.runPromise);
2221
* };
2322
*

packages/lambda/src/Types.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
/**
22
* @since 1.0.0
33
*/
4-
import type { Context } from "aws-lambda";
4+
import type {
5+
ALBEvent,
6+
ALBResult,
7+
APIGatewayProxyEvent,
8+
APIGatewayProxyEventV2,
9+
APIGatewayProxyResult,
10+
APIGatewayProxyResultV2,
11+
CloudFrontRequestEvent,
12+
Context,
13+
DynamoDBStreamEvent,
14+
EventBridgeEvent,
15+
KinesisStreamEvent,
16+
S3Event,
17+
SelfManagedKafkaEvent,
18+
SNSEvent,
19+
SQSEvent,
20+
} from "aws-lambda";
521
import { type Layer } from "effect";
622
import type * as Effect from "effect/Effect";
723

@@ -41,3 +57,66 @@ export type EffectHandlerWithLayer<T, R, E1 = never, E2 = never, A = void> = {
4157
readonly layer: Layer.Layer<R, E2>;
4258
readonly memoMap?: Layer.MemoMap;
4359
};
60+
61+
export {
62+
/**
63+
* @since 1.4.0
64+
*/
65+
ALBEvent,
66+
/**
67+
* @since 1.4.0
68+
*/
69+
ALBResult,
70+
/**
71+
* @since 1.4.0
72+
*/
73+
APIGatewayProxyEvent,
74+
/**
75+
* @since 1.4.0
76+
*/
77+
APIGatewayProxyEventV2,
78+
/**
79+
* @since 1.4.0
80+
*/
81+
APIGatewayProxyResult,
82+
/**
83+
* @since 1.4.0
84+
*/
85+
APIGatewayProxyResultV2,
86+
/**
87+
* @since 1.4.0
88+
*/
89+
CloudFrontRequestEvent,
90+
/**
91+
* @since 1.4.0
92+
*/
93+
Context as LambdaContext,
94+
/**
95+
* @since 1.4.0
96+
*/
97+
DynamoDBStreamEvent,
98+
/**
99+
* @since 1.4.0
100+
*/
101+
EventBridgeEvent,
102+
/**
103+
* @since 1.4.0
104+
*/
105+
KinesisStreamEvent,
106+
/**
107+
* @since 1.4.0
108+
*/
109+
S3Event,
110+
/**
111+
* @since 1.4.0
112+
*/
113+
SelfManagedKafkaEvent,
114+
/**
115+
* @since 1.4.0
116+
*/
117+
SNSEvent,
118+
/**
119+
* @since 1.4.0
120+
*/
121+
SQSEvent,
122+
};

packages/lambda/src/internal/aws/alb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import url from "node:url";
22

3-
import type { ALBEvent, ALBResult } from "aws-lambda";
3+
import type { ALBEvent, ALBResult } from "../../Types.js";
44
import type { EventSource } from "../types.js";
55
import { getMultiValueHeaders, getRequestValuesFromEvent } from "../utils.js";
66

0 commit comments

Comments
 (0)