1.0.5 โ€ข Published 7 months ago

@darksnow-ui/commander v1.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
7 months ago

@darksnow-ui/commander

๐Ÿš€ Enterprise-grade command system for React applications with Command Palette integration

npm version TypeScript License: MIT

Transform your React app into a power-user's dream with a VS Code-style command system. Register commands from anywhere, execute them programmatically or via Command Palette, and watch your UX reach new levels.

Commander

โœจ Why Commander?

๐ŸŽฏ The Problem

  • Scattered actions across your app with no centralized control
  • No unified way to execute operations programmatically + via UI
  • Command Palettes that require manual registration and maintenance
  • Poor discoverability of available actions for power users

๐Ÿ’ก The Solution

// 1. Register commands anywhere in your app
useCustomCommand({
  key: 'file:save',
  label: 'Save File',
  shortcut: 'ctrl+s',
  handle: async () => saveCurrentFile()
});

// 2. Execute programmatically with full type safety
const saveFile = useInvoker<SaveInput, SaveResult>('file:save');
const result = await saveFile({ filename: 'document.txt' });

// 3. Users find it instantly in Command Palette (Ctrl+Shift+P)
// 4. Command auto-removes when component unmounts

๐Ÿš€ Features

๐Ÿ—๏ธ Core Architecture

  • ๐ŸŽฏ Command Pattern - Centralized command management with O(1) lookups
  • ๐Ÿ” Intelligent Search - Hierarchical scoring algorithm with fuzzy matching
  • โŒจ๏ธ Keyboard First - Built-in shortcut support with conflict resolution
  • ๐Ÿท๏ธ Smart Organization - Categories, tags, owners, and priority system
  • ๐Ÿ”„ Event-Driven - Comprehensive lifecycle events for monitoring and analytics

โš›๏ธ React Integration

  • ๐ŸŽจ Zero Boilerplate - Context Provider handles everything automatically
  • โšก Temporary Commands - Components register/unregister commands automatically
  • ๐Ÿ›ก๏ธ Type Safety - Full TypeScript with generics for inputs/outputs
  • ๐ŸŽช Specialized Hooks - 10 purpose-built hooks for every use case
  • ๐Ÿ”ฅ Hot Reload Friendly - Commands survive React Fast Refresh

๐ŸŽ›๏ธ Advanced Features

  • ๐Ÿ“Š Execution Tracking - History, analytics, and recent commands
  • ๐Ÿ”’ Conditional Availability - Commands appear/disappear based on context
  • โฑ๏ธ Timeout Handling - Automatic timeouts with graceful error handling
  • ๐Ÿ› ๏ธ Dev Tools - Built-in debugging commands and global access
  • ๐Ÿ“ˆ Performance - Optimized for apps with hundreds of commands

๐Ÿ“ฆ Installation

npm install @darksnow-ui/commander
# or
yarn add @darksnow-ui/commander
# or
pnpm add @darksnow-ui/commander

โšก Quick Start

1. Setup Provider (one time in your app root)

import { CommanderProvider, Commander } from '@darksnow-ui/commander';

// Create your commander instance (usually in src/core/commander.ts)
const commander = new Commander();

function App() {
  return (
    <CommanderProvider 
      commander={commander} 
      enableDevTools
      onReady={(commander) => {
        console.log('Commander ready with', commander.commands().length, 'commands');
      }}
    >
      <MyApp />
    </CommanderProvider>
  );
}

2. Register Commands (in any component)

import { useCustomCommand } from '@darksnow-ui/commander';

function FileEditor({ file }) {
  // Command automatically appears in Command Palette!
  useCustomCommand({
    key: `file:save:${file.id}`,
    label: `Save ${file.name}`,
    icon: '๐Ÿ’พ',
    shortcut: 'ctrl+s',
    when: () => file.isDirty, // Only when file has changes
    handle: async () => {
      await saveFile(file);
      return { saved: true, filename: file.name };
    }
  });

  return <div>Your file editor UI...</div>;
}

3. Execute Commands (programmatically)

import { useInvoker } from '@darksnow-ui/commander';

function ActionButton() {
  const saveFile = useInvoker<SaveInput, SaveResult>('file:save');
  
  const handleSave = async () => {
    try {
      const result = await saveFile({ filename: 'document.txt' });
      toast.success(`Saved: ${result.filename}`);
    } catch (error) {
      toast.error('Save failed');
    }
  };

  return <button onClick={handleSave}>Save File</button>;
}

๐ŸŽฃ React Hooks

Core Hooks

useCommander()

Access the complete Commander API:

const { commander, search, invoke, commands, has, getCommand } = useCommander();

// Search for commands
const results = await search('save');

