hikma-engine
Version:
Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents
266 lines (265 loc) • 8.93 kB
JavaScript
/**
* @file Response formatting utilities for consistent API responses.
* Provides standardized response formatting with metadata injection.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatResponse = exports.responseFormatter = exports.ResponseFormatter = void 0;
exports.timingMiddleware = timingMiddleware;
exports.compressResponse = compressResponse;
exports.validateResponse = validateResponse;
exports.calculateResponseSize = calculateResponseSize;
/**
* Creates standard response metadata from request context.
*/
function createResponseMetadata(req, startTime, options = {}) {
const processingTime = startTime ? Date.now() - startTime : undefined;
const metadata = {
timestamp: new Date().toISOString(),
requestId: req.context?.requestId || req.headers['x-request-id'] || 'unknown',
};
// Add optional metadata
if (processingTime !== undefined) {
metadata.processingTime = processingTime;
}
if (req.path) {
metadata.path = req.path;
}
if (req.method) {
metadata.method = req.method;
}
return metadata;
}
/**
* Creates pagination metadata for paginated responses.
*/
function createPaginationMetadata(currentPage, pageSize, totalResults, offset) {
const totalPages = totalResults ? Math.ceil(totalResults / pageSize) : undefined;
const hasNextPage = totalPages ? currentPage < totalPages : false;
const hasPreviousPage = currentPage > 1;
return {
currentPage,
totalPages,
pageSize,
totalResults,
hasNextPage,
hasPreviousPage,
nextPage: hasNextPage ? currentPage + 1 : undefined,
previousPage: hasPreviousPage ? currentPage - 1 : undefined,
offset,
};
}
/**
* Response formatter class for creating standardized API responses.
*/
class ResponseFormatter {
constructor(options = {}) {
this.options = {
includeDebugInfo: process.env.NODE_ENV === 'development',
includeCacheInfo: true,
includePerformance: true,
includeTimestamp: true,
...options,
};
}
/**
* Creates a successful API response.
*/
success(req, data, startTime, pagination) {
const metadata = createResponseMetadata(req, startTime, this.options);
const response = {
success: true,
data,
meta: metadata,
};
// Add pagination metadata if provided
if (pagination) {
response.meta.pagination = createPaginationMetadata(pagination.page, pagination.limit, pagination.total, pagination.offset);
}
return response;
}
/**
* Creates an error API response.
*/
error(req, code, message, statusCode, details, startTime) {
const metadata = createResponseMetadata(req, startTime, this.options);
const response = {
success: false,
error: {
code,
message,
details,
},
meta: metadata,
};
return response;
}
/**
* Creates a paginated response with search results.
*/
paginatedResults(req, results, pagination, startTime, additionalData) {
const data = {
results,
...additionalData,
};
return this.success(req, data, startTime, pagination);
}
/**
* Creates a health check response.
*/
healthCheck(req, status, checks, startTime) {
const data = {
status,
timestamp: new Date().toISOString(),
uptime: Math.floor(process.uptime()),
version: process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development',
checks,
};
return this.success(req, data, startTime);
}
/**
* Creates an extended response with additional metadata.
*/
extended(req, data, startTime, extensions) {
const response = this.success(req, data, startTime);
if (extensions && this.options.includeDebugInfo) {
const extendedMeta = response.meta;
if (extensions.cache && this.options.includeCacheInfo) {
extendedMeta.cache = extensions.cache;
}
if (extensions.performance && this.options.includePerformance) {
extendedMeta.performance = extensions.performance;
}
if (extensions.debug && this.options.includeDebugInfo) {
extendedMeta.debug = extensions.debug;
}
}
return response;
}
}
exports.ResponseFormatter = ResponseFormatter;
/**
* Default response formatter instance.
*/
exports.responseFormatter = new ResponseFormatter();
/**
* Utility functions for quick response formatting.
*/
exports.formatResponse = {
/**
* Quick success response.
*/
success: (req, data, startTime) => exports.responseFormatter.success(req, data, startTime),
/**
* Quick error response.
*/
error: (req, code, message, details, startTime) => exports.responseFormatter.error(req, code, message, undefined, details, startTime),
/**
* Quick paginated response.
*/
paginated: (req, results, page, limit, total, startTime) => exports.responseFormatter.paginatedResults(req, results, { page, limit, total, offset: (page - 1) * limit }, startTime),
/**
* Quick health check response.
*/
health: (req, status, checks, startTime) => exports.responseFormatter.healthCheck(req, status, checks, startTime),
};
/**
* Middleware to inject timing information into responses.
*/
function timingMiddleware(req, res, next) {
// Store start time in request context
if (req.context) {
req.context.startTime = Date.now();
}
// Override response methods to inject timing
const originalJson = res.json;
res.json = function (data) {
const startTime = req.context?.startTime;
// If data is already a formatted response, add timing if missing
if (data && typeof data === 'object' && 'success' in data && 'meta' in data) {
if (startTime && !data.meta.processingTime) {
data.meta.processingTime = Date.now() - startTime;
}
}
return originalJson.call(this, data);
};
next();
}
/**
* Response compression utility for large responses.
*/
function compressResponse(data) {
// Remove undefined values and empty arrays/objects
function cleanObject(obj) {
if (Array.isArray(obj)) {
return obj.map(cleanObject).filter(item => item !== undefined);
}
if (obj && typeof obj === 'object') {
const cleaned = {};
for (const [key, value] of Object.entries(obj)) {
const cleanedValue = cleanObject(value);
if (cleanedValue !== undefined) {
cleaned[key] = cleanedValue;
}
}
return Object.keys(cleaned).length > 0 ? cleaned : undefined;
}
return obj;
}
return cleanObject(data);
}
/**
* Response validation utility to ensure responses meet API standards.
*/
function validateResponse(response) {
const errors = [];
// Check basic structure
if (!response || typeof response !== 'object') {
errors.push('Response must be an object');
return { valid: false, errors };
}
// Check required fields
if (!('success' in response)) {
errors.push('Response must have a "success" field');
}
if (!('meta' in response)) {
errors.push('Response must have a "meta" field');
}
// Check meta structure
if (response.meta) {
if (!response.meta.timestamp) {
errors.push('Response meta must include timestamp');
}
if (!response.meta.requestId) {
errors.push('Response meta must include requestId');
}
}
// Check success response structure
if (response.success === true) {
if (!('data' in response)) {
errors.push('Success response must have a "data" field');
}
}
// Check error response structure
if (response.success === false) {
if (!response.error) {
errors.push('Error response must have an "error" field');
}
else {
if (!response.error.code) {
errors.push('Error response must have an error code');
}
if (!response.error.message) {
errors.push('Error response must have an error message');
}
}
}
return { valid: errors.length === 0, errors };
}
/**
* Response size calculator for monitoring.
*/
function calculateResponseSize(response) {
return Buffer.byteLength(JSON.stringify(response), 'utf8');
}
;