@morojs/moro 1.8.9 → 1.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/cluster/thread-cluster.d.ts +178 -0
- package/dist/core/cluster/thread-cluster.js +389 -0
- package/dist/core/cluster/thread-cluster.js.map +1 -0
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +28 -1
- package/dist/core/http/moro-engine-server.js +274 -36
- package/dist/core/http/moro-engine-server.js.map +1 -1
- package/dist/core/http/utils/compression.js +12 -6
- package/dist/core/http/utils/compression.js.map +1 -1
- package/dist/core/http/utils/uws-worker-clustering.js +10 -6
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.js +3 -2
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/leader-election.js +16 -5
- package/dist/core/jobs/leader-election.js.map +1 -1
- package/dist/core/networking/websocket-manager.js +4 -2
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/runtime/node-adapter.d.ts +1 -1
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/utilities/builtin.d.ts +1 -0
- package/dist/core/utilities/builtin.js +25 -0
- package/dist/core/utilities/builtin.js.map +1 -0
- package/dist/core/utilities/package-utils.d.ts +28 -3
- package/dist/core/utilities/package-utils.js +24 -12
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/workers/worker-manager.js +4 -3
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/moro.d.ts +5 -0
- package/dist/moro.js +364 -204
- package/dist/moro.js.map +1 -1
- package/dist/types/http.d.ts +1 -1
- package/dist/types/http.js.map +1 -1
- 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
|
|
9
|
-
|
|
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
|
|
115
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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":"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","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,4 +1,4 @@
|
|
|
1
|
-
import { IncomingMessage, ServerResponse } from 'http';
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import { BaseRuntimeAdapter } from './base-adapter.js';
|
|
3
3
|
import { HttpRequest, HttpResponse } from '../../types/http.js';
|
|
4
4
|
import { RuntimeHttpResponse } from '../../types/runtime.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-adapter.js","sourceRoot":"","sources":["../../../src/core/runtime/node-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["// Node.js runtime adapter\nimport { IncomingMessage, ServerResponse } from 'http';\nimport { BaseRuntimeAdapter } from './base-adapter.js';\nimport { HttpRequest, HttpResponse } from '../../types/http.js';\nimport { RuntimeHttpResponse } from '../../types/runtime.js';\nimport { MoroHttpServer } from '../http/http-server.js';\n\nexport class NodeRuntimeAdapter extends BaseRuntimeAdapter {\n readonly type = 'node' as const;\n\n async adaptRequest(req: IncomingMessage): Promise<HttpRequest> {\n const { pathname, query } = this.parseUrl(req.url || '/');\n\n // Parse body for POST/PUT/PATCH requests\n let body: any;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (['POST', 'PUT', 'PATCH'].includes(req.method!)) {\n body = await this.parseRequestBody(req);\n }\n\n const baseRequest = {\n // Copy IncomingMessage properties we need\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n method: req.method!,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n url: req.url!,\n headers: req.headers as Record<string, string>,\n httpVersion: req.httpVersion,\n httpVersionMajor: req.httpVersionMajor,\n httpVersionMinor: req.httpVersionMinor,\n socket: req.socket,\n\n // Add MoroJS-specific properties\n path: pathname,\n query,\n body,\n ip: this.getClientIP(req),\n params: {},\n requestId: '',\n cookies: {},\n files: {},\n } as Partial<HttpRequest>;\n\n return this.enhanceRequest(baseRequest);\n }\n\n async adaptResponse(\n moroResponse: HttpResponse | RuntimeHttpResponse,\n _req: IncomingMessage\n ): Promise<ServerResponse> {\n // For Node.js, we typically work with the actual ServerResponse\n // This method is mainly for converting mock responses back to real ones\n return moroResponse as any;\n }\n\n createServer(handler: (req: HttpRequest, res: HttpResponse) => Promise<void>): MoroHttpServer {\n const httpServer = new MoroHttpServer();\n\n // Replace the default request handler with our runtime-aware handler\n const originalServer = httpServer.getServer();\n originalServer.removeAllListeners('request');\n\n originalServer.on('request', (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n try {\n const moroReq = await this.adaptRequest(req);\n const moroRes = this.enhanceResponse(res);\n\n await handler(moroReq, moroRes);\n } catch (error) {\n if (!res.headersSent) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(\n JSON.stringify({\n success: false,\n error: 'Internal server error',\n message: error instanceof Error ? error.message : 'Unknown error',\n })\n );\n }\n }\n })();\n });\n\n return httpServer;\n }\n\n listen(server: MoroHttpServer, port: number, host?: string, callback?: () => void): void {\n server.listen(port, host as any, callback);\n }\n\n // Helper methods\n private async parseRequestBody(req: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', chunk => {\n body += chunk.toString();\n });\n req.on('end', () => {\n try {\n const contentType = req.headers['content-type'] || '';\n resolve(this.parseBody(body, contentType));\n } catch (error) {\n reject(error);\n }\n });\n req.on('error', reject);\n });\n }\n\n private getClientIP(req: IncomingMessage): string {\n const forwarded = req.headers['x-forwarded-for'] as string;\n if (forwarded) {\n return forwarded.split(',')[0]?.trim() ?? 'unknown';\n }\n return req.socket.remoteAddress || 'unknown';\n }\n\n private enhanceResponse(res: ServerResponse): HttpResponse {\n const enhanced = res as any;\n\n // Add MoroJS response methods if they don't exist\n if (!enhanced.json) {\n enhanced.json = function (data: any) {\n this.setHeader('Content-Type', 'application/json');\n this.end(JSON.stringify(data));\n };\n }\n\n if (!enhanced.status) {\n enhanced.status = function (code: number) {\n this.statusCode = code;\n return this;\n };\n }\n\n if (!enhanced.send) {\n enhanced.send = function (data: string | Buffer) {\n this.end(data);\n };\n }\n\n if (!enhanced.cookie) {\n enhanced.cookie = function (name: string, value: string, _options?: any) {\n if (!this.headersSent) {\n const cookieString = `${name}=${value}`;\n this.setHeader('Set-Cookie', cookieString);\n }\n return this;\n };\n }\n\n if (!enhanced.clearCookie) {\n enhanced.clearCookie = function (name: string, _options?: any) {\n if (!this.headersSent) {\n this.setHeader('Set-Cookie', `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`);\n }\n return this;\n };\n }\n\n if (!enhanced.redirect) {\n enhanced.redirect = function (url: string, status?: number) {\n if (!this.headersSent) {\n this.statusCode = status || 302;\n this.setHeader('Location', url);\n this.end();\n }\n };\n }\n\n if (!enhanced.sendFile) {\n enhanced.sendFile = async function (filePath: string) {\n const fs = await import('fs');\n const path = await import('path');\n\n try {\n const data = await fs.promises.readFile(filePath);\n const ext = path.extname(filePath);\n\n // Basic content type detection\n const contentTypes: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n };\n\n const contentType = contentTypes[ext] || 'application/octet-stream';\n this.setHeader('Content-Type', contentType);\n this.end(data);\n } catch {\n this.statusCode = 404;\n this.end('File not found');\n }\n };\n }\n\n return enhanced;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"node-adapter.js","sourceRoot":"","sources":["../../../src/core/runtime/node-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["// Node.js runtime adapter\nimport type { IncomingMessage, ServerResponse } from 'http';\nimport { BaseRuntimeAdapter } from './base-adapter.js';\nimport { HttpRequest, HttpResponse } from '../../types/http.js';\nimport { RuntimeHttpResponse } from '../../types/runtime.js';\nimport { MoroHttpServer } from '../http/http-server.js';\n\nexport class NodeRuntimeAdapter extends BaseRuntimeAdapter {\n readonly type = 'node' as const;\n\n async adaptRequest(req: IncomingMessage): Promise<HttpRequest> {\n const { pathname, query } = this.parseUrl(req.url || '/');\n\n // Parse body for POST/PUT/PATCH requests\n let body: any;\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (['POST', 'PUT', 'PATCH'].includes(req.method!)) {\n body = await this.parseRequestBody(req);\n }\n\n const baseRequest = {\n // Copy IncomingMessage properties we need\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n method: req.method!,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n url: req.url!,\n headers: req.headers as Record<string, string>,\n httpVersion: req.httpVersion,\n httpVersionMajor: req.httpVersionMajor,\n httpVersionMinor: req.httpVersionMinor,\n socket: req.socket,\n\n // Add MoroJS-specific properties\n path: pathname,\n query,\n body,\n ip: this.getClientIP(req),\n params: {},\n requestId: '',\n cookies: {},\n files: {},\n } as Partial<HttpRequest>;\n\n return this.enhanceRequest(baseRequest);\n }\n\n async adaptResponse(\n moroResponse: HttpResponse | RuntimeHttpResponse,\n _req: IncomingMessage\n ): Promise<ServerResponse> {\n // For Node.js, we typically work with the actual ServerResponse\n // This method is mainly for converting mock responses back to real ones\n return moroResponse as any;\n }\n\n createServer(handler: (req: HttpRequest, res: HttpResponse) => Promise<void>): MoroHttpServer {\n const httpServer = new MoroHttpServer();\n\n // Replace the default request handler with our runtime-aware handler\n const originalServer = httpServer.getServer();\n originalServer.removeAllListeners('request');\n\n originalServer.on('request', (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n try {\n const moroReq = await this.adaptRequest(req);\n const moroRes = this.enhanceResponse(res);\n\n await handler(moroReq, moroRes);\n } catch (error) {\n if (!res.headersSent) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(\n JSON.stringify({\n success: false,\n error: 'Internal server error',\n message: error instanceof Error ? error.message : 'Unknown error',\n })\n );\n }\n }\n })();\n });\n\n return httpServer;\n }\n\n listen(server: MoroHttpServer, port: number, host?: string, callback?: () => void): void {\n server.listen(port, host as any, callback);\n }\n\n // Helper methods\n private async parseRequestBody(req: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', chunk => {\n body += chunk.toString();\n });\n req.on('end', () => {\n try {\n const contentType = req.headers['content-type'] || '';\n resolve(this.parseBody(body, contentType));\n } catch (error) {\n reject(error);\n }\n });\n req.on('error', reject);\n });\n }\n\n private getClientIP(req: IncomingMessage): string {\n const forwarded = req.headers['x-forwarded-for'] as string;\n if (forwarded) {\n return forwarded.split(',')[0]?.trim() ?? 'unknown';\n }\n return req.socket.remoteAddress || 'unknown';\n }\n\n private enhanceResponse(res: ServerResponse): HttpResponse {\n const enhanced = res as any;\n\n // Add MoroJS response methods if they don't exist\n if (!enhanced.json) {\n enhanced.json = function (data: any) {\n this.setHeader('Content-Type', 'application/json');\n this.end(JSON.stringify(data));\n };\n }\n\n if (!enhanced.status) {\n enhanced.status = function (code: number) {\n this.statusCode = code;\n return this;\n };\n }\n\n if (!enhanced.send) {\n enhanced.send = function (data: string | Buffer) {\n this.end(data);\n };\n }\n\n if (!enhanced.cookie) {\n enhanced.cookie = function (name: string, value: string, _options?: any) {\n if (!this.headersSent) {\n const cookieString = `${name}=${value}`;\n this.setHeader('Set-Cookie', cookieString);\n }\n return this;\n };\n }\n\n if (!enhanced.clearCookie) {\n enhanced.clearCookie = function (name: string, _options?: any) {\n if (!this.headersSent) {\n this.setHeader('Set-Cookie', `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`);\n }\n return this;\n };\n }\n\n if (!enhanced.redirect) {\n enhanced.redirect = function (url: string, status?: number) {\n if (!this.headersSent) {\n this.statusCode = status || 302;\n this.setHeader('Location', url);\n this.end();\n }\n };\n }\n\n if (!enhanced.sendFile) {\n enhanced.sendFile = async function (filePath: string) {\n const fs = await import('fs');\n const path = await import('path');\n\n try {\n const data = await fs.promises.readFile(filePath);\n const ext = path.extname(filePath);\n\n // Basic content type detection\n const contentTypes: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n };\n\n const contentType = contentTypes[ext] || 'application/octet-stream';\n this.setHeader('Content-Type', contentType);\n this.end(data);\n } catch {\n this.statusCode = 404;\n this.end('File not found');\n }\n };\n }\n\n return enhanced;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function requireBuiltin<T = unknown>(id: string): T;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Synchronous, lazy access to Node built-ins from ESM.
|
|
2
|
+
//
|
|
3
|
+
// `import x from 'node:x'` in an ESM package builds the built-in's ESM facade
|
|
4
|
+
// eagerly at import time - for node:http that pulls in http2, tls, zlib and
|
|
5
|
+
// worker_threads (~14 MB RSS against ~3 MB through require) - and it happens
|
|
6
|
+
// for every app, whether or not the code path that needs the module ever
|
|
7
|
+
// runs. requireBuiltin() resolves the same module object on first use, so a
|
|
8
|
+
// server that never clusters never loads node:cluster (which drags in
|
|
9
|
+
// child_process, dgram and net), one that never compresses never loads zlib,
|
|
10
|
+
// and so on. Built-ins are always synchronously requirable, so nothing on
|
|
11
|
+
// these paths becomes async. The base given to createRequire is irrelevant for
|
|
12
|
+
// built-ins; 'file:///' keeps this file free of import.meta so the CJS
|
|
13
|
+
// transform Jest applies to the test suite still compiles it.
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
const req = createRequire('file:///');
|
|
16
|
+
const cache = new Map();
|
|
17
|
+
export function requireBuiltin(id) {
|
|
18
|
+
let m = cache.get(id);
|
|
19
|
+
if (m === undefined) {
|
|
20
|
+
m = req(id);
|
|
21
|
+
cache.set(id, m);
|
|
22
|
+
}
|
|
23
|
+
return m;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=builtin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtin.js","sourceRoot":"","sources":["../../../src/core/utilities/builtin.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,4EAA4E;AAC5E,sEAAsE;AACtE,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,uEAAuE;AACvE,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB,CAAC;AAEzC,MAAM,UAAU,cAAc,CAAc,EAAU;IACpD,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtB,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QACpB,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;QACZ,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,CAAM,CAAC;AAChB,CAAC","sourcesContent":["// Synchronous, lazy access to Node built-ins from ESM.\n//\n// `import x from 'node:x'` in an ESM package builds the built-in's ESM facade\n// eagerly at import time - for node:http that pulls in http2, tls, zlib and\n// worker_threads (~14 MB RSS against ~3 MB through require) - and it happens\n// for every app, whether or not the code path that needs the module ever\n// runs. requireBuiltin() resolves the same module object on first use, so a\n// server that never clusters never loads node:cluster (which drags in\n// child_process, dgram and net), one that never compresses never loads zlib,\n// and so on. Built-ins are always synchronously requirable, so nothing on\n// these paths becomes async. The base given to createRequire is irrelevant for\n// built-ins; 'file:///' keeps this file free of import.meta so the CJS\n// transform Jest applies to the test suite still compiles it.\nimport { createRequire } from 'module';\n\nconst req = createRequire('file:///');\nconst cache = new Map<string, unknown>();\n\nexport function requireBuiltin<T = unknown>(id: string): T {\n let m = cache.get(id);\n if (m === undefined) {\n m = req(id);\n cache.set(id, m);\n }\n return m as T;\n}\n"]}
|
|
@@ -48,12 +48,37 @@ export declare const NATIVE_ENGINE_PACKAGES: readonly ["@morojs/engine", "uWebSo
|
|
|
48
48
|
export interface EngineCapabilities {
|
|
49
49
|
/** The full serve() limit surface is parsed (engine >= 1.1.0). */
|
|
50
50
|
limits: boolean;
|
|
51
|
-
/** In-process TLS termination via options.ssl (engine >= 1.
|
|
51
|
+
/** In-process TLS termination via options.ssl (engine >= 1.1.x). */
|
|
52
52
|
tls: boolean;
|
|
53
|
-
/** ALPN HTTP/2 via options.http2 (
|
|
53
|
+
/** ALPN HTTP/2 via options.http2 (reserved). */
|
|
54
54
|
http2: boolean;
|
|
55
|
-
/** WebSocket permessage-deflate via options.wsDeflate
|
|
55
|
+
/** WebSocket permessage-deflate via options.wsDeflate. */
|
|
56
56
|
wsDeflate: boolean;
|
|
57
|
+
/** responseTimeoutMs / responseBackpressureLimit / maxUriSize are parsed. */
|
|
58
|
+
responseLimits: boolean;
|
|
59
|
+
/** ssl.ciphers / ssl.ciphersuites / ssl.ecdhCurve are parsed. */
|
|
60
|
+
tlsPolicy: boolean;
|
|
61
|
+
/** setStaticRoute()/clearStaticRoutes() exist (not used by the framework:
|
|
62
|
+
* a static route bypasses middleware and hooks, so exposing it is an API
|
|
63
|
+
* decision, not an adapter one). */
|
|
64
|
+
staticRoutes: boolean;
|
|
65
|
+
/** prepareResponse()/respondPrepared()/respondPreparedEmpty()/endWith()
|
|
66
|
+
* exist (engine >= 1.1.6): the adapter replays prepared header blocks for
|
|
67
|
+
* the responses it emits itself. */
|
|
68
|
+
responseTemplates: boolean;
|
|
69
|
+
/** onAborted/onWritable are delivered on a later loop turn, never from
|
|
70
|
+
* inside respond()/write()/end() (engine >= 1.1.6). */
|
|
71
|
+
asyncNotify: boolean;
|
|
72
|
+
/** A server left open at environment teardown is closed by the engine's
|
|
73
|
+
* cleanup hook, so the engine may run inside worker threads that can be
|
|
74
|
+
* terminate()d (engine >= 1.1.6). Gates thread-based clustering. */
|
|
75
|
+
workerThreads: boolean;
|
|
76
|
+
/** V8 fast API calls are installed on the hot entry points (informational). */
|
|
77
|
+
fastCalls: boolean;
|
|
78
|
+
/** Batched pipelined dispatch (onRequestBatch / getBatchBuffers / getPath). */
|
|
79
|
+
batchDispatch: boolean;
|
|
80
|
+
/** I/O transport in use ('uv', 'uring'), when the engine reports it. */
|
|
81
|
+
transport?: string;
|
|
57
82
|
}
|
|
58
83
|
export interface NativeEngineLoadResult {
|
|
59
84
|
/** The loaded engine module (CJS namespace: App, SSLApp, ...) */
|
|
@@ -67,12 +67,24 @@ export function createUserRequire() {
|
|
|
67
67
|
*/
|
|
68
68
|
export const NATIVE_ENGINE_PACKAGES = ['@morojs/engine', 'uWebSockets.js'];
|
|
69
69
|
const FRAMEWORK_PACKAGE_NAME = '@morojs/moro';
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
// One place to name every boolean flag: getEngineCapabilities coerces each
|
|
71
|
+
// with `=== true`, and NO_CAPABILITIES is derived from the same list, so a
|
|
72
|
+
// flag added here can never be read on one path and forgotten on the other.
|
|
73
|
+
const CAP_KEYS = [
|
|
74
|
+
'limits',
|
|
75
|
+
'tls',
|
|
76
|
+
'http2',
|
|
77
|
+
'wsDeflate',
|
|
78
|
+
'responseLimits',
|
|
79
|
+
'tlsPolicy',
|
|
80
|
+
'staticRoutes',
|
|
81
|
+
'responseTemplates',
|
|
82
|
+
'asyncNotify',
|
|
83
|
+
'workerThreads',
|
|
84
|
+
'fastCalls',
|
|
85
|
+
'batchDispatch',
|
|
86
|
+
];
|
|
87
|
+
const NO_CAPABILITIES = Object.freeze(Object.fromEntries(CAP_KEYS.map(k => [k, false])));
|
|
76
88
|
const capabilitiesCache = new WeakMap();
|
|
77
89
|
/**
|
|
78
90
|
* Feature-detect a loaded engine module. Reads probe().capabilities when the
|
|
@@ -94,12 +106,12 @@ export function getEngineCapabilities(module) {
|
|
|
94
106
|
const probe = surface.probe();
|
|
95
107
|
const c = probe?.capabilities;
|
|
96
108
|
if (c && typeof c === 'object') {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
const next = {};
|
|
110
|
+
for (const key of CAP_KEYS)
|
|
111
|
+
next[key] = c[key] === true;
|
|
112
|
+
if (typeof probe.transport === 'string')
|
|
113
|
+
next.transport = probe.transport;
|
|
114
|
+
caps = next;
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
}
|