@vercel/fun
Version:
Local Lambda development environment
201 lines • 8.22 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RuntimeServer = void 0;
const node_http_1 = require("node:http");
const node_url_1 = require("node:url");
const debug_1 = __importDefault(require("debug"));
const micro_1 = require("micro");
const uuid_1 = require("uuid");
const path_match_1 = __importDefault(require("path-match"));
const once_1 = __importDefault(require("@tootallnate/once"));
const deferred_1 = require("./deferred");
const pathMatch = (0, path_match_1.default)();
const match = pathMatch('/:version/runtime/:subject/:target/:action?');
const debug = (0, debug_1.default)('@vercel/fun:runtime-server');
function send404(res) {
res.statusCode = 404;
res.end();
}
class RuntimeServer extends node_http_1.Server {
constructor(fn) {
super();
this.version = '2018-06-01';
const serve = this.serve.bind(this);
this.on('request', (req, res) => (0, micro_1.run)(req, res, serve));
this.lambda = fn;
this.initDeferred = (0, deferred_1.createDeferred)();
this.resetInvocationState();
}
resetInvocationState() {
this.nextDeferred = (0, deferred_1.createDeferred)();
this.invokeDeferred = null;
this.resultDeferred = null;
this.currentRequestId = (0, uuid_1.v4)();
}
serve(req, res) {
return __awaiter(this, void 0, void 0, function* () {
debug('%s %s', req.method, req.url);
const params = match((0, node_url_1.parse)(req.url).pathname);
if (!params) {
return send404(res);
}
const { version, subject, target, action } = params;
if (this.version !== version) {
debug('Invalid API version, expected %o but got %o', this.version, version);
return send404(res);
}
// Routing logic
if (subject === 'invocation') {
if (target === 'next') {
return this.handleNextInvocation(req, res);
}
else {
// Assume it's an "AwsRequestId"
if (action === 'response') {
return this.handleInvocationResponse(req, res, target);
}
else if (action === 'error') {
return this.handleInvocationError(req, res, target);
}
else {
return send404(res);
}
}
}
else if (subject === 'init') {
if (target === 'error') {
return this.handleInitializationError(req, res);
}
else {
return send404(res);
}
}
else {
return send404(res);
}
});
}
handleNextInvocation(req, res) {
return __awaiter(this, void 0, void 0, function* () {
const { initDeferred } = this;
if (initDeferred) {
debug('Runtime successfully initialized');
this.initDeferred = null;
initDeferred.resolve();
}
this.invokeDeferred = (0, deferred_1.createDeferred)();
this.resultDeferred = (0, deferred_1.createDeferred)();
this.nextDeferred.resolve();
this.nextDeferred = null;
debug('Waiting for the `invoke()` function to be called');
// @ts-ignore
req.setTimeout(0); // disable default 2 minute socket timeout
const params = yield this.invokeDeferred.promise;
// TODO: use dynamic values from lambda params
const deadline = 5000;
const functionArn = 'arn:aws:lambda:us-west-1:977805900156:function:nate-dump';
res.setHeader('Lambda-Runtime-Aws-Request-Id', this.currentRequestId);
res.setHeader('Lambda-Runtime-Invoked-Function-Arn', functionArn);
res.setHeader('Lambda-Runtime-Deadline-Ms', String(deadline));
const finish = (0, once_1.default)(res, 'finish');
res.end(params.Payload);
yield finish;
});
}
handleInvocationResponse(req, res, requestId) {
return __awaiter(this, void 0, void 0, function* () {
// `RequestResponse` = 200
// `Event` = 202
// `DryRun` = 204
const statusCode = 200;
const payload = {
StatusCode: statusCode,
ExecutedVersion: '$LATEST',
Payload: yield (0, micro_1.text)(req, { limit: '6mb' })
};
res.statusCode = 202;
const finish = (0, once_1.default)(res, 'finish');
res.end();
yield finish;
this.resultDeferred.resolve(payload);
this.resetInvocationState();
});
}
handleInvocationError(req, res, requestId) {
return __awaiter(this, void 0, void 0, function* () {
const statusCode = 200;
const payload = {
StatusCode: statusCode,
FunctionError: 'Handled',
ExecutedVersion: '$LATEST',
Payload: yield (0, micro_1.text)(req, { limit: '6mb' })
};
res.statusCode = 202;
const finish = (0, once_1.default)(res, 'finish');
res.end();
yield finish;
this.resultDeferred.resolve(payload);
this.resetInvocationState();
});
}
handleInitializationError(req, res) {
return __awaiter(this, void 0, void 0, function* () {
const statusCode = 200;
const payload = {
StatusCode: statusCode,
FunctionError: 'Unhandled',
ExecutedVersion: '$LATEST',
Payload: yield (0, micro_1.text)(req, { limit: '6mb' })
};
res.statusCode = 202;
const finish = (0, once_1.default)(res, 'finish');
res.end();
yield finish;
this.initDeferred.resolve(payload);
});
}
invoke(params = { InvocationType: 'RequestResponse' }) {
return __awaiter(this, void 0, void 0, function* () {
if (this.nextDeferred) {
debug('Waiting for `next` invocation request from runtime');
yield this.nextDeferred.promise;
}
if (!params.Payload) {
params.Payload = '{}';
}
this.invokeDeferred.resolve(params);
const result = yield this.resultDeferred.promise;
return result;
});
}
close(callback) {
const deferred = this.initDeferred || this.resultDeferred;
if (deferred) {
const statusCode = 200;
deferred.resolve({
StatusCode: statusCode,
FunctionError: 'Unhandled',
ExecutedVersion: '$LATEST',
Payload: JSON.stringify({
errorMessage: `RequestId: ${this.currentRequestId} Process exited before completing request`
})
});
}
super.close(callback);
return this;
}
}
exports.RuntimeServer = RuntimeServer;
//# sourceMappingURL=runtime-server.js.map