yinzerflow 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -1
- package/index.js.map +1 -1
- package/package.json +1 -1
package/index.d.ts
CHANGED
package/index.js.map
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"import type { InternalHttpHeaders } from '@typedefs/constants/http.js';\nimport type { InternalIpAddressResult } from '@typedefs/internal/InternalIpAddress.js';\nimport type { InternalIpValidationConfig } from '@typedefs/internal/InternalConfiguration.js';\nimport type { InternalSetupImpl } from '@typedefs/internal/InternalSetupImpl.ts';\n\n// Private IP ranges (RFC 1918, RFC 4193, RFC 3927)\nconst PRIVATE_IP_RANGES = [\n // IPv4 private ranges\n /^(?<classA>10)\\./,\n /^(?<classB>172)\\.(?<classBRange>1[6-9]|2[0-9]|3[0-1])\\./,\n /^(?<classC>192)\\.(?<classCRange>168)\\./,\n /^(?<linkLocal>169)\\.(?<linkLocalRange>254)\\./, // link-local\n /^(?<loopback>127)\\./, // loopback\n // IPv6 private ranges\n /^(?<ipv6Loopback>::1)$/, // loopback\n /^(?<ipv6LinkLocal>fe80):/i, // link-local\n /^(?<ipv6UniqueLocalFC>fc00):/i, // unique local\n /^(?<ipv6UniqueLocalFD>fd00):/i, // unique local\n];\n\n/**\n * Validates if an IP address is properly formatted\n */\nexport const isValidIpAddress = (ip: string): boolean => {\n if (!ip || typeof ip !== 'string') return false;\n\n // Remove brackets from IPv6 addresses\n const cleanIp = ip.replace(/^\\[|\\]$/g, '');\n\n // IPv4 validation with named capture groups\n const ipv4Regex = /^(?<octet>(?<highByte>25[0-5]|(?<midByte>2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$/;\n if (ipv4Regex.test(cleanIp)) {\n const parts = cleanIp.split('.');\n return (\n parts.length === 4 &&\n parts.every((part) => {\n const num = parseInt(part, 10);\n return num >= 0 && num <= 255;\n })\n );\n }\n\n // IPv6 validation with named capture groups (simplified but robust)\n // First check for invalid patterns (multiple :: compression)\n if (cleanIp.includes('::') && (cleanIp.match(/::/g) ?? []).length > 1) {\n return false; // Multiple :: compression is invalid\n }\n\n const ipv6Regex =\n /^(?<ipv6Address>(?<fullAddress>(?<hexQuad>[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4})|(?<compressedAddress>(?<leadingPart>[0-9a-fA-F]{1,4}:){1,7}:)|(?<mixedCompression>(?<frontPart>[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})|(?<doubleColonOnly>::)|(?<linkLocal>fe80:(?<linkSuffix>:[0-9a-fA-F]{0,4}){0,4}(?<zoneId>%[0-9a-zA-Z]+)?)|(?<ipv4MappedFull>::ffff:(?<mappedIpv4>(?<mappedOctet>[0-9]{1,3}\\.){3}[0-9]{1,3}))|(?<generalPattern>(?<segmentGroup>[0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}))$/;\n\n return ipv6Regex.test(cleanIp);\n};\n\n/**\n * Checks if an IP address is in a private range\n */\nexport const isPrivateIp = (ip: string): boolean => {\n if (!ip) return false;\n const cleanIp = ip.replace(/^\\[|\\]$/g, '');\n return PRIVATE_IP_RANGES.some((range) => range.test(cleanIp));\n};\n\n/**\n * Checks if an IP is in the trusted proxy list\n * Supports '*' wildcard to trust any proxy\n */\nexport const isTrustedProxy = (ip: string, trustedProxies: Array<string>): boolean => {\n if (!ip || !trustedProxies.length) return false;\n\n // Support '*' wildcard to trust any proxy\n if (trustedProxies.includes('*')) return true;\n\n return trustedProxies.includes(ip);\n};\n\n/**\n * Detects potential IP spoofing patterns\n */\nexport const detectSpoofingPatterns = (chain: Array<string>, config: InternalIpValidationConfig): boolean => {\n if (!config.detectSpoofing || chain.length <= 1) return false;\n\n // Pattern 1: Too many hops (potential amplification attack)\n if (chain.length > config.maxChainLength) return true;\n\n // Pattern 2: Duplicate IPs in chain (suspicious)\n const uniqueIps = new Set(chain);\n if (uniqueIps.size !== chain.length) return true;\n\n // Pattern 3: Mix of invalid and valid IPs (obfuscation attempt)\n const validCount = chain.filter(isValidIpAddress).length;\n if (validCount > 0 && validCount < chain.length) return true;\n\n // Pattern 4: Reverse proxy spoofing detection disabled\n // Note: The pattern \"public_ip, private_ip\" is normal for legitimate X-Forwarded-For headers\n // More sophisticated detection would require knowledge of expected network topology\n\n return false;\n};\n\n/**\n * Validates X-Forwarded-For proxy chain\n */\nconst _validateXForwardedForChain = (ipChain: Array<string>, config: InternalIpValidationConfig): boolean => {\n if (ipChain.length <= 1) return true;\n\n const lastIp = ipChain[ipChain.length - 1];\n return Boolean(lastIp && isTrustedProxy(lastIp, config.trustedProxies));\n};\n\n/**\n * Extracts client IP from IP chain based on header type\n */\nconst _extractClientIp = (ipChain: Array<string>, headerName: string): string | undefined => {\n if (headerName === 'x-forwarded-for') {\n return ipChain[0]; // Client IP is first in X-Forwarded-For\n }\n return ipChain[ipChain.length - 1]; // For other headers, take the last value\n};\n\n/**\n * Creates a valid IP result object\n */\nconst _createValidIpResult = (options: {\n clientIp: string;\n headerName: string;\n ipChain: Array<string>;\n config: InternalIpValidationConfig;\n}): InternalIpAddressResult => {\n const { clientIp, headerName, ipChain, config } = options;\n const isPrivate = isPrivateIp(clientIp);\n const trusted = headerName === 'x-forwarded-for' ? isTrustedProxy(ipChain[ipChain.length - 1] ?? '', config.trustedProxies) : true;\n\n return {\n ip: clientIp,\n isValid: true,\n isPrivate,\n source: headerName,\n trusted,\n };\n};\n\n/**\n * Creates an invalid IP result object\n */\nconst _createInvalidIpResult = (): InternalIpAddressResult => ({\n ip: '',\n isValid: false,\n isPrivate: false,\n source: 'socket',\n trusted: false,\n});\n\n/**\n * Extracts and validates IP addresses from headers with security checks\n */\nconst _extractIpFromHeaders = (headers: Partial<Record<InternalHttpHeaders, string>>, config: InternalIpValidationConfig): InternalIpAddressResult => {\n // Try each header in preference order\n for (const headerName of config.headerPreference) {\n const headerValue = headers[headerName];\n if (!headerValue) continue;\n\n // Handle comma-separated IP chains\n const ipChain = headerValue\n .split(',')\n .map((ip) => ip.trim())\n .filter(Boolean);\n if (ipChain.length === 0) continue;\n\n // Detect spoofing patterns\n if (detectSpoofingPatterns(ipChain, config)) continue;\n\n // For X-Forwarded-For, validate the proxy chain\n if (headerName === 'x-forwarded-for' && !_validateXForwardedForChain(ipChain, config)) {\n continue;\n }\n\n // Extract the client IP\n const clientIp = _extractClientIp(ipChain, headerName);\n if (!clientIp || !isValidIpAddress(clientIp)) continue;\n\n // Check if private IPs are allowed\n const isPrivate = isPrivateIp(clientIp);\n if (isPrivate && !config.allowPrivateIps) continue;\n\n return _createValidIpResult({ clientIp, headerName, ipChain, config });\n }\n\n return _createInvalidIpResult();\n};\n\n/**\n * Advanced IP parsing with comprehensive security validation\n * Uses server-level IP security configuration with optional overrides\n */\nexport const parseIpAddressSecure = (\n setup: InternalSetupImpl,\n headers: Partial<Record<InternalHttpHeaders, string>>,\n configOverride: Partial<InternalIpValidationConfig> = {},\n): InternalIpAddressResult => {\n const serverConfig = setup._configuration.ipSecurity;\n const finalConfig = { ...serverConfig, ...configOverride };\n\n // Try to extract IP from headers first\n const headerResult = _extractIpFromHeaders(headers, finalConfig);\n if (headerResult.isValid) {\n return headerResult;\n }\n\n // No valid IP found from headers\n return {\n ip: '',\n isValid: false,\n isPrivate: false,\n source: 'socket',\n trusted: false,\n };\n};\n\n/**\n * Simple IP parsing function that returns just the IP address string\n * Provides backward compatibility for existing code\n */\nexport const parseIpAddress = (setup: InternalSetupImpl, headers: Partial<Record<InternalHttpHeaders, string>>): string => {\n const result = parseIpAddressSecure(setup, headers);\n return result.ip;\n};\n",
|
|
23
23
|
"/**\n * Extract the boundary from the content type header\n *\n * @example\n * ```ts\n * extractBoundaryFromHeader('multipart/form-data; boundary=----WebKitFormBoundary1234567890');\n * // Returns '----WebKitFormBoundary1234567890'\n * ```\n */\nexport const extractBoundaryFromHeader = (contentTypeHeader?: string): string | undefined => {\n if (!contentTypeHeader) return undefined;\n\n const boundaryMatch = /boundary\\s*=\\s*(?<temp1>[^;,\\s]*)/i.exec(contentTypeHeader);\n return boundaryMatch?.[1];\n};\n",
|
|
24
24
|
"import type { InternalHttpHeaders } from '@typedefs/constants/http.js';\n\n// Security limits to prevent DoS attacks\nconst MAX_HEADERS = 100;\nconst MAX_HEADER_NAME_LENGTH = 200;\nconst MAX_HEADER_VALUE_LENGTH = 8192;\n\n/**\n * Parse incoming request headers from raw headers string with security validation\n *\n * This is the main orchestrator function that delegates to specialized helpers\n * for each processing step: validation → parsing → sanitization → security\n *\n * @example\n * ```ts\n * parseRequestHeaders('Host: example.com\\r\\nContent-Type: application/json\\r\\n\\r\\n');\n * // Returns { host: 'example.com', 'content-type': 'application/json' }\n * ```\n */\nexport const parseRequestHeaders = (rawHeaders: string): Partial<Record<InternalHttpHeaders, string>> => {\n if (!rawHeaders) return {};\n\n // Step 1: Pre-parse validation (format, DoS protection)\n validateRawHeaders(rawHeaders);\n\n // Step 2: Parse header structure into key-value pairs\n const parsedHeaders = parseHeaderStructure(rawHeaders);\n\n // Step 3: Sanitize header values (remove control chars, etc.)\n const sanitizedHeaders = sanitizeHeaders(parsedHeaders);\n\n // Step 4: Apply security policies (business logic, injection prevention)\n const securedHeaders = applySecurityPolicies(sanitizedHeaders);\n\n return securedHeaders;\n};\n\n/**\n * Step 1: Validate raw headers before parsing\n * Checks for DoS protection and basic format sanity\n */\nexport const validateRawHeaders = (rawHeaders: string): void => {\n // SECURITY: Limit number of potential headers to prevent DoS\n const headerLines = rawHeaders.split(/\\r\\n|\\r|\\n/);\n if (headerLines.length > MAX_HEADERS) {\n throw new Error(`Too many headers: maximum ${MAX_HEADERS} allowed`);\n }\n\n // Basic format validation could go here\n // (e.g., overall size limits, obvious malformation)\n};\n\n/**\n * Step 2: Parse header structure into key-value pairs\n * Handles line ending normalization and basic parsing\n */\nexport const parseHeaderStructure = (rawHeaders: string): Record<string, string> => {\n const headers: Record<string, string> = {};\n\n // Normalize line endings and split\n const normalizedHeaders = rawHeaders.replace(/\\r\\n|\\r|\\n/g, '\\n');\n const headerLines = normalizedHeaders.split('\\n');\n\n for (const line of headerLines) {\n if (!line.trim()) continue;\n\n const colonIndex = line.indexOf(':');\n if (colonIndex === -1) continue;\n\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n\n if (!key) continue;\n\n // SECURITY: Validate header name against RFC 7230\n if (!isValidHeaderName(key)) {\n throw new Error(`Invalid header name: ${key}`);\n }\n\n // SECURITY: Limit header name length\n if (key.length > MAX_HEADER_NAME_LENGTH) {\n throw new Error(`Header name too long: maximum ${MAX_HEADER_NAME_LENGTH} characters allowed`);\n }\n\n // SECURITY: Limit header value length\n if (value.length > MAX_HEADER_VALUE_LENGTH) {\n throw new Error(`Header value too long: maximum ${MAX_HEADER_VALUE_LENGTH} characters allowed`);\n }\n\n // Store with lowercase key for consistency\n headers[key.toLowerCase()] = value;\n }\n\n return headers;\n};\n\n/**\n * Step 3: Sanitize header values\n * Removes control characters and normalizes values\n */\nexport const sanitizeHeaders = (headers: Record<string, string>): Record<string, string> => {\n const sanitized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n sanitized[key] = sanitizeHeaderValue(value);\n }\n\n return sanitized;\n};\n\n/**\n * Step 4: Apply security policies\n * Business logic validation and injection prevention\n */\nexport const applySecurityPolicies = (headers: Record<string, string>): Record<string, string> =>\n // Note: CRLF injection detection would go here if we were setting response headers\n // For parsing incoming request headers, this step mainly handles business logic validation\n\n // Could add checks like:\n // - Suspicious header combinations\n // - Known attack patterns in values\n // - Business-specific header validation\n\n headers;\n\n/**\n * Validate header name according to RFC 7230\n */\nconst isValidHeaderName = (name: string): boolean => {\n // RFC 7230: header-name = token\n // token = 1*tchar\n // tchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" /\n // \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n const validHeaderNameRegex = /^[a-zA-Z0-9!#$%&'*+\\-.^_`|~]+$/;\n return validHeaderNameRegex.test(name);\n};\n\n/**\n * Sanitize header value to prevent injection attacks\n */\nconst sanitizeHeaderValue = (value: string): string => {\n // SECURITY: Remove control characters except horizontal tab\n // Allow printable ASCII, horizontal tab (0x09), and extended ASCII\n const sanitized = value.replace(/[\\x00-\\x08\\x0A-\\x1F\\x7F]/g, '');\n return sanitized;\n};\n",
|
|
25
|
-
"import { parseBody } from './utils/parseBody.ts';\nimport type { InternalContentType, InternalHttpHeaders, InternalHttpMethod } from '@typedefs/constants/http.js';\nimport { parseHttpRequest } from '@core/execution/utils/parseHttpRequest.ts';\nimport { parseQuery } from '@core/execution/utils/parseQuery.ts';\nimport { parseIpAddress } from '@core/execution/utils/parseIpAddress.ts';\nimport { extractBoundaryFromHeader } from '@core/execution/utils/extractBoundaryFromHeader.ts';\nimport { parseRequestHeaders } from '@core/execution/utils/parseRequestHeaders.ts';\nimport type { Request } from '@typedefs/public/Request.ts';\nimport type { SetupImpl } from '@core/setup/SetupImpl.ts';\nimport type { InternalRequestImpl } from '@typedefs/internal/InternalRequestImpl.ts';\nimport type { InternalSetupImpl } from '@typedefs/internal/InternalSetupImpl.ts';\n\nexport class RequestImpl implements InternalRequestImpl {\n readonly _rawRequest: Buffer | string;\n readonly _setup: InternalSetupImpl;\n\n method: InternalHttpMethod;\n path: string;\n protocol: string;\n headers: Partial<Record<InternalHttpHeaders, string>>;\n body: unknown;\n query: Record<string,
|
|
25
|
+
"import { parseBody } from './utils/parseBody.ts';\nimport type { InternalContentType, InternalHttpHeaders, InternalHttpMethod } from '@typedefs/constants/http.js';\nimport { parseHttpRequest } from '@core/execution/utils/parseHttpRequest.ts';\nimport { parseQuery } from '@core/execution/utils/parseQuery.ts';\nimport { parseIpAddress } from '@core/execution/utils/parseIpAddress.ts';\nimport { extractBoundaryFromHeader } from '@core/execution/utils/extractBoundaryFromHeader.ts';\nimport { parseRequestHeaders } from '@core/execution/utils/parseRequestHeaders.ts';\nimport type { Request } from '@typedefs/public/Request.ts';\nimport type { SetupImpl } from '@core/setup/SetupImpl.ts';\nimport type { InternalRequestImpl } from '@typedefs/internal/InternalRequestImpl.ts';\nimport type { InternalSetupImpl } from '@typedefs/internal/InternalSetupImpl.ts';\n\nexport class RequestImpl implements InternalRequestImpl {\n readonly _rawRequest: Buffer | string;\n readonly _setup: InternalSetupImpl;\n\n method: InternalHttpMethod;\n path: string;\n protocol: string;\n headers: Partial<Record<InternalHttpHeaders, string>>;\n body: unknown;\n query: Record<string, unknown>;\n params: Record<string, string>;\n ipAddress: string;\n rawBody: Buffer | string;\n\n constructor(rawRequest: Request['rawBody'], setup: SetupImpl, clientAddress?: string) {\n this._rawRequest = rawRequest;\n this._setup = setup;\n\n // Set initial IP address from socket, will be updated if headers provide better info\n this.ipAddress = clientAddress ?? '';\n\n const { method, path, protocol, headers, body, query, params, rawBody } = this._parseRequestIntoObject();\n\n this.method = method;\n this.path = path;\n this.protocol = protocol;\n this.headers = headers;\n this.body = body;\n this.query = query ?? {};\n this.params = params ?? {};\n this.rawBody = rawBody;\n\n // Update IP address if parsing from headers provides something\n const parsedIpAddress = parseIpAddress(this._setup, headers);\n if (parsedIpAddress) {\n this.ipAddress = parsedIpAddress;\n }\n }\n\n private _parseRequestIntoObject(): Omit<Request, 'ipAddress'> {\n const request = this._rawRequest.toString();\n\n const { method, path, protocol, headersRaw, rawBody } = parseHttpRequest(request);\n\n const headers = parseRequestHeaders(headersRaw);\n\n // Extract content type and boundary for body parsing\n const contentTypeHeader = headers['content-type'];\n const mainContentType = contentTypeHeader?.split(';')[0]?.trim().toLowerCase() as InternalContentType;\n const boundary = extractBoundaryFromHeader(contentTypeHeader);\n\n return {\n method,\n path,\n protocol,\n headers,\n body: parseBody(rawBody, {\n headerContentType: mainContentType,\n boundary,\n config: this._setup._configuration.bodyParser,\n }),\n query: parseQuery(path),\n params: {}, // Route params will be set in RequestHandlerImpl when route is matched\n rawBody,\n };\n }\n}\n",
|
|
26
26
|
"import dayjs from 'dayjs';\nimport { formatBodyIntoString } from '@core/execution/utils/formatBodyIntoString.ts';\nimport { determineEncoding } from '@core/execution/utils/determineEncoding.ts';\nimport { inferContentType } from '@core/execution/utils/inferContentType.ts';\nimport { mapStatusCodeToMessage } from '@core/execution/utils/mapStatusCodeToMessage.ts';\nimport { filterAndValidateHeaders } from '@core/execution/utils/validateResponseHeaders.ts';\nimport { httpEncoding, httpStatus, httpStatusCode } from '@constants/http.ts';\nimport type { InternalHttpEncoding, InternalHttpHeaders, InternalHttpStatus, InternalHttpStatusCode } from '@typedefs/constants/http.js';\nimport type { Request } from '@typedefs/public/Request.ts';\nimport type { InternalResponseImpl } from '@typedefs/internal/InternalResponseImpl.d.ts';\nimport { calculateContentSizeInBytes } from '@core/utils/calculateContentSizeInBytes.ts';\n\nexport class ResponseImpl implements InternalResponseImpl {\n readonly _request: Request;\n\n _statusCode: InternalHttpStatusCode = httpStatusCode.ok;\n _status: InternalHttpStatus = httpStatus.ok;\n _headers: Partial<Record<InternalHttpHeaders, string>> = {};\n _body: unknown = '';\n _stringBody = '';\n _encoding: InternalHttpEncoding = httpEncoding.utf8;\n\n constructor(request: Request) {\n this._request = request;\n this._setSecurityHeaders();\n }\n\n _parseResponseIntoString(): void {\n // Example: HTTP/1.1 200 OK\n const statusLine = `${this._request.protocol} ${this._statusCode} ${this._status}`;\n\n // Example: Content-Type: text/html\n const headerLines = Object.entries(this._headers).map(([key, value]) => `${key}: ${value}`);\n\n // Determine encoding based on Content-Type header and body content\n const encoding = determineEncoding(this._headers['content-type'], this._body);\n\n // Example: <html><body><h1>Hello, world!</h1></body></html> or { \"message\": \"Hello, world!\" }\n const body = formatBodyIntoString(this._body, { encoding });\n\n // Example: HTTP/1.1 200 OK\\nContent-Type: text/html\\n\\n<html><body><h1>Hello, world!</h1></body></html>\n this._encoding = encoding;\n\n // Fix: Handle the case when there are no headers properly\n const headersSection = headerLines.length > 0 ? `${headerLines.join('\\n')}\\n` : '';\n this._stringBody = `${statusLine}\\n${headersSection}\\n${body}`;\n\n const contentLength = calculateContentSizeInBytes(this._stringBody);\n this._setHeadersIfNotSet({\n Date: dayjs().format('ddd, DD MMM YYYY HH:mm:ss [GMT]'),\n 'Content-Length': String(contentLength),\n });\n }\n\n _setHeadersIfNotSet(headers: Partial<Record<InternalHttpHeaders, string>>): void {\n // SECURITY: Filter undefined values and validate response headers for CRLF injection\n // Only validate headers that aren't already set\n const headersToSet: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined && !(key in this._headers)) {\n headersToSet[key] = value;\n }\n }\n\n const validatedHeaders = filterAndValidateHeaders(headersToSet);\n\n // Set headers after validation passes\n Object.assign(this._headers, validatedHeaders);\n }\n\n _setBody(body: unknown): void {\n this._body = body;\n\n // Auto-set content-type if not already set\n if (!this._headers['content-type']) {\n const detectedContentType = inferContentType(body);\n this._setHeadersIfNotSet({\n 'Content-Type': detectedContentType,\n });\n }\n }\n\n setStatusCode(statusCode: InternalHttpStatusCode): void {\n this._statusCode = statusCode;\n this._status = mapStatusCodeToMessage(statusCode);\n }\n\n addHeaders(headers: Partial<Record<InternalHttpHeaders, string>>): void {\n // SECURITY: Filter undefined values and validate response headers for CRLF injection\n const validatedHeaders = filterAndValidateHeaders(headers);\n\n // Set headers after validation passes\n this._headers = { ...this._headers, ...validatedHeaders };\n }\n\n removeHeaders(headerNames: Array<InternalHttpHeaders>): void {\n for (const headerName of headerNames) {\n delete this._headers[headerName];\n }\n }\n\n /**\n * Set default security headers to protect against common vulnerabilities\n * These headers are set only if not already present, allowing users to override if needed\n */\n _setSecurityHeaders(): void {\n this._setHeadersIfNotSet({\n 'X-Content-Type-Options': 'nosniff',\n 'X-Frame-Options': 'DENY',\n 'X-XSS-Protection': '1; mode=block',\n 'Referrer-Policy': 'strict-origin-when-cross-origin',\n });\n }\n}\n",
|
|
27
27
|
"/**\n * Format the body into a string for HTTP response transmission\n *\n * Handles various body types including:\n * - Objects/Arrays: JSON stringified\n * - Strings: returned as-is\n * - Binary data (Buffer/Uint8Array): converted to base64 or kept as binary string\n * - Primitives: converted to string\n * - null/undefined: empty string\n *\n * @example\n * ```typescript\n * // JSON object\n * const body = { message: 'Hello, world!' };\n * const formattedBody = formatBodyIntoString(body);\n * // formattedBody === '{\"message\":\"Hello, world!\"}'\n *\n * // Binary data\n * const binaryBody = Buffer.from('binary data');\n * const formattedBinary = formatBodyIntoString(binaryBody);\n * // formattedBinary === 'binary data' (as string)\n *\n * // Base64 encoding option for true binary\n * const imageBuffer = Buffer.from([0xFF, 0xD8, 0xFF]); // JPEG header\n * const base64Body = formatBodyIntoString(imageBuffer, { encoding: 'base64' });\n * // base64Body === '/9j/' (base64 encoded)\n * ```\n */\nexport const formatBodyIntoString = (body: unknown, options?: { encoding?: 'base64' | 'binary' | 'utf8' }): string => {\n const encoding = options?.encoding ?? 'utf8';\n\n // Handle null/undefined\n if (body === null || body === undefined) return '';\n\n // Handle binary data types\n if (Buffer.isBuffer(body)) return handleBuffer(body, encoding);\n if (body instanceof Uint8Array) return handleUint8Array(body, encoding);\n if (body instanceof ArrayBuffer) return handleArrayBuffer(body, encoding);\n\n // Handle strings\n if (typeof body === 'string') return body;\n\n // Handle objects and arrays (including Date, etc.)\n if (typeof body === 'object') return handleObjectsAndArrays(body);\n\n // Handle all primitives and functions - they all stringify the same way\n // This covers: number, boolean, bigint, symbol, function\n return String(body as string);\n};\n\nconst handleBuffer = (body: Buffer, encoding: 'base64' | 'binary' | 'utf8'): string => {\n if (encoding === 'base64') return body.toString('base64');\n if (encoding === 'binary') return body.toString('binary');\n return body.toString('utf8');\n};\n\nconst handleUint8Array = (body: Uint8Array, encoding: 'base64' | 'binary' | 'utf8'): string => {\n const buffer = Buffer.from(body);\n return handleBuffer(buffer, encoding);\n};\n\nconst handleArrayBuffer = (body: ArrayBuffer, encoding: 'base64' | 'binary' | 'utf8'): string => {\n const buffer = Buffer.from(body);\n return handleBuffer(buffer, encoding);\n};\n\nconst handleObjectsAndArrays = (body: unknown): string => {\n try {\n return JSON.stringify(body);\n } catch (_) {\n // Fallback for non-serializable objects (circular refs, etc.)\n // This will return something like \"[object Object]\" but that's better than crashing\n return String(body);\n }\n};\n",
|
|
28
28
|
"import { httpStatus, httpStatusCode } from '@constants/http.ts';\nimport type { InternalHttpStatus, InternalHttpStatusCode } from '@typedefs/constants/http.js';\n\n/**\n * Map of status codes to their corresponding status messages\n * Built dynamically from the constants to ensure they stay in sync\n */\nconst statusCodeMap = new Map<number, string>();\n\n// Build the map from the constants\nfor (const [key, code] of Object.entries(httpStatusCode)) {\n const statusKey = key as keyof typeof httpStatus;\n const message = httpStatus[statusKey];\n\n statusCodeMap.set(code, message);\n}\n\n/**\n * Map a status code to its corresponding HTTP status message\n *\n * @param statusCode - The HTTP status code to map\n * @returns The corresponding HTTP status message\n *\n * @example\n * ```typescript\n * mapStatusCodeToMessage(200) // returns \"OK\"\n * mapStatusCodeToMessage(404) // returns \"Not Found\"\n * mapStatusCodeToMessage(500) // returns \"Internal Server Error\"\n * ```\n */\nexport const mapStatusCodeToMessage = (statusCode: InternalHttpStatusCode): InternalHttpStatus => {\n const message = statusCodeMap.get(statusCode);\n\n if (!message) {\n throw new Error(`Unknown status code: ${statusCode}`);\n }\n\n return message as InternalHttpStatus;\n};\n\n/**\n * Check if a status code is valid/supported\n *\n * @param statusCode - The status code to validate\n * @returns true if the status code is supported\n */\nexport const isValidStatusCode = (statusCode: number): statusCode is InternalHttpStatusCode => statusCodeMap.has(statusCode);\n",
|
package/package.json
CHANGED