@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.
- package/LICENSE +1 -1
- package/docs/api-client.md +7 -7
- package/docs/cache.md +1 -74
- package/docs/core-services.md +4 -116
- package/docs/cron.md +1 -1
- package/docs/errors.md +2 -2
- package/docs/events.md +3 -98
- package/docs/handlers.md +13 -48
- package/docs/logger.md +3 -58
- package/docs/middleware.md +2 -2
- package/docs/plugins.md +13 -64
- package/docs/project-structure.md +4 -142
- package/docs/rate-limiter.md +4 -136
- package/docs/router.md +6 -14
- package/docs/sse.md +1 -99
- package/docs/sveltekit-adapter.md +420 -0
- package/package.json +8 -11
- package/registry.d.ts +15 -14
- package/src/core/cache.ts +0 -75
- package/src/core/cron.ts +3 -96
- package/src/core/errors.ts +78 -11
- package/src/core/events.ts +1 -47
- package/src/core/index.ts +0 -4
- package/src/core/jobs.ts +0 -112
- package/src/core/logger.ts +12 -79
- package/src/core/rate-limiter.ts +29 -108
- package/src/core/sse.ts +1 -84
- package/src/core.ts +13 -104
- package/src/generator/index.ts +566 -0
- package/src/generator/zod-to-ts.ts +114 -0
- package/src/handlers.ts +14 -110
- package/src/index.ts +30 -24
- package/src/middleware.ts +2 -5
- package/src/registry.ts +4 -0
- package/src/router.ts +47 -1
- package/src/server.ts +618 -332
- package/README.md +0 -254
- package/cli/commands/dev.ts +0 -134
- package/cli/commands/generate.ts +0 -605
- package/cli/commands/init.ts +0 -205
- package/cli/commands/interactive.ts +0 -417
- package/cli/commands/plugin.ts +0 -192
- package/cli/commands/route.ts +0 -195
- package/cli/donkeylabs +0 -2
- package/cli/index.ts +0 -114
- package/docs/svelte-frontend.md +0 -324
- package/docs/testing.md +0 -438
- package/mcp/donkeylabs-mcp +0 -3238
- 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
|
-
|
|
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
|
-
|
|
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
|
|
4
|
+
export { AppServer, type ServerConfig } from "./server";
|
|
5
5
|
|
|
6
6
|
// Router
|
|
7
|
-
export {
|
|
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
|
|
19
|
+
// Handlers
|
|
10
20
|
export {
|
|
11
21
|
createHandler,
|
|
12
|
-
createRoute,
|
|
13
22
|
TypedHandler,
|
|
14
23
|
RawHandler,
|
|
15
|
-
|
|
24
|
+
Handlers,
|
|
16
25
|
type HandlerRuntime,
|
|
17
|
-
type
|
|
18
|
-
type
|
|
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
|
|
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> {
|
package/src/registry.ts
ADDED
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
|
+
|