// Execute any command
await invoke('file:save', { filename: 'test.txt' });

// Get all commands
const allCommands = commands();

// Check if command exists
if (has('file:save')) {
  const saveCommand = getCommand('file:save');
}

useCustomCommand()

Register temporary commands that auto-cleanup:

useCustomCommand({
  key: 'my-command',
  label: 'My Command',
  description: 'Does something awesome',
  category: 'actions',
  icon: '๐Ÿš€',
  shortcut: 'ctrl+shift+a',
  tags: ['quick', 'action'],
  priority: 10,
  when: () => isFeatureEnabled(), // Conditional availability
  handle: async (input) => {
    // Your command logic
    return { success: true };
  }
});

Execution Hooks

useCommand()

Full-featured command execution with state tracking:

const command = useCommand<Input, Output>('my-command', {
  throwOnError: true,
  source: 'api',
  onSuccess: (result) => console.log('Success:', result),
  onError: (error) => console.error('Error:', error)
});

// Execute with various methods
await command.invoke({ data: 'test' }); // throws on error
const result = await command.attempt({ data: 'test' }); // returns ExecutionResult
await command.execute({ data: 'test' }, {
  onSuccess: (result) => toast.success('Done!'),
  onError: (error) => toast.error(error.message)
});

// Access state
console.log(command.isLoading);
console.log(command.lastResult);
console.log(command.lastError);
console.log(command.executionCount);

// Check availability
if (command.isAvailable) {
  // Show UI
}

useInvoker()

Direct function execution (simplified):

const saveFile = useInvoker<Input, Output>('file:save');

// Direct invocation - returns a function!
const result = await saveFile({ data: 'test' });

// With options
const saveWithOptions = useInvoker('file:save', {
  throwOnError: false,
  onSuccess: (result) => toast.success('Saved!')
});

Specialized Hooks

useAction()

For commands without parameters:

const logout = useAction('auth:logout');
const refresh = useAction('app:refresh');

// Simple execution
await logout();
await refresh();

useCommandState()

Always returns the CommandInvoker object (alias for useCommand):

const command = useCommandState('my-command');

// Access state and methods
console.log(command.isLoading);
console.log(command.lastResult);
await command.invoke(data);

useSafeInvoker()

Non-throwing execution with ExecutionResult:

const saveFile = useSafeInvoker<Input, Output>('file:save');

const result = await saveFile({ filename: 'test.txt' });

if (result.success) {
  console.log('Saved:', result.result);
} else {
  console.error('Failed:', result.error);
}

useBoundInvoker()

Pre-configured command execution:

// Always saves as PDF
const savePdf = useBoundInvoker('file:save', { format: 'pdf' });

await savePdf({ filename: 'document' }); // format is already bound

useToggleInvoker()

For boolean toggle commands:

const toggleDarkMode = useToggleInvoker('ui:dark-mode');
const toggleSidebar = useToggleInvoker('ui:sidebar');

// Toggle state
await toggleDarkMode(true);
await toggleSidebar(); // toggles current state

useBatchInvoker()

Sequential execution with progress tracking:

const deployPipeline = useBatchInvoker(
  ['build', 'test', 'deploy'],
  { 
    stopOnError: true,
    onProgress: (step, total) => console.log(`${step}/${total}`)
  }
);

const results = await deployPipeline([
  { target: 'production' },
  { suite: 'all' },
  { env: 'prod' }
]);

useParallelInvoker()

Parallel execution with Promise.allSettled:

const loadDashboard = useParallelInvoker([
  'stats:revenue',
  'stats:users', 
  'stats:orders'
]);

const results = await loadDashboard([
  { period: '30d' },
  { status: 'active' },
  { status: 'pending' }
]);

๐Ÿ—๏ธ Core Concepts

Commander Instance

The heart of the system - manages all commands:

// Create and configure
const commander = new Commander();
commander.maxHistorySize = 200;
commander.maxRecentSize = 15;

// Register system commands
commander.add({
  key: 'app:refresh',
  label: 'Refresh Application',
  handle: async () => location.reload()
});

Command Structure

Commands are the building blocks:

interface Command<TInput, TOutput> {
  key: CommandKey;                    // Unique identifier
  label: string;                      // Human-readable name
  handle: (input?: TInput) => Promise<TOutput>; // The actual function
  description?: string;               // Detailed description
  category?: CommandCategory;         // Organization
  icon?: string;                      // Visual indicator
  shortcut?: string;                  // Keyboard shortcut
  when?: () => boolean | Promise<boolean>; // Conditional availability
  tags?: string[];                    // Search tags
  priority?: number;                  // Search ranking
  owner?: string;                     // Who registered it
  source?: CommandSource;             // Where it came from
}

