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/LICENSE +21 -0
- package/README.md +726 -0
- package/dist/action-engine.d.ts +90 -0
- package/dist/action-engine.d.ts.map +1 -0
- package/dist/action-engine.js +166 -0
- package/dist/action-engine.js.map +1 -0
- package/dist/event-bus.d.ts +55 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +110 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-registry.d.ts +23 -0
- package/dist/plugin-registry.d.ts.map +1 -0
- package/dist/plugin-registry.js +108 -0
- package/dist/plugin-registry.js.map +1 -0
- package/dist/runtime-context.d.ts +127 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +227 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/runtime.d.ts +125 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +257 -0
- package/dist/runtime.js.map +1 -0
- package/dist/screen-registry.d.ts +51 -0
- package/dist/screen-registry.d.ts.map +1 -0
- package/dist/screen-registry.js +82 -0
- package/dist/screen-registry.js.map +1 -0
- package/dist/types.d.ts +189 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +93 -0
- package/dist/types.js.map +1 -0
- package/dist/ui-bridge.d.ts +39 -0
- package/dist/ui-bridge.d.ts.map +1 -0
- package/dist/ui-bridge.js +74 -0
- package/dist/ui-bridge.js.map +1 -0
- package/package.json +60 -0
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.**
|