@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,520 @@
1
+ # API Client
2
+
3
+ Code-generated, fully-typed API client for consuming routes with TypeScript. Supports typed requests, SSE events, and automatic authentication handling.
4
+
5
+ ## Quick Start
6
+
7
+ ```ts
8
+ // Generate client from your server routes
9
+ // bun run gen:client server.ts
10
+
11
+ // Import and use
12
+ import { createApiClient } from "./client";
13
+
14
+ const api = createApiClient({ baseUrl: "http://localhost:3000" });
15
+
16
+ // Typed route calls
17
+ const user = await api.users.get({ id: 1 });
18
+ console.log(user.name); // Fully typed!
19
+
20
+ // SSE events with typed handlers
21
+ api.connect();
22
+ api.on("notifications.new", (data) => {
23
+ console.log(data.message); // Typed!
24
+ });
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Generation
30
+
31
+ ### CLI Command
32
+
33
+ ```sh
34
+ # Generate from specific server files
35
+ bun scripts/generate-client.ts server.ts
36
+
37
+ # Generate from multiple files
38
+ bun scripts/generate-client.ts server.ts api.ts
39
+
40
+ # Custom output location
41
+ bun scripts/generate-client.ts --output ./src/api server.ts
42
+
43
+ # Using npm script
44
+ bun run gen:client server.ts
45
+ ```
46
+
47
+ ### Options
48
+
49
+ | Option | Default | Description |
50
+ |--------|---------|-------------|
51
+ | `--output <path>` | `./client` | Output directory for generated files |
52
+ | `--name <name>` | `index.ts` | Generated client filename |
53
+ | `--help` | - | Show usage information |
54
+
55
+ ### Generated Files
56
+
57
+ ```
58
+ client/
59
+ ├── base.ts # Runtime (ApiClientBase, errors, types)
60
+ └── index.ts # Generated client with typed routes/events
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Client Configuration
66
+
67
+ ```ts
68
+ interface ApiClientConfig {
69
+ /** Base URL of the API server */
70
+ baseUrl: string;
71
+
72
+ /** Default headers for all requests */
73
+ headers?: Record<string, string>;
74
+
75
+ /** Credentials mode (default: "include" for cookies) */
76
+ credentials?: "include" | "same-origin" | "omit";
77
+
78
+ /** Custom fetch implementation (for Node.js or testing) */
79
+ fetch?: typeof fetch;
80
+ }
81
+
82
+ const api = createApiClient({
83
+ baseUrl: "http://localhost:3000",
84
+ headers: {
85
+ "X-Client-Version": "1.0.0",
86
+ },
87
+ credentials: "include",
88
+ });
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Route Calls
94
+
95
+ ### Typed Routes
96
+
97
+ Generated from `.route().typed()` definitions:
98
+
99
+ ```ts
100
+ // Server-side definition
101
+ router.route("users.get").typed({
102
+ input: z.object({ id: z.number() }),
103
+ output: z.object({ id: z.number(), name: z.string(), email: z.string() }),
104
+ handle: async (input, ctx) => { ... },
105
+ });
106
+
107
+ // Generated client method
108
+ const user = await api.users.get({ id: 1 });
109
+ // user: { id: number; name: string; email: string }
110
+ ```
111
+
112
+ ### Route Namespaces
113
+
114
+ Routes are grouped by prefix:
115
+
116
+ ```ts
117
+ // Server: createRouter("users")
118
+ api.users.get({ id: 1 });
119
+ api.users.create({ name: "Alice" });
120
+ api.users.update({ id: 1, name: "Bob" });
121
+
122
+ // Server: createRouter("orders")
123
+ api.orders.list({ page: 1 });
124
+ api.orders.create({ items: [...] });
125
+ ```
126
+
127
+ ### Request Options
128
+
129
+ ```ts
130
+ // Abort signal
131
+ const controller = new AbortController();
132
+ const user = await api.users.get({ id: 1 }, {
133
+ signal: controller.signal,
134
+ });
135
+
136
+ // Custom headers per request
137
+ const order = await api.orders.create({ items: [...] }, {
138
+ headers: { "X-Idempotency-Key": "unique-key" },
139
+ });
140
+ ```
141
+
142
+ ### Raw Routes
143
+
144
+ For non-JSON endpoints:
145
+
146
+ ```ts
147
+ // Server-side
148
+ router.route("download").raw({
149
+ handle: async (req, ctx) => new Response(fileBuffer),
150
+ });
151
+
152
+ // Client-side (returns raw Response)
153
+ const response = await api.files.download();
154
+ const blob = await response.blob();
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Error Handling
160
+
161
+ ```ts
162
+ import { ApiError, ValidationError } from "./client";
163
+
164
+ try {
165
+ const user = await api.users.create({ email: "invalid" });
166
+ } catch (error) {
167
+ if (error instanceof ValidationError) {
168
+ // Zod validation failed (400)
169
+ console.log("Validation errors:", error.details);
170
+ // [{ path: ["email"], message: "Invalid email" }]
171
+ } else if (error instanceof ApiError) {
172
+ // HTTP error
173
+ console.log("Status:", error.status);
174
+ console.log("Body:", error.body);
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Error Types
180
+
181
+ | Error | Status | Description |
182
+ |-------|--------|-------------|
183
+ | `ValidationError` | 400 | Zod schema validation failed |
184
+ | `ApiError` | Any | Generic HTTP error with status/body |
185
+
186
+ ---
187
+
188
+ ## SSE Events
189
+
190
+ ### Connection
191
+
192
+ ```ts
193
+ // Connect to SSE endpoint
194
+ api.connect();
195
+ // Or with options
196
+ api.connect({
197
+ endpoint: "/events", // Default: "/sse"
198
+ channels: ["orders"], // Subscribe to specific channels
199
+ autoReconnect: true, // Auto-reconnect on disconnect (default)
200
+ reconnectDelay: 3000, // Reconnect delay in ms (default: 3000)
201
+ onConnect: () => console.log("Connected"),
202
+ onDisconnect: () => console.log("Disconnected"),
203
+ onError: (e) => console.error("SSE error", e),
204
+ });
205
+
206
+ // Check connection status
207
+ console.log(api.connected); // boolean
208
+
209
+ // Disconnect
210
+ api.disconnect();
211
+ ```
212
+
213
+ ### Typed Event Handlers
214
+
215
+ Events are typed from plugin `events` definitions:
216
+
217
+ ```ts
218
+ // Plugin definition
219
+ export const notificationsPlugin = createPlugin.define({
220
+ name: "notifications",
221
+ events: {
222
+ new: z.object({
223
+ id: z.number(),
224
+ message: z.string(),
225
+ type: z.enum(["info", "warning", "error"]),
226
+ }),
227
+ unreadCount: z.object({ count: z.number() }),
228
+ },
229
+ // ...
230
+ });
231
+
232
+ // Client-side (generated types)
233
+ api.on("notifications.new", (data) => {
234
+ // data: { id: number; message: string; type: "info" | "warning" | "error" }
235
+ showToast(data.message, data.type);
236
+ });
237
+
238
+ api.on("notifications.unreadCount", (data) => {
239
+ // data: { count: number }
240
+ updateBadge(data.count);
241
+ });
242
+ ```
243
+
244
+ ### Event Subscription
245
+
246
+ ```ts
247
+ // Subscribe to event (returns unsubscribe function)
248
+ const unsubscribe = api.on("orders.statusChanged", (data) => {
249
+ console.log("Order status:", data.status);
250
+ });
251
+
252
+ // Later: unsubscribe
253
+ unsubscribe();
254
+
255
+ // One-time subscription
256
+ api.once("orders.statusChanged", (data) => {
257
+ console.log("First status change:", data.status);
258
+ });
259
+
260
+ // Remove all handlers for event
261
+ api.off("orders.statusChanged");
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Authentication
267
+
268
+ ### HTTP-Only Cookies (Recommended)
269
+
270
+ The client uses `credentials: "include"` by default, which automatically sends and receives HTTP-only cookies:
271
+
272
+ ```ts
273
+ // Login sets HTTP-only cookie automatically
274
+ await api.auth.login({ username: "alice", password: "secret" });
275
+
276
+ // Subsequent requests include cookie automatically
277
+ const user = await api.users.me(); // Authenticated!
278
+
279
+ // Logout clears cookie
280
+ await api.auth.logout();
281
+ ```
282
+
283
+ ### Custom Auth Headers
284
+
285
+ ```ts
286
+ const api = createApiClient({
287
+ baseUrl: "http://localhost:3000",
288
+ headers: {
289
+ Authorization: `Bearer ${token}`,
290
+ },
291
+ });
292
+
293
+ // Or update per-request
294
+ await api.users.get({ id: 1 }, {
295
+ headers: { Authorization: `Bearer ${newToken}` },
296
+ });
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Plugin Client Configuration
302
+
303
+ Plugins can configure client behavior:
304
+
305
+ ```ts
306
+ // plugins/auth/index.ts
307
+ export const authPlugin = createPlugin.define({
308
+ name: "auth",
309
+ client: {
310
+ credentials: "include", // Ensures cookies are sent
311
+ },
312
+ // ...
313
+ });
314
+ ```
315
+
316
+ The generator merges all plugin client configs to determine defaults.
317
+
318
+ ---
319
+
320
+ ## Browser & Node.js
321
+
322
+ ### Browser
323
+
324
+ Works out of the box with native `fetch` and `EventSource`:
325
+
326
+ ```ts
327
+ import { createApiClient } from "./client";
328
+ const api = createApiClient({ baseUrl: "http://localhost:3000" });
329
+ ```
330
+
331
+ ### Node.js / Bun
332
+
333
+ Native fetch is available in Bun and Node.js 18+:
334
+
335
+ ```ts
336
+ import { createApiClient } from "./client";
337
+
338
+ const api = createApiClient({
339
+ baseUrl: "http://localhost:3000",
340
+ });
341
+ ```
342
+
343
+ For SSE in Node.js, use `eventsource` polyfill:
344
+
345
+ ```ts
346
+ import EventSource from "eventsource";
347
+ globalThis.EventSource = EventSource;
348
+
349
+ // Then use normally
350
+ api.connect();
351
+ ```
352
+
353
+ ---
354
+
355
+ ## Complete Example
356
+
357
+ ```ts
358
+ import { createApiClient, ApiError, ValidationError } from "./client";
359
+
360
+ // Create client
361
+ const api = createApiClient({ baseUrl: "http://localhost:3000" });
362
+
363
+ async function main() {
364
+ try {
365
+ // Login
366
+ await api.auth.login({ username: "alice", password: "secret" });
367
+
368
+ // Connect to SSE
369
+ api.connect({
370
+ onConnect: () => console.log("SSE connected"),
371
+ });
372
+
373
+ // Listen for events
374
+ api.on("notifications.new", (data) => {
375
+ console.log(`New notification: ${data.message}`);
376
+ });
377
+
378
+ // Make typed API calls
379
+ const user = await api.users.get({ id: 1 });
380
+ console.log(`Hello, ${user.name}!`);
381
+
382
+ // Create order
383
+ const order = await api.orders.create({
384
+ items: [
385
+ { productId: 1, quantity: 2 },
386
+ { productId: 3, quantity: 1 },
387
+ ],
388
+ });
389
+ console.log(`Order created: ${order.id}`);
390
+
391
+ } catch (error) {
392
+ if (error instanceof ValidationError) {
393
+ console.error("Validation failed:", error.details);
394
+ } else if (error instanceof ApiError) {
395
+ console.error(`API error ${error.status}:`, error.body);
396
+ } else {
397
+ throw error;
398
+ }
399
+ }
400
+ }
401
+
402
+ // Cleanup on exit
403
+ process.on("SIGINT", () => {
404
+ api.disconnect();
405
+ process.exit();
406
+ });
407
+
408
+ main();
409
+ ```
410
+
411
+ ---
412
+
413
+ ## Type Generation
414
+
415
+ ### How It Works
416
+
417
+ The generator scans your code for:
418
+
419
+ 1. **Routes**: Extracts `createRouter()` calls and `.route().typed()` definitions
420
+ 2. **Events**: Scans `plugins/*/index.ts` for `events: { ... }` definitions
421
+ 3. **Client Config**: Reads `client: { ... }` from plugins
422
+
423
+ Zod schemas are converted to TypeScript types:
424
+
425
+ | Zod Schema | Generated Type |
426
+ |------------|----------------|
427
+ | `z.string()` | `string` |
428
+ | `z.number()` | `number` |
429
+ | `z.boolean()` | `boolean` |
430
+ | `z.object({ a: z.string() })` | `{ a: string }` |
431
+ | `z.array(z.number())` | `number[]` |
432
+ | `z.enum(["a", "b"])` | `"a" \| "b"` |
433
+ | `z.optional()` | `T \| undefined` |
434
+ | `z.nullable()` | `T \| null` |
435
+
436
+ ### Regenerating
437
+
438
+ Run the generator when you:
439
+
440
+ - Add new routes
441
+ - Modify route input/output schemas
442
+ - Add plugin events
443
+ - Change plugin client config
444
+
445
+ ```sh
446
+ bun run gen:client server.ts
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Best Practices
452
+
453
+ ### 1. Regenerate After Schema Changes
454
+
455
+ ```sh
456
+ # After modifying routes or events
457
+ bun run gen:client server.ts
458
+ ```
459
+
460
+ ### 2. Handle Errors Gracefully
461
+
462
+ ```ts
463
+ async function fetchUser(id: number) {
464
+ try {
465
+ return await api.users.get({ id });
466
+ } catch (error) {
467
+ if (error instanceof ApiError && error.status === 404) {
468
+ return null;
469
+ }
470
+ throw error;
471
+ }
472
+ }
473
+ ```
474
+
475
+ ### 3. Use Abort Controllers for Cancellation
476
+
477
+ ```ts
478
+ const controller = new AbortController();
479
+
480
+ // Cancel after 5 seconds
481
+ setTimeout(() => controller.abort(), 5000);
482
+
483
+ try {
484
+ await api.reports.generate({ type: "annual" }, {
485
+ signal: controller.signal,
486
+ });
487
+ } catch (error) {
488
+ if (error.name === "AbortError") {
489
+ console.log("Request cancelled");
490
+ }
491
+ }
492
+ ```
493
+
494
+ ### 4. Reconnect SSE on Auth Changes
495
+
496
+ ```ts
497
+ // After login, reconnect to get authenticated events
498
+ await api.auth.login({ ... });
499
+ api.disconnect();
500
+ api.connect();
501
+
502
+ // After logout
503
+ await api.auth.logout();
504
+ api.disconnect();
505
+ ```
506
+
507
+ ### 5. Clean Up SSE on Unmount
508
+
509
+ ```ts
510
+ // React example
511
+ useEffect(() => {
512
+ api.connect();
513
+ const unsub = api.on("updates", setData);
514
+
515
+ return () => {
516
+ unsub();
517
+ api.disconnect();
518
+ };
519
+ }, []);
520
+ ```