@donkeylabs/server 0.3.0 → 0.4.0

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 (49) hide show
  1. package/LICENSE +1 -1
  2. package/docs/api-client.md +7 -7
  3. package/docs/cache.md +1 -74
  4. package/docs/core-services.md +4 -116
  5. package/docs/cron.md +1 -1
  6. package/docs/errors.md +2 -2
  7. package/docs/events.md +3 -98
  8. package/docs/handlers.md +13 -48
  9. package/docs/logger.md +3 -58
  10. package/docs/middleware.md +2 -2
  11. package/docs/plugins.md +13 -64
  12. package/docs/project-structure.md +4 -142
  13. package/docs/rate-limiter.md +4 -136
  14. package/docs/router.md +6 -14
  15. package/docs/sse.md +1 -99
  16. package/docs/sveltekit-adapter.md +420 -0
  17. package/package.json +8 -11
  18. package/registry.d.ts +15 -14
  19. package/src/core/cache.ts +0 -75
  20. package/src/core/cron.ts +3 -96
  21. package/src/core/errors.ts +78 -11
  22. package/src/core/events.ts +1 -47
  23. package/src/core/index.ts +0 -4
  24. package/src/core/jobs.ts +0 -112
  25. package/src/core/logger.ts +12 -79
  26. package/src/core/rate-limiter.ts +29 -108
  27. package/src/core/sse.ts +1 -84
  28. package/src/core.ts +13 -104
  29. package/src/generator/index.ts +566 -0
  30. package/src/generator/zod-to-ts.ts +114 -0
  31. package/src/handlers.ts +14 -110
  32. package/src/index.ts +30 -24
  33. package/src/middleware.ts +2 -5
  34. package/src/registry.ts +4 -0
  35. package/src/router.ts +47 -1
  36. package/src/server.ts +618 -332
  37. package/README.md +0 -254
  38. package/cli/commands/dev.ts +0 -134
  39. package/cli/commands/generate.ts +0 -605
  40. package/cli/commands/init.ts +0 -205
  41. package/cli/commands/interactive.ts +0 -417
  42. package/cli/commands/plugin.ts +0 -192
  43. package/cli/commands/route.ts +0 -195
  44. package/cli/donkeylabs +0 -2
  45. package/cli/index.ts +0 -114
  46. package/docs/svelte-frontend.md +0 -324
  47. package/docs/testing.md +0 -438
  48. package/mcp/donkeylabs-mcp +0 -3238
  49. package/mcp/server.ts +0 -3238
package/src/handlers.ts CHANGED
@@ -1,101 +1,6 @@
1
1
  import type { RouteDefinition, ServerContext } from "./router";
2
2
  import { z } from "zod";
3
3
 