Command Builder

Fluent API for command creation:

import { CommandBuilder } from '@darksnow-ui/commander';

const saveCommand = CommandBuilder
  .create<SaveInput, SaveOutput>('file:save')
  .label('Save File')
  .description('Save the current file to disk')
  .category('file')
  .icon('๐Ÿ’พ')
  .shortcut('ctrl+s')
  .tags(['file', 'save', 'disk'])
  .priority(100)
  .handle(async (input) => {
    const result = await saveFile(input);
    return { success: true, path: result.path };
  })
  .build();

commander.add(saveCommand);

Search System

Intelligent hierarchical scoring:

const results = await commander.search('save file', {
  category: 'file',     // Filter by category
  owner: 'editor',      // Filter by owner
  tags: ['important'],  // Filter by tags
  limit: 10            // Limit results
});

// Results are scored by:
// 1. Exact key match (highest)
// 2. Label match
// 3. Description match
// 4. Tag match
// 5. Fuzzy match (lowest)

Event System

Monitor command lifecycle:

// Listen to events
commander.on('commandRegistered', (command) => {
  console.log('New command:', command.key);
});

commander.on('beforeExecute', ({ command, input }) => {
  analytics.track('command_execute', { key: command.key });
});

commander.on('afterExecute', ({ command, result, duration }) => {
  console.log(`Command ${command.key} took ${duration}ms`);
});

commander.on('executionError', ({ command, error }) => {
  errorReporter.log(error);
});

๐Ÿ“š Documentation

๐Ÿ“– Guides

๐ŸŽฏ Examples

function TextEditor({ document }) {
  const [content, setContent] = useState(document.content);
  const [isDirty, setIsDirty] = useState(false);

  // Save command - only available when dirty
  useCustomCommand({
    key: `doc:save:${document.id}`,
    label: `Save ${document.name}`,
    category: 'file',
    icon: '๐Ÿ’พ',
    shortcut: 'ctrl+s',
    when: () => isDirty,
    handle: async () => {
      await saveDocument(document.id, content);
      setIsDirty(false);
      return { saved: true };
    }
  });

  // Format command
  useCustomCommand({
    key: `doc:format:${document.id}`,
    label: `Format ${document.name}`,
    category: 'edit',
    icon: 'โœจ',
    shortcut: 'shift+alt+f',
    handle: async () => {
      const formatted = await formatText(content);
      setContent(formatted);
      setIsDirty(true);
      return { formatted: true };
    }
  });

  return (
    <textarea 
      value={content} 
      onChange={(e) => {
        setContent(e.target.value);
        setIsDirty(true);
      }}
    />
  );
}
function ProductList({ products }) {
  // Register command for each product
  products.forEach(product => {
    useCustomCommand({
      key: `product:edit:${product.id}`,
      label: `Edit ${product.name}`,
      description: `SKU: ${product.sku}`,
      category: 'products',
      icon: 'โœ๏ธ',
      tags: ['product', 'edit', product.category],
      searchKeywords: [product.name, product.sku, product.brand],
      handle: async () => {
        await openProductEditor(product.id);
        return { opened: true };
      }
    });
  });

  // Bulk operations
  const bulkDelete = useInvoker<{ ids: string[] }>('products:bulk-delete');
  
  const handleBulkDelete = async (selectedIds: string[]) => {
    const result = await bulkDelete({ ids: selectedIds });
    if (result.success) {
      toast.success(`Deleted ${result.count} products`);
    }
  };

  return <ProductGrid products={products} onBulkDelete={handleBulkDelete} />;
}
function GameControls() {
  const [isPaused, setIsPaused] = useState(false);
  
  // Game actions
  useCustomCommand({
    key: 'game:pause',
    label: isPaused ? 'Resume Game' : 'Pause Game',
    icon: isPaused ? 'โ–ถ๏ธ' : 'โธ๏ธ',
    shortcut: 'space',
    handle: async () => {
      setIsPaused(!isPaused);
      return { paused: !isPaused };
    }
  });

  useCustomCommand({
    key: 'game:save',
    label: 'Quick Save',
    icon: '๐Ÿ’พ',
    shortcut: 'f5',
    when: () => !isPaused,
    handle: async () => {
      const slot = await saveGame();
      return { slot };
    }
  });

  useCustomCommand({
    key: 'game:load',
    label: 'Quick Load',
    icon: '๐Ÿ“‚',
    shortcut: 'f9',
    handle: async () => {
      await loadLastSave();
      return { loaded: true };
    }
  });

  return <GameUI paused={isPaused} />;
}

๐Ÿ† Comparison

