@donkeylabs/cli 0.1.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 (53) hide show
  1. package/package.json +2 -2
  2. package/src/client/base.ts +481 -0
  3. package/src/commands/generate.ts +262 -53
  4. package/src/index.ts +0 -0
  5. package/templates/starter/package.json +3 -3
  6. package/templates/starter/src/index.ts +19 -30
  7. package/templates/starter/src/routes/health/handlers/ping.ts +22 -0
  8. package/templates/starter/src/routes/health/index.ts +16 -2
  9. package/templates/sveltekit-app/bun.lock +547 -0
  10. package/templates/sveltekit-app/donkeylabs.config.ts +2 -0
  11. package/templates/sveltekit-app/package.json +10 -8
  12. package/templates/sveltekit-app/scripts/watch-server.ts +55 -0
  13. package/templates/sveltekit-app/src/lib/api.ts +195 -81
  14. package/templates/sveltekit-app/src/routes/+page.server.ts +3 -3
  15. package/templates/sveltekit-app/src/routes/+page.svelte +235 -96
  16. package/templates/sveltekit-app/src/server/index.ts +29 -247
  17. package/templates/sveltekit-app/src/server/plugins/demo/index.ts +144 -0
  18. package/templates/sveltekit-app/src/server/routes/cache/handlers/delete.ts +15 -0
  19. package/templates/sveltekit-app/src/server/routes/cache/handlers/get.ts +15 -0
  20. package/templates/sveltekit-app/src/server/routes/cache/handlers/keys.ts +15 -0
  21. package/templates/sveltekit-app/src/server/routes/cache/handlers/set.ts +15 -0
  22. package/templates/sveltekit-app/src/server/routes/cache/index.ts +46 -0
  23. package/templates/sveltekit-app/src/server/routes/counter/handlers/decrement.ts +17 -0
  24. package/templates/sveltekit-app/src/server/routes/counter/handlers/get.ts +17 -0
  25. package/templates/sveltekit-app/src/server/routes/counter/handlers/increment.ts +17 -0
  26. package/templates/sveltekit-app/src/server/routes/counter/handlers/reset.ts +17 -0
  27. package/templates/sveltekit-app/src/server/routes/counter/index.ts +39 -0
  28. package/templates/sveltekit-app/src/server/routes/cron/handlers/list.ts +17 -0
  29. package/templates/sveltekit-app/src/server/routes/cron/index.ts +24 -0
  30. package/templates/sveltekit-app/src/server/routes/events/handlers/emit.ts +15 -0
  31. package/templates/sveltekit-app/src/server/routes/events/index.ts +19 -0
  32. package/templates/sveltekit-app/src/server/routes/index.ts +8 -0
  33. package/templates/sveltekit-app/src/server/routes/jobs/handlers/enqueue.ts +15 -0
  34. package/templates/sveltekit-app/src/server/routes/jobs/handlers/stats.ts +15 -0
  35. package/templates/sveltekit-app/src/server/routes/jobs/index.ts +28 -0
  36. package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/check.ts +15 -0
  37. package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/reset.ts +15 -0
  38. package/templates/sveltekit-app/src/server/routes/ratelimit/index.ts +29 -0
  39. package/templates/sveltekit-app/src/server/routes/sse/handlers/broadcast.ts +15 -0
  40. package/templates/sveltekit-app/src/server/routes/sse/handlers/clients.ts +15 -0
  41. package/templates/sveltekit-app/src/server/routes/sse/index.ts +28 -0
  42. package/templates/sveltekit-app/{svelte.config.js → svelte.config.ts} +4 -5
  43. package/templates/sveltekit-app/tsconfig.json +4 -9
  44. package/templates/sveltekit-app/vite.config.ts +2 -1
  45. package/templates/starter/CLAUDE.md +0 -144
  46. package/templates/starter/src/client.test.ts +0 -7
  47. package/templates/starter/src/db.ts +0 -9
  48. package/templates/starter/src/routes/health/ping/index.ts +0 -13
  49. package/templates/starter/src/routes/health/ping/models/model.ts +0 -23
  50. package/templates/starter/src/routes/health/ping/schema.ts +0 -14
  51. package/templates/starter/src/routes/health/ping/tests/integ.test.ts +0 -20
  52. package/templates/starter/src/routes/health/ping/tests/unit.test.ts +0 -21
  53. package/templates/starter/src/test-ctx.ts +0 -24