4
- // ============================================
5
- // Route and Handler Types for DX
6
- // ============================================
7
-
8
- /**
9
- * Route contract interface - generated routes export this shape
10
- */
11
- export interface RouteContract {
12
- input: any;
13
- output: any;
14
- }
15
-
16
- /**
17
- * Handler interface for model classes.
18
- *
19
- * @example
20
- * import type { Handler } from "@donkeylabs/server";
21
- * import type { Health } from "$server/routes";
22
- *
23
- * export class PingModel implements Handler<Health.Ping> {
24
- * handle(input: Health.Ping.Input): Health.Ping.Output {
25
- * return { status: "ok", timestamp: new Date().toISOString() };
26
- * }
27
- * }
28
- */
29
- export interface Handler<T extends RouteContract> {
30
- handle(input: T["input"]): T["output"] | Promise<T["output"]>;
31
- }
32
-
33
- /**
34
- * Typed route definition.
35
- * Use with generated route types: `const route: Route<Health.Ping> = { ... }`
36
- *
37
- * @example
38
- * import { Health } from ".@donkeylabs/server/routes";
39
- * import type { Route } from "@donkeylabs/server";
40
- *
41
- * export const pingRoute: Route<Health.Ping> = {
42
- * input: Health.Ping.Input,
43
- * output: Health.Ping.Output,
44
- * handle: async (input, ctx) => new PingModel(ctx).handle(input),
45
- * };
46
- */
47
- export interface Route<T extends RouteContract> {
48
- input: z.ZodType<T["input"]>;
49
- output: z.ZodType<T["output"]>;
50
- handle: (input: T["input"], ctx: ServerContext) => T["output"] | Promise<T["output"]>;
51
- }
52
-
53
- /**
54
- * Route configuration with inferred types from zod schemas.
55
- */
56
- export interface TypedRouteConfig<TInput, TOutput> {
57
- input: z.ZodType<TInput>;
58
- output: z.ZodType<TOutput>;
59
- handle: (input: TInput, ctx: ServerContext) => TOutput | Promise<TOutput>;
60
- }
61
-
62
- export interface RawRouteConfig {
63
- handle: (req: Request, ctx: ServerContext) => Response | Promise<Response>;
64
- }
65
-
66
- /**
67
- * Create a typed route with full type inference from zod schemas.
68
- *
69
- * @example
70
- * export const pingRoute = createRoute.typed({
71
- * input: z.object({ echo: z.string().optional() }),
72
- * output: z.object({ status: z.literal("ok"), timestamp: z.string() }),
73
- * handle: async (input, ctx) => ({
74
- * status: "ok",
75
- * timestamp: new Date().toISOString(),
76
- * }),
77
- * });
78
- */
79
- export const createRoute = {
80
- typed<TInput, TOutput>(config: TypedRouteConfig<TInput, TOutput>) {
81
- return {
82
- ...config,
83
- _handler: "typed" as const,
84
- };
85
- },
86
-
87
- raw(config: RawRouteConfig) {
88
- return {
89
- ...config,
90
- _handler: "raw" as const,
91
- };
92
- },
93
- };
94
-
95
- // ============================================
96
- // Handler Runtimes
97
- // ============================================
98
-
99
4
  export interface HandlerRuntime<Fn extends Function = Function> {
100
5
  execute(
101
6
  req: Request,
@@ -133,39 +38,37 @@ export function createHandler<Fn extends Function>(
133
38
  };
134
39
  }
135
40
 
41
+ // ==========================================
42
+ // 1. Typed Handler (Default)
43
+ // ==========================================
136
44
  export type TypedFn = (input: any, ctx: ServerContext) => Promise<any> | any;
137
45
  export type TypedHandler = HandlerRuntime<TypedFn>;
138
46
 
139
47
  export const TypedHandler: TypedHandler = {
140
48
  async execute(req, def, handle, ctx) {
141
- if (req.method !== "POST") {
142
- return new Response("Method Not Allowed", { status: 405 });
143
- }
144
-
145
- let body: unknown = {};
146
- try {
147
- body = await req.json();
148
- } catch {
149
- return Response.json({ error: "Invalid JSON" }, { status: 400 });
150
- }
49
+ if (req.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
50
+ let body: any = {};
51
+ try { body = await req.json(); } catch(e) { return Response.json({error: "Invalid JSON"}, {status:400}); }
151
52
 
152
53
  try {
153
54
  const input = def.input ? def.input.parse(body) : body;
154
55
  const result = await handle(input, ctx);
155
56
  const output = def.output ? def.output.parse(result) : result;
156
57
  return Response.json(output);
157
- } catch (e) {
58
+ } catch (e: any) {
158
59
  console.error(e);
159
60
  if (e instanceof z.ZodError) {
160
61
  return Response.json({ error: "Validation Failed", details: e.issues }, { status: 400 });
161
62
  }
162
- const message = e instanceof Error ? e.message : "Internal Error";
163
- return Response.json({ error: message }, { status: 500 });
63
+ return Response.json({ error: e.message || "Internal Error" }, { status: 500 });
164
64
  }
165
65
  },
166
66
  __signature: undefined as unknown as TypedFn
167
- };
67
+ }
168
68
 
69
+ // ==========================================
70
+ // 2. Raw Handler
71
+ // ==========================================
169
72
  export type RawFn = (req: Request, ctx: ServerContext) => Promise<Response> | Response;
170
73
  export type RawHandler = HandlerRuntime<RawFn>;
171
74
 
@@ -174,9 +77,10 @@ export const RawHandler: RawHandler = {
174
77
  return await handle(req, ctx);
175
78
  },
176
79
  __signature: undefined as unknown as RawFn
177
- };
80
+ }
178
81
 
