Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2.0.0-beta.7</Version>
<Version>2.0.0-beta.8</Version>
<!-- SPDX license identifier for MIT -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- Other useful metadata -->
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ builder.Services.AddScoped<IGreetingService, GreetingService>();
var lambda = builder.Build();

// Inject the service directly into the handler
lambda.MapHandler(([Event] string input, IGreetingService greeting) => greeting.Greet(input));
lambda.MapHandler(([FromEvent] string input, IGreetingService greeting) => greeting.Greet(input));

await lambda.RunAsync();

Expand Down
6 changes: 3 additions & 3 deletions docs/features/envelopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var builder = LambdaApplication.CreateBuilder();
var lambda = builder.Build();

lambda.MapHandler(
([Event] SqsEnvelope<OrderMessage> envelope, ILogger<Program> logger) =>
([FromEvent] SqsEnvelope<OrderMessage> envelope, ILogger<Program> logger) =>
{
foreach (var record in envelope.Records)
{
Expand Down Expand Up @@ -90,7 +90,7 @@ different success and error response types.
```csharp title="API Gateway Example" linenums="1"
using MinimalLambda.Envelopes.ApiGateway;

lambda.MapHandler(([Event] ApiGatewayRequestEnvelope<LoginRequest> request) =>
lambda.MapHandler(([FromEvent] ApiGatewayRequestEnvelope<LoginRequest> request) =>
{
// Each return statement uses a different strongly typed model
if (string.IsNullOrEmpty(request.BodyContent?.Username))
Expand Down Expand Up @@ -273,7 +273,7 @@ public sealed class CustomRequestEnvelope : IRequestEnvelope
In a handler:

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(([Event] CustomRequestEnvelope envelope) =>
lambda.MapHandler(([FromEvent] CustomRequestEnvelope envelope) =>
{
if (envelope.PayloadContent is null)
return new { Error = "Invalid payload" };
Expand Down
4 changes: 2 additions & 2 deletions docs/features/open_telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ lambda.UseOpenTelemetryTracing();// (7)!
lambda.OnShutdownFlushOpenTelemetry();// (8)!

lambda.MapHandler(// (9)!
async ([Event] Request request, ILogger<Program> logger, CancellationToken cancellationToken) =>
async ([FromEvent] Request request, ILogger<Program> logger, CancellationToken cancellationToken) =>
{
logger.LogInformation("Responding to {Name}", request.Name);

Expand Down Expand Up @@ -290,7 +290,7 @@ namespace MinimalLambda.Example.OpenTelemetry;
internal static class Function
{
internal static async Task<Response> Handler(
[Event] Request request,
[FromEvent] Request request,
IService service,
Instrumentation instrumentation,
CancellationToken cancellationToken
Expand Down
8 changes: 4 additions & 4 deletions docs/getting-started/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Every event runs through the middleware pipeline and handler you registered with
**Invocation steps:**

1. Lambda runtime delivers the JSON payload.
2. The generated handler deserializes it (if you marked a parameter with `[Event]`).
2. The generated handler deserializes it (if you marked a parameter with `[FromEvent]`).
3. `MinimalLambda` creates a scoped `IServiceProvider` for the invocation.
4. Middleware executes in the order it was registered.
5. Your handler runs and can resolve services/contexts from DI.
Expand Down Expand Up @@ -132,7 +132,7 @@ Every middleware component and handler can ask for `ILambdaHostContext`. Think o

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(async (
[Event] OrderRequest request,
[FromEvent] OrderRequest request,
ILambdaHostContext context,
IOrderService service,
CancellationToken ct
Expand All @@ -151,7 +151,7 @@ lambda.MapHandler(async (

Handlers and lifecycle hooks can request multiple parameter types simultaneously:

- `[Event] T event` – Optional marker for the deserialized payload. Include it only when your Lambda expects input; the generator enforces that at most one parameter carries `[Event]`.
- `[FromEvent] T event` – Optional marker for the deserialized payload. Include it only when your Lambda expects input; the generator enforces that at most one parameter carries `[FromEvent]`.
- Services – Any registered service, keyed service (`[FromKeyedServices("key")]`), or options type.
- Context – `ILambdaHostContext` or the raw `ILambdaContext` from the AWS SDK.
- `CancellationToken` – Linked to end-to-end timeouts; pass it downstream.
Expand All @@ -176,7 +176,7 @@ lambda.UseMiddleware(async (context, next) =>
await next(context);
});

lambda.MapHandler(([Event] OrderRequest order) => new OrderResponse(order.Id, true));
lambda.MapHandler(([FromEvent] OrderRequest order) => new OrderResponse(order.Id, true));
```

**Ordering guidance:**
Expand Down
16 changes: 8 additions & 8 deletions docs/getting-started/first-lambda.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,21 @@ lambda.UseMiddleware(

## Step 6: Register the Handler

Map your handler function with the `[Event]` attribute to mark the Lambda event parameter.
Map your handler function with the `[FromEvent]` attribute to mark the Lambda event parameter.

```csharp title="Program.cs (continued)"
// Register the handler with dependency injection
lambda.MapHandler(
([Event] GreetingRequest request, IGreetingService service) =>
([FromEvent] GreetingRequest request, IGreetingService service) =>
{
var message = service.GetGreeting(request.Name, request.Language);
return new GreetingResponse(message, DateTime.UtcNow);
}
);
```

!!! warning "The [Event] Attribute"
Add `[Event]` to at most one handler parameter to mark it as the deserialized Lambda event. If your handler does not accept an event payload (e.g., scheduled invocations or DI-only inputs), you can omit the attribute entirely.
!!! warning "The [FromEvent] Attribute"
Add `[FromEvent]` to at most one handler parameter to mark it as the deserialized Lambda event. If your handler does not accept an event payload (e.g., scheduled invocations or DI-only inputs), you can omit the attribute entirely.

## Step 7: Run the Lambda

Expand Down Expand Up @@ -219,7 +219,7 @@ lambda.UseMiddleware(

// Handler
lambda.MapHandler(
([Event] GreetingRequest request, IGreetingService service) =>
([FromEvent] GreetingRequest request, IGreetingService service) =>
{
var message = service.GetGreeting(request.Name, request.Language);
return new GreetingResponse(message, DateTime.UtcNow);
Expand Down Expand Up @@ -569,9 +569,9 @@ sequenceDiagram

**Solution**: Verify you've added `[property: JsonPropertyName("...")]` attributes to your record properties.

### Build Errors with [Event] Attribute
### Build Errors with [FromEvent] Attribute

**Error**: `The [Event] attribute is not recognized`
**Error**: `The [FromEvent] attribute is not recognized`

**Solution**: Add `using MinimalLambda.Builder;` to the top of your file (or fully qualify `[MinimalLambda.Builder.Event]`). The attribute ships with the `MinimalLambda` package—no additional project configuration is required.

Expand All @@ -598,7 +598,7 @@ Congratulations! You've built and deployed a complete Lambda function. You now u
- ✅ Defining strongly-typed request/response models
- ✅ Creating and registering services with dependency injection
- ✅ Adding middleware for cross-cutting concerns
- ✅ Using the `[Event]` attribute for handler parameters
- ✅ Using the `[FromEvent]` attribute for handler parameters
- ✅ Testing locally with the Lambda Test Tool
- ✅ Deploying to AWS with SAM, CDK, or manual methods

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ using MinimalLambda;
var builder = LambdaApplication.CreateBuilder();
var lambda = builder.Build();

lambda.MapHandler(([Event] string input) => input.ToUpper());
lambda.MapHandler(([FromEvent] string input) => input.ToUpper());

await lambda.RunAsync();
```
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ builder.Services.ConfigureLambdaHostOptions(options =>
options.InvocationCancellationBuffer = TimeSpan.FromSeconds(5);
});

lambda.MapHandler(async ([Event] Order order, IOrderService service, CancellationToken ct) =>
lambda.MapHandler(async ([FromEvent] Order order, IOrderService service, CancellationToken ct) =>
await service.ProcessAsync(order, ct));
```

Expand Down
6 changes: 3 additions & 3 deletions docs/guides/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ across middleware and handlers:

```csharp title="Handlers"
lambda.MapHandler(async (
[Event] OrderRequest request,
[FromEvent] OrderRequest request,
IOrderService orders, // scoped service
ILambdaHostContext context, // framework context
CancellationToken cancellation // host-managed token
Expand All @@ -65,7 +65,7 @@ lambda.MapHandler(async (
- `Properties` – cross-invocation storage backed by the singleton container
- `Features` – typed feature collections (advanced scenarios)

If your handler doesn't need the Lambda payload, omit the `[Event]` parameter entirely and inject only services.
If your handler doesn't need the Lambda payload, omit the `[FromEvent]` parameter entirely and inject only services.

!!! tip "Cancellation buffers"
The cancellation token fires slightly **before** AWS kills the process:
Expand Down Expand Up @@ -119,7 +119,7 @@ builder.Services.AddKeyedSingleton<INotifier, EmailNotifier>("email");
builder.Services.AddKeyedSingleton<INotifier, SmsNotifier>("sms");

lambda.MapHandler((
[Event] Order order,
[FromEvent] Order order,
[FromKeyedServices("sms")] INotifier notifier
) => notifier.NotifyAsync(order));
```
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ hooks so you can add the right amount of protection without fighting the framewo
want different logging, metrics, or response shaping.

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(([Event] OrderRequest request, IOrderService service) =>
lambda.MapHandler(([FromEvent] OrderRequest request, IOrderService service) =>
service.ProcessAsync(request) // unhandled exception flows to Lambda runtime
);
```
Expand Down Expand Up @@ -63,7 +63,7 @@ Keep handlers thin, but do catch exceptions when you want a different payload or
logic. Leave everything else to your middleware/global policy.

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(async ([Event] CheckoutRequest request, ICheckoutService service) =>
lambda.MapHandler(async ([FromEvent] CheckoutRequest request, ICheckoutService service) =>
{
try
{
Expand Down
28 changes: 14 additions & 14 deletions docs/guides/handler-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ builder.Services.AddScoped<IGreetingService, GreetingService>();

var lambda = builder.Build();

lambda.MapHandler(([Event] string name, IGreetingService greetings) =>
lambda.MapHandler(([FromEvent] string name, IGreetingService greetings) =>
greetings.Greet(name)
);

Expand Down Expand Up @@ -55,7 +55,7 @@ public static class OrderHandlers
{
public static void MapOrderHandler(this ILambdaInvocationBuilder app)
{
app.MapHandler(([Event] OrderRequest req, IOrderService orders) =>
app.MapHandler(([FromEvent] OrderRequest req, IOrderService orders) =>
orders.ProcessAsync(req)
);
}
Expand All @@ -82,13 +82,13 @@ await lambda.RunAsync();
- Feature providers are registered when `MapHandler` is called, so ensure the registered handler matches your expected event/response types
- This pattern is designed for **conditional registration**, not for handling multiple event types in a single Lambda (which requires a custom routing solution)

## Handler Signatures and the `[Event]` Parameter
## Handler Signatures and the `[FromEvent]` Parameter

Handlers that receive an incoming payload must identify exactly one parameter with `[Event]`. The generator uses that marker to synthesize deserialization logic (JSON by default, or whatever envelope/serializer is active). If your Lambda does **not** expect input (e.g., scheduled jobs, health checks, etc.), you can omit the `[Event]` attribute entirely—just define a handler with no payload parameter and `MinimalLambda` skips the event binding phase.
Handlers that receive an incoming payload must identify exactly one parameter with `[FromEvent]`. The generator uses that marker to synthesize deserialization logic (JSON by default, or whatever envelope/serializer is active). If your Lambda does **not** expect input (e.g., scheduled jobs, health checks, etc.), you can omit the `[FromEvent]` attribute entirely—just define a handler with no payload parameter and `MinimalLambda` skips the event binding phase.

- `[Event]` may appear on reference types, structs, records, collection types, or envelope types such as `ApiGatewayRequestEnvelope<T>`.
- Handlers without payloads can simply omit `[Event]` by not declaring an event parameter at all.
- When you do accept a payload, exactly one parameter must be annotated. Missing or duplicate `[Event]` attributes trigger compile-time diagnostics so you catch signature issues early.
- `[FromEvent]` may appear on reference types, structs, records, collection types, or envelope types such as `ApiGatewayRequestEnvelope<T>`.
- Handlers without payloads can simply omit `[FromEvent]` by not declaring an event parameter at all.
- When you do accept a payload, exactly one parameter must be annotated. Missing or duplicate `[FromEvent]` attributes trigger compile-time diagnostics so you catch signature issues early.

```csharp title="Program.cs" linenums="1"
// No incoming event required
Expand All @@ -104,7 +104,7 @@ Handlers can mix lambda events with services, context objects, and cancellation

| Parameter | Source |
|--------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| `[Event] T event` | Deserialized from the Lambda payload (or envelope). Optional—only include when the handler expects an input. |
| `[FromEvent] T event` | Deserialized from the Lambda payload (or envelope). Optional—only include when the handler expects an input. |
| `IServiceType service` | Resolved from the DI container using the invocation scope. |
| `[FromKeyedServices("key")] IServiceType keyed` | Resolves a keyed service registered with `AddKeyed*`. Keys must be constants supported by the BCL. |
| `ILambdaHostContext context` | Framework context that extends `ILambdaContext`, exposes scoped `ServiceProvider`, `Items`, `Features`, `Properties`, and the invocation `CancellationToken`. |
Expand All @@ -113,7 +113,7 @@ Handlers can mix lambda events with services, context objects, and cancellation

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(async (
[Event] OrderRequest request,
[FromEvent] OrderRequest request,
[FromKeyedServices("primary")] IOrderProcessor orderProcessor,
ILambdaHostContext context,
CancellationToken ct
Expand Down Expand Up @@ -146,7 +146,7 @@ Each invocation receives its own dependency injection scope and `ILambdaHostCont

```csharp title="Program.cs" linenums="1"
lambda.MapHandler(async (
[Event] ApiGatewayRequestEnvelope<Order> request,
[FromEvent] ApiGatewayRequestEnvelope<Order> request,
ILambdaHostContext context,
ILogger<Program> logger,
CancellationToken ct
Expand Down Expand Up @@ -178,7 +178,7 @@ lambda.MapHandler(async (
`MapHandler` is decorated as a C# 12 interceptor target. During compilation the generator:

1. Ensures the project is built with C# 11+ so interceptors are available (otherwise `LH0004`).
2. When a payload parameter exists, verifies exactly one `[Event]` annotation is present.
2. When a payload parameter exists, verifies exactly one `[FromEvent]` annotation is present.
3. Validates keyed service metadata so the requested key matches the DI container's capabilities (`LH0003` when the key uses an unsupported type such as arrays).
4. Emits a strongly typed `Handle` call that deserializes the payload (if any), resolves services via generated code, sets up features, and serializes the response.

Expand Down Expand Up @@ -211,7 +211,7 @@ At runtime:
static class Handlers
{
public static async Task<Response> HandleAsync(
[Event] Request request,
[FromEvent] Request request,
IService service,
CancellationToken ct
)
Expand All @@ -223,9 +223,9 @@ At runtime:

## Troubleshooting

**`LH0002: No parameter marked with [Event]`**
**`LH0002: No parameter marked with [FromEvent]`**

Add a `[Event]` attribute when your handler accepts an input payload. This diagnostic does **not** appear for payload-less handlers because no event parameter is required in that case.
Add a `[FromEvent]` attribute when your handler accepts an input payload. This diagnostic does **not** appear for payload-less handlers because no event parameter is required in that case.

**`InvalidOperationException: No service for type ... has been registered`**

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ builder.Services.AddSingleton<IGreetingService, GreetingService>();

var lambda = builder.Build();

lambda.MapHandler(([Event] string name, IGreetingService service) =>
lambda.MapHandler(([FromEvent] string name, IGreetingService service) =>
service.Greet(name)
);

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Register type-safe Lambda handlers with automatic dependency injection and sourc
**Topics covered:**

- MapHandler method usage
- `[Event]` attribute requirements
- `[FromEvent]` attribute requirements
- Injectable parameter types
- Return type handling
- Source generation benefits
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/lifecycle-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ lambda.OnInit(async (IDistributedCache cache, ILogger<Program> logger, Cancellat
return true; // Keep hosting if every handler returns true
});

lambda.MapHandler(([Event] Request request) => new Response("OK"));
lambda.MapHandler(([FromEvent] Request request) => new Response("OK"));

await lambda.RunAsync();
```
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ lambda.UseMiddleware(async (context, next) =>
Console.WriteLine("[Metrics] After handler");
});

lambda.MapHandler(([Event] Request request) => new Response("ok"));
lambda.MapHandler(([FromEvent] Request request) => new Response("ok"));
await lambda.RunAsync();
```

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ already know, while still embracing Lambda’s execution model.
lambda.MapHandler(
async (
// ✅ Automatic envelope deserialization with strong typing
[Event] ApiGatewayRequestEnvelope<GreetingRequest> request,
[FromEvent] ApiGatewayRequestEnvelope<GreetingRequest> request,
// ✅ Automatic DI injection - proper scoped lifetime per invocation
IGreetingService service,
// ✅ Automatic cancellation token - framework manages timeout
Expand Down Expand Up @@ -168,7 +168,7 @@ using MinimalLambda.Builder;
var builder = LambdaApplication.CreateBuilder();
var lambda = builder.Build();

lambda.MapHandler(([Event] string name) => $"Hello {name}!");
lambda.MapHandler(([FromEvent] string name) => $"Hello {name}!");

await lambda.RunAsync();
```
Expand Down
2 changes: 1 addition & 1 deletion examples/MinimalLambda.Example.Events/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
var lambda = builder.Build();

lambda.MapHandler(
([Event] ApiGatewayRequestEnvelope<Request> request, ILogger<Program> logger) =>
([FromEvent] ApiGatewayRequestEnvelope<Request> request, ILogger<Program> logger) =>
{
logger.LogInformation("In Handler. Payload: {Payload}", request.Body);

Expand Down
2 changes: 1 addition & 1 deletion examples/MinimalLambda.Example.HelloWorld/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

// Map your handler - the event is automatically injected
lambda.MapHandler(
([Event] Request request) => new Response($"Hello {request.Name}!", DateTime.UtcNow)
([FromEvent] Request request) => new Response($"Hello {request.Name}!", DateTime.UtcNow)
);

// Run the Lambda
Expand Down
2 changes: 1 addition & 1 deletion examples/MinimalLambda.Example.HelloWorldAot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
);

lambda.MapHandler(
([Event] string input) =>
([FromEvent] string input) =>
{
Console.WriteLine("hello world from aot");
return "hello world from aot";
Expand Down
Loading
Loading