bosbun 0.0.5 → 0.0.7

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/README.md +12 -12
  2. package/package.json +5 -5
  3. package/src/cli/add.ts +5 -5
  4. package/src/cli/create.ts +67 -7
  5. package/src/cli/feat.ts +5 -5
  6. package/src/cli/index.ts +18 -17
  7. package/src/cli/start.ts +2 -4
  8. package/src/core/build.ts +17 -17
  9. package/src/core/client/App.svelte +12 -12
  10. package/src/core/client/hydrate.ts +7 -7
  11. package/src/core/client/prefetch.ts +8 -8
  12. package/src/core/client/router.svelte.ts +1 -1
  13. package/src/core/dev.ts +8 -7
  14. package/src/core/env.ts +1 -1
  15. package/src/core/envCodegen.ts +20 -20
  16. package/src/core/hooks.ts +2 -2
  17. package/src/core/html.ts +20 -20
  18. package/src/core/paths.ts +41 -0
  19. package/src/core/plugin.ts +48 -14
  20. package/src/core/prerender.ts +3 -2
  21. package/src/core/renderer.ts +3 -3
  22. package/src/core/routeFile.ts +6 -6
  23. package/src/core/routeTypes.ts +10 -10
  24. package/src/core/server.ts +4 -4
  25. package/src/core/types.ts +1 -1
  26. package/src/lib/index.ts +3 -3
  27. package/templates/default/.env.example +6 -6
  28. package/templates/default/README.md +3 -3
  29. package/templates/default/package.json +4 -4
  30. package/templates/default/public/favicon.svg +14 -0
  31. package/templates/default/src/routes/+page.svelte +4 -4
  32. package/templates/default/tsconfig.json +1 -1
  33. package/templates/demo/.env.example +52 -0
  34. package/templates/demo/README.md +23 -0
  35. package/templates/demo/package.json +20 -0
  36. package/templates/demo/public/.gitkeep +0 -0
  37. package/templates/demo/public/favicon.svg +14 -0
  38. package/templates/demo/src/app.css +132 -0
  39. package/templates/demo/src/app.d.ts +7 -0
  40. package/templates/demo/src/hooks.server.ts +21 -0
  41. package/templates/demo/src/lib/utils.ts +1 -0
  42. package/templates/demo/src/routes/(public)/+layout.svelte +31 -0
  43. package/templates/demo/src/routes/(public)/+page.svelte +79 -0
  44. package/templates/demo/src/routes/(public)/about/+page.server.ts +1 -0
  45. package/templates/demo/src/routes/(public)/about/+page.svelte +31 -0
  46. package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +38 -0
  47. package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -0
  48. package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +62 -0
  49. package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +53 -0
  50. package/templates/demo/src/routes/+error.svelte +15 -0
  51. package/templates/demo/src/routes/+layout.server.ts +10 -0
  52. package/templates/demo/src/routes/+layout.svelte +6 -0
  53. package/templates/demo/src/routes/actions-test/+page.server.ts +28 -0
  54. package/templates/demo/src/routes/actions-test/+page.svelte +60 -0
  55. package/templates/demo/src/routes/api/hello/+server.ts +44 -0
  56. package/templates/demo/tsconfig.json +22 -0
