Split the long `LambdaRuntimeClient.swift` file in two parts: the
`LambdaRuntimeClient` and the `LambdaChannelHandler` for easier reading
and maintenance
This PR implements a mechanism to propagate connection loss information
from the Lambda runtime client to the runtime loop, enabling termination
without backtrace when the connection to the Lambda control plane (or a
Mock Server) is lost.
The changes are:
- When the connection is lost,
`ChannelHandlerDelegate.channelInnactive()` now correctly calls
`resume(throwing:)` on the ending continuation, for all states
(`.waitingForNextInvocation ` and `.sentResponse`). This eliminates the
hangs on connection lost..
- I added top-level error handling on `LambdaRuntime._run()`
- Add a unit test to check that either
`LambdaruntimeError.connectionToControlPlaneLost`, a `ChannelError`, or
an `IOError` is thrown when the server closes the connection
Instant's value now correctly prints as an EPOCH number
### Motivation:
A regression was introduced by
https://github.com/swift-server/swift-aws-lambda-runtime/pull/540. The
HTTP headers returned by `LocalServer` contained an invalid
representation of the Lamba Deadline.
See https://github.com/swift-server/swift-aws-lambda-runtime/issues/551
### Modifications:
- Add `CustomStringConvertible` to `LambdaClock.Instant` to just print
the `Int64` value
- add a unit test
### Result:
The runtime works correctly with the new `LambdaClock`
Revert streaming codable handler change and propose it as an example
instead of an handler API.
**Motivation:**
I made a mistake when submitting this PR
https://github.com/swift-server/swift-aws-lambda-runtime/pull/532
It provides a Streaming+Codable handler that conveniently allows
developers to write handlers with `Codable` events for streaming
functions.
This is a mistake for three reasons:
- This is the only handler that assumes a Lamba Event structure as
input. I added a minimal `FunctionUrlRequest` and `FunctionURLResponse`
to avoid importing the AWS Lambda Events library. It is the first
handler to be event-specific. I don't think the runtime should introduce
event specific code.
- The handler only works when Lambda functions are exposed through
Function URLs. Streaming functions can also be invoke by API or CLI.
- The handler hides `FunctionURLRequest` details (HTTP headers, query
parameters, etc.) from developers
Developers were unaware they were trading flexibility for convenience
The lack of clear documentation about these limitations led to incorrect
usage patterns and frustrated developers who needed full request control
or were using other invocation methods.
**Modifications:**
- Removed the Streaming+Codable API from the library
- Moved the Streaming+Codable code to an example
- Added prominent warning section in the example README explaining the
limitations
- Clarified when to use Streaming+Codable vs ByteBuffer approaches
- Added decision rule framework to help developers choose the right
approach
**Result:**
The only API provided by the library to use Streaming Lambda functions
is exposing the raw `ByteBuffer` as input, there is no more `Codable`
handler for Streaming functions available in the API. I kept the
`Streaming+Codable` code an example.
After this change, developers have clear guidance on when to use each
streaming approach:
- Use streaming codable for Function URL + JSON payload + no request
details needed
- Use ByteBuffer StreamingLambdaHandler for full control, other
invocation methods, or request metadata access
This prevents misuse of the API and sets proper expectations about the
handler's capabilities and limitations, leading to better developer
experience and fewer integration issues.
### Motivation:
AWS Lambda response streaming now supports a default maximum response
payload size of 200 MB (increased from 20 MB). This update aligns the
documentation with the new AWS limits.
### Modifications:
- Updated readme.md: Changed "soft limit of 20 MB" to "default maximum
response payload size of 200 MB"
- Updated Examples/Streaming/README.md: Same change as above
- Updated terminology to match AWS official documentation
### Result:
Documentation now accurately reflects the current AWS Lambda streaming
response limits, enabling users to understand they can stream up to 200
MB payloads.
Reference:
https://aws.amazon.com/about-aws/whats-new/2025/07/aws-lambda-response-streaming-200-mb-payloads/
Add Hummingbird web framework integration example for AWS Lambda
**Motivation:**
Developers using the Hummingbird web framework need a clear example of
how to integrate it with AWS Lambda. The existing examples focus on
basic Lambda handlers, but don't demonstrate how to use popular Swift
web frameworks like Hummingbird in a serverless context.
**Modifications:**
Added a complete Hummingbird Lambda example in Examples/Hummingbird/
including Package.swift with Hummingbird Lambda dependencies, main.swift
demonstrating router setup with API Gateway V2 integration, SAM template
for deployment, and comprehensive README documentation with build,
deploy, and usage instructions.
**Result:**
Developers can now easily create AWS Lambda functions using the
Hummingbird web framework, with a working example that shows router
configuration, API Gateway integration, and complete deployment workflow
using familiar Hummingbird syntax.
Fix
[#384](https://github.com/swift-server/swift-aws-lambda-runtime/issues/384)
Note: this PR introduces an API change that will break Lambda functions
using `LambdaContext`, we should integrate this change during the beta
otherwise it will require a major version bump.
### Motivation:
`DispatchWallTime` has no public API to extract the time in
milliseconds, making it a dead end.
Previous implementation used the internal representation of time inside
`DispatchWallTime` to extract the value, creating a risk if its
implementation will change in the future.
Moreover, the use of `DispatchWallTime` obliges users to import the
`Dispatch` library or `Foundation`.
Old Code:
```
extension DispatchWallTime {
@usableFromInline
init(millisSinceEpoch: Int64) {
let nanoSinceEpoch = UInt64(millisSinceEpoch) * 1_000_000
let seconds = UInt64(nanoSinceEpoch / 1_000_000_000)
let nanoseconds = nanoSinceEpoch - (seconds * 1_000_000_000)
self.init(timespec: timespec(tv_sec: Int(seconds), tv_nsec: Int(nanoseconds)))
}
var millisSinceEpoch: Int64 {
Int64(bitPattern: self.rawValue) / -1_000_000
}
}
```
Issue
[#384](https://github.com/swift-server/swift-aws-lambda-runtime/issues/384)
has a long discussion about possible replacements, including creating a
brand new `UTCClock`, which I think is an overkill for this project.
Instead, I propose this simple implementation, based on two assumptions:
- AWS always sends the time in milliseconds since Unix Epoch (1st Jan
1970) ([Lambda Runtime API
documentation](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next))
- AWS always uses UTC time (not only for Lambda, this is a general rule
for all AWS APIs) ([TZ=UTC on
Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html))
Therefore, this library just needs to store and make math on
milliseconds since epoch, without having to care about timezone.
I had two possibilities to implement the storage and the math on
milliseconds since Unix Epoch: either I could use an `UInt64` (as does
[the Rust
implementation](https://github.com/awslabs/aws-lambda-rust-runtime/blob/aff8d883c62997ef2615714dce9f7ddfd557147d/lambda-runtime/src/types.rs#L70))
or I could use a standard Swift type, such as `Duration`.
`Duration` is a good candidate for this because 1/ the time we receive
from the Lambda Service API is indeed a duration between 1/1/1970 and
the execution deadline for the Lambda function, expressed in
milliseconds, 2/ it gives a strong type that can be verified by the
compiler, and 3/ it is possible to do basic arithmetic operations and
compare two values.
As an additional benefit, it allows library users to not import
`Dispatch` or `Foundation`
### Modifications:
I made two changes:
1. I extend the `Duration` type to provide us with simple unix epoch
time manipulation functions and values.
```swift
extension Duration {
/// Returns the time in milliseconds since the Unix epoch.
@usableFromInline
static var millisSinceEpoch: Duration {
var ts = timespec()
clock_gettime(CLOCK_REALTIME, &ts)
return .milliseconds(Int64(ts.tv_sec) * 1000 + Int64(ts.tv_nsec) / 1_000_000)
}
/// Returns a Duration between Unix epoch and the distant future
@usableFromInline
static var distantFuture: Duration {
// Use a very large value to represent the distant future
millisSinceEpoch + Duration.seconds(.greatestFiniteMagnitude)
}
/// Returns the Duration in milliseconds
@usableFromInline
func milliseconds() -> Int64 {
Int64(self / .milliseconds(1))
}
/// Create a Duration from milliseconds since Unix Epoch
@usableFromInline
init(millisSinceEpoch: Int64) {
self = .milliseconds(millisSinceEpoch)
}
}
```
3. I replaced all references to `DispatchWallTime` by `Duration`
### Result:
No more `DispatchWallTime`
No dependencies on Foundation, as I use `clock_gettime()` to get the
epoch from the system clock.
re-implement MAX_INVOCATIONS and fix shell script
Fix https://github.com/swift-server/swift-aws-lambda-runtime/issues/377
### Motivation:
In v1, there was a script measuring the performance of the invocation
loop.
Re-instate this script to allow users and developers to measure the
performance impact of their changes.
### Modifications:
I re-implemented MAX_INVOCATIONS, to avoid the client looping against
the Mock Server. But this time, MAX_INVOCATIONS is handled on the
server, not on the client.
I slightly modified the script to work with v2 and the new MockServer.
### Result:
The script works.
This PR has a dependency on
https://github.com/swift-server/swift-aws-lambda-runtime/issues/465
Do not use a String for Lambdacontext.ClientContext, use a struct instead.
Fix for https://github.com/swift-server/swift-aws-lambda-runtime/issues/169
Note: this PR introduces an API change that will break function using
`LambdaContext`, we should integrate this change during the beta
otherwise it will require a major version bump.
### Motivation:
Let the compiler detect type errors for us
### Modifications:
- Create a struct for ClientContext and it's embedded ClientApplication
- add three unit test to validate the struct
### Result:
No more String?
---------
Co-authored-by: Konrad `ktoso` Malawski <konrad.malawski@project13.pl>
When reading about the V2 changes , I noticed that the link for Response
Streaming in the AWS docs was leading to a 404.
A trailing `]` character was the reason - this PR fixes it.
```
note: The majority of content should be under level-3 headers under the "Overview" section
--> Deployment.md:22:1-22:17
20 | * [Third-party tools](#third-party-tools)
21 |
22 + ## Prerequisites
| ╰─suggestion: Change the title to "Overview"
23 |
24 | 1. Your AWS Account
```
and many others.
Action: I lowered all titles one level down.
Add user-facing API for Streaming Lambda functions that receives JSON
events
### Motivation:
Streaming Lambda functions developed by developers had no choice but to
implement a handler that receives incoming data as a `ByteBuffer`. While
this is useful for low-level development, I assume most developers will
want to receive a JSON event to trigger their streaming Lambda function.
Going efficiently from a `ByteBuffer` to a Swift struct requires some
code implemented in the `JSON+ByteBuffer.swift` file of the librray. We
propose to further help developers by providing them with a new
`handler()` function that directly receives their `Decodable` type.
### Modifications:
This PR adds a public facing API (+ unit test + updated README) allowing
developers to write a handler method accepting any `Decodable` struct as
input.
```swift
import AWSLambdaRuntime
import NIOCore
// Define your input event structure
struct StreamingRequest: Decodable {
let count: Int
let message: String
let delayMs: Int?
}
// Use the new streaming handler with JSON decoding
let runtime = LambdaRuntime { (event: StreamingRequest, responseWriter, context: LambdaContext) in
context.logger.info("Received request to send \(event.count) messages")
// Stream the messages
for i in 1...event.count {
let response = "Message \(i)/\(event.count): \(event.message)\n"
try await responseWriter.write(ByteBuffer(string: response))
// Optional delay between messages
if let delay = event.delayMs, delay > 0 {
try await Task.sleep(for: .milliseconds(delay))
}
}
// Finish the stream
try await responseWriter.finish()
// Optional: Execute background work after response is sent
context.logger.info("Background work: processing completed")
}
try await runtime.run()
```
This interface provides:
- **Type-safe JSON input**: Automatic decoding of JSON events into Swift
structs
- **Streaming responses**: Full control over when and how to stream data
back to clients
- **Background work support**: Ability to execute code after the
response stream is finished
- **Familiar API**: Uses the same closure-based pattern as regular
Lambda handlers
Because streaming Lambda functions can be invoked either directly
through the API or through Lambda Function URL, this PR adds the
decoding logic to support both types, shielding developers from working
with Function URL requests and base64 encoding.
We understand these choice will have an impact on the raw performance
for event handling. Those advanced users that want to get the maximum
might use the existing `handler(_ event: ByteBuffer, writer:
LambaStreamingWriter)` function to implement their own custom decoding
logic.
This PR provides a balance between ease of use for 80% of the users vs
ultimate performance, without closing the door for the 20% who need it.
### Result:
Lambda function developers can now use arbitrary `Decodable` Swift
struct or Lambda events to trigger their streaming functions. 🎉
---------
Co-authored-by: Tim Condon <0xTim@users.noreply.github.com>
Add a log statement in the Lambda loop, before calling the user's
handler to show the raw payload before any attempt to decode it.
This was available in Runtime v1 and is now ported to v2.
This fixes
https://github.com/swift-server/swift-aws-lambda-runtime/issues/404
### Motivation:
This is useful when handling custom event and there is a Decoding error.
It allows to see the exact payload received by the handler before any
attempt to decode it.
### Modifications:
Add a log.trace statement with metatadata. Metadata are computed only
when the log level is trace or below.
### Result:
```
2025-07-21T08:58:33+0200 trace LambdaRuntime : Event's first bytes={"name": "me", "age": 50} aws-request-id=769127502334125 [AWSLambdaRuntime] sending invocation event to lambda handler
```
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Add a hard coded version number to the user agent string, for an
eventual identification by the Lambda service
### Motivation:
It's [an
issue](https://github.com/swift-server/swift-aws-lambda-runtime/issues/108)
that was open more than 5 years ago and was never addressed. At the
time, the consensus was to pickup a version number for the Package.swift
file and the maintainer at the time decided to wait for Swift to
implement this.
Five years later, and several major version of Swift later, this is
still not available. I decided to move on and implement a less optimal
solution. This can be replaced in the future if package version ever
becomes part of Package.swift.
### Modifications:
Add a version enum to isolate the versioning in one place. I decided to
keep it simple and not over engineering it with major, minor, patch and
pre-release. At the time, it's a simple string. This is all what we need
for usage in the user agent string.
### Result:
User agent now identifies as `Swift-Lambda/2,0` instead of
`Swift-Lambda/unknown`
This is a proposal to fix issue #507
**changes**
- `LambdaRuntime.init()` uses a `Mutex<Bool>` to make sure only one
instance is created
- `LambdaRuntime.init()` can now throw an error in case an instance
already exists (I did not use `fatalError()` to make it easier to test)
- All `convenience init()` methods catch possible errors instead of
re-throwing it to a void breaking the user-facing API
- Renamed existing `LambdaRuntimeError` to `LambdaRuntimeClientError`
- Introduced a new type `LambdaRuntimeError` to represent the double
initialization error
---------
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Co-authored-by: Adam Fowler <adamfowler71@gmail.com>
A proposal for a new set of SwiftPM plugins to facilitate the
scaffolding, build, archive, and deployment of Lambda functions.
This is a call for comments to finalize the design before starting an
actual implementation
Allow user to give their logger to the LambdaRuntime.
### Motivation:
Overloaded versions of `LambdaRuntime.init` don't allow to pass a logger
### Modifications:
Add a `logger` parameter to overloaded versions of `LambdaRuntime.init`
### Result:
It is now possible to write
```
let runtime = LambdaRuntime(logger: Logger(label: "MyLogger"), body: handler)
```
- Add ServiceLifecycle version of `LambdaRuntime.run` that wraps
internal `_run` call in `cancelOnGracefulShutdown`
- Add cancellation handlers for shutting down existing connections in
Local lambda
- Added test for lambda graceful shutdown
### Motivation:
Ensure local lambda supports graceful shutdown
Add SendableMetatype conformance to StreamingLambdaHandler
### Motivation:
Swift 6.2 introduced new protocol SendableMetatype for Types that are
Sendable
### Modifications:
Added `_Lambda_SendableMetatype` typealias for SendableMetatype in Swift
6.2
and
```swift
public protocol StreamingLambdaHandler: _Lambda_SendableMetatype {
```
### Result:
No more compile warnings
Remove dependency on XCTest
### Motivation:
As 6.1 does not include XCTest anymore, finish the migration to Swift
testing
### Modifications:
Replace XCTest by Swift Testing in two files
### Result:
`swift test` works on 6.1.2
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
~~Attempt to fix CI~~
Remove usage of deprecated parameters on unit-test.
- explicitly enable tests on nightly 6.0
- explicitly enable tests on nightly next
- explicitly enable tests on nightly main
At the time of this writing, this resolves to
```
linux_6_0_container_image="swift:6.0-jammy"
linux_nightly_next_container_image="swiftlang/swift:nightly-6.1-jammy"
linux_nightly_main_container_image="swiftlang/swift:nightly-main-jammy"
```
---------
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Resources are not included in the archive on Linux
https://github.com/swift-server/swift-aws-lambda-runtime/issues/505
In addition, the script that checks if the resources are correctly
included in an archive when building on Linux is not working.
It failed to detected missing resources.
This patch fix both the plugin and the CI script
When we include Traits, we changed the CI container from Swift 6.0
Amazon Linux to Swift 6.1 Nightly on Ubuntu.
This broke the CI because AWS SDK used in the `Examples/S3_AWSSDK`
example depends on `libssl-devel` which is not installed by default on
Jammy images.
This PR reverts back the container used to test all examples to an
Amazon Linux 2, but this time the 6.1-nightly version to allow to
compile the package traits.
This should address
https://github.com/swift-server/swift-aws-lambda-runtime/issues/494
The user-agent string was repeated at 5+ different places in the source
code.
I defined it as a constant in the `ControlPlaneRequest` struct where
other similar constant have been defined and make sure the rest of the
code only uses that constant.
This should address
https://github.com/swift-server/swift-aws-lambda-runtime/issues/492
Allow `LambdaChannelHandler.nextInvocation` to be cancelled.
### Motivation:
If we want to use ServiceLifecycle with the lambda runtime the lambda
runtime needs to be cancellable either via a ServiceLifecycle graceful
shutdown or via Task cancellation. To avoid bringing in the
ServiceLifecycle dependency this PR adds cancellation via Task
cancellation handler.
### Modifications:
Add `withTaskCancellationHandler` to nextInvocation which calls close on
cancel.
In `LambdaChannelHandler.channelInactive` resume continuation if state
is `waitingForNextInvocation`
Added `LambdaRuntimeClientTests.testCancellation`
### Result:
You can now cancel the runtime while it is waiting for the next
invocation.
---------
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
Instructions for customizing the output path are incorrect
### Motivation:
`displayHelpMessage` was incorrect - it claimed that the output path was
customizable via a `--output-directory` flag, but the code actually
looks for `--output-path`
### Modifications:
Changing the help message here, instead of the code, in case existing
projects already depend on `--output-path`
### Result:
`displayHelpMessage` will show the correct instructions