tuix
Version:
A performant TUI framework for Bun with JSX and reactive state management
286 lines (249 loc) • 9.2 kB
text/typescript
/**
* 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
}