@donkeylabs/server 0.1.3 → 0.1.4

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.
Files changed (55) hide show
  1. package/examples/starter/node_modules/@donkeylabs/server/README.md +15 -0
  2. package/examples/starter/node_modules/@donkeylabs/server/cli/commands/generate.ts +461 -0
  3. package/examples/starter/node_modules/@donkeylabs/server/cli/commands/init.ts +476 -0
  4. package/examples/starter/node_modules/@donkeylabs/server/cli/commands/interactive.ts +223 -0
  5. package/examples/starter/node_modules/@donkeylabs/server/cli/commands/plugin.ts +192 -0
  6. package/examples/starter/node_modules/@donkeylabs/server/cli/donkeylabs +106 -0
  7. package/examples/starter/node_modules/@donkeylabs/server/cli/index.ts +100 -0
  8. package/examples/starter/node_modules/@donkeylabs/server/context.d.ts +17 -0
  9. package/examples/starter/node_modules/@donkeylabs/server/docs/api-client.md +520 -0
  10. package/examples/starter/node_modules/@donkeylabs/server/docs/cache.md +437 -0
  11. package/examples/starter/node_modules/@donkeylabs/server/docs/cli.md +353 -0
  12. package/examples/starter/node_modules/@donkeylabs/server/docs/core-services.md +338 -0
  13. package/examples/starter/node_modules/@donkeylabs/server/docs/cron.md +465 -0
  14. package/examples/starter/node_modules/@donkeylabs/server/docs/errors.md +303 -0
  15. package/examples/starter/node_modules/@donkeylabs/server/docs/events.md +460 -0
  16. package/examples/starter/node_modules/@donkeylabs/server/docs/handlers.md +549 -0
  17. package/examples/starter/node_modules/@donkeylabs/server/docs/jobs.md +556 -0
  18. package/examples/starter/node_modules/@donkeylabs/server/docs/logger.md +316 -0
  19. package/examples/starter/node_modules/@donkeylabs/server/docs/middleware.md +682 -0
  20. package/examples/starter/node_modules/@donkeylabs/server/docs/plugins.md +524 -0
  21. package/examples/starter/node_modules/@donkeylabs/server/docs/project-structure.md +493 -0
  22. package/examples/starter/node_modules/@donkeylabs/server/docs/rate-limiter.md +525 -0
  23. package/examples/starter/node_modules/@donkeylabs/server/docs/router.md +566 -0
  24. package/examples/starter/node_modules/@donkeylabs/server/docs/sse.md +542 -0
  25. package/examples/starter/node_modules/@donkeylabs/server/docs/svelte-frontend.md +324 -0
  26. package/examples/starter/node_modules/@donkeylabs/server/mcp/donkeylabs-mcp +3238 -0
  27. package/examples/starter/node_modules/@donkeylabs/server/mcp/server.ts +3238 -0
  28. package/examples/starter/node_modules/@donkeylabs/server/package.json +77 -0
  29. package/examples/starter/node_modules/@donkeylabs/server/registry.d.ts +11 -0
  30. package/examples/starter/node_modules/@donkeylabs/server/src/client/base.ts +481 -0
  31. package/examples/starter/node_modules/@donkeylabs/server/src/client/index.ts +150 -0
  32. package/examples/starter/node_modules/@donkeylabs/server/src/core/cache.ts +183 -0
  33. package/examples/starter/node_modules/@donkeylabs/server/src/core/cron.ts +255 -0
  34. package/examples/starter/node_modules/@donkeylabs/server/src/core/errors.ts +320 -0
  35. package/examples/starter/node_modules/@donkeylabs/server/src/core/events.ts +163 -0
  36. package/examples/starter/node_modules/@donkeylabs/server/src/core/index.ts +94 -0
  37. package/examples/starter/node_modules/@donkeylabs/server/src/core/jobs.ts +334 -0
  38. package/examples/starter/node_modules/@donkeylabs/server/src/core/logger.ts +131 -0
  39. package/examples/starter/node_modules/@donkeylabs/server/src/core/rate-limiter.ts +193 -0
  40. package/examples/starter/node_modules/@donkeylabs/server/src/core/sse.ts +210 -0
  41. package/examples/starter/node_modules/@donkeylabs/server/src/core.ts +428 -0
  42. package/examples/starter/node_modules/@donkeylabs/server/src/handlers.ts +87 -0
  43. package/examples/starter/node_modules/@donkeylabs/server/src/harness.ts +70 -0
  44. package/examples/starter/node_modules/@donkeylabs/server/src/index.ts +38 -0
  45. package/examples/starter/node_modules/@donkeylabs/server/src/middleware.ts +34 -0
  46. package/examples/starter/node_modules/@donkeylabs/server/src/registry.ts +13 -0
  47. package/examples/starter/node_modules/@donkeylabs/server/src/router.ts +155 -0
  48. package/examples/starter/node_modules/@donkeylabs/server/src/server.ts +234 -0
  49. package/examples/starter/node_modules/@donkeylabs/server/templates/init/donkeylabs.config.ts.template +14 -0
  50. package/examples/starter/node_modules/@donkeylabs/server/templates/init/index.ts.template +41 -0
  51. package/examples/starter/node_modules/@donkeylabs/server/templates/plugin/index.ts.template +25 -0
  52. package/examples/starter/src/routes/health/ping/models/model.ts +11 -7
  53. package/package.json +3 -3
  54. package/examples/starter/node_modules/.svelte2tsx-language-server-files/svelte-native-jsx.d.ts +0 -32
  55. package/examples/starter/node_modules/.svelte2tsx-language-server-files/svelte-shims-v4.d.ts +0 -290
