@donkeylabs/cli 2.0.14 → 2.0.16

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 (85) hide show
  1. package/package.json +1 -1
  2. package/src/commands/config.ts +610 -0
  3. package/src/commands/deploy-enhanced.ts +354 -0
  4. package/src/commands/deploy.ts +204 -0
  5. package/src/commands/generate.ts +11 -13
  6. package/src/commands/init-enhanced.ts +1994 -0
  7. package/src/deployment/manager.ts +356 -0
  8. package/src/index.ts +47 -19
  9. package/templates/starter/.env.example +0 -44
  10. package/templates/starter/.gitignore.template +0 -4
  11. package/templates/starter/donkeylabs.config.ts +0 -6
  12. package/templates/starter/package.json +0 -21
  13. package/templates/starter/src/index.ts +0 -54
  14. package/templates/starter/src/plugins/stats/index.ts +0 -105
  15. package/templates/starter/src/routes/health/handlers/ping.ts +0 -22
  16. package/templates/starter/src/routes/health/index.ts +0 -19
  17. package/templates/starter/tsconfig.json +0 -27
  18. package/templates/sveltekit-app/.env.example +0 -59
  19. package/templates/sveltekit-app/README.md +0 -103
  20. package/templates/sveltekit-app/bun.lock +0 -683
  21. package/templates/sveltekit-app/donkeylabs.config.ts +0 -12
  22. package/templates/sveltekit-app/package.json +0 -38
  23. package/templates/sveltekit-app/src/app.css +0 -40
  24. package/templates/sveltekit-app/src/app.html +0 -12
  25. package/templates/sveltekit-app/src/hooks.server.ts +0 -4
  26. package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +0 -30
  27. package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +0 -3
  28. package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +0 -48
  29. package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +0 -9
  30. package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +0 -18
  31. package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +0 -18
  32. package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +0 -18
  33. package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +0 -18
  34. package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +0 -18
  35. package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +0 -21
  36. package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +0 -21
  37. package/templates/sveltekit-app/src/lib/components/ui/index.ts +0 -4
  38. package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +0 -2
  39. package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +0 -20
  40. package/templates/sveltekit-app/src/lib/permissions.ts +0 -213
  41. package/templates/sveltekit-app/src/lib/utils/index.ts +0 -6
  42. package/templates/sveltekit-app/src/routes/+layout.svelte +0 -8
  43. package/templates/sveltekit-app/src/routes/+page.server.ts +0 -25
  44. package/templates/sveltekit-app/src/routes/+page.svelte +0 -680
  45. package/templates/sveltekit-app/src/routes/workflows/+page.server.ts +0 -23
  46. package/templates/sveltekit-app/src/routes/workflows/+page.svelte +0 -522
  47. package/templates/sveltekit-app/src/server/events.ts +0 -28
  48. package/templates/sveltekit-app/src/server/index.ts +0 -124
  49. package/templates/sveltekit-app/src/server/plugins/auth/auth.test.ts +0 -377
  50. package/templates/sveltekit-app/src/server/plugins/auth/index.ts +0 -815
  51. package/templates/sveltekit-app/src/server/plugins/auth/migrations/001_create_users.ts +0 -25
  52. package/templates/sveltekit-app/src/server/plugins/auth/migrations/002_create_sessions.ts +0 -32
  53. package/templates/sveltekit-app/src/server/plugins/auth/migrations/003_create_refresh_tokens.ts +0 -33
  54. package/templates/sveltekit-app/src/server/plugins/auth/migrations/004_create_passkeys.ts +0 -60
  55. package/templates/sveltekit-app/src/server/plugins/auth/schema.ts +0 -65
  56. package/templates/sveltekit-app/src/server/plugins/demo/index.ts +0 -262
  57. package/templates/sveltekit-app/src/server/plugins/email/email.test.ts +0 -369
  58. package/templates/sveltekit-app/src/server/plugins/email/index.ts +0 -411
  59. package/templates/sveltekit-app/src/server/plugins/email/migrations/001_create_email_tokens.ts +0 -33
  60. package/templates/sveltekit-app/src/server/plugins/email/schema.ts +0 -24
  61. package/templates/sveltekit-app/src/server/plugins/permissions/index.ts +0 -1048
  62. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/001_create_tenants.ts +0 -63
  63. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/002_create_roles.ts +0 -90
  64. package/templates/sveltekit-app/src/server/plugins/permissions/migrations/003_create_resource_grants.ts +0 -50
  65. package/templates/sveltekit-app/src/server/plugins/permissions/permissions.test.ts +0 -566
  66. package/templates/sveltekit-app/src/server/plugins/permissions/schema.ts +0 -67
  67. package/templates/sveltekit-app/src/server/plugins/workflow-demo/index.ts +0 -198
  68. package/templates/sveltekit-app/src/server/routes/auth/auth.schemas.ts +0 -66
  69. package/templates/sveltekit-app/src/server/routes/auth/handlers/login.handler.ts +0 -18
  70. package/templates/sveltekit-app/src/server/routes/auth/handlers/logout.handler.ts +0 -16
  71. package/templates/sveltekit-app/src/server/routes/auth/handlers/me.handler.ts +0 -20
  72. package/templates/sveltekit-app/src/server/routes/auth/handlers/refresh.handler.ts +0 -17
  73. package/templates/sveltekit-app/src/server/routes/auth/handlers/register.handler.ts +0 -19
  74. package/templates/sveltekit-app/src/server/routes/auth/handlers/update-profile.handler.ts +0 -21
  75. package/templates/sveltekit-app/src/server/routes/auth/index.ts +0 -73
  76. package/templates/sveltekit-app/src/server/routes/demo.ts +0 -464
  77. package/templates/sveltekit-app/src/server/routes/example/example.schemas.ts +0 -22
  78. package/templates/sveltekit-app/src/server/routes/example/handlers/greet.handler.ts +0 -21
  79. package/templates/sveltekit-app/src/server/routes/example/index.ts +0 -28
  80. package/templates/sveltekit-app/src/server/routes/permissions/index.ts +0 -248
  81. package/templates/sveltekit-app/src/server/routes/tenants/index.ts +0 -339
  82. package/templates/sveltekit-app/static/robots.txt +0 -3
  83. package/templates/sveltekit-app/svelte.config.ts +0 -17
  84. package/templates/sveltekit-app/tsconfig.json +0 -20
  85. package/templates/sveltekit-app/vite.config.ts +0 -12