179
82
  export const Handlers = {
180
83
  typed: TypedHandler,
181
84
  raw: RawHandler
182
85
  };
86
+
package/src/index.ts CHANGED
@@ -1,21 +1,32 @@
1
1
  // @donkeylabs/server - Main exports
2
2
 
3
3
  // Server
4
- export { AppServer, type ServerConfig, type ServerInspection } from "./server";
4
+ export { AppServer, type ServerConfig } from "./server";
5
5
 
6
6
  // Router
7
- export { createRouter, type Router, type RouteBuilder, type ServerContext, type IRouter, type IRouteBuilder, type IMiddlewareBuilder } from "./router";
7
+ export {
8
+ createRouter,
9
+ defineRoute,
10
+ type Router,
11
+ type RouteBuilder,
12
+ type ServerContext,
13
+ type IRouter,
14
+ type IRouteBuilder,
15
+ type IMiddlewareBuilder,
16
+ type TypedRouteConfig,
17
+ } from "./router";
8
18
 
9
- // Handlers and Route types
19
+ // Handlers
10
20
  export {
11
21
  createHandler,
12
- createRoute,
13
22
  TypedHandler,
14
23
  RawHandler,
15
- type Handler,
24
+ Handlers,
16
25
  type HandlerRuntime,
17
- type Route,
18
- type RouteContract,
26
+ type TypedFn,
27
+ type TypedHandler as TypedHandlerType,
28
+ type RawFn,
29
+ type RawHandler as RawHandlerType,
19
30
  } from "./handlers";
20
31
 
21
32
  // Core Plugin System
