Lambda Bun runtime rewrite
When I created @beesolve/lambda-bun-runtime I mostly did it because it seems that the Lambda is very low on the Bun priority list and I wanted to have something which is up to date where I can fix the bugs quickly. I also wanted to provide better DX and tooling built around the CDK.
After using it for a while I realized that the approach of the official Bun's custom layer for Lambda does not align with my mindset.
If you like Bun and you've built the application which you just want to see running on AWS Lambda you should be OK with the official runtime. If you on the other hand want to leverage full capabilities of AWS Lambda and its ecosystem + you want better DX you should read on.
Official Bun lambda layer
The official layer provided by Bun is built around the idea that users who comes from Bun might want to deploy their application to AWS Lambda. The whole architecture is built around this premise. It feels like aftertought - you can try it but there is no serious project you should built around this. It's not very well maintained either.
When you try to use the official layer you need to adhere to Fetch API. Don't get me wrong, I think Fetch API is great and I love that more and more frameworks and tools are being built around it as it is web standard and it ultimately means your applications will be portable. The AWS Lambda is not built around this concept meaning that if you want to do anything meaningful you need to make tough decisions and lot of workarounds in your application design. Here I am talking about the design of the runtime.ts.
Official runtime support basic HttpV1 (REST API Gateway) and Httpv2 (HTTP API Gateway/Function URL). It also supports WebSocket API Gateway which is being mapped to the Bun websockets interface. This is in my opinion wrong. The problem is that the author is trying to bend how Lambda works in order to deliver easy migration for existing users of Bun. Unfortunatelly easy lift and shift approach is what most of the time does not work.
Another problem is that whenever user want to use Bun runtime with other Lambda integrations like SQS the Fetch API paradigm is enforced because of how the whole runtime.ts works. So now instead of event you get Request and instead of returning data you need to return Response. This just does not make sense.
The last thing which convinced me that something needs to be changed is that when using the official runtime you are unable to access original event and original Lambda context - both of these things are essential if you want to do more serious stuff with your Lambda functions.
I think Bun introduced wrong abstraction here.
The rewrite
When Bun v1.3.14 came out I took it as an opportunity to rething the approach rather than just publish another update.
What haven't changed
I haven't rewritten the whole package of course. What was right from start stays as is. For instance the package is still built with projen so it can be available through Constructs.dev as previously1.
The package still provides CDK constructs for better DX with CDK:
BunLayer- deploys pre-built Bun layer into your AWS accountBunFunction- deploys Lambda function with custom Bun runtime which runs your handler
You can use these as previously out of box.
What changed
The first thing I've changed is that I've replaced the complicated runtime.ts with light-weight version. The custom runtime.mts does only one job and it does it well - it passes the event obtained from Lambda service to the defined handler and it passes returned value from handler back to Lambda service. This is what Custom runtime is supposed to do.
Now you can use AWS Lambda as you did previously. When you understand Lambda you shouldn't care about the language. When you use Node.js or Python you always want to receive event and context, process it and then return the response or some data. This is the mental model of Lambda. I don't see reason why it should be different for Bun.
As a next thing I rewritten I asked the LLM agent2 to rewrite the shell script for building the layer to TypeScript. This script now leverages the Bun shell. I also asked nicely for the test coverage.
Another thing which I wanted to do previously was provide an automated way of building the code when using BunFunction construct. Since Bun.build() is asynchronous and cannot be called within the Construct constructor, I've leveraged Code.fromCustomCommand() functionality. Here I assume that if you want to run Bun on Lambda you probably have Bun already installed locally. Unfortunatelly when you have lot of BunFunction constructs in your project this means that all of the code is going to be built in sequence which is not that great. There is still option of pointing the BunFunction to the bundled JavaScript files when you want to build your code outside of the CDK lifecycle.
Lastly, I understand that you still might want to leverage Fetch API where it makes sense. This is only the reasonable thing to expect. That's why I have provided an example usage within the README.md which shows you how you can leverage @beesolve/lambda-fetch-api so you can map HttpV1 and HttpV2 Lambda events to your pre-existing Fetch API compatible application. The nice perk here is the usage of AsyncLocalStorage for accessing Lambda event and context.
If you still want to use WebSockets you need to implement them transparently - exactly the same way as if you would use Node.js.
More improvements through automation
The goal from start was to create package which would stay up to date with the Bun releases. So far I've been upgrading this package manually which is a bit cumbersome and most of the time not very up to date. That's why I decided to investigate (with my LLM friends) the options for automating this task. My idea was to set up a Github action which would check for the new version of Bun each day. Then it would build new Lambda layer, create new version of package and publish it.
As if with any automation it is always good idea to have some tests in place - you want to be sure that nothing has been broken when publishing new version automatically.
I have added two sets of tests - unit tests which test code but also an integration tests which will deploy the function to the AWS account and test the whole thing against the real Lambda events in real environment.
So now you can rely on this package more than ever before.
New major version + versioning explained
These changes are definitely not backward compatible. That's why I've published new major version.
I also wanted to somewhat align the Bun versioning with versioning of this package so I have come up with this:
- major - 2 - version of this library
- minor - 103 - major version of Bun + 100x minor version of Bun (100x is there to avoid conflicts)
- patch - 14 - aligns with Bun versioning
This also means that whenever I want to fix something I need to wait for the next version of Bun but as I don't think I will need to fix anything soon I think I can live with it. I might be surprised in the future of course but I will adapt as the problems surface.
Conclusion
The new version of runtime does one thing - pass the event to your handler and return the result. No Fetch API wrapper, no hidden abstractions, no magic. Just Lambda working the way Lambda works, with Bun as the runtime.
If you still want to use Fetch API (maybe you are using Bun server locally as I do) the tooling is there, you just need to wire things explicitly.
Thanks to the automated releases you don't need to wait for me to publish an update anymore. Whenever a new version of Bun is released, the package gets updated automatically.
I think being explicit about architecture is always better way than trying to reason about someone elses abstractions. That's why this ended up being a rewrite and not just another update.
