@donkeylabs/cli 0.4.2 → 0.4.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 (31) hide show
  1. package/package.json +4 -1
  2. package/src/commands/generate.ts +122 -1
  3. package/src/commands/init.ts +31 -0
  4. package/templates/starter/src/index.ts +21 -4
  5. package/templates/sveltekit-app/package.json +3 -3
  6. package/templates/sveltekit-app/src/server/index.ts +14 -20
  7. package/templates/sveltekit-app/src/server/routes/demo.ts +237 -0
  8. package/templates/sveltekit-app/src/server/routes/cache/handlers/delete.ts +0 -15
  9. package/templates/sveltekit-app/src/server/routes/cache/handlers/get.ts +0 -15
  10. package/templates/sveltekit-app/src/server/routes/cache/handlers/keys.ts +0 -15
  11. package/templates/sveltekit-app/src/server/routes/cache/handlers/set.ts +0 -15
  12. package/templates/sveltekit-app/src/server/routes/cache/index.ts +0 -46
  13. package/templates/sveltekit-app/src/server/routes/counter/handlers/decrement.ts +0 -17
  14. package/templates/sveltekit-app/src/server/routes/counter/handlers/get.ts +0 -17
  15. package/templates/sveltekit-app/src/server/routes/counter/handlers/increment.ts +0 -17
  16. package/templates/sveltekit-app/src/server/routes/counter/handlers/reset.ts +0 -17
  17. package/templates/sveltekit-app/src/server/routes/counter/index.ts +0 -39
  18. package/templates/sveltekit-app/src/server/routes/cron/handlers/list.ts +0 -17
  19. package/templates/sveltekit-app/src/server/routes/cron/index.ts +0 -24
  20. package/templates/sveltekit-app/src/server/routes/events/handlers/emit.ts +0 -15
  21. package/templates/sveltekit-app/src/server/routes/events/index.ts +0 -19
  22. package/templates/sveltekit-app/src/server/routes/index.ts +0 -8
  23. package/templates/sveltekit-app/src/server/routes/jobs/handlers/enqueue.ts +0 -15
  24. package/templates/sveltekit-app/src/server/routes/jobs/handlers/stats.ts +0 -15
  25. package/templates/sveltekit-app/src/server/routes/jobs/index.ts +0 -28
  26. package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/check.ts +0 -15
  27. package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/reset.ts +0 -15
  28. package/templates/sveltekit-app/src/server/routes/ratelimit/index.ts +0 -29
  29. package/templates/sveltekit-app/src/server/routes/sse/handlers/broadcast.ts +0 -15
  30. package/templates/sveltekit-app/src/server/routes/sse/handlers/clients.ts +0 -15
  31. package/templates/sveltekit-app/src/server/routes/sse/index.ts +0 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -24,6 +24,9 @@
24
24
  "typecheck": "bun --bun tsc --noEmit"
25
25
  },
26
26
  "dependencies": {
27
+ "kysely": "^0.27.0",
28
+ "kysely-bun-sqlite": "^0.3.0",
29
+ "kysely-codegen": "^0.17.0",
27
30
  "picocolors": "^1.1.1",
28
31
  "prompts": "^2.4.2"
29
32
  },
@@ -1,8 +1,12 @@
1
- import { readdir, writeFile, readFile, mkdir } from "node:fs/promises";
1
+ import { readdir, writeFile, readFile, mkdir, unlink } from "node:fs/promises";
2
2
  import { join, relative, dirname, basename } from "node:path";
3
3
  import { existsSync } from "node:fs";
4
4
  import { spawn } from "node:child_process";
5
5
  import pc from "picocolors";
6
+ import { Kysely, Migrator, FileMigrationProvider } from "kysely";
7
+ import { BunSqliteDialect } from "kysely-bun-sqlite";
8
+ import { Database } from "bun:sqlite";
9
+ import { generate, KyselyBunSqliteDialect } from "kysely-codegen";
6
10
 
7
11
  interface DonkeylabsConfig {
8
12
  plugins: string[];
@@ -325,6 +329,8 @@ async function extractRoutesFromServer(entryPath: string): Promise<RouteInfo[]>
325
329
  return [];
326
330
  }
327
331
 
