UNPKG

@unblessed/core

Version:

Platform-agnostic terminal UI core library with runtime dependency injection

1 lines 14.6 kB
{"version":3,"sources":["../src/runtime.ts"],"names":[],"mappings":";;;AAwcO,SAAS,gBACd,OAAA,EAC2C;AAC3C,EAAA,OAAO,QAAQ,MAAA,KAAW,MAAA;AAC5B;AAwBO,SAAS,kBACd,OAAA,EACkD;AAClD,EAAA,OAAO,QAAQ,SAAA,KAAc,MAAA;AAC/B;AAYO,SAAS,kBACd,OAAA,EACoD;AACpD,EAAA,OAAO,QAAQ,UAAA,KAAe,MAAA;AAChC","file":"runtime.cjs","sourcesContent":["/**\n * Runtime abstraction layer for @unblessed/core\n *\n * @remarks\n * The Runtime interface provides platform-specific APIs through dependency injection.\n * This allows unblessed to work across different platforms (Node.js, browsers, Deno, etc.)\n * by abstracting platform-specific operations behind a common interface.\n *\n * **Key Concepts**:\n * - **Runtime Interface**: Defines what platforms must provide\n * - **Runtime Context**: Global singleton holding the current runtime\n * - **Platform Implementations**: Node.js runtime, browser runtime, etc.\n * - **Dependency Injection**: Core code uses runtime instead of direct imports\n *\n * **Platform adapters**:\n * - `@unblessed/node`: Wraps real Node.js APIs (fs, path, process, child_process, tty, etc.)\n * - `@unblessed/browser`: Provides browser polyfills and virtual filesystem\n * - Tests: Uses real Node.js APIs for testing\n *\n * @example Using runtime in code\n * ```typescript\n * import { getRuntime } from '@unblessed/core/runtime-context';\n *\n * function loadTerminfo(term: string): Buffer {\n * const runtime = getRuntime();\n *\n * // Use runtime.fs instead of import fs\n * const path = runtime.path.join('/usr/share/terminfo', term[0], term);\n *\n * if (!runtime.fs.existsSync(path)) {\n * throw new Error(`Terminfo not found: ${term}`);\n * }\n *\n * return runtime.fs.readFileSync(path);\n * }\n * ```\n *\n * @example Feature detection for optional APIs\n * ```typescript\n * import { getRuntime, hasImageSupport } from '@unblessed/core';\n *\n * function canRenderImages(): boolean {\n * const runtime = getRuntime();\n * return runtime.images !== undefined;\n * }\n *\n * function parseImage(buffer: Buffer) {\n * const runtime = getRuntime();\n *\n * if (!runtime.images) {\n * throw new Error('Image support not available');\n * }\n *\n * const png = runtime.images.png.PNG.sync.read(buffer);\n * return png;\n * }\n * ```\n *\n * @example Type-safe feature detection\n * ```typescript\n * const runtime = getRuntime();\n *\n * if (hasImageSupport(runtime)) {\n * // TypeScript knows runtime.images exists here!\n * const png = runtime.images.png.PNG.sync.read(buffer);\n * }\n * ```\n *\n * @example Platform detection\n * ```typescript\n * import { getRuntime } from '@unblessed/core/runtime-context';\n *\n * const runtime = getRuntime();\n *\n * if (runtime.process.platform === 'browser') {\n * console.log('Running in browser');\n * } else {\n * console.log('Running in Node.js');\n * }\n * ```\n *\n * @see {@link getRuntime} for accessing the current runtime\n * @see {@link setRuntime} for platform initialization\n */\n\n// Type-only imports from @types/node\nimport type { Buffer } from \"buffer\";\nimport type * as child_process from \"child_process\";\nimport type EventEmitter from \"events\";\nimport type * as fs from \"fs\";\nimport type * as net from \"net\";\nimport type * as path from \"path\";\nimport type { Readable, Writable } from \"stream\";\nimport type { StringDecoder } from \"string_decoder\";\nimport type * as tty from \"tty\";\nimport type * as url from \"url\";\nimport type * as util from \"util\";\n\n/**\n * Complete runtime abstraction interface\n * All @unblessed/core modules accept this interface for platform operations\n *\n * Core APIs (always required):\n * - fs, path, process, buffer, url, utils\n *\n * Optional APIs (use feature detection):\n * - images: PNG/GIF rendering (only needed by Image widgets)\n * - processes: Child process spawning (Terminal widget, image tools)\n * - networking: Network and TTY operations (GPM mouse - very rare)\n */\nexport interface Runtime {\n // ============================================================\n // CORE APIs (Always Required)\n // ============================================================\n\n /** File system operations */\n fs: FileSystemAPI;\n /** Path manipulation operations */\n path: PathAPI;\n /** Process operations (stdin/stdout/env/etc) */\n process: ProcessAPI;\n /** Buffer operations */\n buffer: BufferAPI;\n /** URL operations (fileURLToPath for module resolution) */\n url: UrlAPI;\n /** Utility functions (inspect, format) */\n util: UtilAPI;\n /** Stream operations (Readable, Writable) */\n stream: StreamAPI;\n /** String decoder for buffer/string conversion */\n stringDecoder: StringDecoderAPI;\n /** Event emitter for event-driven programming */\n events: EventsAPI;\n\n // ============================================================\n // OPTIONAL APIs (Feature Detection)\n // ============================================================\n\n /** Image processing (PNG/GIF rendering) - Optional */\n images?: ImageAPI;\n /** Process spawning - Optional */\n processes?: ProcessesAPI;\n /** Networking and TTY operations - Optional */\n networking?: NetworkingAPI;\n}\n\n/**\n * File system operations interface\n *\n * @remarks\n * Subset of Node.js fs module needed by unblessed for:\n * - Reading terminfo/termcap files\n * - Loading font definitions\n * - Logging and debugging\n * - Temporary file operations\n *\n * @example Reading terminfo files\n * ```typescript\n * const runtime = getRuntime();\n * const data = runtime.fs.readFileSync('/usr/share/terminfo/x/xterm');\n * ```\n *\n * @example Checking file existence\n * ```typescript\n * const runtime = getRuntime();\n * if (runtime.fs.existsSync('/path/to/file')) {\n * const content = runtime.fs.readFileSync('/path/to/file', 'utf8');\n * }\n * ```\n */\nexport interface FileSystemAPI {\n readFileSync: typeof fs.readFileSync;\n readdirSync: typeof fs.readdirSync;\n existsSync: typeof fs.existsSync;\n statSync: typeof fs.statSync;\n mkdirSync: typeof fs.mkdirSync;\n createWriteStream: typeof fs.createWriteStream;\n readFile: typeof fs.readFile;\n unlink: typeof fs.unlink;\n writeFile: typeof fs.writeFile;\n stat: typeof fs.stat;\n readdir: typeof fs.readdir;\n lstatSync: typeof fs.lstatSync;\n readlinkSync: typeof fs.readlinkSync;\n}\n\n/**\n * Path manipulation interface\n * Subset of Node.js path module\n */\nexport interface PathAPI {\n join: typeof path.join;\n resolve: typeof path.resolve;\n dirname: typeof path.dirname;\n basename: typeof path.basename;\n normalize: typeof path.normalize;\n extname: typeof path.extname;\n sep: typeof path.sep;\n delimiter: typeof path.delimiter;\n}\n\n/**\n * Process operations interface\n *\n * @remarks\n * Subset of Node.js process global used for:\n * - I/O streams (stdin/stdout/stderr)\n * - Environment variables (TERM, EDITOR, HOME, etc.)\n * - Process events (exit, SIGTSTP, etc.)\n * - Platform detection (platform, arch)\n *\n * @example Accessing I/O streams\n * ```typescript\n * const runtime = getRuntime();\n * const { Readable, Writable } = runtime.utils.stream;\n *\n * const readable = new Readable();\n * readable.push('Hello\\n');\n * readable.push(null);\n * readable.pipe(runtime.process.stdout);\n * ```\n *\n * @example Environment variables\n * ```typescript\n * const runtime = getRuntime();\n * const term = runtime.process.env.TERM || 'xterm-256color';\n * const editor = runtime.process.env.EDITOR || 'vi';\n * const home = runtime.process.env.HOME || '/';\n * ```\n */\nexport interface ProcessAPI {\n stdin: NodeJS.ReadStream & { fd: 0 };\n stdout: NodeJS.WriteStream & { fd: 1 };\n stderr: NodeJS.WriteStream & { fd: 2 };\n platform: NodeJS.Platform;\n arch: NodeJS.Architecture;\n env: NodeJS.ProcessEnv;\n cwd: () => string;\n exit: (code?: number) => never;\n pid: number;\n title: string;\n version: string;\n on: (event: string, listener: (...args: any[]) => void) => any;\n once: (event: string, listener: (...args: any[]) => void) => any;\n removeListener: (event: string, listener: (...args: any[]) => void) => any;\n listeners: (event: any) => Function[];\n nextTick: (callback: Function, ...args: any[]) => void;\n kill: (pid: number, signal?: string | number) => boolean;\n}\n\n/**\n * Child process operations interface\n * Subset of Node.js child_process module\n */\nexport interface ChildProcessAPI {\n spawn: typeof child_process.spawn;\n execSync: typeof child_process.execSync;\n execFileSync: typeof child_process.execFileSync;\n}\n\n/**\n * TTY operations interface\n * Subset of Node.js tty module\n */\nexport interface TtyAPI {\n isatty: typeof tty.isatty;\n}\n\n/**\n * URL operations interface\n * Subset of Node.js url module\n */\nexport interface UrlAPI {\n parse: typeof url.parse;\n format: typeof url.format;\n fileURLToPath: typeof url.fileURLToPath;\n}\n\n/**\n * Utility functions interface\n * Subset of Node.js util module\n */\nexport interface UtilAPI {\n inspect: typeof util.inspect;\n format: typeof util.format;\n}\n\nexport interface NetAPI {\n createConnection: typeof net.createConnection;\n}\n\n/**\n * String decoder interface\n * Subset of Node.js string_decoder module\n */\nexport interface StringDecoderAPI {\n StringDecoder: typeof StringDecoder;\n}\n\n/**\n * Stream operations interface\n * Subset of Node.js stream module\n */\nexport interface StreamAPI {\n Readable: typeof Readable;\n Writable: typeof Writable;\n}\n\n/**\n * Readable type alias for use throughout the codebase\n */\nexport type ReadableType = InstanceType<StreamAPI[\"Readable\"]>;\n\n/**\n * Writable type alias for use throughout the codebase\n */\nexport type WritableType = InstanceType<StreamAPI[\"Writable\"]>;\n\nexport interface EventsAPI {\n EventEmitter: typeof EventEmitter;\n}\n\n/**\n * EventEmitter type alias for use throughout the codebase\n */\nexport interface EventEmitterType\n extends InstanceType<EventsAPI[\"EventEmitter\"]> {}\n\n/**\n * Buffer operations interface\n * Subset of Node.js buffer module\n */\nexport interface BufferAPI {\n Buffer: typeof Buffer;\n}\n\n/**\n * Buffer type alias for use throughout the codebase\n */\nexport type BufferType = InstanceType<BufferAPI[\"Buffer\"]>;\n\n/**\n * PNG image library interface (pngjs)\n */\nexport interface PngAPI {\n PNG: {\n new (options?: any): {\n width: number;\n height: number;\n data: BufferType;\n gamma: number;\n parse(\n data: BufferType,\n callback?: (error: Error | null, data: any) => void,\n ): any;\n pack(): any;\n on(event: string, callback: (...args: any[]) => void): any;\n };\n sync: {\n read(data: BufferType): {\n width: number;\n height: number;\n data: BufferType;\n gamma: number;\n };\n };\n };\n}\n\n/**\n * GIF image library interface (omggif)\n */\nexport interface GifAPI {\n GifReader: new (buffer: BufferType) => {\n width: number;\n height: number;\n numFrames(): number;\n loopCount(): number;\n frameInfo(frameNum: number): {\n x: number;\n y: number;\n width: number;\n height: number;\n has_local_palette: boolean;\n palette_offset: number | null;\n palette_size: number | null;\n data_offset: number;\n data_length: number;\n transparent_index: number | null;\n interlaced: boolean;\n delay: number;\n disposal: number;\n };\n decodeAndBlitFrameRGBA(frameNum: number, pixels: Uint8Array): void;\n };\n}\n\n// ============================================================\n// Grouped Optional APIs\n// ============================================================\n\n/**\n * Image processing API group (optional)\n * Combines PNG and GIF libraries for image rendering\n */\nexport interface ImageAPI {\n /** PNG image library (pngjs) */\n png: PngAPI;\n /** GIF image library (omggif) */\n gif: GifAPI;\n}\n\n/**\n * Process spawning API group (optional)\n * Child process operations for specialized widgets\n */\nexport interface ProcessesAPI {\n /** Child process spawning */\n childProcess: ChildProcessAPI;\n}\n\n/**\n * Networking API group (optional)\n * Network connections and TTY operations\n */\nexport interface NetworkingAPI {\n /** Network socket operations */\n net: NetAPI;\n /** TTY operations */\n tty: TtyAPI;\n}\n\n// ============================================================\n// Backward Compatibility Helpers\n// ============================================================\n\n/**\n * Check if runtime has image processing support\n *\n * @remarks\n * Type guard to check if the current runtime supports PNG/GIF rendering.\n * Use this before accessing `runtime.images` to avoid runtime errors.\n *\n * @param runtime - Runtime instance to check\n * @returns True if image support is available\n *\n * @example Type-safe feature detection\n * ```typescript\n * const runtime = getRuntime();\n *\n * if (hasImageSupport(runtime)) {\n * // TypeScript knows runtime.images exists here!\n * const png = runtime.images.png.PNG.sync.read(buffer);\n * }\n * ```\n */\nexport function hasImageSupport(\n runtime: Runtime,\n): runtime is Runtime & { images: ImageAPI } {\n return runtime.images !== undefined;\n}\n\n/**\n * Check if runtime has process spawning support\n *\n * @remarks\n * Type guard to check if the current runtime can spawn child processes.\n * Needed for Terminal widget, text editors (vi, nano), and image tools.\n *\n * @param runtime - Runtime instance to check\n * @returns True if process support is available\n *\n * @example Conditional feature usage\n * ```typescript\n * const runtime = getRuntime();\n *\n * if (hasProcessSupport(runtime)) {\n * // TypeScript knows runtime.processes exists\n * const proc = runtime.processes.childProcess.spawn('vi', ['file.txt']);\n * } else {\n * console.warn('Cannot spawn processes in this environment');\n * }\n * ```\n */\nexport function hasProcessSupport(\n runtime: Runtime,\n): runtime is Runtime & { processes: ProcessesAPI } {\n return runtime.processes !== undefined;\n}\n\n/**\n * Check if runtime has networking support\n *\n * @remarks\n * Type guard for network and TTY operations. Currently only used for\n * GPM mouse protocol on Linux console (very rare).\n *\n * @param runtime - Runtime instance to check\n * @returns True if networking support is available\n */\nexport function hasNetworkSupport(\n runtime: Runtime,\n): runtime is Runtime & { networking: NetworkingAPI } {\n return runtime.networking !== undefined;\n}\n"]}