@@ -0,0 +1,62 @@
1
+ import type { LoadEvent, MetadataEvent } from "bosbun";
2
+
3
+ const posts: Record<string, { title: string; date: string; tags: string[]; content: string }> = {
4
+ "hello-world": {
5
+ title: "Hello, World!",
6
+ date: "2026-03-05",
7
+ tags: ["intro", "bosbun"],
8
+ content: `Welcome to Bosbun! This page was loaded by a +page.server.ts file.
9
+
10
+ The slug param was extracted from the URL by the route matcher and passed to the load() function as params.slug.
11
+
12
+ This is standard SvelteKit-compatible server loading.`,
13
+ },
14
+ "route-groups": {
15
+ title: "Route Groups Explained",
16
+ date: "2026-03-04",
17
+ tags: ["routing", "layouts"],
18
+ content: `Route groups like (public), (auth), and (admin) are directory names that are invisible in the URL.
19
+
20
+ They let you share layouts across a set of routes without adding a URL segment. A directory named (public) applies its +layout.svelte to all routes inside it, but /public never appears in the browser URL.
21
+
22
+ This page lives at routes/(public)/blog/[slug]/+page.svelte but is served at /blog/route-groups.`,
23
+ },
24
+ "dynamic-params": {
25
+ title: "Dynamic Params with [slug]",
26
+ date: "2026-03-03",
27
+ tags: ["routing", "dynamic"],
28
+ content: `A directory named [slug] creates a dynamic route segment that matches any URL value.
29
+
30
+ The matched value is available as params.slug inside +page.server.ts load() and inside the page component via data.params.slug.
31
+
32
+ The route matcher uses 3-pass priority: exact matches first, then dynamic segments, then catch-all routes.`,
33
+ },
34
+ };
35
+
36
+ export function metadata({ params }: MetadataEvent) {
37
+ // In production this would be a DB query for the post
38
+ const post = posts[params.slug] ?? null;
39
+ return {
40
+ title: post ? `${post.title} — Blog` : `Post not found`,
41
+ description: post ? `A blog post about ${params.slug}` : undefined,
42
+ meta: post
43
+ ? [{ property: "og:title", content: post.title }]
44
+ : [],
45
+ // Pass fetched post to load() — avoids duplicate query
46
+ data: { post },
47
+ };
48
+ }
49
+
50
+ export async function load({ params, parent, metadata }: LoadEvent) {
51
+ // parent() gives us data from +layout.server.ts (appName, requestTime)
52
+ const parentData = await parent();
53
+
54
+ // Reuse post from metadata() — no duplicate DB query
55
+ const post = metadata?.post ?? posts[params.slug] ?? null;
56
+
57
+ return {
58
+ post,
59
+ slug: params.slug,
60
+ appName: parentData.appName as string,
61
+ };
62
+ }
@@ -0,0 +1,53 @@
1
+ <script lang="ts">
2
+ import type { PageData } from './$types';
3
+
4
+ let { data }: { data: PageData } = $props();
5
+
6
+ const post = $derived(data.post);
7
+ const slug = $derived(data.slug ?? data.params.slug ?? "");
8
+ </script>
9
+
10
+ <svelte:head>
11
+ <title>{post ? post.title : "Post Not Found"} | {{PROJECT_NAME}}</title>
12
+ </svelte:head>
13
+
14
+ {#if post}
15
+ <article class="space-y-6 max-w-2xl">
16
+ <a href="/blog" class="text-sm text-muted-foreground hover:text-foreground transition-colors">← Blog</a>
17
+
18
+ <div class="space-y-2">
19
+ <div class="flex flex-wrap gap-1">
20
+ {#each post.tags as tag}
21
+ <span class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">{tag}</span>
22
+ {/each}
23
+ </div>
24
+ <h1 class="text-4xl font-bold tracking-tight">{post.title}</h1>
25
+ <p class="text-sm text-muted-foreground font-mono">{post.date}</p>
26
+ </div>
27
+
28
+ <hr class="border-border" />
29
+
30
+ <div class="space-y-4">
31
+ {#each post.content.split("\n\n") as paragraph}
32
+ <p class="text-foreground leading-relaxed">{paragraph}</p>
33
+ {/each}
34
+ </div>
35
+
36
+ <hr class="border-border" />
37
+
38
+ <!-- Route debug box — demonstrates params flowing from server to client -->
39
+ <div class="rounded-lg border bg-muted/40 p-4 space-y-1 text-sm font-mono">
40
+ <p class="font-sans text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">Route debug</p>
41
+ <p><span class="text-muted-foreground">pattern: </span>/blog/[slug]</p>
42
+ <p><span class="text-muted-foreground">params.slug: </span><span class="text-primary font-semibold">{slug}</span></p>
43
+ <p><span class="text-muted-foreground">loaded by: </span>+page.server.ts</p>
44
+ <p><span class="text-muted-foreground">parent data: </span>{data.appName}</p>
45
+ </div>
46
+ </article>
47
+ {:else}
48
+ <div class="flex flex-col items-center justify-center py-20 text-center space-y-4">
49
+ <p class="text-7xl font-bold text-destructive">404</p>
50
+ <p class="text-xl text-muted-foreground">Post "<span class="font-mono">{slug}</span>" not found.</p>
51
+ <a href="/blog" class="rounded-md border px-4 py-2 text-sm hover:bg-muted transition-colors">Back to Blog</a>
52
+ </div>
53
+ {/if}
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ let { data }: { data: { status: number; message: string } } = $props();
3
+ </script>
4
+
5
+ <svelte:head>
6
+ <title>{data.status} — {data.message}</title>
7
+ </svelte:head>
8
+
9
+ <div class="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
10
+ <p class="text-8xl font-bold text-gray-200">{data.status}</p>
11
+ <p class="text-2xl font-semibold text-gray-700">{data.message}</p>
12
+ <a href="/" class="mt-4 px-5 py-2 rounded-lg bg-gray-900 text-white text-sm hover:bg-gray-700 transition-colors">
13
+ Go home
14
+ </a>
15
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { LoadEvent } from "bosbun";
2
+
3
+ // Data returned here is available to all child loaders via parent()
4
+ // and to all layouts via the `data` prop.
5
+ export async function load({ locals }: LoadEvent) {
6
+ return {
7
+ appName: "{{PROJECT_NAME}}",
8
+ requestTime: locals.requestTime as number | null ?? null,
9
+ };
10
+ }
@@ -0,0 +1,6 @@
1
+ <script lang="ts">
2
+ import "../app.css";
3
+ let { children }: { children: any } = $props();
4
+ </script>
5
+
6
+ {@render children()}
@@ -0,0 +1,28 @@
1
+ import { fail, redirect } from "bosbun";
2
+ import type { RequestEvent } from "bosbun";
3
+
4
+ export async function load() {
5
+ return { greeting: "Test form actions" };
6
+ }
7
+
8
+ export const actions = {
9
+ default: async ({ request }: RequestEvent) => {
10
+ const data = await request.formData();
11
+ const email = data.get("email") as string;
12
+ const name = data.get("name") as string;
13
+
14
+ const errors: Record<string, string> = {};
15
+ if (!email) errors.email = "Email is required";
16
+ if (!name) errors.name = "Name is required";
17
+
18
+ if (Object.keys(errors).length > 0) {
19
+ return fail(400, { email, name, errors });
20
+ }
21
+
22
+ return { success: true, email, name };
23
+ },
24
+
25
+ reset: async () => {
26
+ return { cleared: true };
27
+ },
28
+ };
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import type { PageData, ActionData } from './$types';
3
+ let { data, form }: { data: PageData; form: ActionData } = $props();
4
+ </script>
5
+
6
+ <div class="max-w-md mx-auto mt-10 p-6">
7
+ <h1 class="text-2xl font-bold mb-4">{data.greeting}</h1>
8
+
9
+ {#if form?.success}
10
+ <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
11
+ Welcome, {form.name} ({form.email})!
12
+ </div>
13
+ {/if}
14
+
15
+ {#if form?.cleared}
16
+ <div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded mb-4">
17
+ Form cleared.
18
+ </div>
19
+ {/if}
20
+
21
+ <form method="POST" class="space-y-4">
22
+ <div>
23
+ <label for="name" class="block text-sm font-medium">Name</label>
24
+ <input
25
+ id="name"
26
+ name="name"
27
+ type="text"
28
+ value={form?.name ?? ''}
29
+ class="mt-1 block w-full rounded border border-gray-300 px-3 py-2"
30
+ />
31
+ {#if form?.errors?.name}
32
+ <p class="text-red-500 text-sm mt-1">{form.errors.name}</p>
33
+ {/if}
34
+ </div>
35
+
36
+ <div>
37
+ <label for="email" class="block text-sm font-medium">Email</label>
38
+ <input
39
+ id="email"
40
+ name="email"
41
+ type="text"
42
+ value={form?.email ?? ''}
43
+ class="mt-1 block w-full rounded border border-gray-300 px-3 py-2"
44
+ />
45
+ {#if form?.errors?.email}
46
+ <p class="text-red-500 text-sm mt-1">{form.errors.email}</p>
47
+ {/if}
48
+ </div>
49
+
50
+ <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
51
+ Submit
52
+ </button>
53
+ </form>
54
+
55
+ <form method="POST" action="?/reset" class="mt-4">
56
+ <button type="submit" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
57
+ Reset (named action)
58
+ </button>
59
+ </form>
60
+ </div>
@@ -0,0 +1,44 @@
1
+ import type { RequestEvent } from "bosbun";
2
+
3
+ export function GET({ params, locals }: RequestEvent) {
4
+ return Response.json({
5
+ method: "GET",
6
+ message: "Hello from Bosbun API!",
7
+ params,
8
+ locals: {
9
+ requestTime: locals.requestTime ?? null,
10
+ user: locals.user ?? null,
11
+ },
12
+ });
13
+ }
14
+
15
+ export async function POST({ request, locals }: RequestEvent) {
16
+ const body = await request.json().catch(() => ({}));
17
+ return Response.json({ method: "POST", received: body, locals });
18
+ }
19
+
20
+ export async function PUT({ request, locals }: RequestEvent) {
21
+ const body = await request.json().catch(() => ({}));
22
+ return Response.json({ method: "PUT", received: body, locals });
23
+ }
24
+
25
+ export async function PATCH({ request, locals }: RequestEvent) {
26
+ const body = await request.json().catch(() => ({}));
27
+ return Response.json({ method: "PATCH", received: body, locals });
28
+ }
29
+
30
+ export function DELETE({ params, locals }: RequestEvent) {
31
+ return Response.json({ method: "DELETE", deleted: true, params, locals });
32
+ }
33
+
34
+ export function OPTIONS() {
35
+ return new Response(null, {
36
+ status: 204,
37
+ headers: {
38
+ Allow: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
39
+ "Access-Control-Allow-Origin": "*",
40
+ "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
41
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
42
+ },
43
+ });
44
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "allowJs": true,
8
+ "skipLibCheck": true,
9
+ "allowImportingTsExtensions": true,
10
+ "noEmit": true,
11
+ "verbatimModuleSyntax": true,
12
+ "types": ["bun-types"],
13
+ "lib": ["dom", "dom.iterable", "esnext"],
14
+ "rootDirs": [".", ".bosbun/types"],
15
+ "paths": {
16
+ "$lib": ["./src/lib"],
17
+ "$lib/*": ["./src/lib/*"]
18
+ }
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist"]
22
+ }