skeleton-crew-runtime 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,726 @@
1
+ # Skeleton Crew Runtime
2
+
3
+ **Modernize your legacy app without rewriting it.**
4
+
5
+ A minimal runtime that lets you add new features to existing applications using a clean plugin architecture — while keeping your old code running.
6
+
7
+ ## The Problem
8
+
9
+ You have a working application, but:
10
+
11
+ - Adding features means touching fragile legacy code
12
+ - Different parts use different patterns (callbacks, promises, globals)
13
+ - Testing is hard because everything is tightly coupled
14
+ - You want to use modern patterns but can't justify a full rewrite
15
+
16
+ **Sound familiar?**
17
+
18
+ ## The Solution
19
+
20
+ Skeleton Crew lets you write new features as isolated plugins that can access your existing services — without touching legacy code.
21
+
22
+ ```typescript
23
+ // Your existing app (unchanged)
24
+ const legacyApp = {
25
+ database: new DatabaseConnection(),
26
+ logger: new Logger(),
27
+ userService: new UserService()
28
+ };
29
+
30
+ // Add Skeleton Crew alongside it
31
+ const runtime = new Runtime({
32
+ hostContext: {
33
+ db: legacyApp.database,
34
+ logger: legacyApp.logger,
35
+ users: legacyApp.userService
36
+ }
37
+ });
38
+
39
+ // Write new features as clean, testable plugins
40
+ const newFeaturePlugin = {
41
+ name: "analytics",
42
+ version: "1.0.0",
43
+ setup(ctx) {
44
+ // Access legacy services through context
45
+ const { db, logger } = ctx.host;
46
+
47
+ ctx.actions.registerAction({
48
+ id: "analytics:track",
49
+ handler: async (event) => {
50
+ logger.info("Tracking event:", event);
51
+ await db.insert("analytics", event);
52
+ ctx.events.emit("analytics:tracked", event);
53
+ }
54
+ });
55
+ }
56
+ };
57
+
58
+ await runtime.initialize();
59
+ runtime.getContext().plugins.registerPlugin(newFeaturePlugin);
60
+ ```
61
+
62
+ **Result:** New code is clean, testable, and isolated. Old code keeps working.
63
+
64
+ ## Why This Approach Works
65
+
66
+ ### ✅ Zero Risk
67
+ Your existing code doesn't change. New features run alongside it.
68
+
69
+ ### ✅ Incremental Migration
70
+ Migrate one feature at a time. No big-bang rewrites.
71
+
72
+ ### ✅ Immediate Value
73
+ Start writing better code today. See benefits immediately.
74
+
75
+ ### ✅ Team Friendly
76
+ New developers work in clean plugin code. Legacy experts maintain old code.
77
+
78
+ ### ✅ Future Proof
79
+ When you're ready, gradually replace legacy services. Or don't — both work fine.
80
+
81
+
82
+ ## Quick Start: Add Your First Feature
83
+
84
+ ### 1. Install
85
+
86
+ ```bash
87
+ npm install skeleton-crew-runtime
88
+ ```
89
+
90
+ ### 2. Create Runtime with Your Existing Services
91
+
92
+ ```typescript
93
+ import { Runtime } from "skeleton-crew-runtime";
94
+
95
+ // Inject your existing services
96
+ const runtime = new Runtime({
97
+ hostContext: {
98
+ db: yourDatabase,
99
+ api: yourApiClient,
100
+ logger: yourLogger,
101
+ config: yourConfig
102
+ }
103
+ });
104
+
105
+ await runtime.initialize();
106
+ ```
107
+
108
+ ### 3. Write a Plugin for Your New Feature
109
+
110
+ ```typescript
111
+ // plugins/notifications.ts
112
+ export const NotificationsPlugin = {
113
+ name: "notifications",
114
+ version: "1.0.0",
115
+
116
+ setup(ctx) {
117
+ // Access your existing services
118
+ const { db, logger } = ctx.host;
119
+
120
+ // Register an action
121
+ ctx.actions.registerAction({
122
+ id: "notifications:send",
123
+ handler: async ({ userId, message }) => {
124
+ logger.info(`Sending notification to user ${userId}`);
125
+
126
+ // Use your existing database
127
+ await db.insert("notifications", {
128
+ userId,
129
+ message,
130
+ createdAt: new Date()
131
+ });
132
+
133
+ // Emit event for other plugins
134
+ ctx.events.emit("notification:sent", { userId, message });
135
+
136
+ return { success: true };
137
+ }
138
+ });
139
+
140
+ // Listen to events from other parts of your app
141
+ ctx.events.on("user:registered", async (user) => {
142
+ await ctx.actions.runAction("notifications:send", {
143
+ userId: user.id,
144
+ message: "Welcome to our app!"
145
+ });
146
+ });
147
+ }
148
+ };
149
+ ```
150
+
151
+ ### 4. Register and Use
152
+
153
+ ```typescript
154
+ const ctx = runtime.getContext();
155
+
156
+ // Register your plugin
157
+ ctx.plugins.registerPlugin(NotificationsPlugin);
158
+
159
+ // Call from anywhere in your app
160
+ await ctx.actions.runAction("notifications:send", {
161
+ userId: 123,
162
+ message: "Your order has shipped!"
163
+ });
164
+ ```
165
+
166
+ **That's it.** Your new feature is isolated, testable, and doesn't touch legacy code.
167
+
168
+ ## Core Concepts (5 Minutes to Learn)
169
+
170
+ ### 1. Host Context: Bridge to Legacy Code
171
+
172
+ Inject your existing services so plugins can use them:
173
+
174
+ ```typescript
175
+ const runtime = new Runtime({
176
+ hostContext: {
177
+ db: legacyDatabase, // Your existing DB connection
178
+ cache: redisClient, // Your existing cache
179
+ auth: authService // Your existing auth
180
+ }
181
+ });
182
+
183
+ // Plugins access via ctx.host
184
+ const { db, cache, auth } = ctx.host;
185
+ ```
186
+
187
+ ### 2. Actions: Business Logic
188
+
189
+ Encapsulate operations as named, testable actions:
190
+
191
+ ```typescript
192
+ ctx.actions.registerAction({
193
+ id: "orders:create",
194
+ handler: async (orderData) => {
195
+ const { db } = ctx.host;
196
+ const order = await db.insert("orders", orderData);
197
+ ctx.events.emit("order:created", order);
198
+ return order;
199
+ }
200
+ });
201
+
202
+ // Call from anywhere
203
+ const order = await ctx.actions.runAction("orders:create", data);
204
+ ```
205
+
206
+ ### 3. Events: Decouple Features
207
+
208
+ Let features communicate without direct dependencies:
209
+
210
+ ```typescript
211
+ // Feature A: Emit event
212
+ ctx.events.emit("order:created", order);
213
+
214
+ // Feature B: React to event (doesn't know about Feature A)
215
+ ctx.events.on("order:created", (order) => {
216
+ ctx.actions.runAction("email:send", {
217
+ to: order.customerEmail,
218
+ template: "order-confirmation"
219
+ });
220
+ });
221
+ ```
222
+
223
+ ### 4. Plugins: Isolated Features
224
+
225
+ Group related functionality into plugins:
226
+
227
+ ```typescript
228
+ export const OrdersPlugin = {
229
+ name: "orders",
230
+ version: "1.0.0",
231
+ setup(ctx) {
232
+ // Register actions, screens, event handlers
233
+ // Everything for "orders" feature in one place
234
+ }
235
+ };
236
+ ```
237
+
238
+ ### 5. Screens (Optional): UI Definitions
239
+
240
+ Define screens that any UI framework can render:
241
+
242
+ ```typescript
243
+ ctx.screens.registerScreen({
244
+ id: "orders:list",
245
+ title: "Orders",
246
+ component: OrderListComponent // React, Vue, or anything
247
+ });
248
+ ```
249
+
250
+ ## Real-World Migration Patterns
251
+
252
+ ### Pattern 1: Feature Flags (Safest)
253
+
254
+ Gradually switch features from legacy to plugins:
255
+
256
+ ```typescript
257
+ const features = {
258
+ notifications: 'plugin', // Using Skeleton Crew
259
+ payments: 'legacy', // Still using old code
260
+ reports: 'plugin' // Using Skeleton Crew
261
+ };
262
+
263
+ class App {
264
+ async sendNotification(userId, message) {
265
+ if (features.notifications === 'plugin') {
266
+ return this.runtime.getContext().actions.runAction(
267
+ 'notifications:send',
268
+ { userId, message }
269
+ );
270
+ } else {
271
+ return this.legacyNotifications.send(userId, message);
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ **Benefits:** Roll back instantly if issues arise. Test in production with small user percentage.
278
+
279
+ ### Pattern 2: New Features Only
280
+
281
+ Keep legacy code frozen. All new features are plugins:
282
+
283
+ ```typescript
284
+ // Legacy code (frozen, no changes)
285
+ class LegacyApp {
286
+ constructor() {
287
+ this.db = new Database();
288
+ this.users = new UserService(this.db);
289
+ }
290
+ }
291
+
292
+ // New features as plugins
293
+ const runtime = new Runtime({
294
+ hostContext: {
295
+ db: legacyApp.db,
296
+ users: legacyApp.users
297
+ }
298
+ });
299
+
300
+ // New "analytics" feature - clean plugin code
301
+ const AnalyticsPlugin = {
302
+ name: "analytics",
303
+ version: "1.0.0",
304
+ setup(ctx) {
305
+ const { db, users } = ctx.host;
306
+
307
+ ctx.actions.registerAction({
308
+ id: "analytics:track",
309
+ handler: async (event) => {
310
+ await db.insert("analytics", event);
311
+ }
312
+ });
313
+ }
314
+ };
315
+ ```
316
+
317
+ **Benefits:** Legacy code stays stable. New code is clean and testable. No rewrite needed.
318
+
319
+ ### Pattern 3: Strangler Fig
320
+
321
+ Gradually replace legacy services with plugin-based ones:
322
+
323
+ ```typescript
324
+ // Phase 1: Legacy service
325
+ const legacyAuth = new LegacyAuthService();
326
+
327
+ // Phase 2: New auth plugin (same interface)
328
+ const AuthPlugin = {
329
+ name: "auth",
330
+ version: "2.0.0",
331
+ setup(ctx) {
332
+ ctx.actions.registerAction({
333
+ id: "auth:login",
334
+ handler: async (credentials) => {
335
+ // New implementation
336
+ }
337
+ });
338
+ }
339
+ };
340
+
341
+ // Phase 3: Switch gradually
342
+ const useNewAuth = process.env.NEW_AUTH === 'true';
343
+
344
+ async function login(credentials) {
345
+ if (useNewAuth) {
346
+ return runtime.getContext().actions.runAction('auth:login', credentials);
347
+ } else {
348
+ return legacyAuth.login(credentials);
349
+ }
350
+ }
351
+ ```
352
+
353
+ **Benefits:** Replace services one at a time. Run both versions in parallel. Validate before full switch.
354
+
355
+ ### Pattern 4: Event-Driven Integration
356
+
357
+ Let legacy code emit events that plugins handle:
358
+
359
+ ```typescript
360
+ // Legacy code (minimal change - just emit events)
361
+ class LegacyUserService {
362
+ async createUser(userData) {
363
+ const user = await this.db.insert('users', userData);
364
+
365
+ // Add this one line
366
+ eventBus.emit('user:created', user);
367
+
368
+ return user;
369
+ }
370
+ }
371
+
372
+ // New plugin reacts to legacy events
373
+ const WelcomeEmailPlugin = {
374
+ name: "welcome-emails",
375
+ version: "1.0.0",
376
+ setup(ctx) {
377
+ ctx.events.on('user:created', async (user) => {
378
+ await ctx.actions.runAction('email:send', {
379
+ to: user.email,
380
+ template: 'welcome'
381
+ });
382
+ });
383
+ }
384
+ };
385
+ ```
386
+
387
+ **Benefits:** Minimal legacy changes. New features are completely isolated. Easy to add/remove features.
388
+
389
+ ## When to Use Skeleton Crew
390
+
391
+ ### ✅ Perfect For
392
+
393
+ - **Legacy modernization** - Add features without touching old code
394
+ - **Internal tools** - Admin panels, dashboards, dev tools
395
+ - **Browser extensions** - Background scripts with plugin architecture
396
+ - **Modular applications** - Features that can be enabled/disabled
397
+ - **Multi-team codebases** - Teams work on isolated plugins
398
+ - **Gradual rewrites** - Migrate piece by piece
399
+
400
+ ### ❌ Not Ideal For
401
+
402
+ - **Greenfield apps with simple needs** - Might be overkill
403
+ - **Static websites** - No need for runtime architecture
404
+ - **Apps with single feature** - Plugin system adds unnecessary complexity
405
+
406
+ ---
407
+
408
+ ## Advanced Features
409
+
410
+ ### Introspection API
411
+
412
+ Debug and monitor your runtime:
413
+
414
+ ```typescript
415
+ const ctx = runtime.getContext();
416
+
417
+ // List all registered resources
418
+ const actions = ctx.introspect.listActions();
419
+ const plugins = ctx.introspect.listPlugins();
420
+ const screens = ctx.introspect.listScreens();
421
+
422
+ // Get metadata
423
+ const stats = ctx.introspect.getMetadata();
424
+ // { runtimeVersion: "0.1.0", totalActions: 15, totalPlugins: 5 }
425
+ ```
426
+
427
+ **Use cases:** Admin dashboards, debugging tools, runtime monitoring.
428
+
429
+ ### Action Timeouts
430
+
431
+ Prevent hanging operations:
432
+
433
+ ```typescript
434
+ ctx.actions.registerAction({
435
+ id: "api:fetch",
436
+ timeout: 5000, // 5 seconds max
437
+ handler: async () => {
438
+ return await fetch('/api/data');
439
+ }
440
+ });
441
+ ```
442
+
443
+ ### Event Patterns
444
+
445
+ **Fire-and-forget:**
446
+ ```typescript
447
+ ctx.events.emit('user:created', user); // Synchronous
448
+ ```
449
+
450
+ **Wait for handlers:**
451
+ ```typescript
452
+ await ctx.events.emitAsync('order:processed', order); // Asynchronous
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Learning Resources
458
+
459
+ ### Complete Migration Guide
460
+
461
+ See [Migration Guide](docs/guides/migration-guide.md) for:
462
+ - Step-by-step migration strategies
463
+ - Real-world examples
464
+ - Common pitfalls and solutions
465
+ - Testing strategies
466
+
467
+ ### Tutorial: Build a Task Manager
468
+
469
+ Learn by building a complete app from scratch:
470
+
471
+ ```bash
472
+ npm run build
473
+ npm run tutorial:01 # Start with step 1
474
+ ```
475
+
476
+ See [example/tutorial/README.md](example/tutorial/README.md).
477
+
478
+ ### Example Applications
479
+
480
+ Run focused examples:
481
+
482
+ ```bash
483
+ npm run example:01 # Plugin System
484
+ npm run example:02 # Screen Registry
485
+ npm run example:03 # Action Engine
486
+ npm run example:04 # Event Bus
487
+ npm run example:05 # Runtime Context
488
+ npm run example # Complete playground
489
+ ```
490
+
491
+ ## How It Works
492
+
493
+ ```
494
+ ┌─────────────────────────────────────────────────┐
495
+ │ Your Legacy Application │
496
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
497
+ │ │ Database │ │ API │ │ Logger │ │
498
+ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
499
+ │ │ │ │ │
500
+ │ └─────────────┴──────────────┘ │
501
+ │ │ │
502
+ │ Host Context (injected) │
503
+ └─────────────────────┬───────────────────────────┘
504
+
505
+ ┌─────────────────────▼───────────────────────────┐
506
+ │ Skeleton Crew Runtime │
507
+ │ │
508
+ │ ┌──────────────────────────────────────────┐ │
509
+ │ │ Plugin 1 Plugin 2 Plugin 3 │ │
510
+ │ │ (new code) (new code) (new code) │ │
511
+ │ └──────────────────────────────────────────┘ │
512
+ │ │ │ │ │
513
+ │ ┌────▼────┐ ┌───▼────┐ ┌───▼────┐ │
514
+ │ │ Actions │ │ Events │ │Screens │ │
515
+ │ └─────────┘ └────────┘ └────────┘ │
516
+ └─────────────────────────────────────────────────┘
517
+ ```
518
+
519
+ **Key insight:** Legacy services flow up through host context. New features are isolated plugins that use those services.
520
+
521
+ ## Testing Your Plugins
522
+
523
+ One of the biggest wins: plugins are easy to test.
524
+
525
+ ```typescript
526
+ import { describe, it, expect, beforeEach } from 'vitest';
527
+ import { Runtime } from 'skeleton-crew-runtime';
528
+ import { NotificationsPlugin } from './notifications.js';
529
+
530
+ describe('NotificationsPlugin', () => {
531
+ let runtime;
532
+ let mockDb;
533
+
534
+ beforeEach(async () => {
535
+ // Mock your legacy services
536
+ mockDb = {
537
+ insert: vi.fn().mockResolvedValue({ id: 1 })
538
+ };
539
+
540
+ // Create isolated runtime for testing
541
+ runtime = new Runtime({
542
+ hostContext: { db: mockDb }
543
+ });
544
+
545
+ await runtime.initialize();
546
+ runtime.getContext().plugins.registerPlugin(NotificationsPlugin);
547
+ });
548
+
549
+ it('sends notification', async () => {
550
+ const ctx = runtime.getContext();
551
+
552
+ const result = await ctx.actions.runAction('notifications:send', {
553
+ userId: 123,
554
+ message: 'Test'
555
+ });
556
+
557
+ expect(result.success).toBe(true);
558
+ expect(mockDb.insert).toHaveBeenCalledWith('notifications', {
559
+ userId: 123,
560
+ message: 'Test',
561
+ createdAt: expect.any(Date)
562
+ });
563
+ });
564
+ });
565
+ ```
566
+
567
+ **Benefits:**
568
+ - No need to set up entire legacy app
569
+ - Mock only what you need
570
+ - Fast, isolated tests
571
+ - Easy to test edge cases
572
+
573
+ ## API Quick Reference
574
+
575
+ ### Runtime Setup
576
+
577
+ ```typescript
578
+ import { Runtime } from 'skeleton-crew-runtime';
579
+
580
+ const runtime = new Runtime({
581
+ hostContext: {
582
+ // Your existing services
583
+ db: yourDatabase,
584
+ logger: yourLogger
585
+ }
586
+ });
587
+
588
+ await runtime.initialize();
589
+ const ctx = runtime.getContext();
590
+ ```
591
+
592
+ ### Working with Actions
593
+
594
+ ```typescript
595
+ // Register
596
+ ctx.actions.registerAction({
597
+ id: 'feature:action',
598
+ timeout: 5000, // optional
599
+ handler: async (params) => {
600
+ const { db } = ctx.host;
601
+ return await db.query(params);
602
+ }
603
+ });
604
+
605
+ // Execute
606
+ const result = await ctx.actions.runAction('feature:action', params);
607
+ ```
608
+
609
+ ### Working with Events
610
+
611
+ ```typescript
612
+ // Subscribe
613
+ ctx.events.on('entity:changed', (data) => {
614
+ console.log('Changed:', data);
615
+ });
616
+
617
+ // Emit (fire-and-forget)
618
+ ctx.events.emit('entity:changed', { id: 123 });
619
+
620
+ // Emit (wait for handlers)
621
+ await ctx.events.emitAsync('entity:changed', { id: 123 });
622
+ ```
623
+
624
+ ### Working with Plugins
625
+
626
+ ```typescript
627
+ // Define
628
+ export const MyPlugin = {
629
+ name: 'my-plugin',
630
+ version: '1.0.0',
631
+ setup(ctx) {
632
+ // Register actions, screens, events
633
+ },
634
+ dispose(ctx) {
635
+ // Optional cleanup
636
+ }
637
+ };
638
+
639
+ // Register
640
+ ctx.plugins.registerPlugin(MyPlugin);
641
+ ```
642
+
643
+ ### Accessing Host Context
644
+
645
+ ```typescript
646
+ // In any plugin
647
+ setup(ctx) {
648
+ const { db, logger, cache } = ctx.host;
649
+
650
+ // Use your existing services
651
+ await db.query('SELECT * FROM users');
652
+ logger.info('Plugin initialized');
653
+ }
654
+ ```
655
+
656
+ ### Introspection
657
+
658
+ ```typescript
659
+ // List resources
660
+ const actions = ctx.introspect.listActions();
661
+ const plugins = ctx.introspect.listPlugins();
662
+
663
+ // Get metadata
664
+ const actionMeta = ctx.introspect.getActionDefinition('feature:action');
665
+ const stats = ctx.introspect.getMetadata();
666
+ ```
667
+
668
+ **Full API documentation:** [API.md](docs/api/API.md)
669
+
670
+ ## FAQ
671
+
672
+ ### Do I need to rewrite my app?
673
+
674
+ No. Skeleton Crew runs alongside your existing code. Write new features as plugins, keep old code unchanged.
675
+
676
+ ### What if I want to migrate existing features later?
677
+
678
+ You can gradually replace legacy code with plugins using feature flags. Or don't — both approaches work fine.
679
+
680
+ ### Does this work with my UI framework?
681
+
682
+ Yes. Skeleton Crew is UI-agnostic. Use React, Vue, Svelte, or no UI at all. The runtime doesn't care.
683
+
684
+ ### Is this overkill for small apps?
685
+
686
+ 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.
687
+
688
+ ### How big is the runtime?
689
+
690
+ Less than 5KB gzipped. Minimal overhead.
691
+
692
+ ### Can I use this in production?
693
+
694
+ Yes. The runtime is stable and tested. Start with non-critical features, then expand.
695
+
696
+ ---
697
+
698
+ ## Documentation
699
+
700
+ - **[Migration Guide](docs/guides/migration-guide.md)** - Step-by-step migration strategies
701
+ - **[API Reference](docs/api/API.md)** - Complete TypeScript API
702
+ - **[Examples Guide](docs/guides/EXAMPLES_GUIDE.md)** - Learn through examples
703
+ - **[Browser Extensions](docs/use-cases/BROWSER_TOOLS.md)** - Building browser tools
704
+ - **[Project Overview](docs/PROJECT_OVERVIEW.md)** - Architecture deep dive
705
+
706
+ ## Get Started
707
+
708
+ ```bash
709
+ npm install skeleton-crew-runtime
710
+ ```
711
+
712
+ Then follow the [Quick Start](#quick-start-add-your-first-feature) above.
713
+
714
+ ---
715
+
716
+ ## Contributing
717
+
718
+ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
719
+
720
+ ## License
721
+
722
+ MIT
723
+
724
+ ---
725
+
726
+ **Built for developers who need to modernize legacy apps without the risk of a full rewrite.**