@@ -40,29 +51,24 @@ export {
40
51
  export { createMiddleware } from "./middleware";
41
52
 
42
53
  // Config helper
43
- export function defineConfig(config: {
54
+ export interface DonkeylabsConfig {
55
+ /** Glob patterns for plugin files */
44
56
  plugins: string[];
57
+ /** Output directory for generated types */
45
58
  outDir: string;
59
+ /** Server entry file for route extraction */
60
+ entry?: string;
61
+ /** Route files pattern */
46
62
  routes?: string;
63
+ /** Client generation options */
47
64
  client?: { output: string };
48
- }) {
65
+ /** Adapter package for framework-specific generation (e.g., "@donkeylabs/adapter-sveltekit") */
66
+ adapter?: string;
67
+ }
68
+
69
+ export function defineConfig(config: DonkeylabsConfig): DonkeylabsConfig {
49
70
  return config;
50
71
  }
51
72
 
52
73
  // Re-export HttpError for custom error creation
53
74
  export { HttpError } from "./core/errors";
54
-
55
- // Re-export SSE types for channel configuration
56
- export { type SSEChannelConfig } from "./core/sse";
57
-
58
- // Re-export Rate Limiter types for rule configuration
59
- export { type RateLimitRule, type RateLimitResult } from "./core/rate-limiter";
60
-
61
- // Re-export Job types for job registration
62
- export { type JobDefinition, type RegisteredJob } from "./core/jobs";
63
-
64
- // Re-export Cron types for cron task registration
65
- export { type CronTaskDefinition, type CronTask } from "./core/cron";
66
-
67
- // Re-export Cache types for namespacing
68
- export { type NamespacedCache } from "./core/cache";
package/src/middleware.ts CHANGED
@@ -3,8 +3,7 @@ import type { ServerContext } from "./router";
3
3
  // The next function - calls the next middleware or handler
4
4
  export type NextFn = () => Promise<Response>;
5
5
 
6
- // Middleware function signature
7
- // ctx is ServerContext (request-time context with db, plugins, etc.)
6
+ // Middleware function signature (what plugins implement)
8
7
  export type MiddlewareFn<TConfig = void> = (
9
8
  req: Request,
10
9
  ctx: ServerContext,
@@ -18,9 +17,7 @@ export interface MiddlewareRuntime<TConfig = void> {
18
17
  readonly __config: TConfig; // Phantom type for config inference
19
18
  }
20
19
 
21
- // Factory to create middleware
22
- // Note: For typed plugin access, use the middleware function pattern in createPlugin.define()
23
- // which gives you PluginContext in the closure
20
+ // Factory to create middleware (mirrors createHandler pattern)
24
21
  export function createMiddleware<TConfig = void>(
25
22
  execute: MiddlewareFn<TConfig>
26
23
  ): MiddlewareRuntime<TConfig> {
@@ -0,0 +1,4 @@
1
+ // Runtime registry - placeholder for user projects
2
+ // This file is re-generated by `donkeylabs generate` in user projects
3
+ // to register custom handlers and middleware at runtime.
4
+ /// <reference path="../registry.d.ts" />
package/src/router.ts CHANGED
@@ -36,10 +36,18 @@ export type RouteDefinition<
36
36
  : HandlerRegistry[T]["__signature"];
37
37
  };
38
38
 
39
+ export interface HandlerClass<I = any, O = any> {
40
+ new (ctx: ServerContext): { handle(input: I): Promise<O> | O };
41
+ }
42
+
43
+ function isHandlerClass(fn: any): fn is HandlerClass {
44
+ return typeof fn === 'function' && fn.prototype && typeof fn.prototype.handle === 'function';
45
+ }
46
+
39
47
  export interface TypedRouteConfig<I = any, O = any> {
40
48
  input?: z.ZodType<I>;
41
49
  output?: z.ZodType<O>;
42
- handle: (input: I, ctx: ServerContext) => Promise<O> | O;
50
+ handle: ((input: I, ctx: ServerContext) => Promise<O> | O) | HandlerClass<I, O>;
43
51
  }
44
52
 
45
53
  export interface RouteMetadata {
@@ -69,6 +77,10 @@ export class RouteBuilder<TRouter extends Router> implements IRouteBuilderBase<T
69
77
  ) {}
70
78
 
71
79
  typed<I, O>(config: TypedRouteConfig<I, O>): TRouter {
80
+ if (isHandlerClass(config.handle)) {
81
+ const HandlerClass = config.handle;
82
+ config.handle = (input, ctx) => new HandlerClass(ctx).handle(input);
83
+ }
72
84
  return this.router.addRoute(this.name, "typed", config, this._middleware);
73
85
  }
74
86
 
@@ -156,6 +168,23 @@ export class Router implements IRouter {
156
168
  }));
157
169
  }
158
170
 
171
+ /** Get route metadata with TypeScript type strings for code generation */
172
+ getTypedMetadata(): Array<{
173
+ name: string;
174
+ handler: string;
175
+ inputType?: string;
176
+ outputType?: string;
177
+ }> {
178
+ // Dynamic import to avoid circular deps
179
+ const { zodSchemaToTs } = require("./generator/zod-to-ts");
180
+ return this.getRoutes().map(route => ({
181
+ name: route.name,
182
+ handler: route.handler,
183
+ inputType: route.input ? zodSchemaToTs(route.input) : undefined,
184
+ outputType: route.output ? zodSchemaToTs(route.output) : undefined,
185
+ }));
186
+ }
187
+
159
188
  getPrefix(): string {
160
189
  return this.prefix;
161
190
  }
@@ -177,3 +206,20 @@ function createMiddlewareBuilderProxy<TRouter extends Router>(router: TRouter):
177
206
  }
178
207
 
179
208
  export const createRouter = (prefix?: string): IRouter => new Router(prefix);
209
+
210
+ /**
211
+ * Define a route with type inference for input/output schemas.
212
+ *
213
+ * @example
214
+ * export const myRoute = defineRoute({
215
+ * input: z.object({ name: z.string() }),
216
+ * output: z.object({ greeting: z.string() }),
217
+ * handle: async ({ name }, ctx) => {
218
+ * return { greeting: `Hello ${name}` };
219
+ * }
220
+ * });
221
+ */
222
+ export function defineRoute<I, O>(config: TypedRouteConfig<I, O>): TypedRouteConfig<I, O> {
223
+ return config;
224
+ }
225
+