332
+ const TIMEOUT_MS = 10000; // 10 second timeout
333
+
328
334
  return new Promise((resolve) => {
329
335
  const child = spawn("bun", [fullPath], {
330
336
  env: { ...process.env, DONKEYLABS_GENERATE: "1" },
@@ -334,6 +340,16 @@ async function extractRoutesFromServer(entryPath: string): Promise<RouteInfo[]>
334
340
 
335
341
  let stdout = "";
336
342
  let stderr = "";
343
+ let timedOut = false;
344
+
345
+ // Timeout handler
346
+ const timeout = setTimeout(() => {
347
+ timedOut = true;
348
+ child.kill("SIGTERM");
349
+ console.warn(pc.yellow(`Route extraction timed out after ${TIMEOUT_MS / 1000}s`));
350
+ console.warn(pc.dim("Make sure your entry file handles DONKEYLABS_GENERATE=1 and calls process.exit(0)"));
351
+ resolve([]);
352
+ }, TIMEOUT_MS);
337
353
 
338
354
  child.stdout?.on("data", (data) => {
339
355
  stdout += data.toString();
@@ -344,6 +360,9 @@ async function extractRoutesFromServer(entryPath: string): Promise<RouteInfo[]>
344
360
  });
345
361
 
346
362
  child.on("close", (code) => {
363
+ clearTimeout(timeout);
364
+ if (timedOut) return; // Already resolved
365
+
347
366
  if (code !== 0) {
348
367
  console.warn(pc.yellow(`Failed to extract routes from server (exit code ${code})`));
349
368
  if (stderr) console.warn(pc.dim(stderr));
@@ -374,12 +393,105 @@ async function extractRoutesFromServer(entryPath: string): Promise<RouteInfo[]>
374
393
  });
375
394
 
376
395
  child.on("error", (err) => {
396
+ clearTimeout(timeout);
377
397
  console.warn(pc.yellow(`Failed to run entry file: ${err.message}`));
378
398
  resolve([]);
379
399
  });
380
400
  });
381
401
  }
382
402
 
403
+ /**
404
+ * Generate schema.ts from plugin migrations using kysely-codegen
405
+ */
406
+ async function generatePluginSchemas(
407
+ plugins: { name: string; path: string; exportName: string }[]
408
+ ): Promise<string[]> {
409
+ const generated: string[] = [];
410
+
411
+ for (const plugin of plugins) {
412
+ const pluginDir = dirname(join(process.cwd(), plugin.path));
413
+ const migrationsDir = join(pluginDir, "migrations");
414
+
415
+ // Skip plugins without migrations folder
416
+ if (!existsSync(migrationsDir)) {
417
+ continue;
418
+ }
419
+
420
+ // Check if there are any migration files
421
+ const migrationFiles = await readdir(migrationsDir);
422
+ const hasMigrations = migrationFiles.some(
423
+ (f) => f.endsWith(".ts") && !f.endsWith(".d.ts")
424
+ );
425
+
426
+ if (!hasMigrations) {
427
+ continue;
428
+ }
429
+
430
+ console.log(pc.dim(` Generating schema for ${plugin.name}...`));
431
+
432
+ const dbPath = join(process.cwd(), `.temp_schema_${plugin.name}.db`);
433
+
434
+ try {
435
+ // Create temp SQLite database
436
+ const db = new Kysely<any>({
437
+ dialect: new BunSqliteDialect({
438
+ database: new Database(dbPath),
439
+ }),
440
+ });
441
+
442
+ // Run migrations using Kysely's FileMigrationProvider
443
+ const migrator = new Migrator({
444
+ db,
445
+ provider: new FileMigrationProvider({
446
+ fs: await import("node:fs/promises"),
447
+ path: await import("node:path"),
448
+ migrationFolder: migrationsDir,
449
+ }),
450
+ });
451
+
452
+ const { error } = await migrator.migrateToLatest();
453
+
454
+ if (error) {
455
+ console.warn(
456
+ pc.yellow(` Warning: Migration failed for ${plugin.name}: ${error}`)
457
+ );
458
+ await db.destroy();
459
+ try {
460
+ await unlink(dbPath);
461
+ } catch {}
462
+ continue;
463
+ }
464
+
465
+ // Generate schema.ts using kysely-codegen
466
+ const schemaPath = join(pluginDir, "schema.ts");
467
+
468
+ await generate({
469
+ db,
470
+ outFile: schemaPath,
471
+ dialect: new KyselyBunSqliteDialect(),
472
+ });
473
+
474
+ generated.push(plugin.name);
475
+
476
+ // Cleanup
477
+ await db.destroy();
478
+ try {
479
+ await unlink(dbPath);
480
+ } catch {}
481
+ } catch (err: any) {
482
+ console.warn(
483
+ pc.yellow(` Warning: Schema generation failed for ${plugin.name}: ${err.message}`)
484
+ );
485
+ // Cleanup on error
486
+ try {
487
+ await unlink(dbPath);
488
+ } catch {}
489
+ }
490
+ }
491
+
492
+ return generated;
493
+ }
494
+
383
495
  export async function generateCommand(_args: string[]): Promise<void> {
384
496
  const config = await loadConfig();
385
497
  const outDir = config.outDir || ".@donkeylabs/server";
@@ -390,6 +502,15 @@ export async function generateCommand(_args: string[]): Promise<void> {
390
502
  const plugins = await findPlugins(config.plugins);
391
503
  const fileRoutes = await findRoutes(config.routes || "./src/routes/**/schema.ts");
392
504
 
505
+ // Generate schema.ts from migrations for plugins that have them
506
+ const schemaPlugins = await generatePluginSchemas(plugins);
507
+ if (schemaPlugins.length > 0) {
508
+ console.log(
509
+ pc.green("Generated schemas:"),
510
+ schemaPlugins.map((p) => pc.dim(p)).join(", ")
511
+ );
512
+ }
513
+
393
514
  // Extract routes by running the server with DONKEYLABS_GENERATE=1
394
515
  const entryPath = config.entry || "./src/index.ts";
395
516
  const serverRoutes = await extractRoutesFromServer(entryPath);
@@ -166,6 +166,9 @@ export async function initCommand(args: string[]) {
166
166
  console.log(pc.dim(" Run 'bun install' manually to install dependencies.\n"));
167
167
  } else {
168
168
  console.log(pc.green("\n✓ Dependencies installed\n"));
169
+
170
+ // Copy CLAUDE.md and docs/ from @donkeylabs/server to project root
171
+ await copyDocsFromServer(targetDir);
169
172
  }
170
173
 
171
174
  // Ask about MCP setup
@@ -319,3 +322,31 @@ async function copyDirectory(src: string, dest: string): Promise<void> {
319
322
  }
320
323
  }
321
324
  }
325
+
326
+ /**
327
+ * Copy CLAUDE.md and docs/ from @donkeylabs/server to project root
328
+ * for AI-assisted development
329
+ */
330
+ async function copyDocsFromServer(targetDir: string): Promise<void> {
331
+ const serverPkgPath = join(targetDir, "node_modules", "@donkeylabs", "server");
332
+
333
+ if (!existsSync(serverPkgPath)) {
334
+ return; // Server package not installed
335
+ }
336
+
337
+ // Copy CLAUDE.md
338
+ const claudeMdSrc = join(serverPkgPath, "CLAUDE.md");
339
+ if (existsSync(claudeMdSrc)) {
340
+ const claudeMdDest = join(targetDir, "CLAUDE.md");
341
+ await copyFile(claudeMdSrc, claudeMdDest);
342
+ console.log(pc.green(" Created:"), "CLAUDE.md (AI instructions)");
343
+ }
344
+
345
+ // Copy docs/ directory
346
+ const docsSrc = join(serverPkgPath, "docs");
347
+ if (existsSync(docsSrc)) {
348
+ const docsDest = join(targetDir, "docs");
349
+ await copyDirectory(docsSrc, docsDest);
350
+ console.log(pc.green(" Created:"), "docs/ (detailed documentation)");
351
+ }
352
+ }
@@ -1,8 +1,15 @@
1
- import { db } from "./db";
2
1
  import { AppServer, createRouter } from "@donkeylabs/server";
2
+ import { Kysely } from "kysely";
3
+ import { BunSqliteDialect } from "kysely-bun-sqlite";
4
+ import { Database } from "bun:sqlite";
3
5
  import { healthRouter } from "./routes/health";
4
6
  import { statsPlugin } from "./plugins/stats";
5
7
 
8
+ // Simple in-memory database
9
+ const db = new Kysely<{}>({
10
+ dialect: new BunSqliteDialect({ database: new Database(":memory:") }),
11
+ });
12
+
6
13
  const server = new AppServer({
7
14
  port: Number(process.env.PORT) || 3000,
8
15
  db,
@@ -26,12 +33,22 @@ export function createApi(baseUrl: string, options?: ApiClientOptions) {
26
33
  // Register plugins
27
34
  server.registerPlugin(statsPlugin);
28
35
 
29
- const api = createRouter("api")
36
+ const api = createRouter("api");
30
37
  // Register routes
31
38
  api.router(healthRouter);
32
39
 
40
+ server.use(api);
33
41
 
42
+ // Handle DONKEYLABS_GENERATE mode for CLI type generation
43
+ if (process.env.DONKEYLABS_GENERATE === "1") {
44
+ const routes = api.getRoutes().map((route) => ({
45
+ name: route.name,
46
+ handler: route.handler || "typed",
47
+ inputType: route.input ? "(generated)" : undefined,
48
+ outputType: route.output ? "(generated)" : undefined,
49
+ }));
50
+ console.log(JSON.stringify({ routes }));
51
+ process.exit(0);
52
+ }
34
53
 
35
-
36
- server.use(api);
37
54
  await server.start();
@@ -24,9 +24,9 @@
24
24
  "vite": "^7.2.6"
25
25
  },
26
26
  "dependencies": {
27
- "@donkeylabs/cli": "^0.4.2",
28
- "@donkeylabs/adapter-sveltekit": "^0.4.2",
29
- "@donkeylabs/server": "^0.4.2",
27
+ "@donkeylabs/cli": "^0.4.4",
28
+ "@donkeylabs/adapter-sveltekit": "^0.4.4",
29
+ "@donkeylabs/server": "^0.4.4",
30
30
  "bits-ui": "^2.15.4",
31
31
  "clsx": "^2.1.1",
32
32
  "kysely": "^0.27.6",
@@ -4,17 +4,7 @@ import { Kysely } from "kysely";
4
4
  import { BunSqliteDialect } from "kysely-bun-sqlite";
5
5
  import { Database } from "bun:sqlite";
6
6
  import { demoPlugin } from "./plugins/demo";
7
-
8
- // Import routes
9
- import {
10
- counterRoutes,
11
- cacheRoutes,
12
- jobsRoutes,
13
- cronRoutes,
14
- ratelimitRoutes,
15
- eventsRoutes,
16
- sseRoutes,
17
- } from "./routes";
7
+ import demoRoutes from "./routes/demo";
18
8
 
19
9
  // Simple in-memory database
20
10
  const db = new Kysely<{}>({
@@ -33,13 +23,17 @@ export const server = new AppServer({
33
23
  // Register plugin
34
24
  server.registerPlugin(demoPlugin);
35
25
 
36
- // Register all routes
37
- server.use(counterRoutes);
38
- server.use(cacheRoutes);
39
- server.use(jobsRoutes);
40
- server.use(cronRoutes);
41
- server.use(ratelimitRoutes);
42
- server.use(eventsRoutes);
43
- server.use(sseRoutes);
44
-
26
+ // Register routes
27
+ server.use(demoRoutes);
45
28
 
29
+ // Handle DONKEYLABS_GENERATE mode for CLI type generation
30
+ if (process.env.DONKEYLABS_GENERATE === "1") {
31
+ const routes = demoRoutes.getRoutes().map((route) => ({
32
+ name: route.name,
33
+ handler: route.handler || "typed",
34
+ inputType: route.input ? "(generated)" : undefined,
35
+ outputType: route.output ? "(generated)" : undefined,
36
+ }));
37
+ console.log(JSON.stringify({ routes }));
38
+ process.exit(0);
39
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Demo Router - Showcases @donkeylabs/server core features
3
+ *
4
+ * Routes delegate to the demo plugin service which handles
5
+ * the core service integrations with proper types.
6
+ */
7
+
8
+ import { createRouter, defineRoute } from "@donkeylabs/server";
9
+ import { z } from "zod";
10
+
11
+ const demo = createRouter("api");
12
+
13
+ // =============================================================================
14
+ // COUNTER - Uses plugin service for state
15
+ // =============================================================================
16
+
17
+ demo.route("counter.get").typed(
18
+ defineRoute({
19
+ output: z.object({ count: z.number() }),
20
+ handle: async (_, ctx) => {
21
+ return { count: ctx.plugins.demo.getCounter() };
22
+ },
23
+ })
24
+ );
25
+
26
+ demo.route("counter.increment").typed(
27
+ defineRoute({
28
+ output: z.object({ count: z.number() }),
29
+ handle: async (_, ctx) => {
30
+ return { count: ctx.plugins.demo.increment() };
31
+ },
32
+ })
33
+ );
34
+
35
+ demo.route("counter.decrement").typed(
36
+ defineRoute({
37
+ output: z.object({ count: z.number() }),
38
+ handle: async (_, ctx) => {
39
+ return { count: ctx.plugins.demo.decrement() };
40
+ },
41
+ })
42
+ );
43
+
44
+ demo.route("counter.reset").typed(
45
+ defineRoute({
46
+ output: z.object({ count: z.number() }),
47
+ handle: async (_, ctx) => {
48
+ return { count: ctx.plugins.demo.reset() };
49
+ },
50
+ })
51
+ );
52
+
53
+ // =============================================================================
54
+ // CACHE - In-memory caching (via plugin service)
55
+ // =============================================================================
56
+
57
+ demo.route("cache.set").typed(
58
+ defineRoute({
59
+ input: z.object({
60
+ key: z.string(),
61
+ value: z.any(),
62
+ ttl: z.number().optional(),
63
+ }),
64
+ output: z.object({ success: z.boolean() }),
65
+ handle: async (input, ctx) => {
66
+ return ctx.plugins.demo.cacheSet(input.key, input.value, input.ttl);
67
+ },
68
+ })
69
+ );
70
+
71
+ demo.route("cache.get").typed(
72
+ defineRoute({
73
+ input: z.object({ key: z.string() }),
74
+ output: z.object({ value: z.any().optional(), exists: z.boolean() }),
75
+ handle: async (input, ctx) => {
76
+ return ctx.plugins.demo.cacheGet(input.key);
77
+ },
78
+ })
79
+ );
80
+
81
+ demo.route("cache.delete").typed(
82
+ defineRoute({
83
+ input: z.object({ key: z.string() }),
84
+ output: z.object({ success: z.boolean() }),
85
+ handle: async (input, ctx) => {
86
+ return ctx.plugins.demo.cacheDelete(input.key);
87
+ },
88
+ })
89
+ );
90
+
91
+ demo.route("cache.keys").typed(
92
+ defineRoute({
93
+ output: z.object({ keys: z.array(z.string()), size: z.number() }),
94
+ handle: async (_, ctx) => {
95
+ return ctx.plugins.demo.cacheKeys();
96
+ },
97
+ })
98
+ );
99
+
100
+ // =============================================================================
101
+ // SSE - Server-Sent Events (via plugin service)
102
+ // =============================================================================
103
+
104
+ demo.route("sse.broadcast").typed(
105
+ defineRoute({
106
+ input: z.object({
107
+ channel: z.string().default("events"),
108
+ event: z.string().default("manual"),
109
+ data: z.any(),
110
+ }),
111
+ output: z.object({ success: z.boolean() }),
112
+ handle: async (input, ctx) => {
113
+ const channel = input.channel ?? "events";
114
+ const event = input.event ?? "manual";
115
+ return ctx.plugins.demo.broadcast(channel, event, input.data);
116
+ },
117
+ })
118
+ );
119
+
120
+ demo.route("sse.clients").typed(
121
+ defineRoute({
122
+ output: z.object({ total: z.number(), byChannel: z.number() }),
123
+ handle: async (_, ctx) => {
124
+ return ctx.plugins.demo.getSSEClients();
125
+ },
126
+ })
127
+ );
128
+
129
+ // =============================================================================
130
+ // JOBS - Background job queue (via plugin service)
131
+ // =============================================================================
132
+
133
+ demo.route("jobs.enqueue").typed(
134
+ defineRoute({
135
+ input: z.object({
136
+ name: z.string().default("demo-job"),
137
+ data: z.record(z.any()).optional(),
138
+ delay: z.number().optional(),
139
+ }),
140
+ output: z.object({ jobId: z.string() }),
141
+ handle: async (input, ctx) => {
142
+ const name = input.name ?? "demo-job";
143
+ return ctx.plugins.demo.enqueueJob(name, input.data || {}, input.delay);
144
+ },
145
+ })
146
+ );
147
+
148
+ demo.route("jobs.stats").typed(
149
+ defineRoute({
150
+ output: z.object({
151
+ pending: z.number(),
152
+ running: z.number(),
153
+ completed: z.number(),
154
+ }),
155
+ handle: async (_, ctx) => {
156
+ return ctx.plugins.demo.getJobStats();
157
+ },
158
+ })
159
+ );
160
+
161
+ // =============================================================================
162
+ // EVENTS - Pub/sub system (via plugin service)
163
+ // =============================================================================
164
+
165
+ demo.route("events.emit").typed(
166
+ defineRoute({
167
+ input: z.object({
168
+ event: z.string(),
169
+ data: z.record(z.any()).optional(),
170
+ }),
171
+ output: z.object({ success: z.boolean() }),
172
+ handle: async (input, ctx) => {
173
+ return ctx.plugins.demo.emitEvent(input.event, input.data || {});
174
+ },
175
+ })
176
+ );
177
+
178
+ // =============================================================================
179
+ // RATE LIMITING (via plugin service)
180
+ // =============================================================================
181
+
182
+ demo.route("ratelimit.check").typed(
183
+ defineRoute({
184
+ input: z.object({
185
+ key: z.string(),
186
+ limit: z.number().default(10),
187
+ window: z.number().default(60),
188
+ }),
189
+ output: z.object({
190
+ allowed: z.boolean(),
191
+ remaining: z.number(),
192
+ limit: z.number(),
193
+ resetAt: z.date(),
194
+ }),
195
+ handle: async (input, ctx) => {
196
+ const limit = input.limit ?? 10;
197
+ const window = input.window ?? 60;
198
+ return ctx.plugins.demo.checkRateLimit(input.key, limit, window * 1000);
199
+ },
200
+ })
201
+ );
202
+
203
+ demo.route("ratelimit.reset").typed(
204
+ defineRoute({
205
+ input: z.object({ key: z.string() }),
206
+ output: z.object({ success: z.boolean() }),
207
+ handle: async (input, ctx) => {
208
+ return ctx.plugins.demo.resetRateLimit(input.key);
209
+ },
210
+ })
211
+ );
212
+
213
+ // =============================================================================
214
+ // CRON - Scheduled tasks info (via plugin service)
215
+ // =============================================================================
216
+
217
+ demo.route("cron.list").typed(
218
+ defineRoute({
219
+ output: z.object({
220
+ tasks: z.array(
221
+ z.object({
222
+ id: z.string(),
223
+ name: z.string(),
224
+ expression: z.string(),
225
+ enabled: z.boolean(),
226
+ lastRun: z.string().optional(),
227
+ nextRun: z.string().optional(),
228
+ })
229
+ ),
230
+ }),
231
+ handle: async (_, ctx) => {
232
+ return { tasks: ctx.plugins.demo.getCronTasks() };
233
+ },
234
+ })
235
+ );
236
+
237
+ export default demo;
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class DeleteCacheHandler implements Handler<Routes.Api.Cache.Delete> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Cache.Delete.Input): Routes.Api.Cache.Delete.Output {
13
- return this.ctx.plugins.demo.cacheDelete(input.key);
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class GetCacheHandler implements Handler<Routes.Api.Cache.Get> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Cache.Get.Input): Routes.Api.Cache.Get.Output {
13
- return this.ctx.plugins.demo.cacheGet(input.key);
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class KeysCacheHandler implements Handler<Routes.Api.Cache.Keys> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Cache.Keys.Input): Routes.Api.Cache.Keys.Output {
13
- return this.ctx.plugins.demo.cacheKeys();
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class SetCacheHandler implements Handler<Routes.Api.Cache.Set> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Cache.Set.Input): Routes.Api.Cache.Set.Output {
13
- return this.ctx.plugins.demo.cacheSet(input.key, input.value, input.ttl);
14
- }
15
- }
@@ -1,46 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { SetCacheHandler } from "./handlers/set";
5
- import { GetCacheHandler } from "./handlers/get";
6
- import { DeleteCacheHandler } from "./handlers/delete";
7
- import { KeysCacheHandler } from "./handlers/keys";
8
-
9
- const router = createRouter("api");
10
-
11
- router.route("cache.set").typed(
12
- defineRoute({
13
- input: z.object({
14
- key: z.string(),
15
- value: z.any(),
16
- ttl: z.number().optional()
17
- }),
18
- output: z.object({ success: z.boolean() }),
19
- handle: SetCacheHandler,
20
- })
21
- );
22
-
23
- router.route("cache.get").typed(
24
- defineRoute({
25
- input: z.object({ key: z.string() }),
26
- output: z.object({ value: z.any().optional(), exists: z.boolean() }),
27
- handle: GetCacheHandler,
28
- })
29
- );
30
-
31
- router.route("cache.delete").typed(
32
- defineRoute({
33
- input: z.object({ key: z.string() }),
34
- output: z.object({ success: z.boolean() }),
35
- handle: DeleteCacheHandler,
36
- })
37
- );
38
-
39
- router.route("cache.keys").typed(
40
- defineRoute({
41
- output: z.object({ keys: z.array(z.string()) }),
42
- handle: KeysCacheHandler,
43
- })
44
- );
45
-
46
- export default router;
@@ -1,17 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class DecrementCounterHandler implements Handler<Routes.Api.Counter.Decrement> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Counter.Decrement.Input): Routes.Api.Counter.Decrement.Output {
13
- return {
14
- count: this.ctx.plugins.demo.decrement(),
15
- };
16
- }
17
- }
@@ -1,17 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class GetCounterHandler implements Handler<Routes.Api.Counter.Get> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Counter.Get.Input): Routes.Api.Counter.Get.Output {
13
- return {
14
- count: this.ctx.plugins.demo.getCounter(),
15
- };
16
- }
17
- }
@@ -1,17 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class IncrementCounterHandler implements Handler<Routes.Api.Counter.Increment> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Counter.Increment.Input): Routes.Api.Counter.Increment.Output {
13
- return {
14
- count: this.ctx.plugins.demo.increment(),
15
- };
16
- }
17
- }
@@ -1,17 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class ResetCounterHandler implements Handler<Routes.Api.Counter.Reset> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Counter.Reset.Input): Routes.Api.Counter.Reset.Output {
13
- return {
14
- count: this.ctx.plugins.demo.reset(),
15
- };
16
- }
17
- }
@@ -1,39 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { GetCounterHandler } from "./handlers/get";
5
- import { IncrementCounterHandler } from "./handlers/increment";
6
- import { DecrementCounterHandler } from "./handlers/decrement";
7
- import { ResetCounterHandler } from "./handlers/reset";
8
-
9
- const router = createRouter("api");
10
-
11
- router.route("counter.get").typed(
12
- defineRoute({
13
- output: z.object({ count: z.number() }),
14
- handle: GetCounterHandler,
15
- })
16
- );
17
-
18
- router.route("counter.increment").typed(
19
- defineRoute({
20
- output: z.object({ count: z.number() }),
21
- handle: IncrementCounterHandler,
22
- })
23
- );
24
-
25
- router.route("counter.decrement").typed(
26
- defineRoute({
27
- output: z.object({ count: z.number() }),
28
- handle: DecrementCounterHandler,
29
- })
30
- );
31
-
32
- router.route("counter.reset").typed(
33
- defineRoute({
34
- output: z.object({ count: z.number() }),
35
- handle: ResetCounterHandler,
36
- })
37
- );
38
-
39
- export default router;
@@ -1,17 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class ListCronHandler implements Handler<Routes.Api.Cron.List> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Cron.List.Input): Routes.Api.Cron.List.Output {
13
- return {
14
- tasks: this.ctx.plugins.demo.getCronTasks()
15
- };
16
- }
17
- }
@@ -1,24 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { ListCronHandler } from "./handlers/list";
5
-
6
- const router = createRouter("api");
7
-
8
- router.route("cron.list").typed(
9
- defineRoute({
10
- output: z.object({
11
- tasks: z.array(z.object({
12
- id: z.string(),
13
- name: z.string(),
14
- expression: z.string(),
15
- enabled: z.boolean(),
16
- lastRun: z.string().optional(),
17
- nextRun: z.string().optional()
18
- }))
19
- }),
20
- handle: ListCronHandler,
21
- })
22
- );
23
-
24
- export default router;
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class EmitEventHandler implements Handler<Routes.Api.Events.Emit> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Events.Emit.Input): Routes.Api.Events.Emit.Output {
13
- return this.ctx.plugins.demo.emitEvent(input.event!, input.data);
14
- }
15
- }
@@ -1,19 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { EmitEventHandler } from "./handlers/emit";
5
-
6
- const router = createRouter("api");
7
-
8
- router.route("events.emit").typed(
9
- defineRoute({
10
- input: z.object({
11
- event: z.string().default("demo.test"),
12
- data: z.any().default({ test: true })
13
- }),
14
- output: z.object({ success: z.boolean() }),
15
- handle: EmitEventHandler,
16
- })
17
- );
18
-
19
- export default router;
@@ -1,8 +0,0 @@
1
- // Export all route modules
2
- export { default as counterRoutes } from "./counter";
3
- export { default as cacheRoutes } from "./cache";
4
- export { default as jobsRoutes } from "./jobs";
5
- export { default as cronRoutes } from "./cron";
6
- export { default as ratelimitRoutes } from "./ratelimit";
7
- export { default as eventsRoutes } from "./events";
8
- export { default as sseRoutes } from "./sse";
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class EnqueueJobHandler implements Handler<Routes.Api.Jobs.Enqueue> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Jobs.Enqueue.Input): Routes.Api.Jobs.Enqueue.Output {
13
- return this.ctx.plugins.demo.enqueueJob(input.name!, input.data, input.delay);
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class StatsJobHandler implements Handler<Routes.Api.Jobs.Stats> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Jobs.Stats.Input): Routes.Api.Jobs.Stats.Output {
13
- return this.ctx.plugins.demo.getJobStats();
14
- }
15
- }
@@ -1,28 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { EnqueueJobHandler } from "./handlers/enqueue";
5
- import { StatsJobHandler } from "./handlers/stats";
6
-
7
- const router = createRouter("api");
8
-
9
- router.route("jobs.enqueue").typed(
10
- defineRoute({
11
- input: z.object({
12
- name: z.string().default("demo-job"),
13
- data: z.any().default({}),
14
- delay: z.number().optional()
15
- }),
16
- output: z.object({ jobId: z.string() }),
17
- handle: EnqueueJobHandler,
18
- })
19
- );
20
-
21
- router.route("jobs.stats").typed(
22
- defineRoute({
23
- output: z.object({ pending: z.number(), running: z.number(), completed: z.number() }),
24
- handle: StatsJobHandler,
25
- })
26
- );
27
-
28
- export default router;
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class CheckRateLimitHandler implements Handler<Routes.Api.Ratelimit.Check> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Ratelimit.Check.Input): Routes.Api.Ratelimit.Check.Output {
13
- return this.ctx.plugins.demo.checkRateLimit(input.key!, input.limit!, input.window!);
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class ResetRateLimitHandler implements Handler<Routes.Api.Ratelimit.Reset> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Ratelimit.Reset.Input): Routes.Api.Ratelimit.Reset.Output {
13
- return this.ctx.plugins.demo.resetRateLimit(input.key!);
14
- }
15
- }
@@ -1,29 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { CheckRateLimitHandler } from "./handlers/check";
5
- import { ResetRateLimitHandler } from "./handlers/reset";
6
-
7
- const router = createRouter("api");
8
-
9
- router.route("ratelimit.check").typed(
10
- defineRoute({
11
- input: z.object({
12
- key: z.string().default("demo"),
13
- limit: z.number().default(5),
14
- window: z.number().default(60000)
15
- }),
16
- output: z.object({ allowed: z.boolean(), remaining: z.number(), resetAt: z.date() }),
17
- handle: CheckRateLimitHandler,
18
- })
19
- );
20
-
21
- router.route("ratelimit.reset").typed(
22
- defineRoute({
23
- input: z.object({ key: z.string().default("demo") }),
24
- output: z.object({ success: z.boolean() }),
25
- handle: ResetRateLimitHandler,
26
- })
27
- );
28
-
29
- export default router;
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class BroadcastSseHandler implements Handler<Routes.Api.Sse.Broadcast> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Sse.Broadcast.Input): Routes.Api.Sse.Broadcast.Output {
13
- return this.ctx.plugins.demo.broadcast(input.channel!, input.event!, input.data);
14
- }
15
- }
@@ -1,15 +0,0 @@
1
-
2
- import type { Handler, AppContext } from "$lib/api";
3
- import type { Routes } from "$lib/api";
4
-
5
- export class ClientsSseHandler implements Handler<Routes.Api.Sse.Clients> {
6
- ctx: AppContext;
7
-
8
- constructor(ctx: AppContext) {
9
- this.ctx = ctx;
10
- }
11
-
12
- handle(input: Routes.Api.Sse.Clients.Input): Routes.Api.Sse.Clients.Output {
13
- return this.ctx.plugins.demo.getSSEClients();
14
- }
15
- }
@@ -1,28 +0,0 @@
1
-
2
- import { createRouter, defineRoute } from "@donkeylabs/server";
3
- import { z } from "zod";
4
- import { BroadcastSseHandler } from "./handlers/broadcast";
5
- import { ClientsSseHandler } from "./handlers/clients";
6
-
7
- const router = createRouter("api");
8
-
9
- router.route("sse.broadcast").typed(
10
- defineRoute({
11
- input: z.object({
12
- channel: z.string().default("events"),
13
- event: z.string().default("manual"),
14
- data: z.any()
15
- }),
16
- output: z.object({ success: z.boolean(), recipients: z.number() }),
17
- handle: BroadcastSseHandler,
18
- })
19
- );
20
-
21
- router.route("sse.clients").typed(
22
- defineRoute({
23
- output: z.object({ total: z.number(), byChannel: z.number() }),
24
- handle: ClientsSseHandler,
25
- })
26
- );
27
-
28
- export default router;