@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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/package.json +51 -0
- package/src/commands/generate.ts +585 -0
- package/src/commands/init.ts +201 -0
- package/src/commands/interactive.ts +223 -0
- package/src/commands/plugin.ts +205 -0
- package/src/index.ts +108 -0
- package/templates/starter/.env.example +3 -0
- package/templates/starter/.gitignore.template +4 -0
- package/templates/starter/CLAUDE.md +144 -0
- package/templates/starter/donkeylabs.config.ts +6 -0
- package/templates/starter/package.json +21 -0
- package/templates/starter/src/client.test.ts +7 -0
- package/templates/starter/src/db.ts +9 -0
- package/templates/starter/src/index.ts +48 -0
- package/templates/starter/src/plugins/stats/index.ts +105 -0
- package/templates/starter/src/routes/health/index.ts +5 -0
- package/templates/starter/src/routes/health/ping/index.ts +13 -0
- package/templates/starter/src/routes/health/ping/models/model.ts +23 -0
- package/templates/starter/src/routes/health/ping/schema.ts +14 -0
- package/templates/starter/src/routes/health/ping/tests/integ.test.ts +20 -0
- package/templates/starter/src/routes/health/ping/tests/unit.test.ts +21 -0
- package/templates/starter/src/test-ctx.ts +24 -0
- package/templates/starter/tsconfig.json +27 -0
- package/templates/sveltekit-app/.env.example +3 -0
- package/templates/sveltekit-app/README.md +103 -0
- package/templates/sveltekit-app/donkeylabs.config.ts +10 -0
- package/templates/sveltekit-app/package.json +36 -0
- package/templates/sveltekit-app/src/app.css +40 -0
- package/templates/sveltekit-app/src/app.html +12 -0
- package/templates/sveltekit-app/src/hooks.server.ts +4 -0
- package/templates/sveltekit-app/src/lib/api.ts +134 -0
- package/templates/sveltekit-app/src/lib/components/ui/badge/badge.svelte +30 -0
- package/templates/sveltekit-app/src/lib/components/ui/badge/index.ts +3 -0
- package/templates/sveltekit-app/src/lib/components/ui/button/button.svelte +48 -0
- package/templates/sveltekit-app/src/lib/components/ui/button/index.ts +9 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card-content.svelte +18 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card-description.svelte +18 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card-footer.svelte +18 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card-header.svelte +18 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card-title.svelte +18 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/card.svelte +21 -0
- package/templates/sveltekit-app/src/lib/components/ui/card/index.ts +21 -0
- package/templates/sveltekit-app/src/lib/components/ui/index.ts +4 -0
- package/templates/sveltekit-app/src/lib/components/ui/input/index.ts +2 -0
- package/templates/sveltekit-app/src/lib/components/ui/input/input.svelte +20 -0
- package/templates/sveltekit-app/src/lib/utils/index.ts +6 -0
- package/templates/sveltekit-app/src/routes/+layout.svelte +8 -0
- package/templates/sveltekit-app/src/routes/+page.server.ts +25 -0
- package/templates/sveltekit-app/src/routes/+page.svelte +401 -0
- package/templates/sveltekit-app/src/server/index.ts +263 -0
- package/templates/sveltekit-app/static/robots.txt +3 -0
- package/templates/sveltekit-app/svelte.config.js +18 -0
- package/templates/sveltekit-app/tsconfig.json +25 -0
- 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,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,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,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,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,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
|
+
};
|