@donkeylabs/server 0.1.1 → 0.1.2

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/docs/cli.md ADDED
@@ -0,0 +1,353 @@
1
+ # CLI & Scripts
2
+
3
+ Command-line tools for managing plugins, generating types, and scaffolding code.
4
+
5
+ ## Quick Reference
6
+
7
+ ### Non-Interactive Commands (AI-Friendly)
8
+
9
+ These commands run without prompts - ideal for automation and AI assistants:
10
+
11
+ ```sh
12
+ # Create plugin with options
13
+ bun scripts/create-plugin.ts --name myPlugin --schema --deps auth,cache
14
+ bun scripts/create-plugin.ts --help # Show all options
15
+
16
+ # Create server file
17
+ bun scripts/create-server.ts --name server.ts --port 3000 --plugins auth,users
18
+ bun scripts/create-server.ts --help # Show all options
19
+
20
+ # Create migration
21
+ bun scripts/create-migration.ts users add_avatar_column
22
+ bun scripts/create-migration.ts --help # Show usage
23
+
24
+ # Generate types
25
+ bun run gen:registry # Regenerate registry.d.ts
26
+ bun run gen:server # Regenerate context.d.ts
27
+ bun scripts/generate-types.ts <plugin> # Generate schema types for plugin
28
+
29
+ # Watch plugin for changes (runs watcher)
30
+ bun scripts/watcher.ts <plugin>
31
+ ```
32
+
33
+ ### Interactive CLI
34
+
35
+ For guided workflows with menus:
36
+
37
+ ```sh
38
+ bun run cli
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Non-Interactive Commands
44
+
45
+ ### Create Plugin
46
+
47
+ ```sh
48
+ bun scripts/create-plugin.ts [options]
49
+
50
+ Options:
51
+ --name <name> Plugin name (required)
52
+ --schema Include database schema (optional)
53
+ --config Include configuration support (optional)
54
+ --deps <list> Comma-separated dependencies (optional)
55
+ --handlers Include custom handler template (optional)
56
+ --middleware Include custom middleware template (optional)
57
+
58
+ Examples:
59
+ # Simple plugin
60
+ bun scripts/create-plugin.ts --name analytics
61
+
62
+ # Plugin with schema and dependencies
63
+ bun scripts/create-plugin.ts --name orders --schema --deps auth,products
64
+
65
+ # Full-featured plugin
66
+ bun scripts/create-plugin.ts --name notifications --schema --config --deps auth --handlers --middleware
67
+ ```
68
+
69
+ **Generated structure:**
70
+
71
+ ```
72
+ plugins/myPlugin/
73
+ ├── index.ts # Plugin definition
74
+ ├── schema.ts # Database types (if --schema)
75
+ └── migrations/ # Migration folder (if --schema)
76
+ └── 001_initial.ts
77
+ ```
78
+
79
+ ### Create Server
80
+
81
+ ```sh
82
+ bun scripts/create-server.ts [options]
83
+
84
+ Options:
85
+ --name <filename> Output filename (default: server.ts)
86
+ --port <number> Server port (default: 3000)
87
+ --plugins <list> Comma-separated plugins to include
88
+
89
+ Examples:
90
+ # Basic server
91
+ bun scripts/create-server.ts --name app.ts --port 8080
92
+
93
+ # Server with plugins
94
+ bun scripts/create-server.ts --name api.ts --port 3000 --plugins auth,users,orders
95
+ ```
96
+
97
+ ### Create Migration
98
+
99
+ ```sh
100
+ bun scripts/create-migration.ts <plugin> <migration_name>
101
+
102
+ Examples:
103
+ bun scripts/create-migration.ts users add_avatar_column
104
+ bun scripts/create-migration.ts orders add_status_index
105
+ ```
106
+
107
+ **Generated file:** `plugins/<plugin>/migrations/002_add_avatar_column.ts`
108
+
109
+ ### Generate Types
110
+
111
+ ```sh
112
+ # Regenerate all plugin/handler registry types
113
+ bun run gen:registry
114
+
115
+ # Regenerate server context types
116
+ bun run gen:server
117
+
118
+ # Generate schema types for a specific plugin
119
+ bun scripts/generate-types.ts <plugin>
120
+
121
+ Examples:
122
+ bun scripts/generate-types.ts users
123
+ bun scripts/generate-types.ts orders
124
+ ```
125
+
126
+ ### Watch Plugin
127
+
128
+ ```sh
129
+ bun scripts/watch.ts <plugin>
130
+
131
+ Examples:
132
+ bun scripts/watch.ts auth
133
+ bun scripts/watch.ts orders
134
+ ```
135
+
136
+ Watches for changes and auto-regenerates types.
137
+
138
+ ---
139
+
140
+ ## Interactive CLI
141
+
142
+ Launch the interactive menu:
143
+
144
+ ```sh
145
+ bun run cli
146
+ ```
147
+
148
+ ### From Project Root
149
+
150
+ ```
151
+ Plugin CLI
152
+
153
+ Context: Project Root
154
+
155
+ ? Select a command:
156
+ 1. Create New Plugin
157
+ 2. Create New Server
158
+ ─────────────────────────
159
+ 3. Publish Plugin Version
160
+ 4. Install Plugin from Global
161
+ ─────────────────────────
162
+ 5. Generate Registry
163
+ 6. Watch Plugin
164
+ ─────────────────────────
165
+ × Exit
166
+ ```
167
+
168
+ ### From Plugin Directory
169
+
170
+ When running from inside `plugins/<name>/`:
171
+
172
+ ```
173
+ Plugin CLI
174
+
175
+ Context: Plugin 'auth'
176
+
177
+ ? What would you like to do?
178
+ 1. Live Watch
179
+ 2. Generate Schema Types
180
+ 3. Create Migration
181
+ 4. Publish New Version
182
+ ─────────────────────────
183
+ ← Back to Global Menu
184
+ × Exit
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Package.json Scripts
190
+
191
+ Add these to your `package.json`:
192
+
193
+ ```json
194
+ {
195
+ "scripts": {
196
+ "cli": "bun scripts/cli.ts",
197
+ "gen:registry": "bun scripts/generate-registry.ts",
198
+ "gen:server": "bun scripts/generate-server.ts",
199
+ "dev": "bun --watch index.ts",
200
+ "test": "bun test",
201
+ "typecheck": "bun --bun tsc --noEmit"
202
+ }
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Global Plugin Registry
209
+
210
+ Share plugins across projects using the global registry.
211
+
212
+ ### Publish Plugin
213
+
214
+ ```sh
215
+ # Interactive
216
+ bun run cli
217
+ # Select: Publish Plugin Version
218
+
219
+ # Non-interactive (from plugin directory)
220
+ bun scripts/publish.ts --version 1.0.0
221
+ ```
222
+
223
+ ### Install Plugin
224
+
225
+ ```sh
226
+ # Interactive
227
+ bun run cli
228
+ # Select: Install Plugin from Global
229
+
230
+ # Non-interactive
231
+ bun scripts/install.ts --name auth --version latest
232
+ ```
233
+
234
+ ### Check for Updates
235
+
236
+ The CLI automatically checks for updates when opened and shows:
237
+
238
+ ```
239
+ Updates Available:
240
+ auth: 1.0.0 → 1.1.0
241
+ users: 2.0.0 → 2.1.0
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Workflow Examples
247
+
248
+ ### Creating a New Feature
249
+
250
+ ```sh
251
+ # 1. Create the plugin
252
+ bun scripts/create-plugin.ts --name orders --schema --deps auth,products
253
+
254
+ # 2. Edit the generated files
255
+ # - plugins/orders/index.ts (service logic)
256
+ # - plugins/orders/migrations/001_initial.ts (database schema)
257
+
258
+ # 3. Generate types
259
+ bun run gen:registry
260
+
261
+ # 4. Start watching for changes
262
+ bun scripts/watch.ts orders
263
+
264
+ # 5. Add routes and test
265
+ ```
266
+
267
+ ### Adding Database Changes
268
+
269
+ ```sh
270
+ # 1. Create migration
271
+ bun scripts/create-migration.ts orders add_shipping_address
272
+
273
+ # 2. Edit the migration file
274
+ # - plugins/orders/migrations/002_add_shipping_address.ts
275
+
276
+ # 3. Regenerate schema types
277
+ bun scripts/generate-types.ts orders
278
+
279
+ # 4. Update service code to use new columns
280
+ ```
281
+
282
+ ### Setting Up a New Project
283
+
284
+ ```sh
285
+ # 1. Initialize project
286
+ mkdir my-api && cd my-api
287
+ bun init
288
+
289
+ # 2. Install dependencies
290
+ bun add kysely zod
291
+
292
+ # 3. Copy framework files or install from template
293
+
294
+ # 4. Create initial plugins
295
+ bun scripts/create-plugin.ts --name auth --schema
296
+ bun scripts/create-plugin.ts --name users --schema --deps auth
297
+
298
+ # 5. Generate registry
299
+ bun run gen:registry
300
+
301
+ # 6. Create server
302
+ bun scripts/create-server.ts --name index.ts --port 3000 --plugins auth,users
303
+
304
+ # 7. Start development
305
+ bun --watch index.ts
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Environment Variables
311
+
312
+ | Variable | Description |
313
+ |----------|-------------|
314
+ | `INIT_CWD` | Original directory when running via `bun run` |
315
+ | `GLOBAL_REGISTRY_PATH` | Path to global plugin registry |
316
+
317
+ ---
318
+
319
+ ## Troubleshooting
320
+
321
+ ### "Plugin not found" after creation
322
+
323
+ ```sh
324
+ bun run gen:registry
325
+ ```
326
+
327
+ ### Types not updating
328
+
329
+ ```sh
330
+ # Regenerate all types
331
+ bun run gen:registry
332
+ bun run gen:server
333
+
334
+ # Restart TypeScript server in your IDE
335
+ # VS Code: Cmd+Shift+P > "TypeScript: Restart TS Server"
336
+ ```
337
+
338
+ ### Migration not running
339
+
340
+ Check that migration file exports `up` and `down` functions:
341
+
342
+ ```ts
343
+ export async function up(db: Kysely<any>): Promise<void> { ... }
344
+ export async function down(db: Kysely<any>): Promise<void> { ... }
345
+ ```
346
+
347
+ ### Watch mode not detecting changes
348
+
349
+ Ensure you're watching the correct plugin:
350
+
351
+ ```sh
352
+ bun scripts/watch.ts <exact-plugin-name>
353
+ ```
@@ -0,0 +1,338 @@
1
+ # Core Services
2
+
3
+ Core services are foundational utilities automatically available to all plugins and route handlers via `ctx.core`. They provide essential functionality like logging, caching, background jobs, and real-time communication.
4
+
5
+ ## Available Services
6
+
7
+ | Service | Purpose | Default Backend |
8
+ |---------|---------|-----------------|
9
+ | [Logger](logger.md) | Structured logging with levels | Console |
10
+ | [Cache](cache.md) | Key-value store with TTL | In-memory (LRU) |
11
+ | [Events](events.md) | Pub/sub event system | In-memory |
12
+ | [Cron](cron.md) | Scheduled recurring tasks | In-memory |
13
+ | [Jobs](jobs.md) | Background job queue | In-memory |
14
+ | [SSE](sse.md) | Server-Sent Events | In-memory |
15
+ | [RateLimiter](rate-limiter.md) | Request throttling | In-memory |
16
+ | [Errors](errors.md) | HTTP error factories | - |
17
+
18
+ ---
19
+
20
+ ## Accessing Core Services
21
+
22
+ ### In Route Handlers
23
+
24
+ ```ts
25
+ router.route("example").typed({
26
+ handle: async (input, ctx) => {
27
+ // All services available via ctx.core
28
+ ctx.core.logger.info("Processing request", { input });
29
+
30
+ const cached = await ctx.core.cache.get("key");
31
+
32
+ await ctx.core.events.emit("request.processed", { input });
33
+
34
+ return { success: true };
35
+ },
36
+ });
37
+ ```
38
+
39
+ ### In Plugins
40
+
41
+ ```ts
42
+ createPlugin.define({
43
+ name: "myPlugin",
44
+ service: async (ctx) => {
45
+ // Schedule background work
46
+ ctx.core.cron.schedule("0 * * * *", () => {
47
+ ctx.core.logger.info("Hourly task running");
48
+ });
49
+
50
+ // Register job handler
51
+ ctx.core.jobs.register("sendEmail", async (data) => {
52
+ // Process in background
53
+ });
54
+
55
+ // Subscribe to events
56
+ ctx.core.events.on("user.created", async (user) => {
57
+ await ctx.core.jobs.enqueue("sendEmail", {
58
+ to: user.email,
59
+ template: "welcome",
60
+ });
61
+ });
62
+
63
+ return { /* service methods */ };
64
+ },
65
+ });
66
+ ```
67
+
68
+ ### In Middleware
69
+
70
+ ```ts
71
+ createMiddleware(async (req, ctx, next, config) => {
72
+ const start = Date.now();
73
+
74
+ const response = await next();
75
+
76
+ ctx.core.logger.info("Request completed", {
77
+ method: req.method,
78
+ path: new URL(req.url).pathname,
79
+ duration: Date.now() - start,
80
+ status: response.status,
81
+ });
82
+
83
+ return response;
84
+ });
85
+ ```
86
+
87
+ ---
88
+
89
+ ## CoreServices Interface
90
+
91
+ ```ts
92
+ interface CoreServices {
93
+ db: Kysely<any>; // Database connection
94
+ config: Record<string, any>; // Global config
95
+
96
+ // Utility services
97
+ logger: Logger;
98
+ cache: Cache;
99
+ events: Events;
100
+ cron: Cron;
101
+ jobs: Jobs;
102
+ sse: SSE;
103
+ rateLimiter: RateLimiter;
104
+ errors: Errors; // HTTP error factories
105
+ }
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Configuration
111
+
112
+ Configure services when creating the server:
113
+
114
+ ```ts
115
+ import { AppServer } from "./server";
116
+
117
+ const server = new AppServer({
118
+ db: database,
119
+ port: 3000,
120
+
121
+ // Service configurations (all optional)
122
+ logger: {
123
+ level: "debug", // "debug" | "info" | "warn" | "error"
124
+ format: "pretty", // "pretty" | "json"
125
+ },
126
+
127
+ cache: {
128
+ defaultTtlMs: 300000, // 5 minutes
129
+ maxSize: 10000, // LRU max items
130
+ },
131
+
132
+ events: {
133
+ maxHistorySize: 1000, // Event history limit
134
+ },
135
+
136
+ cron: {
137
+ timezone: "UTC", // For future use
138
+ },
139
+
140
+ jobs: {
141
+ concurrency: 5, // Max parallel jobs
142
+ pollInterval: 1000, // Check interval (ms)
143
+ maxAttempts: 3, // Default retries
144
+ },
145
+
146
+ sse: {
147
+ heartbeatInterval: 30000, // Keep-alive interval
148
+ retryInterval: 3000, // Client reconnect hint
149
+ },
150
+
151
+ rateLimiter: {
152
+ // Uses in-memory by default
153
+ },
154
+ });
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Service Lifecycle
160
+
161
+ ### Startup
162
+
163
+ When `server.start()` is called:
164
+
165
+ 1. All services are already initialized (in constructor)
166
+ 2. Cron scheduler starts (`cron.start()`)
167
+ 3. Job processor starts (`jobs.start()`)
168
+ 4. Server begins accepting requests
169
+
170
+ ### Shutdown
171
+
172
+ When `server.shutdown()` is called:
173
+
174
+ 1. SSE connections are closed
175
+ 2. Job processor stops (waits for active jobs)
176
+ 3. Cron scheduler stops
177
+
178
+ ```ts
179
+ // Graceful shutdown
180
+ process.on("SIGTERM", async () => {
181
+ await server.shutdown();
182
+ process.exit(0);
183
+ });
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Common Patterns
189
+
190
+ ### Caching Database Queries
191
+
192
+ ```ts
193
+ async function getUser(id: number, ctx: ServerContext) {
194
+ return ctx.core.cache.getOrSet(
195
+ `user:${id}`,
196
+ () => ctx.db.selectFrom("users").where("id", "=", id).executeTakeFirst(),
197
+ 60000 // 1 minute TTL
198
+ );
199
+ }
200
+ ```
201
+
202
+ ### Event-Driven Architecture
203
+
204
+ ```ts
205
+ // In plugin: emit events
206
+ service: async (ctx) => ({
207
+ async createOrder(data) {
208
+ const order = await ctx.db.insertInto("orders").values(data).execute();
209
+ await ctx.core.events.emit("order.created", order);
210
+ return order;
211
+ },
212
+ });
213
+
214
+ // In another plugin: react to events
215
+ service: async (ctx) => {
216
+ ctx.core.events.on("order.created", async (order) => {
217
+ await ctx.core.jobs.enqueue("sendOrderConfirmation", order);
218
+ await ctx.core.jobs.enqueue("updateInventory", order.items);
219
+ });
220
+ };
221
+ ```
222
+
223
+ ### Rate Limiting Routes
224
+
225
+ ```ts
226
+ router.route("api").typed({
227
+ handle: async (input, ctx) => {
228
+ const result = await ctx.core.rateLimiter.check(
229
+ `api:${ctx.ip}`,
230
+ 100, // 100 requests
231
+ 60000 // per minute
232
+ );
233
+
234
+ if (!result.allowed) {
235
+ throw new Error(`Rate limited. Retry in ${result.retryAfter}s`);
236
+ }
237
+
238
+ // Process request...
239
+ },
240
+ });
241
+ ```
242
+
243
+ ### Real-Time Updates with SSE
244
+
245
+ ```ts
246
+ // Route to establish SSE connection
247
+ router.route("subscribe").raw({
248
+ handle: async (req, ctx) => {
249
+ const { client, response } = ctx.core.sse.addClient();
250
+
251
+ // Subscribe to user's channel
252
+ ctx.core.sse.subscribe(client.id, `user:${ctx.user.id}`);
253
+
254
+ return response;
255
+ },
256
+ });
257
+
258
+ // Broadcast updates from anywhere
259
+ ctx.core.sse.broadcast(`user:${userId}`, "notification", {
260
+ message: "You have a new message",
261
+ });
262
+ ```
263
+
264
+ ### Background Job Processing
265
+
266
+ ```ts
267
+ // Register handler in plugin
268
+ ctx.core.jobs.register("processImage", async (data) => {
269
+ const { imageId, operations } = data;
270
+ // Heavy processing...
271
+ return { processed: true };
272
+ });
273
+
274
+ // Enqueue from route handler
275
+ router.route("upload").typed({
276
+ handle: async (input, ctx) => {
277
+ const image = await saveImage(input.file);
278
+
279
+ // Process in background
280
+ await ctx.core.jobs.enqueue("processImage", {
281
+ imageId: image.id,
282
+ operations: ["resize", "optimize"],
283
+ });
284
+
285
+ return { imageId: image.id, status: "processing" };
286
+ },
287
+ });
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Testing with Core Services
293
+
294
+ The test harness automatically creates all core services:
295
+
296
+ ```ts
297
+ import { createTestHarness } from "./harness";
298
+
299
+ const { core } = await createTestHarness(myPlugin);
300
+
301
+ // All services available
302
+ core.logger.info("Test starting");
303
+ await core.cache.set("test", "value");
304
+ await core.events.emit("test.event", { data: 1 });
305
+
306
+ // Jobs and cron are created but not started
307
+ // Call manually if needed:
308
+ core.jobs.start();
309
+ core.cron.start();
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Custom Adapters
315
+
316
+ Each service supports custom adapters for different backends:
317
+
318
+ ```ts
319
+ import { createCache, type CacheAdapter } from "./core/cache";
320
+
321
+ // Implement custom adapter (e.g., Redis)
322
+ class RedisCacheAdapter implements CacheAdapter {
323
+ async get<T>(key: string): Promise<T | null> { /* ... */ }
324
+ async set<T>(key: string, value: T, ttlMs?: number): Promise<void> { /* ... */ }
325
+ // ... other methods
326
+ }
327
+
328
+ // Use custom adapter
329
+ const cache = createCache({
330
+ adapter: new RedisCacheAdapter(redisClient),
331
+ });
332
+ ```
333
+
334
+ See individual service documentation for adapter interfaces:
335
+ - [Cache Adapters](cache.md#custom-adapters)
336
+ - [Events Adapters](events.md#custom-adapters)
337
+ - [Jobs Adapters](jobs.md#custom-adapters)
338
+ - [Rate Limiter Adapters](rate-limiter.md#custom-adapters)