@@ -1,680 +0,0 @@
1
- <script lang="ts">
2
- import { browser } from "$app/environment";
3
- import { onMount } from "svelte";
4
- import { Button } from "$lib/components/ui/button";
5
- import type { Routes } from "$lib/api";
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardHeader,
11
- CardTitle,
12
- } from "$lib/components/ui/card";
13
- import { Input } from "$lib/components/ui/input";
14
- import { Badge } from "$lib/components/ui/badge";
15
- import { createApi } from "$lib/api";
16
-
17
- // Type for cron tasks returned by the API
18
- interface CronTask {
19
- id: string;
20
- name: string;
21
- expression: string;
22
- enabled: boolean;
23
- lastRun?: string;
24
- nextRun?: string;
25
- }
26
-
27
- let { data } = $props();
28
-
29
- // Create typed API client (browser mode - no locals)
30
- const client = createApi();
31
-
32
- // SSE Events state
33
- let events = $state<
34
- Array<{ id: number; message: string; timestamp: string; source?: string }>
35
- >([]);
36
- let sseConnected = $state(false);
37
- let sseClients = $state({ total: 0, byChannel: 0 });
38
-
39
- // Counter state
40
- let count = $state(data.count);
41
- let counterLoading = $state(false);
42
-
43
- // Cache state
44
- let cacheKey = $state("demo-key");
45
- let cacheValue = $state("Hello World");
46
- let cacheTTL = $state(30000);
47
- let cacheResult = $state<any>(null);
48
- let cacheKeys = $state<string[]>([]);
49
-
50
- // Jobs state
51
- let jobMessage = $state("Test job");
52
- let jobDelay = $state(0);
53
- let jobStats = $state({ pending: 0, running: 0, completed: 0 });
54
- let lastJobId = $state<string | null>(null);
55
-
56
- // Rate Limiter state
57
- let rateLimitKey = $state("demo");
58
- let rateLimitMax = $state(5);
59
- let rateLimitWindow = $state(60000);
60
- let rateLimitResult = $state<any>(null);
61
-
62
- // Cron state
63
- let cronTasks = $state<CronTask[]>([]);
64
-
65
- // Events (pub/sub) state
66
- let eventName = $state("demo.test");
67
- let eventData = $state('{"hello": "world"}');
68
-
69
- // Audit state
70
- let auditAction = $state("user.login");
71
- let auditResource = $state("user");
72
- let auditResourceId = $state("user-123");
73
- let auditEntries = $state<Array<{
74
- id: string;
75
- timestamp: string;
76
- action: string;
77
- actor: string;
78
- resource: string;
79
- resourceId?: string;
80
- metadata?: Record<string, any>;
81
- }>>([]);
82
- let lastAuditId = $state<string | null>(null);
83
-
84
- // WebSocket state
85
- let wsChannel = $state("demo");
86
- let wsMessage = $state("Hello from browser!");
87
- let wsClientCount = $state(0);
88
-
89
- // Counter actions - using typed client
90
- async function counterAction(
91
- action: "get" | "increment" | "decrement" | "reset",
92
- ) {
93
- counterLoading = true;
94
-
95
- const result = await client.api.counter[action]({});
96
- count = result.count;
97
- counterLoading = false;
98
- }
99
-
100
- // Cache actions - using typed client
101
- async function cacheSet() {
102
- await client.api.cache.set({ key: cacheKey, value: cacheValue, ttl: cacheTTL });
103
- cacheResult = { action: "set", success: true };
104
- refreshCacheKeys();
105
- }
106
-
107
- async function cacheGet() {
108
- cacheResult = await client.api.cache.get({ key: cacheKey });
109
- refreshCacheKeys();
110
- }
111
-
112
- async function cacheDelete() {
113
- await client.api.cache.delete({ key: cacheKey });
114
- cacheResult = { action: "deleted", success: true };
115
- refreshCacheKeys();
116
- }
117
-
118
- async function refreshCacheKeys() {
119
- const result = await client.api.cache.keys({});
120
- cacheKeys = result.keys || [];
121
- }
122
-
123
- // Jobs actions - using typed client
124
- async function enqueueJob() {
125
- const result = (await client.api.jobs.enqueue({
126
- name: "demo-job",
127
- data: { message: jobMessage },
128
- delay: jobDelay > 0 ? jobDelay : undefined,
129
- })) as { jobId: string };
130
- lastJobId = result.jobId;
131
- refreshJobStats();
132
- }
133
-
134
- async function refreshJobStats() {
135
- jobStats = (await client.api.jobs.stats({})) as {
136
- pending: number;
137
- running: number;
138
- completed: number;
139
- };
140
- }
141
-
142
- // Rate limiter actions - using typed client
143
- async function checkRateLimit() {
144
- rateLimitResult = await client.api.ratelimit.check({
145
- key: rateLimitKey,
146
- limit: rateLimitMax,
147
- window: rateLimitWindow,
148
- });
149
- }
150
-
151
- async function resetRateLimit() {
152
- await client.api.ratelimit.reset({ key: rateLimitKey });
153
- rateLimitResult = { reset: true, message: "Rate limit reset" };
154
- }
155
-
156
- // Cron actions - using typed client
157
- async function refreshCronTasks() {
158
- const result = (await client.api.cron.list({})) as { tasks: CronTask[] };
159
- cronTasks = result.tasks;
160
- }
161
-
162
- // Events (pub/sub) actions - using typed client
163
- async function emitEvent() {
164
- try {
165
- const parsedData = JSON.parse(eventData);
166
- await client.api.events.emit({ event: eventName, data: parsedData });
167
- } catch (e) {
168
- console.error("Invalid JSON:", e);
169
- }
170
- }
171
-
172
- // SSE actions - using typed client
173
- async function manualBroadcast() {
174
- await client.api.sse.broadcast({
175
- channel: "events",
176
- event: "manual",
177
- data: {
178
- id: Date.now(),
179
- message: "Manual broadcast!",
180
- timestamp: new Date().toISOString(),
181
- source: "manual",
182
- },
183
- });
184
- }
185
-
186
- async function refreshSSEClients() {
187
- sseClients = (await client.api.sse.clients({})) as {
188
- total: number;
189
- byChannel: number;
190
- };
191
- }
192
-
193
- // Audit actions - using typed client
194
- async function auditLogEntry() {
195
- const result = await client.api.audit.log({
196
- action: auditAction,
197
- resource: auditResource,
198
- resourceId: auditResourceId,
199
- metadata: { browser: true, timestamp: Date.now() },
200
- }) as { id: string };
201
- lastAuditId = result.id;
202
- refreshAuditEntries();
203
- }
204
-
205
- async function refreshAuditEntries() {
206
- const result = await client.api.audit.query({ limit: 10 }) as { entries: typeof auditEntries };
207
- auditEntries = result.entries;
208
- }
209
-
210
- // WebSocket actions - using typed client
211
- async function wsBroadcastMessage() {
212
- await client.api.websocket.broadcast({
213
- channel: wsChannel,
214
- event: "chat",
215
- data: {
216
- message: wsMessage,
217
- timestamp: new Date().toISOString(),
218
- },
219
- });
220
- }
221
-
222
- async function wsBroadcastAll() {
223
- await client.api.websocket.broadcastAll({
224
- event: "announcement",
225
- data: {
226
- message: wsMessage,
227
- timestamp: new Date().toISOString(),
228
- },
229
- });
230
- }
231
-
232
- async function refreshWsClients() {
233
- const result = await client.api.websocket.clientCount({}) as { count: number };
234
- wsClientCount = result.count;
235
- }
236
-
237
- onMount(() => {
238
- if (!browser) return;
239
-
240
- // Initial data fetches
241
- refreshCacheKeys();
242
- refreshJobStats();
243
- refreshCronTasks();
244
- refreshSSEClients();
245
- refreshAuditEntries();
246
- refreshWsClients();
247
-
248
- // SSE subscription using the typed client
249
- const unsubscribe = client.sse.subscribe(
250
- ["events"],
251
- (eventType, eventData) => {
252
- // Handle all event types
253
- if (
254
- ["cron-event", "job-completed", "internal-event", "manual"].includes(
255
- eventType,
256
- )
257
- ) {
258
- // Simple prepend - CSS handles animation via :first-child or key-based animation
259
- events = [{ ...eventData }, ...events].slice(0, 15);
260
-
261
- if (eventType === "job-completed") {
262
- refreshJobStats();
263
- }
264
- }
265
- },
266
- );
267
-
268
- // Track connection status
269
- const checkConnection = setInterval(() => {
270
- // The SSE subscribe auto-reconnects, so we just refresh clients
271
- refreshSSEClients().then(() => {
272
- sseConnected = sseClients.byChannel > 0;
273
- });
274
- refreshJobStats();
275
- }, 5000);
276
-
277
- // Set connected after initial subscribe
278
- setTimeout(() => {
279
- sseConnected = true;
280
- refreshSSEClients();
281
- }, 500);
282
-
283
- return () => {
284
- unsubscribe();
285
- clearInterval(checkConnection);
286
- };
287
- });
288
-
289
- function getSourceColor(
290
- source?: string,
291
- ): "default" | "secondary" | "destructive" | "outline" | "success" {
292
- switch (source) {
293
- case "cron":
294
- return "default";
295
- case "manual":
296
- return "secondary";
297
- case "events":
298
- return "outline";
299
- default:
300
- return "success";
301
- }
302
- }
303
-
304
- function getSourceLabel(source?: string) {
305
- switch (source) {
306
- case "cron":
307
- return "CRON";
308
- case "manual":
309
- return "MANUAL";
310
- case "events":
311
- return "PUB/SUB";
312
- default:
313
- return "JOB";
314
- }
315
- }
316
- </script>
317
-
318
- <div class="min-h-screen bg-background">
319
- <div class="container mx-auto max-w-7xl py-8 px-4">
320
- <!-- Header -->
321
- <div class="text-center mb-8">
322
- <h1 class="text-3xl font-bold tracking-tight">@donkeylabs/server Demo</h1>
323
- <p class="text-muted-foreground mt-2">
324
- SvelteKit Adapter — All Core Services
325
- </p>
326
- <div class="flex gap-2 justify-center mt-3">
327
- <Badge variant="outline">
328
- SSR: {data.isSSR ? "Yes" : "No"} | Loaded: {data.loadedAt}
329
- </Badge>
330
- <a href="/workflows">
331
- <Badge variant="default" class="cursor-pointer hover:bg-primary/90">
332
- Try Workflows Demo
333
- </Badge>
334
- </a>
335
- </div>
336
- </div>
337
-
338
- <!-- Grid of feature cards -->
339
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
340
- <!-- Counter / RPC Demo -->
341
- <Card>
342
- <CardHeader>
343
- <CardTitle>RPC Routes</CardTitle>
344
- <CardDescription
345
- >Type-safe API calls with Zod validation</CardDescription
346
- >
347
- </CardHeader>
348
- <CardContent>
349
- <div class="text-center py-4">
350
- <span class="text-5xl font-bold text-primary">{count}</span>
351
- </div>
352
- <div class="flex gap-2 justify-center">
353
- <Button
354
- variant="outline"
355
- size="icon"
356
- onclick={() => counterAction("decrement")}
357
- disabled={counterLoading}>−</Button
358
- >
359
- <Button
360
- variant="secondary"
361
- onclick={() => counterAction("get")}
362
- disabled={counterLoading}>Refresh</Button
363
- >
364
- <Button
365
- variant="outline"
366
- size="icon"
367
- onclick={() => counterAction("increment")}
368
- disabled={counterLoading}>+</Button
369
- >
370
- <Button
371
- variant="ghost"
372
- onclick={() => counterAction("reset")}
373
- disabled={counterLoading}>Reset</Button
374
- >
375
- </div>
376
- </CardContent>
377
- </Card>
378
-
379
- <!-- Cache Demo -->
380
- <Card>
381
- <CardHeader>
382
- <CardTitle>Cache</CardTitle>
383
- <CardDescription>In-memory caching with TTL support</CardDescription>
384
- </CardHeader>
385
- <CardContent class="space-y-3">
386
- <div class="flex gap-2">
387
- <Input bind:value={cacheKey} placeholder="Key" class="flex-1" />
388
- <Input bind:value={cacheValue} placeholder="Value" class="flex-1" />
389
- </div>
390
- <div class="flex gap-2">
391
- <Button onclick={cacheSet} size="sm">Set</Button>
392
- <Button onclick={cacheGet} size="sm" variant="secondary">Get</Button
393
- >
394
- <Button onclick={cacheDelete} size="sm" variant="outline"
395
- >Delete</Button
396
- >
397
- </div>
398
- {#if cacheResult}
399
- <pre
400
- class="text-xs bg-muted p-2 rounded-md overflow-auto">{JSON.stringify(
401
- cacheResult,
402
- null,
403
- 2,
404
- )}</pre>
405
- {/if}
406
- <p class="text-xs text-muted-foreground">
407
- Keys ({cacheKeys.length}): {cacheKeys.length > 0
408
- ? cacheKeys.join(", ")
409
- : "none"}
410
- </p>
411
- </CardContent>
412
- </Card>
413
-
414
- <!-- Jobs Demo -->
415
- <Card>
416
- <CardHeader>
417
- <CardTitle>Background Jobs</CardTitle>
418
- <CardDescription>Async job queue with optional delay</CardDescription>
419
- </CardHeader>
420
- <CardContent class="space-y-3">
421
- <div class="flex gap-2">
422
- <Input
423
- bind:value={jobMessage}
424
- placeholder="Job message"
425
- class="flex-1"
426
- />
427
- <Input
428
- bind:value={jobDelay}
429
- type="number"
430
- placeholder="Delay"
431
- class="w-20"
432
- />
433
- </div>
434
- <div class="flex gap-2">
435
- <Button onclick={enqueueJob} size="sm">Enqueue</Button>
436
- <Button onclick={refreshJobStats} size="sm" variant="outline"
437
- >Refresh</Button
438
- >
439
- </div>
440
- {#if lastJobId}
441
- <p class="text-xs text-muted-foreground">
442
- Last Job: <code class="bg-muted px-1 rounded">{lastJobId}</code>
443
- </p>
444
- {/if}
445
- <div class="flex gap-3 text-xs text-muted-foreground">
446
- <span>Pending: {jobStats.pending}</span>
447
- <span>Running: {jobStats.running}</span>
448
- <span>Done: {jobStats.completed}</span>
449
- </div>
450
- </CardContent>
451
- </Card>
452
-
453
- <!-- Rate Limiter Demo -->
454
- <Card>
455
- <CardHeader>
456
- <CardTitle>Rate Limiter</CardTitle>
457
- <CardDescription>Sliding window rate limiting</CardDescription>
458
- </CardHeader>
459
- <CardContent class="space-y-3">
460
- <div class="flex gap-2">
461
- <Input bind:value={rateLimitKey} placeholder="Key" class="flex-1" />
462
- <Input
463
- bind:value={rateLimitMax}
464
- type="number"
465
- placeholder="Limit"
466
- class="w-16"
467
- />
468
- <Input
469
- bind:value={rateLimitWindow}
470
- type="number"
471
- placeholder="Window"
472
- class="w-20"
473
- />
474
- </div>
475
- <div class="flex gap-2">
476
- <Button onclick={checkRateLimit} size="sm">Check</Button>
477
- <Button onclick={resetRateLimit} size="sm" variant="outline"
478
- >Reset</Button
479
- >
480
- </div>
481
- {#if rateLimitResult}
482
- <pre
483
- class="text-xs p-2 rounded-md overflow-auto {rateLimitResult.allowed ===
484
- false
485
- ? 'bg-destructive/10 text-destructive'
486
- : 'bg-muted'}">{JSON.stringify(rateLimitResult, null, 2)}</pre>
487
- {/if}
488
- </CardContent>
489
- </Card>
490
-
491
- <!-- Cron Demo -->
492
- <Card>
493
- <CardHeader>
494
- <CardTitle>Cron Jobs</CardTitle>
495
- <CardDescription
496
- >Scheduled tasks with cron expressions</CardDescription
497
- >
498
- </CardHeader>
499
- <CardContent class="space-y-3">
500
- <Button onclick={refreshCronTasks} size="sm" variant="outline"
501
- >Refresh Tasks</Button
502
- >
503
- {#if cronTasks.length > 0}
504
- <ul class="space-y-2">
505
- {#each cronTasks as task}
506
- <li class="flex items-center gap-2 text-sm">
507
- <span class="font-medium">{task.name}</span>
508
- <code class="text-xs bg-muted px-1 rounded"
509
- >{task.expression}</code
510
- >
511
- <Badge
512
- variant={task.enabled ? "success" : "secondary"}
513
- class="text-xs"
514
- >
515
- {task.enabled ? "Active" : "Paused"}
516
- </Badge>
517
- </li>
518
- {/each}
519
- </ul>
520
- {:else}
521
- <p class="text-sm text-muted-foreground italic">
522
- No scheduled tasks
523
- </p>
524
- {/if}
525
- </CardContent>
526
- </Card>
527
-
528
- <!-- Events (Pub/Sub) Demo -->
529
- <Card>
530
- <CardHeader>
531
- <CardTitle>Events (Pub/Sub)</CardTitle>
532
- <CardDescription>Internal event system with wildcards</CardDescription
533
- >
534
- </CardHeader>
535
- <CardContent class="space-y-3">
536
- <Input
537
- bind:value={eventName}
538
- placeholder="Event name (e.g., demo.test)"
539
- />
540
- <textarea
541
- bind:value={eventData}
542
- placeholder="JSON data"
543
- rows="2"
544
- class="flex w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
545
- ></textarea>
546
- <Button onclick={emitEvent} size="sm">Emit Event</Button>
547
- <p class="text-xs text-muted-foreground italic">
548
- Events matching "demo.*" broadcast to SSE
549
- </p>
550
- </CardContent>
551
- </Card>
552
-
553
- <!-- Audit Demo -->
554
- <Card>
555
- <CardHeader>
556
- <CardTitle>Audit Trail</CardTitle>
557
- <CardDescription>Compliance logging and tracking</CardDescription>
558
- </CardHeader>
559
- <CardContent class="space-y-3">
560
- <div class="flex gap-2">
561
- <Input bind:value={auditAction} placeholder="Action" class="flex-1" />
562
- <Input bind:value={auditResource} placeholder="Resource" class="flex-1" />
563
- </div>
564
- <Input bind:value={auditResourceId} placeholder="Resource ID (optional)" />
565
- <div class="flex gap-2">
566
- <Button onclick={auditLogEntry} size="sm">Log Entry</Button>
567
- <Button onclick={refreshAuditEntries} size="sm" variant="outline">Refresh</Button>
568
- </div>
569
- {#if lastAuditId}
570
- <p class="text-xs text-muted-foreground">
571
- Last: <code class="bg-muted px-1 rounded">{lastAuditId}</code>
572
- </p>
573
- {/if}
574
- {#if auditEntries.length > 0}
575
- <div class="max-h-32 overflow-y-auto space-y-1">
576
- {#each auditEntries.slice(0, 5) as entry}
577
- <div class="text-xs p-2 bg-muted rounded flex justify-between items-center">
578
- <span class="font-medium">{entry.action}</span>
579
- <span class="text-muted-foreground">{entry.resource}</span>
580
- <span class="text-muted-foreground">{new Date(entry.timestamp).toLocaleTimeString()}</span>
581
- </div>
582
- {/each}
583
- </div>
584
- {:else}
585
- <p class="text-xs text-muted-foreground italic">No audit entries yet</p>
586
- {/if}
587
- </CardContent>
588
- </Card>
589
-
590
- <!-- WebSocket Demo -->
591
- <Card>
592
- <CardHeader>
593
- <CardTitle>WebSocket</CardTitle>
594
- <CardDescription>Bidirectional real-time messaging</CardDescription>
595
- </CardHeader>
596
- <CardContent class="space-y-3">
597
- <div class="flex gap-2">
598
- <Input bind:value={wsChannel} placeholder="Channel" class="w-28" />
599
- <Input bind:value={wsMessage} placeholder="Message" class="flex-1" />
600
- </div>
601
- <div class="flex gap-2">
602
- <Button onclick={wsBroadcastMessage} size="sm">Broadcast</Button>
603
- <Button onclick={wsBroadcastAll} size="sm" variant="secondary">Broadcast All</Button>
604
- <Button onclick={refreshWsClients} size="sm" variant="outline">Refresh</Button>
605
- </div>
606
- <div class="flex items-center gap-2">
607
- <span class="relative flex h-2 w-2">
608
- <span class="relative inline-flex rounded-full h-2 w-2 {wsClientCount > 0 ? 'bg-green-500' : 'bg-gray-400'}"></span>
609
- </span>
610
- <span class="text-xs text-muted-foreground">
611
- {wsClientCount} WebSocket client{wsClientCount !== 1 ? 's' : ''} connected
612
- </span>
613
- </div>
614
- <p class="text-xs text-muted-foreground italic">
615
- Messages logged to audit trail automatically
616
- </p>
617
- </CardContent>
618
- </Card>
619
- </div>
620
-
621
- <!-- SSE Events Stream - Full Width -->
622
- <Card>
623
- <CardHeader class="flex flex-row items-center justify-between">
624
- <div>
625
- <CardTitle>Live Events (SSE)</CardTitle>
626
- <CardDescription>Real-time server → client push</CardDescription>
627
- </div>
628
- <div class="flex items-center gap-2">
629
- <span class="relative flex h-3 w-3">
630
- {#if sseConnected}
631
- <span
632
- class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
633
- ></span>
634
- <span
635
- class="relative inline-flex rounded-full h-3 w-3 bg-green-500"
636
- ></span>
637
- {:else}
638
- <span
639
- class="relative inline-flex rounded-full h-3 w-3 bg-gray-400"
640
- ></span>
641
- {/if}
642
- </span>
643
- <span class="text-sm text-muted-foreground">
644
- {sseConnected ? "Connected" : "Disconnected"} ({sseClients.byChannel}
645
- clients)
646
- </span>
647
- </div>
648
- </CardHeader>
649
- <CardContent>
650
- <div class="flex gap-2 mb-4">
651
- <Button onclick={manualBroadcast} size="sm">Manual Broadcast</Button>
652
- <Button onclick={refreshSSEClients} size="sm" variant="outline"
653
- >Refresh Clients</Button
654
- >
655
- </div>
656
- {#if events.length === 0}
657
- <p class="text-sm text-muted-foreground italic">
658
- Waiting for events... (cron broadcasts every 5s)
659
- </p>
660
- {:else}
661
- <ul class="space-y-2 max-h-80 overflow-y-auto">
662
- {#each events as event}
663
- <li
664
- class="flex items-center gap-3 p-3 rounded-lg border bg-muted/50 animate-in slide-in-from-left-2 duration-300"
665
- >
666
- <Badge variant={getSourceColor(event.source)}
667
- >{getSourceLabel(event.source)}</Badge
668
- >
669
- <span class="flex-1 text-sm font-medium">{event.message}</span>
670
- <span class="text-xs text-muted-foreground"
671
- >{new Date(event.timestamp).toLocaleTimeString()}</span
672
- >
673
- </li>
674
- {/each}
675
- </ul>
676
- {/if}
677
- </CardContent>
678
- </Card>
679
- </div>
680
- </div>
@@ -1,23 +0,0 @@
1
- // Workflow demo page - SSR load
2
- import type { PageServerLoad } from "./$types";
3
- import { createApi } from "$lib/api";
4
-
5
- export const load: PageServerLoad = async ({ locals }) => {
6
- const client = createApi({ locals });
7
-
8
- try {
9
- // Load initial workflow instances
10
- const result = await (client as any).demo.workflow.list({});
11
- return {
12
- instances: result.instances || [],
13
- loadedAt: new Date().toISOString(),
14
- isSSR: true,
15
- };
16
- } catch (e) {
17
- return {
18
- instances: [],
19
- loadedAt: new Date().toISOString(),
20
- isSSR: true,
21
- };
22
- }
23
- };