@donkeylabs/cli 0.1.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/package.json +51 -0
  4. package/src/commands/generate.ts +585 -0
  5. package/src/commands/init.ts +201 -0
  6. package/src/commands/interactive.ts +223 -0
  7. package/src/commands/plugin.ts +205 -0
  8. package/src/index.ts +108 -0
  9. package/templates/starter/.env.example +3 -0
  10. package/templates/starter/.gitignore.template +4 -0
  11. package/templates/starter/CLAUDE.md +144 -0
  12. package/templates/starter/donkeylabs.config.ts +6 -0
  13. package/templates/starter/package.json +21 -0
  14. package/templates/starter/src/client.test.ts +7 -0
  15. package/templates/starter/src/db.ts +9 -0
  16. package/templates/starter/src/index.ts +48 -0
  17. package/templates/starter/src/plugins/stats/index.ts +105 -0
  18. package/templates/starter/src/routes/health/index.ts +5 -0
  19. package/templates/starter/src/routes/health/ping/index.ts +13 -0
  20. package/templates/starter/src/routes/health/ping/models/model.ts +23 -0
  21. package/templates/starter/src/routes/health/ping/schema.ts +14 -0
  22. package/templates/starter/src/routes/health/ping/tests/integ.test.ts +20 -0
  23. package/templates/starter/src/routes/health/ping/tests/unit.test.ts +21 -0
  24. package/templates/starter/src/test-ctx.ts +24 -0
  25. package/templates/starter/tsconfig.json +27 -0
  26. package/templates/sveltekit-app/.env.example +3 -0
  27. package/templates/sveltekit-app/README.md +103 -0
  28. package/templates/sveltekit-app/donkeylabs.config.ts +10 -0
  29. package/templates/sveltekit-app/package.json +36 -0
  30. package/templates/sveltekit-app/src/app.css +40 -0
  31. package/templates/sveltekit-app/src/app.html +12 -0
  32. package/templates/sveltekit-app/src/hooks.server.ts +4 -0
  33. package/templates/sveltekit-app/src/lib/api.ts +134 -0
  34. package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +30 -0
  35. package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +3 -0
  36. package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +48 -0
  37. package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +9 -0
  38. package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +18 -0
  39. package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +18 -0
  40. package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +18 -0
  41. package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +18 -0
  42. package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +18 -0
  43. package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +21 -0
  44. package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +21 -0
  45. package/templates/sveltekit-app/src/lib/components/ui/index.ts +4 -0
  46. package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +2 -0
  47. package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +20 -0
  48. package/templates/sveltekit-app/src/lib/utils/index.ts +6 -0
  49. package/templates/sveltekit-app/src/routes/+layout.svelte +8 -0
  50. package/templates/sveltekit-app/src/routes/+page.server.ts +25 -0
  51. package/templates/sveltekit-app/src/routes/+page.svelte +401 -0
  52. package/templates/sveltekit-app/src/server/index.ts +263 -0
  53. package/templates/sveltekit-app/static/robots.txt +3 -0
  54. package/templates/sveltekit-app/svelte.config.js +18 -0
  55. package/templates/sveltekit-app/tsconfig.json +25 -0
  56. package/templates/sveltekit-app/vite.config.ts +7 -0
