@donkeylabs/server 0.1.0 → 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/cli/commands/init.ts +201 -12
- package/cli/donkeylabs +6 -0
- package/context.d.ts +17 -0
- package/docs/api-client.md +520 -0
- package/docs/cache.md +437 -0
- package/docs/cli.md +353 -0
- package/docs/core-services.md +338 -0
- package/docs/cron.md +465 -0
- package/docs/errors.md +303 -0
- package/docs/events.md +460 -0
- package/docs/handlers.md +549 -0
- package/docs/jobs.md +556 -0
- package/docs/logger.md +316 -0
- package/docs/middleware.md +682 -0
- package/docs/plugins.md +524 -0
- package/docs/project-structure.md +493 -0
- package/docs/rate-limiter.md +525 -0
- package/docs/router.md +566 -0
- package/docs/sse.md +542 -0
- package/docs/svelte-frontend.md +324 -0
- package/package.json +12 -9
- package/registry.d.ts +11 -0
- package/src/index.ts +1 -1
- package/src/server.ts +1 -0
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)
|