UNPKG

@noony-serverless/core

Version:

A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript

240 lines 9.03 kB
"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