-
Notifications
You must be signed in to change notification settings - Fork 95
MVP Bun runtime for AWS and local #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f5cd59a
bf25c7b
e29be38
840a406
f68dd1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| const timer = ms => new Promise( res => setTimeout(res, ms)); | ||
|
|
||
| exports.handler = async function(event) { | ||
| var sleep = event.sleep; | ||
| return timer(sleep*1000); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "name": "", | ||
| "version": "1.0.0", | ||
| "description": "", | ||
| "author": "", | ||
| "license": "", | ||
| "dependencies": { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| { | ||
| "timeout": 120, | ||
| "memory": 128, | ||
| "languages": ["python", "nodejs"], | ||
| "languages": [ | ||
| "python", | ||
| "nodejs", | ||
| "bun" | ||
| ], | ||
| "modules": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| const Mustache = require('mustache'), | ||
| fs = require('fs'), | ||
| path = require('path'); | ||
|
|
||
| function random(b, e) { | ||
| return Math.round(Math.random() * (e - b) + b); | ||
| } | ||
|
|
||
| exports.handler = async function(event) { | ||
| var random_numbers = new Array(event.random_len); | ||
| for(var i = 0; i < event.random_len; ++i) { | ||
| random_numbers[i] = random(0, 100); | ||
| } | ||
| var input = { | ||
| cur_time: new Date().toLocaleString(), | ||
| username: event.username, | ||
| random_numbers: random_numbers | ||
| }; | ||
|
|
||
| var file = path.resolve(__dirname, 'templates', 'template.html'); | ||
| return new Promise((resolve, reject) => { | ||
| fs.readFile(file, "utf-8", | ||
| function(err, data) { | ||
| if(err) reject(err); | ||
| resolve(Mustache.render(data, input)); | ||
| } | ||
| ); | ||
| }); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #!/bin/bash | ||
|
|
||
| DIR=$1 | ||
| VERBOSE=$2 | ||
| SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" | ||
| path="${SCRIPT_DIR}/templates/" | ||
| if [ "$VERBOSE" = true ]; then | ||
| echo "Update ${DIR} with static templates ${path}" | ||
| fi | ||
| cp -r ${SCRIPT_DIR}/templates ${DIR} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "", | ||
| "version": "1.0.0", | ||
| "description": "", | ||
| "author": "", | ||
| "license": "", | ||
| "dependencies": { | ||
| "mustache": "^3.2.1" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Randomly generated data.</title> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen"> | ||
| <style type="text/css"> | ||
| .container { | ||
| max-width: 500px; | ||
| padding-top: 100px; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="container"> | ||
| <p>Welcome {{username}}!</p> | ||
| <p>Data generated at: {{cur_time}}!</p> | ||
| <p>Requested random numbers:</p> | ||
| <ul> | ||
| {{#random_numbers}} | ||
| <li>{{.}}</li> | ||
| {{/random_numbers}} | ||
| </ul> | ||
| </div> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| { | ||
| "timeout": 10, | ||
| "memory": 128, | ||
| "languages": ["python", "nodejs"], | ||
| "languages": [ | ||
| "python", | ||
| "nodejs", | ||
| "bun" | ||
| ], | ||
| "modules": [] | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fs = require('fs'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| path = require('path'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request = require('request'), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| storage = require('./storage'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let storage_handler = new storage.storage(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function streamToPromise(stream) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Promise(function(resolve, reject) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stream.on("close", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resolve(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stream.on("error", reject); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exports.handler = async function(event) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let bucket = event.bucket.bucket | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output_prefix = event.bucket.output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let url = event.object.url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let upload_key = path.basename(url) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let download_path = path.join('/tmp', upload_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var file = fs.createWriteStream(download_path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request(url).pipe(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let promise = streamToPromise(file); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for HTTP request. The 🛡️ Proposed fix var file = fs.createWriteStream(download_path);
- request(url).pipe(file);
+ const req = request(url);
+ req.on('error', (err) => {
+ file.destroy();
+ throw new Error(`Download failed: ${err.message}`);
+ });
+ req.pipe(file);
let promise = streamToPromise(file);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var keyName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let upload = promise.then( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [keyName, promise] = storage_handler.upload(bucket, path.join(output_prefix, upload_key), download_path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await promise; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await upload; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {bucket: bucket, url: url, key: keyName} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initialize keyName or handle undefined case.
🔧 Proposed fixOption 1: Initialize with a default value: - var keyName;
+ var keyName = null;
let upload = promise.then(
async () => {
[keyName, promise] = storage_handler.upload(bucket, path.join(output_prefix, upload_key), download_path);
await promise;
}
);
await upload;
+ if (!keyName) {
+ throw new Error('Upload failed: key name not set');
+ }
return {bucket: bucket, url: url, key: keyName}Option 2: Restructure to avoid the issue: - var keyName;
- let upload = promise.then(
- async () => {
- [keyName, promise] = storage_handler.upload(bucket, path.join(output_prefix, upload_key), download_path);
- await promise;
- }
- );
- await upload;
- return {bucket: bucket, url: url, key: keyName}
+ await promise;
+ const [keyName, uploadPromise] = storage_handler.upload(bucket, path.join(output_prefix, upload_key), download_path);
+ await uploadPromise;
+ return {bucket: bucket, url: url, key: keyName}📝 Committable suggestion
Suggested change
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "", | ||
| "version": "1.0.0", | ||
| "description": "", | ||
| "author": "", | ||
| "license": "", | ||
| "dependencies": { | ||
| "request": "^2.88.0" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer: The npm package request was officially deprecated in February 2020 (deprecation message added on Feb 11, 2020). See the package pages and the maintainers' deprecation discussion (GitHub issue #3142). [1][2][3] Sources
🏁 Script executed: # Check the context of this package.json and see how request is used
find benchmarks/100.webapps/120.uploader/bun -type f -name "*.js" -o -name "*.ts" | head -20Repository: spcl/serverless-benchmarks Length of output: 121 🏁 Script executed: # Search for usage of request package in the uploader code
rg "require.*request|import.*request" benchmarks/100.webapps/120.uploader/bun/ 2>/dev/null || echo "No direct request imports found"Repository: spcl/serverless-benchmarks Length of output: 157 🏁 Script executed: # Check the full package.json to understand the context
cat benchmarks/100.webapps/120.uploader/bun/package.jsonRepository: spcl/serverless-benchmarks Length of output: 212 🏁 Script executed: cat benchmarks/100.webapps/120.uploader/bun/function.jsRepository: spcl/serverless-benchmarks Length of output: 1057 Replace deprecated The ♻️ Replace with Bun's native fetch const fs = require('fs'),
path = require('path'),
- request = require('request'),
storage = require('./storage');Then update the download: var file = fs.createWriteStream(download_path);
- request(url).pipe(file);
+ const response = await fetch(url);
+ response.body.pipe(file);Remove
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,12 @@ | ||
| { | ||
| "timeout": 30, | ||
| "memory": 128, | ||
| "languages": ["python", "nodejs"], | ||
| "modules": ["storage"] | ||
| "languages": [ | ||
| "python", | ||
| "nodejs", | ||
| "bun" | ||
| ], | ||
| "modules": [ | ||
| "storage" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||
| const sharp = require('sharp'), | ||||||||||||||||||||||||||
| path = require('path'), | ||||||||||||||||||||||||||
| storage = require('./storage'); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let storage_handler = new storage.storage(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| exports.handler = async function(event) { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| bucket = event.bucket.bucket | ||||||||||||||||||||||||||
| input_prefix = event.bucket.input | ||||||||||||||||||||||||||
| output_prefix = event.bucket.output | ||||||||||||||||||||||||||
| let key = event.object.key | ||||||||||||||||||||||||||
| width = event.object.width | ||||||||||||||||||||||||||
| height = event.object.height | ||||||||||||||||||||||||||
|
Comment on lines
+9
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Missing variable declarations create implicit globals. Variables
🐛 Proposed fix- bucket = event.bucket.bucket
- input_prefix = event.bucket.input
- output_prefix = event.bucket.output
+ const bucket = event.bucket.bucket;
+ const input_prefix = event.bucket.input;
+ const output_prefix = event.bucket.output;
let key = event.object.key
- width = event.object.width
- height = event.object.height
+ const width = event.object.width;
+ const height = event.object.height;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| let pos = key.lastIndexOf('.'); | ||||||||||||||||||||||||||
| let upload_key = key.substr(0, pos < 0 ? key.length : pos) + '.png'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const sharp_resizer = sharp().resize(width, height).png(); | ||||||||||||||||||||||||||
| let read_promise = storage_handler.downloadStream(bucket, path.join(input_prefix, key)); | ||||||||||||||||||||||||||
| let [writeStream, promise, uploadName] = storage_handler.uploadStream(bucket, path.join(output_prefix, upload_key)); | ||||||||||||||||||||||||||
| read_promise.then( | ||||||||||||||||||||||||||
| (input_stream) => { | ||||||||||||||||||||||||||
| input_stream.pipe(sharp_resizer).pipe(writeStream); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| await promise; | ||||||||||||||||||||||||||
| return {bucket: output_prefix, key: uploadName} | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "name": "", | ||
| "version": "1.0.0", | ||
| "description": "", | ||
| "author": "", | ||
| "license": "", | ||
| "dependencies": { | ||
| "sharp": "0.32.6" | ||
| } | ||
|
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Short answer: [email protected] can work in Bun, but it depends on Bun running sharp’s native install steps (prebuilt libvips or a native build). Bun has strong Node-API/N‑API support and now reports a Node version compatible with recent prebuilt binaries, and Bun’s package manager will run known postinstall scripts (including optimizations for sharp) or can be told to trust the package. If Bun does not run the lifecycle script, sharp’s install will fail and you’ll need to allow the lifecycle script or build libvips locally. What to do
Why this works
Sources
Ensure sharp's postinstall script runs in Bun. The sharp library (version 0.32.6) requires postinstall scripts to download or build prebuilt libvips binaries. Bun may need explicit configuration: either add 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,12 @@ | ||
| { | ||
| "timeout": 60, | ||
| "memory": 256, | ||
| "languages": ["python", "nodejs"], | ||
| "modules": ["storage"] | ||
| "languages": [ | ||
| "python", | ||
| "nodejs", | ||
| "bun" | ||
| ], | ||
| "modules": [ | ||
| "storage" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const path = require('path'), fs = require('fs'); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function process_output(data, http_trigger) { | ||||||||||||||||||||||||||||||||||||
| if(http_trigger) | ||||||||||||||||||||||||||||||||||||
| return JSON.stringify(data); | ||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| exports.handler = async function(event, context) { | ||||||||||||||||||||||||||||||||||||
| var begin = Date.now()/1000; | ||||||||||||||||||||||||||||||||||||
| var start = process.hrtime(); | ||||||||||||||||||||||||||||||||||||
| var http_trigger = "body" in event; | ||||||||||||||||||||||||||||||||||||
| var input_data = http_trigger ? JSON.parse(event.body) : event | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for JSON parsing. JSON.parse can throw a SyntaxError if 🛡️ Proposed fix- var input_data = http_trigger ? JSON.parse(event.body) : event
+ var input_data = http_trigger ? (() => {
+ try {
+ return JSON.parse(event.body);
+ } catch (e) {
+ throw new Error(`Invalid JSON in request body: ${e.message}`);
+ }
+ })() : event📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| var func = require('./function/function') | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Move require statement to module level. Loading the function module inside the handler adds unnecessary overhead to every invocation. In AWS Lambda, the module scope is preserved between invocations in warm containers, so requiring at the top level improves performance. ⚡ Proposed refactorMove the require to the top of the file: const path = require('path'), fs = require('fs');
+const func = require('./function/function');
function process_output(data, http_trigger) {
if(http_trigger)
return JSON.stringify(data);
else
return data;
}
exports.handler = async function(event, context) {
var begin = Date.now()/1000;
var start = process.hrtime();
var http_trigger = "body" in event;
var input_data = http_trigger ? JSON.parse(event.body) : event
- var func = require('./function/function')
var ret = func.handler(input_data);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| var ret = func.handler(input_data); | ||||||||||||||||||||||||||||||||||||
| return ret.then( | ||||||||||||||||||||||||||||||||||||
| (result) => { | ||||||||||||||||||||||||||||||||||||
| var elapsed = process.hrtime(start); | ||||||||||||||||||||||||||||||||||||
| var end = Date.now()/1000; | ||||||||||||||||||||||||||||||||||||
| var micro = elapsed[1] / 1e3 + elapsed[0] * 1e6; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| var is_cold = false; | ||||||||||||||||||||||||||||||||||||
| var fname = path.join('/tmp','cold_run'); | ||||||||||||||||||||||||||||||||||||
| if(!fs.existsSync(fname)) { | ||||||||||||||||||||||||||||||||||||
| is_cold = true; | ||||||||||||||||||||||||||||||||||||
| fs.closeSync(fs.openSync(fname, 'w')); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||
| statusCode: 200, | ||||||||||||||||||||||||||||||||||||
| body: process_output({ | ||||||||||||||||||||||||||||||||||||
| begin: begin, | ||||||||||||||||||||||||||||||||||||
| end: end, | ||||||||||||||||||||||||||||||||||||
| compute_time: micro, | ||||||||||||||||||||||||||||||||||||
| results_time: 0, | ||||||||||||||||||||||||||||||||||||
| result: {output: result}, | ||||||||||||||||||||||||||||||||||||
| is_cold: is_cold, | ||||||||||||||||||||||||||||||||||||
| request_id: context.awsRequestId | ||||||||||||||||||||||||||||||||||||
| }, http_trigger) | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| (error) => { | ||||||||||||||||||||||||||||||||||||
| throw(error); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /** | ||
| * Custom runtime while loop for AWS lambda. | ||
| * Listens for function events, executes handler, and returns results. | ||
| * | ||
| * ENV variables based on https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime | ||
| * API endpoints based on https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html | ||
| */ | ||
|
|
||
| import { handler } from "./handler.js"; | ||
|
|
||
| const RUNTIME_API = process.env.AWS_LAMBDA_RUNTIME_API; | ||
| const API_BASE = `http://${RUNTIME_API}/2018-06-01/runtime`; | ||
|
|
||
| while (true) { | ||
| const nextResponse = await fetch(`${API_BASE}/invocation/next`); | ||
| const event = await nextResponse.json(); | ||
| const requestId = nextResponse.headers.get("Lambda-Runtime-Aws-Request-Id"); | ||
|
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for Lambda API invocation fetch. The fetch to retrieve the next invocation lacks error handling. Network failures or API errors will crash the runtime loop, causing the Lambda function to fail permanently. 🛡️ Proposed fix with error handling- const nextResponse = await fetch(`${API_BASE}/invocation/next`);
- const event = await nextResponse.json();
- const requestId = nextResponse.headers.get("Lambda-Runtime-Aws-Request-Id");
+ let nextResponse, event, requestId;
+ try {
+ nextResponse = await fetch(`${API_BASE}/invocation/next`);
+ event = await nextResponse.json();
+ requestId = nextResponse.headers.get("Lambda-Runtime-Aws-Request-Id");
+ } catch (error) {
+ console.error("Failed to fetch next invocation:", error);
+ // Brief delay before retrying to avoid tight loop on persistent errors
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ continue;
+ }🤖 Prompt for AI Agents |
||
|
|
||
| // NOTE: If more context is needed inside the handler, they can be added here | ||
| const context = { awsRequestId: requestId }; | ||
|
|
||
| try { | ||
| const response = await handler(event, context); | ||
|
|
||
| await fetch(`${API_BASE}/invocation/${requestId}/response`, { | ||
| method: "POST", | ||
| body: JSON.stringify(response), | ||
| }); | ||
| } catch (error) { | ||
| console.error(error); | ||
| await fetch(`${API_BASE}/invocation/${requestId}/error`, { | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| errorMessage: error.message, | ||
| errorType: "Runtime.UserCodeError", | ||
| stackTrace: error.stack ? error.stack.split("\n") : [], | ||
| }), | ||
| }); | ||
|
Comment on lines
+25
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for response/error posting. The fetch calls that post responses and errors lack error handling. If these POST requests fail, the runtime will crash instead of continuing to process subsequent invocations. 🛡️ Proposed fix with error handling try {
const response = await handler(event, context);
- await fetch(`${API_BASE}/invocation/${requestId}/response`, {
- method: "POST",
- body: JSON.stringify(response),
- });
+ try {
+ await fetch(`${API_BASE}/invocation/${requestId}/response`, {
+ method: "POST",
+ body: JSON.stringify(response),
+ });
+ } catch (postError) {
+ console.error("Failed to post response:", postError);
+ }
} catch (error) {
console.error(error);
- await fetch(`${API_BASE}/invocation/${requestId}/error`, {
- method: "POST",
- body: JSON.stringify({
- errorMessage: error.message,
- errorType: "Runtime.UserCodeError",
- stackTrace: error.stack ? error.stack.split("\n") : [],
- }),
- });
+ try {
+ await fetch(`${API_BASE}/invocation/${requestId}/error`, {
+ method: "POST",
+ body: JSON.stringify({
+ errorMessage: error.message,
+ errorType: "Runtime.UserCodeError",
+ stackTrace: error.stack ? error.stack.split("\n") : [],
+ }),
+ });
+ } catch (postError) {
+ console.error("Failed to post error:", postError);
+ }
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||
|
|
||||||||||||||||||
| const aws = require('aws-sdk'), | ||||||||||||||||||
| fs = require('fs'), | ||||||||||||||||||
| path = require('path'), | ||||||||||||||||||
| uuid = require('uuid'), | ||||||||||||||||||
| util = require('util'), | ||||||||||||||||||
| stream = require('stream'); | ||||||||||||||||||
|
|
||||||||||||||||||
| class aws_storage { | ||||||||||||||||||
|
|
||||||||||||||||||
| constructor() { | ||||||||||||||||||
| this.S3 = new aws.S3(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| unique_name(file) { | ||||||||||||||||||
| let name = path.parse(file); | ||||||||||||||||||
| let uuid_name = uuid.v4().split('-')[0]; | ||||||||||||||||||
| return path.join(name.dir, util.format('%s.%s%s', name.name, uuid_name, name.ext)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| upload(bucket, file, filepath) { | ||||||||||||||||||
| var upload_stream = fs.createReadStream(filepath); | ||||||||||||||||||
| let uniqueName = this.unique_name(file); | ||||||||||||||||||
| let params = {Bucket: bucket, Key: uniqueName, Body: upload_stream}; | ||||||||||||||||||
| var upload = this.S3.upload(params); | ||||||||||||||||||
| return [uniqueName, upload.promise()]; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| download(bucket, file, filepath) { | ||||||||||||||||||
| var file = fs.createWriteStream(filepath); | ||||||||||||||||||
| this.S3.getObject( {Bucket: bucket, Key: file} ).createReadStream().pipe(file); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
Comment on lines
+29
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Variable shadowing breaks the download function. Line 30 redeclares 🐛 Proposed fix download(bucket, file, filepath) {
- var file = fs.createWriteStream(filepath);
- this.S3.getObject( {Bucket: bucket, Key: file} ).createReadStream().pipe(file);
+ var writeStream = fs.createWriteStream(filepath);
+ this.S3.getObject( {Bucket: bucket, Key: file} ).createReadStream().pipe(writeStream);
};📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 30-30: Shouldn't redeclare 'file'. Consider to delete it or rename it. 'file' is defined here: (lint/suspicious/noRedeclare) 🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| uploadStream(bucket, file) { | ||||||||||||||||||
| var write_stream = new stream.PassThrough(); | ||||||||||||||||||
| let uniqueName = this.unique_name(file); | ||||||||||||||||||
| // putObject won't work correctly for streamed data (length has to be known before) | ||||||||||||||||||
| // https://stackoverflow.com/questions/38442512/difference-between-upload-and-putobject-for-uploading-a-file-to-s3 | ||||||||||||||||||
| var upload = this.S3.upload( {Bucket: bucket, Key: uniqueName, Body: write_stream} ); | ||||||||||||||||||
| return [write_stream, upload.promise(), uniqueName]; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| // We return a promise to match the API for other providers | ||||||||||||||||||
| downloadStream(bucket, file) { | ||||||||||||||||||
| // AWS.Request -> read stream | ||||||||||||||||||
| let downloaded = this.S3.getObject( {Bucket: bucket, Key: file} ).createReadStream(); | ||||||||||||||||||
| return Promise.resolve(downloaded); | ||||||||||||||||||
| }; | ||||||||||||||||||
| }; | ||||||||||||||||||
| exports.storage = aws_storage; | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use HTTPS for Bootstrap CDN and consider updating Bootstrap version.
The template has two issues:
http://instead ofhttps://for the Bootstrap CDN creates a mixed content vulnerability and will trigger browser warnings when the page is served over HTTPS.🔒 Proposed fix
Or update to Bootstrap 5 (latest stable):
📝 Committable suggestion
🤖 Prompt for AI Agents