Feature@darksnow-ui/commandercmdkkbarCustom Solution
TypeScriptโœ… Full genericsโœ… Basicโœ… Good๐Ÿคท Depends
React Hooksโœ… 10 specialized hooksโŒ NoneโŒ Limited๐Ÿคท Depends
Auto Cleanupโœ… AutomaticโŒ ManualโŒ ManualโŒ Manual
Conditional Commandsโœ… Built-in when()โŒ ManualโŒ Manual๐Ÿคท Depends
State Managementโœ… Built-in trackingโŒ NoneโŒ None๐Ÿคท Depends
Event Systemโœ… Full lifecycleโŒ Noneโœ… Limited๐Ÿคท Depends
Search Algorithmโœ… Hierarchical scoringโœ… Fuzzyโœ… Fuzzy๐Ÿคท Depends
Batch Executionโœ… Built-in hooksโŒ NoneโŒ None๐Ÿคท Depends
Performanceโœ… O(1) operationsโš ๏ธ O(n)โš ๏ธ O(n)๐Ÿคท Depends
Bundle Size๐Ÿ“ฆ ~50KB๐Ÿ“ฆ ~40KB๐Ÿ“ฆ ~45KB๐Ÿคท Depends

๐Ÿš€ Performance

Built for scale with real-world optimization:

  • โšก O(1) Operations: Command lookup, registration, and removal
  • ๐Ÿ” Efficient Search: Optimized scoring algorithm
  • ๐Ÿง  Smart Memoization: React renders optimized automatically
  • ๐Ÿ“ฆ Memory Bounded: Automatic cleanup of history and listeners
  • ๐ŸŽฏ Lazy Evaluation: Commands only checked when needed
  • ๐Ÿ“Š Battle Tested: Used in production with 500+ commands

๐Ÿ›ก๏ธ TypeScript

First-class TypeScript support with advanced patterns:

// Strongly typed commands
interface SaveFileInput {
  filename: string;
  content: string;
  format?: 'utf8' | 'binary';
}

interface SaveFileOutput {
  success: boolean;
  path: string;
  size: number;
}

// Type-safe registration
useCustomCommand<SaveFileInput, SaveFileOutput>({
  key: 'file:save',
  label: 'Save File',
  handle: async (input) => {
    // input is typed as SaveFileInput
    const result = await fs.writeFile(input.filename, input.content);
    // Must return SaveFileOutput
    return {
      success: true,
      path: result.path,
      size: input.content.length
    };
  }
});

// Type-safe execution
const saveFile = useInvoker<SaveFileInput, SaveFileOutput>('file:save');
const result = await saveFile({
  filename: 'test.txt',
  content: 'Hello world'
}); // result is typed as SaveFileOutput

๐Ÿงช Testing

Comprehensive test coverage with 192 tests:

import { renderHook } from '@testing-library/react';
import { CommanderProvider, useCustomCommand } from '@darksnow-ui/commander';

test('command registration and execution', async () => {
  const { result } = renderHook(
    () => {
      useCustomCommand({
        key: 'test:command',
        label: 'Test Command',
        handle: async (input: { value: number }) => {
          return { doubled: input.value * 2 };
        }
      });
      
      return useInvoker<{ value: number }, { doubled: number }>('test:command');
    },
    {
      wrapper: ({ children }) => (
        <CommanderProvider commander={new Commander()}>
          {children}
        </CommanderProvider>
      )
    }
  );

  const output = await result.current({ value: 5 });
  expect(output.doubled).toBe(10);
});

๐Ÿ”ง Configuration

Commander Options

const commander = new Commander({
  maxHistorySize: 200,     // Maximum execution history entries
  maxRecentSize: 15,       // Maximum recent commands to track
  executionTimeout: 30000, // Default timeout for commands (ms)
  enableDevTools: true,    // Enable debugging features
});

Provider Options

<CommanderProvider
  commander={commander}
  enableDevTools={true}
  onReady={(commander) => {
    // Called when commander is ready
    console.log('Commander initialized');
  }}
>
  {children}
</CommanderProvider>

๐Ÿ“„ License

MIT ยฉ Anderson Rosa

๐Ÿค Contributing

We love contributions! See our Contributing Guide for details.

๐Ÿ’ฌ Community & Support

โญ Show Your Support

If Commander helps your project, please consider:

  • โญ Starring this repository
  • ๐Ÿฆ Sharing on social media
  • ๐Ÿ“ Writing about your experience
  • ๐Ÿค Contributing to the project

Built with โค๏ธ by Anderson Rosa

Part of the DarkSnow UI ecosystem

โญ Star on GitHub โ€ข ๐Ÿ“– Read the Docs โ€ข ๐Ÿš€ View Examples

1.0.5

7 months ago

1.0.4

7 months ago

1.0.3

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago