@donkeylabs/server 0.1.1 → 0.1.2

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.
@@ -0,0 +1,324 @@
1
+ # Svelte 5 Frontend
2
+
3
+ Guide for building Svelte 5 frontends with the generated API client.
4
+
5
+ ## Setup
6
+
7
+ ### Create SvelteKit App
8
+
9
+ ```sh
10
+ bunx sv create frontend
11
+ cd frontend
12
+ bun install
13
+ ```
14
+
15
+ ### Generate API Client
16
+
17
+ From your server project:
18
+
19
+ ```sh
20
+ bun run gen:client --output ../frontend/src/lib/api
21
+ ```
22
+
23
+ This creates `frontend/src/lib/api/index.ts` with typed methods.
24
+
25
+ ---
26
+
27
+ ## Basic Usage
28
+
29
+ ### Initialize Client
30
+
31
+ ```ts
32
+ // src/lib/api.ts
33
+ import { createApiClient } from "./api";
34
+
35
+ export const api = createApiClient({
36
+ baseUrl: "http://localhost:3000",
37
+ });
38
+ ```
39
+
40
+ ### Use in Components
41
+
42
+ ```svelte
43
+ <script lang="ts">
44
+ import { api } from "$lib/api";
45
+
46
+ let items = $state<Item[]>([]);
47
+ let loading = $state(true);
48
+ let error = $state<string | null>(null);
49
+
50
+ async function load() {
51
+ try {
52
+ const result = await api.items.list({});
53
+ items = result.items;
54
+ } catch (e) {
55
+ error = e instanceof Error ? e.message : "Failed to load";
56
+ } finally {
57
+ loading = false;
58
+ }
59
+ }
60
+
61
+ $effect(() => {
62
+ load();
63
+ });
64
+ </script>
65
+
66
+ {#if loading}
67
+ <p>Loading...</p>
68
+ {:else if error}
69
+ <p class="error">{error}</p>
70
+ {:else}
71
+ <ul>
72
+ {#each items as item (item.id)}
73
+ <li>{item.name}</li>
74
+ {/each}
75
+ </ul>
76
+ {/if}
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Svelte 5 Patterns
82
+
83
+ ### State with $state
84
+
85
+ ```svelte
86
+ <script lang="ts">
87
+ // Reactive state
88
+ let count = $state(0);
89
+ let user = $state<User | null>(null);
90
+ let items = $state<Item[]>([]);
91
+
92
+ // Derived values
93
+ let doubled = $derived(count * 2);
94
+ let hasItems = $derived(items.length > 0);
95
+ </script>
96
+ ```
97
+
98
+ ### Effects with $effect
99
+
100
+ ```svelte
101
+ <script lang="ts">
102
+ let id = $state(1);
103
+
104
+ // Runs when id changes
105
+ $effect(() => {
106
+ loadUser(id);
107
+ });
108
+
109
+ // Cleanup pattern
110
+ $effect(() => {
111
+ const interval = setInterval(refresh, 5000);
112
+ return () => clearInterval(interval);
113
+ });
114
+ </script>
115
+ ```
116
+
117
+ ### Form Handling
118
+
119
+ ```svelte
120
+ <script lang="ts">
121
+ let title = $state("");
122
+ let submitting = $state(false);
123
+
124
+ async function handleSubmit(e: SubmitEvent) {
125
+ e.preventDefault();
126
+ if (!title.trim()) return;
127
+
128
+ submitting = true;
129
+ try {
130
+ await api.items.create({ title });
131
+ title = "";
132
+ await load(); // Refresh list
133
+ } catch (e) {
134
+ error = e instanceof Error ? e.message : "Failed to create";
135
+ } finally {
136
+ submitting = false;
137
+ }
138
+ }
139
+ </script>
140
+
141
+ <form onsubmit={handleSubmit}>
142
+ <input bind:value={title} placeholder="Title" />
143
+ <button disabled={submitting}>
144
+ {submitting ? "Creating..." : "Create"}
145
+ </button>
146
+ </form>
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Real-time Updates (SSE)
152
+
153
+ ### Connect on Mount
154
+
155
+ ```svelte
156
+ <script lang="ts">
157
+ import { api } from "$lib/api";
158
+ import { onMount } from "svelte";
159
+
160
+ let items = $state<Item[]>([]);
161
+
162
+ onMount(() => {
163
+ // Connect to SSE
164
+ api.connect();
165
+
166
+ // Listen for updates
167
+ const unsubscribe = api.on("items.changed", (data) => {
168
+ if (data.action === "created") {
169
+ items = [data.item, ...items];
170
+ } else if (data.action === "deleted") {
171
+ items = items.filter((i) => i.id !== data.item.id);
172
+ }
173
+ });
174
+
175
+ // Cleanup
176
+ return () => {
177
+ unsubscribe();
178
+ api.disconnect();
179
+ };
180
+ });
181
+ </script>
182
+ ```
183
+
184
+ ### Connection Status
185
+
186
+ ```svelte
187
+ <script lang="ts">
188
+ let connected = $state(false);
189
+
190
+ onMount(() => {
191
+ api.connect({
192
+ onConnect: () => (connected = true),
193
+ onDisconnect: () => (connected = false),
194
+ });
195
+
196
+ return () => api.disconnect();
197
+ });
198
+ </script>
199
+
200
+ <span class={connected ? "online" : "offline"}>
201
+ {connected ? "Connected" : "Disconnected"}
202
+ </span>
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Error Handling
208
+
209
+ ```svelte
210
+ <script lang="ts">
211
+ import { ApiError, ValidationError } from "$lib/api";
212
+
213
+ async function save() {
214
+ try {
215
+ await api.items.create({ title });
216
+ } catch (e) {
217
+ if (e instanceof ValidationError) {
218
+ // Show field errors
219
+ errors = e.details; // [{ path: ["title"], message: "Required" }]
220
+ } else if (e instanceof ApiError) {
221
+ if (e.status === 404) {
222
+ error = "Not found";
223
+ } else if (e.status === 401) {
224
+ goto("/login");
225
+ } else {
226
+ error = e.body?.message || "Server error";
227
+ }
228
+ }
229
+ }
230
+ }
231
+ </script>
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Complete Example
237
+
238
+ ```svelte
239
+ <!-- src/routes/+page.svelte -->
240
+ <script lang="ts">
241
+ import { api, type Item } from "$lib/api";
242
+ import { onMount } from "svelte";
243
+
244
+ let items = $state<Item[]>([]);
245
+ let loading = $state(true);
246
+ let error = $state<string | null>(null);
247
+ let newTitle = $state("");
248
+
249
+ async function load() {
250
+ try {
251
+ const result = await api.items.list({});
252
+ items = result.items;
253
+ } catch (e) {
254
+ error = e instanceof Error ? e.message : "Failed to load";
255
+ } finally {
256
+ loading = false;
257
+ }
258
+ }
259
+
260
+ async function create() {
261
+ if (!newTitle.trim()) return;
262
+ try {
263
+ await api.items.create({ title: newTitle });
264
+ newTitle = "";
265
+ await load();
266
+ } catch (e) {
267
+ error = e instanceof Error ? e.message : "Failed to create";
268
+ }
269
+ }
270
+
271
+ async function remove(id: number) {
272
+ try {
273
+ await api.items.delete({ id });
274
+ items = items.filter((i) => i.id !== id);
275
+ } catch (e) {
276
+ error = e instanceof Error ? e.message : "Failed to delete";
277
+ }
278
+ }
279
+
280
+ onMount(() => {
281
+ load();
282
+ });
283
+ </script>
284
+
285
+ <main>
286
+ <h1>Items</h1>
287
+
288
+ {#if error}
289
+ <p class="error">{error}</p>
290
+ {/if}
291
+
292
+ <form onsubmit={(e) => { e.preventDefault(); create(); }}>
293
+ <input bind:value={newTitle} placeholder="New item" />
294
+ <button>Add</button>
295
+ </form>
296
+
297
+ {#if loading}
298
+ <p>Loading...</p>
299
+ {:else}
300
+ <ul>
301
+ {#each items as item (item.id)}
302
+ <li>
303
+ {item.title}
304
+ <button onclick={() => remove(item.id)}>Delete</button>
305
+ </li>
306
+ {/each}
307
+ </ul>
308
+ {/if}
309
+ </main>
310
+
311
+ <style>
312
+ .error { color: red; }
313
+ </style>
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Guidelines
319
+
320
+ - **Keep components focused** - one concern per component
321
+ - **Handle loading states** - always show loading/error feedback
322
+ - **Clean up on unmount** - disconnect SSE, clear intervals
323
+ - **Use $derived for computed values** - not $state
324
+ - **Type your state** - `$state<Type[]>([])` not `$state([])`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
6
  "main": "./src/index.ts",
@@ -27,9 +27,10 @@
27
27
  "src",
28
28
  "cli",
29
29
  "mcp",
30
+ "docs",
30
31
  "templates",
31
- "cli/donkeylabs",
32
- "mcp/donkeylabs-mcp"
32
+ "context.d.ts",
33
+ "registry.d.ts"
33
34
  ],
34
35
  "scripts": {
35
36
  "gen:registry": "bun scripts/generate-registry.ts",
@@ -45,18 +46,20 @@
45
46
  "devDependencies": {
46
47
  "@types/bun": "latest",
47
48
  "@types/prompts": "^2.4.9",
48
- "kysely-codegen": "^0.19.0"
49
+ "kysely": "^0.27.6",
50
+ "kysely-bun-sqlite": "^0.3.2",
51
+ "kysely-codegen": "^0.19.0",
52
+ "zod": "^3.24.0"
49
53
  },
50
54
  "peerDependencies": {
51
- "typescript": "^5"
55
+ "typescript": "^5",
56
+ "kysely": "^0.27.0 || ^0.28.0",
57
+ "zod": "^3.20.0"
52
58
  },
53
59
  "dependencies": {
54
60
  "@modelcontextprotocol/sdk": "^1.25.2",
55
- "kysely": "^0.28.9",
56
- "kysely-bun-sqlite": "^0.4.0",
57
61
  "picocolors": "^1.1.1",
58
- "prompts": "^2.4.2",
59
- "zod": "^4.3.5"
62
+ "prompts": "^2.4.2"
60
63
  },
61
64
  "keywords": [
62
65
  "api",
package/registry.d.ts ADDED
@@ -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 {};
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ export {
22
22
  } from "./core";
23
23
 
24
24
  // Middleware
25
- export { createMiddleware, composeMiddleware } from "./middleware";
25
+ export { createMiddleware } from "./middleware";
26
26
 
27
27
  // Config helper
28
28
  export function defineConfig(config: {
package/src/server.ts CHANGED
@@ -190,6 +190,7 @@ export class AppServer {
190
190
  plugins: this.manager.getServices(),
191
191
  core: this.coreServices,
192
192
  errors: this.coreServices.errors,
193
+ config: this.coreServices.config,
193
194
  ip,
194
195
  requestId: crypto.randomUUID(),
195
196
  };