@darksnow-ui/commander
Version:
Command pattern implementation with React hooks for building command palettes and keyboard-driven UIs
550 lines (538 loc) • 20.7 kB
text/typescript
import * as react_jsx_runtime from 'react/jsx-runtime';
import { ReactNode } from 'react';
type CommandKey = string & {
__brand: "CommandKey";
};
type CommandCategory = "system" | "file" | "edit" | "view" | "debug" | "tools" | "custom";
interface Command<TInput = any, TOutput = any> {
key: CommandKey;
label: string;
description?: string;
category?: CommandCategory;
owner?: string;
tags?: string[];
icon?: string;
shortcut?: string;
when?: () => boolean | Promise<boolean>;
handle: (this: Commander, input?: TInput) => Promise<TOutput>;
timeout?: number;
searchKeywords?: string[];
priority?: number;
}
interface EventListener<T = any> {
id: string;
event: string;
callback: (...args: T[]) => void | Promise<void>;
once?: boolean;
}
interface CommandExecutionContext {
command: Command;
input: any;
startTime: Date;
source: "palette" | "shortcut" | "api";
}
interface SearchResult {
command: Command;
score: number;
matchedTerms: string[];
}
interface SearchOptions {
category?: CommandCategory;
owner?: string;
tags?: string[];
limit?: number;
includeUnavailable?: boolean;
}
interface ExecutionResult<T = any> {
success: boolean;
result?: T;
error?: any;
command: CommandKey;
}
interface CommanderStats {
totalCommands: number;
categories: number;
executionHistory: number;
recentCommands: number;
listeners: number;
}
declare class CommandHandler {
private key;
private commander;
constructor(key: CommandKey, commander: Commander);
exists(): boolean;
isAvailable(): Promise<boolean>;
invoke<T = any>(input?: any, source?: "palette" | "shortcut" | "api"): Promise<T>;
attempt<T = any>(input?: any, source?: "palette" | "shortcut" | "api"): Promise<ExecutionResult<T>>;
getCommand(): Command | undefined;
}
declare class Commander {
protected _commands: Map<CommandKey, Command>;
protected listeners: Map<string, EventListener[]>;
protected executionHistory: CommandExecutionContext[];
protected recentCommands: CommandKey[];
maxHistorySize: number;
maxRecentSize: number;
constructor(commands?: Command[]);
listen<T = any>(event: string, callback: (...args: T[]) => void | Promise<void>, options?: {
once?: boolean;
}): EventListener<T>;
removeListener(listener: EventListener): void;
private emit;
commands(): Command[];
getCommand(key: CommandKey): Command | undefined;
add(command: Command): Command;
remove(commandOrKey: string | CommandKey | Command): boolean;
removeByOwner(owner: string): number;
removeByCategory(category: CommandCategory): number;
has(key: CommandKey): boolean;
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
private getAllAvailable;
isCommandAvailable(command: Command): Promise<boolean>;
getAvailableCommands(options?: {
category?: CommandCategory;
owner?: string;
}): Promise<Command[]>;
invoke<T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api"): Promise<T>;
private executeWithTimeout;
attempt<T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api"): Promise<ExecutionResult<T>>;
private trackExecution;
private addToRecent;
getExecutionHistory(limit?: number): CommandExecutionContext[];
getRecentCommands(): Command[];
clearHistory(): void;
getCommandsByCategory(): Map<CommandCategory, Command[]>;
getCategories(): CommandCategory[];
createInvoker(key: CommandKey): CommandHandler;
invoker(key: CommandKey): CommandHandler;
getStats(): CommanderStats;
destroy(): void;
}
declare class CommandBuilder<TInput = any, TOutput = any> {
private command;
/**
* Create a new CommandBuilder instance
*/
static create<T = any, R = any>(key: string): CommandBuilder<T, R>;
/**
* Set the command key (required)
*/
key(key: CommandKey): this;
/**
* Set the command label (required)
*/
label(label: string): this;
/**
* Set the command description
*/
description(description: string): this;
/**
* Set the command category
*/
category(category: CommandCategory): this;
/**
* Set the command owner
*/
owner(owner: string): this;
/**
* Add tags to the command
*/
tags(...tags: string[]): this;
/**
* Set the command icon
*/
icon(icon: string): this;
/**
* Set the keyboard shortcut
*/
shortcut(shortcut: string): this;
/**
* Set the availability condition
*/
when(condition: () => boolean | Promise<boolean>): this;
/**
* Add search keywords
*/
searchKeywords(...keywords: string[]): this;
/**
* Set the command priority
*/
priority(priority: number): this;
/**
* Set the command timeout
*/
timeout(ms: number): this;
/**
* Set the command handler (required)
*/
handle(handler: (input?: TInput) => Promise<TOutput>): this;
/**
* Build and return the command
*/
build(): Command<TInput, TOutput>;
/**
* Clone this builder to create a new one with the same configuration
*/
clone(): CommandBuilder<TInput, TOutput>;
/**
* Reset the builder to start over
*/
reset(): this;
/**
* Get the current command state (for debugging)
*/
getState(): Partial<Command<TInput, TOutput>>;
}
declare class CommandTemplate {
/**
* Create a system command template
*/
static system(): CommandBuilder;
/**
* Create a file command template
*/
static file(): CommandBuilder;
/**
* Create a debug command template
*/
static debug(): CommandBuilder;
/**
* Create a view command template
*/
static view(): CommandBuilder;
/**
* Create a tools command template
*/
static tools(): CommandBuilder;
/**
* Create a custom command template
*/
static custom(): CommandBuilder;
}
/**
* Start building a command with a fluent API
*/
declare function command<TInput = any, TOutput = any>(key: string): CommandBuilder<TInput, TOutput>;
/**
* Create a simple command quickly
*/
declare function simpleCommand<TInput = any, TOutput = any>(key: string, label: string, handler: (input?: TInput) => Promise<TOutput>): Command<TInput, TOutput>;
interface CommanderContextValue {
commander: Commander;
commands: () => Command[];
add: (command: Command) => Command;
remove: (commandOrKey: string | CommandKey | Command) => boolean;
removeByOwner: (owner: string) => number;
removeByCategory: (category: CommandCategory) => number;
has: (key: CommandKey) => boolean;
getCommand: (key: CommandKey) => Command | undefined;
invoke: <T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api") => Promise<T>;
attempt: <T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api") => Promise<ExecutionResult<T>>;
search: (query: string, options?: SearchOptions) => Promise<SearchResult[]>;
isCommandAvailable: (command: Command) => Promise<boolean>;
getAvailableCommands: (options?: {
category?: CommandCategory;
owner?: string;
}) => Promise<Command[]>;
getCommandsByCategory: () => Map<CommandCategory, Command[]>;
getCategories: () => CommandCategory[];
getExecutionHistory: (limit?: number) => any[];
getRecentCommands: () => Command[];
clearHistory: () => void;
listen: (event: string, callback: (...args: any[]) => void | Promise<void>, options?: {
once?: boolean;
}) => any;
removeListener: (listener: any) => void;
createInvoker: (key: CommandKey) => any;
getStats: () => any;
isReady: boolean;
}
interface CommanderProviderProps {
children: ReactNode;
commander: Commander;
onReady?: (commander: Commander) => void;
enableDevTools?: boolean;
}
declare function CommanderProvider({ children, commander, onReady, enableDevTools, }: CommanderProviderProps): react_jsx_runtime.JSX.Element;
/**
* Hook to access the Commander context
* Returns the full Commander API
*/
declare function useCommander(): CommanderContextValue;
/**
* Hook to get just the commander instance
*/
declare function useCommanderInstance(): Commander;
/**
* Hook for direct command execution
*/
declare function useInvoke(): <T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api") => Promise<T>;
/**
* Hook for safe command execution
*/
declare function useAttempt(): <T = any>(key: CommandKey, input?: any, source?: "palette" | "shortcut" | "api") => Promise<ExecutionResult<T>>;
/**
* Hook for command search
*/
declare function useSearch(): (query: string, options?: SearchOptions) => Promise<SearchResult[]>;
/**
* Hook to get all commands
*/
declare function useCommands(): Command<any, any>[];
/**
* Hook to get commands by category
*/
declare function useCommandsByCategory(): Map<CommandCategory, Command<any, any>[]>;
/**
* Hook to get recent commands
*/
declare function useRecentCommands(): Command<any, any>[];
/**
* Hook to get command stats
*/
declare function useCommanderStats(): any;
interface UseCustomCommandProps<TInput = any, TOutput = any> {
key: string;
label?: string;
description?: string;
category?: CommandCategory;
tags?: string[];
icon?: string;
shortcut?: string;
when?: () => boolean | Promise<boolean>;
handle: (input?: TInput) => Promise<TOutput>;
timeout?: number;
priority?: number;
owner?: string;
searchKeywords?: string[];
}
interface CustomCommandInvoker<TInput, TOutput> {
key: CommandKey;
exists: () => boolean;
isAvailable: () => Promise<boolean>;
invoke: (input?: TInput, source?: "palette" | "shortcut" | "api") => Promise<TOutput>;
attempt: (input?: TInput, source?: "palette" | "shortcut" | "api") => Promise<{
success: boolean;
result?: TOutput;
error?: any;
command: CommandKey;
}>;
getCommand: () => Command<TInput, TOutput> | undefined;
}
/**
* Hook to register a temporary command that exists only while the component is mounted
*
* @param props Command configuration
* @returns Invoker object for executing the command
*
* @example
* ```tsx
* function FileEditor({ file }) {
* const saveInvoker = useCustomCommand({
* key: `file:save:${file.id}`,
* label: `Save ${file.name}`,
* icon: '💾',
* shortcut: 'ctrl+s',
* handle: async () => saveFile(file)
* });
*
* // Command is automatically registered and appears in Command Palette
* // Execute programmatically if needed:
* const handleSave = () => saveInvoker.invoke();
* }
* ```
*/
declare function useCustomCommand<TInput = any, TOutput = any>(props: UseCustomCommandProps<TInput, TOutput>): CustomCommandInvoker<TInput, TOutput>;
/**
* Hook for creating a simple action command (no input/output)
*/
declare function useAction$1(key: string, label: string, action: () => Promise<void> | void, options?: Partial<Omit<UseCustomCommandProps<void, void>, "key" | "label" | "handle">>): CustomCommandInvoker<void, void>;
/**
* Hook for creating a toggle command
*/
declare function useToggleCommand(key: string, label: string, isActive: boolean, onToggle: (newState: boolean) => Promise<void> | void, options?: Partial<Omit<UseCustomCommandProps<void, boolean>, "key" | "label" | "handle">>): CustomCommandInvoker<void, boolean>;
/**
* Hook for creating a modal command that returns a promise
*/
declare function useModalCommand<TInput, TOutput>(key: string, label: string, openModal: (input?: TInput) => Promise<TOutput>, options?: Partial<Omit<UseCustomCommandProps<TInput, TOutput>, "key" | "label" | "handle">>): CustomCommandInvoker<TInput, TOutput>;
/**
* Hook for creating a navigation command
*/
declare function useNavigationCommand(key: string, label: string, path: string, navigate: (path: string, state?: any) => void, options?: Partial<Omit<UseCustomCommandProps<void, void>, "key" | "label" | "handle">>): CustomCommandInvoker<void, void>;
/**
* Hook for creating contextual commands based on data
*/
declare function useContextualCommands<TData>(data: TData[], commandFactory: (item: TData, index: number) => UseCustomCommandProps): {
count: number;
refresh: () => void;
};
/**
* Hook to check if a custom command exists and is available
*/
declare function useCommandStatus(key: CommandKey): {
exists: boolean;
checkAvailability: () => Promise<boolean>;
isExecuting: boolean;
lastResult: any;
lastError: any;
};
/**
* Hook to listen for custom command events
*/
declare function useCommandEvents(key: CommandKey, handlers: {
onExecuting?: (context: any) => void;
onExecuted?: (context: {
result: any;
}) => void;
onError?: (error: any) => void;
}): void;
type CommandSource = "palette" | "shortcut" | "api";
interface UseCommandOptions<TInput = any, TOutput = any> {
source?: CommandSource;
throwOnError?: boolean;
timeout?: number;
retry?: number;
retryDelay?: number | ((attempt: number) => number);
debounce?: number;
throttle?: number;
defaultInput?: Partial<TInput>;
validateInput?: (input?: TInput) => boolean | string;
transformInput?: (input?: TInput) => TInput;
transformOutput?: (output: TOutput) => TOutput;
onSuccess?: (result: TOutput, input?: TInput) => void;
onError?: (error: Error, input?: TInput) => void;
onFinally?: (input?: TInput) => void;
resetOnKeyChange?: boolean;
}
interface CommandInvoker<TInput = any, TOutput = any> {
exists: boolean;
isAvailable: boolean;
isLoading: boolean;
lastResult: TOutput | null;
lastError: Error | null;
lastInput: TInput | null;
lastExecution: Date | null;
executionCount: number;
command: Command<TInput, TOutput> | null;
invoke: (input?: TInput, options?: Partial<UseCommandOptions<TInput, TOutput>>) => Promise<TOutput>;
attempt: (input?: TInput, options?: Partial<UseCommandOptions<TInput, TOutput>>) => Promise<ExecutionResult<TOutput>>;
execute: (input?: TInput, callbacks?: ExecuteCallbacks<TInput, TOutput>) => Promise<TOutput | null>;
canInvoke: (input?: TInput) => Promise<boolean>;
validateInput: (input?: TInput) => boolean | string;
reset: () => void;
clearError: () => void;
refresh: () => void;
}
interface ExecuteCallbacks<TInput = any, TOutput = any> {
onSuccess?: (result: TOutput, input?: TInput) => void;
onError?: (error: Error, input?: TInput) => void;
onFinally?: (input?: TInput) => void;
}
/**
* Hook para executar comandos existentes no Commander
*
* @param key - Chave do comando a ser executado
* @param options - Opções de configuração
* @returns CommandInvoker com estado e métodos de execução
*/
declare function useCommand<TInput = any, TOutput = any>(key: CommandKey, options?: UseCommandOptions<TInput, TOutput>): CommandInvoker<TInput, TOutput>;
interface UseInvokerOptions<TInput = any, TOutput = any> {
source?: CommandSource;
throwOnError?: boolean;
defaultInput?: Partial<TInput>;
onSuccess?: (result: TOutput) => void;
onError?: (error: Error) => void;
}
/**
* Hook simplificado para execução de comandos
*
* Retorna diretamente a função de execução, sem necessidade de chamar .invoke()
*
* @param key - Chave do comando a ser executado
* @param options - Opções simplificadas de configuração
* @returns Função de execução direta
*/
declare function useInvoker<TInput = any, TOutput = any>(key: CommandKey, options?: UseInvokerOptions<TInput, TOutput>): (input?: TInput) => Promise<TOutput>;
/**
* Hook para comandos que não precisam de input
*/
declare function useAction(key: CommandKey, options?: Omit<UseInvokerOptions<void, any>, "defaultInput">): () => Promise<any>;
/**
* Hook que retorna sempre o objeto CommandInvoker (alias para useCommand)
*/
declare function useCommandState<TInput = any, TOutput = any>(key: CommandKey, options?: UseInvokerOptions<TInput, TOutput>): CommandInvoker<TInput, TOutput>;
/**
* Hook para execução segura (nunca lança erro)
*/
declare function useSafeInvoker<TInput = any, TOutput = any>(key: CommandKey, options?: Omit<UseInvokerOptions<TInput, TOutput>, "throwOnError">): (input?: TInput) => Promise<ExecutionResult<TOutput>>;
/**
* Hook para comandos com input pré-configurado
*/
declare function useBoundInvoker<TInput = any, TOutput = any>(key: CommandKey, boundInput: Partial<TInput>, options?: Omit<UseInvokerOptions<TInput, TOutput>, "defaultInput">): (input?: Partial<TInput>) => Promise<TOutput>;
/**
* Hook para comandos toggle (boolean)
*/
declare function useToggleInvoker(key: CommandKey, options?: Omit<UseInvokerOptions<boolean, boolean>, "defaultInput">): (state?: boolean) => Promise<boolean>;
/**
* Hook para execução sequencial de múltiplos comandos
*/
declare function useBatchInvoker(keys: CommandKey[], options?: {
source?: CommandSource;
stopOnError?: boolean;
onProgress?: (completed: number, total: number) => void;
}): (inputs?: any[], batchOptions?: {
stopOnError?: boolean;
}) => Promise<ExecutionResult[]>;
/**
* Hook para execução paralela de múltiplos comandos
*/
declare function useParallelInvoker(keys: CommandKey[], options?: {
source?: CommandSource;
}): (inputs?: any[]) => Promise<PromiseSettledResult<any>[]>;
declare class CommandError extends Error {
command?: string | undefined;
name: string;
constructor(message: string, command?: string | undefined);
}
declare class CommandNotFoundError extends CommandError {
name: string;
constructor(command: string);
}
declare class CommandUnavailableError extends CommandError {
name: string;
constructor(command: string);
}
declare class CommandTimeoutError extends CommandError {
name: string;
constructor(command: string, timeout: number);
}
declare class CommandExecutionError extends CommandError {
name: string;
cause: Error;
constructor(command: string, originalError: Error);
}
declare function createCommandError(type: string, command: string, details?: any): CommandError;
declare function generateId(): string;
declare function normalizeSearchTerm(term: string): string;
declare function calculateSearchScore(command: any, queryTerms: string[]): number;
declare function getMatchedTerms(command: any, queryTerms: string[]): string[];
declare function isValidCommandKey(key: any): key is string;
declare function isValidCommand(command: any): boolean;
declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage?: string): Promise<T>;
declare function delay(ms: number): Promise<void>;
declare function debounce<T extends (...args: any[]) => any>(func: T, wait: number, immediate?: boolean): (...args: Parameters<T>) => void;
declare function unique<T>(array: T[]): T[];
declare function removeItem<T>(array: T[], item: T): T[];
declare function deepClone<T>(obj: T): T;
declare class PriorityQueue<T> {
private items;
enqueue(item: T, priority?: number): void;
dequeue(): T | undefined;
peek(): T | undefined;
isEmpty(): boolean;
size(): number;
clear(): void;
remove(predicate: (item: T) => boolean): boolean;
}
declare const VERSION = "1.0.0";
export { type Command, CommandBuilder, type CommandCategory, CommandError, type CommandExecutionContext, CommandExecutionError, CommandHandler, type CommandInvoker, type CommandKey, CommandNotFoundError, type CommandSource, CommandTemplate, CommandTimeoutError, CommandUnavailableError, Commander, CommanderProvider, type CommanderStats, type EventListener, type ExecuteCallbacks, type ExecutionResult, PriorityQueue, type SearchOptions, type SearchResult, type UseCommandOptions, type UseInvokerOptions, VERSION, calculateSearchScore, command, createCommandError, debounce, deepClone, delay, generateId, getMatchedTerms, isValidCommand, isValidCommandKey, normalizeSearchTerm, removeItem, simpleCommand, unique, useAction$1 as useAction, useAction as useActionInvoker, useAttempt, useBatchInvoker, useBoundInvoker, useCommand, useCommandEvents, useCommandState, useCommandStatus, useCommander, useCommanderInstance, useCommanderStats, useCommands, useCommandsByCategory, useContextualCommands, useCustomCommand, useInvoke, useInvoker, useModalCommand, useNavigationCommand, useParallelInvoker, useRecentCommands, useSafeInvoker, useSearch, useToggleCommand, useToggleInvoker, withTimeout };