UNPKG

tuix

Version:

A performant TUI framework for Bun with JSX and reactive state management

286 lines (249 loc) 9.2 kB
/** * Core types for the TUIX TUI framework * * This module defines the fundamental types that implement the Model-View-Update (MVU) * pattern enhanced with Effect.ts for robust error handling and resource management. */ import { Effect, Stream, Context, Data } from "effect" import type { KeyEvent } from "./keys" // ============================================================================= // Core MVU Types // ============================================================================= /** * A command represents an asynchronous operation that will produce a message. * Commands are Effect computations that handle side effects like HTTP requests, * file operations, timers, etc. */ export type Cmd<Msg> = Effect.Effect<Msg, never, AppServices> /** * A subscription represents a continuous stream of messages, such as keyboard * input, mouse events, window resize events, or timer ticks. */ export type Sub<Msg> = Stream.Stream<Msg, never, AppServices> /** * A view represents the visual output of a component. It can be rendered to a string * that will be displayed in the terminal. */ export interface View { readonly render: () => Effect.Effect<string, RenderError, never> readonly width?: number readonly height?: number } /** * The core Component interface that implements the MVU pattern. * All components must implement these three methods. */ export interface Component<Model, Msg> { /** * Initialize the component and return the initial model and any startup commands. */ readonly init: Effect.Effect< readonly [Model, ReadonlyArray<Cmd<Msg>>], never, AppServices > /** * Process a message and return the updated model and any commands to execute. * This is where all state transitions happen. */ readonly update: ( msg: Msg, model: Model ) => Effect.Effect< readonly [Model, ReadonlyArray<Cmd<Msg>>], never, AppServices > /** * Render the current model to a view that can be displayed. * This should be a pure function with no side effects. */ readonly view: (model: Model) => View /** * Optional subscriptions that provide continuous streams of messages. * Common subscriptions include keyboard input, mouse events, and timers. */ readonly subscriptions?: (model: Model) => Effect.Effect<Sub<Msg>, never, AppServices> } // ============================================================================= // System Messages // ============================================================================= /** * System-level messages that all components may receive. * These are generated by the framework itself. */ export type SystemMsg = | { readonly _tag: 'KeyPress'; readonly key: KeyEvent } | { readonly _tag: 'MouseEvent'; readonly mouse: MouseEvent } | { readonly _tag: 'WindowResize'; readonly size: WindowSize } | { readonly _tag: 'Quit' } | { readonly _tag: 'Interrupt' } | { readonly _tag: 'Suspend' } | { readonly _tag: 'Focus'; readonly componentId?: string } | { readonly _tag: 'Blur'; readonly componentId?: string } /** * Component messages extend system messages with component-specific messages. * Use discriminated unions for type-safe message handling. */ export type ComponentMsg<T = never> = SystemMsg | T // ============================================================================= // Input Events // ============================================================================= /** * Keyboard event with modifier keys and special key handling. * Re-exported from keys module for better organization. */ export type { KeyEvent, KeyType } from "./keys.ts" /** * Mouse event with position and button information. */ export interface MouseEvent { readonly type: 'press' | 'release' | 'wheel' | 'motion' readonly button: 'left' | 'right' | 'middle' | 'wheel-up' | 'wheel-down' | 'none' readonly x: number readonly y: number readonly ctrl: boolean readonly alt: boolean readonly shift: boolean } /** * Terminal window size change event. */ export interface WindowSize { readonly width: number readonly height: number } // ============================================================================= // Application Configuration // ============================================================================= /** * Configuration options for TUI applications. */ export interface AppOptions { readonly alternateScreen?: boolean readonly mouse?: boolean readonly fps?: number readonly exitKeys?: ReadonlyArray<string> readonly debug?: boolean } /** * Viewport represents a rectangular region of the terminal. */ export interface Viewport { readonly x: number readonly y: number readonly width: number readonly height: number } /** * Terminal capabilities detected at runtime. */ export interface TerminalCapabilities { readonly colors: '16' | '256' | 'truecolor' readonly unicode: boolean readonly mouse: boolean readonly alternateScreen: boolean readonly cursorShapes: boolean } // ============================================================================= // Error Types // ============================================================================= /** * Terminal operation errors. */ export class TerminalError extends Data.TaggedError("TerminalError")<{ readonly operation: string readonly cause?: unknown }> {} /** * Input handling errors. */ export class InputError extends Data.TaggedError("InputError")<{ readonly device: "keyboard" | "mouse" | "terminal" readonly cause?: unknown }> {} /** * Rendering errors. */ export class RenderError extends Data.TaggedError("RenderError")<{ readonly component?: string readonly phase: "render" | "layout" | "paint" readonly cause?: unknown }> {} /** * Storage errors for configuration and state persistence. */ export class StorageError extends Data.TaggedError("StorageError")<{ readonly operation: "read" | "write" | "delete" readonly path?: string readonly cause?: unknown }> {} /** * Union of all possible application errors. */ export type AppError = TerminalError | InputError | RenderError | StorageError // ============================================================================= // Service Types (Forward Declarations) // ============================================================================= /** * Forward declaration of service interfaces. * These will be fully defined in the services module. */ export interface TerminalService extends Context.Tag<"TerminalService", { readonly clear: Effect.Effect<void, TerminalError, never> readonly write: (text: string) => Effect.Effect<void, TerminalError, never> readonly moveCursor: (x: number, y: number) => Effect.Effect<void, TerminalError, never> readonly getSize: Effect.Effect<WindowSize, TerminalError, never> readonly setRawMode: (enabled: boolean) => Effect.Effect<void, TerminalError, never> readonly setAlternateScreen: (enabled: boolean) => Effect.Effect<void, TerminalError, never> readonly getCapabilities: Effect.Effect<TerminalCapabilities, TerminalError, never> }> {} export interface InputService extends Context.Tag<"InputService", { readonly keyEvents: Sub<KeyEvent> readonly mouseEvents: Sub<MouseEvent> readonly resizeEvents: Sub<WindowSize> readonly enableMouse: Effect.Effect<void, InputError, never> readonly disableMouse: Effect.Effect<void, InputError, never> }> {} export interface RendererService extends Context.Tag<"RendererService", { readonly render: (view: View) => Effect.Effect<void, RenderError, never> readonly beginFrame: Effect.Effect<void, RenderError, never> readonly endFrame: Effect.Effect<void, RenderError, never> readonly setViewport: (viewport: Viewport) => Effect.Effect<void, RenderError, never> readonly clearDirtyRegions: Effect.Effect<void, never, never> }> {} export interface StorageService extends Context.Tag<"StorageService", { readonly saveState: <T>(key: string, data: T) => Effect.Effect<void, StorageError, never> readonly loadState: <T>(key: string) => Effect.Effect<T | null, StorageError, never> readonly clearState: (key: string) => Effect.Effect<void, StorageError, never> }> {} /** * Union of all application services. */ export type AppServices = TerminalService | InputService | RendererService | StorageService // ============================================================================= // Utility Types // ============================================================================= /** * Extract the model type from a component. */ export type ModelOf<T> = T extends Component<infer M, any> ? M : never /** * Extract the message type from a component. */ export type MsgOf<T> = T extends Component<any, infer Msg> ? Msg : never /** * A program represents a complete TUI application with its model and message types. */ export interface Program<Model, Msg> extends Component<Model, Msg> { readonly options?: AppOptions } /** * Runtime state for a running TUI application. */ export interface RuntimeState<Model> { readonly model: Model readonly running: boolean readonly viewport: Viewport readonly capabilities: TerminalCapabilities }