@morojs/moro 1.8.15 → 1.8.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"package-utils.js","sourceRoot":"","sources":["../../../src/core/utilities/package-utils.ts"],"names":[],"mappings":"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","sourcesContent":["// Package and Module Resolution Utilities\n// ESM-compatible helpers for detecting and resolving optional dependencies\n\nimport { createRequire } from 'module';\nimport { dirname, join } from 'path';\nimport { existsSync, realpathSync } from 'fs';\nimport { pathToFileURL } from 'url';\nimport { selfRequire } from './self-require.js';\n\n/**\n * Check if a package is available in the user's node_modules\n * Uses ESM-compatible module resolution from the user's working directory\n *\n * @param packageName - The name of the package to check (e.g., 'socket.io', 'zod')\n * @returns true if the package is installed, false otherwise\n *\n * @example\n * if (isPackageAvailable('socket.io')) {\n * // socket.io is installed, safe to import\n * const { Server } = await import('socket.io');\n * }\n */\nexport function isPackageAvailable(packageName: string): boolean {\n try {\n const userRequire = createRequire(join(process.cwd(), 'package.json'));\n userRequire.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve a package path from the user's node_modules\n * Uses ESM-compatible module resolution from the user's working directory\n *\n * @param packageName - The name of the package to resolve\n * @returns The resolved package path\n * @throws Error if the package is not found\n *\n * @example\n * const socketIOPath = resolveUserPackage('socket.io');\n * const { Server } = await import(socketIOPath);\n */\nexport function resolveUserPackage(packageName: string): string {\n const userRequire = createRequire(join(process.cwd(), 'package.json'));\n return userRequire.resolve(packageName);\n}\n\n/**\n * Create a require function for the user's working directory\n * Useful for resolving packages installed by the user, not in Moro's dependencies\n *\n * @returns A require function scoped to the user's working directory\n *\n * @example\n * const userRequire = createUserRequire();\n * const packagePath = userRequire.resolve('some-package');\n */\nexport function createUserRequire() {\n return createRequire(join(process.cwd(), 'package.json'));\n}\n\n// ---------------------------------------------------------------------------\n// Native HTTP engine loading\n// ---------------------------------------------------------------------------\n\n/**\n * Package names probed for the native HTTP engine, in priority order.\n * '@morojs/engine' is Moro's own engine (installed as an optionalDependency of\n * the framework); 'uWebSockets.js' is honored as a legacy user-installed peer.\n */\nexport const NATIVE_ENGINE_PACKAGES = ['@morojs/engine', 'uWebSockets.js'] as const;\n\nconst FRAMEWORK_PACKAGE_NAME = '@morojs/moro';\n\n/** Feature flags the framework gates option-passing on, so it never hands a\n * serve() option to an engine build that predates it. Everything defaults\n * false, so an older engine behaves exactly as it did before these existed. */\nexport interface EngineCapabilities {\n /** The full serve() limit surface is parsed (engine >= 1.1.0). */\n limits: boolean;\n /** In-process TLS termination via options.ssl (engine >= 1.1.x). */\n tls: boolean;\n /** ALPN HTTP/2 via options.http2 (reserved). */\n http2: boolean;\n /** WebSocket permessage-deflate via options.wsDeflate. */\n wsDeflate: boolean;\n /** responseTimeoutMs / responseBackpressureLimit / maxUriSize are parsed. */\n responseLimits: boolean;\n /** ssl.ciphers / ssl.ciphersuites / ssl.ecdhCurve are parsed. */\n tlsPolicy: boolean;\n /** updateSsl(serverId, ssl) exists: certificate rotation without a restart\n * (engine >= 1.1.8). Gates app.reloadTLS() on the engine. */\n tlsReload: boolean;\n /** setStaticRoute()/clearStaticRoutes() exist. Used for a literal body\n * given to the route builder's .handler() when the route has nothing\n * else configured, because a static route bypasses middleware, hooks,\n * auth and validation by design. */\n staticRoutes: boolean;\n /** prepareResponse()/respondPrepared()/respondPreparedEmpty()/endWith()\n * exist (engine >= 1.1.6): the adapter replays prepared header blocks for\n * the responses it emits itself. */\n responseTemplates: boolean;\n /** onAborted/onWritable are delivered on a later loop turn, never from\n * inside respond()/write()/end() (engine >= 1.1.6). */\n asyncNotify: boolean;\n /** A server left open at environment teardown is closed by the engine's\n * cleanup hook, so the engine may run inside worker threads that can be\n * terminate()d (engine >= 1.1.6). Gates thread-based clustering. */\n workerThreads: boolean;\n /** V8 fast API calls are installed on the hot entry points (informational). */\n fastCalls: boolean;\n /** Batched pipelined dispatch (onRequestBatch / getBatchBuffers / getPath). */\n batchDispatch: boolean;\n /** Every callback into JS runs inside a Node callback scope, so nextTicks\n * and microtasks queued during dispatch run when the callback returns;\n * the adapter then needs no drain of its own (engine > 1.1.6). */\n callbackScope: boolean;\n /** setParamRoute()/clearParamRoutes() exist (engine >= 1.1.9): a route\n * with one variable path segment, echoed as the body, answered inside the\n * engine. Used for param(name) handlers on bare routes. */\n paramRoutes: boolean;\n /** I/O transport in use ('epoll', 'uv', 'uring'), when the engine reports it. */\n transport?: string;\n}\n\n// One place to name every boolean flag: getEngineCapabilities coerces each\n// with `=== true`, and NO_CAPABILITIES is derived from the same list, so a\n// flag added here can never be read on one path and forgotten on the other.\nconst CAP_KEYS = [\n 'limits',\n 'tls',\n 'http2',\n 'wsDeflate',\n 'responseLimits',\n 'tlsPolicy',\n 'tlsReload',\n 'staticRoutes',\n 'responseTemplates',\n 'asyncNotify',\n 'workerThreads',\n 'fastCalls',\n 'callbackScope',\n 'batchDispatch',\n 'paramRoutes',\n] as const;\n\nexport interface NativeEngineLoadResult {\n /** The loaded engine module (CJS namespace: App, SSLApp, ...) */\n module: any;\n /** Which package satisfied the load */\n source: string;\n /** The engine package's version, when its package.json is resolvable */\n version?: string;\n /** Feature flags (Moro-shaped engine only; all-false for uWS or an\n * engine too old to advertise them). */\n capabilities?: EngineCapabilities;\n}\n\nconst NO_CAPABILITIES: EngineCapabilities = Object.freeze(\n Object.fromEntries(CAP_KEYS.map(k => [k, false]))\n) as unknown as EngineCapabilities;\n\nconst capabilitiesCache = new WeakMap<object, EngineCapabilities>();\n\n/**\n * Feature-detect a loaded engine module. Reads probe().capabilities when the\n * engine exposes it (>= 1.1.0); otherwise everything is false so the framework\n * passes only the options an older engine understood. uWS-style modules (no\n * probe/serve) report all-false — their SSL/limits go through the uWS-specific\n * paths, not these flags.\n */\nexport function getEngineCapabilities(module: any): EngineCapabilities {\n const surface = module?.default || module;\n if (!surface || typeof surface !== 'object') return NO_CAPABILITIES;\n const cached = capabilitiesCache.get(surface);\n if (cached) return cached;\n\n let caps: EngineCapabilities = NO_CAPABILITIES;\n try {\n if (typeof surface.probe === 'function') {\n const probe = surface.probe();\n const c = probe?.capabilities;\n if (c && typeof c === 'object') {\n const next: Record<string, boolean | string> = {};\n for (const key of CAP_KEYS) next[key] = c[key] === true;\n if (typeof probe.transport === 'string') next.transport = probe.transport;\n caps = next as unknown as EngineCapabilities;\n }\n }\n } catch {\n caps = NO_CAPABILITIES;\n }\n capabilitiesCache.set(surface, caps);\n return caps;\n}\n\n// undefined = never attempted, null = attempted and failed\nlet cachedEngine: NativeEngineLoadResult | null | undefined;\nlet engineLoadErrors: string[] = [];\n\n/**\n * Locate the installed framework package directory so the engine (a dependency\n * of moro, not of the user's app) resolves under strict layouts like pnpm's.\n * Walks up from cwd looking for node_modules/@morojs/moro, resolving symlinks\n * so pnpm's store-linked layout anchors requires next to moro's own deps.\n * Inside the framework repo itself (tests, examples) cwd is the framework dir.\n */\nfunction findFrameworkAnchor(): ReturnType<typeof createRequire> | null {\n // Primary: anchor to moro's OWN compiled location, so moro's `@morojs/engine`\n // dependency resolves relative to where moro is installed regardless of\n // `process.cwd()` (Docker WORKDIR, supervisord, pm2, a subdirectory, …). The\n // cwd-based walk below is a defensive fallback for exotic layouts.\n if (selfRequire) return selfRequire;\n\n try {\n const cwdPackageJson = join(process.cwd(), 'package.json');\n if (existsSync(cwdPackageJson)) {\n const require = createRequire(cwdPackageJson);\n const name = require(cwdPackageJson)?.name;\n if (name === FRAMEWORK_PACKAGE_NAME) {\n return require;\n }\n }\n } catch {\n // fall through to the node_modules walk\n }\n\n let dir = process.cwd();\n for (;;) {\n const packageJson = join(dir, 'node_modules', '@morojs', 'moro', 'package.json');\n try {\n if (existsSync(packageJson)) {\n return createRequire(join(realpathSync(dirname(packageJson)), 'package.json'));\n }\n } catch {\n // unreadable/broken link - keep walking up\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Synchronously load the native HTTP engine, trying '@morojs/engine' first and\n * falling back to a user-installed 'uWebSockets.js'. Loading is require-based\n * (both packages are CJS at their core - .node addon loading is require-based),\n * so the instance is shared with any later import() via the CJS module cache.\n *\n * The default call is memoized: the engine either loads once per process or it\n * doesn't. Failure details are retained for logging via\n * getNativeEngineLoadErrors().\n *\n * @param options.candidates - Override the probed package names (testing).\n * When provided, the call bypasses the cache.\n * @returns The loaded engine and its source package, or null if none loads\n */\nexport function loadNativeEngine(options?: {\n candidates?: readonly string[];\n /** When provided, every per-attempt failure message is appended here, so a\n * candidate-override caller (which bypasses the memoized diagnostics) can\n * still report exactly WHY the engine didn't load — not just \"not installed\". */\n collectErrors?: string[];\n}): NativeEngineLoadResult | null {\n const useCache = !options?.candidates;\n if (useCache && cachedEngine !== undefined) {\n if (options?.collectErrors && !cachedEngine) {\n options.collectErrors.push(...engineLoadErrors);\n }\n return cachedEngine;\n }\n\n const candidates = options?.candidates ?? NATIVE_ENGINE_PACKAGES;\n const errors: string[] = [];\n let result: NativeEngineLoadResult | null = null;\n\n const frameworkRequire = findFrameworkAnchor();\n const userRequire = createUserRequire();\n\n for (const candidate of candidates) {\n // Moro's own engine lives in moro's dependencies (framework anchor first);\n // the legacy uWS peer dep lives in the user's node_modules (cwd first).\n const anchors =\n candidate === 'uWebSockets.js'\n ? [userRequire, frameworkRequire]\n : [frameworkRequire, userRequire];\n\n for (const anchor of anchors) {\n if (!anchor) continue;\n try {\n const module = anchor(candidate);\n // Capability check: the HTTP server needs either a uWS-style App()\n // factory or the Moro-shaped engine surface (serve()/respond()).\n // A package that loads but can't serve (e.g. a pre-M1 @morojs/engine\n // that only exposes probe()) must not shadow a usable alternative.\n const surface = module?.default || module;\n const uwsStyle = typeof surface?.App === 'function';\n const moroStyle =\n !uwsStyle &&\n typeof surface?.serve === 'function' &&\n typeof surface?.respond === 'function';\n if (!uwsStyle && !moroStyle) {\n errors.push(\n `${candidate}: loaded but provides neither App() nor serve()/respond() - not a usable HTTP engine`\n );\n continue;\n }\n let version: string | undefined;\n try {\n // May be blocked by the package's exports map - version is optional\n version = anchor(`${candidate}/package.json`)?.version;\n } catch {\n version = undefined;\n }\n result = {\n module,\n source: candidate,\n ...(version !== undefined ? { version } : {}),\n capabilities: moroStyle ? getEngineCapabilities(module) : NO_CAPABILITIES,\n };\n break;\n } catch (error) {\n const message = error instanceof Error ? error.message.split('\\n')[0] : String(error);\n errors.push(`${candidate}: ${message}`);\n }\n }\n if (result) break;\n }\n\n // Candidate-override probes (e.g. the clustering worker's uWS-only check)\n // must not clobber the diagnostics of the real default load, which\n // framework startup logging reads via getNativeEngineLoadErrors().\n if (useCache) {\n engineLoadErrors = result ? [] : [...new Set(errors)];\n cachedEngine = result;\n }\n // Hand this specific call's failure detail back to a caller that asked for it,\n // regardless of caching (so 'moro'/'uws' fallback reasons are precise).\n if (options?.collectErrors && !result) {\n options.collectErrors.push(...new Set(errors));\n }\n return result;\n}\n\n/**\n * Failure details from the most recent unsuccessful loadNativeEngine() call,\n * one entry per package/anchor attempt. Empty after a successful load.\n */\nexport function getNativeEngineLoadErrors(): readonly string[] {\n return engineLoadErrors;\n}\n\n/**\n * Clear the memoized engine load result. Testing only.\n */\nexport function resetNativeEngineLoaderForTesting(): void {\n cachedEngine = undefined;\n engineLoadErrors = [];\n}\n\n/**\n * Convert a file path to a file URL for ESM dynamic imports\n * This is required on Windows where absolute paths like C:\\path\\to\\module\n * need to be converted to file:/// URLs for dynamic import() to work\n *\n * @param filePath - The file path to convert (absolute or relative)\n * @returns A proper URL string for ESM import() that works cross-platform\n *\n * @example\n * const modulePath = 'C:\\\\Users\\\\project\\\\module.js';\n * const module = await import(filePathToImportURL(modulePath));\n */\nexport function filePathToImportURL(filePath: string): string {\n if (!filePath) {\n return filePath;\n }\n\n // If it's already a URL (starts with file:// or http(s)://), return as-is\n if (\n filePath.startsWith('file://') ||\n filePath.startsWith('http://') ||\n filePath.startsWith('https://')\n ) {\n return filePath;\n }\n\n // Check if it's a Windows absolute path (C:\\ or C:/)\n const windowsAbsolutePathRegex = /^[A-Za-z]:[\\\\//]/;\n const isWindowsAbsolute = windowsAbsolutePathRegex.test(filePath);\n\n // Check if it's a Unix absolute path (starts with /)\n const isUnixAbsolute = filePath.startsWith('/');\n\n // Check if it's a Windows UNC path (\\\\server\\share)\n const isWindowsUNC = filePath.startsWith('\\\\\\\\');\n\n // Convert absolute paths to file URLs\n if (isWindowsAbsolute || isUnixAbsolute || isWindowsUNC) {\n return pathToFileURL(filePath).href;\n }\n\n // For relative paths, return as-is (they work in import())\n return filePath;\n}\n"]}
1
+ {"version":3,"file":"package-utils.js","sourceRoot":"","sources":["../../../src/core/utilities/package-utils.ts"],"names":[],"mappings":"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","sourcesContent":["// Package and Module Resolution Utilities\n// ESM-compatible helpers for detecting and resolving optional dependencies\n\nimport { createRequire } from 'module';\nimport { dirname, join } from 'path';\nimport { existsSync, realpathSync } from 'fs';\nimport { pathToFileURL } from 'url';\nimport { selfRequire } from './self-require.js';\n\n/**\n * Check if a package is available in the user's node_modules\n * Uses ESM-compatible module resolution from the user's working directory\n *\n * @param packageName - The name of the package to check (e.g., 'socket.io', 'zod')\n * @returns true if the package is installed, false otherwise\n *\n * @example\n * if (isPackageAvailable('socket.io')) {\n * // socket.io is installed, safe to import\n * const { Server } = await import('socket.io');\n * }\n */\nexport function isPackageAvailable(packageName: string): boolean {\n try {\n const userRequire = createRequire(join(process.cwd(), 'package.json'));\n userRequire.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve a package path from the user's node_modules\n * Uses ESM-compatible module resolution from the user's working directory\n *\n * @param packageName - The name of the package to resolve\n * @returns The resolved package path\n * @throws Error if the package is not found\n *\n * @example\n * const socketIOPath = resolveUserPackage('socket.io');\n * const { Server } = await import(socketIOPath);\n */\nexport function resolveUserPackage(packageName: string): string {\n const userRequire = createRequire(join(process.cwd(), 'package.json'));\n return userRequire.resolve(packageName);\n}\n\n/**\n * Create a require function for the user's working directory\n * Useful for resolving packages installed by the user, not in Moro's dependencies\n *\n * @returns A require function scoped to the user's working directory\n *\n * @example\n * const userRequire = createUserRequire();\n * const packagePath = userRequire.resolve('some-package');\n */\nexport function createUserRequire() {\n return createRequire(join(process.cwd(), 'package.json'));\n}\n\n// ---------------------------------------------------------------------------\n// Native HTTP engine loading\n// ---------------------------------------------------------------------------\n\n/**\n * Package names probed for the native HTTP engine, in priority order.\n * '@morojs/engine' is Moro's own engine (installed as an optionalDependency of\n * the framework); 'uWebSockets.js' is honored as a legacy user-installed peer.\n */\nexport const NATIVE_ENGINE_PACKAGES = ['@morojs/engine', 'uWebSockets.js'] as const;\n\nconst FRAMEWORK_PACKAGE_NAME = '@morojs/moro';\n\n/** Feature flags the framework gates option-passing on, so it never hands a\n * serve() option to an engine build that predates it. Everything defaults\n * false, so an older engine behaves exactly as it did before these existed. */\nexport interface EngineCapabilities {\n /** The full serve() limit surface is parsed (engine >= 1.1.0). */\n limits: boolean;\n /** In-process TLS termination via options.ssl (engine >= 1.1.x). */\n tls: boolean;\n /** ALPN HTTP/2 via options.http2 (reserved). */\n http2: boolean;\n /** WebSocket permessage-deflate via options.wsDeflate. */\n wsDeflate: boolean;\n /** responseTimeoutMs / responseBackpressureLimit / maxUriSize are parsed. */\n responseLimits: boolean;\n /** ssl.ciphers / ssl.ciphersuites / ssl.ecdhCurve are parsed. */\n tlsPolicy: boolean;\n /** updateSsl(serverId, ssl) exists: certificate rotation without a restart\n * (engine >= 1.1.8). Gates app.reloadTLS() on the engine. */\n tlsReload: boolean;\n /** setStaticRoute()/clearStaticRoutes() exist. Used for a literal body\n * given to the route builder's .handler() when the route has nothing\n * else configured, because a static route bypasses middleware, hooks,\n * auth and validation by design. */\n staticRoutes: boolean;\n /** prepareResponse()/respondPrepared()/respondPreparedEmpty()/endWith()\n * exist (engine >= 1.1.6): the adapter replays prepared header blocks for\n * the responses it emits itself. */\n responseTemplates: boolean;\n /** onAborted/onWritable are delivered on a later loop turn, never from\n * inside respond()/write()/end() (engine >= 1.1.6). */\n asyncNotify: boolean;\n /** A server left open at environment teardown is closed by the engine's\n * cleanup hook, so the engine may run inside worker threads that can be\n * terminate()d (engine >= 1.1.6). Gates thread-based clustering. */\n workerThreads: boolean;\n /** V8 fast API calls are installed on the hot entry points (informational). */\n fastCalls: boolean;\n /** Batched pipelined dispatch (onRequestBatch / getBatchBuffers / getPath). */\n batchDispatch: boolean;\n /** Every callback into JS runs inside a Node callback scope, so nextTicks\n * and microtasks queued during dispatch run when the callback returns;\n * the adapter then needs no drain of its own (engine > 1.1.6). */\n callbackScope: boolean;\n /** setParamRoute()/clearParamRoutes() exist (engine >= 1.1.9): a route\n * with one variable path segment, echoed as the body, answered inside the\n * engine. Used for param(name) handlers on bare routes. */\n paramRoutes: boolean;\n /** setWsAutoUpgrade()/wsGetHeaders()/wsGetQuery()/wsGetRemoteAddress()\n * exist (engine >= 1.1.15): the engine answers a valid WebSocket\n * handshake itself and fires onWsOpen, so an upgrade never round-trips\n * through onRequest; the handshake stays readable on the socket. */\n wsAutoUpgrade: boolean;\n /** I/O transport in use ('epoll', 'uv', 'uring'), when the engine reports it. */\n transport?: string;\n}\n\n// One place to name every boolean flag: getEngineCapabilities coerces each\n// with `=== true`, and NO_CAPABILITIES is derived from the same list, so a\n// flag added here can never be read on one path and forgotten on the other.\nconst CAP_KEYS = [\n 'limits',\n 'tls',\n 'http2',\n 'wsDeflate',\n 'responseLimits',\n 'tlsPolicy',\n 'tlsReload',\n 'staticRoutes',\n 'responseTemplates',\n 'asyncNotify',\n 'workerThreads',\n 'fastCalls',\n 'callbackScope',\n 'batchDispatch',\n 'paramRoutes',\n 'wsAutoUpgrade',\n] as const;\n\nexport interface NativeEngineLoadResult {\n /** The loaded engine module (CJS namespace: App, SSLApp, ...) */\n module: any;\n /** Which package satisfied the load */\n source: string;\n /** The engine package's version, when its package.json is resolvable */\n version?: string;\n /** Feature flags (Moro-shaped engine only; all-false for uWS or an\n * engine too old to advertise them). */\n capabilities?: EngineCapabilities;\n}\n\nconst NO_CAPABILITIES: EngineCapabilities = Object.freeze(\n Object.fromEntries(CAP_KEYS.map(k => [k, false]))\n) as unknown as EngineCapabilities;\n\nconst capabilitiesCache = new WeakMap<object, EngineCapabilities>();\n\n/**\n * Feature-detect a loaded engine module. Reads probe().capabilities when the\n * engine exposes it (>= 1.1.0); otherwise everything is false so the framework\n * passes only the options an older engine understood. uWS-style modules (no\n * probe/serve) report all-false — their SSL/limits go through the uWS-specific\n * paths, not these flags.\n */\nexport function getEngineCapabilities(module: any): EngineCapabilities {\n const surface = module?.default || module;\n if (!surface || typeof surface !== 'object') return NO_CAPABILITIES;\n const cached = capabilitiesCache.get(surface);\n if (cached) return cached;\n\n let caps: EngineCapabilities = NO_CAPABILITIES;\n try {\n if (typeof surface.probe === 'function') {\n const probe = surface.probe();\n const c = probe?.capabilities;\n if (c && typeof c === 'object') {\n const next: Record<string, boolean | string> = {};\n for (const key of CAP_KEYS) next[key] = c[key] === true;\n if (typeof probe.transport === 'string') next.transport = probe.transport;\n caps = next as unknown as EngineCapabilities;\n }\n }\n } catch {\n caps = NO_CAPABILITIES;\n }\n capabilitiesCache.set(surface, caps);\n return caps;\n}\n\n// undefined = never attempted, null = attempted and failed\nlet cachedEngine: NativeEngineLoadResult | null | undefined;\nlet engineLoadErrors: string[] = [];\n\n/**\n * Locate the installed framework package directory so the engine (a dependency\n * of moro, not of the user's app) resolves under strict layouts like pnpm's.\n * Walks up from cwd looking for node_modules/@morojs/moro, resolving symlinks\n * so pnpm's store-linked layout anchors requires next to moro's own deps.\n * Inside the framework repo itself (tests, examples) cwd is the framework dir.\n */\nfunction findFrameworkAnchor(): ReturnType<typeof createRequire> | null {\n // Primary: anchor to moro's OWN compiled location, so moro's `@morojs/engine`\n // dependency resolves relative to where moro is installed regardless of\n // `process.cwd()` (Docker WORKDIR, supervisord, pm2, a subdirectory, …). The\n // cwd-based walk below is a defensive fallback for exotic layouts.\n if (selfRequire) return selfRequire;\n\n try {\n const cwdPackageJson = join(process.cwd(), 'package.json');\n if (existsSync(cwdPackageJson)) {\n const require = createRequire(cwdPackageJson);\n const name = require(cwdPackageJson)?.name;\n if (name === FRAMEWORK_PACKAGE_NAME) {\n return require;\n }\n }\n } catch {\n // fall through to the node_modules walk\n }\n\n let dir = process.cwd();\n for (;;) {\n const packageJson = join(dir, 'node_modules', '@morojs', 'moro', 'package.json');\n try {\n if (existsSync(packageJson)) {\n return createRequire(join(realpathSync(dirname(packageJson)), 'package.json'));\n }\n } catch {\n // unreadable/broken link - keep walking up\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Synchronously load the native HTTP engine, trying '@morojs/engine' first and\n * falling back to a user-installed 'uWebSockets.js'. Loading is require-based\n * (both packages are CJS at their core - .node addon loading is require-based),\n * so the instance is shared with any later import() via the CJS module cache.\n *\n * The default call is memoized: the engine either loads once per process or it\n * doesn't. Failure details are retained for logging via\n * getNativeEngineLoadErrors().\n *\n * @param options.candidates - Override the probed package names (testing).\n * When provided, the call bypasses the cache.\n * @returns The loaded engine and its source package, or null if none loads\n */\nexport function loadNativeEngine(options?: {\n candidates?: readonly string[];\n /** When provided, every per-attempt failure message is appended here, so a\n * candidate-override caller (which bypasses the memoized diagnostics) can\n * still report exactly WHY the engine didn't load — not just \"not installed\". */\n collectErrors?: string[];\n}): NativeEngineLoadResult | null {\n const useCache = !options?.candidates;\n if (useCache && cachedEngine !== undefined) {\n if (options?.collectErrors && !cachedEngine) {\n options.collectErrors.push(...engineLoadErrors);\n }\n return cachedEngine;\n }\n\n const candidates = options?.candidates ?? NATIVE_ENGINE_PACKAGES;\n const errors: string[] = [];\n let result: NativeEngineLoadResult | null = null;\n\n const frameworkRequire = findFrameworkAnchor();\n const userRequire = createUserRequire();\n\n for (const candidate of candidates) {\n // Moro's own engine lives in moro's dependencies (framework anchor first);\n // the legacy uWS peer dep lives in the user's node_modules (cwd first).\n const anchors =\n candidate === 'uWebSockets.js'\n ? [userRequire, frameworkRequire]\n : [frameworkRequire, userRequire];\n\n for (const anchor of anchors) {\n if (!anchor) continue;\n try {\n const module = anchor(candidate);\n // Capability check: the HTTP server needs either a uWS-style App()\n // factory or the Moro-shaped engine surface (serve()/respond()).\n // A package that loads but can't serve (e.g. a pre-M1 @morojs/engine\n // that only exposes probe()) must not shadow a usable alternative.\n const surface = module?.default || module;\n const uwsStyle = typeof surface?.App === 'function';\n const moroStyle =\n !uwsStyle &&\n typeof surface?.serve === 'function' &&\n typeof surface?.respond === 'function';\n if (!uwsStyle && !moroStyle) {\n errors.push(\n `${candidate}: loaded but provides neither App() nor serve()/respond() - not a usable HTTP engine`\n );\n continue;\n }\n let version: string | undefined;\n try {\n // May be blocked by the package's exports map - version is optional\n version = anchor(`${candidate}/package.json`)?.version;\n } catch {\n version = undefined;\n }\n result = {\n module,\n source: candidate,\n ...(version !== undefined ? { version } : {}),\n capabilities: moroStyle ? getEngineCapabilities(module) : NO_CAPABILITIES,\n };\n break;\n } catch (error) {\n const message = error instanceof Error ? error.message.split('\\n')[0] : String(error);\n errors.push(`${candidate}: ${message}`);\n }\n }\n if (result) break;\n }\n\n // Candidate-override probes (e.g. the clustering worker's uWS-only check)\n // must not clobber the diagnostics of the real default load, which\n // framework startup logging reads via getNativeEngineLoadErrors().\n if (useCache) {\n engineLoadErrors = result ? [] : [...new Set(errors)];\n cachedEngine = result;\n }\n // Hand this specific call's failure detail back to a caller that asked for it,\n // regardless of caching (so 'moro'/'uws' fallback reasons are precise).\n if (options?.collectErrors && !result) {\n options.collectErrors.push(...new Set(errors));\n }\n return result;\n}\n\n/**\n * Failure details from the most recent unsuccessful loadNativeEngine() call,\n * one entry per package/anchor attempt. Empty after a successful load.\n */\nexport function getNativeEngineLoadErrors(): readonly string[] {\n return engineLoadErrors;\n}\n\n/**\n * Clear the memoized engine load result. Testing only.\n */\nexport function resetNativeEngineLoaderForTesting(): void {\n cachedEngine = undefined;\n engineLoadErrors = [];\n}\n\n/**\n * Convert a file path to a file URL for ESM dynamic imports\n * This is required on Windows where absolute paths like C:\\path\\to\\module\n * need to be converted to file:/// URLs for dynamic import() to work\n *\n * @param filePath - The file path to convert (absolute or relative)\n * @returns A proper URL string for ESM import() that works cross-platform\n *\n * @example\n * const modulePath = 'C:\\\\Users\\\\project\\\\module.js';\n * const module = await import(filePathToImportURL(modulePath));\n */\nexport function filePathToImportURL(filePath: string): string {\n if (!filePath) {\n return filePath;\n }\n\n // If it's already a URL (starts with file:// or http(s)://), return as-is\n if (\n filePath.startsWith('file://') ||\n filePath.startsWith('http://') ||\n filePath.startsWith('https://')\n ) {\n return filePath;\n }\n\n // Check if it's a Windows absolute path (C:\\ or C:/)\n const windowsAbsolutePathRegex = /^[A-Za-z]:[\\\\//]/;\n const isWindowsAbsolute = windowsAbsolutePathRegex.test(filePath);\n\n // Check if it's a Unix absolute path (starts with /)\n const isUnixAbsolute = filePath.startsWith('/');\n\n // Check if it's a Windows UNC path (\\\\server\\share)\n const isWindowsUNC = filePath.startsWith('\\\\\\\\');\n\n // Convert absolute paths to file URLs\n if (isWindowsAbsolute || isUnixAbsolute || isWindowsUNC) {\n return pathToFileURL(filePath).href;\n }\n\n // For relative paths, return as-is (they work in import())\n return filePath;\n}\n"]}
@@ -387,6 +387,12 @@ export interface PerformanceConfig {
387
387
  * Default 16384.
388
388
  */
389
389
  maxInlineBytes?: number;
390
+ /** zlib options for gzip/deflate; a `level` here wins over the shared level. */
391
+ gzip?: import('zlib').ZlibOptions;
392
+ /** brotli options; a quality under `params` wins over the one the shared level maps to. */
393
+ brotli?: import('zlib').BrotliOptions;
394
+ /** Keep one gzip encoder per setting and reset it between bodies (default true). */
395
+ reuseEncoders?: boolean;
390
396
  };
