@morojs/moro 1.8.12 → 1.8.14
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/README.md +4 -2
- package/dist/core/cluster/thread-cluster.d.ts +25 -0
- package/dist/core/cluster/thread-cluster.js +95 -3
- package/dist/core/cluster/thread-cluster.js.map +1 -1
- package/dist/core/config/config-validator.js +20 -0
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/index.js +19 -2
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/framework.d.ts +26 -0
- package/dist/core/framework.js +195 -6
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +19 -0
- package/dist/core/http/http-server.js +75 -1
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +15 -0
- package/dist/core/http/http2-server.js +36 -3
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -0
- package/dist/core/http/moro-engine-server.js +138 -12
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/body-type.d.ts +12 -0
- package/dist/core/http/utils/body-type.js +70 -0
- package/dist/core/http/utils/body-type.js.map +1 -0
- package/dist/core/http/utils/compression.d.ts +21 -0
- package/dist/core/http/utils/compression.js +53 -7
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/utils/ssl-config.d.ts +11 -0
- package/dist/core/http/utils/ssl-config.js +3 -0
- package/dist/core/http/utils/ssl-config.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +5 -4
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +3 -0
- package/dist/core/http/uws-http-server.js +47 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/middleware/built-in/body-parsers/index.d.ts +18 -0
- package/dist/core/middleware/built-in/body-parsers/index.js +41 -0
- package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.d.ts +32 -8
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js +117 -25
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +7 -0
- package/dist/core/middleware/built-in/compression/core.js +32 -1
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/session/core.d.ts +1 -0
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.d.ts +18 -0
- package/dist/core/middleware/built-in/static/core.js +128 -7
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/engine-adapter.js +40 -1
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/socketio-adapter.js +10 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.d.ts +2 -2
- package/dist/core/networking/adapters/uws-adapter.js +54 -3
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +9 -2
- package/dist/core/networking/adapters/ws-adapter.js +97 -12
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/websocket-adapter.d.ts +19 -1
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/routing/index.d.ts +12 -8
- package/dist/core/routing/index.js +7 -2
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/unified-router.d.ts +46 -8
- package/dist/core/routing/unified-router.js +108 -32
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +8 -1
- package/dist/core/utilities/package-utils.js +2 -0
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +2 -2
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +12 -1
- package/dist/moro.js +63 -7
- package/dist/moro.js.map +1 -1
- package/dist/types/cache.d.ts +2 -0
- package/dist/types/cache.js.map +1 -1
- package/dist/types/config.d.ts +16 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/http.d.ts +6 -0
- package/dist/types/http.js.map +1 -1
- package/dist/types/session.d.ts +1 -0
- package/dist/types/session.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,23 @@ import { loadConfigFromAllSources, loadConfigFromAllSourcesAsync } from './confi
|
|
|
23
23
|
import { initializeAndLockConfig, getGlobalConfig as getConfig, isConfigLocked, resetConfigForTesting, } from './config-manager.js';
|
|
24
24
|
import { createFrameworkLogger } from '../logger/index.js';
|
|
25
25
|
const logger = createFrameworkLogger('ConfigSystem');
|
|
26
|
+
/**
|
|
27
|
+
* Configuration is one-per-process: the first createApp() locks it and every
|
|
28
|
+
* later createApp() in the same process (or worker thread) receives that
|
|
29
|
+
* same config. A second app that passes its OWN options (another port, TLS,
|
|
30
|
+
* ...) would otherwise silently boot with the first app's settings, so name
|
|
31
|
+
* the ignored keys at ERROR rather than a debug line nobody sees.
|
|
32
|
+
*/
|
|
33
|
+
function warnIgnoredOptions(options) {
|
|
34
|
+
const ignored = options ? Object.keys(options).filter(k => k !== 'logger') : [];
|
|
35
|
+
if (ignored.length === 0) {
|
|
36
|
+
logger.debug('Configuration already locked, returning existing config');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
logger.error(`createApp() called again after the configuration was locked; ignoring options [${ignored.join(', ')}]. ` +
|
|
40
|
+
'MoroJS keeps one configuration per process: the first createApp() wins and later apps share it. ' +
|
|
41
|
+
'Run a second differently-configured app in its own process.', 'ConfigLock');
|
|
42
|
+
}
|
|
26
43
|
/**
|
|
27
44
|
* Initialize configuration system with createApp options
|
|
28
45
|
* This is the main entry point called by createApp()
|
|
@@ -32,7 +49,7 @@ const logger = createFrameworkLogger('ConfigSystem');
|
|
|
32
49
|
*/
|
|
33
50
|
export function initializeConfig(options) {
|
|
34
51
|
if (isConfigLocked()) {
|
|
35
|
-
|
|
52
|
+
warnIgnoredOptions(options);
|
|
36
53
|
return getConfig();
|
|
37
54
|
}
|
|
38
55
|
logger.debug('Initializing configuration system');
|
|
@@ -53,7 +70,7 @@ export function initializeConfig(options) {
|
|
|
53
70
|
*/
|
|
54
71
|
export async function initializeConfigAsync(options) {
|
|
55
72
|
if (isConfigLocked()) {
|
|
56
|
-
|
|
73
|
+
warnIgnoredOptions(options);
|
|
57
74
|
return getConfig();
|
|
58
75
|
}
|
|
59
76
|
logger.debug('Initializing configuration system (async)');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,mCAAmC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AAGjC,mEAAmE;AACnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,8CAA8C;AAC9C,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,wBAAwB,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EACL,uBAAuB,EACvB,eAAe,IAAI,SAAS,EAC5B,cAAc,EACd,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,GAAG,qBAAqB,CAAC,cAAc,CAAC,CAAC;AAErD;;;;;;GAMG;AACH,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/config/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Configuration System - Immutable Config with createApp Override Support\n *\n * This is the main entry point for the MoroJS configuration system.\n * It provides a clean, immutable configuration that is locked at createApp() time.\n *\n * Key Features:\n * - Immutable configuration after initialization\n * - Clear precedence: Env Vars > createApp Options > Config File > Defaults\n * - Type-safe validation\n * - Single source of truth\n */\n\n// Export types and core components\nexport * from './schema.js';\nexport * from './config-sources.js';\nexport * from './config-validator.js';\nexport * from './file-loader.js';\nexport type { OriginFunction } from '../../types/config.js';\n\n// Export specific functions from config-manager to avoid conflicts\nexport {\n initializeAndLockConfig,\n isConfigLocked,\n resetConfigForTesting,\n} from './config-manager.js';\n\n// Export utilities for backward compatibility\nexport * from './utils.js';\n\nimport { MoroOptions } from '../../types/core.js';\nimport { AppConfig } from '../../types/config.js';\nimport { loadConfigFromAllSources, loadConfigFromAllSourcesAsync } from './config-sources.js';\nimport {\n initializeAndLockConfig,\n getGlobalConfig as getConfig,\n isConfigLocked,\n resetConfigForTesting,\n} from './config-manager.js';\nimport { createFrameworkLogger } from '../logger/index.js';\n\nconst logger = createFrameworkLogger('ConfigSystem');\n\n/**\n * Configuration is one-per-process: the first createApp() locks it and every\n * later createApp() in the same process (or worker thread) receives that\n * same config. A second app that passes its OWN options (another port, TLS,\n * ...) would otherwise silently boot with the first app's settings, so name\n * the ignored keys at ERROR rather than a debug line nobody sees.\n */\nfunction warnIgnoredOptions(options?: MoroOptions): void {\n const ignored = options ? Object.keys(options).filter(k => k !== 'logger') : [];\n if (ignored.length === 0) {\n logger.debug('Configuration already locked, returning existing config');\n return;\n }\n logger.error(\n `createApp() called again after the configuration was locked; ignoring options [${ignored.join(', ')}]. ` +\n 'MoroJS keeps one configuration per process: the first createApp() wins and later apps share it. ' +\n 'Run a second differently-configured app in its own process.',\n 'ConfigLock'\n );\n}\n\n/**\n * Initialize configuration system with createApp options\n * This is the main entry point called by createApp()\n *\n * @param options - createApp options that can override config file and defaults\n * @returns Immutable, validated configuration object\n */\nexport function initializeConfig(options?: MoroOptions): Readonly<AppConfig> {\n if (isConfigLocked()) {\n warnIgnoredOptions(options);\n return getConfig();\n }\n\n logger.debug('Initializing configuration system');\n\n // Load configuration from all sources with proper precedence\n const config = loadConfigFromAllSources(options);\n\n // Lock the configuration to prevent further changes\n initializeAndLockConfig(config);\n\n logger.info(\n `Configuration system initialized and locked: ${process.env.NODE_ENV || 'development'}:${config.server.port} (sources: env + file + options + defaults)`\n );\n\n return config;\n}\n\n/**\n * Initialize configuration system asynchronously.\n * Uses dynamic import() for the config file — supports ESM config files in\n * projects with \"type\":\"module\". Called internally by the async createApp().\n *\n * @param options - createApp options that can override config file and defaults\n * @returns Immutable, validated configuration object\n */\nexport async function initializeConfigAsync(options?: MoroOptions): Promise<Readonly<AppConfig>> {\n if (isConfigLocked()) {\n warnIgnoredOptions(options);\n return getConfig();\n }\n\n logger.debug('Initializing configuration system (async)');\n\n const config = await loadConfigFromAllSourcesAsync(options);\n\n initializeAndLockConfig(config);\n\n logger.info(\n `Configuration system initialized and locked: ${process.env.NODE_ENV || 'development'}:${config.server.port} (sources: env + file + options + defaults)`\n );\n\n return config;\n}\n\n/**\n * Load configuration without locking (for testing and utilities)\n * This maintains backward compatibility with existing code\n */\nexport function loadConfig(): AppConfig {\n return loadConfigFromAllSources();\n}\n\n/**\n * Load configuration with createApp options (for testing and utilities)\n * This maintains backward compatibility with existing code\n */\nexport function loadConfigWithOptions(options: MoroOptions): AppConfig {\n return loadConfigFromAllSources(options);\n}\n\n/**\n * Get the current global configuration\n * Alias for getGlobalConfig() for backward compatibility\n */\nexport function getGlobalConfig(): Readonly<AppConfig> {\n return getConfig();\n}\n\n/**\n * Check if configuration has been initialized and locked\n * Alias for isConfigLocked() for backward compatibility\n */\nexport function isConfigInitialized(): boolean {\n return isConfigLocked();\n}\n\n/**\n * Reset configuration state (for testing only)\n * @internal\n */\nexport function resetConfig(): void {\n resetConfigForTesting();\n}\n"]}
|
|
@@ -212,6 +212,7 @@ export const DEFAULT_CONFIG = {
|
|
|
212
212
|
enabled: false, // Opt-in to avoid overhead
|
|
213
213
|
level: 6,
|
|
214
214
|
threshold: 1024,
|
|
215
|
+
maxInlineBytes: 16384, // compressed on the calling thread up to here, on the pool above
|
|
215
216
|
},
|
|
216
217
|
circuitBreaker: {
|
|
217
218
|
enabled: false, // Opt-in to avoid overhead
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/core/config/schema.ts"],"names":[],"mappings":"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","sourcesContent":["// Core Configuration Schema for Moro Framework\n\nimport type { AppConfig } from '../../types/config.js';\n\n// Minimal default configuration - performance-focused, most things opt-in\nexport const DEFAULT_CONFIG: AppConfig = {\n server: {\n port: 3001,\n host: 'localhost',\n // 0 = unlimited. Historically defaulted to 1000/30000 but those values\n // were VALIDATED-BUT-NEVER-APPLIED to any server, so the observed behavior\n // has always been \"unlimited connections, no request timeout\". Now that\n // both are actually wired through, keeping 0 preserves that behavior;\n // enforcing the old numbers would silently break existing deployments.\n // A user who sets either explicitly finally gets it applied.\n maxConnections: 0,\n timeout: 0, // deprecated alias for timeouts.request; 0 = disabled\n bodySizeLimit: '10mb',\n maxUploadSize: '100mb', // Separate limit for file uploads (multipart/form-data)\n // Per-phase timeouts. keepAlive/headers were previously hardcoded in the\n // Node server (5000/6000); they become documented, overridable defaults.\n // idle/request default to 0 = the per-server default (engine: 120s idle /\n // 300s request; Node: its built-in 5min requestTimeout).\n timeouts: {\n keepAlive: 5000,\n headers: 6000,\n idle: 0,\n request: 0,\n },\n // Fine-grained limits. Only multipart carries defaults here (matching the\n // parser's previous hardcodes); header/ws/high-water limits are left unset\n // so each runtime uses its own documented default unless overridden.\n limits: {\n multipart: {\n maxParts: 1000,\n maxPartHeaderBytes: '16kb',\n },\n },\n // engine: left unset so resolution can tell \"user chose it\" from \"default\".\n // Unset resolves to 'moro' (Moro's native engine, Node.js fallback if it\n // can't load); set 'node' to disable it or 'uws' to opt into uWebSockets.js.\n useUWebSockets: false, // Deprecated alias for engine: 'uws'\n requestTracking: {\n enabled: true, // Enable by default for debugging\n },\n requestLogging: {\n // Enabled in development; disabled in production where a per-request\n // stdout write is a real throughput cost. Set explicitly to override.\n enabled: process.env.NODE_ENV !== 'production',\n },\n errorBoundary: {\n enabled: true, // Always enabled for safety\n },\n },\n serviceDiscovery: {\n enabled: false,\n type: 'memory',\n consulUrl: 'http://localhost:8500',\n kubernetesNamespace: 'default',\n healthCheckInterval: 30000,\n retryAttempts: 3,\n },\n database: {},\n modules: {\n apiPrefix: '/api/', // Default prefix for module routes - set to '' to disable\n cache: {\n enabled: false, // Opt-in for better performance\n defaultTtl: 300,\n maxSize: 1000,\n strategy: 'lru',\n },\n rateLimit: {\n enabled: false, // Opt-in to avoid unnecessary overhead\n defaultRequests: 100,\n defaultWindow: 60000,\n skipSuccessfulRequests: false,\n skipFailedRequests: false,\n },\n validation: {\n enabled: false,\n stripUnknown: true,\n abortEarly: false,\n allowUnknown: false,\n },\n session: {\n enabled: false, // Opt-in\n store: 'memory',\n // No hardcoded default: a shared shipped secret is insecure. Empty means\n // \"configure one\" (mirrors csrf.secret below); the session layer should\n // warn + generate a random secret when none is set, like auth/csrf do.\n secret: '',\n name: 'connect.sid',\n rolling: false,\n resave: false,\n saveUninitialized: false,\n cookie: {\n maxAge: 86400000, // 24 hours in ms\n httpOnly: true,\n secure: false,\n sameSite: 'lax',\n path: '/',\n },\n proxy: false,\n unset: 'keep',\n },\n autoDiscovery: {\n enabled: true, // Enable by default for better DX\n paths:\n process.env.NODE_ENV === 'production'\n ? ['./dist/modules', './dist/src/modules', './modules']\n : ['./modules', './src/modules'],\n patterns: ['**/*.module.{ts,js}', '**/index.{ts,js}', '**/*.config.{ts,js}'],\n recursive: true,\n loadingStrategy: 'eager',\n watchForChanges: false, // Opt-in for development\n ignorePatterns: ['**/*.test.{ts,js}', '**/*.spec.{ts,js}', '**/node_modules/**'],\n loadOrder: 'dependency',\n failOnError: false, // Graceful degradation\n maxDepth: 5,\n },\n },\n // File-based auto routing: import route files under ./src/routes on startup.\n // Enabled by default; a no-op when the directory doesn't exist.\n routing: true,\n logging: {\n level: 'info',\n format: 'pretty',\n enableColors: true,\n enableTimestamp: true,\n enableContext: true,\n // Opt-in: appends JSON metadata tail in pretty logs. Off by default to avoid noise.\n enableMetadata: false,\n // Opt-in: includes (memory) perf info per log. Off by default to avoid the\n // process.memoryUsage() call and the \"(210MB)\" suffix.\n enablePerformance: false,\n outputs: {\n console: true,\n file: {\n enabled: false,\n path: './logs/moro.log',\n maxSize: '10MB',\n maxFiles: 5,\n },\n webhook: {\n enabled: false,\n headers: {},\n },\n },\n },\n security: {\n cors: {\n enabled: false, // Opt-in for better performance\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400, // 24 hours\n preflightContinue: false,\n },\n helmet: {\n enabled: false, // Opt-in for better performance\n // Simplified options (backward compatible)\n contentSecurityPolicy: true,\n hsts: true,\n noSniff: true,\n frameguard: true,\n // Detailed options (use these for advanced configuration)\n xFrameOptions: 'DENY',\n xContentTypeOptions: true,\n xXssProtection: true,\n referrerPolicy: 'strict-origin-when-cross-origin',\n strictTransportSecurity: {\n maxAge: 31536000,\n includeSubDomains: true,\n },\n xDownloadOptions: true,\n xPermittedCrossDomainPolicies: true,\n },\n csrf: {\n enabled: false, // Opt-in\n secret: '',\n tokenLength: 32,\n cookieName: '_csrf',\n headerName: 'x-csrf-token',\n ignoreMethods: ['GET', 'HEAD', 'OPTIONS'],\n sameSite: true,\n },\n csp: {\n enabled: false, // Opt-in\n directives: {\n defaultSrc: [\"'self'\"],\n scriptSrc: [\"'self'\"],\n styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n imgSrc: [\"'self'\", 'data:', 'https:'],\n connectSrc: [\"'self'\"],\n fontSrc: [\"'self'\"],\n objectSrc: [\"'none'\"],\n mediaSrc: [\"'self'\"],\n frameSrc: [\"'none'\"],\n },\n reportOnly: false,\n nonce: false,\n },\n rateLimit: {\n global: {\n enabled: false,\n requests: 1000,\n window: 60000,\n },\n },\n },\n external: {},\n performance: {\n compression: {\n enabled: false, // Opt-in to avoid overhead\n level: 6,\n threshold: 1024,\n },\n circuitBreaker: {\n enabled: false, // Opt-in to avoid overhead\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n },\n clustering: {\n enabled: false,\n workers: 1,\n memoryPerWorkerGB: undefined,\n },\n },\n websocket: {\n enabled: false, // Opt-in - user must explicitly enable WebSockets\n },\n};\n\n// Schema validation is now handled by config-validator.ts\n// This export is kept for backward compatibility only\n// Note: For actual validation, use validateConfig() from config-validator.ts directly\nexport const ConfigSchema = {\n parse: (data: any): AppConfig => {\n // Simple pass-through for backward compatibility\n // Real validation happens in the config loading pipeline\n return data as AppConfig;\n },\n};\n\n// Re-export types for backward compatibility\nexport type {\n AppConfig,\n DeepPartial,\n ServerConfig,\n ServiceDiscoveryConfig,\n DatabaseConfig,\n ModuleDefaultsConfig,\n LoggingConfig,\n SecurityConfig,\n ExternalServicesConfig,\n PerformanceConfig,\n} from '../../types/config.js';\n\n// For backward compatibility with modules that expect schema objects\nexport const ServerConfigSchema = { parse: (data: any) => data };\nexport const ServiceDiscoveryConfigSchema = { parse: (data: any) => data };\nexport const DatabaseConfigSchema = { parse: (data: any) => data };\nexport const ModuleDefaultsConfigSchema = { parse: (data: any) => data };\nexport const LoggingConfigSchema = { parse: (data: any) => data };\nexport const SecurityConfigSchema = { parse: (data: any) => data };\nexport const ExternalServicesConfigSchema = { parse: (data: any) => data };\nexport const PerformanceConfigSchema = { parse: (data: any) => data };\n"]}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/core/config/schema.ts"],"names":[],"mappings":"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","sourcesContent":["// Core Configuration Schema for Moro Framework\n\nimport type { AppConfig } from '../../types/config.js';\n\n// Minimal default configuration - performance-focused, most things opt-in\nexport const DEFAULT_CONFIG: AppConfig = {\n server: {\n port: 3001,\n host: 'localhost',\n // 0 = unlimited. Historically defaulted to 1000/30000 but those values\n // were VALIDATED-BUT-NEVER-APPLIED to any server, so the observed behavior\n // has always been \"unlimited connections, no request timeout\". Now that\n // both are actually wired through, keeping 0 preserves that behavior;\n // enforcing the old numbers would silently break existing deployments.\n // A user who sets either explicitly finally gets it applied.\n maxConnections: 0,\n timeout: 0, // deprecated alias for timeouts.request; 0 = disabled\n bodySizeLimit: '10mb',\n maxUploadSize: '100mb', // Separate limit for file uploads (multipart/form-data)\n // Per-phase timeouts. keepAlive/headers were previously hardcoded in the\n // Node server (5000/6000); they become documented, overridable defaults.\n // idle/request default to 0 = the per-server default (engine: 120s idle /\n // 300s request; Node: its built-in 5min requestTimeout).\n timeouts: {\n keepAlive: 5000,\n headers: 6000,\n idle: 0,\n request: 0,\n },\n // Fine-grained limits. Only multipart carries defaults here (matching the\n // parser's previous hardcodes); header/ws/high-water limits are left unset\n // so each runtime uses its own documented default unless overridden.\n limits: {\n multipart: {\n maxParts: 1000,\n maxPartHeaderBytes: '16kb',\n },\n },\n // engine: left unset so resolution can tell \"user chose it\" from \"default\".\n // Unset resolves to 'moro' (Moro's native engine, Node.js fallback if it\n // can't load); set 'node' to disable it or 'uws' to opt into uWebSockets.js.\n useUWebSockets: false, // Deprecated alias for engine: 'uws'\n requestTracking: {\n enabled: true, // Enable by default for debugging\n },\n requestLogging: {\n // Enabled in development; disabled in production where a per-request\n // stdout write is a real throughput cost. Set explicitly to override.\n enabled: process.env.NODE_ENV !== 'production',\n },\n errorBoundary: {\n enabled: true, // Always enabled for safety\n },\n },\n serviceDiscovery: {\n enabled: false,\n type: 'memory',\n consulUrl: 'http://localhost:8500',\n kubernetesNamespace: 'default',\n healthCheckInterval: 30000,\n retryAttempts: 3,\n },\n database: {},\n modules: {\n apiPrefix: '/api/', // Default prefix for module routes - set to '' to disable\n cache: {\n enabled: false, // Opt-in for better performance\n defaultTtl: 300,\n maxSize: 1000,\n strategy: 'lru',\n },\n rateLimit: {\n enabled: false, // Opt-in to avoid unnecessary overhead\n defaultRequests: 100,\n defaultWindow: 60000,\n skipSuccessfulRequests: false,\n skipFailedRequests: false,\n },\n validation: {\n enabled: false,\n stripUnknown: true,\n abortEarly: false,\n allowUnknown: false,\n },\n session: {\n enabled: false, // Opt-in\n store: 'memory',\n // No hardcoded default: a shared shipped secret is insecure. Empty means\n // \"configure one\" (mirrors csrf.secret below); the session layer should\n // warn + generate a random secret when none is set, like auth/csrf do.\n secret: '',\n name: 'connect.sid',\n rolling: false,\n resave: false,\n saveUninitialized: false,\n cookie: {\n maxAge: 86400000, // 24 hours in ms\n httpOnly: true,\n secure: false,\n sameSite: 'lax',\n path: '/',\n },\n proxy: false,\n unset: 'keep',\n },\n autoDiscovery: {\n enabled: true, // Enable by default for better DX\n paths:\n process.env.NODE_ENV === 'production'\n ? ['./dist/modules', './dist/src/modules', './modules']\n : ['./modules', './src/modules'],\n patterns: ['**/*.module.{ts,js}', '**/index.{ts,js}', '**/*.config.{ts,js}'],\n recursive: true,\n loadingStrategy: 'eager',\n watchForChanges: false, // Opt-in for development\n ignorePatterns: ['**/*.test.{ts,js}', '**/*.spec.{ts,js}', '**/node_modules/**'],\n loadOrder: 'dependency',\n failOnError: false, // Graceful degradation\n maxDepth: 5,\n },\n },\n // File-based auto routing: import route files under ./src/routes on startup.\n // Enabled by default; a no-op when the directory doesn't exist.\n routing: true,\n logging: {\n level: 'info',\n format: 'pretty',\n enableColors: true,\n enableTimestamp: true,\n enableContext: true,\n // Opt-in: appends JSON metadata tail in pretty logs. Off by default to avoid noise.\n enableMetadata: false,\n // Opt-in: includes (memory) perf info per log. Off by default to avoid the\n // process.memoryUsage() call and the \"(210MB)\" suffix.\n enablePerformance: false,\n outputs: {\n console: true,\n file: {\n enabled: false,\n path: './logs/moro.log',\n maxSize: '10MB',\n maxFiles: 5,\n },\n webhook: {\n enabled: false,\n headers: {},\n },\n },\n },\n security: {\n cors: {\n enabled: false, // Opt-in for better performance\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400, // 24 hours\n preflightContinue: false,\n },\n helmet: {\n enabled: false, // Opt-in for better performance\n // Simplified options (backward compatible)\n contentSecurityPolicy: true,\n hsts: true,\n noSniff: true,\n frameguard: true,\n // Detailed options (use these for advanced configuration)\n xFrameOptions: 'DENY',\n xContentTypeOptions: true,\n xXssProtection: true,\n referrerPolicy: 'strict-origin-when-cross-origin',\n strictTransportSecurity: {\n maxAge: 31536000,\n includeSubDomains: true,\n },\n xDownloadOptions: true,\n xPermittedCrossDomainPolicies: true,\n },\n csrf: {\n enabled: false, // Opt-in\n secret: '',\n tokenLength: 32,\n cookieName: '_csrf',\n headerName: 'x-csrf-token',\n ignoreMethods: ['GET', 'HEAD', 'OPTIONS'],\n sameSite: true,\n },\n csp: {\n enabled: false, // Opt-in\n directives: {\n defaultSrc: [\"'self'\"],\n scriptSrc: [\"'self'\"],\n styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n imgSrc: [\"'self'\", 'data:', 'https:'],\n connectSrc: [\"'self'\"],\n fontSrc: [\"'self'\"],\n objectSrc: [\"'none'\"],\n mediaSrc: [\"'self'\"],\n frameSrc: [\"'none'\"],\n },\n reportOnly: false,\n nonce: false,\n },\n rateLimit: {\n global: {\n enabled: false,\n requests: 1000,\n window: 60000,\n },\n },\n },\n external: {},\n performance: {\n compression: {\n enabled: false, // Opt-in to avoid overhead\n level: 6,\n threshold: 1024,\n maxInlineBytes: 16384, // compressed on the calling thread up to here, on the pool above\n },\n circuitBreaker: {\n enabled: false, // Opt-in to avoid overhead\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n },\n clustering: {\n enabled: false,\n workers: 1,\n memoryPerWorkerGB: undefined,\n },\n },\n websocket: {\n enabled: false, // Opt-in - user must explicitly enable WebSockets\n },\n};\n\n// Schema validation is now handled by config-validator.ts\n// This export is kept for backward compatibility only\n// Note: For actual validation, use validateConfig() from config-validator.ts directly\nexport const ConfigSchema = {\n parse: (data: any): AppConfig => {\n // Simple pass-through for backward compatibility\n // Real validation happens in the config loading pipeline\n return data as AppConfig;\n },\n};\n\n// Re-export types for backward compatibility\nexport type {\n AppConfig,\n DeepPartial,\n ServerConfig,\n ServiceDiscoveryConfig,\n DatabaseConfig,\n ModuleDefaultsConfig,\n LoggingConfig,\n SecurityConfig,\n ExternalServicesConfig,\n PerformanceConfig,\n} from '../../types/config.js';\n\n// For backward compatibility with modules that expect schema objects\nexport const ServerConfigSchema = { parse: (data: any) => data };\nexport const ServiceDiscoveryConfigSchema = { parse: (data: any) => data };\nexport const DatabaseConfigSchema = { parse: (data: any) => data };\nexport const ModuleDefaultsConfigSchema = { parse: (data: any) => data };\nexport const LoggingConfigSchema = { parse: (data: any) => data };\nexport const SecurityConfigSchema = { parse: (data: any) => data };\nexport const ExternalServicesConfigSchema = { parse: (data: any) => data };\nexport const PerformanceConfigSchema = { parse: (data: any) => data };\n"]}
|
package/dist/core/framework.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { MoroEngineServer } from './http/moro-engine-server.js';
|
|
|
5
5
|
import { MoroHttp2Server, Http2ServerOptions } from './http/http2-server.js';
|
|
6
6
|
import { Container } from './utilities/index.js';
|
|
7
7
|
import { WebSocketManager } from './networking/websocket-manager.js';
|
|
8
|
+
import { type SSLConfigInput } from './http/utils/ssl-config.js';
|
|
8
9
|
import { ModuleConfig } from '../types/module.js';
|
|
9
10
|
import { MoroOptions as CoreMoroOptions } from '../types/core.js';
|
|
10
11
|
import { WebSocketAdapter, WebSocketAdapterOptions } from './networking/websocket-adapter.js';
|
|
@@ -56,6 +57,12 @@ export declare class Moro extends EventEmitter {
|
|
|
56
57
|
private usingEngine;
|
|
57
58
|
private usingHttp2;
|
|
58
59
|
private engineInfo;
|
|
60
|
+
/** The normalized server.ssl in effect (updated by reloadTLS) */
|
|
61
|
+
private sslConfig;
|
|
62
|
+
private tlsWatchers;
|
|
63
|
+
private tlsWatchTimer;
|
|
64
|
+
/** Hash of the last key/cert/CA bytes loaded from disk (skips no-op reloads) */
|
|
65
|
+
private tlsMaterialHash;
|
|
59
66
|
private websocketSetupPromise;
|
|
60
67
|
constructor(options?: MoroOptions);
|
|
61
68
|
use(middleware: any): this;
|
|
@@ -116,6 +123,25 @@ export declare class Moro extends EventEmitter {
|
|
|
116
123
|
setupFinalModuleHandler(): void;
|
|
117
124
|
private setupWebSocketHandlers;
|
|
118
125
|
private checkRateLimit;
|
|
126
|
+
/**
|
|
127
|
+
* Rotate the TLS certificate/key on the running listener. The new material
|
|
128
|
+
* is validated (files readable, PEM parses, key matches cert) BEFORE the
|
|
129
|
+
* listener is touched, so a half-written file or a wrong pair rejects and
|
|
130
|
+
* the current certificate keeps serving; connections that already
|
|
131
|
+
* handshaked are never affected. Policy fields (minVersion, mTLS flags,
|
|
132
|
+
* passphrase) and the CA carry over unless the update sets them.
|
|
133
|
+
*/
|
|
134
|
+
reloadTLS(ssl?: SSLConfigInput): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* `server.ssl.watch`: reload the certificate when its files change on disk.
|
|
137
|
+
* The DIRECTORIES holding the files are watched (an ACME client or a
|
|
138
|
+
* Kubernetes secret mount replaces files by rename/symlink swap, which a
|
|
139
|
+
* per-file watcher misses), changes are debounced, unchanged bytes are
|
|
140
|
+
* ignored, and a failed reload is logged while the current certificate
|
|
141
|
+
* keeps serving. Watchers are non-persistent and closed by stopTlsWatch().
|
|
142
|
+
*/
|
|
143
|
+
startTlsWatch(): void;
|
|
144
|
+
stopTlsWatch(): void;
|
|
119
145
|
/**
|
|
120
146
|
* Which HTTP server backs this app, which package provides it, and the
|
|
121
147
|
* fallback reason when the native engine was requested but unavailable.
|
package/dist/core/framework.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
2
4
|
import { EventEmitter } from 'events';
|
|
5
|
+
import { requireBuiltin } from './utilities/builtin.js';
|
|
3
6
|
import { MoroHttpServer } from './http/index.js';
|
|
4
7
|
import { UWebSocketsHttpServer } from './http/uws-http-server.js';
|
|
5
8
|
import { MoroEngineServer } from './http/moro-engine-server.js';
|
|
@@ -11,7 +14,7 @@ import { WebSocketManager } from './networking/websocket-manager.js';
|
|
|
11
14
|
import { CircuitBreaker } from './utilities/circuit-breaker.js';
|
|
12
15
|
import { isPackageAvailable, loadNativeEngine, getNativeEngineLoadErrors, } from './utilities/package-utils.js';
|
|
13
16
|
import { parseSizeToBytes } from './http/utils/size.js';
|
|
14
|
-
import { normalizeSSLConfig, sslForNode, sslForUws, sslIsComplete, } from './http/utils/ssl-config.js';
|
|
17
|
+
import { normalizeSSLConfig, sslForEngine, sslForNode, sslForUws, sslIsComplete, } from './http/utils/ssl-config.js';
|
|
15
18
|
import { MoroEventBus } from './events/index.js';
|
|
16
19
|
import { createFrameworkLogger, logger as globalLogger } from './logger/index.js';
|
|
17
20
|
import { mergeWebSocketConfig, } from './networking/websocket-adapter.js';
|
|
@@ -38,6 +41,12 @@ export class Moro extends EventEmitter {
|
|
|
38
41
|
usingHttp2 = false;
|
|
39
42
|
// Which server actually booted (engine = native uWS-style engine)
|
|
40
43
|
engineInfo = { server: 'node' };
|
|
44
|
+
/** The normalized server.ssl in effect (updated by reloadTLS) */
|
|
45
|
+
sslConfig = null;
|
|
46
|
+
tlsWatchers = [];
|
|
47
|
+
tlsWatchTimer;
|
|
48
|
+
/** Hash of the last key/cert/CA bytes loaded from disk (skips no-op reloads) */
|
|
49
|
+
tlsMaterialHash;
|
|
41
50
|
// WebSocket initialization promise to handle async adapter detection
|
|
42
51
|
websocketSetupPromise = null;
|
|
43
52
|
constructor(options = {}) {
|
|
@@ -73,6 +82,7 @@ export class Moro extends EventEmitter {
|
|
|
73
82
|
// projected per-runtime, so a single server.ssl / server.limits flows
|
|
74
83
|
// everywhere regardless of which server boots.
|
|
75
84
|
const ssl = normalizeSSLConfig(this.config.server?.ssl, options.https, this.logger);
|
|
85
|
+
this.sslConfig = ssl;
|
|
76
86
|
const rt = this.buildRuntimeLimits();
|
|
77
87
|
if (engineChoice.kind === 'engine') {
|
|
78
88
|
try {
|
|
@@ -84,7 +94,10 @@ export class Moro extends EventEmitter {
|
|
|
84
94
|
if (typeof engineSurface?.serve === 'function' &&
|
|
85
95
|
typeof engineSurface?.respond === 'function') {
|
|
86
96
|
this.httpServer = new MoroEngineServer({
|
|
87
|
-
|
|
97
|
+
// Project to the engine's own option names (key_file_name / key,
|
|
98
|
+
// ...). The normalized shape's keyFile/certFile mean nothing to
|
|
99
|
+
// serve(), which would throw and silently fall back to Node https.
|
|
100
|
+
...(ssl ? { ssl: sslForEngine(ssl) } : {}),
|
|
88
101
|
limits: rt,
|
|
89
102
|
maxBodySize: rt.maxBodySize,
|
|
90
103
|
maxUploadSize: rt.maxUploadSize,
|
|
@@ -124,7 +137,10 @@ export class Moro extends EventEmitter {
|
|
|
124
137
|
// Construction failed after a successful module load - unexpected, but
|
|
125
138
|
// never boot nothing: fall back to the Node.js http server.
|
|
126
139
|
const reason = error instanceof Error ? error.message : String(error);
|
|
127
|
-
|
|
140
|
+
// With TLS configured this is almost always a certificate/key problem,
|
|
141
|
+
// and a deployment tuned for the engine would otherwise run on a
|
|
142
|
+
// different server behind a warning buried among INFO lines.
|
|
143
|
+
this.logger[ssl ? 'error' : 'warn'](`Native engine (${engineChoice.requested}) failed to initialize, falling back to Node.js http.Server. Error: ${reason}`, 'ServerInit');
|
|
128
144
|
this.usingUWebSockets = false;
|
|
129
145
|
this.usingEngine = false;
|
|
130
146
|
this.engineInfo = { server: 'node', fallbackReason: reason };
|
|
@@ -987,6 +1003,162 @@ export class Moro extends EventEmitter {
|
|
|
987
1003
|
limit.count++;
|
|
988
1004
|
return true;
|
|
989
1005
|
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Rotate the TLS certificate/key on the running listener. The new material
|
|
1008
|
+
* is validated (files readable, PEM parses, key matches cert) BEFORE the
|
|
1009
|
+
* listener is touched, so a half-written file or a wrong pair rejects and
|
|
1010
|
+
* the current certificate keeps serving; connections that already
|
|
1011
|
+
* handshaked are never affected. Policy fields (minVersion, mTLS flags,
|
|
1012
|
+
* passphrase) and the CA carry over unless the update sets them.
|
|
1013
|
+
*/
|
|
1014
|
+
async reloadTLS(ssl) {
|
|
1015
|
+
const current = this.sslConfig;
|
|
1016
|
+
if (!current) {
|
|
1017
|
+
throw new Error('reloadTLS: this app was not started with TLS (server.ssl)');
|
|
1018
|
+
}
|
|
1019
|
+
if (this.usingUWebSockets) {
|
|
1020
|
+
throw new Error('reloadTLS is not supported on the uWebSockets.js engine');
|
|
1021
|
+
}
|
|
1022
|
+
let next;
|
|
1023
|
+
if (ssl) {
|
|
1024
|
+
const update = normalizeSSLConfig(ssl, undefined, this.logger);
|
|
1025
|
+
if (!update || !sslIsComplete(update)) {
|
|
1026
|
+
throw new Error('reloadTLS: ssl must carry both a key and a certificate');
|
|
1027
|
+
}
|
|
1028
|
+
const hasCa = Boolean(update.ca || update.caFile);
|
|
1029
|
+
next = {
|
|
1030
|
+
// operator policy carries over...
|
|
1031
|
+
passphrase: current.passphrase,
|
|
1032
|
+
minVersion: current.minVersion,
|
|
1033
|
+
requestCert: current.requestCert,
|
|
1034
|
+
rejectUnauthorized: current.rejectUnauthorized,
|
|
1035
|
+
// ...as does the CA when the update names none
|
|
1036
|
+
...(hasCa ? {} : { ca: current.ca, caFile: current.caFile }),
|
|
1037
|
+
// and the update's own fields win (undefined entries dropped so they
|
|
1038
|
+
// do not shadow the carried-over values)
|
|
1039
|
+
...Object.fromEntries(Object.entries(update).filter(([, v]) => v !== undefined)),
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
if (!current.keyFile || !current.certFile) {
|
|
1044
|
+
throw new Error('reloadTLS() with no argument re-reads the server.ssl key/cert files, but this app was configured with inline PEM - pass the new material');
|
|
1045
|
+
}
|
|
1046
|
+
next = { ...current };
|
|
1047
|
+
}
|
|
1048
|
+
// One consistent snapshot of the material, validated on every backend the
|
|
1049
|
+
// same way (node's own parser: bad PEM / key-cert mismatch throw here).
|
|
1050
|
+
const nodeSsl = sslForNode(next);
|
|
1051
|
+
const tls = requireBuiltin('tls');
|
|
1052
|
+
tls.createSecureContext(nodeSsl);
|
|
1053
|
+
if (this.usingEngine) {
|
|
1054
|
+
// Hand the engine the validated bytes rather than the paths, so it
|
|
1055
|
+
// cannot read a different (newer, half-written) file than we checked.
|
|
1056
|
+
const engineSsl = sslForEngine({
|
|
1057
|
+
...next,
|
|
1058
|
+
key: nodeSsl.key,
|
|
1059
|
+
cert: nodeSsl.cert,
|
|
1060
|
+
ca: nodeSsl.ca,
|
|
1061
|
+
keyFile: undefined,
|
|
1062
|
+
certFile: undefined,
|
|
1063
|
+
caFile: undefined,
|
|
1064
|
+
});
|
|
1065
|
+
this.httpServer.updateSsl(engineSsl);
|
|
1066
|
+
}
|
|
1067
|
+
else if (this.usingHttp2) {
|
|
1068
|
+
this.httpServer.updateSsl(nodeSsl);
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
this.httpServer.updateSsl(nodeSsl);
|
|
1072
|
+
}
|
|
1073
|
+
this.sslConfig = next;
|
|
1074
|
+
this.tlsMaterialHash = MoroCoreTls.hashMaterial(nodeSsl);
|
|
1075
|
+
this.logger.info('TLS certificate reloaded', 'TLS');
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* `server.ssl.watch`: reload the certificate when its files change on disk.
|
|
1079
|
+
* The DIRECTORIES holding the files are watched (an ACME client or a
|
|
1080
|
+
* Kubernetes secret mount replaces files by rename/symlink swap, which a
|
|
1081
|
+
* per-file watcher misses), changes are debounced, unchanged bytes are
|
|
1082
|
+
* ignored, and a failed reload is logged while the current certificate
|
|
1083
|
+
* keeps serving. Watchers are non-persistent and closed by stopTlsWatch().
|
|
1084
|
+
*/
|
|
1085
|
+
startTlsWatch() {
|
|
1086
|
+
const ssl = this.sslConfig;
|
|
1087
|
+
const watch = this.config.server?.ssl?.watch;
|
|
1088
|
+
if (!ssl || !watch || this.tlsWatchers.length > 0)
|
|
1089
|
+
return;
|
|
1090
|
+
if (!ssl.keyFile || !ssl.certFile) {
|
|
1091
|
+
this.logger.warn('server.ssl.watch needs keyFile/certFile paths - inline PEM cannot be watched', 'TLS');
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (this.usingUWebSockets) {
|
|
1095
|
+
this.logger.warn('server.ssl.watch is not supported on the uWebSockets.js engine', 'TLS');
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const debounceMs = typeof watch === 'object' && typeof watch.debounceMs === 'number' && watch.debounceMs > 0
|
|
1099
|
+
? watch.debounceMs
|
|
1100
|
+
: 1000;
|
|
1101
|
+
try {
|
|
1102
|
+
this.tlsMaterialHash = MoroCoreTls.hashMaterial(sslForNode(ssl));
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
// unreadable now; the first change event will report properly
|
|
1106
|
+
}
|
|
1107
|
+
const files = [ssl.keyFile, ssl.certFile, ...(ssl.caFile ?? [])].map(f => path.resolve(f));
|
|
1108
|
+
const schedule = () => {
|
|
1109
|
+
if (this.tlsWatchTimer)
|
|
1110
|
+
clearTimeout(this.tlsWatchTimer);
|
|
1111
|
+
this.tlsWatchTimer = setTimeout(() => {
|
|
1112
|
+
this.tlsWatchTimer = undefined;
|
|
1113
|
+
let hash;
|
|
1114
|
+
try {
|
|
1115
|
+
hash = MoroCoreTls.hashMaterial(sslForNode(this.sslConfig ?? ssl));
|
|
1116
|
+
}
|
|
1117
|
+
catch (err) {
|
|
1118
|
+
// Mid-rotation (one file replaced, the other not yet): wait for the
|
|
1119
|
+
// next event rather than logging a scary error per half-write.
|
|
1120
|
+
this.logger.debug(`TLS files not readable yet: ${err instanceof Error ? err.message : String(err)}`, 'TLS');
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (hash === this.tlsMaterialHash)
|
|
1124
|
+
return; // touched, not changed
|
|
1125
|
+
this.reloadTLS().catch(err => {
|
|
1126
|
+
this.logger.error(`TLS reload failed; the current certificate keeps serving: ${err instanceof Error ? err.message : String(err)}`, 'TLS');
|
|
1127
|
+
});
|
|
1128
|
+
}, debounceMs);
|
|
1129
|
+
this.tlsWatchTimer.unref?.();
|
|
1130
|
+
};
|
|
1131
|
+
for (const dir of new Set(files.map(f => path.dirname(f)))) {
|
|
1132
|
+
try {
|
|
1133
|
+
const watcher = fs.watch(dir, { persistent: false }, () => schedule());
|
|
1134
|
+
watcher.on('error', err => {
|
|
1135
|
+
this.logger.warn(`TLS file watcher error on ${dir}: ${err.message}`, 'TLS');
|
|
1136
|
+
});
|
|
1137
|
+
this.tlsWatchers.push(watcher);
|
|
1138
|
+
}
|
|
1139
|
+
catch (err) {
|
|
1140
|
+
this.logger.warn(`Cannot watch ${dir} for certificate changes: ${err instanceof Error ? err.message : String(err)}`, 'TLS');
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
if (this.tlsWatchers.length > 0) {
|
|
1144
|
+
this.logger.info(`Watching TLS certificate files for changes (${files.length} files)`, 'TLS');
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
stopTlsWatch() {
|
|
1148
|
+
if (this.tlsWatchTimer) {
|
|
1149
|
+
clearTimeout(this.tlsWatchTimer);
|
|
1150
|
+
this.tlsWatchTimer = undefined;
|
|
1151
|
+
}
|
|
1152
|
+
for (const watcher of this.tlsWatchers) {
|
|
1153
|
+
try {
|
|
1154
|
+
watcher.close();
|
|
1155
|
+
}
|
|
1156
|
+
catch {
|
|
1157
|
+
// already closed
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
this.tlsWatchers = [];
|
|
1161
|
+
}
|
|
990
1162
|
/**
|
|
991
1163
|
* Which HTTP server backs this app, which package provides it, and the
|
|
992
1164
|
* fallback reason when the native engine was requested but unavailable.
|
|
@@ -1214,14 +1386,20 @@ export class Moro extends EventEmitter {
|
|
|
1214
1386
|
return this.circuitBreakers.get(key);
|
|
1215
1387
|
}
|
|
1216
1388
|
listen(port, host, callback) {
|
|
1389
|
+
// Certificate watching (server.ssl.watch) starts once the listener is up
|
|
1390
|
+
const userCb = typeof host === 'function' ? host : callback;
|
|
1391
|
+
const onListening = () => {
|
|
1392
|
+
this.startTlsWatch();
|
|
1393
|
+
userCb?.();
|
|
1394
|
+
};
|
|
1217
1395
|
if (typeof host === 'function') {
|
|
1218
|
-
this.httpServer.listen(port,
|
|
1396
|
+
this.httpServer.listen(port, onListening);
|
|
1219
1397
|
}
|
|
1220
1398
|
else if (host) {
|
|
1221
|
-
this.httpServer.listen(port, host,
|
|
1399
|
+
this.httpServer.listen(port, host, onListening);
|
|
1222
1400
|
}
|
|
1223
1401
|
else {
|
|
1224
|
-
this.httpServer.listen(port,
|
|
1402
|
+
this.httpServer.listen(port, onListening);
|
|
1225
1403
|
}
|
|
1226
1404
|
}
|
|
1227
1405
|
// Compatibility method for existing controllers
|
|
@@ -1238,4 +1416,15 @@ export class Moro extends EventEmitter {
|
|
|
1238
1416
|
return undefined;
|
|
1239
1417
|
}
|
|
1240
1418
|
}
|
|
1419
|
+
/** TLS helpers kept out of the class body (no per-instance state). */
|
|
1420
|
+
const MoroCoreTls = {
|
|
1421
|
+
hashMaterial(ssl) {
|
|
1422
|
+
const h = crypto.createHash('sha256');
|
|
1423
|
+
h.update(ssl.key);
|
|
1424
|
+
h.update(ssl.cert);
|
|
1425
|
+
for (const ca of ssl.ca ?? [])
|
|
1426
|
+
h.update(ca);
|
|
1427
|
+
return h.digest('hex');
|
|
1428
|
+
},
|
|
1429
|
+
};
|
|
1241
1430
|
//# sourceMappingURL=framework.js.map
|