@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,77 @@
1
+ {
2
+ "name": "@donkeylabs/server",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "bin": {
9
+ "donkeylabs": "./cli/donkeylabs",
10
+ "donkeylabs-mcp": "./mcp/donkeylabs-mcp"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.ts",
15
+ "import": "./src/index.ts"
16
+ },
17
+ "./client": {
18
+ "types": "./src/client/base.ts",
19
+ "import": "./src/client/base.ts"
20
+ },
21
+ "./harness": {
22
+ "types": "./src/harness.ts",
23
+ "import": "./src/harness.ts"
24
+ }
25
+ },
26
+ "files": [
27
+ "src",
28
+ "cli",
29
+ "mcp",
30
+ "docs",
31
+ "templates",
32
+ "context.d.ts",
33
+ "registry.d.ts"
34
+ ],
35
+ "scripts": {
36
+ "gen:registry": "bun scripts/generate-registry.ts",
37
+ "gen:server": "bun scripts/generate-server.ts",
38
+ "gen:client": "bun scripts/generate-client.ts",
39
+ "cli": "bun cli/index.ts",
40
+ "test": "bun test",
41
+ "typecheck": "bun --bun tsc --noEmit",
42
+ "dev": "bun --watch src/index.ts",
43
+ "start": "bun src/index.ts",
44
+ "gen:types": "donkeylabs generate"
45
+ },
46
+ "devDependencies": {
47
+ "@types/bun": "latest",
48
+ "@types/prompts": "^2.4.9",
49
+ "kysely": "^0.27.6",
50
+ "kysely-bun-sqlite": "^0.3.2",
51
+ "kysely-codegen": "^0.19.0",
52
+ "zod": "^3.24.0"
53
+ },
54
+ "peerDependencies": {
55
+ "typescript": "^5",
56
+ "kysely": "^0.27.0 || ^0.28.0",
57
+ "zod": "^3.20.0"
58
+ },
59
+ "dependencies": {
60
+ "@modelcontextprotocol/sdk": "^1.25.2",
61
+ "picocolors": "^1.1.1",
62
+ "prompts": "^2.4.2"
63
+ },
64
+ "keywords": [
65
+ "api",
66
+ "rpc",
67
+ "plugin",
68
+ "bun",
69
+ "typescript",
70
+ "type-safe"
71
+ ],
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "https://github.com/donkeylabs/server"
75
+ },
76
+ "license": "MIT"
77
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Plugin and handler registry.
3
+ * This is a stub - run `donkeylabs generate` to create project-specific types.
4
+ */
5
+
6
+ // Extend PluginHandlerRegistry in your project's generated types
7
+ declare module "./src/core" {
8
+ interface PluginHandlerRegistry {}
9
+ }
10
+
11
+ export {};
@@ -0,0 +1,481 @@
1
+ /**
2
+ * API Client Base Runtime
3
+ *
4
+ * This file provides the runtime implementation for the generated API client.
5
+ * It handles HTTP requests, SSE connections, and typed event handling.
6
+ */
7
+
8
+ // ============================================
9
+ // Type Declarations (for environments without DOM types)
10
+ // ============================================
11
+
12
+ type CredentialsMode = "include" | "same-origin" | "omit";
13
+
14
+ // EventSource type declarations for non-DOM environments
15
+ interface SSEMessageEvent {
16
+ data: string;
17
+ lastEventId: string;
18
+ origin: string;
19
+ }
20
+
21
+ type SSEEventHandler = (event: SSEMessageEvent | Event) => void;
22
+
23
+ declare class EventSource {
24
+ readonly readyState: number;
25
+ readonly url: string;
26
+ onopen: ((event: Event) => void) | null;
27
+ onmessage: ((event: SSEMessageEvent) => void) | null;
28
+ onerror: ((event: Event) => void) | null;
29
+ constructor(url: string, eventSourceInitDict?: { withCredentials?: boolean });
30
+ addEventListener(type: string, listener: SSEEventHandler): void;
31
+ removeEventListener(type: string, listener: SSEEventHandler): void;
32
+ close(): void;
33
+ }
34
+
35
+ // ============================================
36
+ // Error Types
37
+ // ============================================
38
+
39
+ /**
40
+ * API Error response from the server.
41
+ * Matches the HttpError format from the server.
42
+ */
43
+ export interface ApiErrorBody {
44
+ error: string;
45
+ message: string;
46
+ details?: Record<string, any>;
47
+ }
48
+
49
+ /**
50
+ * Base API error class.
51
+ * Thrown when the server returns a non-2xx response.
52
+ */
53
+ export class ApiError extends Error {
54
+ /** HTTP status code */
55
+ public readonly status: number;
56
+ /** Error code from server (e.g., "BAD_REQUEST", "NOT_FOUND") */
57
+ public readonly code: string;
58
+ /** Full response body */
59
+ public readonly body: ApiErrorBody | any;
60
+ /** Additional error details */
61
+ public readonly details?: Record<string, any>;
62
+
63
+ constructor(
64
+ status: number,
65
+ body: ApiErrorBody | any,
66
+ message?: string
67
+ ) {
68
+ super(message || body?.message || `API Error: ${status}`);
69
+ this.name = "ApiError";
70
+ this.status = status;
71
+ this.body = body;
72
+ this.code = body?.error || "UNKNOWN_ERROR";
73
+ this.details = body?.details;
74
+ }
75
+
76
+ /**
77
+ * Check if this is a specific error type
78
+ */
79
+ is(code: string): boolean {
80
+ return this.code === code;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Validation error (400 Bad Request with validation details).
86
+ * Contains detailed information about what failed validation.
87
+ */
88
+ export class ValidationError extends ApiError {
89
+ /** Validation issue details */
90
+ public readonly validationDetails: Array<{ path: (string | number)[]; message: string }>;
91
+
92
+ constructor(details: Array<{ path: (string | number)[]; message: string }>) {
93
+ super(400, { error: "BAD_REQUEST", message: "Validation Failed", details }, "Validation Failed");
94
+ this.name = "ValidationError";
95
+ this.validationDetails = details;
96
+ }
97
+
98
+ /**
99
+ * Get errors for a specific field path
100
+ */
101
+ getFieldErrors(fieldPath: string | string[]): string[] {
102
+ const path = Array.isArray(fieldPath) ? fieldPath : [fieldPath];
103
+ return this.validationDetails
104
+ .filter((issue) => {
105
+ if (issue.path.length !== path.length) return false;
106
+ return issue.path.every((p, i) => p === path[i]);
107
+ })
108
+ .map((issue) => issue.message);
109
+ }
110
+
111
+ /**
112
+ * Check if a specific field has errors
113
+ */
114
+ hasFieldError(fieldPath: string | string[]): boolean {
115
+ return this.getFieldErrors(fieldPath).length > 0;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Convenience error type checks
121
+ */
122
+ export const ErrorCodes = {
123
+ BAD_REQUEST: "BAD_REQUEST",
124
+ UNAUTHORIZED: "UNAUTHORIZED",
125
+ FORBIDDEN: "FORBIDDEN",
126
+ NOT_FOUND: "NOT_FOUND",
127
+ METHOD_NOT_ALLOWED: "METHOD_NOT_ALLOWED",
128
+ CONFLICT: "CONFLICT",
129
+ GONE: "GONE",
130
+ UNPROCESSABLE_ENTITY: "UNPROCESSABLE_ENTITY",
131
+ TOO_MANY_REQUESTS: "TOO_MANY_REQUESTS",
132
+ INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
133
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
134
+ BAD_GATEWAY: "BAD_GATEWAY",
135
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
136
+ GATEWAY_TIMEOUT: "GATEWAY_TIMEOUT",
137
+ } as const;
138
+
139
+ // ============================================
140
+ // Request Options
141
+ // ============================================
142
+
143
+ export interface RequestOptions {
144
+ headers?: Record<string, string>;
145
+ signal?: AbortSignal;
146
+ }
147
+
148
+ export interface ApiClientOptions {
149
+ /** Default headers to include in all requests */
150
+ headers?: Record<string, string>;
151
+ /** Credentials mode for fetch (default: 'include' for HTTP-only cookies) */
152
+ credentials?: CredentialsMode;
153
+ /** Called when authentication state changes (login/logout) */
154
+ onAuthChange?: (authenticated: boolean) => void;
155
+ /** Custom fetch implementation (for testing or Node.js polyfills) */
156
+ fetch?: typeof fetch;
157
+ }
158
+
159
+ // ============================================
160
+ // SSE Types
161
+ // ============================================
162
+
163
+ export interface SSEOptions {
164
+ /** Endpoint path for SSE connection (default: '/sse') */
165
+ endpoint?: string;
166
+ /** Channels to subscribe to on connect */
167
+ channels?: string[];
168
+ /** Called when connection is established */
169
+ onConnect?: () => void;
170
+ /** Called when connection is lost */
171
+ onDisconnect?: () => void;
172
+ /** Called on connection error */
173
+ onError?: (error: Event) => void;
174
+ /** Auto-reconnect on disconnect (default: true) */
175
+ autoReconnect?: boolean;
176
+ /** Reconnect delay in ms (default: 3000) */
177
+ reconnectDelay?: number;
178
+ }
179
+
180
+ // ============================================
181
+ // Base Client Implementation
182
+ // ============================================
183
+
184
+ /**
185
+ * Base API client with HTTP request handling and SSE support.
186
+ * Extended by the generated client with typed routes and events.
187
+ */
188
+ export class ApiClientBase<TEvents extends Record<string, any> = Record<string, any>> {
189
+ protected readonly baseUrl: string;
190
+ protected readonly options: ApiClientOptions;
191
+ private eventSource: EventSource | null = null;
192
+ private eventHandlers: Map<string, Set<(data: any) => void>> = new Map();
193
+ private sseOptions: SSEOptions = {};
194
+ private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
195
+
196
+ constructor(baseUrl: string, options: ApiClientOptions = {}) {
197
+ this.baseUrl = baseUrl.replace(/\/$/, "");
198
+ this.options = {
199
+ credentials: "include",
200
+ ...options,
201
+ };
202
+ }
203
+
204
+ // ==========================================
205
+ // HTTP Request Methods
206
+ // ==========================================
207
+
208
+ /**
209
+ * Make a typed POST request to a route
210
+ */
211
+ protected async request<TInput, TOutput>(
212
+ route: string,
213
+ input: TInput,
214
+ options: RequestOptions = {}
215
+ ): Promise<TOutput> {
216
+ const fetchFn = this.options.fetch || fetch;
217
+
218
+ const response = await fetchFn(`${this.baseUrl}/${route}`, {
219
+ method: "POST",
220
+ headers: {
221
+ "Content-Type": "application/json",
222
+ ...this.options.headers,
223
+ ...options.headers,
224
+ },
225
+ credentials: this.options.credentials,
226
+ body: JSON.stringify(input),
227
+ signal: options.signal,
228
+ });
229
+
230
+ if (!response.ok) {
231
+ const body = (await response.json().catch(() => ({}))) as Record<string, any>;
232
+
233
+ // Check for validation errors (from Zod validation)
234
+ // Server sends: { error: "BAD_REQUEST", message: "Validation Failed", details: { issues: [...] } }
235
+ if (response.status === 400 && body.details?.issues) {
236
+ throw new ValidationError(body.details.issues);
237
+ }
238
+
239
+ throw new ApiError(response.status, body, body.message);
240
+ }
241
+
242
+ // Handle empty responses (204 No Content)
243
+ if (response.status === 204) {
244
+ return undefined as TOutput;
245
+ }
246
+
247
+ return response.json() as Promise<TOutput>;
248
+ }
249
+
250
+ /**
251
+ * Make a raw request (for non-JSON endpoints)
252
+ */
253
+ protected async rawRequest(
254
+ route: string,
255
+ init: RequestInit = {}
256
+ ): Promise<Response> {
257
+ const fetchFn = this.options.fetch || fetch;
258
+
259
+ return fetchFn(`${this.baseUrl}/${route}`, {
260
+ ...init,
261
+ headers: {
262
+ ...this.options.headers,
263
+ ...init.headers,
264
+ },
265
+ credentials: this.options.credentials,
266
+ });
267
+ }
268
+
269
+ // ==========================================
270
+ // SSE Connection Methods
271
+ // ==========================================
272
+
273
+ /**
274
+ * Connect to SSE endpoint for real-time updates
275
+ */
276
+ connect(options: SSEOptions = {}): void {
277
+ if (this.eventSource) {
278
+ this.disconnect();
279
+ }
280
+
281
+ this.sseOptions = {
282
+ endpoint: "/sse",
283
+ autoReconnect: true,
284
+ reconnectDelay: 3000,
285
+ ...options,
286
+ };
287
+
288
+ const url = new URL(`${this.baseUrl}${this.sseOptions.endpoint}`);
289
+
290
+ // Add channel subscriptions as query params
291
+ if (this.sseOptions.channels?.length) {
292
+ for (const channel of this.sseOptions.channels) {
293
+ url.searchParams.append("channel", channel);
294
+ }
295
+ }
296
+
297
+ this.eventSource = new EventSource(url.toString(), {
298
+ withCredentials: true,
299
+ });
300
+
301
+ this.eventSource.onopen = () => {
302
+ this.sseOptions.onConnect?.();
303
+ };
304
+
305
+ this.eventSource.onerror = (event) => {
306
+ this.sseOptions.onError?.(event);
307
+
308
+ // Handle reconnection (readyState 2 = CLOSED)
309
+ if (this.eventSource?.readyState === 2) {
310
+ this.sseOptions.onDisconnect?.();
311
+
312
+ if (this.sseOptions.autoReconnect) {
313
+ this.scheduleReconnect();
314
+ }
315
+ }
316
+ };
317
+
318
+ // Listen for all events and dispatch to handlers
319
+ this.eventSource.onmessage = (event) => {
320
+ this.dispatchEvent("message", event.data);
321
+ };
322
+
323
+ // Set up listeners for specific event types
324
+ for (const eventName of this.eventHandlers.keys()) {
325
+ if (eventName !== "message") {
326
+ this.eventSource.addEventListener(eventName, (event) => {
327
+ if ("data" in event) {
328
+ this.dispatchEvent(eventName, event.data);
329
+ }
330
+ });
331
+ }
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Disconnect from SSE endpoint
337
+ */
338
+ disconnect(): void {
339
+ if (this.reconnectTimeout) {
340
+ clearTimeout(this.reconnectTimeout);
341
+ this.reconnectTimeout = null;
342
+ }
343
+
344
+ if (this.eventSource) {
345
+ this.eventSource.close();
346
+ this.eventSource = null;
347
+ this.sseOptions.onDisconnect?.();
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Check if SSE is connected
353
+ */
354
+ get connected(): boolean {
355
+ // readyState 1 = OPEN
356
+ return this.eventSource?.readyState === 1;
357
+ }
358
+
359
+ // ==========================================
360
+ // Event Handling
361
+ // ==========================================
362
+
363
+ /**
364
+ * Subscribe to a typed SSE event
365
+ * @returns Unsubscribe function
366
+ */
367
+ on<E extends keyof TEvents>(
368
+ event: E,
369
+ handler: (data: TEvents[E]) => void
370
+ ): () => void {
371
+ const eventName = String(event);
372
+ let handlers = this.eventHandlers.get(eventName);
373
+
374
+ if (!handlers) {
375
+ handlers = new Set();
376
+ this.eventHandlers.set(eventName, handlers);
377
+
378
+ // If already connected, add event listener
379
+ if (this.eventSource && eventName !== "message") {
380
+ this.eventSource.addEventListener(eventName, (event) => {
381
+ if ("data" in event) {
382
+ this.dispatchEvent(eventName, event.data);
383
+ }
384
+ });
385
+ }
386
+ }
387
+
388
+ handlers.add(handler as (data: any) => void);
389
+
390
+ // Return unsubscribe function
391
+ return () => {
392
+ handlers?.delete(handler as (data: any) => void);
393
+ if (handlers?.size === 0) {
394
+ this.eventHandlers.delete(eventName);
395
+ }
396
+ };
397
+ }
398
+
399
+ /**
400
+ * Subscribe to an event once
401
+ */
402
+ once<E extends keyof TEvents>(
403
+ event: E,
404
+ handler: (data: TEvents[E]) => void
405
+ ): () => void {
406
+ const unsubscribe = this.on(event, (data) => {
407
+ unsubscribe();
408
+ handler(data);
409
+ });
410
+ return unsubscribe;
411
+ }
412
+
413
+ /**
414
+ * Remove all handlers for an event
415
+ */
416
+ off<E extends keyof TEvents>(event: E): void {
417
+ this.eventHandlers.delete(String(event));
418
+ }
419
+
420
+ // ==========================================
421
+ // Private Helpers
422
+ // ==========================================
423
+
424
+ private dispatchEvent(eventName: string, rawData: string): void {
425
+ const handlers = this.eventHandlers.get(eventName);
426
+ if (!handlers?.size) return;
427
+
428
+ let data: any;
429
+ try {
430
+ data = JSON.parse(rawData);
431
+ } catch {
432
+ data = rawData;
433
+ }
434
+
435
+ for (const handler of handlers) {
436
+ try {
437
+ handler(data);
438
+ } catch (error) {
439
+ console.error(`Error in event handler for "${eventName}":`, error);
440
+ }
441
+ }
442
+ }
443
+
444
+ private scheduleReconnect(): void {
445
+ if (this.reconnectTimeout) return;
446
+
447
+ this.reconnectTimeout = setTimeout(() => {
448
+ this.reconnectTimeout = null;
449
+ this.connect(this.sseOptions);
450
+ }, this.sseOptions.reconnectDelay);
451
+ }
452
+ }
453
+
454
+ // ============================================
455
+ // Type Helpers for Generated Client
456
+ // ============================================
457
+
458
+ /**
459
+ * Extract the input type from a Zod schema
460
+ */
461
+ export type ZodInput<T> = T extends { _input: infer I } ? I : never;
462
+
463
+ /**
464
+ * Extract the output type from a Zod schema
465
+ */
466
+ export type ZodOutput<T> = T extends { _output: infer O } ? O : never;
467
+
468
+ /**
469
+ * Create a typed route method
470
+ */
471
+ export type RouteMethod<TInput, TOutput> = (
472
+ input: TInput,
473
+ options?: RequestOptions
474
+ ) => Promise<TOutput>;
475
+
476
+ /**
477
+ * Create a route namespace from route definitions
478
+ */
479
+ export type RouteNamespace<TRoutes extends Record<string, { input: any; output: any }>> = {
480
+ [K in keyof TRoutes]: RouteMethod<TRoutes[K]["input"], TRoutes[K]["output"]>;
481
+ };
@@ -0,0 +1,150 @@
1
+ // Auto-generated by scripts/generate-client.ts
2
+ // DO NOT EDIT MANUALLY
3
+
4
+ import {
5
+ ApiClientBase,
6
+ ApiError,
7
+ ValidationError,
8
+ type RequestOptions,
9
+ type ApiClientOptions,
10
+ type SSEOptions,
11
+ } from "./base";
12
+
13
+ // ============================================
14
+ // Route Types
15
+ // ============================================
16
+
17
+ export namespace Routes {
18
+ export namespace Users {
19
+ export type GetInput = {
20
+ id: number;
21
+ };
22
+ export type GetOutput = {
23
+ id: number;
24
+ name: string;
25
+ email: string;
26
+ };
27
+
28
+ export type CreateInput = {
29
+ name: string;
30
+ email: string;
31
+ };
32
+ export type CreateOutput = {
33
+ id: number;
34
+ name: string;
35
+ email: string;
36
+ };
37
+
38
+ export type ListInput = {
39
+ page?: number;
40
+ limit?: number;
41
+ };
42
+ export type ListOutput = {
43
+ users: {
44
+ id: number;
45
+ name: string;
46
+ }[];
47
+ total: number;
48
+ };
49
+ }
50
+
51
+ export namespace Orders {
52
+ export type CreateInput = {
53
+ items: {
54
+ productId: number;
55
+ quantity: number;
56
+ }[];
57
+ };
58
+ export type CreateOutput = {
59
+ orderId: string;
60
+ total: number;
61
+ };
62
+ }
63
+ }
64
+
65
+ // ============================================
66
+ // SSE Event Types
67
+ // ============================================
68
+
69
+ export interface SSEEvents {}
70
+
71
+ // ============================================
72
+ // API Client
73
+ // ============================================
74
+
75
+ export interface ApiClientConfig extends ApiClientOptions {
76
+ /** Base URL of the API server */
77
+ baseUrl: string;
78
+ }
79
+
80
+ /**
81
+ * Typed API Client
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const api = createApiClient({ baseUrl: "http://localhost:3000" });
86
+ *
87
+ * // Typed route calls
88
+ * const result = await api.users.get({ id: 1 });
89
+ *
90
+ * // SSE events
91
+ * api.connect();
92
+ * api.on("notifications.new", (data) => {
93
+ * console.log(data.message);
94
+ * });
95
+ * ```
96
+ */
97
+ export class ApiClient extends ApiClientBase<SSEEvents> {
98
+ constructor(config: ApiClientConfig) {
99
+ super(config.baseUrl, {
100
+ credentials: "include",
101
+ ...config,
102
+ });
103
+ }
104
+
105
+ // ==========================================
106
+ // Route Namespaces
107
+ // ==========================================
108
+
109
+ users = {
110
+ get: (input: Routes.Users.GetInput, options?: RequestOptions): Promise<Routes.Users.GetOutput> =>
111
+ this.request("users.get", input, options),
112
+
113
+ create: (input: Routes.Users.CreateInput, options?: RequestOptions): Promise<Routes.Users.CreateOutput> =>
114
+ this.request("users.create", input, options),
115
+
116
+ list: (input: Routes.Users.ListInput, options?: RequestOptions): Promise<Routes.Users.ListOutput> =>
117
+ this.request("users.list", input, options)
118
+ };
119
+
120
+ orders = {
121
+ create: (input: Routes.Orders.CreateInput, options?: RequestOptions): Promise<Routes.Orders.CreateOutput> =>
122
+ this.request("orders.create", input, options),
123
+
124
+ status: (init?: RequestInit): Promise<Response> =>
125
+ this.rawRequest("orders.status", init)
126
+ };
127
+ }
128
+
129
+ // ============================================
130
+ // Factory Function
131
+ // ============================================
132
+
133
+ /**
134
+ * Create a new API client instance
135
+ *
136
+ * @param config - Client configuration
137
+ * @returns Typed API client
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const api = createApiClient({ baseUrl: "http://localhost:3000" });
142
+ * const user = await api.users.get({ id: 1 });
143
+ * ```
144
+ */
145
+ export function createApiClient(config: ApiClientConfig): ApiClient {
146
+ return new ApiClient(config);
147
+ }
148
+
149
+ // Re-export base types for convenience
150
+ export { ApiError, ValidationError, type RequestOptions, type SSEOptions };