@@ -0,0 +1,103 @@
1
+ # SvelteKit App Example
2
+
3
+ A complete example of using `@donkeylabs/adapter-sveltekit` to integrate @donkeylabs/server with SvelteKit.
4
+
5
+ ## Features Demonstrated
6
+
7
+ - **SSR Direct Calls** - API calls in `+page.server.ts` use direct function calls (no HTTP)
8
+ - **Browser HTTP Calls** - API calls in `+page.svelte` use fetch
9
+ - **Unified API Client** - Same interface for both SSR and browser
10
+ - **SSE (Server-Sent Events)** - Real-time updates from server to browser
11
+ - **All Core Services** - Cache, Jobs, Cron, Rate Limiter, Events, SSE
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/
17
+ ├── server/
18
+ │ └── index.ts # @donkeylabs/server setup (plugins, routes)
19
+ ├── lib/
20
+ │ ├── api.ts # Typed API client
21
+ │ └── components/ui/ # UI components (shadcn-svelte)
22
+ ├── routes/
23
+ │ ├── +page.server.ts # SSR data loading (direct calls)
24
+ │ └── +page.svelte # Client-side UI
25
+ └── hooks.server.ts # SvelteKit hooks
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ ```bash
31
+ # Install dependencies
32
+ bun install
33
+
34
+ # Development
35
+ bun run dev
36
+
37
+ # Build
38
+ bun run build
39
+
40
+ # Production
41
+ PORT=3000 bun build/server/entry.js
42
+ ```
43
+
44
+ ## Key Files
45
+
46
+ ### svelte.config.js
47
+
48
+ ```js
49
+ import adapter from '@donkeylabs/adapter-sveltekit';
50
+
51
+ export default {
52
+ kit: {
53
+ adapter: adapter({
54
+ serverEntry: './src/server/index.ts',
55
+ })
56
+ }
57
+ };
58
+ ```
59
+
60
+ ### src/server/index.ts
61
+
62
+ Defines the @donkeylabs/server with plugins and routes.
63
+
64
+ ### src/lib/api.ts
65
+
66
+ Typed API client extending `UnifiedApiClientBase`.
67
+
68
+ ### src/hooks.server.ts
69
+
70
+ ```ts
71
+ import { createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
72
+ export const handle = createHandle();
73
+ ```
74
+
75
+ ### src/routes/+page.server.ts
76
+
77
+ ```ts
78
+ import { createApi } from '$lib/api';
79
+
80
+ export const load = async ({ locals }) => {
81
+ const api = createApi({ locals }); // Direct calls
82
+ const data = await api.counter.get();
83
+ return { count: data.count };
84
+ };
85
+ ```
86
+
87
+ ### src/routes/+page.svelte
88
+
89
+ ```svelte
90
+ <script>
91
+ import { createApi } from '$lib/api';
92
+ const api = createApi(); // HTTP calls
93
+
94
+ // SSE subscription
95
+ api.sse.subscribe(['events'], (event, data) => {
96
+ // Handle real-time updates
97
+ });
98
+ </script>
99
+ ```
100
+
101
+ ## Documentation
102
+
103
+ See [docs/sveltekit-adapter.md](../../docs/sveltekit-adapter.md) for complete documentation.
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "@donkeylabs/server";
2
+
3
+ export default defineConfig({
4
+ plugins: ["./src/server/plugins/**/index.ts"],
5
+ outDir: ".@donkeylabs/server",
6
+ adapter: "@donkeylabs/adapter-sveltekit",
7
+ client: {
8
+ output: "./src/lib/api.ts",
9
+ },
10
+ });
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "my-sveltekit-app",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun --bun vite dev",
8
+ "build": "vite build",
9
+ "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"
13
+ },
14
+ "devDependencies": {
15
+ "@donkeylabs/cli": "*",
16
+ "@sveltejs/kit": "^2.49.1",
17
+ "@sveltejs/vite-plugin-svelte": "^6.2.1",
18
+ "@tailwindcss/vite": "^4.1.18",
19
+ "svelte": "^5.45.6",
20
+ "svelte-check": "^4.3.4",
21
+ "tailwindcss": "^4.1.18",
22
+ "typescript": "^5.9.3",
23
+ "vite": "^7.2.6"
24
+ },
25
+ "dependencies": {
26
+ "@donkeylabs/adapter-sveltekit": "*",
27
+ "@donkeylabs/server": "*",
28
+ "bits-ui": "^2.15.4",
29
+ "clsx": "^2.1.1",
30
+ "kysely": "^0.27.6",
31
+ "kysely-bun-sqlite": "^0.3.2",
32
+ "tailwind-merge": "^3.4.0",
33
+ "tailwind-variants": "^3.2.2",
34
+ "zod": "^3.24.0"
35
+ }
36
+ }
@@ -0,0 +1,40 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-background: oklch(100% 0 0);
5
+ --color-foreground: oklch(14.1% 0.005 285.82);
6
+ --color-card: oklch(100% 0 0);
7
+ --color-card-foreground: oklch(14.1% 0.005 285.82);
8
+ --color-popover: oklch(100% 0 0);
9
+ --color-popover-foreground: oklch(14.1% 0.005 285.82);
10
+ --color-primary: oklch(20.5% 0.006 285.88);
11
+ --color-primary-foreground: oklch(98.5% 0.002 247.86);
12
+ --color-secondary: oklch(96.8% 0.003 264.54);
13
+ --color-secondary-foreground: oklch(20.5% 0.006 285.88);
14
+ --color-muted: oklch(96.8% 0.003 264.54);
15
+ --color-muted-foreground: oklch(55.2% 0.016 285.94);
16
+ --color-accent: oklch(96.8% 0.003 264.54);
17
+ --color-accent-foreground: oklch(20.5% 0.006 285.88);
18
+ --color-destructive: oklch(57.7% 0.245 27.33);
19
+ --color-destructive-foreground: oklch(98.5% 0.002 247.86);
20
+ --color-border: oklch(91.4% 0.007 264.53);
21
+ --color-input: oklch(91.4% 0.007 264.53);
22
+ --color-ring: oklch(70.7% 0.022 261.33);
23
+ --color-success: oklch(72.3% 0.219 149.58);
24
+ --color-success-foreground: oklch(20.5% 0.006 285.88);
25
+ --color-warning: oklch(79.5% 0.184 86.05);
26
+ --color-warning-foreground: oklch(20.5% 0.006 285.88);
27
+ --radius-sm: 0.25rem;
28
+ --radius-md: 0.375rem;
29
+ --radius-lg: 0.5rem;
30
+ --radius-xl: 0.75rem;
31
+ }
32
+
33
+ @layer base {
34
+ * {
35
+ @apply border-border;
36
+ }
37
+ body {
38
+ @apply bg-background text-foreground;
39
+ }
40
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ %sveltekit.head%
8
+ </head>
9
+ <body data-sveltekit-preload-data="hover">
10
+ <div style="display: contents">%sveltekit.body%</div>
11
+ </body>
12
+ </html>
@@ -0,0 +1,4 @@
1
+ // SvelteKit hooks for @donkeylabs/adapter-sveltekit
2
+ import { createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
3
+
4
+ export const handle = createHandle();
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Generated API client for the demo
3
+ * Extends UnifiedApiClientBase to handle SSR direct calls and browser HTTP calls
4
+ */
5
+ import { UnifiedApiClientBase } from "@donkeylabs/adapter-sveltekit/client";
6
+
7
+ // Route type definitions
8
+ export interface CounterResponse {
9
+ count: number;
10
+ }
11
+
12
+ export interface CacheGetResponse {
13
+ value: any;
14
+ exists: boolean;
15
+ }
16
+
17
+ export interface CacheKeysResponse {
18
+ keys: string[];
19
+ size: number;
20
+ }
21
+
22
+ export interface JobEnqueueResponse {
23
+ jobId: string;
24
+ }
25
+
26
+ export interface JobStatsResponse {
27
+ pending: number;
28
+ running: number;
29
+ completed: number;
30
+ }
31
+
32
+ export interface CronTask {
33
+ id: string;
34
+ name: string;
35
+ expression: string;
36
+ enabled: boolean;
37
+ lastRun?: string;
38
+ nextRun?: string;
39
+ }
40
+
41
+ export interface CronListResponse {
42
+ tasks: CronTask[];
43
+ }
44
+
45
+ export interface RateLimitResult {
46
+ allowed: boolean;
47
+ remaining: number;
48
+ limit: number;
49
+ resetAt: string;
50
+ retryAfter?: number;
51
+ }
52
+
53
+ export interface SSEClientsResponse {
54
+ total: number;
55
+ byChannel: number;
56
+ }
57
+
58
+ /**
59
+ * Typed API client for the demo server
60
+ */
61
+ export class ApiClient extends UnifiedApiClientBase {
62
+ // Counter routes
63
+ 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", {}),
68
+ };
69
+
70
+ // Cache routes
71
+ 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", {}),
80
+ };
81
+
82
+ // Jobs routes
83
+ 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", {}),
88
+ };
89
+
90
+ // Cron routes
91
+ cron = {
92
+ list: () =>
93
+ this.request<{}, CronListResponse>("api.cron.list", {}),
94
+ };
95
+
96
+ // Rate limiter routes
97
+ 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),
102
+ };
103
+
104
+ // Events routes
105
+ events = {
106
+ emit: (input: { event?: string; data?: any }) =>
107
+ this.request<typeof input, { success: boolean }>("api.events.emit", input),
108
+ };
109
+
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", {}),
116
+ };
117
+ }
118
+
119
+ /**
120
+ * 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
+ */
132
+ export function createApi(options?: { locals?: any }) {
133
+ return new ApiClient(options);
134
+ }
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { BadgeVariant } from "./index";
4
+ import type { Snippet } from "svelte";
5
+ import type { HTMLAttributes } from "svelte/elements";
6
+
7
+ interface Props extends HTMLAttributes<HTMLDivElement> {
8
+ variant?: BadgeVariant;
9
+ class?: string;
10
+ children?: Snippet;
11
+ }
12
+
13
+ let { variant = "default", class: className, children, ...restProps }: Props = $props();
14
+
15
+ const baseStyles = "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2";
16
+
17
+ const variants: Record<BadgeVariant, string> = {
18
+ default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
19
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
20
+ destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
21
+ outline: "text-foreground",
22
+ success: "border-transparent bg-success text-success-foreground shadow hover:bg-success/80",
23
+ };
24
+ </script>
25
+
26
+ <div class={cn(baseStyles, variants[variant], className)} {...restProps}>
27
+ {#if children}
28
+ {@render children()}
29
+ {/if}
30
+ </div>
@@ -0,0 +1,3 @@
1
+ import Root from "./badge.svelte";
2
+ export type BadgeVariant = "default" | "secondary" | "destructive" | "outline" | "success";
3
+ export { Root, Root as Badge };
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { ButtonVariant, ButtonSize } from "./index";
4
+ import type { Snippet } from "svelte";
5
+ import type { HTMLButtonAttributes } from "svelte/elements";
6
+
7
+ interface Props extends HTMLButtonAttributes {
8
+ variant?: ButtonVariant;
9
+ size?: ButtonSize;
10
+ class?: string;
11
+ children?: Snippet;
12
+ }
13
+
14
+ let {
15
+ variant = "default",
16
+ size = "default",
17
+ class: className,
18
+ children,
19
+ ...restProps
20
+ }: Props = $props();
21
+
22
+ const baseStyles = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
23
+
24
+ const variants: Record<ButtonVariant, string> = {
25
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
26
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
27
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
28
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
29
+ ghost: "hover:bg-accent hover:text-accent-foreground",
30
+ link: "text-primary underline-offset-4 hover:underline",
31
+ };
32
+
33
+ const sizes: Record<ButtonSize, string> = {
34
+ default: "h-9 px-4 py-2",
35
+ sm: "h-8 rounded-md px-3 text-xs",
36
+ lg: "h-10 rounded-md px-8",
37
+ icon: "h-9 w-9",
38
+ };
39
+ </script>
40
+
41
+ <button
42
+ class={cn(baseStyles, variants[variant], sizes[size], className)}
43
+ {...restProps}
44
+ >
45
+ {#if children}
46
+ {@render children()}
47
+ {/if}
48
+ </button>
@@ -0,0 +1,9 @@
1
+ import Root from "./button.svelte";
2
+
3
+ export type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
4
+ export type ButtonSize = "default" | "sm" | "lg" | "icon";
5
+
6
+ export {
7
+ Root,
8
+ Root as Button,
9
+ };
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <div class={cn("p-6 pt-0", className)} {...restProps}>
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </div>
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLParagraphElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <p class={cn("text-sm text-muted-foreground", className)} {...restProps}>
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </p>
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <div class={cn("flex items-center p-6 pt-0", className)} {...restProps}>
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </div>
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <div class={cn("flex flex-col space-y-1.5 p-6", className)} {...restProps}>
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </div>
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLHeadingElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <h3 class={cn("font-semibold leading-none tracking-tight", className)} {...restProps}>
15
+ {#if children}
16
+ {@render children()}
17
+ {/if}
18
+ </h3>
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { Snippet } from "svelte";
4
+ import type { HTMLAttributes } from "svelte/elements";
5
+
6
+ interface Props extends HTMLAttributes<HTMLDivElement> {
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { class: className, children, ...restProps }: Props = $props();
12
+ </script>
13
+
14
+ <div
15
+ class={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
16
+ {...restProps}
17
+ >
18
+ {#if children}
19
+ {@render children()}
20
+ {/if}
21
+ </div>
@@ -0,0 +1,21 @@
1
+ import Root from "./card.svelte";
2
+ import Content from "./card-content.svelte";
3
+ import Description from "./card-description.svelte";
4
+ import Footer from "./card-footer.svelte";
5
+ import Header from "./card-header.svelte";
6
+ import Title from "./card-title.svelte";
7
+
8
+ export {
9
+ Root,
10
+ Content,
11
+ Description,
12
+ Footer,
13
+ Header,
14
+ Title,
15
+ Root as Card,
16
+ Content as CardContent,
17
+ Description as CardDescription,
18
+ Footer as CardFooter,
19
+ Header as CardHeader,
20
+ Title as CardTitle,
21
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./button";
2
+ export * from "./card";
3
+ export * from "./input";
4
+ export * from "./badge";
@@ -0,0 +1,2 @@
1
+ import Root from "./input.svelte";
2
+ export { Root, Root as Input };
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ import { cn } from "$lib/utils";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
4
+
5
+ interface Props extends HTMLInputAttributes {
6
+ class?: string;
7
+ value?: string | number;
8
+ }
9
+
10
+ let { class: className, value = $bindable(), ...restProps }: Props = $props();
11
+ </script>
12
+
13
+ <input
14
+ class={cn(
15
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
16
+ className
17
+ )}
18
+ bind:value
19
+ {...restProps}
20
+ />
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,8 @@
1
+ <script lang="ts">
2
+ import "../app.css";
3
+ import type { Snippet } from "svelte";
4
+
5
+ let { children }: { children: Snippet } = $props();
6
+ </script>
7
+
8
+ {@render children()}
@@ -0,0 +1,25 @@
1
+ // Test SSR direct service calls using the typed API client
2
+ import type { PageServerLoad } from './$types';
3
+ import { createApi } from '$lib/api';
4
+
5
+ export const load: PageServerLoad = async ({ locals }) => {
6
+ // Create API client with locals for direct SSR calls (no HTTP!)
7
+ const api = createApi({ locals });
8
+
9
+ try {
10
+ // Direct service call through typed client
11
+ const { count } = await api.counter.get();
12
+ return {
13
+ count,
14
+ loadedAt: new Date().toISOString(),
15
+ isSSR: true,
16
+ };
17
+ } catch (e) {
18
+ // Fallback if plugins not loaded yet
19
+ return {
20
+ count: 'N/A (plugins not loaded)',
21
+ loadedAt: new Date().toISOString(),
22
+ isSSR: true,
23
+ };
24
+ }
25
+ };