@@ -0,0 +1,493 @@
1
+ # Project Structure
2
+
3
+ This guide explains the canonical structure, naming conventions, and patterns for this framework. Following these conventions ensures consistency and maintainability.
4
+
5
+ ---
6
+
7
+ ## Directory Layout
8
+
9
+ ```
10
+ project-root/
11
+ ├── core.ts # Plugin system, PluginManager, type helpers
12
+ ├── router.ts # Route builder, handler registry
13
+ ├── handlers.ts # TypedHandler, RawHandler, createHandler
14
+ ├── middleware.ts # Middleware system, createMiddleware
15
+ ├── server.ts # AppServer, HTTP handling
16
+ ├── harness.ts # Test harness factory
17
+ ├── index.ts # Main entry point (or server.ts)
18
+
19
+ ├── context.d.ts # [GENERATED] Global context types
20
+ ├── registry.d.ts # [GENERATED] Plugin/handler registry
21
+
22
+ ├── core/ # Core services
23
+ │ ├── index.ts # Re-exports all services
24
+ │ ├── logger.ts # Logger service
25
+ │ ├── cache.ts # Cache service
26
+ │ ├── events.ts # Events service
27
+ │ ├── cron.ts # Cron service
28
+ │ ├── jobs.ts # Jobs service
29
+ │ ├── sse.ts # SSE service
30
+ │ └── rate-limiter.ts # Rate limiter
31
+
32
+ ├── plugins/ # Plugin modules
33
+ │ ├── auth/
34
+ │ │ ├── index.ts # Plugin definition (REQUIRED)
35
+ │ │ ├── schema.ts # Generated DB types
36
+ │ │ └── migrations/ # SQL migrations
37
+ │ │ ├── 001_create_users.ts
38
+ │ │ └── 002_add_roles.ts
39
+ │ ├── orders/
40
+ │ │ ├── index.ts
41
+ │ │ ├── schema.ts
42
+ │ │ └── migrations/
43
+ │ └── ...
44
+
45
+ ├── scripts/ # CLI and generation scripts
46
+ │ ├── cli.ts # Interactive CLI
47
+ │ ├── create-plugin.ts # Plugin scaffolding
48
+ │ ├── create-server.ts # Server scaffolding
49
+ │ ├── generate-registry.ts
50
+ │ ├── generate-types.ts
51
+ │ └── watch.ts
52
+
53
+ ├── test/ # Test files
54
+ │ ├── core/ # Core service tests
55
+ │ ├── plugins/ # Plugin tests
56
+ │ └── integration.test.ts
57
+
58
+ ├── docs/ # Documentation
59
+
60
+ ├── package.json
61
+ ├── tsconfig.json
62
+ └── CLAUDE.md # Framework documentation
63
+ ```
64
+
65
+ ---
66
+
67
+ ## File Naming Conventions
68
+
69
+ | Pattern | Example | Purpose |
70
+ |---------|---------|---------|
71
+ | `index.ts` | `plugins/auth/index.ts` | Plugin entry point |
72
+ | `schema.ts` | `plugins/auth/schema.ts` | Generated database types |
73
+ | `NNN_name.ts` | `001_create_users.ts` | Migrations (numbered) |
74
+ | `*.test.ts` | `auth.test.ts` | Test files |
75
+ | `*.d.ts` | `registry.d.ts` | Type declarations |
76
+
77
+ ---
78
+
79
+ ## Plugin Structure
80
+
81
+ Every plugin MUST follow this structure:
82
+
83
+ ```
84
+ plugins/<name>/
85
+ ├── index.ts # Plugin definition (REQUIRED)
86
+ ├── schema.ts # Database types (if using DB)
87
+ └── migrations/ # Migrations (if using DB)
88
+ ├── 001_initial.ts
89
+ ├── 002_add_column.ts
90
+ └── ...
91
+ ```
92
+
93
+ ### Plugin index.ts Template
94
+
95
+ ```ts
96
+ // plugins/<name>/index.ts
97
+ import { createPlugin } from "../../core";
98
+ import type { DB } from "./schema"; // If using database
99
+
100
+ // Configuration type (if plugin is configurable)
101
+ interface MyPluginConfig {
102
+ option1: string;
103
+ option2?: number;
104
+ }
105
+
106
+ export const myPlugin = createPlugin
107
+ .withSchema<DB>() // Add if using database
108
+ .withConfig<MyPluginConfig>() // Add if configurable
109
+ .define({
110
+ name: "myPlugin",
111
+ dependencies: [], // Other plugins this depends on
112
+ handlers: {}, // Custom handlers
113
+ middleware: {}, // Custom middleware
114
+
115
+ // Main service factory
116
+ service: async (ctx) => {
117
+ // ctx.db - Database with your schema
118
+ // ctx.deps - Services from dependencies
119
+ // ctx.config - Your configuration
120
+
121
+ return {
122
+ // Service methods exposed via ctx.plugins.myPlugin
123
+ myMethod: async () => { ... },
124
+ };
125
+ },
126
+ });
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Routes Structure
132
+
133
+ Routes should be organized by domain:
134
+
135
+ ```
136
+ routes/
137
+ ├── index.ts # Export all routers
138
+ ├── users.ts # createRouter("users")
139
+ ├── orders.ts # createRouter("orders")
140
+ └── admin.ts # createRouter("admin")
141
+ ```
142
+
143
+ ### Route File Template
144
+
145
+ ```ts
146
+ // routes/users.ts
147
+ import { createRouter } from "../router";
148
+ import { z } from "zod";
149
+
150
+ export const usersRouter = createRouter("users")
151
+ .route("list").typed({
152
+ input: z.object({ page: z.number().default(1) }),
153
+ handle: async (input, ctx) => {
154
+ return ctx.plugins.users.list(input.page);
155
+ },
156
+ })
157
+
158
+ .route("get").typed({
159
+ input: z.object({ id: z.number() }),
160
+ handle: async (input, ctx) => {
161
+ return ctx.plugins.users.getById(input.id);
162
+ },
163
+ })
164
+
165
+ .middleware.auth({ required: true })
166
+ .route("create").typed({
167
+ input: z.object({ email: z.string().email(), name: z.string() }),
168
+ handle: async (input, ctx) => {
169
+ return ctx.plugins.users.create(input);
170
+ },
171
+ });
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Do's and Don'ts
177
+
178
+ ### DO: Use the Plugin System
179
+
180
+ ```ts
181
+ // GOOD: Business logic in plugin service
182
+ // plugins/orders/index.ts
183
+ service: async (ctx) => ({
184
+ async create(data: OrderData) {
185
+ const order = await ctx.db.insertInto("orders").values(data).execute();
186
+ await ctx.core.events.emit("order.created", order);
187
+ return order;
188
+ },
189
+ })
190
+
191
+ // Route handler just calls service
192
+ router.route("create").typed({
193
+ handle: async (input, ctx) => ctx.plugins.orders.create(input),
194
+ });
195
+ ```
196
+
197
+ ```ts
198
+ // BAD: Business logic in route handler
199
+ router.route("create").typed({
200
+ handle: async (input, ctx) => {
201
+ // 50 lines of business logic here...
202
+ const order = await ctx.db.insertInto("orders")...
203
+ // validation...
204
+ // event emission...
205
+ // etc...
206
+ },
207
+ });
208
+ ```
209
+
210
+ ### DO: Use Core Services
211
+
212
+ ```ts
213
+ // GOOD: Use built-in services
214
+ ctx.core.logger.info("Order created", { orderId: order.id });
215
+ ctx.core.cache.set(`order:${id}`, order, 60000);
216
+ ctx.core.events.emit("order.created", order);
217
+ ctx.core.jobs.enqueue("sendOrderEmail", { orderId: order.id });
218
+ ```
219
+
220
+ ```ts
221
+ // BAD: Roll your own
222
+ console.log("Order created:", order.id); // No structured logging
223
+ const cache = new Map(); // No TTL, no persistence
224
+ ```
225
+
226
+ ### DO: Use Type-Safe Patterns
227
+
228
+ ```ts
229
+ // GOOD: Zod schemas for validation
230
+ router.route("create").typed({
231
+ input: z.object({
232
+ email: z.string().email(),
233
+ age: z.number().int().positive(),
234
+ }),
235
+ handle: async (input, ctx) => {
236
+ // input is fully typed
237
+ },
238
+ });
239
+ ```
240
+
241
+ ```ts
242
+ // BAD: Manual validation
243
+ router.route("create").typed({
244
+ handle: async (input: any, ctx) => {
245
+ if (!input.email || !input.email.includes("@")) {
246
+ throw new Error("Invalid email");
247
+ }
248
+ // More manual checks...
249
+ },
250
+ });
251
+ ```
252
+
253
+ ### DO: Use Middleware for Cross-Cutting Concerns
254
+
255
+ ```ts
256
+ // GOOD: Middleware
257
+ router.middleware
258
+ .auth({ required: true })
259
+ .rateLimit({ limit: 100, window: "1m" })
260
+ .route("protected").typed({ ... });
261
+ ```
262
+
263
+ ```ts
264
+ // BAD: Duplicated in every handler
265
+ router.route("protected1").typed({
266
+ handle: async (input, ctx) => {
267
+ if (!ctx.user) throw new Error("Unauthorized");
268
+ // rate limit check...
269
+ },
270
+ });
271
+ router.route("protected2").typed({
272
+ handle: async (input, ctx) => {
273
+ if (!ctx.user) throw new Error("Unauthorized"); // Duplicated!
274
+ // rate limit check...
275
+ },
276
+ });
277
+ ```
278
+
279
+ ### DO: Follow Naming Conventions
280
+
281
+ ```ts
282
+ // GOOD: Consistent naming
283
+ export const authPlugin = createPlugin.define({ name: "auth", ... });
284
+ export const usersRouter = createRouter("users");
285
+
286
+ // Plugin service methods: verb + noun
287
+ service: (ctx) => ({
288
+ createUser: async (data) => { ... },
289
+ getUserById: async (id) => { ... },
290
+ updateUser: async (id, data) => { ... },
291
+ deleteUser: async (id) => { ... },
292
+ })
293
+ ```
294
+
295
+ ```ts
296
+ // BAD: Inconsistent naming
297
+ export const AUTH = createPlugin.define({ name: "Authentication", ... });
298
+ export const router = createRouter("Users");
299
+
300
+ service: (ctx) => ({
301
+ create: async (data) => { ... }, // Unclear what's being created
302
+ get: async (id) => { ... }, // Unclear what's being retrieved
303
+ })
304
+ ```
305
+
306
+ ### DON'T: Create Unnecessary Files
307
+
308
+ ```ts
309
+ // BAD: Over-engineered structure
310
+ plugins/auth/
311
+ ├── index.ts
312
+ ├── schema.ts
313
+ ├── types/
314
+ │ ├── index.ts
315
+ │ ├── user.ts
316
+ │ ├── session.ts
317
+ │ └── token.ts
318
+ ├── services/
319
+ │ ├── index.ts
320
+ │ ├── user.service.ts
321
+ │ └── auth.service.ts
322
+ ├── handlers/
323
+ │ └── custom.handler.ts
324
+ ├── middleware/
325
+ │ └── auth.middleware.ts
326
+ ├── utils/
327
+ │ ├── hash.ts
328
+ │ └── token.ts
329
+ └── migrations/
330
+ ```
331
+
332
+ ```ts
333
+ // GOOD: Keep it simple
334
+ plugins/auth/
335
+ ├── index.ts // Everything in one file, or max 2-3
336
+ ├── schema.ts
337
+ └── migrations/
338
+ ```
339
+
340
+ ### DON'T: Edit Generated Files
341
+
342
+ ```ts
343
+ // BAD: Editing generated files
344
+ // registry.d.ts - THIS IS GENERATED, DO NOT EDIT
345
+
346
+ // GOOD: Regenerate instead
347
+ bun run gen:registry
348
+ ```
349
+
350
+ ### DON'T: Import Internals Directly
351
+
352
+ ```ts
353
+ // BAD: Importing internal implementation
354
+ import { someInternalFunction } from "./plugins/auth/internal";
355
+
356
+ // GOOD: Use plugin service
357
+ ctx.plugins.auth.publicMethod();
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Migration Patterns
363
+
364
+ ### Creating Tables
365
+
366
+ ```ts
367
+ // plugins/<name>/migrations/001_create_orders.ts
368
+ import type { Kysely } from "kysely";
369
+
370
+ export async function up(db: Kysely<any>): Promise<void> {
371
+ await db.schema
372
+ .createTable("orders")
373
+ .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())
374
+ .addColumn("userId", "integer", (col) => col.notNull())
375
+ .addColumn("total", "real", (col) => col.notNull())
376
+ .addColumn("status", "text", (col) => col.notNull().defaultTo("pending"))
377
+ .addColumn("createdAt", "text", (col) => col.notNull())
378
+ .execute();
379
+
380
+ await db.schema
381
+ .createIndex("orders_user_idx")
382
+ .on("orders")
383
+ .column("userId")
384
+ .execute();
385
+ }
386
+
387
+ export async function down(db: Kysely<any>): Promise<void> {
388
+ await db.schema.dropTable("orders").execute();
389
+ }
390
+ ```
391
+
392
+ ### Adding Columns
393
+
394
+ ```ts
395
+ // plugins/<name>/migrations/002_add_shipping.ts
396
+ export async function up(db: Kysely<any>): Promise<void> {
397
+ await db.schema
398
+ .alterTable("orders")
399
+ .addColumn("shippingAddress", "text")
400
+ .execute();
401
+ }
402
+
403
+ export async function down(db: Kysely<any>): Promise<void> {
404
+ await db.schema
405
+ .alterTable("orders")
406
+ .dropColumn("shippingAddress")
407
+ .execute();
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Testing Patterns
414
+
415
+ ### Plugin Unit Tests
416
+
417
+ ```ts
418
+ // test/plugins/auth.test.ts
419
+ import { createTestHarness } from "../../harness";
420
+ import { authPlugin } from "../../plugins/auth";
421
+
422
+ describe("Auth Plugin", () => {
423
+ let harness: Awaited<ReturnType<typeof createTestHarness>>;
424
+
425
+ beforeEach(async () => {
426
+ harness = await createTestHarness(authPlugin);
427
+ });
428
+
429
+ afterEach(async () => {
430
+ await harness.cleanup();
431
+ });
432
+
433
+ test("creates user", async () => {
434
+ const service = harness.manager.getServices().auth;
435
+ const user = await service.createUser({ email: "test@test.com" });
436
+ expect(user.email).toBe("test@test.com");
437
+ });
438
+ });
439
+ ```
440
+
441
+ ### Integration Tests
442
+
443
+ ```ts
444
+ // test/integration.test.ts
445
+ import { createTestHarness } from "../harness";
446
+ import { authPlugin } from "../plugins/auth";
447
+ import { ordersPlugin } from "../plugins/orders";
448
+
449
+ describe("Integration", () => {
450
+ test("orders plugin uses auth", async () => {
451
+ const { manager } = await createTestHarness(ordersPlugin, [authPlugin]);
452
+
453
+ const auth = manager.getServices().auth;
454
+ const orders = manager.getServices().orders;
455
+
456
+ const user = await auth.createUser({ email: "test@test.com" });
457
+ const order = await orders.create({ userId: user.id, total: 100 });
458
+
459
+ expect(order.userId).toBe(user.id);
460
+ });
461
+ });
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Checklist for New Features
467
+
468
+ When adding a new feature:
469
+
470
+ - [ ] Create plugin in `plugins/<name>/index.ts`
471
+ - [ ] Add database schema if needed (`migrations/`)
472
+ - [ ] Export plugin from index
473
+ - [ ] Run `bun run gen:registry`
474
+ - [ ] Add routes in `routes/<name>.ts`
475
+ - [ ] Register router in server
476
+ - [ ] Add tests in `test/`
477
+ - [ ] Run `bun test` to verify
478
+ - [ ] Run `bun --bun tsc --noEmit` to type check
479
+
480
+ ---
481
+
482
+ ## Common Mistakes to Avoid
483
+
484
+ 1. **Putting business logic in route handlers** - Use plugin services
485
+ 2. **Not using Zod validation** - Always validate input
486
+ 3. **Editing generated files** - Regenerate instead
487
+ 4. **Creating too many files** - Keep plugins simple
488
+ 5. **Not running gen:registry** - Do this after any plugin change
489
+ 6. **Manual auth/rate limit checks** - Use middleware
490
+ 7. **Console.log for logging** - Use `ctx.core.logger`
491
+ 8. **Rolling your own cache** - Use `ctx.core.cache`
492
+ 9. **Duplicating code across routes** - Extract to plugin service
493
+ 10. **Forgetting to emit events** - Use `ctx.core.events` for side effects