@morojs/moro 1.8.9 → 1.8.11

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.
Files changed (42) hide show
  1. package/README.md +4 -0
  2. package/dist/core/cluster/thread-cluster.d.ts +178 -0
  3. package/dist/core/cluster/thread-cluster.js +389 -0
  4. package/dist/core/cluster/thread-cluster.js.map +1 -0
  5. package/dist/core/framework.js.map +1 -1
  6. package/dist/core/http/moro-engine-server.d.ts +48 -1
  7. package/dist/core/http/moro-engine-server.js +386 -37
  8. package/dist/core/http/moro-engine-server.js.map +1 -1
  9. package/dist/core/http/utils/compression.js +12 -6
  10. package/dist/core/http/utils/compression.js.map +1 -1
  11. package/dist/core/http/utils/uws-worker-clustering.js +10 -6
  12. package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
  13. package/dist/core/http/uws-http-server.js +3 -2
  14. package/dist/core/http/uws-http-server.js.map +1 -1
  15. package/dist/core/jobs/leader-election.js +16 -5
  16. package/dist/core/jobs/leader-election.js.map +1 -1
  17. package/dist/core/networking/websocket-manager.js +4 -2
  18. package/dist/core/networking/websocket-manager.js.map +1 -1
  19. package/dist/core/routing/index.d.ts +9 -2
  20. package/dist/core/routing/index.js +12 -7
  21. package/dist/core/routing/index.js.map +1 -1
  22. package/dist/core/routing/unified-router.d.ts +25 -1
  23. package/dist/core/routing/unified-router.js +69 -6
  24. package/dist/core/routing/unified-router.js.map +1 -1
  25. package/dist/core/runtime/node-adapter.d.ts +1 -1
  26. package/dist/core/runtime/node-adapter.js.map +1 -1
  27. package/dist/core/utilities/builtin.d.ts +1 -0
  28. package/dist/core/utilities/builtin.js +25 -0
  29. package/dist/core/utilities/builtin.js.map +1 -0
  30. package/dist/core/utilities/package-utils.d.ts +29 -3
  31. package/dist/core/utilities/package-utils.js +24 -12
  32. package/dist/core/utilities/package-utils.js.map +1 -1
  33. package/dist/core/workers/worker-manager.js +4 -3
  34. package/dist/core/workers/worker-manager.js.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/moro.d.ts +5 -0
  38. package/dist/moro.js +401 -211
  39. package/dist/moro.js.map +1 -1
  40. package/dist/types/http.d.ts +1 -1
  41. package/dist/types/http.js.map +1 -1
  42. package/package.json +1 -1
@@ -5,8 +5,14 @@ import crypto from 'crypto';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import os from 'os';
8
- import cluster from 'cluster';
9
- import { isMainThread } from 'worker_threads';
8
+ import { requireBuiltin } from '../utilities/builtin.js';
9
+ // worker_threads on first use (core/utilities/builtin.ts). node:cluster is
10
+ // never loaded here: a cluster worker is exactly a process with
11
+ // NODE_UNIQUE_ID in its environment (that is how node:cluster itself decides
12
+ // isWorker), and asking cluster would pull child_process/dgram/net (~7 MB)
13
+ // into every app that starts the job scheduler.
14
+ const wt = () => requireBuiltin('worker_threads');
15
+ const isClusterWorkerProcess = () => process.env.NODE_UNIQUE_ID !== undefined;
10
16
  /**
11
17
  * LeaderElection - Ensures only one job scheduler runs across distributed instances
12
18
  * Supports:
@@ -111,11 +117,16 @@ return 0`;
111
117
  * Check if this process should participate in leader election
112
118
  */