391
397
  circuitBreaker: {
392
398
  enabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,0DAA0D","sourcesContent":["// TypeScript-based Configuration Types for Moro Framework\n\n/**\n * CORS origin function type for dynamic origin validation\n */\nexport type OriginFunction = (\n origin: string | undefined,\n req: any\n) => string | string[] | boolean | Promise<string | string[] | boolean>;\n\n/**\n * Deep partial type that makes all properties and nested properties optional\n * Used for user-provided configuration where only partial overrides are needed\n */\nexport type DeepPartial<T> = T extends object\n ? T extends Array<infer U>\n ? Array<DeepPartial<U>>\n : T extends (...args: any[]) => any\n ? T\n : { [P in keyof T]?: DeepPartial<T[P]> }\n : T;\n\n/** Unified SSL/TLS config (superset of both historical shapes). */\nexport interface SSLConfig {\n /** File-path shape (uWS-style; keyFile/certFile also accepted) */\n key_file_name?: string;\n cert_file_name?: string;\n ca_file_name?: string | string[];\n keyFile?: string;\n certFile?: string;\n caFile?: string | string[];\n /** Inline-PEM shape (node-style) */\n key?: string | Buffer;\n cert?: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n passphrase?: string;\n minVersion?: 'TLSv1.2' | 'TLSv1.3';\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n /**\n * Reload the certificate when the key/cert files change on disk (file-path\n * shape only; `true` = 1000 ms debounce). Equivalent to calling\n * `app.reloadTLS()` after each change.\n */\n watch?: boolean | { debounceMs?: number };\n}\n\n/** Per-phase receive timeouts. 0 = use the per-server default / disabled. */\nexport interface ServerTimeoutsConfig {\n /** Full-request receive budget (ms); does NOT reset on activity. */\n request?: number;\n /** Socket inactivity timeout (ms). */\n idle?: number;\n /** Node keep-alive socket timeout (ms). Default 5000. */\n keepAlive?: number;\n /** Node headers-received timeout (ms). Default 6000. */\n headers?: number;\n}\n\nexport interface MultipartLimitsConfig {\n maxParts?: number;\n maxPartHeaderBytes?: number | string;\n maxFiles?: number;\n maxFileSize?: number | string;\n}\n\n/** Fine-grained limits; every value is a documented default, not a hard cap. */\nexport interface ServerLimitsConfig {\n /** Max request head (request line + headers) size. Node maxHeaderSize / engine maxHeadSize. */\n maxHeaderSize?: number | string;\n /** Max header count. Node maxHeadersCount / engine maxHeaders. */\n maxHeaders?: number;\n multipart?: MultipartLimitsConfig;\n /** Reassembled WebSocket message cap (engine wsMaxMessageSize). */\n wsMaxMessageSize?: number | string;\n /** WebSocket send backpressure cap (engine wsBackpressureLimit). 0 = unlimited. */\n wsBackpressureLimit?: number | string;\n /** Write-queue high-water mark (engine writeHighWaterMark). */\n writeHighWaterMark?: number | string;\n /** Per-connection not-yet-parsed backlog cap (engine maxPendingBytes). */\n maxPendingBytes?: number | string;\n}\n\nexport interface Http2SettingsConfig {\n maxConcurrentStreams?: number;\n initialWindowSize?: number;\n maxFrameSize?: number;\n maxHeaderListSize?: number;\n headerTableSize?: number;\n /** Accepted for compatibility but never sent: RFC 9113 forbids a server from advertising ENABLE_PUSH=1 (clients drop the connection), and whether push is possible is the client's setting, read per stream. */\n enablePush?: boolean;\n enableConnectProtocol?: boolean;\n maxHeaderSize?: number;\n}\n\nexport interface ServerConfig {\n port: number;\n host: string;\n maxConnections: number;\n /** @deprecated Use timeouts.request. Kept as an alias; still honored. */\n timeout: number;\n bodySizeLimit: string;\n maxUploadSize: string; // Maximum size for file uploads (multipart/form-data)\n /** Per-phase receive timeouts (finally applied to every runtime). */\n timeouts?: ServerTimeoutsConfig;\n /** Fine-grained size/count limits, passed through to whichever runtime serves. */\n limits?: ServerLimitsConfig;\n /** TCP listen backlog. */\n backlog?: number;\n /** HTTP/2: true, or an options object. Served natively by the engine when\n * it supports h2 (feature-detected), else by the Node http2 server. */\n http2?:\n boolean | { allowHTTP1?: boolean; maxSessionMemory?: number; settings?: Http2SettingsConfig };\n requestTracking: {\n enabled: boolean;\n };\n requestLogging: {\n enabled: boolean;\n };\n errorBoundary: {\n enabled: boolean;\n };\n /**\n * HTTP engine selection. Default: 'moro'.\n * - 'moro' (default): Moro's own native engine (@morojs/engine), falling back\n * to the Node.js http server if it cannot load on this platform/Node ABI\n * - 'node': the Node.js http server (no native engine)\n * - 'uws': opt in to uWebSockets.js, falling back to Node.js if it cannot load\n * Any chosen engine that cannot load degrades to Node.js and logs why; the app\n * never fails to boot. Inspect what actually booted via app.engine.\n */\n engine?: 'moro' | 'node' | 'uws';\n /** @deprecated Use engine: 'uws' instead. Enable uWebSockets for both HTTP and WebSocket */\n useUWebSockets?: boolean;\n /** Unified SSL/TLS config; flows to whichever runtime serves. */\n ssl?: SSLConfig;\n}\n\nexport interface ServiceDiscoveryConfig {\n enabled: boolean;\n type: 'memory' | 'consul' | 'kubernetes';\n consulUrl: string;\n kubernetesNamespace: string;\n healthCheckInterval: number;\n retryAttempts: number;\n}\n\nexport interface DatabaseConfig {\n url?: string;\n redis?: {\n url: string;\n maxRetries: number;\n retryDelay: number;\n keyPrefix: string;\n };\n mysql?: {\n host: string;\n port: number;\n database?: string;\n username?: string;\n password?: string;\n connectionLimit: number;\n acquireTimeout: number;\n timeout: number;\n };\n postgresql?: {\n host: string;\n port: number;\n database?: string;\n user?: string;\n password?: string;\n connectionLimit: number;\n ssl?: boolean;\n };\n sqlite?: {\n filename: string;\n memory?: boolean;\n verbose?: boolean;\n };\n mongodb?: {\n url?: string;\n host?: string;\n port?: number;\n database?: string;\n username?: string;\n password?: string;\n };\n}\n\n// Validation error context passed to error handlers\nexport interface ValidationErrorContext {\n request: {\n method: string;\n path: string;\n url: string;\n headers: Record<string, any>;\n };\n route?: {\n path: string;\n method: string;\n };\n field: 'body' | 'query' | 'params' | 'headers';\n}\n\n// Validation error detail structure\nexport interface ValidationErrorDetail {\n field: string;\n message: string;\n code?: string;\n value?: any;\n path?: (string | number)[];\n}\n\n// Validation error response structure\nexport interface ValidationErrorResponse {\n status: number;\n body: any;\n headers?: Record<string, string>;\n}\n\n// Validation error handler function type\nexport type ValidationErrorHandler = (\n errors: ValidationErrorDetail[],\n context: ValidationErrorContext\n) => ValidationErrorResponse;\n\nexport interface ModuleDefaultsConfig {\n apiPrefix?: string; // Prefix for module routes, defaults to '/api/' - set to empty string '' to disable\n cache: {\n enabled: boolean;\n defaultTtl: number;\n maxSize: number;\n strategy: 'lru' | 'lfu' | 'fifo';\n };\n rateLimit: {\n enabled: boolean;\n defaultRequests: number;\n defaultWindow: number;\n skipSuccessfulRequests: boolean;\n skipFailedRequests: boolean;\n };\n validation: {\n enabled: boolean;\n stripUnknown: boolean;\n abortEarly: boolean;\n allowUnknown?: boolean;\n onError?: ValidationErrorHandler;\n };\n session?: {\n enabled: boolean;\n store: 'memory' | 'redis' | 'file';\n storeOptions?: {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n keyPrefix?: string;\n path?: string;\n max?: number;\n };\n secret?: string;\n name?: string;\n rolling?: boolean;\n resave?: boolean;\n saveUninitialized?: boolean;\n cookie?: {\n maxAge?: number;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n domain?: string;\n path?: string;\n };\n proxy?: boolean;\n unset?: 'destroy' | 'keep';\n };\n autoDiscovery: {\n enabled: boolean;\n paths: string[];\n patterns: string[];\n recursive: boolean;\n loadingStrategy: 'eager' | 'lazy' | 'conditional';\n watchForChanges: boolean;\n ignorePatterns: string[];\n loadOrder: 'alphabetical' | 'dependency' | 'custom';\n failOnError: boolean;\n maxDepth: number;\n };\n}\n\nexport interface LoggingConfig {\n level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n format: 'pretty' | 'json' | 'compact';\n enableColors: boolean;\n enableTimestamp: boolean;\n enableContext: boolean;\n // Opt-in: appends a JSON metadata object (e.g. {\"framework\":\"moro\",...}) to pretty log lines.\n // Has no effect on 'json' format - structured output always includes metadata.\n enableMetadata: boolean;\n // Opt-in: collects and renders per-log performance data such as memory usage (e.g. \"(210MB)\").\n // Disabling skips the process.memoryUsage() call on every log line.\n enablePerformance: boolean;\n outputs: {\n console: boolean;\n file: {\n enabled: boolean;\n path: string;\n maxSize: string;\n maxFiles: number;\n };\n webhook: {\n enabled: boolean;\n url?: string;\n headers: Record<string, string>;\n };\n };\n}\n\nexport interface SecurityConfig {\n cors: {\n enabled: boolean;\n origin: string | string[] | boolean | OriginFunction;\n methods: string[];\n allowedHeaders: string[];\n exposedHeaders?: string[];\n credentials: boolean;\n maxAge?: number;\n preflightContinue?: boolean;\n };\n helmet: {\n enabled: boolean;\n // Simplified options (backward compatible)\n contentSecurityPolicy?: boolean;\n hsts?: boolean;\n noSniff?: boolean;\n frameguard?: boolean;\n // Detailed options (for advanced configuration)\n xFrameOptions?: 'DENY' | 'SAMEORIGIN';\n xContentTypeOptions?: boolean;\n xXssProtection?: boolean;\n referrerPolicy?: string;\n strictTransportSecurity?: { maxAge?: number; includeSubDomains?: boolean };\n xDownloadOptions?: boolean;\n xPermittedCrossDomainPolicies?: boolean;\n };\n csrf?: {\n enabled: boolean;\n secret?: string;\n tokenLength?: number;\n cookieName?: string;\n headerName?: string;\n ignoreMethods?: string[];\n sameSite?: boolean;\n };\n csp?: {\n enabled: boolean;\n directives?: {\n defaultSrc?: string[];\n scriptSrc?: string[];\n styleSrc?: string[];\n imgSrc?: string[];\n connectSrc?: string[];\n fontSrc?: string[];\n objectSrc?: string[];\n mediaSrc?: string[];\n frameSrc?: string[];\n childSrc?: string[];\n workerSrc?: string[];\n formAction?: string[];\n upgradeInsecureRequests?: boolean;\n blockAllMixedContent?: boolean;\n };\n reportOnly?: boolean;\n reportUri?: string;\n nonce?: boolean;\n };\n rateLimit: {\n global: {\n enabled: boolean;\n requests: number;\n window: number;\n };\n };\n}\n\nexport interface ExternalServicesConfig {\n stripe?: {\n secretKey?: string | undefined;\n publishableKey?: string | undefined;\n webhookSecret?: string | undefined;\n apiVersion: string;\n };\n paypal?: {\n clientId?: string | undefined;\n clientSecret?: string | undefined;\n webhookId?: string | undefined;\n environment: 'sandbox' | 'production';\n };\n smtp?: {\n host?: string | undefined;\n port: number;\n secure: boolean;\n username?: string | undefined;\n password?: string | undefined;\n };\n}\n\nexport interface PerformanceConfig {\n compression: {\n enabled: boolean;\n level: number;\n threshold: number;\n /** Encoding preference order (default ['br','gzip','deflate']). */\n encodings?: Array<'br' | 'gzip' | 'deflate'>;\n /**\n * Bodies at or below this size are compressed on the calling thread and\n * sent with a Content-Length; larger ones are compressed on the\n * threadpool and streamed. 0 sends every body through the pool.\n * Default 16384.\n */\n maxInlineBytes?: number;\n };\n circuitBreaker: {\n enabled: boolean;\n failureThreshold: number;\n resetTimeout: number;\n monitoringPeriod: number;\n };\n clustering: {\n enabled: boolean;\n workers: number | 'auto';\n memoryPerWorkerGB?: number | undefined;\n };\n}\n\nexport interface WebSocketConfig {\n enabled: boolean;\n adapter?: string | 'socket.io' | 'ws' | 'uws';\n compression?: boolean;\n customIdGenerator?: () => string;\n options?: {\n cors?: {\n origin?: string | string[];\n credentials?: boolean;\n };\n path?: string;\n maxPayloadLength?: number;\n idleTimeout?: number;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n };\n}\n\nexport interface JobsConfig {\n enabled?: boolean;\n maxConcurrentJobs?: number;\n gracefulShutdownTimeout?: number;\n leaderElection?: {\n enabled?: boolean;\n strategy?: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n };\n executor?: {\n maxRetries?: number;\n retryDelay?: number;\n retryBackoff?: 'linear' | 'exponential';\n retryBackoffMultiplier?: number;\n maxRetryDelay?: number;\n timeout?: number;\n enableCircuitBreaker?: boolean;\n circuitBreakerThreshold?: number;\n circuitBreakerResetTimeout?: number;\n enableMemoryMonitoring?: boolean;\n memoryThreshold?: number;\n };\n stateManager?: {\n persistPath?: string;\n historySize?: number;\n persistInterval?: number;\n enableAutoPersist?: boolean;\n enableRecovery?: boolean;\n };\n}\n\n// File-based routing configuration. When enabled, route files under the\n// configured directories are imported during startup (after module discovery)\n// so their `getApp().get(...)` registrations execute automatically.\nexport interface RoutingConfig {\n enabled: boolean;\n // Directories scanned via loadRoutes(). Defaults to ./src/routes in\n // development and ./dist/(src/)routes in production when omitted.\n paths?: string[];\n}\n\n// Main configuration interface\nexport interface AppConfig {\n server: ServerConfig;\n serviceDiscovery: ServiceDiscoveryConfig;\n database: DatabaseConfig;\n modules: ModuleDefaultsConfig;\n // Enable file-based auto routing (./src/routes). Defaults to true.\n routing: boolean | RoutingConfig;\n logging: LoggingConfig;\n security: SecurityConfig;\n external: ExternalServicesConfig;\n performance: PerformanceConfig;\n websocket: WebSocketConfig;\n jobs?: JobsConfig;\n queue?: QueueConfig;\n}\n\n// Queue Configuration\nexport interface QueueConfig {\n adapter?: 'bull' | 'rabbitmq' | 'sqs' | 'kafka' | 'memory';\n connection?: {\n host?: string;\n port?: number;\n username?: string;\n password?: string;\n database?: number;\n brokers?: string[];\n groupId?: string;\n region?: string;\n queueUrl?: string;\n [key: string]: any;\n };\n concurrency?: number;\n retry?: {\n maxAttempts: number;\n backoff: 'fixed' | 'exponential' | 'linear';\n initialDelay: number;\n maxDelay?: number;\n };\n deadLetterQueue?: {\n enabled: boolean;\n maxRetries: number;\n queueName?: string;\n };\n defaultJobOptions?: {\n removeOnComplete?: boolean | number;\n removeOnFail?: boolean | number;\n attempts?: number;\n backoff?: {\n type: 'fixed' | 'exponential' | 'linear';\n delay: number;\n };\n };\n prefix?: string;\n limiter?: {\n max: number;\n duration: number;\n };\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,0DAA0D","sourcesContent":["// TypeScript-based Configuration Types for Moro Framework\n\n/**\n * CORS origin function type for dynamic origin validation\n */\nexport type OriginFunction = (\n origin: string | undefined,\n req: any\n) => string | string[] | boolean | Promise<string | string[] | boolean>;\n\n/**\n * Deep partial type that makes all properties and nested properties optional\n * Used for user-provided configuration where only partial overrides are needed\n */\nexport type DeepPartial<T> = T extends object\n ? T extends Array<infer U>\n ? Array<DeepPartial<U>>\n : T extends (...args: any[]) => any\n ? T\n : { [P in keyof T]?: DeepPartial<T[P]> }\n : T;\n\n/** Unified SSL/TLS config (superset of both historical shapes). */\nexport interface SSLConfig {\n /** File-path shape (uWS-style; keyFile/certFile also accepted) */\n key_file_name?: string;\n cert_file_name?: string;\n ca_file_name?: string | string[];\n keyFile?: string;\n certFile?: string;\n caFile?: string | string[];\n /** Inline-PEM shape (node-style) */\n key?: string | Buffer;\n cert?: string | Buffer;\n ca?: string | Buffer | Array<string | Buffer>;\n passphrase?: string;\n minVersion?: 'TLSv1.2' | 'TLSv1.3';\n requestCert?: boolean;\n rejectUnauthorized?: boolean;\n /**\n * Reload the certificate when the key/cert files change on disk (file-path\n * shape only; `true` = 1000 ms debounce). Equivalent to calling\n * `app.reloadTLS()` after each change.\n */\n watch?: boolean | { debounceMs?: number };\n}\n\n/** Per-phase receive timeouts. 0 = use the per-server default / disabled. */\nexport interface ServerTimeoutsConfig {\n /** Full-request receive budget (ms); does NOT reset on activity. */\n request?: number;\n /** Socket inactivity timeout (ms). */\n idle?: number;\n /** Node keep-alive socket timeout (ms). Default 5000. */\n keepAlive?: number;\n /** Node headers-received timeout (ms). Default 6000. */\n headers?: number;\n}\n\nexport interface MultipartLimitsConfig {\n maxParts?: number;\n maxPartHeaderBytes?: number | string;\n maxFiles?: number;\n maxFileSize?: number | string;\n}\n\n/** Fine-grained limits; every value is a documented default, not a hard cap. */\nexport interface ServerLimitsConfig {\n /** Max request head (request line + headers) size. Node maxHeaderSize / engine maxHeadSize. */\n maxHeaderSize?: number | string;\n /** Max header count. Node maxHeadersCount / engine maxHeaders. */\n maxHeaders?: number;\n multipart?: MultipartLimitsConfig;\n /** Reassembled WebSocket message cap (engine wsMaxMessageSize). */\n wsMaxMessageSize?: number | string;\n /** WebSocket send backpressure cap (engine wsBackpressureLimit). 0 = unlimited. */\n wsBackpressureLimit?: number | string;\n /** Write-queue high-water mark (engine writeHighWaterMark). */\n writeHighWaterMark?: number | string;\n /** Per-connection not-yet-parsed backlog cap (engine maxPendingBytes). */\n maxPendingBytes?: number | string;\n}\n\nexport interface Http2SettingsConfig {\n maxConcurrentStreams?: number;\n initialWindowSize?: number;\n maxFrameSize?: number;\n maxHeaderListSize?: number;\n headerTableSize?: number;\n /** Accepted for compatibility but never sent: RFC 9113 forbids a server from advertising ENABLE_PUSH=1 (clients drop the connection), and whether push is possible is the client's setting, read per stream. */\n enablePush?: boolean;\n enableConnectProtocol?: boolean;\n maxHeaderSize?: number;\n}\n\nexport interface ServerConfig {\n port: number;\n host: string;\n maxConnections: number;\n /** @deprecated Use timeouts.request. Kept as an alias; still honored. */\n timeout: number;\n bodySizeLimit: string;\n maxUploadSize: string; // Maximum size for file uploads (multipart/form-data)\n /** Per-phase receive timeouts (finally applied to every runtime). */\n timeouts?: ServerTimeoutsConfig;\n /** Fine-grained size/count limits, passed through to whichever runtime serves. */\n limits?: ServerLimitsConfig;\n /** TCP listen backlog. */\n backlog?: number;\n /** HTTP/2: true, or an options object. Served natively by the engine when\n * it supports h2 (feature-detected), else by the Node http2 server. */\n http2?:\n boolean | { allowHTTP1?: boolean; maxSessionMemory?: number; settings?: Http2SettingsConfig };\n requestTracking: {\n enabled: boolean;\n };\n requestLogging: {\n enabled: boolean;\n };\n errorBoundary: {\n enabled: boolean;\n };\n /**\n * HTTP engine selection. Default: 'moro'.\n * - 'moro' (default): Moro's own native engine (@morojs/engine), falling back\n * to the Node.js http server if it cannot load on this platform/Node ABI\n * - 'node': the Node.js http server (no native engine)\n * - 'uws': opt in to uWebSockets.js, falling back to Node.js if it cannot load\n * Any chosen engine that cannot load degrades to Node.js and logs why; the app\n * never fails to boot. Inspect what actually booted via app.engine.\n */\n engine?: 'moro' | 'node' | 'uws';\n /** @deprecated Use engine: 'uws' instead. Enable uWebSockets for both HTTP and WebSocket */\n useUWebSockets?: boolean;\n /** Unified SSL/TLS config; flows to whichever runtime serves. */\n ssl?: SSLConfig;\n}\n\nexport interface ServiceDiscoveryConfig {\n enabled: boolean;\n type: 'memory' | 'consul' | 'kubernetes';\n consulUrl: string;\n kubernetesNamespace: string;\n healthCheckInterval: number;\n retryAttempts: number;\n}\n\nexport interface DatabaseConfig {\n url?: string;\n redis?: {\n url: string;\n maxRetries: number;\n retryDelay: number;\n keyPrefix: string;\n };\n mysql?: {\n host: string;\n port: number;\n database?: string;\n username?: string;\n password?: string;\n connectionLimit: number;\n acquireTimeout: number;\n timeout: number;\n };\n postgresql?: {\n host: string;\n port: number;\n database?: string;\n user?: string;\n password?: string;\n connectionLimit: number;\n ssl?: boolean;\n };\n sqlite?: {\n filename: string;\n memory?: boolean;\n verbose?: boolean;\n };\n mongodb?: {\n url?: string;\n host?: string;\n port?: number;\n database?: string;\n username?: string;\n password?: string;\n };\n}\n\n// Validation error context passed to error handlers\nexport interface ValidationErrorContext {\n request: {\n method: string;\n path: string;\n url: string;\n headers: Record<string, any>;\n };\n route?: {\n path: string;\n method: string;\n };\n field: 'body' | 'query' | 'params' | 'headers';\n}\n\n// Validation error detail structure\nexport interface ValidationErrorDetail {\n field: string;\n message: string;\n code?: string;\n value?: any;\n path?: (string | number)[];\n}\n\n// Validation error response structure\nexport interface ValidationErrorResponse {\n status: number;\n body: any;\n headers?: Record<string, string>;\n}\n\n// Validation error handler function type\nexport type ValidationErrorHandler = (\n errors: ValidationErrorDetail[],\n context: ValidationErrorContext\n) => ValidationErrorResponse;\n\nexport interface ModuleDefaultsConfig {\n apiPrefix?: string; // Prefix for module routes, defaults to '/api/' - set to empty string '' to disable\n cache: {\n enabled: boolean;\n defaultTtl: number;\n maxSize: number;\n strategy: 'lru' | 'lfu' | 'fifo';\n };\n rateLimit: {\n enabled: boolean;\n defaultRequests: number;\n defaultWindow: number;\n skipSuccessfulRequests: boolean;\n skipFailedRequests: boolean;\n };\n validation: {\n enabled: boolean;\n stripUnknown: boolean;\n abortEarly: boolean;\n allowUnknown?: boolean;\n onError?: ValidationErrorHandler;\n };\n session?: {\n enabled: boolean;\n store: 'memory' | 'redis' | 'file';\n storeOptions?: {\n url?: string;\n host?: string;\n port?: number;\n password?: string;\n keyPrefix?: string;\n path?: string;\n max?: number;\n };\n secret?: string;\n name?: string;\n rolling?: boolean;\n resave?: boolean;\n saveUninitialized?: boolean;\n cookie?: {\n maxAge?: number;\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n domain?: string;\n path?: string;\n };\n proxy?: boolean;\n unset?: 'destroy' | 'keep';\n };\n autoDiscovery: {\n enabled: boolean;\n paths: string[];\n patterns: string[];\n recursive: boolean;\n loadingStrategy: 'eager' | 'lazy' | 'conditional';\n watchForChanges: boolean;\n ignorePatterns: string[];\n loadOrder: 'alphabetical' | 'dependency' | 'custom';\n failOnError: boolean;\n maxDepth: number;\n };\n}\n\nexport interface LoggingConfig {\n level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n format: 'pretty' | 'json' | 'compact';\n enableColors: boolean;\n enableTimestamp: boolean;\n enableContext: boolean;\n // Opt-in: appends a JSON metadata object (e.g. {\"framework\":\"moro\",...}) to pretty log lines.\n // Has no effect on 'json' format - structured output always includes metadata.\n enableMetadata: boolean;\n // Opt-in: collects and renders per-log performance data such as memory usage (e.g. \"(210MB)\").\n // Disabling skips the process.memoryUsage() call on every log line.\n enablePerformance: boolean;\n outputs: {\n console: boolean;\n file: {\n enabled: boolean;\n path: string;\n maxSize: string;\n maxFiles: number;\n };\n webhook: {\n enabled: boolean;\n url?: string;\n headers: Record<string, string>;\n };\n };\n}\n\nexport interface SecurityConfig {\n cors: {\n enabled: boolean;\n origin: string | string[] | boolean | OriginFunction;\n methods: string[];\n allowedHeaders: string[];\n exposedHeaders?: string[];\n credentials: boolean;\n maxAge?: number;\n preflightContinue?: boolean;\n };\n helmet: {\n enabled: boolean;\n // Simplified options (backward compatible)\n contentSecurityPolicy?: boolean;\n hsts?: boolean;\n noSniff?: boolean;\n frameguard?: boolean;\n // Detailed options (for advanced configuration)\n xFrameOptions?: 'DENY' | 'SAMEORIGIN';\n xContentTypeOptions?: boolean;\n xXssProtection?: boolean;\n referrerPolicy?: string;\n strictTransportSecurity?: { maxAge?: number; includeSubDomains?: boolean };\n xDownloadOptions?: boolean;\n xPermittedCrossDomainPolicies?: boolean;\n };\n csrf?: {\n enabled: boolean;\n secret?: string;\n tokenLength?: number;\n cookieName?: string;\n headerName?: string;\n ignoreMethods?: string[];\n sameSite?: boolean;\n };\n csp?: {\n enabled: boolean;\n directives?: {\n defaultSrc?: string[];\n scriptSrc?: string[];\n styleSrc?: string[];\n imgSrc?: string[];\n connectSrc?: string[];\n fontSrc?: string[];\n objectSrc?: string[];\n mediaSrc?: string[];\n frameSrc?: string[];\n childSrc?: string[];\n workerSrc?: string[];\n formAction?: string[];\n upgradeInsecureRequests?: boolean;\n blockAllMixedContent?: boolean;\n };\n reportOnly?: boolean;\n reportUri?: string;\n nonce?: boolean;\n };\n rateLimit: {\n global: {\n enabled: boolean;\n requests: number;\n window: number;\n };\n };\n}\n\nexport interface ExternalServicesConfig {\n stripe?: {\n secretKey?: string | undefined;\n publishableKey?: string | undefined;\n webhookSecret?: string | undefined;\n apiVersion: string;\n };\n paypal?: {\n clientId?: string | undefined;\n clientSecret?: string | undefined;\n webhookId?: string | undefined;\n environment: 'sandbox' | 'production';\n };\n smtp?: {\n host?: string | undefined;\n port: number;\n secure: boolean;\n username?: string | undefined;\n password?: string | undefined;\n };\n}\n\nexport interface PerformanceConfig {\n compression: {\n enabled: boolean;\n level: number;\n threshold: number;\n /** Encoding preference order (default ['br','gzip','deflate']). */\n encodings?: Array<'br' | 'gzip' | 'deflate'>;\n /**\n * Bodies at or below this size are compressed on the calling thread and\n * sent with a Content-Length; larger ones are compressed on the\n * threadpool and streamed. 0 sends every body through the pool.\n * Default 16384.\n */\n maxInlineBytes?: number;\n /** zlib options for gzip/deflate; a `level` here wins over the shared level. */\n gzip?: import('zlib').ZlibOptions;\n /** brotli options; a quality under `params` wins over the one the shared level maps to. */\n brotli?: import('zlib').BrotliOptions;\n /** Keep one gzip encoder per setting and reset it between bodies (default true). */\n reuseEncoders?: boolean;\n };\n circuitBreaker: {\n enabled: boolean;\n failureThreshold: number;\n resetTimeout: number;\n monitoringPeriod: number;\n };\n clustering: {\n enabled: boolean;\n workers: number | 'auto';\n memoryPerWorkerGB?: number | undefined;\n };\n}\n\nexport interface WebSocketConfig {\n enabled: boolean;\n adapter?: string | 'socket.io' | 'ws' | 'uws';\n compression?: boolean;\n customIdGenerator?: () => string;\n options?: {\n cors?: {\n origin?: string | string[];\n credentials?: boolean;\n };\n path?: string;\n maxPayloadLength?: number;\n idleTimeout?: number;\n ssl?: {\n key_file_name?: string;\n cert_file_name?: string;\n passphrase?: string;\n };\n };\n}\n\nexport interface JobsConfig {\n enabled?: boolean;\n maxConcurrentJobs?: number;\n gracefulShutdownTimeout?: number;\n leaderElection?: {\n enabled?: boolean;\n strategy?: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n };\n executor?: {\n maxRetries?: number;\n retryDelay?: number;\n retryBackoff?: 'linear' | 'exponential';\n retryBackoffMultiplier?: number;\n maxRetryDelay?: number;\n timeout?: number;\n enableCircuitBreaker?: boolean;\n circuitBreakerThreshold?: number;\n circuitBreakerResetTimeout?: number;\n enableMemoryMonitoring?: boolean;\n memoryThreshold?: number;\n };\n stateManager?: {\n persistPath?: string;\n historySize?: number;\n persistInterval?: number;\n enableAutoPersist?: boolean;\n enableRecovery?: boolean;\n };\n}\n\n// File-based routing configuration. When enabled, route files under the\n// configured directories are imported during startup (after module discovery)\n// so their `getApp().get(...)` registrations execute automatically.\nexport interface RoutingConfig {\n enabled: boolean;\n // Directories scanned via loadRoutes(). Defaults to ./src/routes in\n // development and ./dist/(src/)routes in production when omitted.\n paths?: string[];\n}\n\n// Main configuration interface\nexport interface AppConfig {\n server: ServerConfig;\n serviceDiscovery: ServiceDiscoveryConfig;\n database: DatabaseConfig;\n modules: ModuleDefaultsConfig;\n // Enable file-based auto routing (./src/routes). Defaults to true.\n routing: boolean | RoutingConfig;\n logging: LoggingConfig;\n security: SecurityConfig;\n external: ExternalServicesConfig;\n performance: PerformanceConfig;\n websocket: WebSocketConfig;\n jobs?: JobsConfig;\n queue?: QueueConfig;\n}\n\n// Queue Configuration\nexport interface QueueConfig {\n adapter?: 'bull' | 'rabbitmq' | 'sqs' | 'kafka' | 'memory';\n connection?: {\n host?: string;\n port?: number;\n username?: string;\n password?: string;\n database?: number;\n brokers?: string[];\n groupId?: string;\n region?: string;\n queueUrl?: string;\n [key: string]: any;\n };\n concurrency?: number;\n retry?: {\n maxAttempts: number;\n backoff: 'fixed' | 'exponential' | 'linear';\n initialDelay: number;\n maxDelay?: number;\n };\n deadLetterQueue?: {\n enabled: boolean;\n maxRetries: number;\n queueName?: string;\n };\n defaultJobOptions?: {\n removeOnComplete?: boolean | number;\n removeOnFail?: boolean | number;\n attempts?: number;\n backoff?: {\n type: 'fixed' | 'exponential' | 'linear';\n delay: number;\n };\n };\n prefix?: string;\n limiter?: {\n max: number;\n duration: number;\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morojs/moro",
3
- "version": "1.8.15",
3
+ "version": "1.8.17",
4
4
  "description": "High-performance Node.js framework with intelligent routing, automatic middleware ordering, enterprise authentication (Better Auth), type-safe validation, and functional architecture",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",