UNPKG

@darksnow-ui/commander

Version:

Command pattern implementation with React hooks for building command palettes and keyboard-driven UIs

740 lines (611 loc) 27.9 kB
# Commander Architecture > Documentação técnica da arquitetura do `@darksnow-ui/commander` ## Visão Geral O **Commander** é um sistema de gerenciamento de comandos enterprise-grade para aplicações React. Implementa o padrão Command com funcionalidades avançadas como validação de input, sistema de eventos, busca inteligente e histórico de execução. ``` ┌─────────────────────────────────────────────────────────────────────────────┐ @darksnow-ui/commander ├─────────────────────────────────────────────────────────────────────────────┤ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ React Commander Builder Errors Hooks │───▶│ Core │◀───│ Pattern System └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ Types & Interfaces └─────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## Estrutura de Arquivos ``` src/ ├── commander.ts # Classe principal Commander ├── builder.ts # CommandBuilder e templates ├── errors.ts # Classes de erro customizadas ├── types.ts # Tipos e interfaces TypeScript ├── utils.ts # Funções utilitárias ├── index.ts # Exports públicos ├── hooks/ ├── index.ts # Re-exports dos hooks ├── context.tsx # CommanderProvider e useCommander ├── useCommand.ts # Hook principal para executar comandos ├── useCustomCommand.ts # Hook para registrar comandos temporários └── useInvoker.ts # Hook simplificado para invocação └── __tests__/ ├── commander.test.ts ├── builder.test.ts ├── validation.test.ts ├── utils.test.ts └── ...hooks tests ``` --- ## Core: Commander Class ### Estruturas de Dados ```typescript class Commander { // Armazenamento principal - Map para O(1) lookup protected _commands: Map<CommandKey, Command> = new Map(); // Sistema de eventos Pub/Sub protected listeners: Map<string, EventListener[]> = new Map(); // Histórico de execuções (buffer circular) protected executionHistory: CommandExecutionContext[] = []; // Comandos recentes (LRU cache) protected recentCommands: CommandKey[] = []; // Configurações public maxHistorySize = 100; public maxRecentSize = 10; } ``` ### Decisões de Design | Estrutura | Escolha | Justificativa | |-----------|---------|---------------| | Commands | `Map<CommandKey, Command>` | O(1) lookup por chave | | Listeners | `Map<string, EventListener[]>` | Múltiplos listeners por evento | | History | `Array` (circular) | Memória limitada, FIFO natural | | Recent | `Array` (LRU) | Ordem de acesso importa | --- ## Pipeline de Execução ### Fluxo do `invoke()` ``` invoke(key, input, source) ├── 1. COMMAND LOOKUP ─────────────────────────────────── O(1) └── Map.get(key) └── Throw CommandNotFoundError if missing ├── 2. AVAILABILITY CHECK ─────────────────────────────── O(1) ou async └── command.when() boolean └── Throw CommandUnavailableError if false ├── 3. INPUT VALIDATION ───────────────────────────────── O(1) ou async └── command.inputValidator(input) true | errors[] └── Throw InputValidationError if errors └── Emit "command:validation-error" event ├── 4. BUILD CONTEXT ──────────────────────────────────── O(1) └── { command, input, startTime, source } ├── 5. EMIT "command:executing" ───────────────────────── O(n listeners) ├── 6. EXECUTE WITH TIMEOUT ───────────────────────────── O(handler) └── Promise.race([handler, timeout]) └── Throw CommandTimeoutError if exceeded ├── 7. TRACK EXECUTION ────────────────────────────────── O(1) └── Add to history (circular buffer) └── Update recent commands (LRU) ├── 8. EMIT "command:completed" ───────────────────────── O(n listeners) └── 9. RETURN RESULT ──────────────────────────────────── O(1) ``` ### Diagrama de Sequência ``` ┌──────────┐ ┌───────────┐ ┌─────────┐ ┌─────────┐ Client Commander Command Handler └────┬─────┘ └─────┬─────┘ └────┬────┘ └────┬────┘ invoke(key,in) │────────────────▶│ get(key) │───────────────▶│ when() │───────────────▶│ │◀───────────────│ inputValidator() │───────────────▶│ │◀───────────────│ emit("executing") │─ ─▶│ handle(input) │───────────────────────────────▶│ │◀───────────────────────────────│ result trackExecution() │─ ─▶│ emit("completed") │─ ─▶│ │◀────────────────│ result ``` --- ## Sistema de Tipos ### Hierarquia de Tipos ```typescript // Tipos base (branded types para type safety) type CommandKey = string & { __brand: "CommandKey" }; type CommandCategory = "system" | "file" | "edit" | "view" | "debug" | "tools" | "custom"; // Interface principal do comando interface Command<TInput = any, TOutput = any> { // Identificação (required) key: CommandKey; label: string; handle: (input?: TInput) => Promise<TOutput>; // Metadados (optional) description?: string; category?: CommandCategory; owner?: string; tags?: string[]; icon?: string; shortcut?: string; searchKeywords?: string[]; priority?: number; timeout?: number; // Comportamento (optional) when?: () => boolean | Promise<boolean>; inputValidator?: InputValidator<TInput>; } // Validação de input interface ValidationErrorDetail { path: string; // "email", "user.name", "items[0].id" message: string; // "Email is required" code?: string; // "required", "type", "format" expected?: unknown; received?: unknown; } type ValidationResult = true | ValidationErrorDetail[]; type InputValidator<T> = (input: T | undefined) => ValidationResult | Promise<ValidationResult>; ``` ### Diagrama de Tipos ``` ┌─────────────────┐ Command <TInput,TOut> └────────┬────────┘ ┌───────────────────┼───────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ CommandKey InputValidator CommandCategory│ (branded) <TInput> (union) └─────────────────┘ └────────┬────────┘ └─────────────────┘ ┌─────────────────┐ │ValidationResult true | errors[] └────────┬────────┘ ┌─────────────────┐ │ValidationError Detail └─────────────────┘ ``` --- ## Sistema de Validação ### Fluxo de Validação ``` Input inputValidator() ValidationResult ┌──────────┴──────────┐ true ValidationErrorDetail[] Continue to Throw InputValidationError handler Emit "command:validation-error" ``` ### InputValidationError ```typescript class InputValidationError extends CommandError { errors: ValidationErrorDetail[]; input?: unknown; // Helper methods getFieldErrors(path: string): ValidationErrorDetail[]; getRequiredErrors(): ValidationErrorDetail[]; getMissingFields(): string[]; hasFieldError(path: string): boolean; toJSON(): object; } ``` ### Integração com Bibliotecas ```typescript // Zod inputValidator: (input) => { const result = schema.safeParse(input); if (result.success) return true; return result.error.issues.map(i => ({ path: i.path.join('.'), message: i.message, code: i.code })); } // Yup inputValidator: async (input) => { try { await schema.validate(input, { abortEarly: false }); return true; } catch (err) { return err.inner.map(e => ({ path: e.path, message: e.message, code: e.type })); } } // AJV (JSON Schema) inputValidator: (input) => { const valid = ajv.validate(schema, input); if (valid) return true; return ajv.errors.map(e => ({ path: e.instancePath.slice(1).replace(/\//g, '.'), message: e.message, code: e.keyword })); } ``` --- ## Sistema de Eventos ### Eventos Disponíveis | Evento | Payload | Quando | |--------|---------|--------| | `command:added` | `Command` | Comando registrado | | `command:removed` | `Command` | Comando removido | | `command:executing` | `CommandExecutionContext` | Antes da execução | | `command:completed` | `CommandExecutionContext, result` | Execução bem-sucedida | | `command:failed` | `CommandExecutionContext, error` | Execução falhou | | `command:error` | `CommandKey, error` | Erro geral (not found, unavailable) | | `command:validation-error` | `CommandKey, InputValidationError` | Validação falhou | | `history:cleared` | - | Histórico limpo | ### Arquitetura Pub/Sub ``` ┌─────────────────────────────────────────────────────────────┐ Event System ├─────────────────────────────────────────────────────────────┤ listeners: Map<string, EventListener[]> ┌─────────────────┐ ┌─────────────────┐ "command:added" │"command:executing"│ ├─────────────────┤ ├─────────────────┤ [listener1] [listener1] [listener2] [listener2] [listener3] [listener3] └─────────────────┘ └─────────────────┘ emit(event, ...args) └── Promise.allSettled(listeners.map(l => l.callback())) └─────────────────────────────────────────────────────────────┘ ``` ### Opções de Listener ```typescript // Listener permanente const listener = commander.listen("command:completed", (ctx, result) => { analytics.track("command_executed", { key: ctx.command.key }); }); // Listener único (auto-remove após primeira execução) commander.listen("command:added", handler, { once: true }); // Remover listener commander.removeListener(listener); ``` --- ## Sistema de Busca ### Algoritmo de Scoring ``` Para cada comando que passa nos filtros: ├── Label exact match ──────────────── +100 pontos ├── Label starts with ─────────────── +80 pontos ├── Label contains ────────────────── +60 pontos ├── Description match ─────────────── +40 pontos ├── Tags match ────────────────────── +30 pontos ├── SearchKeywords match ──────────── +50 pontos ├── Fuzzy match (Levenshtein) ─────── +1-30 pontos └── Sort by: score (desc) priority (desc) ``` ### Complexidade | Operação | Complexidade | Notas | |----------|--------------|-------| | `search(query)` | O(n × m) | n = comandos, m = termos | | `getAllAvailable()` | O(n) | Com availability check | | `getCommandsByCategory()` | O(n) | Agrupa por categoria | --- ## React Hooks ### Hierarquia de Hooks ``` ┌─────────────────────┐ CommanderProvider (Context Provider) └──────────┬──────────┘ ┌──────────┴──────────┐ useCommander() (Context Consumer) └──────────┬──────────┘ ┌──────────────────────┼──────────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ useCommand useCustomCommand│ useInvoker (Execution) (Registration) (Simplified) └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` ### useCommand - API Completa ```typescript interface CommandInvoker<TInput, TOutput> { // Estado exists: boolean; isAvailable: boolean; isLoading: boolean; lastResult: TOutput | null; lastError: Error | null; lastInput: TInput | null; lastExecution: Date | null; executionCount: number; command: Command | null; // Métodos de execução invoke(input?, options?): Promise<TOutput>; attempt(input?, options?): Promise<ExecutionResult<TOutput>>; execute(input?, callbacks?): Promise<TOutput | null>; // Verificações canInvoke(input?): Promise<boolean>; validateInput(input?): boolean | string; // Utilitários reset(): void; clearError(): void; refresh(): void; } ``` ### Opções do useCommand ```typescript interface UseCommandOptions<TInput, TOutput> { // Execução source?: "palette" | "shortcut" | "api"; throwOnError?: boolean; timeout?: number; // Retry & Rate Limiting retry?: number; retryDelay?: number | ((attempt: number) => number); debounce?: number; throttle?: number; // Input handling defaultInput?: Partial<TInput>; validateInput?: (input?: TInput) => boolean | string; transformInput?: (input?: TInput) => TInput; // Output handling transformOutput?: (output: TOutput) => TOutput; // Callbacks onSuccess?: (result: TOutput, input?: TInput) => void; onError?: (error: Error, input?: TInput) => void; onFinally?: (input?: TInput) => void; // Estado resetOnKeyChange?: boolean; } ``` --- ## Builder Pattern ### CommandBuilder API ```typescript CommandBuilder.create<TInput, TOutput>("command:key") .label("Command Label") // Required .description("Description") // Optional .category("tools") // Optional .owner("my-module") // Optional .tags("tag1", "tag2") // Optional .icon("icon-name") // Optional .shortcut("ctrl+shift+p") // Optional .searchKeywords("keyword1", "kw2") // Optional .priority(10) // Optional .timeout(5000) // Optional .when(() => isFeatureEnabled) // Optional .inputValidator(validator) // Optional .handle(async (input) => result) // Required .build(); ``` ### Templates ```typescript // Comandos pré-configurados por categoria CommandTemplate.system() // category: "system", owner: "system", priority: 10 CommandTemplate.file() // category: "file", tags: ["file"], priority: 5 CommandTemplate.debug() // category: "debug", when: () => isDev, priority: 15 CommandTemplate.view() // category: "view", tags: ["view", "ui"], priority: 3 CommandTemplate.tools() // category: "tools", tags: ["tools", "utility"], priority: 8 CommandTemplate.custom() // category: "custom", owner: "temporary", priority: 1 ``` --- ## Hierarquia de Erros ``` Error └── CommandError ├── CommandNotFoundError # Comando não existe ├── CommandUnavailableError # when() retornou false ├── CommandTimeoutError # Timeout excedido ├── CommandExecutionError # Erro durante handle() └── InputValidationError # Validação de input falhou ``` ### Factory de Erros ```typescript createCommandError(type, command, details?) // Tipos: // "not-found" CommandNotFoundError // "unavailable" CommandUnavailableError // "timeout" CommandTimeoutError(command, details.timeout) // "execution" CommandExecutionError(command, details.error) // "validation" InputValidationError(command, details.errors, details.input) ``` ### Type Guards ```typescript isInputValidationError(error) // error is InputValidationError isCommandError(error) // error is CommandError ``` --- ## Performance ### Análise de Complexidade | Operação | Time | Space | Notas | |----------|------|-------|-------| | `add(command)` | O(1) | O(1) | Map.set | | `remove(key)` | O(1) | O(1) | Map.delete | | `has(key)` | O(1) | O(1) | Map.has | | `getCommand(key)` | O(1) | O(1) | Map.get | | `invoke(key)` | O(1) + handler | O(1) | Lookup + execute | | `search(query)` | O(n × m) | O(n) | n=commands, m=terms | | `getAvailableCommands()` | O(n) | O(n) | Async availability | | `getCommandsByCategory()` | O(n) | O(n) | Grouping | ### Otimizações 1. **Lazy Evaluation** - Availability checks quando necessário - Event emission se listeners - History tracking com buffer limitado 2. **Memory Management** - History circular (max 100 entries) - Recent commands LRU (max 10 entries) - Listeners cleanup automático para `once` 3. **Search Optimizations** - Early filtering antes do scoring - Limit results para parar busca - Normalização de termos com cache --- ## Boas Práticas ### Nomenclatura de Commands ```typescript // Padrão: <domain>:<action> ou <domain>:<entity>:<action> "file:save" "file:open" "user:create" "user:profile:update" "admin:users:delete" ``` ### Organização por Owner ```typescript // Componente registra seus comandos useCustomCommand({ key: "editor:format", label: "Format Document", owner: "editor-component", // identificação handle: async () => formatDocument() }); // Cleanup automático quando componente desmonta // ou manual: commander.removeByOwner("editor-component"); ``` ### Validação de Input ```typescript // Sempre valide inputs críticos commander.add({ key: "payment:process", label: "Process Payment", inputValidator: (input) => { const errors = []; if (!input?.amount || input.amount <= 0) { errors.push({ path: "amount", message: "Invalid amount", code: "invalid" }); } if (!input?.cardToken) { errors.push({ path: "cardToken", message: "Card required", code: "required" }); } return errors.length ? errors : true; }, handle: async (input) => processPayment(input) }); ``` ### Error Handling ```typescript // Usando attempt() para não lançar exceção const result = await commander.attempt("risky:command", input); if (!result.success) { if (isInputValidationError(result.error)) { showValidationErrors(result.error.errors); } else { showGenericError(result.error); } } // Ou com try/catch try { await commander.invoke("risky:command", input); } catch (error) { if (isInputValidationError(error)) { // Campos faltando - pode redirecionar para coletar const missing = error.getMissingFields(); await collectMissingFields(missing); } } ``` --- ## Roadmap: Middleware System > Em desenvolvimento - veja `PROPOSAL-COMMANDER-INTERCEPTOR.md` ### Arquitetura Planejada ``` invoke(key, input, source) ├── 1. Command Lookup ├── 2. Build Context ├── 3. MIDDLEWARE PIPELINE ─────────────────────────── NOVO ├── Global middlewares ├── Category middlewares ├── Pattern middlewares ("file:*") ├── Specific middlewares ("file:save") └── Custom filter middlewares ├── 4. Availability Check ├── 5. Input Validation ├── 6. Execute Handler └── 7. Return Result ``` ### API Planejada ```typescript // Global commander.use(loggingMiddleware); // Por categoria commander.use("admin", authMiddleware); // Por pattern commander.use("file:*", auditMiddleware); // Por comando commander.use("user:delete", confirmMiddleware); // Por filtro commander.use(cmd => cmd.tags?.includes("dangerous"), confirmMiddleware); // Arrays commander.use(["user:delete", "data:purge"], [authMiddleware, confirmMiddleware]); ``` --- ## Referências - [README.md](../README.md) - Documentação de uso - [useInvoker-guide.md](./useInvoker-guide.md) - Guia detalhado de hooks - [Middleware Proposal](./proposals/PROPOSAL-COMMANDER-INTERCEPTOR.md) - Proposta de middleware system --- *Última atualização: 2025-12-03* *Versão: 1.0.5*