113
119
  shouldParticipate() {
114
- // Only main thread and primary cluster process can be leader
115
- if (!isMainThread) {
120
+ // Only the main thread of a non-worker process can be leader. Under
121
+ // thread clustering that is exactly the cluster primary (its worker
122
+ // threads are !isMainThread); under process clustering it is the primary
123
+ // process (workers have cluster.isWorker). Either way the scheduler runs
124
+ // in the primary (Moro.startPrimaryServices), so one participant per
125
+ // cluster - no change needed for threads.
126
+ if (!wt().isMainThread) {
116
127
  return false;
117
128
  }
118
- if (cluster.isWorker) {
129
+ if (isClusterWorkerProcess()) {
119
130
  return false;
120
131
  }
121
132
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"leader-election.js","sourceRoot":"","sources":["../../../src/core/jobs/leader-election.ts"],"names":[],"mappings":"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","sourcesContent":["// Production-grade Leader Election for Distributed Job Scheduling\n// Supports file-based and Redis-based locking for K8s and clustered environments\n\nimport { EventEmitter } from 'events';\nimport crypto from 'crypto';\nimport { Logger } from '../../types/logger.js';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport cluster from 'cluster';\nimport { isMainThread } from 'worker_threads';\n\nexport interface LeaderElectionOptions {\n strategy: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n redisClient?: any; // Optional Redis client\n forceLeader?: boolean; // Force this instance to be leader (dev/testing)\n instanceId?: string; // Custom instance ID\n}\n\nexport interface LeaderInfo {\n instanceId: string;\n hostname: string;\n pid: number;\n electedAt: Date;\n lastHeartbeat: Date;\n metadata?: Record<string, any>;\n}\n\n/**\n * LeaderElection - Ensures only one job scheduler runs across distributed instances\n * Supports:\n * - File-based locking (for local/NFS environments)\n * - Redis-based locking (for distributed systems)\n * - Kubernetes pod detection\n * - Automatic failover with health checks\n */\nexport class LeaderElection extends EventEmitter {\n private isLeader = false;\n private strategy: 'file' | 'redis' | 'none';\n private lockPath?: string;\n private lockTimeout: number;\n private heartbeatInterval: number;\n private heartbeatTimer?: NodeJS.Timeout | undefined;\n private checkTimer?: NodeJS.Timeout | undefined;\n private logger: Logger;\n private loggerContext = 'LeaderElection';\n private instanceId: string;\n private leaderInfo?: LeaderInfo;\n private redisClient?: any;\n private isShuttingDown = false;\n private lockAcquireAttempts = 0;\n\n private static readonly REDIS_LOCK_KEY = 'moro:jobs:leader:lock';\n\n // Compare-and-refresh: rewrite the value and extend the TTL only if the stored\n // lock still belongs to this instance (ownership is fenced by instanceId).\n private static readonly REDIS_REFRESH_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n return 1\nend\nreturn 0`;\n\n // Compare-and-delete: release the lock only if it still belongs to this instance.\n private static readonly REDIS_RELEASE_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n return redis.call('DEL', KEYS[1])\nend\nreturn 0`;\n\n constructor(logger: Logger, options: LeaderElectionOptions = { strategy: 'file' }) {\n super();\n this.logger = logger;\n this.strategy = options.strategy;\n this.lockTimeout = options.lockTimeout ?? 30000; // 30s default\n this.heartbeatInterval = options.heartbeatInterval ?? 10000; // 10s default\n this.redisClient = options.redisClient;\n\n // Generate unique instance ID\n this.instanceId =\n options.instanceId ||\n `${os.hostname()}-${process.pid}-${Date.now()}-${crypto.randomBytes(6).toString('hex')}`;\n\n // Setup lock path for file-based strategy\n if (this.strategy === 'file') {\n if (options.lockPath) {\n this.lockPath = options.lockPath;\n } else {\n const tmpDir = os.tmpdir();\n this.lockPath = path.join(tmpDir, 'moro-jobs-leader.lock');\n }\n }\n\n // Force leader mode (for dev/testing)\n if (options.forceLeader) {\n this.isLeader = true;\n this.logger.warn('Leader election forced - running as leader');\n }\n\n this.logger.debug('LeaderElection initialized', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n lockPath: this.lockPath,\n forceLeader: options.forceLeader,\n });\n }\n\n /**\n * Start leader election process\n */\n public async start(): Promise<void> {\n if (this.strategy === 'none') {\n this.logger.debug('Leader election disabled, assuming leader role');\n this.becomeLeader();\n return;\n }\n\n // Check if we should participate in leader election\n if (!this.shouldParticipate()) {\n this.logger.info('Not participating in leader election (worker process/thread)');\n return;\n }\n\n this.logger.info('Starting leader election...', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n });\n\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n\n // Start periodic checks\n this.startPeriodicCheck();\n }\n\n /**\n * Check if this process should participate in leader election\n */\n private shouldParticipate(): boolean {\n // Only main thread and primary cluster process can be leader\n if (!isMainThread) {\n return false;\n }\n\n if (cluster.isWorker) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Try to acquire leadership\n */\n private async tryAcquireLeadership(): Promise<boolean> {\n if (this.isShuttingDown) {\n return false;\n }\n\n this.lockAcquireAttempts++;\n\n try {\n let acquired = false;\n\n if (this.strategy === 'file') {\n acquired = await this.tryAcquireFileLock();\n } else if (this.strategy === 'redis') {\n acquired = await this.tryAcquireRedisLock();\n }\n\n if (acquired) {\n this.becomeLeader();\n return true;\n } else {\n // Check if current leader is still alive\n await this.checkLeaderHealth();\n return false;\n }\n } catch (error) {\n this.logger.error('Error during leader election', this.loggerContext, {\n error,\n attempt: this.lockAcquireAttempts,\n });\n return false;\n }\n }\n\n /**\n * Try to acquire file-based lock\n *\n * Uses O_EXCL (open flag 'wx') for the common \"no lock yet\" case so exactly one\n * instance can win the create race. An expired lock is taken over via an atomic\n * temp-write + rename. A takeover race between two instances is a last-writer-wins\n * rename, which is then reconciled by the periodic verifyLeadership() check and the\n * ownership-checked heartbeat below, so a single leader is still guaranteed.\n */\n private async tryAcquireFileLock(): Promise<boolean> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n const serialized = JSON.stringify(lockInfo);\n\n // Fast path: atomically create the lock file (O_EXCL). Fails with EEXIST if\n // another instance already holds the lock.\n try {\n const handle = await fs.promises.open(this.lockPath, 'wx');\n try {\n await handle.writeFile(serialized, 'utf-8');\n } finally {\n await handle.close();\n }\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'EEXIST') {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n // Lock file already exists — fall through to the expiry/takeover check.\n }\n\n try {\n // Read existing lock and decide whether we may take it over.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const existingLeader: LeaderInfo = JSON.parse(lockData);\n\n const lockAge = Date.now() - new Date(existingLeader.lastHeartbeat).getTime();\n\n if (lockAge < this.lockTimeout) {\n // Lock is still valid and held by another instance.\n this.leaderInfo = existingLeader;\n this.logger.debug('Leader lock held by another instance', this.loggerContext, {\n leader: existingLeader.instanceId,\n age: lockAge,\n });\n return false;\n }\n\n // Lock expired — take over with an atomic temp-write + rename.\n this.logger.warn('Leader lock expired, taking over', this.loggerContext, {\n previousLeader: existingLeader.instanceId,\n lockAge,\n });\n\n await this.atomicWriteLock(serialized);\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n }\n\n /**\n * Atomically (re)write the lock file: write to a unique temp file, then rename\n * over the target. rename() is atomic on POSIX so readers never see a partial\n * lock file.\n */\n private async atomicWriteLock(serialized: string): Promise<void> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const tempPath = `${this.lockPath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;\n await fs.promises.writeFile(tempPath, serialized, 'utf-8');\n try {\n await fs.promises.rename(tempPath, this.lockPath);\n } catch (error) {\n await fs.promises.unlink(tempPath).catch(() => {});\n throw error;\n }\n }\n\n /**\n * Try to acquire Redis-based lock\n */\n private async tryAcquireRedisLock(): Promise<boolean> {\n if (!this.redisClient) {\n throw new Error('Redis client not configured');\n }\n\n try {\n const lockKey = LeaderElection.REDIS_LOCK_KEY;\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n\n // Try to set lock with NX (only if not exists) and PX (expiration in ms)\n const result = await this.redisClient.set(\n lockKey,\n JSON.stringify(lockInfo),\n 'PX',\n this.lockTimeout,\n 'NX'\n );\n\n if (result === 'OK') {\n this.leaderInfo = lockInfo;\n return true;\n }\n\n // Lock exists, get current leader info\n const existingLock = await this.redisClient.get(lockKey);\n if (existingLock) {\n this.leaderInfo = JSON.parse(existingLock);\n }\n\n return false;\n } catch (error) {\n this.logger.error('Failed to acquire Redis lock', this.loggerContext, { error });\n return false;\n }\n }\n\n /**\n * Become leader\n */\n private becomeLeader(): void {\n if (this.isLeader) {\n return;\n }\n\n this.isLeader = true;\n this.logger.info('Became leader', this.loggerContext, {\n instanceId: this.instanceId,\n strategy: this.strategy,\n attempts: this.lockAcquireAttempts,\n });\n\n this.emit('leader:elected', {\n instanceId: this.instanceId,\n electedAt: this.leaderInfo?.electedAt,\n });\n\n // Start heartbeat\n this.startHeartbeat();\n }\n\n /**\n * Step down as leader\n */\n private async stepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.info('Stepping down as leader', this.loggerContext, {\n instanceId: this.instanceId,\n });\n\n this.isLeader = false;\n this.stopHeartbeat();\n\n // Release lock — only if we still own it, so we never delete a lock that\n // another instance has since acquired.\n try {\n if (this.strategy === 'file' && this.lockPath) {\n try {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId === this.instanceId) {\n await fs.promises.unlink(this.lockPath).catch(() => {});\n }\n } catch {\n // Lock file missing or unreadable — nothing to release.\n }\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-delete: only DEL if the stored lock still belongs to us.\n await this.redisClient.eval(\n LeaderElection.REDIS_RELEASE_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId\n );\n }\n } catch (error) {\n this.logger.error('Failed to release lock during step down', this.loggerContext, { error });\n }\n\n this.emit('leader:stepdown', { instanceId: this.instanceId });\n }\n\n /**\n * Start heartbeat to maintain leadership\n */\n private startHeartbeat(): void {\n if (this.heartbeatTimer) {\n return;\n }\n\n this.heartbeatTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n try {\n await this.sendHeartbeat();\n } catch (error) {\n this.logger.error('Heartbeat failed', this.loggerContext, { error });\n // Lost leadership\n await this.stepDown();\n // Try to reacquire\n const retryTimer = setTimeout(() => {\n void this.tryAcquireLeadership();\n }, 1000);\n retryTimer.unref(); // Don't keep process alive\n }\n })();\n }, this.heartbeatInterval);\n\n // Don't keep process alive\n this.heartbeatTimer.unref();\n }\n\n /**\n * Stop heartbeat\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n }\n }\n\n /**\n * Send heartbeat to maintain lock\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.leaderInfo) {\n return;\n }\n\n this.leaderInfo.lastHeartbeat = new Date();\n\n if (this.strategy === 'file' && this.lockPath) {\n // Verify we still own the lock before refreshing it. If the file is gone or\n // now owned by another instance, we have lost leadership — throw so the\n // heartbeat handler steps us down and attempts a clean re-acquire.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId !== this.instanceId) {\n throw new Error('Lost leadership: file lock is owned by another instance');\n }\n await this.atomicWriteLock(JSON.stringify(this.leaderInfo));\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-refresh: only rewrite + extend the TTL if we still own the lock.\n const refreshed = await this.redisClient.eval(\n LeaderElection.REDIS_REFRESH_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId,\n JSON.stringify(this.leaderInfo),\n this.lockTimeout\n );\n if (Number(refreshed) !== 1) {\n throw new Error('Lost leadership: redis lock is no longer owned by this instance');\n }\n }\n\n this.logger.debug('Heartbeat sent', this.loggerContext, { instanceId: this.instanceId });\n this.emit('leader:heartbeat', {\n instanceId: this.instanceId,\n timestamp: this.leaderInfo.lastHeartbeat,\n });\n }\n\n /**\n * Start periodic check for leadership changes\n */\n private startPeriodicCheck(): void {\n if (this.checkTimer) {\n return;\n }\n\n const checkInterval = Math.floor(this.heartbeatInterval * 1.5);\n\n this.checkTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n if (!this.isLeader) {\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n } else {\n // Verify we still hold the lock\n await this.verifyLeadership();\n }\n })();\n }, checkInterval);\n\n // Don't keep process alive\n this.checkTimer.unref();\n }\n\n /**\n * Stop periodic check\n */\n private stopPeriodicCheck(): void {\n if (this.checkTimer) {\n clearInterval(this.checkTimer);\n this.checkTimer = undefined;\n }\n }\n\n /**\n * Verify we still hold leadership\n */\n private async verifyLeadership(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n try {\n let stillLeader = false;\n\n if (this.strategy === 'file' && this.lockPath) {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n } else if (this.strategy === 'redis' && this.redisClient) {\n const lockData = await this.redisClient.get(LeaderElection.REDIS_LOCK_KEY);\n if (lockData) {\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n }\n }\n\n if (!stillLeader) {\n this.logger.warn('Lost leadership', this.loggerContext, { instanceId: this.instanceId });\n await this.stepDown();\n }\n } catch (error) {\n this.logger.error('Failed to verify leadership', this.loggerContext, { error });\n await this.stepDown();\n }\n }\n\n /**\n * Check health of current leader\n */\n private async checkLeaderHealth(): Promise<void> {\n if (!this.leaderInfo || this.leaderInfo.instanceId === this.instanceId) {\n return;\n }\n\n const heartbeatAge = Date.now() - new Date(this.leaderInfo.lastHeartbeat).getTime();\n\n if (heartbeatAge > this.lockTimeout) {\n this.logger.warn('Current leader appears unhealthy', this.loggerContext, {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n\n this.emit('leader:unhealthy', {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n }\n }\n\n /**\n * Get instance metadata\n */\n private getInstanceMetadata(): Record<string, any> {\n const metadata: Record<string, any> = {\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptime: process.uptime(),\n };\n\n // K8s detection\n if (process.env.KUBERNETES_SERVICE_HOST) {\n metadata.kubernetes = {\n podName: process.env.HOSTNAME || os.hostname(),\n namespace: process.env.POD_NAMESPACE,\n nodeName: process.env.NODE_NAME,\n };\n }\n\n // Container detection\n if (process.env.CONTAINER) {\n metadata.container = process.env.CONTAINER;\n }\n\n return metadata;\n }\n\n /**\n * Check if current instance is leader\n */\n public isCurrentLeader(): boolean {\n return this.isLeader;\n }\n\n /**\n * Get current leader info\n */\n public getLeaderInfo(): LeaderInfo | undefined {\n return this.leaderInfo ? { ...this.leaderInfo } : undefined;\n }\n\n /**\n * Get instance ID\n */\n public getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Force step down (for testing/admin)\n */\n public async forceStepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.warn('Forced step down requested', this.loggerContext, {\n instanceId: this.instanceId,\n });\n await this.stepDown();\n }\n\n /**\n * Shutdown and cleanup\n */\n public async shutdown(): Promise<void> {\n this.logger.info('LeaderElection shutting down...', this.loggerContext, {\n instanceId: this.instanceId,\n wasLeader: this.isLeader,\n });\n\n this.isShuttingDown = true;\n\n this.stopHeartbeat();\n this.stopPeriodicCheck();\n\n if (this.isLeader) {\n await this.stepDown();\n }\n\n this.removeAllListeners();\n this.logger.info('LeaderElection shutdown complete');\n }\n}\n"]}
1
+ {"version":3,"file":"leader-election.js","sourceRoot":"","sources":["../../../src/core/jobs/leader-election.ts"],"names":[],"mappings":"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","sourcesContent":["// Production-grade Leader Election for Distributed Job Scheduling\n// Supports file-based and Redis-based locking for K8s and clustered environments\n\nimport { EventEmitter } from 'events';\nimport crypto from 'crypto';\nimport { Logger } from '../../types/logger.js';\nimport fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { requireBuiltin } from '../utilities/builtin.js';\n\n// worker_threads on first use (core/utilities/builtin.ts). node:cluster is\n// never loaded here: a cluster worker is exactly a process with\n// NODE_UNIQUE_ID in its environment (that is how node:cluster itself decides\n// isWorker), and asking cluster would pull child_process/dgram/net (~7 MB)\n// into every app that starts the job scheduler.\nconst wt = () => requireBuiltin<typeof import('worker_threads')>('worker_threads');\nconst isClusterWorkerProcess = () => process.env.NODE_UNIQUE_ID !== undefined;\n\nexport interface LeaderElectionOptions {\n strategy: 'file' | 'redis' | 'none';\n lockPath?: string;\n lockTimeout?: number;\n heartbeatInterval?: number;\n redisClient?: any; // Optional Redis client\n forceLeader?: boolean; // Force this instance to be leader (dev/testing)\n instanceId?: string; // Custom instance ID\n}\n\nexport interface LeaderInfo {\n instanceId: string;\n hostname: string;\n pid: number;\n electedAt: Date;\n lastHeartbeat: Date;\n metadata?: Record<string, any>;\n}\n\n/**\n * LeaderElection - Ensures only one job scheduler runs across distributed instances\n * Supports:\n * - File-based locking (for local/NFS environments)\n * - Redis-based locking (for distributed systems)\n * - Kubernetes pod detection\n * - Automatic failover with health checks\n */\nexport class LeaderElection extends EventEmitter {\n private isLeader = false;\n private strategy: 'file' | 'redis' | 'none';\n private lockPath?: string;\n private lockTimeout: number;\n private heartbeatInterval: number;\n private heartbeatTimer?: NodeJS.Timeout | undefined;\n private checkTimer?: NodeJS.Timeout | undefined;\n private logger: Logger;\n private loggerContext = 'LeaderElection';\n private instanceId: string;\n private leaderInfo?: LeaderInfo;\n private redisClient?: any;\n private isShuttingDown = false;\n private lockAcquireAttempts = 0;\n\n private static readonly REDIS_LOCK_KEY = 'moro:jobs:leader:lock';\n\n // Compare-and-refresh: rewrite the value and extend the TTL only if the stored\n // lock still belongs to this instance (ownership is fenced by instanceId).\n private static readonly REDIS_REFRESH_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n return 1\nend\nreturn 0`;\n\n // Compare-and-delete: release the lock only if it still belongs to this instance.\n private static readonly REDIS_RELEASE_SCRIPT = `\nlocal cur = redis.call('GET', KEYS[1])\nif not cur then return 0 end\nlocal ok, decoded = pcall(cjson.decode, cur)\nif ok and decoded['instanceId'] == ARGV[1] then\n return redis.call('DEL', KEYS[1])\nend\nreturn 0`;\n\n constructor(logger: Logger, options: LeaderElectionOptions = { strategy: 'file' }) {\n super();\n this.logger = logger;\n this.strategy = options.strategy;\n this.lockTimeout = options.lockTimeout ?? 30000; // 30s default\n this.heartbeatInterval = options.heartbeatInterval ?? 10000; // 10s default\n this.redisClient = options.redisClient;\n\n // Generate unique instance ID\n this.instanceId =\n options.instanceId ||\n `${os.hostname()}-${process.pid}-${Date.now()}-${crypto.randomBytes(6).toString('hex')}`;\n\n // Setup lock path for file-based strategy\n if (this.strategy === 'file') {\n if (options.lockPath) {\n this.lockPath = options.lockPath;\n } else {\n const tmpDir = os.tmpdir();\n this.lockPath = path.join(tmpDir, 'moro-jobs-leader.lock');\n }\n }\n\n // Force leader mode (for dev/testing)\n if (options.forceLeader) {\n this.isLeader = true;\n this.logger.warn('Leader election forced - running as leader');\n }\n\n this.logger.debug('LeaderElection initialized', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n lockPath: this.lockPath,\n forceLeader: options.forceLeader,\n });\n }\n\n /**\n * Start leader election process\n */\n public async start(): Promise<void> {\n if (this.strategy === 'none') {\n this.logger.debug('Leader election disabled, assuming leader role');\n this.becomeLeader();\n return;\n }\n\n // Check if we should participate in leader election\n if (!this.shouldParticipate()) {\n this.logger.info('Not participating in leader election (worker process/thread)');\n return;\n }\n\n this.logger.info('Starting leader election...', this.loggerContext, {\n strategy: this.strategy,\n instanceId: this.instanceId,\n });\n\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n\n // Start periodic checks\n this.startPeriodicCheck();\n }\n\n /**\n * Check if this process should participate in leader election\n */\n private shouldParticipate(): boolean {\n // Only the main thread of a non-worker process can be leader. Under\n // thread clustering that is exactly the cluster primary (its worker\n // threads are !isMainThread); under process clustering it is the primary\n // process (workers have cluster.isWorker). Either way the scheduler runs\n // in the primary (Moro.startPrimaryServices), so one participant per\n // cluster - no change needed for threads.\n if (!wt().isMainThread) {\n return false;\n }\n\n if (isClusterWorkerProcess()) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Try to acquire leadership\n */\n private async tryAcquireLeadership(): Promise<boolean> {\n if (this.isShuttingDown) {\n return false;\n }\n\n this.lockAcquireAttempts++;\n\n try {\n let acquired = false;\n\n if (this.strategy === 'file') {\n acquired = await this.tryAcquireFileLock();\n } else if (this.strategy === 'redis') {\n acquired = await this.tryAcquireRedisLock();\n }\n\n if (acquired) {\n this.becomeLeader();\n return true;\n } else {\n // Check if current leader is still alive\n await this.checkLeaderHealth();\n return false;\n }\n } catch (error) {\n this.logger.error('Error during leader election', this.loggerContext, {\n error,\n attempt: this.lockAcquireAttempts,\n });\n return false;\n }\n }\n\n /**\n * Try to acquire file-based lock\n *\n * Uses O_EXCL (open flag 'wx') for the common \"no lock yet\" case so exactly one\n * instance can win the create race. An expired lock is taken over via an atomic\n * temp-write + rename. A takeover race between two instances is a last-writer-wins\n * rename, which is then reconciled by the periodic verifyLeadership() check and the\n * ownership-checked heartbeat below, so a single leader is still guaranteed.\n */\n private async tryAcquireFileLock(): Promise<boolean> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n const serialized = JSON.stringify(lockInfo);\n\n // Fast path: atomically create the lock file (O_EXCL). Fails with EEXIST if\n // another instance already holds the lock.\n try {\n const handle = await fs.promises.open(this.lockPath, 'wx');\n try {\n await handle.writeFile(serialized, 'utf-8');\n } finally {\n await handle.close();\n }\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code !== 'EEXIST') {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n // Lock file already exists — fall through to the expiry/takeover check.\n }\n\n try {\n // Read existing lock and decide whether we may take it over.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const existingLeader: LeaderInfo = JSON.parse(lockData);\n\n const lockAge = Date.now() - new Date(existingLeader.lastHeartbeat).getTime();\n\n if (lockAge < this.lockTimeout) {\n // Lock is still valid and held by another instance.\n this.leaderInfo = existingLeader;\n this.logger.debug('Leader lock held by another instance', this.loggerContext, {\n leader: existingLeader.instanceId,\n age: lockAge,\n });\n return false;\n }\n\n // Lock expired — take over with an atomic temp-write + rename.\n this.logger.warn('Leader lock expired, taking over', this.loggerContext, {\n previousLeader: existingLeader.instanceId,\n lockAge,\n });\n\n await this.atomicWriteLock(serialized);\n this.leaderInfo = lockInfo;\n return true;\n } catch (error) {\n this.logger.error('Failed to acquire file lock', this.loggerContext, {\n error,\n lockPath: this.lockPath,\n });\n return false;\n }\n }\n\n /**\n * Atomically (re)write the lock file: write to a unique temp file, then rename\n * over the target. rename() is atomic on POSIX so readers never see a partial\n * lock file.\n */\n private async atomicWriteLock(serialized: string): Promise<void> {\n if (!this.lockPath) {\n throw new Error('Lock path not configured');\n }\n\n const tempPath = `${this.lockPath}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;\n await fs.promises.writeFile(tempPath, serialized, 'utf-8');\n try {\n await fs.promises.rename(tempPath, this.lockPath);\n } catch (error) {\n await fs.promises.unlink(tempPath).catch(() => {});\n throw error;\n }\n }\n\n /**\n * Try to acquire Redis-based lock\n */\n private async tryAcquireRedisLock(): Promise<boolean> {\n if (!this.redisClient) {\n throw new Error('Redis client not configured');\n }\n\n try {\n const lockKey = LeaderElection.REDIS_LOCK_KEY;\n const lockInfo: LeaderInfo = {\n instanceId: this.instanceId,\n hostname: os.hostname(),\n pid: process.pid,\n electedAt: new Date(),\n lastHeartbeat: new Date(),\n metadata: this.getInstanceMetadata(),\n };\n\n // Try to set lock with NX (only if not exists) and PX (expiration in ms)\n const result = await this.redisClient.set(\n lockKey,\n JSON.stringify(lockInfo),\n 'PX',\n this.lockTimeout,\n 'NX'\n );\n\n if (result === 'OK') {\n this.leaderInfo = lockInfo;\n return true;\n }\n\n // Lock exists, get current leader info\n const existingLock = await this.redisClient.get(lockKey);\n if (existingLock) {\n this.leaderInfo = JSON.parse(existingLock);\n }\n\n return false;\n } catch (error) {\n this.logger.error('Failed to acquire Redis lock', this.loggerContext, { error });\n return false;\n }\n }\n\n /**\n * Become leader\n */\n private becomeLeader(): void {\n if (this.isLeader) {\n return;\n }\n\n this.isLeader = true;\n this.logger.info('Became leader', this.loggerContext, {\n instanceId: this.instanceId,\n strategy: this.strategy,\n attempts: this.lockAcquireAttempts,\n });\n\n this.emit('leader:elected', {\n instanceId: this.instanceId,\n electedAt: this.leaderInfo?.electedAt,\n });\n\n // Start heartbeat\n this.startHeartbeat();\n }\n\n /**\n * Step down as leader\n */\n private async stepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.info('Stepping down as leader', this.loggerContext, {\n instanceId: this.instanceId,\n });\n\n this.isLeader = false;\n this.stopHeartbeat();\n\n // Release lock — only if we still own it, so we never delete a lock that\n // another instance has since acquired.\n try {\n if (this.strategy === 'file' && this.lockPath) {\n try {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId === this.instanceId) {\n await fs.promises.unlink(this.lockPath).catch(() => {});\n }\n } catch {\n // Lock file missing or unreadable — nothing to release.\n }\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-delete: only DEL if the stored lock still belongs to us.\n await this.redisClient.eval(\n LeaderElection.REDIS_RELEASE_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId\n );\n }\n } catch (error) {\n this.logger.error('Failed to release lock during step down', this.loggerContext, { error });\n }\n\n this.emit('leader:stepdown', { instanceId: this.instanceId });\n }\n\n /**\n * Start heartbeat to maintain leadership\n */\n private startHeartbeat(): void {\n if (this.heartbeatTimer) {\n return;\n }\n\n this.heartbeatTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n try {\n await this.sendHeartbeat();\n } catch (error) {\n this.logger.error('Heartbeat failed', this.loggerContext, { error });\n // Lost leadership\n await this.stepDown();\n // Try to reacquire\n const retryTimer = setTimeout(() => {\n void this.tryAcquireLeadership();\n }, 1000);\n retryTimer.unref(); // Don't keep process alive\n }\n })();\n }, this.heartbeatInterval);\n\n // Don't keep process alive\n this.heartbeatTimer.unref();\n }\n\n /**\n * Stop heartbeat\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n }\n }\n\n /**\n * Send heartbeat to maintain lock\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.leaderInfo) {\n return;\n }\n\n this.leaderInfo.lastHeartbeat = new Date();\n\n if (this.strategy === 'file' && this.lockPath) {\n // Verify we still own the lock before refreshing it. If the file is gone or\n // now owned by another instance, we have lost leadership — throw so the\n // heartbeat handler steps us down and attempts a clean re-acquire.\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n if (currentLeader.instanceId !== this.instanceId) {\n throw new Error('Lost leadership: file lock is owned by another instance');\n }\n await this.atomicWriteLock(JSON.stringify(this.leaderInfo));\n } else if (this.strategy === 'redis' && this.redisClient) {\n // Compare-and-refresh: only rewrite + extend the TTL if we still own the lock.\n const refreshed = await this.redisClient.eval(\n LeaderElection.REDIS_REFRESH_SCRIPT,\n 1,\n LeaderElection.REDIS_LOCK_KEY,\n this.instanceId,\n JSON.stringify(this.leaderInfo),\n this.lockTimeout\n );\n if (Number(refreshed) !== 1) {\n throw new Error('Lost leadership: redis lock is no longer owned by this instance');\n }\n }\n\n this.logger.debug('Heartbeat sent', this.loggerContext, { instanceId: this.instanceId });\n this.emit('leader:heartbeat', {\n instanceId: this.instanceId,\n timestamp: this.leaderInfo.lastHeartbeat,\n });\n }\n\n /**\n * Start periodic check for leadership changes\n */\n private startPeriodicCheck(): void {\n if (this.checkTimer) {\n return;\n }\n\n const checkInterval = Math.floor(this.heartbeatInterval * 1.5);\n\n this.checkTimer = setInterval(() => {\n void (async () => {\n if (this.isShuttingDown) {\n return;\n }\n\n if (!this.isLeader) {\n // Try to acquire leadership\n await this.tryAcquireLeadership();\n } else {\n // Verify we still hold the lock\n await this.verifyLeadership();\n }\n })();\n }, checkInterval);\n\n // Don't keep process alive\n this.checkTimer.unref();\n }\n\n /**\n * Stop periodic check\n */\n private stopPeriodicCheck(): void {\n if (this.checkTimer) {\n clearInterval(this.checkTimer);\n this.checkTimer = undefined;\n }\n }\n\n /**\n * Verify we still hold leadership\n */\n private async verifyLeadership(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n try {\n let stillLeader = false;\n\n if (this.strategy === 'file' && this.lockPath) {\n const lockData = await fs.promises.readFile(this.lockPath, 'utf-8');\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n } else if (this.strategy === 'redis' && this.redisClient) {\n const lockData = await this.redisClient.get(LeaderElection.REDIS_LOCK_KEY);\n if (lockData) {\n const currentLeader: LeaderInfo = JSON.parse(lockData);\n stillLeader = currentLeader.instanceId === this.instanceId;\n }\n }\n\n if (!stillLeader) {\n this.logger.warn('Lost leadership', this.loggerContext, { instanceId: this.instanceId });\n await this.stepDown();\n }\n } catch (error) {\n this.logger.error('Failed to verify leadership', this.loggerContext, { error });\n await this.stepDown();\n }\n }\n\n /**\n * Check health of current leader\n */\n private async checkLeaderHealth(): Promise<void> {\n if (!this.leaderInfo || this.leaderInfo.instanceId === this.instanceId) {\n return;\n }\n\n const heartbeatAge = Date.now() - new Date(this.leaderInfo.lastHeartbeat).getTime();\n\n if (heartbeatAge > this.lockTimeout) {\n this.logger.warn('Current leader appears unhealthy', this.loggerContext, {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n\n this.emit('leader:unhealthy', {\n leader: this.leaderInfo.instanceId,\n heartbeatAge,\n });\n }\n }\n\n /**\n * Get instance metadata\n */\n private getInstanceMetadata(): Record<string, any> {\n const metadata: Record<string, any> = {\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptime: process.uptime(),\n };\n\n // K8s detection\n if (process.env.KUBERNETES_SERVICE_HOST) {\n metadata.kubernetes = {\n podName: process.env.HOSTNAME || os.hostname(),\n namespace: process.env.POD_NAMESPACE,\n nodeName: process.env.NODE_NAME,\n };\n }\n\n // Container detection\n if (process.env.CONTAINER) {\n metadata.container = process.env.CONTAINER;\n }\n\n return metadata;\n }\n\n /**\n * Check if current instance is leader\n */\n public isCurrentLeader(): boolean {\n return this.isLeader;\n }\n\n /**\n * Get current leader info\n */\n public getLeaderInfo(): LeaderInfo | undefined {\n return this.leaderInfo ? { ...this.leaderInfo } : undefined;\n }\n\n /**\n * Get instance ID\n */\n public getInstanceId(): string {\n return this.instanceId;\n }\n\n /**\n * Force step down (for testing/admin)\n */\n public async forceStepDown(): Promise<void> {\n if (!this.isLeader) {\n return;\n }\n\n this.logger.warn('Forced step down requested', this.loggerContext, {\n instanceId: this.instanceId,\n });\n await this.stepDown();\n }\n\n /**\n * Shutdown and cleanup\n */\n public async shutdown(): Promise<void> {\n this.logger.info('LeaderElection shutting down...', this.loggerContext, {\n instanceId: this.instanceId,\n wasLeader: this.isLeader,\n });\n\n this.isShuttingDown = true;\n\n this.stopHeartbeat();\n this.stopPeriodicCheck();\n\n if (this.isLeader) {\n await this.stepDown();\n }\n\n this.removeAllListeners();\n this.logger.info('LeaderElection shutdown complete');\n }\n}\n"]}
@@ -1,6 +1,8 @@
1
1
  // WebSocket Manager for Moro Framework
2
2
  // Manages WebSocket connections using pluggable adapters
3
- import * as zlib from 'zlib';
3
+ import { requireBuiltin } from '../utilities/builtin.js';
4
+ // zlib on first use (core/utilities/builtin.ts).
5
+ const zlibMod = () => requireBuiltin('zlib');
4
6
  import { CircuitBreaker } from '../utilities/index.js';
5
7
  import { createFrameworkLogger } from '../logger/index.js';
6
8
  import { normalizeValidationError } from '../validation/schema-interface.js';
@@ -234,7 +236,7 @@ export class WebSocketManager {
234
236
  if (buffer[0] === 0x1f && buffer[1] === 0x8b) {
235
237
  // This is gzipped data
236
238
  try {
237
- return zlib.gunzipSync(buffer);
239
+ return zlibMod().gunzipSync(buffer);
238
240
  }
239
241
  catch {
240
242
  return buffer;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-manager.js","sourceRoot":"","sources":["../../../src/core/networking/websocket-manager.ts"],"names":[],"mappings":"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","sourcesContent":["// WebSocket Manager for Moro Framework\n// Manages WebSocket connections using pluggable adapters\nimport * as zlib from 'zlib';\nimport { Container } from '../utilities/index.js';\nimport { CircuitBreaker } from '../utilities/index.js';\nimport { ModuleConfig, WebSocketDefinition } from '../../types/module.js';\nimport { WebSocketAdapter, WebSocketNamespace, WebSocketConnection } from './websocket-adapter.js';\nimport { createFrameworkLogger } from '../logger/index.js';\nimport { normalizeValidationError } from '../validation/schema-interface.js';\n\nexport class WebSocketManager {\n private circuitBreakers = new Map<string, CircuitBreaker>();\n private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();\n private logger = createFrameworkLogger('WebSocketManager');\n\n constructor(\n private adapter: WebSocketAdapter,\n private container: Container\n ) {\n this.logger.info(`Initialized with ${adapter.getAdapterName()} adapter`, 'AdapterInit');\n }\n\n /**\n * Set custom ID generator for the adapter\n */\n setCustomIdGenerator(generator: () => string): void {\n this.adapter.setCustomIdGenerator(generator);\n }\n\n /**\n * Enable compression for the adapter\n */\n enableCompression(options?: any): void {\n this.adapter.setCompression(true, options);\n }\n\n /**\n * Get the underlying adapter\n */\n getAdapter(): WebSocketAdapter {\n return this.adapter;\n }\n\n /**\n * Get connection count across all namespaces\n */\n getConnectionCount(): number {\n return this.adapter.getConnectionCount();\n }\n\n /**\n * Create or get a namespace\n */\n getNamespace(namespace: string): WebSocketNamespace {\n return this.adapter.createNamespace(namespace);\n }\n\n /**\n * Register WebSocket handlers for a module\n */\n async registerHandler(\n namespace: WebSocketNamespace,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): Promise<void> {\n namespace.on('connection', (socket: WebSocketConnection) => {\n this.logger.debug(`New connection: ${socket.id}`, 'Connection', {\n namespace: namespace.constructor.name,\n module: moduleConfig.name,\n });\n\n this.setupSocketMiddleware(socket, moduleConfig.name);\n this.setupSocketHandlers(socket, wsConfig, moduleConfig);\n });\n }\n\n /**\n * Setup socket-specific middleware\n */\n private setupSocketMiddleware(socket: WebSocketConnection, moduleName: string): void {\n // Add module context to socket data\n socket.data.module = moduleName;\n socket.data.connectedAt = Date.now();\n\n // Setup heartbeat if supported\n if (socket.compressedEmit) {\n const heartbeatInterval = setInterval(() => {\n if (socket.connected) {\n socket.emit('heartbeat', { timestamp: Date.now() });\n } else {\n clearInterval(heartbeatInterval);\n }\n }, 30000); // 30 seconds\n\n socket.on('disconnect', () => {\n clearInterval(heartbeatInterval);\n });\n }\n\n // Log disconnection\n socket.on('disconnect', () => {\n this.logger.debug(`Socket disconnected: ${socket.id}`, 'Disconnect', {\n module: moduleName,\n duration: Date.now() - socket.data.connectedAt,\n });\n });\n }\n\n /**\n * Setup handlers for a specific WebSocket configuration\n */\n private setupSocketHandlers(\n socket: WebSocketConnection,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): void {\n socket.on(wsConfig.event, (data: any, callback?: CallableFunction) => {\n void (async () => {\n const handlerKey = `${moduleConfig.name}.${wsConfig.handler}`;\n\n try {\n // Rate limiting\n if (\n wsConfig.rateLimit &&\n !this.checkRateLimit(socket.id, handlerKey, wsConfig.rateLimit)\n ) {\n const error = {\n success: false,\n error: 'Rate limit exceeded',\n code: 'RATE_LIMIT',\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n\n // Universal validation (works with any ValidationSchema)\n if (wsConfig.validation) {\n try {\n data = await wsConfig.validation.parseAsync(data);\n } catch (validationError: any) {\n // Handle universal validation errors\n const normalizedError = normalizeValidationError(validationError);\n const error = {\n success: false,\n error: 'Validation failed',\n details: normalizedError.issues.map((issue: any) => ({\n field: issue.path.length > 0 ? issue.path.join('.') : 'data',\n message: issue.message,\n code: issue.code,\n })),\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n }\n\n // Circuit breaker protection\n const circuitBreaker = this.getCircuitBreaker(handlerKey);\n\n const result = await circuitBreaker.execute(async () => {\n const controller = this.container.resolve(moduleConfig.name);\n return await (controller as any)[wsConfig.handler](socket, data);\n });\n\n // Handle response\n if (callback) {\n callback({ success: true, data: result });\n } else if (result !== undefined) {\n socket.emit(`${wsConfig.event}:response`, { success: true, data: result });\n }\n\n // Handle room operations\n if (wsConfig.rooms) {\n wsConfig.rooms.forEach(room => socket.join(room));\n }\n\n // Handle broadcasting\n if (wsConfig.broadcast && result !== undefined) {\n socket.broadcast.emit(wsConfig.event, { success: true, data: result });\n }\n } catch (error) {\n this.logger.error('WebSocket handler error', 'HandlerError', {\n handler: handlerKey,\n error: error instanceof Error ? error.message : String(error),\n socketId: socket.id,\n });\n\n const errorResponse = {\n success: false,\n error: 'Internal server error',\n code: 'HANDLER_ERROR',\n };\n\n if (callback) callback(errorResponse);\n else socket.emit('error', errorResponse);\n }\n })();\n });\n }\n\n /**\n * Get or create circuit breaker for handler\n */\n private getCircuitBreaker(handlerKey: string): CircuitBreaker {\n if (!this.circuitBreakers.has(handlerKey)) {\n const circuitBreaker = new CircuitBreaker({\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n });\n this.circuitBreakers.set(handlerKey, circuitBreaker);\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.circuitBreakers.get(handlerKey)!;\n }\n\n /**\n * Check rate limit for socket and handler\n */\n private checkRateLimit(\n socketId: string,\n handlerKey: string,\n rateLimit: { requests: number; window: number }\n ): boolean {\n if (!this.rateLimiters.has(handlerKey)) {\n this.rateLimiters.set(handlerKey, new Map());\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const handlerLimiter = this.rateLimiters.get(handlerKey)!;\n const now = Date.now();\n const windowStart = now - rateLimit.window;\n\n // Clean old entries\n for (const [id, data] of handlerLimiter.entries()) {\n if (data.resetTime < windowStart) {\n handlerLimiter.delete(id);\n }\n }\n\n // Check current socket\n const socketData = handlerLimiter.get(socketId);\n if (!socketData) {\n handlerLimiter.set(socketId, { count: 1, resetTime: now });\n return true;\n }\n\n if (socketData.resetTime < windowStart) {\n socketData.count = 1;\n socketData.resetTime = now;\n return true;\n }\n\n if (socketData.count >= rateLimit.requests) {\n return false;\n }\n\n socketData.count++;\n return true;\n }\n\n /**\n * Process binary data efficiently\n */\n private processBinaryData(buffer: Buffer): any {\n // Example binary processing - can be extended based on needs\n if (buffer.length === 0) return buffer;\n\n // Simple compression check\n if (buffer[0] === 0x1f && buffer[1] === 0x8b) {\n // This is gzipped data\n try {\n return zlib.gunzipSync(buffer);\n } catch {\n return buffer;\n }\n }\n\n return buffer;\n }\n\n /**\n * Check if data should be compressed\n */\n private shouldCompress(data: any): boolean {\n if (typeof data === 'string') {\n return data.length > 1024;\n }\n if (Buffer.isBuffer(data)) {\n return data.length > 1024;\n }\n if (typeof data === 'object') {\n return JSON.stringify(data).length > 1024;\n }\n return false;\n }\n\n /**\n * Close the WebSocket manager and underlying adapter\n */\n async close(): Promise<void> {\n this.logger.info('Closing WebSocket manager', 'Shutdown');\n\n // Clear rate limiters\n this.rateLimiters.clear();\n\n // Clear circuit breakers\n this.circuitBreakers.clear();\n\n // Close adapter\n await this.adapter.close();\n }\n}\n"]}
1
+ {"version":3,"file":"websocket-manager.js","sourceRoot":"","sources":["../../../src/core/networking/websocket-manager.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,yDAAyD;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,iDAAiD;AACjD,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,cAAc,CAAwB,MAAM,CAAC,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,MAAM,OAAO,gBAAgB;IAMjB;IACA;IANF,eAAe,GAAG,IAAI,GAAG,EAA0B,CAAC;IACpD,YAAY,GAAG,IAAI,GAAG,EAA6D,CAAC;IACpF,MAAM,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;IAE3D,YACU,OAAyB,EACzB,SAAoB;QADpB,YAAO,GAAP,OAAO,CAAkB;QACzB,cAAS,GAAT,SAAS,CAAW;QAE5B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IAC1F,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,SAAuB;QAC1C,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,OAAa;QAC7B,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,kBAAkB;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,SAAiB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CACnB,SAA6B,EAC7B,QAA6B,EAC7B,YAA0B;QAE1B,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAA2B,EAAE,EAAE;YACzD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE;gBAC9D,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,IAAI;gBACrC,MAAM,EAAE,YAAY,CAAC,IAAI;aAC1B,CAAC,CAAC;YAEH,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;YACtD,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,MAA2B,EAAE,UAAkB;QAC3E,oCAAoC;QACpC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAErC,+BAA+B;QAC/B,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YAC1B,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;gBACzC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;oBACrB,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACtD,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,iBAAiB,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa;YAExB,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;gBAC3B,aAAa,CAAC,iBAAiB,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;QACL,CAAC;QAED,oBAAoB;QACpB,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE;gBACnE,MAAM,EAAE,UAAU;gBAClB,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW;aAC/C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,mBAAmB,CACzB,MAA2B,EAC3B,QAA6B,EAC7B,YAA0B;QAE1B,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAS,EAAE,QAA2B,EAAE,EAAE;YACnE,KAAK,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,UAAU,GAAG,GAAG,YAAY,CAAC,IAAI,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBAE9D,IAAI,CAAC;oBACH,gBAAgB;oBAChB,IACE,QAAQ,CAAC,SAAS;wBAClB,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC/D,CAAC;wBACD,MAAM,KAAK,GAAG;4BACZ,OAAO,EAAE,KAAK;4BACd,KAAK,EAAE,qBAAqB;4BAC5B,IAAI,EAAE,YAAY;yBACnB,CAAC;wBACF,IAAI,QAAQ;4BAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;;4BACzB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;wBACjC,OAAO;oBACT,CAAC;oBAED,yDAAyD;oBACzD,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;wBACxB,IAAI,CAAC;4BACH,IAAI,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;wBACpD,CAAC;wBAAC,OAAO,eAAoB,EAAE,CAAC;4BAC9B,qCAAqC;4BACrC,MAAM,eAAe,GAAG,wBAAwB,CAAC,eAAe,CAAC,CAAC;4BAClE,MAAM,KAAK,GAAG;gCACZ,OAAO,EAAE,KAAK;gCACd,KAAK,EAAE,mBAAmB;gCAC1B,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,CAAC;oCACnD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM;oCAC5D,OAAO,EAAE,KAAK,CAAC,OAAO;oCACtB,IAAI,EAAE,KAAK,CAAC,IAAI;iCACjB,CAAC,CAAC;6BACJ,CAAC;4BACF,IAAI,QAAQ;gCAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;;gCACzB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;4BACjC,OAAO;wBACT,CAAC;oBACH,CAAC;oBAED,6BAA6B;oBAC7B,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;oBAE1D,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;wBACrD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;wBAC7D,OAAO,MAAO,UAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBACnE,CAAC,CAAC,CAAC;oBAEH,kBAAkB;oBAClB,IAAI,QAAQ,EAAE,CAAC;wBACb,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;oBAC5C,CAAC;yBAAM,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;wBAChC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;oBAC7E,CAAC;oBAED,yBAAyB;oBACzB,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;wBACnB,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;oBACpD,CAAC;oBAED,sBAAsB;oBACtB,IAAI,QAAQ,CAAC,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;wBAC/C,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;oBACzE,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,cAAc,EAAE;wBAC3D,OAAO,EAAE,UAAU;wBACnB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;wBAC7D,QAAQ,EAAE,MAAM,CAAC,EAAE;qBACpB,CAAC,CAAC;oBAEH,MAAM,aAAa,GAAG;wBACpB,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,uBAAuB;wBAC9B,IAAI,EAAE,eAAe;qBACtB,CAAC;oBAEF,IAAI,QAAQ;wBAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;;wBACjC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;gBAC3C,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,UAAkB;QAC1C,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1C,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,gBAAgB,EAAE,CAAC;gBACnB,YAAY,EAAE,KAAK;gBACnB,gBAAgB,EAAE,KAAK;aACxB,CAAC,CAAC;YACH,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACvD,CAAC;QACD,oEAAoE;QACpE,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;IAC/C,CAAC;IAED;;OAEG;IACK,cAAc,CACpB,QAAgB,EAChB,UAAkB,EAClB,SAA+C;QAE/C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,oEAAoE;QACpE,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC;QAE3C,oBAAoB;QACpB,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;YAClD,IAAI,IAAI,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;gBACjC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,uBAAuB;QACvB,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,UAAU,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;YACvC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC;YACrB,UAAU,CAAC,SAAS,GAAG,GAAG,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,UAAU,CAAC,KAAK,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,MAAc;QACtC,6DAA6D;QAC7D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAEvC,2BAA2B;QAC3B,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC7C,uBAAuB;YACvB,IAAI,CAAC;gBACH,OAAO,OAAO,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,IAAS;QAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAC5B,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAC5B,CAAC;QACD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE,UAAU,CAAC,CAAC;QAE1D,sBAAsB;QACtB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAE1B,yBAAyB;QACzB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,gBAAgB;QAChB,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF","sourcesContent":["// WebSocket Manager for Moro Framework\n// Manages WebSocket connections using pluggable adapters\nimport { requireBuiltin } from '../utilities/builtin.js';\n\n// zlib on first use (core/utilities/builtin.ts).\nconst zlibMod = () => requireBuiltin<typeof import('zlib')>('zlib');\nimport { Container } from '../utilities/index.js';\nimport { CircuitBreaker } from '../utilities/index.js';\nimport { ModuleConfig, WebSocketDefinition } from '../../types/module.js';\nimport { WebSocketAdapter, WebSocketNamespace, WebSocketConnection } from './websocket-adapter.js';\nimport { createFrameworkLogger } from '../logger/index.js';\nimport { normalizeValidationError } from '../validation/schema-interface.js';\n\nexport class WebSocketManager {\n private circuitBreakers = new Map<string, CircuitBreaker>();\n private rateLimiters = new Map<string, Map<string, { count: number; resetTime: number }>>();\n private logger = createFrameworkLogger('WebSocketManager');\n\n constructor(\n private adapter: WebSocketAdapter,\n private container: Container\n ) {\n this.logger.info(`Initialized with ${adapter.getAdapterName()} adapter`, 'AdapterInit');\n }\n\n /**\n * Set custom ID generator for the adapter\n */\n setCustomIdGenerator(generator: () => string): void {\n this.adapter.setCustomIdGenerator(generator);\n }\n\n /**\n * Enable compression for the adapter\n */\n enableCompression(options?: any): void {\n this.adapter.setCompression(true, options);\n }\n\n /**\n * Get the underlying adapter\n */\n getAdapter(): WebSocketAdapter {\n return this.adapter;\n }\n\n /**\n * Get connection count across all namespaces\n */\n getConnectionCount(): number {\n return this.adapter.getConnectionCount();\n }\n\n /**\n * Create or get a namespace\n */\n getNamespace(namespace: string): WebSocketNamespace {\n return this.adapter.createNamespace(namespace);\n }\n\n /**\n * Register WebSocket handlers for a module\n */\n async registerHandler(\n namespace: WebSocketNamespace,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): Promise<void> {\n namespace.on('connection', (socket: WebSocketConnection) => {\n this.logger.debug(`New connection: ${socket.id}`, 'Connection', {\n namespace: namespace.constructor.name,\n module: moduleConfig.name,\n });\n\n this.setupSocketMiddleware(socket, moduleConfig.name);\n this.setupSocketHandlers(socket, wsConfig, moduleConfig);\n });\n }\n\n /**\n * Setup socket-specific middleware\n */\n private setupSocketMiddleware(socket: WebSocketConnection, moduleName: string): void {\n // Add module context to socket data\n socket.data.module = moduleName;\n socket.data.connectedAt = Date.now();\n\n // Setup heartbeat if supported\n if (socket.compressedEmit) {\n const heartbeatInterval = setInterval(() => {\n if (socket.connected) {\n socket.emit('heartbeat', { timestamp: Date.now() });\n } else {\n clearInterval(heartbeatInterval);\n }\n }, 30000); // 30 seconds\n\n socket.on('disconnect', () => {\n clearInterval(heartbeatInterval);\n });\n }\n\n // Log disconnection\n socket.on('disconnect', () => {\n this.logger.debug(`Socket disconnected: ${socket.id}`, 'Disconnect', {\n module: moduleName,\n duration: Date.now() - socket.data.connectedAt,\n });\n });\n }\n\n /**\n * Setup handlers for a specific WebSocket configuration\n */\n private setupSocketHandlers(\n socket: WebSocketConnection,\n wsConfig: WebSocketDefinition,\n moduleConfig: ModuleConfig\n ): void {\n socket.on(wsConfig.event, (data: any, callback?: CallableFunction) => {\n void (async () => {\n const handlerKey = `${moduleConfig.name}.${wsConfig.handler}`;\n\n try {\n // Rate limiting\n if (\n wsConfig.rateLimit &&\n !this.checkRateLimit(socket.id, handlerKey, wsConfig.rateLimit)\n ) {\n const error = {\n success: false,\n error: 'Rate limit exceeded',\n code: 'RATE_LIMIT',\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n\n // Universal validation (works with any ValidationSchema)\n if (wsConfig.validation) {\n try {\n data = await wsConfig.validation.parseAsync(data);\n } catch (validationError: any) {\n // Handle universal validation errors\n const normalizedError = normalizeValidationError(validationError);\n const error = {\n success: false,\n error: 'Validation failed',\n details: normalizedError.issues.map((issue: any) => ({\n field: issue.path.length > 0 ? issue.path.join('.') : 'data',\n message: issue.message,\n code: issue.code,\n })),\n };\n if (callback) callback(error);\n else socket.emit('error', error);\n return;\n }\n }\n\n // Circuit breaker protection\n const circuitBreaker = this.getCircuitBreaker(handlerKey);\n\n const result = await circuitBreaker.execute(async () => {\n const controller = this.container.resolve(moduleConfig.name);\n return await (controller as any)[wsConfig.handler](socket, data);\n });\n\n // Handle response\n if (callback) {\n callback({ success: true, data: result });\n } else if (result !== undefined) {\n socket.emit(`${wsConfig.event}:response`, { success: true, data: result });\n }\n\n // Handle room operations\n if (wsConfig.rooms) {\n wsConfig.rooms.forEach(room => socket.join(room));\n }\n\n // Handle broadcasting\n if (wsConfig.broadcast && result !== undefined) {\n socket.broadcast.emit(wsConfig.event, { success: true, data: result });\n }\n } catch (error) {\n this.logger.error('WebSocket handler error', 'HandlerError', {\n handler: handlerKey,\n error: error instanceof Error ? error.message : String(error),\n socketId: socket.id,\n });\n\n const errorResponse = {\n success: false,\n error: 'Internal server error',\n code: 'HANDLER_ERROR',\n };\n\n if (callback) callback(errorResponse);\n else socket.emit('error', errorResponse);\n }\n })();\n });\n }\n\n /**\n * Get or create circuit breaker for handler\n */\n private getCircuitBreaker(handlerKey: string): CircuitBreaker {\n if (!this.circuitBreakers.has(handlerKey)) {\n const circuitBreaker = new CircuitBreaker({\n failureThreshold: 5,\n resetTimeout: 60000,\n monitoringPeriod: 10000,\n });\n this.circuitBreakers.set(handlerKey, circuitBreaker);\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n return this.circuitBreakers.get(handlerKey)!;\n }\n\n /**\n * Check rate limit for socket and handler\n */\n private checkRateLimit(\n socketId: string,\n handlerKey: string,\n rateLimit: { requests: number; window: number }\n ): boolean {\n if (!this.rateLimiters.has(handlerKey)) {\n this.rateLimiters.set(handlerKey, new Map());\n }\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const handlerLimiter = this.rateLimiters.get(handlerKey)!;\n const now = Date.now();\n const windowStart = now - rateLimit.window;\n\n // Clean old entries\n for (const [id, data] of handlerLimiter.entries()) {\n if (data.resetTime < windowStart) {\n handlerLimiter.delete(id);\n }\n }\n\n // Check current socket\n const socketData = handlerLimiter.get(socketId);\n if (!socketData) {\n handlerLimiter.set(socketId, { count: 1, resetTime: now });\n return true;\n }\n\n if (socketData.resetTime < windowStart) {\n socketData.count = 1;\n socketData.resetTime = now;\n return true;\n }\n\n if (socketData.count >= rateLimit.requests) {\n return false;\n }\n\n socketData.count++;\n return true;\n }\n\n /**\n * Process binary data efficiently\n */\n private processBinaryData(buffer: Buffer): any {\n // Example binary processing - can be extended based on needs\n if (buffer.length === 0) return buffer;\n\n // Simple compression check\n if (buffer[0] === 0x1f && buffer[1] === 0x8b) {\n // This is gzipped data\n try {\n return zlibMod().gunzipSync(buffer);\n } catch {\n return buffer;\n }\n }\n\n return buffer;\n }\n\n /**\n * Check if data should be compressed\n */\n private shouldCompress(data: any): boolean {\n if (typeof data === 'string') {\n return data.length > 1024;\n }\n if (Buffer.isBuffer(data)) {\n return data.length > 1024;\n }\n if (typeof data === 'object') {\n return JSON.stringify(data).length > 1024;\n }\n return false;\n }\n\n /**\n * Close the WebSocket manager and underlying adapter\n */\n async close(): Promise<void> {\n this.logger.info('Closing WebSocket manager', 'Shutdown');\n\n // Clear rate limiters\n this.rateLimiters.clear();\n\n // Clear circuit breakers\n this.circuitBreakers.clear();\n\n // Close adapter\n await this.adapter.close();\n }\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import { ValidationSchema } from '../validation/schema-interface.js';
2
2
  import { HttpRequest, HttpResponse } from '../http/index.js';
3
+ import { type StaticBody, type StaticResponse } from './unified-router.js';
4
+ export type { StaticBody, StaticResponse };
3
5
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
4
6
  export type RouteHandler<T = any> = (req: HttpRequest, res: HttpResponse) => T | Promise<T>;
5
7
  export type Middleware = (req: HttpRequest, res: HttpResponse, next: () => void) => void | Promise<void>;
@@ -41,6 +43,8 @@ export interface RouteSchema {
41
43
  middleware?: MiddlewarePhases;
42
44
  description?: string;
43
45
  tags?: string[];
46
+ /** Fixed reply of a literal handler; see StaticResponse */
47
+ static?: StaticResponse;
44
48
  }
45
49
  export declare const EXECUTION_PHASES: readonly ["security", "parsing", "rateLimit", "before", "auth", "validation", "transform", "cache", "after", "handler"];
46
50
  export type ExecutionPhase = (typeof EXECUTION_PHASES)[number];
@@ -66,7 +70,10 @@ export interface RouteBuilder {
66
70
  use(...middleware: Middleware[]): RouteBuilder;
67
71
  describe(description: string): RouteBuilder;
68
72
  tag(...tags: string[]): RouteBuilder;
69
- handler<T>(handler: RouteHandler<T>): void;
73
+ /** Terminal. A function handles the request; a string or Buffer IS the
74
+ * response body (as res.send would send it), and on a route with nothing
75
+ * else configured Moro's native engine answers it without calling JS. */
76
+ handler<T>(handler: RouteHandler<T> | StaticBody): void;
70
77
  }
71
78
  export interface CompiledRoute {
72
79
  schema: RouteSchema;
@@ -94,7 +101,7 @@ export declare class IntelligentRouteBuilder implements RouteBuilder {
94
101
  use(...middleware: Middleware[]): RouteBuilder;
95
102
  describe(description: string): RouteBuilder;
96
103
  tag(...tags: string[]): RouteBuilder;
97
- handler<T>(handler: RouteHandler<T>): void;
104
+ handler<T>(handler: RouteHandler<T> | StaticBody): void;
98
105
  }
99
106
  /**
100
107
  * Thin facade implementing CompiledRoute interface
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // NOTE: This file exports types and thin facades to UnifiedRouter
5
5
  // All routing logic has been consolidated in unified-router.ts
6
- import { UnifiedRouter } from './unified-router.js';
6
+ import { UnifiedRouter, compileStaticRoute, } from './unified-router.js';
7
7
  export const EXECUTION_PHASES = [
8
8
  'security',
9
9
  'parsing',
@@ -137,13 +137,18 @@ export class IntelligentRouteBuilder {
137
137
  return this;
138
138
  }
139
139
  handler(handler) {
140
- if (!handler) {
141
- throw new Error('Handler is required');
140
+ if (typeof handler === 'function') {
141
+ // Avoid spread operator - add handler directly
142
+ this.schema.handler = handler;
143
+ // Delegate to UnifiedRouter
144
+ this.router.registerRoute(this.schema);
145
+ return;
142
146
  }
143
- // Avoid spread operator - add handler directly
144
- this.schema.handler = handler;
145
- // Delegate to UnifiedRouter
146
- this.router.registerRoute(this.schema);
147
+ if (typeof handler === 'string' || Buffer.isBuffer(handler)) {
148
+ this.router.registerRoute(compileStaticRoute(this.schema, handler));
149
+ return;
150
+ }
151
+ throw new Error('Handler is required: a function, or the response body as a string or Buffer');
147
152
  }
148
153
  }
149
154
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/routing/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Intelligent Routing System for Moro Framework\n// Schema-first with automatic middleware ordering and chainable API\n//\n// NOTE: This file exports types and thin facades to UnifiedRouter\n// All routing logic has been consolidated in unified-router.ts\n\nimport { ValidationSchema } from '../validation/schema-interface.js';\nimport { HttpRequest, HttpResponse } from '../http/index.js';\nimport { UnifiedRouter } from './unified-router.js';\n\n// ===== TYPE EXPORTS (Keep all - used by app code) =====\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\nexport type RouteHandler<T = any> = (req: HttpRequest, res: HttpResponse) => T | Promise<T>;\nexport type Middleware = (\n req: HttpRequest,\n res: HttpResponse,\n next: () => void\n) => void | Promise<void>;\n\nexport interface ValidationConfig {\n body?: ValidationSchema;\n query?: ValidationSchema;\n params?: ValidationSchema;\n headers?: ValidationSchema;\n}\n\nexport interface AuthConfig {\n roles?: string[];\n permissions?: string[];\n optional?: boolean;\n}\n\nexport interface RateLimitConfig {\n requests: number;\n window: number;\n skipSuccessfulRequests?: boolean;\n skipFailedRequests?: boolean;\n}\n\nexport interface CacheConfig {\n ttl: number;\n key?: string;\n tags?: string[];\n}\n\nexport interface MiddlewarePhases {\n before?: Middleware[];\n after?: Middleware[];\n transform?: Middleware[];\n}\n\nexport interface RouteSchema {\n method: HttpMethod;\n path: string;\n handler: RouteHandler;\n validation?: ValidationConfig;\n auth?: AuthConfig;\n rateLimit?: RateLimitConfig;\n cache?: CacheConfig;\n middleware?: MiddlewarePhases;\n description?: string;\n tags?: string[];\n}\n\nexport const EXECUTION_PHASES = [\n 'security',\n 'parsing',\n 'rateLimit',\n 'before',\n 'auth',\n 'validation',\n 'transform',\n 'cache',\n 'after',\n 'handler',\n] as const;\n\nexport type ExecutionPhase = (typeof EXECUTION_PHASES)[number];\n\nexport interface ValidatedRequest<T = any> extends HttpRequest {\n validatedBody?: T;\n validatedQuery?: any;\n validatedParams?: any;\n validatedHeaders?: any;\n user?: any;\n}\n\n// ===== ROUTE BUILDER INTERFACE (Public API) =====\n\nexport interface RouteBuilder {\n validate(config: ValidationConfig): RouteBuilder;\n body<T>(schema: ValidationSchema<T>): RouteBuilder;\n query<T>(schema: ValidationSchema<T>): RouteBuilder;\n params<T>(schema: ValidationSchema<T>): RouteBuilder;\n headers<T>(schema: ValidationSchema<T>): RouteBuilder;\n auth(config: AuthConfig): RouteBuilder;\n rateLimit(config: RateLimitConfig): RouteBuilder;\n cache(config: CacheConfig): RouteBuilder;\n before(...middleware: Middleware[]): RouteBuilder;\n after(...middleware: Middleware[]): RouteBuilder;\n transform(...middleware: Middleware[]): RouteBuilder;\n use(...middleware: Middleware[]): RouteBuilder;\n describe(description: string): RouteBuilder;\n tag(...tags: string[]): RouteBuilder;\n handler<T>(handler: RouteHandler<T>): void;\n}\n\n// ===== COMPILED ROUTE INTERFACE (Public API) =====\n\nexport interface CompiledRoute {\n schema: RouteSchema;\n execute(req: HttpRequest, res: HttpResponse): Promise<void>;\n}\n\n// ===== FACADE IMPLEMENTATIONS (Delegate to UnifiedRouter) =====\n\n/**\n * Thin facade to UnifiedRouter.RouteBuilder\n * Maintains backward compatibility while using optimized implementation\n */\nexport class IntelligentRouteBuilder implements RouteBuilder {\n private schema: Partial<RouteSchema>;\n private router = UnifiedRouter.getInstance();\n\n constructor(method: HttpMethod, path: string) {\n this.schema = {\n method,\n path,\n middleware: {} as MiddlewarePhases,\n };\n }\n\n validate(config: ValidationConfig): RouteBuilder {\n // Avoid spread operator - manually merge properties\n if (!this.schema.validation) {\n this.schema.validation = config;\n } else {\n if (config.body !== undefined) this.schema.validation.body = config.body;\n if (config.query !== undefined) this.schema.validation.query = config.query;\n if (config.params !== undefined) this.schema.validation.params = config.params;\n if (config.headers !== undefined) this.schema.validation.headers = config.headers;\n }\n return this;\n }\n\n body<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.body = schema;\n return this;\n }\n\n query<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.query = schema;\n return this;\n }\n\n params<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.params = schema;\n return this;\n }\n\n headers<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.headers = schema;\n return this;\n }\n\n auth(config: AuthConfig): RouteBuilder {\n this.schema.auth = config;\n return this;\n }\n\n rateLimit(config: RateLimitConfig): RouteBuilder {\n this.schema.rateLimit = config;\n return this;\n }\n\n cache(config: CacheConfig): RouteBuilder {\n this.schema.cache = config;\n return this;\n }\n\n before(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.before) phases.before = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.before.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n after(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.after) phases.after = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.after.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n transform(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.transform) phases.transform = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.transform.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n use(...middleware: Middleware[]): RouteBuilder {\n return this.after(...middleware);\n }\n\n describe(description: string): RouteBuilder {\n this.schema.description = description;\n return this;\n }\n\n tag(...tags: string[]): RouteBuilder {\n // Avoid spread, use push for better performance\n if (!this.schema.tags) this.schema.tags = [];\n for (let i = 0; i < tags.length; i++) {\n this.schema.tags.push(tags[i] as string);\n }\n return this;\n }\n\n handler<T>(handler: RouteHandler<T>): void {\n if (!handler) {\n throw new Error('Handler is required');\n }\n\n // Avoid spread operator - add handler directly\n this.schema.handler = handler;\n\n // Delegate to UnifiedRouter\n this.router.registerRoute(this.schema as RouteSchema);\n }\n}\n\n/**\n * Thin facade implementing CompiledRoute interface\n * Just stores schema for documentation purposes\n */\nexport class ExecutableRoute implements CompiledRoute {\n constructor(public readonly schema: RouteSchema) {\n // Register with UnifiedRouter\n UnifiedRouter.getInstance().registerRoute(schema);\n }\n\n async execute(_req: HttpRequest, _res: HttpResponse): Promise<void> {\n // This is never called - UnifiedRouter handles execution\n // But we implement it for interface compatibility\n throw new Error(\n 'ExecutableRoute.execute() should not be called directly - routing handled by UnifiedRouter'\n );\n }\n}\n\n// ===== FACTORY FUNCTIONS (Backward Compatibility) =====\n\nexport function createRoute(method: HttpMethod, path: string): RouteBuilder {\n return new IntelligentRouteBuilder(method, path);\n}\n\nexport function defineRoute(schema: RouteSchema): CompiledRoute {\n return new ExecutableRoute(schema);\n}\n\n// ===== RE-EXPORTS FROM UNIFIED ROUTER =====\n\nexport { UnifiedRouter } from './unified-router.js';\nexport { RouteBuilder as UnifiedRouteBuilder } from './unified-router.js';\nexport { PathMatcher, type CompiledPath, type MatchResult } from './path-matcher.js';\nexport { ObjectPoolManager, getPoolManager } from '../pooling/object-pool-manager.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/routing/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Intelligent Routing System for Moro Framework\n// Schema-first with automatic middleware ordering and chainable API\n//\n// NOTE: This file exports types and thin facades to UnifiedRouter\n// All routing logic has been consolidated in unified-router.ts\n\nimport { ValidationSchema } from '../validation/schema-interface.js';\nimport { HttpRequest, HttpResponse } from '../http/index.js';\nimport {\n UnifiedRouter,\n compileStaticRoute,\n type RouteSchema as UnifiedRouteSchema,\n type StaticBody,\n type StaticResponse,\n} from './unified-router.js';\n\nexport type { StaticBody, StaticResponse };\n\n// ===== TYPE EXPORTS (Keep all - used by app code) =====\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\nexport type RouteHandler<T = any> = (req: HttpRequest, res: HttpResponse) => T | Promise<T>;\nexport type Middleware = (\n req: HttpRequest,\n res: HttpResponse,\n next: () => void\n) => void | Promise<void>;\n\nexport interface ValidationConfig {\n body?: ValidationSchema;\n query?: ValidationSchema;\n params?: ValidationSchema;\n headers?: ValidationSchema;\n}\n\nexport interface AuthConfig {\n roles?: string[];\n permissions?: string[];\n optional?: boolean;\n}\n\nexport interface RateLimitConfig {\n requests: number;\n window: number;\n skipSuccessfulRequests?: boolean;\n skipFailedRequests?: boolean;\n}\n\nexport interface CacheConfig {\n ttl: number;\n key?: string;\n tags?: string[];\n}\n\nexport interface MiddlewarePhases {\n before?: Middleware[];\n after?: Middleware[];\n transform?: Middleware[];\n}\n\nexport interface RouteSchema {\n method: HttpMethod;\n path: string;\n handler: RouteHandler;\n validation?: ValidationConfig;\n auth?: AuthConfig;\n rateLimit?: RateLimitConfig;\n cache?: CacheConfig;\n middleware?: MiddlewarePhases;\n description?: string;\n tags?: string[];\n /** Fixed reply of a literal handler; see StaticResponse */\n static?: StaticResponse;\n}\n\nexport const EXECUTION_PHASES = [\n 'security',\n 'parsing',\n 'rateLimit',\n 'before',\n 'auth',\n 'validation',\n 'transform',\n 'cache',\n 'after',\n 'handler',\n] as const;\n\nexport type ExecutionPhase = (typeof EXECUTION_PHASES)[number];\n\nexport interface ValidatedRequest<T = any> extends HttpRequest {\n validatedBody?: T;\n validatedQuery?: any;\n validatedParams?: any;\n validatedHeaders?: any;\n user?: any;\n}\n\n// ===== ROUTE BUILDER INTERFACE (Public API) =====\n\nexport interface RouteBuilder {\n validate(config: ValidationConfig): RouteBuilder;\n body<T>(schema: ValidationSchema<T>): RouteBuilder;\n query<T>(schema: ValidationSchema<T>): RouteBuilder;\n params<T>(schema: ValidationSchema<T>): RouteBuilder;\n headers<T>(schema: ValidationSchema<T>): RouteBuilder;\n auth(config: AuthConfig): RouteBuilder;\n rateLimit(config: RateLimitConfig): RouteBuilder;\n cache(config: CacheConfig): RouteBuilder;\n before(...middleware: Middleware[]): RouteBuilder;\n after(...middleware: Middleware[]): RouteBuilder;\n transform(...middleware: Middleware[]): RouteBuilder;\n use(...middleware: Middleware[]): RouteBuilder;\n describe(description: string): RouteBuilder;\n tag(...tags: string[]): RouteBuilder;\n /** Terminal. A function handles the request; a string or Buffer IS the\n * response body (as res.send would send it), and on a route with nothing\n * else configured Moro's native engine answers it without calling JS. */\n handler<T>(handler: RouteHandler<T> | StaticBody): void;\n}\n\n// ===== COMPILED ROUTE INTERFACE (Public API) =====\n\nexport interface CompiledRoute {\n schema: RouteSchema;\n execute(req: HttpRequest, res: HttpResponse): Promise<void>;\n}\n\n// ===== FACADE IMPLEMENTATIONS (Delegate to UnifiedRouter) =====\n\n/**\n * Thin facade to UnifiedRouter.RouteBuilder\n * Maintains backward compatibility while using optimized implementation\n */\nexport class IntelligentRouteBuilder implements RouteBuilder {\n private schema: Partial<RouteSchema>;\n private router = UnifiedRouter.getInstance();\n\n constructor(method: HttpMethod, path: string) {\n this.schema = {\n method,\n path,\n middleware: {} as MiddlewarePhases,\n };\n }\n\n validate(config: ValidationConfig): RouteBuilder {\n // Avoid spread operator - manually merge properties\n if (!this.schema.validation) {\n this.schema.validation = config;\n } else {\n if (config.body !== undefined) this.schema.validation.body = config.body;\n if (config.query !== undefined) this.schema.validation.query = config.query;\n if (config.params !== undefined) this.schema.validation.params = config.params;\n if (config.headers !== undefined) this.schema.validation.headers = config.headers;\n }\n return this;\n }\n\n body<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.body = schema;\n return this;\n }\n\n query<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.query = schema;\n return this;\n }\n\n params<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.params = schema;\n return this;\n }\n\n headers<T>(schema: ValidationSchema<T>): RouteBuilder {\n if (!this.schema.validation) this.schema.validation = {};\n this.schema.validation.headers = schema;\n return this;\n }\n\n auth(config: AuthConfig): RouteBuilder {\n this.schema.auth = config;\n return this;\n }\n\n rateLimit(config: RateLimitConfig): RouteBuilder {\n this.schema.rateLimit = config;\n return this;\n }\n\n cache(config: CacheConfig): RouteBuilder {\n this.schema.cache = config;\n return this;\n }\n\n before(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.before) phases.before = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.before.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n after(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.after) phases.after = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.after.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n transform(...middleware: Middleware[]): RouteBuilder {\n if (!this.schema.middleware) this.schema.middleware = {};\n const phases = this.schema.middleware as MiddlewarePhases;\n // Avoid spread, use push for better performance\n if (!phases.transform) phases.transform = [];\n for (let i = 0; i < middleware.length; i++) {\n phases.transform.push(middleware[i] as Middleware);\n }\n return this;\n }\n\n use(...middleware: Middleware[]): RouteBuilder {\n return this.after(...middleware);\n }\n\n describe(description: string): RouteBuilder {\n this.schema.description = description;\n return this;\n }\n\n tag(...tags: string[]): RouteBuilder {\n // Avoid spread, use push for better performance\n if (!this.schema.tags) this.schema.tags = [];\n for (let i = 0; i < tags.length; i++) {\n this.schema.tags.push(tags[i] as string);\n }\n return this;\n }\n\n handler<T>(handler: RouteHandler<T> | StaticBody): void {\n if (typeof handler === 'function') {\n // Avoid spread operator - add handler directly\n this.schema.handler = handler;\n\n // Delegate to UnifiedRouter\n this.router.registerRoute(this.schema as RouteSchema);\n return;\n }\n if (typeof handler === 'string' || Buffer.isBuffer(handler)) {\n this.router.registerRoute(\n compileStaticRoute(this.schema as Partial<UnifiedRouteSchema>, handler)\n );\n return;\n }\n throw new Error('Handler is required: a function, or the response body as a string or Buffer');\n }\n}\n\n/**\n * Thin facade implementing CompiledRoute interface\n * Just stores schema for documentation purposes\n */\nexport class ExecutableRoute implements CompiledRoute {\n constructor(public readonly schema: RouteSchema) {\n // Register with UnifiedRouter\n UnifiedRouter.getInstance().registerRoute(schema);\n }\n\n async execute(_req: HttpRequest, _res: HttpResponse): Promise<void> {\n // This is never called - UnifiedRouter handles execution\n // But we implement it for interface compatibility\n throw new Error(\n 'ExecutableRoute.execute() should not be called directly - routing handled by UnifiedRouter'\n );\n }\n}\n\n// ===== FACTORY FUNCTIONS (Backward Compatibility) =====\n\nexport function createRoute(method: HttpMethod, path: string): RouteBuilder {\n return new IntelligentRouteBuilder(method, path);\n}\n\nexport function defineRoute(schema: RouteSchema): CompiledRoute {\n return new ExecutableRoute(schema);\n}\n\n// ===== RE-EXPORTS FROM UNIFIED ROUTER =====\n\nexport { UnifiedRouter } from './unified-router.js';\nexport { RouteBuilder as UnifiedRouteBuilder } from './unified-router.js';\nexport { PathMatcher, type CompiledPath, type MatchResult } from './path-matcher.js';\nexport { ObjectPoolManager, getPoolManager } from '../pooling/object-pool-manager.js';\n"]}
@@ -28,7 +28,31 @@ export interface RouteSchema {
28
28
  middleware?: MiddlewarePhases | Middleware[];
29
29
  description?: string;
30
30
  tags?: string[];
31
+ /** Fixed reply of a literal handler. A server that can answer it
32
+ * natively (MoroEngineServer on @morojs/engine >= 1.1.6) does so without
33
+ * calling into JS; every other server runs `handler`, which sends the
34
+ * same bytes. */
35
+ static?: StaticResponse;
31
36
  }
37
+ /** The reply a literal handler sends, in the engine's shape. */
38
+ export interface StaticResponse {
39
+ status: number;
40
+ /** [name, value, name, value, ...], or null for no headers at all */
41
+ headers: string[] | null;
42
+ body: string | Buffer | null;
43
+ }
44
+ /** What .handler() accepts in place of a function: the response body itself. */
45
+ export type StaticBody = string | Buffer;
46
+ /**
47
+ * A literal body in place of a handler: the route answers with exactly that
48
+ * body, as res.send(body) would (status 200, the implied content-type). When
49
+ * nothing else is configured on the route - no auth, validation, rate limit,
50
+ * cache or middleware, and a literal path - the schema also carries `static`,
51
+ * so a server that can answer it natively does so without calling into JS.
52
+ * Anything configured keeps the route on the normal pipeline, where the
53
+ * handler sends the same bytes after that configuration has run.
54
+ */
55
+ export declare function compileStaticRoute(schema: Partial<RouteSchema>, body: StaticBody): RouteSchema;
32
56
  export declare class RouteBuilder {
33
57
  private schema;
34
58
  private router;
@@ -47,7 +71,7 @@ export declare class RouteBuilder {
47
71
  use(...middleware: Middleware[]): this;
48
72
  describe(description: string): this;
49
73
  tag(...tags: string[]): this;
50
- handler<T>(handler: RouteHandler<T>): void;
74
+ handler<T>(handler: RouteHandler<T> | StaticBody): void;
51
75
  }
52
76
  export declare class UnifiedRouter {
53
77
  private static instance;
@@ -13,6 +13,51 @@ const logger = createFrameworkLogger('UnifiedRouter');
13
13
  const rateLimitCore = new RateLimitCore();
14
14
  const cacheCore = new CacheCore();
15
15
  const validationCore = new ValidationCore();
16
+ // The content-type res.send() implies for a body it was given no header for:
17
+ // the same three cases MoroEngineServer's send() picks a template by, spelled
18
+ // out here so an engine-answered reply carries the bytes send() would have.
19
+ const STATIC_JSON_START = /^\s*[{[]/;
20
+ function impliedContentType(body) {
21
+ if (typeof body !== 'string')
22
+ return 'application/octet-stream';
23
+ return STATIC_JSON_START.test(body)
24
+ ? 'application/json; charset=utf-8'
25
+ : 'text/plain; charset=utf-8';
26
+ }
27
+ /**
28
+ * A literal body in place of a handler: the route answers with exactly that
29
+ * body, as res.send(body) would (status 200, the implied content-type). When
30
+ * nothing else is configured on the route - no auth, validation, rate limit,
31
+ * cache or middleware, and a literal path - the schema also carries `static`,
32
+ * so a server that can answer it natively does so without calling into JS.
33
+ * Anything configured keeps the route on the normal pipeline, where the
34
+ * handler sends the same bytes after that configuration has run.
35
+ */
36
+ export function compileStaticRoute(schema, body) {
37
+ schema.handler = (_req, res) => {
38
+ res.send(body);
39
+ };
40
+ const path = schema.path ?? '';
41
+ const mw = schema.middleware;
42
+ const hasMiddleware = Array.isArray(mw)
43
+ ? mw.length > 0
44
+ : mw !== undefined &&
45
+ Object.keys(mw).some(phase => {
46
+ const list = mw[phase];
47
+ return Array.isArray(list) && list.length > 0;
48
+ });
49
+ const bare = !path.includes(':') &&
50
+ !path.includes('*') &&
51
+ !schema.auth &&
52
+ !schema.validation &&
53
+ !schema.rateLimit &&
54
+ !schema.cache &&
55
+ !hasMiddleware;
56
+ if (bare) {
57
+ schema.static = { status: 200, headers: ['content-type', impliedContentType(body)], body };
58
+ }
59
+ return schema;
60
+ }
16
61
  // Canonical uppercase methods - lets dispatch skip toUpperCase for the
17
62
  // interned method strings every adapter hands over
18
63
  const UPPERCASE_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']);
@@ -143,14 +188,20 @@ export class RouteBuilder {
143
188
  }
144
189
  return this;
145
190
  }
146
- // Terminal method
191
+ // Terminal method: a function, or the response body itself (see
192
+ // compileStaticRoute for what a literal body means).
147
193
  handler(handler) {
148
- if (!handler) {
149
- throw new Error('Handler is required');
194
+ if (typeof handler === 'function') {
195
+ // Avoid spread operator - add handler directly
196
+ this.schema.handler = handler;
197
+ this.router.registerRoute(this.schema);
198
+ return;
150
199
  }
151
- // Avoid spread operator - add handler directly
152
- this.schema.handler = handler;
153
- this.router.registerRoute(this.schema);
200
+ if (typeof handler === 'string' || Buffer.isBuffer(handler)) {
201
+ this.router.registerRoute(compileStaticRoute(this.schema, handler));
202
+ return;
203
+ }
204
+ throw new Error('Handler is required: a function, or the response body as a string or Buffer');
154
205
  }
155
206
  }
156
207
  // ===== Unified Router =====
@@ -278,6 +329,18 @@ export class UnifiedRouter {
278
329
  * Register a route (internal method) - OPTIMIZED
279
330
  */
280
331
  registerRoute(schema) {
332
+ // A literal body in place of a handler (see compileStaticRoute). Every
333
+ // entry point lands here - the builder, app.get(path, body), route(schema)
334
+ // - so this is where the body becomes a handler and, on a bare route, a
335
+ // reply the native engine can answer itself.
336
+ if (typeof schema.handler !== 'function') {
337
+ const body = schema.handler;
338
+ if (typeof body !== 'string' && !Buffer.isBuffer(body)) {
339
+ throw new Error(`Handler for ${schema.method} ${schema.path} must be a function, ` +
340
+ 'or the response body as a string or Buffer');
341
+ }
342
+ compileStaticRoute(schema, body);
343
+ }
281
344
  // OPTIMIZATION: Skip PathMatcher.compile for better performance
282
345
  // Determine if static route (optimized check)
283
346
  const pathLen = schema.path.length;