UNPKG

restifyx.js

Version:

Advanced API endpoint handler system with automatic documentation

504 lines 17.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Endpoint = void 0; exports.CreateEndpoint = CreateEndpoint; const joi_1 = __importDefault(require("joi")); const logger_1 = require("../utils/logger"); /** * Fluent Function Code for defining endpoints * * @example * \`\`\`typescript * const endpoint = new Endpoint() * .setName('Get User') * .setDescription('Get user by ID') * .setPath('/api/users/:id') * .setMethod('GET') * .setTags(['users']) * .addResponse({ * 200: { message: 'Success' }, * 404: 'User not found' * }) * .addQuery({ * include: 'string' * }) * .setHandler(async (header, body, query, params) => { * // Implementation * SendData.json({ user: { id: params.id } }); * }) * \`\`\` */ class Endpoint { constructor() { this.metadata = {}; this.querySchema = {}; this.bodySchema = {}; this.headerSchema = {}; this.cookieSchema = {}; this.paramsSchema = {}; this.responses = {}; } /** * Set the name of the endpoint * * @param name - The name of the endpoint * @returns The Endpoint instance for chaining */ setName(name) { this.metadata.name = name; return this; } /** * Set the description of the endpoint * * @param description - The description of the endpoint * @returns The Endpoint instance for chaining */ setDescription(description) { this.metadata.description = description; return this; } /** * Set the path of the endpoint * * @param path - The path of the endpoint (e.g., '/api/users/:id') * @returns The Endpoint instance for chaining */ setPath(path) { this.metadata.path = path; return this; } /** * Set the HTTP method of the endpoint * * @param method - The HTTP method (GET, POST, PUT, DELETE, etc.) * @returns The Endpoint instance for chaining */ setMethod(method) { this.metadata.method = method; return this; } /** * Set tags for the endpoint (used for Swagger documentation) * * @param tags - Array of tags * @returns The Endpoint instance for chaining */ setTags(tags) { this.metadata.tags = tags; return this; } /** * Add middleware to the endpoint * * @param middleware - Express middleware function(s) * @returns The Endpoint instance for chaining */ addMiddleware(...middleware) { if (!this.metadata.middleware) { this.metadata.middleware = []; } this.metadata.middleware.push(...middleware); return this; } /** * Set rate limiting options for the endpoint * * @param options - Rate limiting options * @returns The Endpoint instance for chaining */ setRateLimit(options) { this.rateLimit = options; return this; } /** * Set caching options for the endpoint * * @param duration - Cache duration in seconds * @param keyGenerator - Optional function to generate cache key * @returns The Endpoint instance for chaining */ setCache(duration, keyGenerator) { this.cacheOptions = { duration, keyGenerator }; return this; } /** * Define expected query parameters and their types * * @param schema - Schema definition for query parameters * @returns The Endpoint instance for chaining */ addQuery(schema) { this.querySchema = { ...this.querySchema, ...schema }; return this; } /** * Define expected body parameters and their types * * @param schema - Schema definition for request body * @returns The Endpoint instance for chaining */ addBody(schema) { this.bodySchema = { ...this.bodySchema, ...schema }; return this; } /** * Define expected header parameters and their types * * @param schema - Schema definition for request headers * @returns The Endpoint instance for chaining */ addHeader(schema) { this.headerSchema = { ...this.headerSchema, ...schema }; return this; } /** * Define expected cookie parameters and their types * * @param schema - Schema definition for cookies * @returns The Endpoint instance for chaining */ addCookie(schema) { this.cookieSchema = { ...this.cookieSchema, ...schema }; return this; } /** * Define expected path parameters and their types * * @param schema - Schema definition for path parameters * @returns The Endpoint instance for chaining */ addParams(schema) { this.paramsSchema = { ...this.paramsSchema, ...schema }; return this; } /** * Define response schemas for different status codes * * @param responses - Object mapping status codes to response schemas * @returns The Endpoint instance for chaining */ addResponse(responses) { this.responses = { ...this.responses, ...responses }; return this; } /** * Set the handler function for the endpoint * * @param handler - The function that handles the endpoint request * @returns The Endpoint instance for chaining */ setHandler(handler) { this.handler = handler; return this; } /** * Build the endpoint metadata and handler * * @returns The endpoint metadata * @internal */ build() { if (!this.metadata.path || !this.metadata.method) { throw new Error("Endpoint must have a path and method"); } if (!this.handler) { throw new Error("Endpoint must have a handler function"); } const joiSchemas = {}; if (Object.keys(this.querySchema).length > 0) { joiSchemas.query = this.createJoiSchema(this.querySchema); } if (Object.keys(this.bodySchema).length > 0) { joiSchemas.body = this.createJoiSchema(this.bodySchema); } if (Object.keys(this.headerSchema).length > 0) { joiSchemas.headers = this.createJoiSchema(this.headerSchema); } if (Object.keys(this.cookieSchema).length > 0) { joiSchemas.cookies = this.createJoiSchema(this.cookieSchema); } if (Object.keys(this.paramsSchema).length > 0) { joiSchemas.params = this.createJoiSchema(this.paramsSchema); } const middleware = []; if (Object.keys(joiSchemas).length > 0) { const { validateRequest } = require("../core/validation"); middleware.push(validateRequest(joiSchemas)); } if (this.rateLimit) { const { rateLimitMiddleware } = require("../utils/rate-limiter"); middleware.push(rateLimitMiddleware(this.rateLimit)); } if (this.cacheOptions) { const { cacheMiddleware } = require("../utils/cache"); middleware.push(cacheMiddleware(this.cacheOptions)); } const { setupEndpointContext } = require("./EndpointHandler"); middleware.unshift(setupEndpointContext); const handlerFn = async (req, res, next) => { try { const logger = (0, logger_1.getLogger)(); if (!global.CURRENT_REQUEST || !global.CURRENT_RESPONSE) { logger.debug("Setting up endpoint context manually in handler"); global.CURRENT_REQUEST = req; global.CURRENT_RESPONSE = res; const originalEnd = res.end; res.end = function (chunk, encoding, cb) { delete global.CURRENT_REQUEST; delete global.CURRENT_RESPONSE; return originalEnd.call(this, chunk, encoding, cb); }; } res.locals.endpoint = { name: this.metadata.name, path: this.metadata.path, method: this.metadata.method, }; const context = { req, res, next }; await this.handler(req.headers, req.body || {}, req.query || {}, req.cookies || {}, req.params || {}, context); } catch (error) { next(error); } }; const endpointMetadata = { path: this.metadata.path, method: this.metadata.method, handler: handlerFn, name: this.metadata.name, description: this.metadata.description, tags: this.metadata.tags, middleware: [...(this.metadata.middleware || []), ...middleware], responses: this.buildResponsesMetadata(), parameters: this.buildParametersMetadata(), requestBody: this.buildRequestBodyMetadata(), }; return endpointMetadata; } createJoiSchema(schema) { const joiSchema = {}; for (const [key, value] of Object.entries(schema)) { if (typeof value === "string") { joiSchema[key] = this.createJoiSchemaForType(value); } else { let validator = this.createJoiSchemaForType(value.type); if (value.required) { validator = validator.required(); } if (value.min !== undefined) { if (value.type === "string") { validator = validator.min(value.min); } else if (value.type === "number") { validator = validator.min(value.min); } else if (value.type === "array") { validator = validator.min(value.min); } else if (value.type === "date") { validator = validator.min(value.min); } } if (value.max !== undefined) { if (value.type === "string" || value.type === "number" || value.type === "array" || value.type === "date") { if (typeof validator.max === "function") { validator = validator.max(value.max); } } } if (value.pattern) { if (value.type === "string" && typeof validator.pattern === "function") { validator = validator.pattern(value.pattern); } } if (value.default !== undefined) { validator = validator.default(value.default); } if (value.allow) { validator = validator.allow(...value.allow); } joiSchema[key] = validator; } } return joi_1.default.object(joiSchema); } createJoiSchemaForType(type) { switch (type) { case "string": return joi_1.default.string(); case "number": return joi_1.default.number(); case "boolean": return joi_1.default.boolean(); case "date": return joi_1.default.date(); case "decimal": return joi_1.default.number().precision(2); case "object": return joi_1.default.object(); case "array": return joi_1.default.array(); default: return joi_1.default.any(); } } buildResponsesMetadata() { const responses = {}; for (const [statusCode, response] of Object.entries(this.responses)) { responses[statusCode] = { description: typeof response === "string" ? response : `${statusCode} response`, content: typeof response === "string" ? undefined : { "application/json": { schema: { type: "object", example: response, }, }, }, }; } if (!responses["400"] && (Object.keys(this.querySchema).length > 0 || Object.keys(this.bodySchema).length > 0 || Object.keys(this.headerSchema).length > 0 || Object.keys(this.paramsSchema).length > 0)) { responses["400"] = { description: "Bad Request - Validation Error", content: { "application/json": { schema: { type: "object", properties: { status: { type: "string", example: "error" }, statusCode: { type: "number", example: 400 }, message: { type: "string", example: "Validation error" }, errorCode: { type: "string", example: "VALIDATION_ERROR" }, details: { type: "array", items: { type: "string" } }, timestamp: { type: "number" }, }, }, }, }, }; } if (!responses["500"]) { responses["500"] = { description: "Internal Server Error", content: { "application/json": { schema: { type: "object", properties: { status: { type: "string", example: "error" }, statusCode: { type: "number", example: 500 }, message: { type: "string", example: "Internal server error" }, errorCode: { type: "string", example: "INTERNAL_ERROR" }, timestamp: { type: "number" }, }, }, }, }, }; } return responses; } buildParametersMetadata() { const parameters = []; for (const [name, schema] of Object.entries(this.querySchema)) { parameters.push({ name, in: "query", required: typeof schema === "object" ? schema.required : false, schema: this.buildParameterSchema(schema), }); } for (const [name, schema] of Object.entries(this.paramsSchema)) { parameters.push({ name, in: "path", required: true, schema: this.buildParameterSchema(schema), }); } for (const [name, schema] of Object.entries(this.headerSchema)) { parameters.push({ name, in: "header", required: typeof schema === "object" ? schema.required : false, schema: this.buildParameterSchema(schema), }); } for (const [name, schema] of Object.entries(this.cookieSchema)) { parameters.push({ name, in: "cookie", required: typeof schema === "object" ? schema.required : false, schema: this.buildParameterSchema(schema), }); } return parameters; } buildRequestBodyMetadata() { if (Object.keys(this.bodySchema).length === 0) { return undefined; } const properties = {}; const required = []; for (const [name, schema] of Object.entries(this.bodySchema)) { properties[name] = this.buildParameterSchema(schema); if (typeof schema === "object" && schema.required) { required.push(name); } } return { required: true, content: { "application/json": { schema: { type: "object", properties, required: required.length > 0 ? required : undefined, }, }, }, }; } buildParameterSchema(schema) { const type = typeof schema === "string" ? schema : schema.type; switch (type) { case "string": return { type: "string" }; case "number": return { type: "number" }; case "boolean": return { type: "boolean" }; case "date": return { type: "string", format: "date-time" }; case "decimal": return { type: "number", format: "float" }; case "object": return { type: "object" }; case "array": return { type: "array", items: { type: "string" } }; default: return { type: "string" }; } } } exports.Endpoint = Endpoint; /** * Create a new endpoint with a fluent function * * @returns A new Endpoint instance */ function CreateEndpoint() { return new Endpoint(); } //# sourceMappingURL=Endpoint.js.map