lifecycleion 0.0.1
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/LICENSE +22 -0
- package/README.md +125 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/arrays.cjs +95 -0
- package/dist/lib/arrays.cjs.map +1 -0
- package/dist/lib/arrays.d.cts +15 -0
- package/dist/lib/arrays.d.ts +15 -0
- package/dist/lib/arrays.js +63 -0
- package/dist/lib/arrays.js.map +1 -0
- package/dist/lib/ascii-tables/index.cjs +642 -0
- package/dist/lib/ascii-tables/index.cjs.map +1 -0
- package/dist/lib/ascii-tables/index.d.cts +66 -0
- package/dist/lib/ascii-tables/index.d.ts +66 -0
- package/dist/lib/ascii-tables/index.js +603 -0
- package/dist/lib/ascii-tables/index.js.map +1 -0
- package/dist/lib/clamp.cjs +41 -0
- package/dist/lib/clamp.cjs.map +1 -0
- package/dist/lib/clamp.d.cts +26 -0
- package/dist/lib/clamp.d.ts +26 -0
- package/dist/lib/clamp.js +15 -0
- package/dist/lib/clamp.js.map +1 -0
- package/dist/lib/constants.cjs +73 -0
- package/dist/lib/constants.cjs.map +1 -0
- package/dist/lib/constants.d.cts +17 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/constants.js +34 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/curly-brackets.cjs +77 -0
- package/dist/lib/curly-brackets.cjs.map +1 -0
- package/dist/lib/curly-brackets.d.cts +17 -0
- package/dist/lib/curly-brackets.d.ts +17 -0
- package/dist/lib/curly-brackets.js +52 -0
- package/dist/lib/curly-brackets.js.map +1 -0
- package/dist/lib/deep-clone.cjs +87 -0
- package/dist/lib/deep-clone.cjs.map +1 -0
- package/dist/lib/deep-clone.d.cts +19 -0
- package/dist/lib/deep-clone.d.ts +19 -0
- package/dist/lib/deep-clone.js +62 -0
- package/dist/lib/deep-clone.js.map +1 -0
- package/dist/lib/error-to-string.cjs +743 -0
- package/dist/lib/error-to-string.cjs.map +1 -0
- package/dist/lib/error-to-string.d.cts +3 -0
- package/dist/lib/error-to-string.d.ts +3 -0
- package/dist/lib/error-to-string.js +706 -0
- package/dist/lib/error-to-string.js.map +1 -0
- package/dist/lib/event-emitter.cjs +899 -0
- package/dist/lib/event-emitter.cjs.map +1 -0
- package/dist/lib/event-emitter.d.cts +78 -0
- package/dist/lib/event-emitter.d.ts +78 -0
- package/dist/lib/event-emitter.js +861 -0
- package/dist/lib/event-emitter.js.map +1 -0
- package/dist/lib/id-helpers.cjs +205 -0
- package/dist/lib/id-helpers.cjs.map +1 -0
- package/dist/lib/id-helpers.d.cts +198 -0
- package/dist/lib/id-helpers.d.ts +198 -0
- package/dist/lib/id-helpers.js +170 -0
- package/dist/lib/id-helpers.js.map +1 -0
- package/dist/lib/is-boolean.cjs +33 -0
- package/dist/lib/is-boolean.cjs.map +1 -0
- package/dist/lib/is-boolean.d.cts +19 -0
- package/dist/lib/is-boolean.d.ts +19 -0
- package/dist/lib/is-boolean.js +8 -0
- package/dist/lib/is-boolean.js.map +1 -0
- package/dist/lib/is-function.cjs +33 -0
- package/dist/lib/is-function.cjs.map +1 -0
- package/dist/lib/is-function.d.cts +3 -0
- package/dist/lib/is-function.d.ts +3 -0
- package/dist/lib/is-function.js +8 -0
- package/dist/lib/is-function.js.map +1 -0
- package/dist/lib/is-number.cjs +38 -0
- package/dist/lib/is-number.cjs.map +1 -0
- package/dist/lib/is-number.d.cts +38 -0
- package/dist/lib/is-number.d.ts +38 -0
- package/dist/lib/is-number.js +12 -0
- package/dist/lib/is-number.js.map +1 -0
- package/dist/lib/is-plain-object.cjs +33 -0
- package/dist/lib/is-plain-object.cjs.map +1 -0
- package/dist/lib/is-plain-object.d.cts +20 -0
- package/dist/lib/is-plain-object.d.ts +20 -0
- package/dist/lib/is-plain-object.js +8 -0
- package/dist/lib/is-plain-object.js.map +1 -0
- package/dist/lib/is-promise.cjs +34 -0
- package/dist/lib/is-promise.cjs.map +1 -0
- package/dist/lib/is-promise.d.cts +3 -0
- package/dist/lib/is-promise.d.ts +3 -0
- package/dist/lib/is-promise.js +9 -0
- package/dist/lib/is-promise.js.map +1 -0
- package/dist/lib/json-helpers.cjs +49 -0
- package/dist/lib/json-helpers.cjs.map +1 -0
- package/dist/lib/json-helpers.d.cts +10 -0
- package/dist/lib/json-helpers.d.ts +10 -0
- package/dist/lib/json-helpers.js +22 -0
- package/dist/lib/json-helpers.js.map +1 -0
- package/dist/lib/lifecycle-manager/index.cjs +5594 -0
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -0
- package/dist/lib/lifecycle-manager/index.d.cts +2044 -0
- package/dist/lib/lifecycle-manager/index.d.ts +2044 -0
- package/dist/lib/lifecycle-manager/index.js +5543 -0
- package/dist/lib/lifecycle-manager/index.js.map +1 -0
- package/dist/lib/logger/index.cjs +2514 -0
- package/dist/lib/logger/index.cjs.map +1 -0
- package/dist/lib/logger/index.d.cts +630 -0
- package/dist/lib/logger/index.d.ts +630 -0
- package/dist/lib/logger/index.js +2470 -0
- package/dist/lib/logger/index.js.map +1 -0
- package/dist/lib/padding-utils.cjs +77 -0
- package/dist/lib/padding-utils.cjs.map +1 -0
- package/dist/lib/padding-utils.d.cts +44 -0
- package/dist/lib/padding-utils.d.ts +44 -0
- package/dist/lib/padding-utils.js +46 -0
- package/dist/lib/padding-utils.js.map +1 -0
- package/dist/lib/process-signal-manager.cjs +1306 -0
- package/dist/lib/process-signal-manager.cjs.map +1 -0
- package/dist/lib/process-signal-manager.d.cts +305 -0
- package/dist/lib/process-signal-manager.d.ts +305 -0
- package/dist/lib/process-signal-manager.js +1269 -0
- package/dist/lib/process-signal-manager.js.map +1 -0
- package/dist/lib/promise-protected-resolver.cjs +828 -0
- package/dist/lib/promise-protected-resolver.cjs.map +1 -0
- package/dist/lib/promise-protected-resolver.d.cts +17 -0
- package/dist/lib/promise-protected-resolver.d.ts +17 -0
- package/dist/lib/promise-protected-resolver.js +791 -0
- package/dist/lib/promise-protected-resolver.js.map +1 -0
- package/dist/lib/retry-utils/index.cjs +2183 -0
- package/dist/lib/retry-utils/index.cjs.map +1 -0
- package/dist/lib/retry-utils/index.d.cts +321 -0
- package/dist/lib/retry-utils/index.d.ts +321 -0
- package/dist/lib/retry-utils/index.js +2133 -0
- package/dist/lib/retry-utils/index.js.map +1 -0
- package/dist/lib/safe-handle-callback.cjs +818 -0
- package/dist/lib/safe-handle-callback.cjs.map +1 -0
- package/dist/lib/safe-handle-callback.d.cts +43 -0
- package/dist/lib/safe-handle-callback.d.ts +43 -0
- package/dist/lib/safe-handle-callback.js +780 -0
- package/dist/lib/safe-handle-callback.js.map +1 -0
- package/dist/lib/serialize-error/index.cjs +93 -0
- package/dist/lib/serialize-error/index.cjs.map +1 -0
- package/dist/lib/serialize-error/index.d.cts +26 -0
- package/dist/lib/serialize-error/index.d.ts +26 -0
- package/dist/lib/serialize-error/index.js +64 -0
- package/dist/lib/serialize-error/index.js.map +1 -0
- package/dist/lib/single-event-observer.cjs +841 -0
- package/dist/lib/single-event-observer.cjs.map +1 -0
- package/dist/lib/single-event-observer.d.cts +54 -0
- package/dist/lib/single-event-observer.d.ts +54 -0
- package/dist/lib/single-event-observer.js +803 -0
- package/dist/lib/single-event-observer.js.map +1 -0
- package/dist/lib/sleep.cjs +37 -0
- package/dist/lib/sleep.cjs.map +1 -0
- package/dist/lib/sleep.d.cts +11 -0
- package/dist/lib/sleep.d.ts +11 -0
- package/dist/lib/sleep.js +12 -0
- package/dist/lib/sleep.js.map +1 -0
- package/dist/lib/strings.cjs +186 -0
- package/dist/lib/strings.cjs.map +1 -0
- package/dist/lib/strings.d.cts +107 -0
- package/dist/lib/strings.d.ts +107 -0
- package/dist/lib/strings.js +149 -0
- package/dist/lib/strings.js.map +1 -0
- package/dist/lib/tmp-dir.cjs +254 -0
- package/dist/lib/tmp-dir.cjs.map +1 -0
- package/dist/lib/tmp-dir.d.cts +63 -0
- package/dist/lib/tmp-dir.d.ts +63 -0
- package/dist/lib/tmp-dir.js +211 -0
- package/dist/lib/tmp-dir.js.map +1 -0
- package/dist/lib/unix-time-helpers.cjs +53 -0
- package/dist/lib/unix-time-helpers.cjs.map +1 -0
- package/dist/lib/unix-time-helpers.d.cts +56 -0
- package/dist/lib/unix-time-helpers.d.ts +56 -0
- package/dist/lib/unix-time-helpers.js +24 -0
- package/dist/lib/unix-time-helpers.js.map +1 -0
- package/package.json +220 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/process-signal-manager.ts","../../src/lib/strings.ts","../../src/lib/constants.ts","../../src/lib/padding-utils.ts","../../src/lib/ascii-tables/ascii-table-utils.ts","../../src/lib/ascii-tables/multi-column-ascii-table.ts","../../src/lib/ascii-tables/key-value-ascii-table.ts","../../src/lib/clamp.ts","../../src/lib/error-to-string.ts","../../src/lib/is-promise.ts","../../src/lib/is-function.ts","../../src/lib/safe-handle-callback.ts"],"sourcesContent":["import { safeHandleCallback } from './safe-handle-callback';\nimport { ulid } from 'ulid';\nimport readline from 'readline';\n\n/**\n * The shutdown signal types that can trigger the shutdown callback\n */\nexport type ShutdownSignal = 'SIGINT' | 'SIGTERM' | 'SIGTRAP';\n\n/**\n * Shared state stored on globalThis to survive module duplication from bundlers.\n * Uses Symbol.for() to ensure the same symbol across all copies of this module.\n */\ninterface ProcessSignalManagerSharedState {\n /**\n * Whether emitKeypressEvents was called on process.stdin.\n * Since process.stdin is a global singleton, this prevents multiple calls.\n */\n keypressEventsEmittedOnStdin: boolean;\n\n /**\n * Set of instance IDs that have successfully attached.\n * Used to coordinate shared resources (raw mode, stdin pause/resume).\n */\n attachedInstances: Set<string>;\n\n /**\n * The instance ID that enabled raw mode, or null if raw mode wasn't enabled by us.\n * Only this instance (or the last remaining instance) should disable raw mode.\n */\n rawModeOwner: string | null;\n\n /**\n * Whether raw mode was enabled by ProcessSignalManager.\n *\n * This is necessary because an instance may fail to disable raw mode during detach,\n * leaving stdin in raw mode while the recorded owner is stale. When a new instance\n * later attaches and finds raw mode already enabled, it can safely adopt ownership\n * *only if* this flag indicates we were responsible for enabling raw mode.\n *\n * If raw mode was enabled by external code, this stays false and we will not disable it.\n */\n rawModeEnabledByManager: boolean;\n}\n\n// Use Symbol.for() for a global symbol registry - survives module duplication\nconst SHARED_STATE_KEY = Symbol.for('lifecycleion.ProcessSignalManager.v1');\n\n/**\n * Get or initialize the shared state on globalThis.\n * This ensures all copies of this module (from bundler duplication) share the same state.\n */\nfunction getSharedState(): ProcessSignalManagerSharedState {\n const g = globalThis as Record<\n symbol,\n ProcessSignalManagerSharedState | undefined\n >;\n if (!g[SHARED_STATE_KEY]) {\n g[SHARED_STATE_KEY] = {\n keypressEventsEmittedOnStdin: false,\n attachedInstances: new Set(),\n rawModeOwner: null,\n rawModeEnabledByManager: false,\n };\n }\n return g[SHARED_STATE_KEY];\n}\n\n/**\n * Transfer raw mode ownership to another attached instance, or clear it if none remain.\n * This is a centralized helper to ensure ownership is always valid.\n *\n * @param shared - The shared state object\n * @param currentOwner - The instance ID giving up ownership (only transfers if this matches current owner)\n * @returns The new owner ID, or null if ownership was cleared\n */\nfunction transferRawModeOwnership(\n shared: ProcessSignalManagerSharedState,\n currentOwner: string,\n): string | null {\n // Only transfer if we're actually the current owner\n if (shared.rawModeOwner !== currentOwner) {\n return shared.rawModeOwner;\n }\n\n // If no instances remain, clear ownership\n if (shared.attachedInstances.size === 0) {\n shared.rawModeOwner = null;\n return null;\n }\n\n // Find a valid new owner from remaining instances\n // Re-validate that the chosen instance is still attached (defensive against concurrent modifications)\n for (const candidateID of shared.attachedInstances) {\n if (shared.attachedInstances.has(candidateID)) {\n shared.rawModeOwner = candidateID;\n return candidateID;\n }\n }\n\n // Fallback: no valid instances (shouldn't happen, but be safe)\n shared.rawModeOwner = null;\n return null;\n}\n\n/**\n * Status information about what the manager is attached to\n */\nexport interface ProcessSignalManagerStatus {\n /**\n * Whether the manager is currently attached to signals and keypresses\n */\n isAttached: boolean;\n\n /**\n * Which handlers are registered\n */\n handlers: {\n /**\n * Whether a shutdown handler is registered\n */\n shutdown: boolean;\n\n /**\n * Whether a reload handler is registered\n */\n reload: boolean;\n\n /**\n * Whether an info handler is registered\n */\n info: boolean;\n\n /**\n * Whether a debug handler is registered\n */\n debug: boolean;\n };\n\n /**\n * What events are currently being listened for (only populated when isListening is true)\n */\n listeningFor: {\n /**\n * Listening for shutdown signals (SIGINT, SIGTERM, SIGTRAP)\n */\n shutdownSignals: boolean;\n\n /**\n * Listening for reload signal (SIGHUP)\n */\n reloadSignal: boolean;\n\n /**\n * Listening for info signal (SIGUSR1)\n */\n infoSignal: boolean;\n\n /**\n * Listening for debug signal (SIGUSR2)\n */\n debugSignal: boolean;\n\n /**\n * Listening for keypresses (Ctrl+C, Escape, R, I, D)\n */\n keypresses: boolean;\n };\n}\n\n/**\n * Configuration options for ProcessSignalManager\n */\nexport interface ProcessSignalManagerOptions {\n /**\n * Optional callback invoked when a shutdown signal is received.\n *\n * Triggered by:\n * - Process signals: SIGINT, SIGTERM, SIGTRAP\n * - Keyboard: Ctrl+C, Escape\n */\n onShutdownRequested?: (method: ShutdownSignal) => void | Promise<void>;\n\n /**\n * Optional callback invoked when reload is requested.\n *\n * Triggered by:\n * - Process signal: SIGHUP\n * - Keyboard: R key press (case-insensitive)\n */\n onReloadRequested?: () => void | Promise<unknown>;\n\n /**\n * Optional callback invoked when info/stats are requested.\n *\n * Triggered by:\n * - Process signal: SIGUSR1\n * - Keyboard: I key press (case-insensitive)\n *\n * Common uses: Print stats, health check, show metrics\n */\n onInfoRequested?: () => void | Promise<unknown>;\n\n /**\n * Optional callback invoked when debug mode is toggled or verbose info is requested.\n *\n * Triggered by:\n * - Process signal: SIGUSR2\n * - Keyboard: D key press (case-insensitive)\n *\n * Common uses: Toggle debug mode, dump full state, enable verbose logging\n */\n onDebugRequested?: () => void | Promise<unknown>;\n\n /**\n * Custom name for the shutdown callback used in error reporting.\n * @default 'onShutdownRequested'\n */\n shutdownCallbackName?: string;\n\n /**\n * Custom name for the reload callback used in error reporting.\n * @default 'onReloadRequested'\n */\n reloadCallbackName?: string;\n\n /**\n * Custom name for the info callback used in error reporting.\n * @default 'onInfoRequested'\n */\n infoCallbackName?: string;\n\n /**\n * Custom name for the debug callback used in error reporting.\n * @default 'onDebugRequested'\n */\n debugCallbackName?: string;\n\n /**\n * Throttle interval in milliseconds for keyboard events (leading-edge rate limiting).\n * Allows an action to trigger at most once per interval. First press fires immediately,\n * subsequent presses within the window are ignored.\n * This prevents accidental double-triggers while allowing predictable repeated actions.\n *\n * Note: This only affects keyboard events, not process signals.\n * Process signals are never throttled as they may come from external sources\n * that expect immediate handling.\n *\n * @default 200 (200ms throttle, allowing 5 triggers per second maximum)\n * @example 300 // Custom 300ms throttle (3.33 triggers per second max)\n * @example 0 // Disable throttling entirely\n */\n keypressThrottleMS?: number;\n}\n\n/**\n * Manages process signals and keyboard events for graceful shutdown, reload, and info/debug functionality.\n *\n * Handles:\n * - Shutdown signals: SIGINT, SIGTERM, SIGTRAP\n * - Reload signal: SIGHUP\n * - Info signal: SIGUSR1\n * - Debug signal: SIGUSR2\n * - Keyboard shortcuts: Ctrl+C, Escape (shutdown), R (reload), I (info), D (debug)\n * All letter keys are case-insensitive\n *\n * All callbacks are executed safely with automatic error handling.\n */\nexport class ProcessSignalManager {\n // Unique identifier for this instance, used for tracking in shared state\n private readonly instanceID: string;\n\n private onShutdownRequested?: (\n method: ShutdownSignal,\n ) => void | Promise<void>;\n private onReloadRequested?: () => void | Promise<unknown>;\n private onInfoRequested?: () => void | Promise<unknown>;\n private onDebugRequested?: () => void | Promise<unknown>;\n\n private shutdownCallbackName: string;\n private reloadCallbackName: string;\n private infoCallbackName: string;\n private debugCallbackName: string;\n\n private shutdownSignalListeners?: {\n [key in ShutdownSignal]: () => void;\n };\n private reloadSignalListener?: () => void;\n private infoSignalListener?: () => void;\n private debugSignalListener?: () => void;\n private keypressHandler?: (str: string, key: unknown) => void;\n private _isAttached = false;\n\n // Throttle state for keyboard events (default 200ms, 0 disables)\n // Track throttle separately per action type so different keys don't interfere with each other\n // Use -Infinity to ensure first press is never throttled (always fires immediately)\n private keypressThrottleMS: number;\n private lastActionTimes = {\n shutdown: -Infinity,\n reload: -Infinity,\n info: -Infinity,\n debug: -Infinity,\n };\n\n constructor(options: ProcessSignalManagerOptions) {\n // Generate unique ID for this instance to track it in the activeInstances Set\n this.instanceID = ulid();\n\n this.onShutdownRequested = options.onShutdownRequested;\n this.onReloadRequested = options.onReloadRequested;\n this.onInfoRequested = options.onInfoRequested;\n this.onDebugRequested = options.onDebugRequested;\n this.shutdownCallbackName =\n options.shutdownCallbackName ?? 'onShutdownRequested';\n this.reloadCallbackName = options.reloadCallbackName ?? 'onReloadRequested';\n this.infoCallbackName = options.infoCallbackName ?? 'onInfoRequested';\n this.debugCallbackName = options.debugCallbackName ?? 'onDebugRequested';\n // Default to 200ms throttle (leading-edge rate limiting), 0 disables\n this.keypressThrottleMS = options.keypressThrottleMS ?? 200;\n\n // Initialize shutdown signal handlers if callback is provided (not yet registered with process)\n // These will be registered when listen() is called\n if (this.onShutdownRequested) {\n const shutdownCallback = this.onShutdownRequested;\n this.shutdownSignalListeners = {\n SIGINT: (): void =>\n safeHandleCallback(\n this.shutdownCallbackName,\n shutdownCallback,\n 'SIGINT',\n ),\n SIGTERM: (): void =>\n safeHandleCallback(\n this.shutdownCallbackName,\n shutdownCallback,\n 'SIGTERM',\n ),\n SIGTRAP: (): void =>\n safeHandleCallback(\n this.shutdownCallbackName,\n shutdownCallback,\n 'SIGTRAP',\n ),\n };\n }\n\n // Initialize reload signal handler if callback is provided (not yet registered with process)\n // This will be registered when listen() is called\n if (this.onReloadRequested) {\n const reloadCallback = this.onReloadRequested;\n this.reloadSignalListener = (): void =>\n safeHandleCallback(this.reloadCallbackName, reloadCallback);\n }\n\n // Initialize info signal handler (SIGUSR1) if callback is provided (not yet registered with process)\n // This will be registered when listen() is called\n if (this.onInfoRequested) {\n const infoCallback = this.onInfoRequested;\n this.infoSignalListener = (): void =>\n safeHandleCallback(this.infoCallbackName, infoCallback);\n }\n\n // Initialize debug signal handler (SIGUSR2) if callback is provided (not yet registered with process)\n // This will be registered when listen() is called\n if (this.onDebugRequested) {\n const debugCallback = this.onDebugRequested;\n this.debugSignalListener = (): void =>\n safeHandleCallback(this.debugCallbackName, debugCallback);\n }\n }\n\n /**\n * Check if the manager is currently attached to signals and keypresses.\n */\n public get isAttached(): boolean {\n return this._isAttached;\n }\n\n /**\n * Get detailed status information about what the manager is attached to.\n *\n * @returns Status object with handler registration and attachment state\n */\n public getStatus(): ProcessSignalManagerStatus {\n return {\n isAttached: this._isAttached,\n handlers: {\n shutdown: !!this.onShutdownRequested,\n reload: !!this.onReloadRequested,\n info: !!this.onInfoRequested,\n debug: !!this.onDebugRequested,\n },\n listeningFor: {\n shutdownSignals: this._isAttached && !!this.shutdownSignalListeners,\n reloadSignal: this._isAttached && !!this.reloadSignalListener,\n infoSignal: this._isAttached && !!this.infoSignalListener,\n debugSignal: this._isAttached && !!this.debugSignalListener,\n // Keypresses are only available if stdin is a TTY\n keypresses:\n this._isAttached && process.stdin.isTTY && !!this.keypressHandler,\n },\n };\n }\n\n /**\n * Attach signal handlers and start listening for process signals and keyboard events.\n * Idempotent - calling multiple times has no effect.\n */\n public attach(): void {\n if (this._isAttached) {\n return;\n }\n\n try {\n this.listenForShutdownSignals();\n this.listenForReloadSignal();\n this.listenForInfoSignal();\n this.listenForDebugSignal();\n this.listenForKeyPresses();\n this._isAttached = true;\n } catch (error) {\n // If any listener registration fails, clean up any handlers that were already registered\n // This prevents partial registration and ensures consistent state\n this.stopListeningForShutdownSignals();\n this.stopListeningForReloadSignal();\n this.stopListeningForInfoSignal();\n this.stopListeningForDebugSignal();\n this.restoreStdin();\n throw error;\n }\n }\n\n /**\n * Detach signal handlers and stop listening for process signals and keyboard events.\n * Cleans up all event listeners and restores stdin to normal mode.\n *\n * Idempotent - calling multiple times has no effect.\n */\n public detach(): void {\n if (!this._isAttached) {\n return;\n }\n\n try {\n this.stopListeningForShutdownSignals();\n this.stopListeningForReloadSignal();\n this.stopListeningForInfoSignal();\n this.stopListeningForDebugSignal();\n this.restoreStdin();\n } finally {\n // Always mark as detached, even if cleanup threw an error\n // This prevents the manager from being stuck in an \"attached\" state\n // that blocks re-attachment attempts\n this._isAttached = false;\n }\n }\n\n /**\n * Manually trigger a shutdown event\n * if the manager is attached and a shutdown handler is registered\n *\n * @param method - The shutdown method (SIGINT, SIGTERM, or SIGTRAP) that will be passed to the shutdown callback\n * @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)\n */\n public triggerShutdown(\n method: ShutdownSignal,\n shouldBypassAttachCheck = false,\n ): void {\n if (\n (this._isAttached || shouldBypassAttachCheck) &&\n this.onShutdownRequested\n ) {\n safeHandleCallback(\n this.shutdownCallbackName,\n this.onShutdownRequested,\n method,\n );\n }\n }\n\n /**\n * Manually trigger a reload event\n * if the manager is attached and a reload handler is registered\n *\n * @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)\n */\n public triggerReload(shouldBypassAttachCheck = false): void {\n if (\n (this._isAttached || shouldBypassAttachCheck) &&\n this.onReloadRequested\n ) {\n safeHandleCallback(this.reloadCallbackName, this.onReloadRequested);\n }\n }\n\n /**\n * Manually trigger an info event\n * if the manager is attached and an info handler is registered\n *\n * @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)\n */\n public triggerInfo(shouldBypassAttachCheck = false): void {\n if ((this._isAttached || shouldBypassAttachCheck) && this.onInfoRequested) {\n safeHandleCallback(this.infoCallbackName, this.onInfoRequested);\n }\n }\n\n /**\n * Manually trigger a debug event\n * if the manager is attached and a debug handler is registered\n *\n * @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)\n */\n public triggerDebug(shouldBypassAttachCheck = false): void {\n if (\n (this._isAttached || shouldBypassAttachCheck) &&\n this.onDebugRequested\n ) {\n safeHandleCallback(this.debugCallbackName, this.onDebugRequested);\n }\n }\n\n /**\n * Check if an action should be throttled based on the last time it was successfully triggered.\n * Uses leading-edge throttle: first press fires immediately, subsequent presses within the\n * throttle window are ignored. Only updates timestamp when action is allowed (not throttled).\n *\n * This is the standard pattern for keyboard shortcuts and prevents accidental double-triggers\n * while allowing predictable repeated actions at a maximum rate.\n *\n * @param action - The action type to check throttling for\n * @returns true if the action should be throttled (ignored), false otherwise\n */\n private shouldThrottle(action: keyof typeof this.lastActionTimes): boolean {\n if (this.keypressThrottleMS <= 0) {\n return false; // Throttling disabled\n }\n\n const now = Date.now();\n const timeSinceLastTrigger = now - this.lastActionTimes[action];\n\n if (timeSinceLastTrigger < this.keypressThrottleMS) {\n return true; // Throttled - too soon after last successful trigger\n }\n\n // Only update timestamp when allowing the action (leading-edge throttle)\n // This allows predictable rate limiting: action can fire at most once per interval\n this.lastActionTimes[action] = now;\n return false;\n }\n\n /**\n * Register handlers for all shutdown signals (SIGINT, SIGTERM, SIGTRAP) if callback is provided.\n * Each signal will trigger the shutdown callback with the appropriate method.\n */\n private listenForShutdownSignals(): void {\n if (this.shutdownSignalListeners) {\n for (const signal of Object.keys(\n this.shutdownSignalListeners,\n ) as ShutdownSignal[]) {\n process.on(signal, this.shutdownSignalListeners[signal]);\n }\n }\n }\n\n /**\n * Remove handlers for all shutdown signals if they were registered.\n * Uses the same function references to ensure proper cleanup.\n */\n private stopListeningForShutdownSignals(): void {\n if (this.shutdownSignalListeners) {\n for (const signal of Object.keys(\n this.shutdownSignalListeners,\n ) as ShutdownSignal[]) {\n process.off(signal, this.shutdownSignalListeners[signal]);\n }\n }\n }\n\n /**\n * Register handler for SIGHUP signal if reload callback is provided.\n * SIGHUP is commonly used to trigger configuration reloads.\n */\n private listenForReloadSignal(): void {\n if (this.reloadSignalListener) {\n process.on('SIGHUP', this.reloadSignalListener);\n }\n }\n\n /**\n * Remove handler for SIGHUP signal.\n * Uses the same function reference to ensure proper cleanup.\n */\n private stopListeningForReloadSignal(): void {\n if (this.reloadSignalListener) {\n process.off('SIGHUP', this.reloadSignalListener);\n }\n }\n\n /**\n * Register handler for SIGUSR1 signal if info callback is provided.\n * SIGUSR1 is commonly used for printing stats, health checks, etc.\n */\n private listenForInfoSignal(): void {\n if (this.infoSignalListener) {\n process.on('SIGUSR1', this.infoSignalListener);\n }\n }\n\n /**\n * Remove handler for SIGUSR1 signal.\n * Uses the same function reference to ensure proper cleanup.\n */\n private stopListeningForInfoSignal(): void {\n if (this.infoSignalListener) {\n process.off('SIGUSR1', this.infoSignalListener);\n }\n }\n\n /**\n * Register handler for SIGUSR2 signal if debug callback is provided.\n * SIGUSR2 is commonly used for toggling debug mode, dumping state, etc.\n */\n private listenForDebugSignal(): void {\n if (this.debugSignalListener) {\n process.on('SIGUSR2', this.debugSignalListener);\n }\n }\n\n /**\n * Remove handler for SIGUSR2 signal.\n * Uses the same function reference to ensure proper cleanup.\n */\n private stopListeningForDebugSignal(): void {\n if (this.debugSignalListener) {\n process.off('SIGUSR2', this.debugSignalListener);\n }\n }\n\n /**\n * Enable keyboard event listening if stdin is a TTY.\n * Sets stdin to raw mode and listens for Ctrl+C, Escape, R, I, and D keypresses.\n *\n * Note: Letter keys are case-insensitive (R/r, I/i, D/d all work).\n *\n * Uses add-then-check pattern to prevent race conditions:\n * 1. Add ourselves to attachedInstances first\n * 2. Check if we're the first (size === 1) to enable raw mode\n * This ensures no gap where another instance could read stale state.\n */\n private listenForKeyPresses(): void {\n if (!process.stdin.isTTY || this.keypressHandler) {\n return;\n }\n\n const shared = getSharedState();\n\n // Only call emitKeypressEvents once per stream to avoid duplicate events\n // Node.js warns against calling this multiple times on the same stream\n // IMPORTANT: Set flag BEFORE calling to prevent race condition where two instances\n // both see false and both call emitKeypressEvents. The call is idempotent-ish\n // (just causes warnings), but we want to avoid it.\n if (!shared.keypressEventsEmittedOnStdin) {\n shared.keypressEventsEmittedOnStdin = true;\n readline.emitKeypressEvents(process.stdin);\n }\n\n // Create the keypress handler\n // Note: Keypresses directly invoke callbacks\n // They don't emit actual process signals to avoid recursion and keep it simple\n this.keypressHandler = (str, key): void => {\n const keyObj = key as Record<string, unknown>;\n const keyName = keyObj.name as string;\n // Note: key.name is always lowercase for letter keys, regardless of shift state\n // So checking for 'r' catches both 'r' and 'R' (making it case-insensitive)\n\n // Handle Ctrl+C manually (if shutdown handler is registered)\n if (keyObj.ctrl && keyName === 'c' && this.onShutdownRequested) {\n if (this.shouldThrottle('shutdown')) {\n return;\n }\n\n safeHandleCallback(\n this.shutdownCallbackName,\n this.onShutdownRequested,\n 'SIGINT',\n );\n }\n // Treat escape as a SIGINT signal (if shutdown handler is registered)\n else if (keyName === 'escape' && this.onShutdownRequested) {\n if (this.shouldThrottle('shutdown')) {\n return;\n }\n\n safeHandleCallback(\n this.shutdownCallbackName,\n this.onShutdownRequested,\n 'SIGINT',\n );\n }\n // Handle R key for reload (case-insensitive)\n else if (keyName === 'r' && this.onReloadRequested) {\n if (this.shouldThrottle('reload')) {\n return;\n }\n\n safeHandleCallback(this.reloadCallbackName, this.onReloadRequested);\n }\n // Handle I key for info (case-insensitive)\n else if (keyName === 'i' && this.onInfoRequested) {\n if (this.shouldThrottle('info')) {\n return;\n }\n\n safeHandleCallback(this.infoCallbackName, this.onInfoRequested);\n }\n // Handle D key for debug (case-insensitive)\n else if (keyName === 'd' && this.onDebugRequested) {\n if (this.shouldThrottle('debug')) {\n return;\n }\n\n safeHandleCallback(this.debugCallbackName, this.onDebugRequested);\n }\n };\n\n // ADD FIRST, then check - prevents race condition where two instances\n // both see size === 0 before either adds themselves\n shared.attachedInstances.add(this.instanceID);\n const isFirstInstance = shared.attachedInstances.size === 1;\n\n try {\n // Register this instance's keypress handler\n // Note: Multiple instances can coexist - each gets its own handler, all receive the same keypresses\n process.stdin.on('keypress', this.keypressHandler);\n } catch (error) {\n // If registration fails, clean up and rethrow\n shared.attachedInstances.delete(this.instanceID);\n this.keypressHandler = undefined;\n throw error;\n }\n\n // Enable raw mode only when the first instance attaches\n // Check AFTER adding ourselves to prevent race condition\n if (isFirstInstance) {\n if (!process.stdin.isRaw) {\n try {\n process.stdin.setRawMode(true);\n // Only record ownership AFTER success - prevents rollback race\n shared.rawModeOwner = this.instanceID;\n shared.rawModeEnabledByManager = true;\n } catch (error) {\n // setRawMode failed - clean up the handler we registered\n // Pass attemptedRawModeEnable=true because setRawMode may have enabled raw mode\n // before throwing (edge case with some terminal emulators)\n this.cleanupKeypressHandler(shared, true);\n throw error;\n }\n } else if (\n shared.rawModeEnabledByManager &&\n (shared.rawModeOwner === null ||\n !shared.attachedInstances.has(shared.rawModeOwner))\n ) {\n // Raw mode is already enabled, and we previously enabled it (per shared flag),\n // but ownership is missing or stale (e.g., last detach failed to disable raw mode).\n // Adopt ownership so the eventual last detach will restore the terminal.\n shared.rawModeOwner = this.instanceID;\n } else if (!shared.rawModeEnabledByManager) {\n // If raw mode is already enabled but we didn't enable it, treat it as external.\n // Ensure we don't carry forward a stale owner value from a prior run/version.\n shared.rawModeOwner = null;\n }\n }\n\n // Resume stdin only when first instance attaches\n if (isFirstInstance) {\n try {\n process.stdin.resume();\n } catch (error) {\n // resume() failed - clean up everything\n this.cleanupKeypressHandler(shared);\n throw error;\n }\n }\n }\n\n /**\n * Helper to clean up keypress handler registration on error.\n * Removes handler, clears instance from shared state, and restores raw mode if needed.\n *\n * @param shared - The shared state object\n * @param didAttemptRawModeEnable - If true, we attempted to enable raw mode (even if ownership wasn't recorded).\n * This handles the edge case where setRawMode(true) throws after actually enabling raw mode.\n */\n private cleanupKeypressHandler(\n shared: ProcessSignalManagerSharedState,\n didAttemptRawModeEnable = false,\n ): void {\n if (this.keypressHandler) {\n try {\n process.stdin.off('keypress', this.keypressHandler);\n } catch {\n // Ignore - best effort cleanup\n }\n this.keypressHandler = undefined;\n }\n\n shared.attachedInstances.delete(this.instanceID);\n\n // If we attempted to enable raw mode and it appears to have been enabled,\n // record that raw mode is managed by us (even if the original setRawMode(true) threw).\n // This allows future instances to adopt ownership and restore the terminal.\n if (didAttemptRawModeEnable && process.stdin.isTTY && process.stdin.isRaw) {\n shared.rawModeEnabledByManager = true;\n if (shared.rawModeOwner === null) {\n shared.rawModeOwner = this.instanceID;\n }\n }\n\n // If we were the raw mode owner but other instances remain, transfer ownership.\n // Use centralized helper to ensure atomic ownership transfer.\n if (\n shared.rawModeEnabledByManager &&\n shared.rawModeOwner === this.instanceID &&\n shared.attachedInstances.size > 0\n ) {\n transferRawModeOwnership(shared, this.instanceID);\n } else if (\n shared.rawModeOwner !== null &&\n shared.attachedInstances.size > 0 &&\n shared.rawModeEnabledByManager &&\n !shared.attachedInstances.has(shared.rawModeOwner)\n ) {\n // Defensive: if ownership somehow points to a detached instance, re-anchor it.\n // Find any valid attached instance to take ownership.\n for (const candidateID of shared.attachedInstances) {\n if (shared.attachedInstances.has(candidateID)) {\n shared.rawModeOwner = candidateID;\n break;\n }\n }\n }\n\n // Restore raw mode if we're the last instance AND either:\n // 1. We own raw mode (normal case), OR\n // 2. We attempted to enable raw mode but ownership wasn't recorded (setRawMode threw after enabling)\n const shouldRestoreRawMode =\n shared.attachedInstances.size === 0 &&\n shared.rawModeEnabledByManager &&\n (shared.rawModeOwner === this.instanceID ||\n (didAttemptRawModeEnable && shared.rawModeOwner === null));\n\n if (shouldRestoreRawMode) {\n try {\n if (process.stdin.isTTY && process.stdin.isRaw) {\n process.stdin.setRawMode(false);\n }\n\n // Clear ownership after successful operation\n // (either raw mode was disabled, or it was already off and doesn't need disabling)\n shared.rawModeOwner = null;\n shared.rawModeEnabledByManager = false;\n } catch {\n // If setRawMode(false) fails, ensure there's a non-null owner so future detaches can retry.\n // This matters in the edge case where setRawMode(true) threw after enabling raw mode:\n // rawModeOwner would still be null, and without setting it here we'd never retry disabling.\n if (didAttemptRawModeEnable && shared.rawModeOwner === null) {\n shared.rawModeOwner = this.instanceID;\n }\n // rawModeEnabledByManager stays true so future instances can adopt and retry.\n // Terminal will be restored on process exit anyway.\n }\n }\n }\n\n /**\n * Restore stdin to normal mode and clean up keypress listener.\n * Uses remove-then-check pattern (mirror of add-then-check in attach):\n * 1. Remove ourselves from attachedInstances first\n * 2. Check if we're the last (size === 0) to disable raw mode and pause stdin\n *\n * Note: Can be called even if keypressHandler is undefined (e.g., during error recovery).\n * In that case, we still update shared state and attempt terminal restoration if we\n * were the recorded raw mode owner.\n */\n private restoreStdin(): void {\n const shared = getSharedState();\n\n // Remove handler if it exists\n if (this.keypressHandler) {\n try {\n process.stdin.off('keypress', this.keypressHandler);\n } catch {\n // Best effort - continue with cleanup even if off() fails\n // This is extremely rare (stdin closed mid-operation)\n }\n this.keypressHandler = undefined;\n }\n\n // Remove this instance from shared state even if we never registered a handler.\n // (Set.delete is a safe no-op if we weren't attached.)\n shared.attachedInstances.delete(this.instanceID);\n\n // Re-check ownership AFTER deletion to get accurate state\n // (avoids race where ownership is transferred to us between capture and deletion)\n const isLastInstance = shared.attachedInstances.size === 0;\n const isCurrentOwner = shared.rawModeOwner === this.instanceID;\n\n // If we were the raw mode owner but other instances remain, transfer ownership.\n // Use centralized helper to ensure atomic ownership transfer.\n if (!isLastInstance && isCurrentOwner && shared.rawModeEnabledByManager) {\n transferRawModeOwnership(shared, this.instanceID);\n } else if (\n !isLastInstance &&\n shared.rawModeOwner !== null &&\n shared.rawModeEnabledByManager &&\n !shared.attachedInstances.has(shared.rawModeOwner)\n ) {\n // Defensive: if ownership somehow points to a detached instance, re-anchor it.\n // Find any valid attached instance to take ownership.\n for (const candidateID of shared.attachedInstances) {\n if (shared.attachedInstances.has(candidateID)) {\n shared.rawModeOwner = candidateID;\n break;\n }\n }\n }\n\n // Restore raw mode when last instance detaches, but only if we are the owner.\n // Re-check isCurrentOwner as ownership may have been transferred above (shouldn't happen)\n // if isLastInstance is true, but be defensive).\n if (\n isLastInstance &&\n shared.rawModeOwner === this.instanceID &&\n shared.rawModeEnabledByManager\n ) {\n try {\n if (process.stdin.isTTY && process.stdin.isRaw) {\n process.stdin.setRawMode(false);\n }\n shared.rawModeOwner = null;\n shared.rawModeEnabledByManager = false;\n } catch {\n // If setRawMode fails, leave the owner set so future detaches can retry\n // Terminal will be restored on process exit anyway\n }\n }\n\n // Pause stdin when last instance detaches\n if (isLastInstance) {\n try {\n process.stdin.pause();\n } catch {\n // Best effort - stdin staying active without handlers is harmless\n }\n }\n }\n}\n","import { removeEmptyStringsFromArray } from './arrays';\n\nexport function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Converts a string or an array of strings to Pascal Case.\n *\n * This function takes an input string or an array of strings, each potentially containing hyphens,\n * and converts them to Pascal Case. It removes any characters that are not letters or numbers,\n * capitalizes the first letter of each substring, and ensures the rest of the substring\n * is in lowercase. Finally, it concatenates all these substrings to produce a Pascal Case\n * output.\n *\n * @param {string | string[]} input - The input string or array of strings to be converted to Pascal Case.\n * @returns {string} The converted string in Pascal Case.\n *\n * Examples:\n * toPascalCase(\"hello-world-123!$\") will return \"HelloWorld123\"\n * toPascalCase([\"hello\", \"world-123!$\"]) will return \"HelloWorld123\"\n */\n\nexport function toPascalCase(input: string | string[]): string {\n // Ensure input is an array\n const inputArray = Array.isArray(input) ? input : [input];\n\n // Process each string in the array\n const parts: string[] = [];\n\n for (const item of inputArray) {\n // Clean the string, split by hyphen, and remove empty strings\n const cleanedItem = item.replace(/[^a-zA-Z0-9-]/g, '');\n parts.push(...removeEmptyStringsFromArray(cleanedItem.split('-')));\n }\n\n // Process and rejoin the input strings\n return parts\n .map(\n (subString) =>\n subString.charAt(0).toUpperCase() + subString.slice(1).toLowerCase(),\n )\n .join('');\n}\n\n/**\n * Converts a string or an array of strings to Camel Case.\n *\n * This function takes an input string or an array of strings, each potentially containing hyphens,\n * and converts them to Camel Case. It removes any characters that are not letters or numbers,\n * capitalizes the first letter of each substring after the first one, and ensures the rest of the substring\n * is in lowercase. For the first substring, it ensures the entire substring is in lowercase.\n * Finally, it concatenates all these substrings to produce a Camel Case output.\n *\n * @param {string | string[]} input - The input string or array of strings to be converted to Camel Case.\n * @returns {string} The converted string in Camel Case.\n *\n * Examples:\n * toCamelCase(\"hello-world-123!$\") will return \"helloWorld123\"\n * toCamelCase([\"hello\", \"world-123!$\"]) will return \"helloWorld123\"\n */\n\nexport function toCamelCase(input: string | string[]): string {\n // Ensure input is an array\n const inputArray = Array.isArray(input) ? input : [input];\n\n // Process each string in the array\n const parts: string[] = [];\n\n for (const item of inputArray) {\n // Clean the string, split by hyphen, and remove empty strings\n const cleanedItem = item.replace(/[^a-zA-Z0-9-]/g, '');\n parts.push(...removeEmptyStringsFromArray(cleanedItem.split('-')));\n }\n\n // Process and rejoin the input strings\n return parts\n .map((subString, index) =>\n index === 0\n ? subString.toLowerCase()\n : subString.charAt(0).toUpperCase() + subString.slice(1).toLowerCase(),\n )\n .join('');\n}\n\n/**\n * This method converts a string or an array of strings to camel case,\n * but if starting with a leading hyphen, it will convert to Pascal case.\n */\n\nexport function toCamelCaseWithPascalOverride(\n input: string | string[],\n): string {\n if (isString(input) && input.startsWith('-')) {\n return toPascalCase(input);\n } else if (\n Array.isArray(input) &&\n input.length > 0 &&\n input[0].startsWith('-')\n ) {\n return toPascalCase(input);\n } else {\n return toCamelCase(input);\n }\n}\n\n/**\n * Converts a string or an array of strings to constant case.\n *\n * The function takes a string or an array of strings, where each string can be separated by a '-',\n * and converts them into a constant case format (all uppercase with underscores between words).\n * It first cleans the input by removing non-alphanumeric characters (except for hyphens), splits the\n * string into parts on hyphens, and then joins these parts with underscores, converting the entire\n * result to uppercase.\n *\n * @param {string | string[]} input - The input string or array of strings to be converted.\n * @returns {string} The converted string in constant case.\n *\n * Example:\n * toConstantCase(\"hello-world\") will return \"HELLO_WORLD\"\n * toConstantCase([\"hello\", \"world\"]) will return \"HELLO_WORLD\"\n */\n\nexport function toConstantCase(input: string | string[]): string {\n // Ensure input is an array\n const inputArray = Array.isArray(input) ? input : [input];\n\n // Process each string in the array\n let parts: string[] = [];\n\n for (const item of inputArray) {\n // Clean the string and split by hyphen\n const cleanedItem = item.replace(/[^a-zA-Z0-9-]/g, '');\n parts.push(...cleanedItem.split('-'));\n }\n\n // Remove empty strings from the array\n parts = removeEmptyStringsFromArray(parts);\n\n // Join parts with underscore and convert to uppercase\n return parts.join('_').toUpperCase();\n}\n\nexport function splitGraphemes(text: string): string[] {\n const graphemes: string[] = [];\n let grapheme = '';\n let zwjSequence = '';\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const nextChar = text[i + 1] || '';\n const code = char.charCodeAt(0);\n\n // Handling combining marks and zero width joiner\n if (\n (code >= 0x0300 && code <= 0x036f) || // Combining Diacritical Marks\n (code >= 0x1ab0 && code <= 0x1aff) || // Combining Diacritical Marks Extended\n (code >= 0x1dc0 && code <= 0x1dff) || // Combining Diacritical Marks Supplement\n (code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks\n (code >= 0x0e31 && code <= 0x0e3a) || // Thai combining marks\n (code >= 0x0e47 && code <= 0x0e4e)\n ) {\n // Thai combining marks\n grapheme += char;\n } else if (char === '\\u200d') {\n // Zero Width Joiner (ZWJ)\n zwjSequence += grapheme + char;\n grapheme = '';\n } else {\n if (grapheme) {\n if (zwjSequence) {\n graphemes.push(zwjSequence + grapheme);\n zwjSequence = '';\n } else {\n graphemes.push(grapheme);\n }\n }\n grapheme = char;\n\n // Handle surrogate pairs (needed for certain characters including emojis)\n if (\n char >= '\\ud800' &&\n char <= '\\udbff' &&\n nextChar >= '\\udc00' &&\n nextChar <= '\\udfff'\n ) {\n grapheme += nextChar;\n i++;\n }\n }\n }\n\n if (grapheme) {\n if (zwjSequence) {\n graphemes.push(zwjSequence + grapheme);\n } else {\n graphemes.push(grapheme);\n }\n }\n\n return graphemes;\n}\n\nexport function skipTrailingNewLines(str: string): string {\n return str.replace(/\\n+$/, '');\n}\n\n/**\n * Filters a string to include only specified characters, optionally replacing disallowed characters.\n *\n * @param str - The input string to be filtered.\n * @param list - An array of allowed characters.\n * @param caseInsensitive - Optional. If true, the filtering is case-insensitive. Default is false.\n * @param replacementChar - Optional. Character to replace disallowed characters. If empty, disallowed characters are removed. Default is ''.\n * @returns A new string containing only the allowed characters from the input string, with disallowed characters optionally replaced.\n *\n * @example\n * // Case-sensitive usage, removing disallowed characters\n * characterAllowedOnly(\"Hello123!\", [\"H\", \"e\", \"l\", \"o\"]);\n * // Returns: \"Hello\"\n *\n * @example\n * // Case-insensitive usage, removing disallowed characters\n * characterAllowedOnly(\"Hello123!\", [\"h\", \"E\", \"L\", \"O\"], true);\n * // Returns: \"Hello\"\n *\n * @example\n * // Using replacement character\n * characterAllowedOnly(\"Hello123!\", [\"H\", \"e\", \"l\", \"o\"], false, \"-\");\n * // Returns: \"Hello---\"\n */\n\nexport function characterAllowedOnly(\n str: string,\n list: string[],\n // eslint-disable-next-line @typescript-eslint/naming-convention\n caseInsensitive = false,\n replacementChar = '',\n): string {\n let newStr = '';\n\n // Convert the allowed list to lowercase if case-insensitive\n if (caseInsensitive) {\n list = Array.from(new Set(list.map((item) => item.toLowerCase())));\n }\n\n // Convert the entire input string to lowercase if case-insensitive\n const processedStr = caseInsensitive ? str.toLowerCase() : str;\n\n for (const c of processedStr) {\n if (list.includes(c)) {\n newStr += c;\n } else if (replacementChar !== '') {\n newStr += replacementChar;\n }\n }\n\n return newStr;\n}\n\n// functions to chop characters from a string.\n\n/**\n * Will remove the matching first character from a string\n * @param str\n * @param char\n * @returns\n */\n\nexport function chopBeginningCharacter(str: string, char: string): string {\n if (str.startsWith(char)) {\n return str.slice(1);\n } else {\n return str;\n }\n}\n\n/**\n * Will remove the matching last character from the string\n * @param str\n * @param char\n * @returns\n */\nexport function chopEndingCharacter(str: string, char: string): string {\n if (str.endsWith(char)) {\n return str.slice(0, -1);\n } else {\n return str;\n }\n}\n\n/**\n * Will remove the matching character, from the beginning and/or end of the string if matching\n * @param str\n * @param char\n * @returns\n */\n\nexport function chopBothBeginningAndEndingCharacters(\n str: string,\n char: string,\n): string {\n return chopBeginningCharacter(chopEndingCharacter(str, char), char);\n}\n","// prettier-ignore\nexport const BLANK_SPACE = ' ';\n\nexport const EOL = '\\n';\nexport const DOUBLE_EOL = EOL + EOL;\nexport const INDENT = ' '.repeat(4);\nexport const DOUBLE_INDENT = INDENT + INDENT;\n\n// prettier-ignore\nexport const SINGLE_QUOTE = \"'\";\n\n// similar to Python string library\nexport const ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';\nexport const ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\nexport const ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE;\nexport const DIGITS = '0123456789';\nexport const HEX_DIGITS = DIGITS + 'abcdefABCDEF';\nexport const OCT_DIGITS = '01234567';\nexport const PUNCTUATION = '!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~';\nexport const WHITESPACE = ' \\t\\n\\r\\v\\f';\nexport const PRINTABLE = DIGITS + ASCII_LETTERS + PUNCTUATION + WHITESPACE;\n","/**\n * Utilities to pad strings with another string up to a defined length.\n * Each function takes a string, length to pad and the string to be used for padding (but defaults to a blank space if not provided) and returns the modified string\n *\n * This only pads on the left side, so text is added to the front until the total string equals the given length.\n *\n * ```typescript\n * padLeft('Hey', 6, '*'); // returns ***Hey\n * ```\n * @category String Padding\n */\n\nimport { BLANK_SPACE } from './constants';\n\nexport function padLeft(\n str: string,\n length: number,\n padStr = BLANK_SPACE,\n): string {\n return str.padStart(length, padStr);\n}\n\n/**\n * This only pads on the right side, so text is added to the end until the total string equals the given length.\n *\n * ```typescript\n * padRight('Hey', 6, '*'); // returns Hey***\n * ```\n *\n * @category String Padding\n */\n\nexport function padRight(\n str: string,\n length: number,\n padStr = BLANK_SPACE,\n): string {\n return str.padEnd(length, padStr);\n}\n\n/**\n * Same as `padCenterPreferLeft` and `padCenterPreferRight` but you can pass in a string with `left` or `right` before the padStr to use more direct.\n *\n * Defaults to `left`\n *\n * @category String Padding\n */\n\nexport function padCenter(\n str: string,\n length: number,\n prefer: 'left' | 'right' = 'left',\n padStr = BLANK_SPACE,\n): string {\n const midStrLength = length - str.length;\n\n if (midStrLength > 0) {\n const padLeftAmount =\n prefer === 'left'\n ? Math.ceil(midStrLength / 2)\n : Math.floor(midStrLength / 2);\n\n const padRightAmount =\n prefer === 'left'\n ? Math.floor(midStrLength / 2)\n : Math.ceil(midStrLength / 2);\n\n return (\n padLeft('', padLeftAmount, padStr) +\n str +\n padRight('', padRightAmount, padStr)\n );\n } else {\n return str;\n }\n}\n\n/**\n * It tries to pad equally on both sides in an attempt to center your text. However if it can't the extra character will be added to the left side\n *\n * @category String Padding\n */\n\nexport function padCenterPreferLeft(\n str: string,\n length: number,\n padStr = BLANK_SPACE,\n): string {\n return padCenter(str, length, 'left', padStr);\n}\n\n/**\n * It tries to pad equally, but if it can't the extra character will be added to the right side\n *\n * @category String Padding\n */\n\nexport function padCenterPreferRight(\n str: string,\n length: number,\n padStr = BLANK_SPACE,\n): string {\n return padCenter(str, length, 'right', padStr);\n}\n","import { padCenterPreferRight, padRight } from '../padding-utils';\nimport stringWidth from 'string-width';\nimport { splitGraphemes } from '../strings';\n\nexport class ASCIITableUtils {\n public static centerText(text: string, width: number): string {\n return padCenterPreferRight(text, width, ' ');\n }\n\n public static createSeparator(\n columnWidths: number[],\n character: string = '=',\n ): string {\n const totalWidth =\n columnWidths.reduce((sum, width) => sum + width + 3, 0) - 1;\n\n return `+${padRight('', totalWidth, character)}+`;\n }\n\n public static wrapText(text: string, maxLength: number): string[] {\n const words = text.split(' ');\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (stringWidth(currentLine) + stringWidth(word) + 1 <= maxLength) {\n currentLine += (currentLine ? ' ' : '') + word;\n } else {\n if (currentLine) {\n lines.push(currentLine);\n }\n\n if (stringWidth(word) <= maxLength) {\n currentLine = word;\n } else {\n const subWords = ASCIITableUtils.splitWord(word, maxLength);\n lines.push(...subWords.slice(0, -1));\n currentLine = subWords[subWords.length - 1];\n }\n }\n }\n\n if (currentLine) {\n lines.push(currentLine);\n }\n\n return lines;\n }\n\n public static splitWord(word: string, maxLength: number): string[] {\n const graphemes = splitGraphemes(word);\n const subWords: string[] = [];\n let currentSubWord = '';\n\n for (const grapheme of graphemes) {\n if (stringWidth(currentSubWord + grapheme) <= maxLength) {\n currentSubWord += grapheme;\n } else {\n subWords.push(currentSubWord);\n currentSubWord = grapheme;\n }\n }\n\n if (currentSubWord) {\n subWords.push(currentSubWord);\n }\n\n return subWords;\n }\n}\n","import { ASCIITableUtils } from './ascii-table-utils';\nimport stringWidth from 'string-width';\n\ninterface MultiColumnASCIITableOptions {\n tableWidth?: number;\n emptyMessage?: string;\n widthMode?: 'flex' | 'fixed';\n}\n\nexport class MultiColumnASCIITable {\n private headers: string[];\n private rows: string[][];\n private tableWidth: number;\n private emptyMessage: string;\n private widthMode: 'flex' | 'fixed';\n\n constructor(headers: string[], options: MultiColumnASCIITableOptions = {}) {\n this.headers = headers;\n this.rows = [];\n this.tableWidth = options.tableWidth || 80;\n this.emptyMessage = options.emptyMessage || '';\n this.widthMode = options.widthMode || 'flex';\n\n const minTableWidth = this.getMinimumWidth();\n\n if (this.tableWidth < minTableWidth) {\n throw new Error(\n `Table width must be at least ${minTableWidth} to accommodate the headers.`,\n );\n }\n }\n\n public getMinimumWidth(): number {\n return this.headers.length * 4 + 1;\n }\n\n public addRow(row: string[]): void {\n if (row.length !== this.headers.length) {\n throw new Error(\n `Number of values in the row (${row.length}) must match the number of headers (${this.headers.length}).`,\n );\n }\n\n this.rows.push(row);\n }\n\n public toString(options: MultiColumnASCIITableOptions = {}): string {\n const tableWidth = options.tableWidth || this.tableWidth;\n const emptyMessage = options.emptyMessage || this.emptyMessage;\n\n if (this.rows.length === 0) {\n const emptyTableWidth = Math.min(tableWidth, 40);\n\n const separator = '+' + '-'.repeat(emptyTableWidth - 2) + '+';\n const emptyMessageLines = ASCIITableUtils.wrapText(\n emptyMessage,\n emptyTableWidth - 4,\n );\n\n const emptyRows = emptyMessageLines.map((line) => {\n const paddingLeft = ' '.repeat(\n Math.floor((emptyTableWidth - stringWidth(line) - 4) / 2),\n );\n\n const paddingRight = ' '.repeat(\n Math.ceil((emptyTableWidth - stringWidth(line) - 4) / 2),\n );\n\n return `| ${paddingLeft}${line}${paddingRight} |`;\n });\n\n if (emptyRows.length === 0) {\n emptyRows.push(`| ${' '.repeat(emptyTableWidth - 4)} |`);\n }\n\n return [separator, ...emptyRows, separator].join('\\n');\n }\n\n const columnWidths = this.calculateColumnWidths(options);\n\n const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);\n const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, '-');\n let tableString = headerSeparator + '\\n';\n\n const header = this.renderRow(this.headers, columnWidths);\n tableString += header + '\\n' + rowSeparator + '\\n';\n\n const rows = this.rows.map((row) => {\n return this.renderRow(row, columnWidths);\n });\n\n tableString += rows.join('\\n' + rowSeparator + '\\n');\n tableString += '\\n' + headerSeparator;\n\n return tableString;\n }\n\n public calculateColumnWidths(\n options: MultiColumnASCIITableOptions = {},\n ): number[] {\n const tableWidth = options.tableWidth || this.tableWidth;\n const widthMode = options.widthMode || this.widthMode;\n\n const numColumns = this.headers.length;\n\n if (widthMode === 'fixed') {\n const availableWidth = tableWidth - (numColumns + 1) * 3 + 1;\n const columnWidth = Math.floor(availableWidth / numColumns);\n const extraWidth = availableWidth % numColumns;\n const columnWidths = new Array(numColumns).fill(columnWidth);\n\n // if there is any remaining width (extraWidth), we distribute it evenly among the columns starting from the first column.\n for (let i = 0; i < extraWidth; i++) {\n columnWidths[i] += 1;\n }\n\n return columnWidths as number[];\n }\n\n const availableWidth = tableWidth - (numColumns + 1) * 3 + 1;\n const maxColumnWidth = Math.floor(availableWidth / numColumns);\n const totalContentWidth = this.headers.reduce(\n (sum, header) => sum + stringWidth(header),\n 0,\n );\n\n if (availableWidth >= totalContentWidth) {\n const remainingWidth = availableWidth - totalContentWidth;\n const extraCharWidth = Math.floor(remainingWidth / numColumns);\n const extraCharRemainder = remainingWidth % numColumns;\n\n const columnWidths = this.headers.map((header, index) => {\n const extraWidth = index < extraCharRemainder ? 1 : 0;\n return stringWidth(header) + extraCharWidth + extraWidth;\n });\n\n return columnWidths;\n } else {\n const columnWidths = this.headers.map(() => maxColumnWidth);\n\n return columnWidths;\n }\n }\n\n private renderRow(row: string[], columnWidths: number[]): string {\n const wrappedCells = row.map((value, index) => {\n const wrappedLines = ASCIITableUtils.wrapText(value, columnWidths[index]);\n\n return wrappedLines\n .map((line) => line.padEnd(columnWidths[index]))\n .join('\\n');\n });\n\n const maxLines = Math.max(\n ...wrappedCells.map((cell) => cell.split('\\n').length),\n );\n\n const paddedRows = [];\n\n for (let i = 0; i < maxLines; i++) {\n const rowLine = wrappedCells.map((cell, index) => {\n const cellLines = cell.split('\\n');\n const cellLine = cellLines[i] || '';\n const padding = ' '.repeat(columnWidths[index] - stringWidth(cellLine));\n\n return ' ' + cellLine + padding + ' ';\n });\n\n paddedRows.push('|' + rowLine.join('|') + '|');\n }\n\n return paddedRows.join('\\n');\n }\n}\n","import { isString } from '../strings';\nimport { padRight } from '../padding-utils';\nimport { MultiColumnASCIITable } from './multi-column-ascii-table';\nimport { ASCIITableUtils } from './ascii-table-utils';\nimport stringWidth from 'string-width';\nimport { clamp } from '../clamp';\n\nexport type TableRowValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | KeyValueASCIITable\n | MultiColumnASCIITable\n | NestedKeyValueEntry[];\n\ntype TableRow = TableRowRegular | TableRowOwn;\n\ninterface TableRowRegular {\n kind: 'regular';\n key: string;\n value: TableRowValue;\n}\n\ninterface TableRowOwn {\n kind: 'own';\n key: string;\n value: string | NestedKeyValueEntry[];\n}\n\nexport interface NestedKeyValueEntry {\n key: string;\n value: string | KeyValueASCIITable | NestedKeyValueEntry[];\n}\n\ninterface KeyValueASCIITableOptions {\n tableWidth?: number;\n autoAdjustWidthWhenPossible?: boolean;\n emptyMessage?: string;\n}\n\nexport class KeyValueASCIITable {\n public readonly tableWidth: number;\n private emptyMessage: string;\n private autoAdjustWidthWhenPossible: boolean = true;\n\n private rows: TableRow[] = [];\n\n constructor(options: KeyValueASCIITableOptions = {}) {\n const minTableWidth = this.getMinimumWidth();\n\n if (options.tableWidth && options.tableWidth < minTableWidth) {\n throw new Error(\n `Table width must be at least ${minTableWidth} to accommodate the table structure.`,\n );\n }\n\n this.tableWidth = options.tableWidth || 80;\n this.autoAdjustWidthWhenPossible =\n options.autoAdjustWidthWhenPossible ?? true;\n this.emptyMessage = options.emptyMessage || '';\n }\n\n public getMinimumWidth(): number {\n // The minimum width for the KeyValueASCIITable is 9 characters:\n // - 2 character for the '| ' at the start\n // - 1 character for the minimum key column width\n // - 3 character for the ' | ' separating the key and value columns\n // - 1 character for the minimum value column width\n // - 2 character for the ' |' at the end\n\n return 9;\n }\n\n /**\n * Adds key and value to the table but placing the value on its own row.\n *\n * @param key\n * @param value\n */\n\n public addValueOnSeparateRow(key: string, value: string): void {\n const row: TableRow = { kind: 'own', key, value };\n\n this.rows.push(row);\n }\n\n /**\n * Adds key and value to the table.\n *\n * If provided value is an instance of ASCIITable or MultiColumnASCIITable, it will be rendered as a nested table on its own row for readability.\n *\n * @param key\n * @param value\n */\n\n public addRow(key: string, value: TableRowValue): void {\n const row: TableRow = { kind: 'regular', key, value };\n\n this.rows.push(row);\n }\n\n public toString(options: KeyValueASCIITableOptions = {}): string {\n const tableWidth = options.tableWidth || this.tableWidth;\n const canAutoAdjustWidthWhenPossible =\n options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;\n\n const emptyMessage = options.emptyMessage || this.emptyMessage;\n\n if (this.rows.length === 0) {\n const emptyTableWidth = Math.min(tableWidth, 40);\n\n const separator = '+' + '-'.repeat(emptyTableWidth - 2) + '+';\n const emptyMessageLines = ASCIITableUtils.wrapText(\n emptyMessage,\n emptyTableWidth - 4,\n );\n\n const emptyRows = emptyMessageLines.map((line) => {\n const paddingLeft = padRight(\n '',\n Math.floor((emptyTableWidth - stringWidth(line) - 4) / 2),\n ' ',\n );\n\n const paddingRight = padRight(\n '',\n Math.ceil((emptyTableWidth - stringWidth(line) - 4) / 2),\n ' ',\n );\n\n return `| ${paddingLeft}${line}${paddingRight} |`;\n });\n\n if (emptyRows.length === 0) {\n emptyRows.push(`| ${' '.repeat(emptyTableWidth - 4)} |`);\n }\n\n return [separator, ...emptyRows, separator].join('\\n');\n }\n\n const columnWidths = this.calculateColumnWidths(options);\n\n const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);\n const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, '-');\n\n let tableString = headerSeparator + '\\n';\n\n for (const [rowIndex, row] of this.rows.entries()) {\n const { kind, key, value } = row;\n\n if (\n kind === 'own' ||\n value instanceof KeyValueASCIITable ||\n value instanceof MultiColumnASCIITable ||\n Array.isArray(value)\n ) {\n const keyString = ASCIITableUtils.centerText(\n key,\n columnWidths[0] + columnWidths[1] + 3,\n );\n\n tableString += `| ${keyString} |\\n`;\n tableString += rowSeparator + '\\n';\n\n let valueString = '';\n\n if (value instanceof KeyValueASCIITable) {\n valueString = this.formatValue(\n value,\n tableWidth - 4,\n canAutoAdjustWidthWhenPossible,\n '',\n );\n } else if (value instanceof MultiColumnASCIITable) {\n valueString = this.formatValue(\n value,\n tableWidth - 4,\n canAutoAdjustWidthWhenPossible,\n '',\n );\n } else if (Array.isArray(value)) {\n valueString = this.formatValue(\n value,\n tableWidth - 4,\n canAutoAdjustWidthWhenPossible,\n '',\n );\n } else if (row.kind === 'own') {\n valueString = this.formatTableRowOnOwnRow(\n value as string,\n tableWidth - 4,\n tableWidth,\n );\n }\n\n const valueLines = valueString.split('\\n');\n const paddedValueLines = valueLines.map((line) => {\n const padding = padRight('', tableWidth - stringWidth(line) - 4, ' ');\n\n return `| ${line}${padding} |`;\n });\n\n tableString += paddedValueLines.join('\\n') + '\\n';\n tableString += headerSeparator + '\\n';\n } else {\n const keyLines = ASCIITableUtils.wrapText(key, columnWidths[0]);\n\n const valueLines = ASCIITableUtils.wrapText(\n this.formatValue(\n value,\n columnWidths[1],\n canAutoAdjustWidthWhenPossible,\n '',\n ),\n columnWidths[1],\n );\n\n const maxLines = Math.max(keyLines.length, valueLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const keyLine = keyLines[i] || '';\n const valueLine = valueLines[i] || '';\n\n const keyPadding = ' '.repeat(columnWidths[0] - stringWidth(keyLine));\n\n const valuePadding = ' '.repeat(\n columnWidths[1] - stringWidth(valueLine),\n );\n\n tableString += `| ${keyLine}${keyPadding} | ${valueLine}${valuePadding} |\\n`;\n\n if (i === maxLines - 1) {\n if (rowIndex === this.rows.length - 1) {\n tableString += headerSeparator + '\\n';\n } else {\n tableString += rowSeparator + '\\n';\n }\n }\n }\n }\n }\n\n return tableString.trim();\n }\n\n private calculateColumnWidths(\n options: KeyValueASCIITableOptions = {},\n ): number[] {\n const tableWidth = options.tableWidth || this.tableWidth;\n const canAutoAdjustWidthWhenPossible =\n options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;\n\n const columnWidths: number[] = [0, 0];\n\n for (const row of this.rows) {\n const { key, value } = row;\n\n const keyWidth = stringWidth(key);\n const maxKeyWidth = Math.floor((tableWidth - 7) / 2);\n\n if (keyWidth > columnWidths[0]) {\n columnWidths[0] = Math.min(keyWidth, maxKeyWidth);\n columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);\n }\n\n if (typeof value === 'string') {\n const valueWidth = Math.max(\n ...value.split('\\n').map((line) => stringWidth(line)),\n );\n\n if (valueWidth > columnWidths[1]) {\n columnWidths[1] = Math.min(\n valueWidth,\n tableWidth - columnWidths[0] - 7,\n );\n\n columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);\n }\n } else if (row.kind === 'own') {\n let valueWidth = 0;\n\n if (isString(value)) {\n valueWidth = Math.max(\n ...value.split('\\n').map((line) => stringWidth(line)),\n );\n }\n\n if (valueWidth > columnWidths[1]) {\n columnWidths[1] = Math.min(\n valueWidth,\n tableWidth - columnWidths[0] - 7,\n );\n\n columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);\n }\n } else if (value instanceof KeyValueASCIITable) {\n // Update column widths based on the nested table\n let nestedTableColumnWidths: number[] = [];\n\n if (canAutoAdjustWidthWhenPossible) {\n const minWidth = value.getMinimumWidth();\n\n const availableWidth = tableWidth - columnWidths[0] - 7;\n const adjustedWidth = clamp(availableWidth, minWidth, availableWidth);\n\n nestedTableColumnWidths = value.calculateColumnWidths({\n tableWidth: adjustedWidth,\n });\n } else {\n nestedTableColumnWidths = value.calculateColumnWidths();\n }\n\n const nestedTableWidth =\n nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) +\n nestedTableColumnWidths.length * 3 -\n 1;\n\n const availableWidth = tableWidth - columnWidths[0] - 7;\n\n if (nestedTableWidth > availableWidth) {\n columnWidths[1] = availableWidth;\n } else {\n columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);\n }\n } else if (value instanceof MultiColumnASCIITable) {\n // Update column widths based on the nested multi-column table\n let nestedTableColumnWidths: number[] = [];\n\n if (canAutoAdjustWidthWhenPossible) {\n const minWidth = value.getMinimumWidth();\n\n const availableWidth = tableWidth - columnWidths[0] - 7;\n const adjustedWidth = clamp(availableWidth, minWidth, availableWidth);\n\n nestedTableColumnWidths = value.calculateColumnWidths({\n tableWidth: adjustedWidth,\n });\n } else {\n nestedTableColumnWidths = value.calculateColumnWidths();\n }\n\n const nestedTableWidth =\n nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) +\n nestedTableColumnWidths.length * 3 -\n 1;\n\n const availableWidth = tableWidth - columnWidths[0] - 7;\n\n if (nestedTableWidth > availableWidth) {\n columnWidths[1] = availableWidth;\n } else {\n columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);\n }\n } else if (Array.isArray(value)) {\n for (const nestedCell of value) {\n const nestedKeyWidth = stringWidth(nestedCell.key);\n const maxNestedKeyWidth = Math.floor((tableWidth - 7) / 2);\n\n if (nestedKeyWidth > columnWidths[0]) {\n columnWidths[0] = Math.min(nestedKeyWidth, maxNestedKeyWidth);\n columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);\n }\n\n if (typeof nestedCell.value === 'string') {\n const nestedValueWidth = Math.max(\n ...nestedCell.value.split('\\n').map((line) => stringWidth(line)),\n );\n\n if (nestedValueWidth > columnWidths[1]) {\n columnWidths[1] = Math.min(\n nestedValueWidth,\n tableWidth - columnWidths[0] - 7,\n );\n columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);\n }\n }\n }\n }\n }\n\n return columnWidths;\n }\n\n private formatValue(\n value:\n | string\n | number\n | boolean\n | null\n | undefined\n | KeyValueASCIITable\n | MultiColumnASCIITable\n | NestedKeyValueEntry[],\n cellWidth: number,\n canAutoAdjustWidthWhenPossible: boolean,\n indent = '',\n ): string {\n if (typeof value === 'string') {\n return value;\n } else if (typeof value === 'number') {\n return String(value);\n } else if (typeof value === 'boolean') {\n return String(value);\n } else if (value === null) {\n return 'null';\n } else if (value === undefined) {\n return 'undefined';\n } else if (value instanceof KeyValueASCIITable) {\n let nestedTableLines: string[];\n\n if (canAutoAdjustWidthWhenPossible) {\n const minWidth = value.getMinimumWidth();\n\n const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);\n\n nestedTableLines = value\n .toString({ tableWidth: adjustedWidth })\n .split('\\n');\n } else {\n nestedTableLines = value.toString().split('\\n');\n }\n\n const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);\n\n return indentedLines.join('\\n');\n } else if (value instanceof MultiColumnASCIITable) {\n let nestedTableLines: string[];\n\n if (canAutoAdjustWidthWhenPossible) {\n const minWidth = value.getMinimumWidth();\n\n const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);\n nestedTableLines = value\n .toString({ tableWidth: adjustedWidth })\n .split('\\n');\n } else {\n nestedTableLines = value.toString().split('\\n');\n }\n\n const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);\n\n return indentedLines.join('\\n');\n } else if (Array.isArray(value)) {\n const nestedValueLines: string[] = [];\n\n for (const { key, value: nestedValue } of value) {\n const formattedKey = `${indent}${key}:`;\n const formattedValue = this.formatValue(\n nestedValue,\n cellWidth - indent.length - stringWidth(key) - 2,\n canAutoAdjustWidthWhenPossible,\n `${indent}`,\n );\n\n const wrappedSpacer = padRight('', 4, ' ');\n\n const wrappedValue = formattedValue\n .split('\\n')\n .map((line) => `${indent}${wrappedSpacer}${line}`);\n\n nestedValueLines.push(formattedKey);\n nestedValueLines.push(...wrappedValue);\n nestedValueLines.push('');\n }\n\n return nestedValueLines.slice(0, -1).join('\\n');\n } else {\n throw new TypeError('Invalid value type provided');\n }\n }\n\n private formatTableRowOnOwnRow(\n value: string,\n width: number,\n maxRowLength: number,\n ): string {\n const lines = value.split('\\n');\n\n const paddedLines = lines.map((line) => {\n const wrappedLines = ASCIITableUtils.wrapText(line, maxRowLength - 4);\n\n return wrappedLines\n .map((wrappedLine) => {\n const padding = padRight(\n '',\n width - stringWidth(wrappedLine) - 2,\n ' ',\n );\n\n return `${wrappedLine}${padding}`;\n })\n .join('\\n');\n });\n\n return paddedLines.join('\\n');\n }\n}\n","export function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max));\n}\n\n/**\n * Clamps a value to a minimum, returning a default if the value is not finite or is undefined/null.\n *\n * Useful for config/settings validation where you want to:\n * - Enforce a minimum value\n * - Handle invalid inputs (Infinity, NaN, undefined, null) gracefully\n *\n * @param value - The value to clamp (can be undefined or null)\n * @param min - The minimum allowed value\n * @param defaultValue - The default to return if value is not finite or is undefined/null\n * @returns The clamped value, or defaultValue if value is not finite/undefined/null\n *\n * @example\n * ```typescript\n * finiteClampMin(5000, 1000, 3000) // 5000 (value > min)\n * finiteClampMin(500, 1000, 3000) // 1000 (enforces min)\n * finiteClampMin(Infinity, 1000, 3000) // 3000 (not finite, use default)\n * finiteClampMin(NaN, 1000, 3000) // 3000 (not finite, use default)\n * finiteClampMin(undefined, 1000, 3000) // 3000 (undefined, use default)\n * finiteClampMin(null, 1000, 3000) // 3000 (null, use default)\n * ```\n */\nexport function finiteClampMin(\n value: number | undefined | null,\n min: number,\n defaultValue: number,\n): number {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return defaultValue;\n }\n\n return Math.max(value, min);\n}\n","import type { NestedKeyValueEntry } from './ascii-tables/key-value-ascii-table';\nimport { KeyValueASCIITable } from './ascii-tables/key-value-ascii-table';\n\nfunction safeStringify(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value);\n }\n\n switch (typeof value) {\n case 'string':\n return value;\n case 'number':\n case 'boolean':\n case 'bigint':\n return String(value);\n case 'object':\n return JSON.stringify(value);\n case 'function':\n return '[Function]';\n case 'symbol':\n return value.toString();\n default:\n // This should never happen, but satisfy the linter\n return String(value as string | number | boolean);\n }\n}\n\nexport function errorToString(error: unknown, maxRowLength = 80): string {\n const table = errorToASCIITable(error, maxRowLength);\n\n return table.toString();\n}\n\nfunction errorToASCIITable(\n error: unknown,\n maxRowLength: number,\n): KeyValueASCIITable {\n const table = new KeyValueASCIITable({\n tableWidth: maxRowLength,\n autoAdjustWidthWhenPossible: true,\n });\n\n if (error && typeof error === 'object') {\n const err = error as Record<string, unknown>;\n table.addRow('Key', 'Value');\n\n if (err['message']) {\n table.addRow('Message', safeStringify(err['message']));\n }\n\n if (err['name']) {\n table.addRow('Name', safeStringify(err['name']));\n }\n\n if (err['code']) {\n table.addRow('Code', safeStringify(err['code']));\n }\n\n if (err['errno']) {\n table.addRow('Errno', safeStringify(err['errno']));\n }\n\n // other conventional that might be used to enhance the error object\n if (err['errPrefix']) {\n table.addRow('Prefix', safeStringify(err['errPrefix']));\n }\n\n if (err['errType']) {\n table.addRow('errType', safeStringify(err['errType']));\n }\n\n if (err['errCode']) {\n table.addRow('errCode', safeStringify(err['errCode']));\n }\n\n if (err['additionalInfo']) {\n const additionalInfo = err['additionalInfo'] as Record<string, unknown>;\n const sensitiveFieldNames =\n (err['sensitiveFieldNames'] as string[]) || [];\n\n for (const key in additionalInfo) {\n if (sensitiveFieldNames.includes(key)) {\n table.addRow(`AdditionalInfo.${key}`, '***');\n } else {\n const value = additionalInfo[key];\n\n table.addRow(\n `AdditionalInfo.${key}`,\n stringifyValue(value, table, maxRowLength),\n );\n }\n }\n }\n\n if (err['stack']) {\n table.addValueOnSeparateRow('Stack', safeStringify(err['stack']));\n }\n }\n\n return table;\n}\n\nfunction stringifyValue(\n value: unknown,\n table: KeyValueASCIITable,\n maxRowLength: number,\n): string | KeyValueASCIITable | NestedKeyValueEntry[] {\n if (typeof value === 'string') {\n return value;\n } else if (Array.isArray(value)) {\n // Handle arrays differently\n return value\n .map((item) => {\n const result = stringifyValue(item, table, maxRowLength);\n // Convert complex types to strings for joining\n if (typeof result === 'string') {\n return result;\n } else if (result instanceof KeyValueASCIITable) {\n return result.toString();\n } else {\n return JSON.stringify(result);\n }\n })\n .join(', ');\n } else if (typeof value === 'object' && value !== null) {\n if (value instanceof Error) {\n return errorToASCIITable(value, maxRowLength - 4);\n } else {\n // Handle objects differently\n const entries: NestedKeyValueEntry[] = Object.entries(value).map(\n ([key, val]) => ({\n key,\n value: stringifyValue(val, table, maxRowLength - 4),\n }),\n );\n\n return entries;\n }\n } else {\n return String(value);\n }\n}\n","// Helper from https://github.com/then/is-promise/tree/master\n// For some reason the @types/is-promise package stopped being picked up on\n\nexport function isPromise(obj: unknown): obj is Promise<unknown> {\n return (\n !!obj &&\n (typeof obj === 'object' || typeof obj === 'function') &&\n // @ts-expect-error - obj is checked to be object/function, then property access works at runtime\n typeof obj['then'] === 'function'\n );\n}\n","export function isFunction(value: unknown): boolean {\n return typeof value === 'function' || value instanceof Function;\n}\n","import { errorToString } from './error-to-string';\nimport { isPromise } from './is-promise';\nimport { isFunction } from './is-function';\nimport { DOUBLE_EOL } from './constants';\n\n/**\n * Safely handles a callback function by catching any errors and reporting them\n * using the global `reportError` event (standard API available in Node.js 15+, Bun, Deno, and browsers).\n * This function can seamlessly handle both synchronous and asynchronous (Promise-based) callback functions.\n *\n * Errors are dispatched as ErrorEvent objects with type 'reportError' via `globalThis.dispatchEvent()`.\n * You can listen for these errors using `globalThis.addEventListener('reportError', handler)`.\n *\n * This function is a \"fire-and-forget\" type of function, meaning it doesn't wait\n * for the callback to complete and doesn't return any result or error. If you need\n * to handle the result or error of the callback, consider using the\n * `safeHandleCallbackAndWait` function instead.\n *\n * @param {string} callbackName - The name of the callback function, used for error reporting.\n * @param {unknown} callback - The callback function to be executed. It can be either a\n * synchronous function or a function that returns a Promise.\n * @param {...unknown[]} args - Additional arguments to pass to the callback function.\n */\n\nexport function safeHandleCallback(\n callbackName: string,\n callback: unknown,\n ...args: unknown[]\n): void {\n const handleError = (error: Error): void => {\n // Dispatch error using the standard reportError event API\n // Available in Node.js 15+, Bun, Deno, and browsers\n if (\n typeof (globalThis as Record<string, unknown>).dispatchEvent ===\n 'function'\n ) {\n (\n globalThis as unknown as {\n dispatchEvent: (event: Event) => void;\n }\n ).dispatchEvent(\n new ErrorEvent('reportError', {\n error: new Error(\n `Error in a callback ${callbackName}: ${DOUBLE_EOL}${errorToString(error)}`,\n ),\n }),\n );\n }\n };\n\n if (isFunction(callback)) {\n try {\n // We need to cast callback to the appropriate function type now\n const result = (callback as (...args: unknown[]) => unknown)(...args);\n\n if (isPromise(result)) {\n // Fire-and-forget async callback\n result.catch((error: unknown) => {\n handleError(error as Error);\n });\n }\n } catch (error) {\n handleError(error as Error);\n }\n } else {\n handleError(\n new Error(`Callback provided for ${callbackName} is not a function`),\n );\n }\n}\n\ninterface CallbackResult<T> {\n success: boolean;\n value?: T;\n error?: Error;\n}\n\n/**\n * Safely handles a callback function by catching any errors and reporting them\n * using the global `reportError` event (standard API available in Node.js 15+, Bun, Deno, and browsers).\n * This function can seamlessly handle both synchronous and asynchronous (Promise-based) callback\n * functions, and it waits for the callback to complete before returning the result or an error.\n *\n * Errors are dispatched as ErrorEvent objects with type 'reportError' via `globalThis.dispatchEvent()`.\n * You can listen for these errors using `globalThis.addEventListener('reportError', handler)`.\n *\n * @param {string} callbackName - The name of the callback function, used for error reporting.\n * @param {unknown} callback - The callback function to be executed. It can be either a\n * synchronous function or a function that returns a Promise.\n * @param {...unknown[]} args - Additional arguments to pass to the callback function.\n * @returns {Promise<CallbackResult<unknown>>} - A promise that resolves with an object containing\n * the success status, value (if any), and error (if any).\n */\n\nexport async function safeHandleCallbackAndWait<T>(\n callbackName: string,\n callback: unknown,\n ...args: unknown[]\n): Promise<CallbackResult<T>> {\n const handleError = (error: Error): CallbackResult<T> => {\n // Dispatch error using the standard reportError event API\n // Available in Node.js 15+, Bun, Deno, and browsers\n if (\n typeof (globalThis as Record<string, unknown>).dispatchEvent ===\n 'function'\n ) {\n (\n globalThis as unknown as {\n dispatchEvent: (event: Event) => void;\n }\n ).dispatchEvent(\n new ErrorEvent('reportError', {\n error: new Error(\n `Error in a callback ${callbackName}: ${DOUBLE_EOL}${errorToString(error)}`,\n ),\n }),\n );\n }\n\n return { success: false, error };\n };\n\n if (isFunction(callback)) {\n try {\n // We need to cast callback to the appropriate function type now\n const result = (callback as (...args: unknown[]) => unknown)(...args);\n\n if (isPromise(result)) {\n // Wait for the async callback to complete\n const value = await (result as Promise<T>);\n\n return { success: true, value };\n } else {\n return { success: true, value: result as T };\n }\n } catch (error) {\n return handleError(error as Error);\n }\n } else {\n return handleError(\n new Error(`Callback provided for ${callbackName} is not a function`),\n );\n }\n}\n"],"mappings":"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","names":["stringWidth","import_string_width","stringWidth","availableWidth","import_string_width","stringWidth","availableWidth","readline"]}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shutdown signal types that can trigger the shutdown callback
|
|
3
|
+
*/
|
|
4
|
+
type ShutdownSignal = 'SIGINT' | 'SIGTERM' | 'SIGTRAP';
|
|
5
|
+
/**
|
|
6
|
+
* Status information about what the manager is attached to
|
|
7
|
+
*/
|
|
8
|
+
interface ProcessSignalManagerStatus {
|
|
9
|
+
/**
|
|
10
|
+
* Whether the manager is currently attached to signals and keypresses
|
|
11
|
+
*/
|
|
12
|
+
isAttached: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Which handlers are registered
|
|
15
|
+
*/
|
|
16
|
+
handlers: {
|
|
17
|
+
/**
|
|
18
|
+
* Whether a shutdown handler is registered
|
|
19
|
+
*/
|
|
20
|
+
shutdown: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a reload handler is registered
|
|
23
|
+
*/
|
|
24
|
+
reload: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether an info handler is registered
|
|
27
|
+
*/
|
|
28
|
+
info: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a debug handler is registered
|
|
31
|
+
*/
|
|
32
|
+
debug: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What events are currently being listened for (only populated when isListening is true)
|
|
36
|
+
*/
|
|
37
|
+
listeningFor: {
|
|
38
|
+
/**
|
|
39
|
+
* Listening for shutdown signals (SIGINT, SIGTERM, SIGTRAP)
|
|
40
|
+
*/
|
|
41
|
+
shutdownSignals: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Listening for reload signal (SIGHUP)
|
|
44
|
+
*/
|
|
45
|
+
reloadSignal: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Listening for info signal (SIGUSR1)
|
|
48
|
+
*/
|
|
49
|
+
infoSignal: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Listening for debug signal (SIGUSR2)
|
|
52
|
+
*/
|
|
53
|
+
debugSignal: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Listening for keypresses (Ctrl+C, Escape, R, I, D)
|
|
56
|
+
*/
|
|
57
|
+
keypresses: boolean;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Configuration options for ProcessSignalManager
|
|
62
|
+
*/
|
|
63
|
+
interface ProcessSignalManagerOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Optional callback invoked when a shutdown signal is received.
|
|
66
|
+
*
|
|
67
|
+
* Triggered by:
|
|
68
|
+
* - Process signals: SIGINT, SIGTERM, SIGTRAP
|
|
69
|
+
* - Keyboard: Ctrl+C, Escape
|
|
70
|
+
*/
|
|
71
|
+
onShutdownRequested?: (method: ShutdownSignal) => void | Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Optional callback invoked when reload is requested.
|
|
74
|
+
*
|
|
75
|
+
* Triggered by:
|
|
76
|
+
* - Process signal: SIGHUP
|
|
77
|
+
* - Keyboard: R key press (case-insensitive)
|
|
78
|
+
*/
|
|
79
|
+
onReloadRequested?: () => void | Promise<unknown>;
|
|
80
|
+
/**
|
|
81
|
+
* Optional callback invoked when info/stats are requested.
|
|
82
|
+
*
|
|
83
|
+
* Triggered by:
|
|
84
|
+
* - Process signal: SIGUSR1
|
|
85
|
+
* - Keyboard: I key press (case-insensitive)
|
|
86
|
+
*
|
|
87
|
+
* Common uses: Print stats, health check, show metrics
|
|
88
|
+
*/
|
|
89
|
+
onInfoRequested?: () => void | Promise<unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* Optional callback invoked when debug mode is toggled or verbose info is requested.
|
|
92
|
+
*
|
|
93
|
+
* Triggered by:
|
|
94
|
+
* - Process signal: SIGUSR2
|
|
95
|
+
* - Keyboard: D key press (case-insensitive)
|
|
96
|
+
*
|
|
97
|
+
* Common uses: Toggle debug mode, dump full state, enable verbose logging
|
|
98
|
+
*/
|
|
99
|
+
onDebugRequested?: () => void | Promise<unknown>;
|
|
100
|
+
/**
|
|
101
|
+
* Custom name for the shutdown callback used in error reporting.
|
|
102
|
+
* @default 'onShutdownRequested'
|
|
103
|
+
*/
|
|
104
|
+
shutdownCallbackName?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Custom name for the reload callback used in error reporting.
|
|
107
|
+
* @default 'onReloadRequested'
|
|
108
|
+
*/
|
|
109
|
+
reloadCallbackName?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Custom name for the info callback used in error reporting.
|
|
112
|
+
* @default 'onInfoRequested'
|
|
113
|
+
*/
|
|
114
|
+
infoCallbackName?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Custom name for the debug callback used in error reporting.
|
|
117
|
+
* @default 'onDebugRequested'
|
|
118
|
+
*/
|
|
119
|
+
debugCallbackName?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Throttle interval in milliseconds for keyboard events (leading-edge rate limiting).
|
|
122
|
+
* Allows an action to trigger at most once per interval. First press fires immediately,
|
|
123
|
+
* subsequent presses within the window are ignored.
|
|
124
|
+
* This prevents accidental double-triggers while allowing predictable repeated actions.
|
|
125
|
+
*
|
|
126
|
+
* Note: This only affects keyboard events, not process signals.
|
|
127
|
+
* Process signals are never throttled as they may come from external sources
|
|
128
|
+
* that expect immediate handling.
|
|
129
|
+
*
|
|
130
|
+
* @default 200 (200ms throttle, allowing 5 triggers per second maximum)
|
|
131
|
+
* @example 300 // Custom 300ms throttle (3.33 triggers per second max)
|
|
132
|
+
* @example 0 // Disable throttling entirely
|
|
133
|
+
*/
|
|
134
|
+
keypressThrottleMS?: number;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Manages process signals and keyboard events for graceful shutdown, reload, and info/debug functionality.
|
|
138
|
+
*
|
|
139
|
+
* Handles:
|
|
140
|
+
* - Shutdown signals: SIGINT, SIGTERM, SIGTRAP
|
|
141
|
+
* - Reload signal: SIGHUP
|
|
142
|
+
* - Info signal: SIGUSR1
|
|
143
|
+
* - Debug signal: SIGUSR2
|
|
144
|
+
* - Keyboard shortcuts: Ctrl+C, Escape (shutdown), R (reload), I (info), D (debug)
|
|
145
|
+
* All letter keys are case-insensitive
|
|
146
|
+
*
|
|
147
|
+
* All callbacks are executed safely with automatic error handling.
|
|
148
|
+
*/
|
|
149
|
+
declare class ProcessSignalManager {
|
|
150
|
+
private readonly instanceID;
|
|
151
|
+
private onShutdownRequested?;
|
|
152
|
+
private onReloadRequested?;
|
|
153
|
+
private onInfoRequested?;
|
|
154
|
+
private onDebugRequested?;
|
|
155
|
+
private shutdownCallbackName;
|
|
156
|
+
private reloadCallbackName;
|
|
157
|
+
private infoCallbackName;
|
|
158
|
+
private debugCallbackName;
|
|
159
|
+
private shutdownSignalListeners?;
|
|
160
|
+
private reloadSignalListener?;
|
|
161
|
+
private infoSignalListener?;
|
|
162
|
+
private debugSignalListener?;
|
|
163
|
+
private keypressHandler?;
|
|
164
|
+
private _isAttached;
|
|
165
|
+
private keypressThrottleMS;
|
|
166
|
+
private lastActionTimes;
|
|
167
|
+
constructor(options: ProcessSignalManagerOptions);
|
|
168
|
+
/**
|
|
169
|
+
* Check if the manager is currently attached to signals and keypresses.
|
|
170
|
+
*/
|
|
171
|
+
get isAttached(): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Get detailed status information about what the manager is attached to.
|
|
174
|
+
*
|
|
175
|
+
* @returns Status object with handler registration and attachment state
|
|
176
|
+
*/
|
|
177
|
+
getStatus(): ProcessSignalManagerStatus;
|
|
178
|
+
/**
|
|
179
|
+
* Attach signal handlers and start listening for process signals and keyboard events.
|
|
180
|
+
* Idempotent - calling multiple times has no effect.
|
|
181
|
+
*/
|
|
182
|
+
attach(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Detach signal handlers and stop listening for process signals and keyboard events.
|
|
185
|
+
* Cleans up all event listeners and restores stdin to normal mode.
|
|
186
|
+
*
|
|
187
|
+
* Idempotent - calling multiple times has no effect.
|
|
188
|
+
*/
|
|
189
|
+
detach(): void;
|
|
190
|
+
/**
|
|
191
|
+
* Manually trigger a shutdown event
|
|
192
|
+
* if the manager is attached and a shutdown handler is registered
|
|
193
|
+
*
|
|
194
|
+
* @param method - The shutdown method (SIGINT, SIGTERM, or SIGTRAP) that will be passed to the shutdown callback
|
|
195
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
196
|
+
*/
|
|
197
|
+
triggerShutdown(method: ShutdownSignal, shouldBypassAttachCheck?: boolean): void;
|
|
198
|
+
/**
|
|
199
|
+
* Manually trigger a reload event
|
|
200
|
+
* if the manager is attached and a reload handler is registered
|
|
201
|
+
*
|
|
202
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
203
|
+
*/
|
|
204
|
+
triggerReload(shouldBypassAttachCheck?: boolean): void;
|
|
205
|
+
/**
|
|
206
|
+
* Manually trigger an info event
|
|
207
|
+
* if the manager is attached and an info handler is registered
|
|
208
|
+
*
|
|
209
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
210
|
+
*/
|
|
211
|
+
triggerInfo(shouldBypassAttachCheck?: boolean): void;
|
|
212
|
+
/**
|
|
213
|
+
* Manually trigger a debug event
|
|
214
|
+
* if the manager is attached and a debug handler is registered
|
|
215
|
+
*
|
|
216
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
217
|
+
*/
|
|
218
|
+
triggerDebug(shouldBypassAttachCheck?: boolean): void;
|
|
219
|
+
/**
|
|
220
|
+
* Check if an action should be throttled based on the last time it was successfully triggered.
|
|
221
|
+
* Uses leading-edge throttle: first press fires immediately, subsequent presses within the
|
|
222
|
+
* throttle window are ignored. Only updates timestamp when action is allowed (not throttled).
|
|
223
|
+
*
|
|
224
|
+
* This is the standard pattern for keyboard shortcuts and prevents accidental double-triggers
|
|
225
|
+
* while allowing predictable repeated actions at a maximum rate.
|
|
226
|
+
*
|
|
227
|
+
* @param action - The action type to check throttling for
|
|
228
|
+
* @returns true if the action should be throttled (ignored), false otherwise
|
|
229
|
+
*/
|
|
230
|
+
private shouldThrottle;
|
|
231
|
+
/**
|
|
232
|
+
* Register handlers for all shutdown signals (SIGINT, SIGTERM, SIGTRAP) if callback is provided.
|
|
233
|
+
* Each signal will trigger the shutdown callback with the appropriate method.
|
|
234
|
+
*/
|
|
235
|
+
private listenForShutdownSignals;
|
|
236
|
+
/**
|
|
237
|
+
* Remove handlers for all shutdown signals if they were registered.
|
|
238
|
+
* Uses the same function references to ensure proper cleanup.
|
|
239
|
+
*/
|
|
240
|
+
private stopListeningForShutdownSignals;
|
|
241
|
+
/**
|
|
242
|
+
* Register handler for SIGHUP signal if reload callback is provided.
|
|
243
|
+
* SIGHUP is commonly used to trigger configuration reloads.
|
|
244
|
+
*/
|
|
245
|
+
private listenForReloadSignal;
|
|
246
|
+
/**
|
|
247
|
+
* Remove handler for SIGHUP signal.
|
|
248
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
249
|
+
*/
|
|
250
|
+
private stopListeningForReloadSignal;
|
|
251
|
+
/**
|
|
252
|
+
* Register handler for SIGUSR1 signal if info callback is provided.
|
|
253
|
+
* SIGUSR1 is commonly used for printing stats, health checks, etc.
|
|
254
|
+
*/
|
|
255
|
+
private listenForInfoSignal;
|
|
256
|
+
/**
|
|
257
|
+
* Remove handler for SIGUSR1 signal.
|
|
258
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
259
|
+
*/
|
|
260
|
+
private stopListeningForInfoSignal;
|
|
261
|
+
/**
|
|
262
|
+
* Register handler for SIGUSR2 signal if debug callback is provided.
|
|
263
|
+
* SIGUSR2 is commonly used for toggling debug mode, dumping state, etc.
|
|
264
|
+
*/
|
|
265
|
+
private listenForDebugSignal;
|
|
266
|
+
/**
|
|
267
|
+
* Remove handler for SIGUSR2 signal.
|
|
268
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
269
|
+
*/
|
|
270
|
+
private stopListeningForDebugSignal;
|
|
271
|
+
/**
|
|
272
|
+
* Enable keyboard event listening if stdin is a TTY.
|
|
273
|
+
* Sets stdin to raw mode and listens for Ctrl+C, Escape, R, I, and D keypresses.
|
|
274
|
+
*
|
|
275
|
+
* Note: Letter keys are case-insensitive (R/r, I/i, D/d all work).
|
|
276
|
+
*
|
|
277
|
+
* Uses add-then-check pattern to prevent race conditions:
|
|
278
|
+
* 1. Add ourselves to attachedInstances first
|
|
279
|
+
* 2. Check if we're the first (size === 1) to enable raw mode
|
|
280
|
+
* This ensures no gap where another instance could read stale state.
|
|
281
|
+
*/
|
|
282
|
+
private listenForKeyPresses;
|
|
283
|
+
/**
|
|
284
|
+
* Helper to clean up keypress handler registration on error.
|
|
285
|
+
* Removes handler, clears instance from shared state, and restores raw mode if needed.
|
|
286
|
+
*
|
|
287
|
+
* @param shared - The shared state object
|
|
288
|
+
* @param didAttemptRawModeEnable - If true, we attempted to enable raw mode (even if ownership wasn't recorded).
|
|
289
|
+
* This handles the edge case where setRawMode(true) throws after actually enabling raw mode.
|
|
290
|
+
*/
|
|
291
|
+
private cleanupKeypressHandler;
|
|
292
|
+
/**
|
|
293
|
+
* Restore stdin to normal mode and clean up keypress listener.
|
|
294
|
+
* Uses remove-then-check pattern (mirror of add-then-check in attach):
|
|
295
|
+
* 1. Remove ourselves from attachedInstances first
|
|
296
|
+
* 2. Check if we're the last (size === 0) to disable raw mode and pause stdin
|
|
297
|
+
*
|
|
298
|
+
* Note: Can be called even if keypressHandler is undefined (e.g., during error recovery).
|
|
299
|
+
* In that case, we still update shared state and attempt terminal restoration if we
|
|
300
|
+
* were the recorded raw mode owner.
|
|
301
|
+
*/
|
|
302
|
+
private restoreStdin;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { ProcessSignalManager, type ProcessSignalManagerOptions, type ProcessSignalManagerStatus, type ShutdownSignal };
|