@noony-serverless/core
Version:
A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript
240 lines • 9.03 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Handler = void 0;
// Container import for direct container creation (no pooling needed)
const typedi_1 = __importDefault(require("typedi"));
const core_1 = require("./core");
/**
* The Handler class is responsible for managing and executing middleware functions
* and a main handler function in a sequential and controlled manner.
*
* This class provides a mechanism for registering middlewares that can
* process a request/response flow either before the main handler (via `before`),
* after the main handler (via `after`), or handle errors (via `onError`).
*
* @example
* ```typescript
* interface MessagePayload {
* action: string;
* data: Record<string, unknown>;
* }
*
* const handler = new Handler<MessagePayload>()
* .use(errorHandler())
* .use(bodyParser())
* .handle(async (context) => {
* const { req } = context;
* // Handle the request with type-safe access to req.validatedBody
* });
* ```
* @template T Type for the input request data.
*/
class Handler {
baseMiddlewares = [];
handler;
// Performance optimization: Pre-computed middleware arrays
reversedMiddlewares = [];
errorMiddlewares = [];
middlewaresPrecomputed = false;
static use(middleware) {
const handler = new Handler();
handler.baseMiddlewares.push(middleware);
return handler;
}
use(middleware) {
// Simple middleware chaining without complex type transformations
this.baseMiddlewares.push(middleware);
return this;
}
handle(handler) {
this.handler = handler;
this.precomputeMiddlewareArrays();
return this;
}
/**
* Performance optimization: Pre-compute middleware arrays to avoid runtime array operations
*/
precomputeMiddlewareArrays() {
if (this.middlewaresPrecomputed)
return;
// Pre-compute reversed array for after/error middlewares
this.reversedMiddlewares = [...this.baseMiddlewares].reverse();
this.errorMiddlewares = this.reversedMiddlewares.filter((m) => m.onError);
this.middlewaresPrecomputed = true;
}
/**
* Universal execute method that works with any HTTP framework
* Automatically detects and adapts GCP, Express, AWS Lambda, Fastify, etc.
*/
async execute(nativeReq, nativeRes) {
// Smart universal adapter - convert any framework's request/response to Noony format
const req = this.adaptToNoonyRequest(nativeReq);
const res = this.adaptToNoonyResponse(nativeRes, nativeReq);
// Direct container creation - simpler and appropriate for serverless
const container = typedi_1.default.of();
const context = (0, core_1.createContext)(req, res, {
container,
});
try {
// Execute before middlewares with performance optimizations
await this.executeBeforeMiddlewares(context);
await this.handler(context);
// Execute after middlewares in reverse order using pre-computed array
await this.executeAfterMiddlewares(context);
}
catch (error) {
context.error = error;
// Execute error handlers using pre-computed array
await this.executeErrorMiddlewares(error, context);
}
// No cleanup needed - container will be garbage collected automatically
}
/**
* Execute before middlewares with optimized batching for independent middlewares
*/
async executeBeforeMiddlewares(context) {
// For backward compatibility and simpler logic, execute all before middlewares in order
for (const middleware of this.baseMiddlewares) {
if (middleware.before) {
await middleware.before(context);
}
}
}
/**
* Execute after middlewares using pre-computed reversed array
*/
async executeAfterMiddlewares(context) {
// For backward compatibility and simpler logic, execute all after middlewares in reverse order
for (const middleware of this.reversedMiddlewares) {
if (middleware.after) {
await middleware.after(context);
}
}
}
/**
* Execute error middlewares using pre-computed array
*/
async executeErrorMiddlewares(error, context) {
for (const middleware of this.errorMiddlewares) {
if (middleware.onError) {
await middleware.onError(error, context);
}
}
}
/**
* Universal request adapter - converts any framework's request to NoonyRequest
*/
adaptToNoonyRequest(nativeReq) {
const req = nativeReq;
// Universal property mapping that works with any HTTP framework
return {
method: req.method || req.httpMethod || core_1.HttpMethod.GET,
url: req.url || req.originalUrl || req.path || '/',
path: req.path || req.resource,
headers: req.headers || {},
query: req.query || req.queryStringParameters || {},
params: req.params || req.pathParameters || {},
body: req.body,
rawBody: req.rawBody || req.body,
parsedBody: req.parsedBody,
validatedBody: req.validatedBody,
ip: req.ip || req.requestContext?.identity?.sourceIp,
userAgent: (typeof req.headers?.['user-agent'] === 'string'
? req.headers['user-agent']
: Array.isArray(req.headers?.['user-agent'])
? req.headers['user-agent'][0]
: undefined) || req.get?.('user-agent'),
};
}
/**
* Universal response adapter - converts any framework's response to NoonyResponse
*/
adaptToNoonyResponse(nativeRes, nativeReq) {
const req = nativeReq;
// Detect AWS Lambda pattern (different from standard HTTP)
if (req?.requestContext && typeof nativeRes === 'function') {
return this.createAWSLambdaResponse(nativeRes);
}
// Standard HTTP response (GCP, Express, Fastify, etc.)
return this.createStandardHTTPResponse(nativeRes);
}
/**
* Create response adapter for AWS Lambda
*/
createAWSLambdaResponse(lambdaCallback) {
let statusCode = 200;
const headers = {};
let body;
return {
status: (code) => {
statusCode = code;
return this.createAWSLambdaResponse(lambdaCallback);
},
json: (data) => {
body = JSON.stringify(data);
headers['Content-Type'] = 'application/json';
lambdaCallback(null, { statusCode, headers, body });
},
send: (data) => {
body = data;
lambdaCallback(null, { statusCode, headers, body });
},
header: (name, value) => {
headers[name] = value;
return this.createAWSLambdaResponse(lambdaCallback);
},
headers: (newHeaders) => {
Object.assign(headers, newHeaders);
return this.createAWSLambdaResponse(lambdaCallback);
},
end: () => lambdaCallback(null, { statusCode, headers, body }),
statusCode,
headersSent: false,
};
}
/**
* Create response adapter for standard HTTP frameworks (GCP, Express, Fastify)
*/
createStandardHTTPResponse(nativeRes) {
const res = nativeRes;
return {
status: (code) => {
res.status(code);
return this.createStandardHTTPResponse(nativeRes);
},
json: (data) => res.json(data),
send: (data) => res.send(data),
header: (name, value) => {
res.header(name, value);
return this.createStandardHTTPResponse(nativeRes);
},
headers: (headers) => {
if (res.set) {
res.set(headers); // Express style
}
else {
Object.entries(headers).forEach(([k, v]) => res.header(k, v)); // GCP style
}
return this.createStandardHTTPResponse(nativeRes);
},
end: () => res.end(),
get statusCode() {
return res.statusCode;
},
get headersSent() {
return res.headersSent;
},
};
}
/**
* @deprecated Use execute() instead - automatically detects framework
*/
async executeGeneric(req, res) {
return this.execute(req, res);
}
}
exports.Handler = Handler;
//# sourceMappingURL=handler.js.map