skeleton-crew-runtime
v0.3.4
Published
A minimal plugin runtime for building modular JavaScript applications.
Downloads
230
Maintainers
Readme
Skeleton Crew Runtime v0.3.3
A minimal plugin runtime for building modular JavaScript applications.
Stop wiring up infrastructure. Start building features.
npm install skeleton-crew-runtime@^0.3.3What's New in v0.3.3
✅ Browser Compatibility - Fixed critical runtime crash in browsers by lazy-loading Node.js dependencies
📝 Documentation - Updated all examples to use proper TypeScript import type syntax
🔧 Stability - Improved test reliability for performance monitoring
What's New in v0.3.2
✅ Service Locator API - New ctx.services API for type-safe inter-plugin communication
📝 Service Locator Guide - New dedicated guide for using the Service Locator pattern
What's New in v0.3.0
✅ Config Validation - Validate plugin configuration before setup runs with detailed error reporting
📝 Logger Documentation - Comprehensive guide to the built-in logging system and external integrations
📊 Optimized Telemetry - Cleaner startup logs with consolidated plugin loading information
🔍 Introspection - Enhanced plugin metadata for better runtime transparency
What's New in v0.2.1
🔍 Plugin Discovery - Automatic plugin loading from file paths and npm packages
🔄 Dependency Resolution - Automatic topological sorting for correct plugin initialization order
🛠️ Enhanced DX - Better error messages with dependency hints for missing actions
🚀 Production Ready - All critical bugs fixed based on real-world usage feedback
✅ Verified Stable - Tested and validated in production migrations
Plugin Discovery Example
// Automatic plugin discovery - no manual registration needed!
const runtime = new Runtime<MyConfig>({
config: myConfig,
// Load plugins from directories
pluginPaths: [
'./plugins', // Load all plugins from directory
'./custom-plugin.js' // Load specific plugin file
],
// Load plugins from npm packages
pluginPackages: [
'@my-org/auth-plugin',
'my-custom-plugin'
]
});
await runtime.initialize(); // Plugins auto-loaded and sorted by dependencies!What's New in v0.2.0
🎯 Generic Runtime/Context - Full TypeScript generic support for type-safe configuration
⚡ Sync Config Access - Direct synchronous access to configuration via ctx.config
🔗 Plugin Dependencies - Explicit dependency resolution with validation
📝 Enhanced Logger - Logger available on context for all plugins
🔄 100% Backward Compatible - All v0.1.x code continues to work
Quick Migration Example
// v0.1.x
const runtime = new Runtime({
hostContext: { config: myConfig }
});
// v0.2.0 - Fully typed!
interface MyConfig { apiUrl: string; }
const runtime = new Runtime<MyConfig>({
config: { apiUrl: 'https://api.example.com' }
});Documentation
Getting Started
- Installation - Install and setup
- API Reference - Complete TypeScript API
- Core Concepts - Understand the fundamentals
- Your First Plugin - Build your first feature
- Logger Guide - Master the built-in logging system
- Config Validation - How to validate plugin configuration
- Service Locator - Type-safe inter-plugin communication
v0.2.0 Migration & Guides
- v0.1.x → v0.2.0 Migration - Complete migration walkthrough
- Plugin Dependencies - Dependency patterns and best practices
- Sync vs Async Patterns - Configuration and execution patterns
- Real-World Examples - Production-ready implementations
- Migration Guide - Integrate with existing apps
- Examples Guide - Learn through code examples
Use Cases
- Browser Extensions - Build browser tools
- CLI Applications - Command-line tools
- Real-Time Apps - Collaboration and sync
Advanced
- Architecture - How it works under the hood
- Troubleshooting - Common issues and solutions
What is this?
Skeleton Crew Runtime is a lightweight foundation for building applications where features can be added, removed, or replaced without touching existing code. Think VS Code's extension system, but for any JavaScript application.
Core idea: Your app is a collection of plugins. Each plugin registers actions (business logic), screens (UI definitions), and events (communication). The runtime coordinates everything.
Result: Add features by dropping in plugins. Remove features by taking them out. No refactoring. No breaking changes.
Why would I use this?
You're building something modular and you might know these challenges:
- Wiring up infrastructure - Event buses, plugin loaders, action registries
- Tight coupling - Changing one feature breaks three others
- Testing nightmares - Can't test features in isolation
- Framework lock-in - Married to React/Vue/whatever forever
- Refactoring hell - Adding features means touching existing code
Skeleton Crew Runtime gives you:
- Plugin isolation - Features don't know about each other
- Event-driven communication - Plugins coordinate without coupling
- Framework freedom - Business logic separate from UI
- Testability - Mock what you need, test what matters
- Minimal core - < 5KB, zero dependencies
Show me code
Here's a complete plugin that adds a feature to your app:
import { Runtime } from 'skeleton-crew-runtime';
import type { PluginDefinition, RuntimeContext } from 'skeleton-crew-runtime';
// v0.2.0: Define your config interface
interface AppConfig {
notifications: {
apiKey: string;
defaultTimeout: number;
};
features: {
pushNotifications: boolean;
};
}
// 1. Create typed runtime
const runtime = new Runtime<AppConfig>({
config: {
notifications: {
apiKey: process.env.NOTIFICATION_API_KEY!,
defaultTimeout: 5000
},
features: {
pushNotifications: true
}
}
});
await runtime.initialize();
const ctx = runtime.getContext();
// 2. Write a plugin (this is a complete feature)
const notificationsPlugin: PluginDefinition<AppConfig> = {
name: 'notifications',
version: '1.0.0',
dependencies: [], // v0.2.0: Explicit dependencies
setup(ctx: RuntimeContext<AppConfig>) {
// ✅ Fully typed config access
const { notifications, features } = ctx.config;
if (!features.pushNotifications) {
ctx.logger.info('Push notifications disabled');
return;
}
// Register business logic with type safety
ctx.actions.registerAction<
{ userId: string; message: string },
{ success: boolean; messageId: string }
>({
id: 'notifications:send',
handler: async ({ userId, message }, ctx) => {
// ✅ Typed config access in handlers
const { apiKey, defaultTimeout } = ctx.config.notifications;
// Your logic here
const messageId = await sendPushNotification(userId, message, {
apiKey,
timeout: defaultTimeout
});
// Let other plugins know
ctx.events.emit('notification:sent', { userId, messageId });
return { success: true, messageId };
},
timeout: notifications.defaultTimeout
});
// React to other plugins
ctx.events.on('user:registered', async (user: any) => {
await ctx.actions.runAction('notifications:send', {
userId: user.id,
message: 'Welcome!'
});
});
ctx.logger.info('Notifications plugin initialized');
}
};
// 3. Register and use
ctx.plugins.registerPlugin(notificationsPlugin);
// anywhere in your app - fully typed!
const result = await ctx.actions.runAction('notifications:send', {
userId: '123',
message: 'Your order shipped!'
});
console.log(`Message sent: ${result.messageId}`);That's it. The plugin is isolated, testable, fully typed, and can be removed without breaking anything.
Core concepts (5 minutes)
1. Plugin Discovery (v0.2.1): Automatic Loading
No more manual plugin registration! The runtime can automatically discover and load plugins:
import { Runtime } from 'skeleton-crew-runtime';
const runtime = new Runtime<MyConfig>({
config: myConfig,
// Discover plugins from file system
pluginPaths: [
'./src/plugins', // Directory: loads all .js/.mjs files
'./auth-plugin.js', // Single file: loads specific plugin
'./dist/plugins' // Works with compiled TypeScript too!
],
// Discover plugins from npm packages
pluginPackages: [
'@my-org/auth-plugin', // npm package with plugin export
'my-logging-plugin' // Any package that exports a plugin
]
});
await runtime.initialize();
// ✅ All plugins auto-loaded and sorted by dependencies!Dependency Resolution: Plugins are automatically sorted by their dependencies array, so they initialize in the correct order.
2. Plugins: Isolated Features
2. Plugins: Isolated Features
A plugin is just an object with a name and a setup function:
import type { PluginDefinition, RuntimeContext } from 'skeleton-crew-runtime';
// v0.2.0: Define your config type
interface MyAppConfig {
apiUrl: string;
features: { analytics: boolean };
}
export const myPlugin: PluginDefinition<MyAppConfig> = {
name: 'my-plugin',
version: '1.0.0',
dependencies: ['config'], // v0.2.0: Explicit dependencies
setup(ctx: RuntimeContext<MyAppConfig>) {
// ✅ Fully typed config access
const { apiUrl, features } = ctx.config;
if (features.analytics) {
ctx.logger.info(`Plugin initialized for ${apiUrl}`);
}
// Register your feature here
},
dispose(ctx: RuntimeContext<MyAppConfig>) {
// Optional: cleanup resources when plugin is removed
// Use this for: closing connections, clearing timers, releasing memory
// Event listeners auto-cleanup, so you usually don't need this
}
};3. Actions: Business Logic
Actions are named functions that do work:
// v0.2.0: Type-safe action registration
interface CreateOrderParams {
customerId: string;
items: Array<{ id: string; quantity: number }>;
}
interface Order {
id: string;
customerId: string;
total: number;
status: 'pending' | 'confirmed';
}
// Register an action
ctx.actions.registerAction<CreateOrderParams, Order>({
id: 'orders:create',
handler: async (orderData, ctx) => {
// ✅ Typed config access
const { apiUrl } = ctx.config;
// ✅ Typed parameters
const { customerId, items } = orderData;
const order = await createOrder(customerId, items);
ctx.events.emit('order:created', order);
ctx.logger.info(`Order created: ${order.id}`);
return order; // ✅ Typed return value
},
timeout: 10000 // Optional timeout
});
// Call from anywhere - fully typed!
const order = await ctx.actions.runAction<CreateOrderParams, Order>(
'orders:create',
{ customerId: '123', items: [{ id: 'item1', quantity: 2 }] }
);4. Events: Decouple Features
Plugins communicate without knowing about each other:
// Plugin A: Emit event
ctx.events.emit('order:created', order);
// Plugin B: React (doesn't know about Plugin A)
ctx.events.on('order:created', (order) => {
sendConfirmationEmail(order);
});
// v0.2.0: Async event handling
await ctx.events.emitAsync('order:created', order); // Wait for all handlers5. Configuration: Type-Safe Access (v0.2.0)
Direct synchronous access to typed configuration:
import { Runtime } from 'skeleton-crew-runtime';
interface AppConfig {
database: { url: string; maxConnections: number };
api: { baseUrl: string; timeout: number };
features: { caching: boolean; analytics: boolean };
}
const runtime = new Runtime<AppConfig>({
config: {
database: {
url: process.env.DATABASE_URL!,
maxConnections: 10
},
api: {
baseUrl: 'https://api.example.com',
timeout: 5000
},
features: {
caching: true,
analytics: process.env.NODE_ENV === 'production'
}
}
});
// In plugins: direct typed access
setup(ctx: RuntimeContext<AppConfig>) {
// ✅ Fully typed, synchronous access
const { database, api, features } = ctx.config;
if (features.caching) {
initializeCache();
}
// ✅ Available in action handlers
ctx.actions.registerAction({
id: 'api:request',
handler: async (endpoint: string) => {
const { baseUrl, timeout } = ctx.config.api;
return await fetch(`${baseUrl}${endpoint}`, { timeout });
}
});
}6. Host Context: Bridge to Existing Code
Inject your existing services so plugins can use them:
import { Runtime } from 'skeleton-crew-runtime';
const runtime = new Runtime<AppConfig>({
config: myTypedConfig,
hostContext: {
// Legacy services
db: yourDatabase,
cache: redisClient,
logger: yourLogger
}
});
await runtime.initialize();
const ctx = runtime.getContext();
// Plugins access via ctx.host (for legacy integration)
const { db, logger } = ctx.host;7. Service Locator (v0.3.1): Type-safe inter-plugin communication
Plugins can register and consume shared services without hard dependency coupling:
// Plugin A: Register a service
setup(ctx) {
const myService = {
doSomething: () => console.log('Service in action!')
};
ctx.services.register('my-api', myService);
}
// Plugin B: Consume the service
setup(ctx) {
// Wait for the providing plugin to initialize first!
const api = ctx.services.get<MyApiType>('my-api');
api.doSomething();
}8. Screens (Optional): UI Definitions
Define screens that any UI framework can render:
ctx.screens.registerScreen({
id: 'orders:list',
title: 'Orders',
component: 'OrderListComponent' // string, class, function, or any type
});What can I build?
Skeleton Crew works for any modular JavaScript application:
Developer Tools
- CLI tools - Task runners, deployment scripts, dev environments
- Browser extensions - Tab managers, productivity tools, dev tools
- Build tools - Custom bundlers, code generators, linters
Internal Applications
- Admin panels - User management, content moderation, analytics
- Dashboards - Monitoring, reporting, data visualization
- Workflow tools - Approval systems, task management, automation
Real-Time Applications
- Collaboration tools - Shared editing, presence, chat
- Live dashboards - Stock tickers, sports scores, IoT monitoring
- Multiplayer features - Game state sync, player coordination
Modular Systems
- Plugin marketplaces - Let users extend your app
- White-label products - Different features for different customers
- Microservices - Coordinate distributed services
Not ideal for: Public-facing websites (use Next.js), complex routing (use React Router), heavy state management (use Redux).
Real examples
CLI Tool (150 lines vs 500+)
What you'll see: Interactive CLI that runs commands, shows output, handles errors. All plugin-based.
# Build a command palette for Git, npm, and Docker:
cd demo/dev-launcher
npm install && npm startReal-Time Collaboration (130 lines vs 500+)
What you'll see: Multiple clients syncing state in real-time. No Firebase, no Socket.io boilerplate.
# Build a multi-user sync system:
cd demo/collab-hub
npm install && npm run build
npm run server # Terminal 1
npm run client # Terminal 2-3FAQ
Do I need to rewrite my app?
No. Skeleton Crew runs alongside your existing code. Write new features as plugins, keep old code unchanged.
What if I want to migrate existing features later?
You can gradually replace legacy code with plugins using feature flags. Or don't — both approaches work fine.
Does this work with my UI framework?
Yes. Skeleton Crew is UI-agnostic. Use React, Vue, Svelte, or no UI at all. The runtime doesn't care.
Is this overkill for small apps?
Possibly. If you have a simple app with no legacy code and no plans to grow, you might not need this. But if you're dealing with technical debt or planning for modularity, it's a good fit.
How big is the runtime?
Less than 5KB gzipped. Minimal overhead.
Can I use this in production?
Yes. The runtime is stable and tested. Start with non-critical features, then expand.
Built for developers who need to modernize legacy apps without the risk of a full rewrite.