@@ -3,6 +3,8 @@ import { defineConfig } from "@donkeylabs/server";
3
3
  export default defineConfig({
4
4
  plugins: ["./src/server/plugins/**/index.ts"],
5
5
  outDir: ".@donkeylabs/server",
6
+ entry: "./src/server/index.ts",
7
+ routes: "./src/server/routes/**/{route,index}.ts",
6
8
  adapter: "@donkeylabs/adapter-sveltekit",
7
9
  client: {
8
10
  output: "./src/lib/api.ts",
@@ -4,15 +4,16 @@
4
4
  "version": "0.0.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "bun --bun vite dev",
8
- "build": "vite build",
7
+ "dev": "bun run gen:types && bun run dev:watch & bun --bun vite dev",
8
+ "dev:watch": "bun --watch --no-clear-screen scripts/watch-server.ts",
9
+ "build": "bun run gen:types && vite build",
9
10
  "preview": "bun build/server/entry.js",
10
- "prepare": "svelte-kit sync || echo ''",
11
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
- "gen:types": "donkeylabs generate"
11
+ "prepare": "bun --bun svelte-kit sync || echo ''",
12
+ "check": "bun --bun svelte-kit sync && bun --bun svelte-check --tsconfig ./tsconfig.json",
13
+ "gen:types": "donkeylabs generate",
14
+ "cli": "donkeylabs"
13
15
  },
14
16
  "devDependencies": {
15
- "@donkeylabs/cli": "*",
16
17
  "@sveltejs/kit": "^2.49.1",
17
18
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
18
19
  "@tailwindcss/vite": "^4.1.18",
@@ -23,8 +24,9 @@
23
24
  "vite": "^7.2.6"
24
25
  },
25
26
  "dependencies": {
26
- "@donkeylabs/adapter-sveltekit": "*",
27
- "@donkeylabs/server": "*",
27
+ "@donkeylabs/cli": "0.1.1",
28
+ "@donkeylabs/adapter-sveltekit": "0.1.2",
29
+ "@donkeylabs/server": "0.3.2",
28
30
  "bits-ui": "^2.15.4",
29
31
  "clsx": "^2.1.1",
30
32
  "kysely": "^0.27.6",
@@ -0,0 +1,55 @@
1
+ // Watch server files and regenerate types on changes
2
+ import { watch } from "node:fs";
3
+ import { exec } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { join } from "node:path";
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ const serverDir = join(import.meta.dir, "..", "src", "server");
10
+ let isGenerating = false;
11
+ let pendingGenerate = false;
12
+
13
+ async function regenerate() {
14
+ if (isGenerating) {
15
+ pendingGenerate = true;
16
+ return;
17
+ }
18
+
19
+ isGenerating = true;
20
+ console.log("\x1b[36m[watch]\x1b[0m Server files changed, regenerating types...");
21
+
22
+ try {
23
+ await execAsync("bun run gen:types");
24
+ console.log("\x1b[32m[watch]\x1b[0m Types regenerated successfully");
25
+ } catch (e: any) {
26
+ console.error("\x1b[31m[watch]\x1b[0m Error regenerating types:", e.message);
27
+ } finally {
28
+ isGenerating = false;
29
+ if (pendingGenerate) {
30
+ pendingGenerate = false;
31
+ await regenerate();
32
+ }
33
+ }
34
+ }
35
+
36
+ // Debounce to avoid multiple rapid regenerations
37
+ let debounceTimer: Timer | null = null;
38
+
39
+ function debouncedRegenerate() {
40
+ if (debounceTimer) clearTimeout(debounceTimer);
41
+ debounceTimer = setTimeout(regenerate, 300);
42
+ }
43
+
44
+ // Watch server directory recursively
45
+ watch(serverDir, { recursive: true }, (eventType, filename) => {
46
+ if (!filename) return;
47
+ if (!filename.endsWith(".ts")) return;
48
+
49
+ debouncedRegenerate();
50
+ });
51
+
52
+ console.log("\x1b[36m[watch]\x1b[0m Watching src/server/ for changes...");
53
+
54
+ // Keep process alive
55
+ await new Promise(() => {});
@@ -1,134 +1,248 @@
1
+ // Auto-generated by @donkeylabs/server
2
+ // DO NOT EDIT MANUALLY
3
+
4
+ import { UnifiedApiClientBase, type ClientOptions } from "@donkeylabs/adapter-sveltekit/client";
5
+
6
+ // Utility type that forces TypeScript to expand types on hover
7
+ type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
8
+
1
9
  /**
2
- * Generated API client for the demo
3
- * Extends UnifiedApiClientBase to handle SSR direct calls and browser HTTP calls
10
+ * Handler interface for implementing route handlers in model classes.
11
+ * @example
12
+ * class CounterModel implements Handler<Routes.Counter.get> {
13
+ * handle(input: Routes.Counter.get.Input): Routes.Counter.get.Output {
14
+ * return { count: 0 };
15
+ * }
16
+ * }
4
17
  */
5
- import { UnifiedApiClientBase } from "@donkeylabs/adapter-sveltekit/client";
18
+ export interface Handler<T extends { Input: any; Output: any }> {
19
+ handle(input: T["Input"]): T["Output"] | Promise<T["Output"]>;
20
+ }
6
21
 
7
- // Route type definitions
8
- export interface CounterResponse {
22
+ // Re-export server context for model classes
23
+ export { type ServerContext as AppContext } from "@donkeylabs/server";
24
+
25
+ // ============================================
26
+ // Route Types
27
+ // ============================================
28
+
29
+ export namespace Routes {
30
+ export namespace Counter {
31
+ export namespace get {
32
+ export type Input = Expand<Record<string, never>>;
33
+ export type Output = Expand<{
9
34
  count: number;
10
- }
35
+ }>;
36
+ }
11
37
 
12
- export interface CacheGetResponse {
38
+ export namespace increment {
39
+ export type Input = Expand<Record<string, never>>;
40
+ export type Output = Expand<{
41
+ count: number;
42
+ }>;
43
+ }
44
+
45
+ export namespace decrement {
46
+ export type Input = Expand<Record<string, never>>;
47
+ export type Output = Expand<{
48
+ count: number;
49
+ }>;
50
+ }
51
+
52
+ export namespace reset {
53
+ export type Input = Expand<Record<string, never>>;
54
+ export type Output = Expand<{
55
+ count: number;
56
+ }>;
57
+ }
58
+ }
59
+
60
+ export namespace Cache {
61
+ export namespace set {
62
+ export type Input = Expand<{
63
+ key: string;
13
64
  value: any;
65
+ ttl?: number;
66
+ }>;
67
+ export type Output = Expand<{
68
+ success: boolean;
69
+ }>;
70
+ }
71
+
72
+ export namespace get {
73
+ export type Input = Expand<{
74
+ key: string;
75
+ }>;
76
+ export type Output = Expand<{
77
+ value?: any;
14
78
  exists: boolean;
15
- }
16
-
17
- export interface CacheKeysResponse {
79
+ }>;
80
+ }
81
+
82
+ export namespace delete {
83
+ export type Input = Expand<{
84
+ key: string;
85
+ }>;
86
+ export type Output = Expand<{
87
+ success: boolean;
88
+ }>;
89
+ }
90
+
91
+ export namespace keys {
92
+ export type Input = Expand<Record<string, never>>;
93
+ export type Output = Expand<{
18
94
  keys: string[];
19
- size: number;
20
- }
95
+ }>;
96
+ }
97
+ }
21
98
 
22
- export interface JobEnqueueResponse {
99
+ export namespace Jobs {
100
+ export namespace enqueue {
101
+ export type Input = Expand<{
102
+ name: string;
103
+ data: any;
104
+ delay?: number;
105
+ }>;
106
+ export type Output = Expand<{
23
107
  jobId: string;
24
- }
108
+ }>;
109
+ }
25
110
 
26
- export interface JobStatsResponse {
111
+ export namespace stats {
112
+ export type Input = Expand<Record<string, never>>;
113
+ export type Output = Expand<{
27
114
  pending: number;
28
115
  running: number;
29
116
  completed: number;
30
- }
31
-
32
- export interface CronTask {
117
+ }>;
118
+ }
119
+ }
120
+
121
+ export namespace Cron {
122
+ export namespace list {
123
+ export type Input = Expand<Record<string, never>>;
124
+ export type Output = Expand<{
125
+ tasks: {
33
126
  id: string;
34
127
  name: string;
35
128
  expression: string;
36
129
  enabled: boolean;
37
130
  lastRun?: string;
38
131
  nextRun?: string;
39
- }
40
-
41
- export interface CronListResponse {
42
- tasks: CronTask[];
43
- }
44
-
45
- export interface RateLimitResult {
132
+ }[];
133
+ }>;
134
+ }
135
+ }
136
+
137
+ export namespace Ratelimit {
138
+ export namespace check {
139
+ export type Input = Expand<{
140
+ key: string;
141
+ limit: number;
142
+ window: number;
143
+ }>;
144
+ export type Output = Expand<{
46
145
  allowed: boolean;
47
146
  remaining: number;
48
- limit: number;
49
- resetAt: string;
50
- retryAfter?: number;
51
- }
52
-
53
- export interface SSEClientsResponse {
147
+ resetAt: Date;
148
+ }>;
149
+ }
150
+
151
+ export namespace reset {
152
+ export type Input = Expand<{
153
+ key: string;
154
+ }>;
155
+ export type Output = Expand<{
156
+ success: boolean;
157
+ }>;
158
+ }
159
+ }
160
+
161
+ export namespace Events {
162
+ export namespace emit {
163
+ export type Input = Expand<{
164
+ event: string;
165
+ data: any;
166
+ }>;
167
+ export type Output = Expand<{
168
+ success: boolean;
169
+ }>;
170
+ }
171
+ }
172
+
173
+ export namespace Sse {
174
+ export namespace broadcast {
175
+ export type Input = Expand<{
176
+ channel: string;
177
+ event: string;
178
+ data: any;
179
+ }>;
180
+ export type Output = Expand<{
181
+ success: boolean;
182
+ recipients: number;
183
+ }>;
184
+ }
185
+
186
+ export namespace clients {
187
+ export type Input = Expand<Record<string, never>>;
188
+ export type Output = Expand<{
54
189
  total: number;
55
190
  byChannel: number;
191
+ }>;
192
+ }
193
+ }
56
194
  }
57
195
 
58
- /**
59
- * Typed API client for the demo server
60
- */
196
+ // ============================================
197
+ // API Client
198
+ // ============================================
199
+
61
200
  export class ApiClient extends UnifiedApiClientBase {
62
- // Counter routes
201
+ constructor(options?: ClientOptions) {
202
+ super(options);
203
+ }
204
+
63
205
  counter = {
64
- get: () => this.request<{}, CounterResponse>("api.counter.get", {}),
65
- increment: () => this.request<{}, CounterResponse>("api.counter.increment", {}),
66
- decrement: () => this.request<{}, CounterResponse>("api.counter.decrement", {}),
67
- reset: () => this.request<{}, CounterResponse>("api.counter.reset", {}),
206
+ get: (input: Routes.Counter.get.Input): Promise<Routes.Counter.get.Output> => this.request("api.counter.get", input),
207
+ increment: (input: Routes.Counter.increment.Input): Promise<Routes.Counter.increment.Output> => this.request("api.counter.increment", input),
208
+ decrement: (input: Routes.Counter.decrement.Input): Promise<Routes.Counter.decrement.Output> => this.request("api.counter.decrement", input),
209
+ reset: (input: Routes.Counter.reset.Input): Promise<Routes.Counter.reset.Output> => this.request("api.counter.reset", input)
68
210
  };
69
211
 
70
- // Cache routes
71
212
  cache = {
72
- set: (input: { key: string; value: any; ttl?: number }) =>
73
- this.request<typeof input, { success: boolean }>("api.cache.set", input),
74
- get: (input: { key: string }) =>
75
- this.request<typeof input, CacheGetResponse>("api.cache.get", input),
76
- delete: (input: { key: string }) =>
77
- this.request<typeof input, { success: boolean }>("api.cache.delete", input),
78
- keys: () =>
79
- this.request<{}, CacheKeysResponse>("api.cache.keys", {}),
213
+ set: (input: Routes.Cache.set.Input): Promise<Routes.Cache.set.Output> => this.request("api.cache.set", input),
214
+ get: (input: Routes.Cache.get.Input): Promise<Routes.Cache.get.Output> => this.request("api.cache.get", input),
215
+ delete: (input: Routes.Cache.delete.Input): Promise<Routes.Cache.delete.Output> => this.request("api.cache.delete", input),
216
+ keys: (input: Routes.Cache.keys.Input): Promise<Routes.Cache.keys.Output> => this.request("api.cache.keys", input)
80
217
  };
81
218
 
82
- // Jobs routes
83
219
  jobs = {
84
- enqueue: (input: { name?: string; data?: any; delay?: number }) =>
85
- this.request<typeof input, JobEnqueueResponse>("api.jobs.enqueue", input),
86
- stats: () =>
87
- this.request<{}, JobStatsResponse>("api.jobs.stats", {}),
220
+ enqueue: (input: Routes.Jobs.enqueue.Input): Promise<Routes.Jobs.enqueue.Output> => this.request("api.jobs.enqueue", input),
221
+ stats: (input: Routes.Jobs.stats.Input): Promise<Routes.Jobs.stats.Output> => this.request("api.jobs.stats", input)
88
222
  };
89
223
 
90
- // Cron routes
91
224
  cron = {
92
- list: () =>
93
- this.request<{}, CronListResponse>("api.cron.list", {}),
225
+ list: (input: Routes.Cron.list.Input): Promise<Routes.Cron.list.Output> => this.request("api.cron.list", input)
94
226
  };
95
227
 
96
- // Rate limiter routes
97
228
  ratelimit = {
98
- check: (input: { key?: string; limit?: number; window?: number }) =>
99
- this.request<typeof input, RateLimitResult>("api.ratelimit.check", input),
100
- reset: (input: { key?: string }) =>
101
- this.request<typeof input, { success: boolean }>("api.ratelimit.reset", input),
229
+ check: (input: Routes.Ratelimit.check.Input): Promise<Routes.Ratelimit.check.Output> => this.request("api.ratelimit.check", input),
230
+ reset: (input: Routes.Ratelimit.reset.Input): Promise<Routes.Ratelimit.reset.Output> => this.request("api.ratelimit.reset", input)
102
231
  };
103
232
 
104
- // Events routes
105
233
  events = {
106
- emit: (input: { event?: string; data?: any }) =>
107
- this.request<typeof input, { success: boolean }>("api.events.emit", input),
234
+ emit: (input: Routes.Events.emit.Input): Promise<Routes.Events.emit.Output> => this.request("api.events.emit", input)
108
235
  };
109
236
 
110
- // SSE routes
111
- sseRoutes = {
112
- broadcast: (input: { channel?: string; event?: string; data: any }) =>
113
- this.request<typeof input, { success: boolean }>("api.sse.broadcast", input),
114
- clients: () =>
115
- this.request<{}, SSEClientsResponse>("api.sse.clients", {}),
237
+ sse = {
238
+ broadcast: (input: Routes.Sse.broadcast.Input): Promise<Routes.Sse.broadcast.Output> => this.request("api.sse.broadcast", input),
239
+ clients: (input: Routes.Sse.clients.Input): Promise<Routes.Sse.clients.Output> => this.request("api.sse.clients", input)
116
240
  };
117
241
  }
118
242
 
119
243
  /**
120
244
  * Create an API client instance
121
- *
122
- * @example
123
- * // In +page.server.ts (SSR - direct calls)
124
- * const api = createApi({ locals });
125
- * const data = await api.counter.get();
126
- *
127
- * @example
128
- * // In +page.svelte (browser - HTTP calls)
129
- * const api = createApi();
130
- * const data = await api.counter.get();
131
245
  */
132
- export function createApi(options?: { locals?: any }) {
246
+ export function createApi(options?: ClientOptions) {
133
247
  return new ApiClient(options);
134
248
  }
@@ -4,13 +4,13 @@ import { createApi } from '$lib/api';
4
4
 
5
5
  export const load: PageServerLoad = async ({ locals }) => {
6
6
  // Create API client with locals for direct SSR calls (no HTTP!)
7
- const api = createApi({ locals });
7
+ const client = createApi({ locals });
8
8
 
9
9
  try {
10
10
  // Direct service call through typed client
11
- const { count } = await api.counter.get();
11
+ const result = await client.counter.get({});
12
12
  return {
13
- count,
13
+ count: result.count,
14
14
  loadedAt: new Date().toISOString(),
15
15
  isSSR: true,
16
16
  };