bosbun 0.0.7 → 0.0.8-rc.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.
package/README.md CHANGED
@@ -1,32 +1,46 @@
1
- # bosbun
1
+ # Bosbun
2
2
 
3
- The `bosbun` package — framework core + CLI.
3
+ > Full documentation: [bosbun.bosapi.com](https://bosbun.bosapi.com)
4
4
 
5
- ## Installation
5
+ A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS.
6
6
 
7
- ```bash
8
- bun add bosbun
9
- ```
7
+ File-based routing inspired by SvelteKit, built on top of the Bun runtime and ElysiaJS HTTP server. No Node.js, no Vite, no adapters.
8
+
9
+ ## Features
10
10
 
11
- Or scaffold a new project:
11
+ - **File-based routing** `+page.svelte`, `+layout.svelte`, `+server.ts`, route groups, dynamic segments, catch-all routes
12
+ - **Server-side rendering** — every page is rendered on the server with full hydration
13
+ - **Server loaders** — `+page.server.ts` and `+layout.server.ts` with `parent()` data threading
14
+ - **API routes** — `+server.ts` exports HTTP verbs (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`)
15
+ - **Middleware hooks** — `hooks.server.ts` with `sequence()` for auth, logging, locals
16
+ - **Dev server with HMR** — file watcher + SSE browser reload, no page blink
17
+ - **Tailwind CSS v4** — compiled at build time, shadcn-inspired design tokens out of the box
18
+ - **CLI** — `bosbun create`, `bosbun dev`, `bosbun build`, `bosbun add`, `bosbun feat`
19
+
20
+ ## Quick Start
12
21
 
13
22
  ```bash
23
+ # Scaffold a new project
14
24
  bun x bosbun create my-app
15
- ```
25
+ cd my-app
16
26
 
17
- ## CLI
27
+ # Start development
28
+ bun run dev
18
29
 
30
+ # Build for production
31
+ bun run build
32
+ bun run start
19
33
  ```
20
- bosbun <command>
21
-
22
- Commands:
23
- create <name> Scaffold a new Bosbun project
24
- dev Start the development server (port 3000)
25
- build Build for production
26
- start Run the production server
27
- add <component> Install a UI component from the registry
28
- feat <feature> Install a feature scaffold from the registry
29
- ```
34
+
35
+ ## Tech Stack
36
+
37
+ | Layer | Technology |
38
+ |-------|------------|
39
+ | Runtime | [Bun](https://bun.sh) |
40
+ | HTTP Server | [ElysiaJS](https://elysiajs.com) |
41
+ | UI | [Svelte 5](https://svelte.dev) (Runes) |
42
+ | CSS | [Tailwind CSS v4](https://tailwindcss.com) |
43
+ | Bundler | Bun.build |
30
44
 
31
45
  ## Routing Conventions
32
46
 
@@ -161,3 +175,7 @@ my-app/
161
175
  ├── dist/ # Build output (git-ignored)
162
176
  └── package.json
163
177
  ```
178
+
179
+ ## License
180
+
181
+ MIT
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "bosbun",
3
- "version": "0.0.7",
3
+ "version": "0.0.8-rc.2",
4
4
  "type": "module",
5
- "description": "A minimalist fullstack framework — SSR + Svelte 5 Runes + Bun + ElysiaJS",
5
+ "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
7
7
  "bun",
8
8
  "svelte",
@@ -14,7 +14,7 @@
14
14
  "license": "MIT",
15
15
  "author": {
16
16
  "name": "Jekibus",
17
- "url": "https://bosapi.com"
17
+ "url": "https://github.com/jekibus"
18
18
  },
19
19
  "homepage": "https://github.com/bosapi/bosbun#readme",
20
20
  "repository": {
@@ -44,7 +44,6 @@
44
44
  "typescript": "^5"
45
45
  },
46
46
  "dependencies": {
47
- "@elysiajs/static": "^1.4.7",
48
47
  "@tailwindcss/cli": "^4.2.1",
49
48
  "bun-plugin-svelte": "^0.0.6",
50
49
  "clsx": "^2.1.1",
package/src/cli/create.ts CHANGED
@@ -6,6 +6,8 @@ import * as readline from "readline";
6
6
  // ─── bosbun create <name> [--template <name>] ──────────────
7
7
 
8
8
  const TEMPLATES_DIR = resolve(import.meta.dir, "../../templates");
9
+ const BOSBUN_PKG = JSON.parse(readFileSync(resolve(import.meta.dir, "../../package.json"), "utf-8"));
10
+ const BOSBUN_VERSION: string = BOSBUN_PKG.version;
9
11
 
10
12
  const TEMPLATE_DESCRIPTIONS: Record<string, string> = {
11
13
  default: "Minimal starter with routing and Tailwind",
@@ -107,7 +109,9 @@ function copyDir(src: string, dest: string, projectName: string) {
107
109
  if (entry.isDirectory()) {
108
110
  copyDir(srcPath, destPath, projectName);
109
111
  } else {
110
- const content = readFileSync(srcPath, "utf-8").replaceAll("{{PROJECT_NAME}}", projectName);
112
+ const content = readFileSync(srcPath, "utf-8")
113
+ .replaceAll("{{PROJECT_NAME}}", projectName)
114
+ .replaceAll("{{BOSBUN_VERSION}}", BOSBUN_VERSION);
111
115
  writeFileSync(destPath, content, "utf-8");
112
116
  }
113
117
  }
package/src/core/build.ts CHANGED
@@ -71,7 +71,7 @@ if (tailwindResult.exitCode !== 0) {
71
71
  }
72
72
  console.log("✅ Tailwind CSS built: public/bosbun-tw.css");
73
73
 
74
- // Separate plugin instances per build target (bosbun:env resolves differently)
74
+ // Separate plugin instances per build target ($env resolves differently)
75
75
  const clientPlugin = makeBosbunPlugin("browser");
76
76
  const serverPlugin = makeBosbunPlugin("bun");
77
77
 
@@ -124,7 +124,7 @@ const serverResult = await Bun.build({
124
124
  splitting: true,
125
125
  naming: { entry: "index.[ext]", chunk: "[name]-[hash].[ext]" },
126
126
  minify: isProduction,
127
- external: ["elysia", "@elysiajs/static"],
127
+ external: ["elysia"],
128
128
  plugins: [serverPlugin, SveltePlugin()],
129
129
  });
130
130
 
@@ -37,7 +37,8 @@
37
37
  if (ssrMode) return;
38
38
 
39
39
  const path = router.currentRoute;
40
- const match = findMatch(clientRoutes, path);
40
+ const pathname = path.split("?")[0].split("#")[0];
41
+ const match = findMatch(clientRoutes, pathname);
41
42
  if (!match) return;
42
43
 
43
44
  let cancelled = false;
@@ -6,13 +6,14 @@ import { findMatch } from "../matcher.ts";
6
6
  import { clientRoutes } from "bosbun:routes";
7
7
 
8
8
  export const router = new class Router {
9
- currentRoute = $state(typeof window !== "undefined" ? window.location.pathname : "/");
9
+ currentRoute = $state(typeof window !== "undefined" ? window.location.pathname + window.location.search + window.location.hash : "/");
10
10
  params = $state<Record<string, string>>({});
11
11
 
12
12
  navigate(path: string) {
13
13
  if (this.currentRoute === path) return;
14
14
  // Unknown route — let the server handle it (renders +error.svelte with 404)
15
- if (!findMatch(clientRoutes, path)) {
15
+ const pathname = path.split("?")[0].split("#")[0];
16
+ if (!findMatch(clientRoutes, pathname)) {
16
17
  window.location.href = path;
17
18
  return;
18
19
  }
package/src/core/csrf.ts CHANGED
@@ -12,7 +12,7 @@ export interface CsrfConfig {
12
12
  allowedOrigins?: string[];
13
13
  }
14
14
 
15
- export const DEFAULT_CSRF_CONFIG: CsrfConfig = {
15
+ const DEFAULT_CSRF_CONFIG: CsrfConfig = {
16
16
  checkOrigin: true,
17
17
  };
18
18
 
package/src/core/dev.ts CHANGED
@@ -8,6 +8,11 @@ console.log("⬡ Bosbun dev server starting...\n");
8
8
 
9
9
  let appProcess: Subprocess | null = null;
10
10
  let sseClients = new Set<ReadableStreamDefaultController>();
11
+ let intentionalKill = false;
12
+ let crashCount = 0;
13
+ let lastCrashTime = 0;
14
+ const MAX_RAPID_CRASHES = 3;
15
+ const RAPID_CRASH_WINDOW = 5_000; // 5 seconds
11
16
 
12
17
  // ─── SSE Broadcast ────────────────────────────────────────
13
18
 
@@ -48,8 +53,10 @@ const APP_PORT = DEV_PORT + 1; // internal, hidden from user
48
53
 
49
54
  async function startAppServer() {
50
55
  if (appProcess) {
56
+ intentionalKill = true;
51
57
  appProcess.kill();
52
58
  await appProcess.exited;
59
+ intentionalKill = false;
53
60
  }
54
61
 
55
62
  // Read the server entry filename from the manifest written by build.ts
@@ -72,6 +79,30 @@ async function startAppServer() {
72
79
  NODE_PATH: BOSBUN_NODE_PATH,
73
80
  },
74
81
  });
82
+
83
+ // Monitor for unexpected crashes
84
+ const proc = appProcess;
85
+ proc.exited.then((code) => {
86
+ if (proc !== appProcess || intentionalKill) return;
87
+ if (code === 0) return; // clean exit
88
+
89
+ const now = Date.now();
90
+ if (now - lastCrashTime < RAPID_CRASH_WINDOW) {
91
+ crashCount++;
92
+ } else {
93
+ crashCount = 1;
94
+ }
95
+ lastCrashTime = now;
96
+
97
+ if (crashCount >= MAX_RAPID_CRASHES) {
98
+ console.error(`\n💥 App crashed ${crashCount} times in ${RAPID_CRASH_WINDOW / 1000}s — waiting for file change to restart\n`);
99
+ crashCount = 0;
100
+ return;
101
+ }
102
+
103
+ console.warn(`\n⚠️ App crashed (exit code ${code}). Restarting...\n`);
104
+ startAppServer();
105
+ });
75
106
  }
76
107
 
77
108
  // ─── Build & Restart ──────────────────────────────────────
@@ -6,7 +6,7 @@ import type { ClassifiedEnv } from "./env.ts";
6
6
  // Generates three files in .bosbun/:
7
7
  // env.server.ts — all vars (static inlined, dynamic via process.env)
8
8
  // env.client.ts — only PUBLIC_* vars (PUBLIC_STATIC_* inlined, PUBLIC_* via window.__BOSBUN_ENV__)
9
- // types/env.d.ts — declare module 'bosbun:env' for IDE autocomplete
9
+ // types/env.d.ts — declare module '$env' for IDE autocomplete
10
10
 
11
11
  export function generateEnvModules(classified: ClassifiedEnv): void {
12
12
  const bosbunDir = join(process.cwd(), ".bosbun");
@@ -22,7 +22,7 @@ export function generateEnvModules(classified: ClassifiedEnv): void {
22
22
  function writeServerEnv(classified: ClassifiedEnv, bosbunDir: string): void {
23
23
  const lines: string[] = [
24
24
  "// Auto-generated by Bosbun. Do not edit.",
25
- "// bosbun:env → server — all vars",
25
+ "// $env → server — all vars",
26
26
  "",
27
27
  ];
28
28
 
@@ -52,7 +52,7 @@ function writeServerEnv(classified: ClassifiedEnv, bosbunDir: string): void {
52
52
  function writeClientEnv(classified: ClassifiedEnv, bosbunDir: string): void {
53
53
  const lines: string[] = [
54
54
  "// Auto-generated by Bosbun. Do not edit.",
55
- "// bosbun:env → client — PUBLIC_* vars only",
55
+ "// $env → client — PUBLIC_* vars only",
56
56
  "",
57
57
  "const __env: Record<string, string> =",
58
58
  " typeof window !== 'undefined' && (window as any).__BOSBUN_ENV__ || {};",
@@ -84,7 +84,7 @@ function writeEnvTypes(classified: ClassifiedEnv, typesDir: string): void {
84
84
 
85
85
  const content = [
86
86
  "// Auto-generated by Bosbun. Do not edit.",
87
- "declare module 'bosbun:env' {",
87
+ "declare module '$env' {",
88
88
  ...declarations,
89
89
  "}",
90
90
  "",
package/src/core/html.ts CHANGED
@@ -13,6 +13,7 @@ export const distManifest: { js: string[]; css: string[]; entry: string } = (()
13
13
  })();
14
14
 
15
15
  export const isDev = process.env.NODE_ENV !== "production";
16
+ const cacheBust = isDev ? `?v=${Date.now()}` : "";
16
17
 
17
18
  // ─── Safe JSON Serialization ──────────────────────────────
18
19
 
@@ -64,8 +65,6 @@ export function buildHtml(
64
65
  csr = true,
65
66
  formData: any = null,
66
67
  ): string {
67
- const cacheBust = isDev ? `?v=${Date.now()}` : "";
68
-
69
68
  const cssLinks = (distManifest.css ?? [])
70
69
  .map((f: string) => `<link rel="stylesheet" href="/dist/client/${f}">`)
71
70
  .join("\n ");
@@ -108,21 +107,11 @@ export function buildHtml(
108
107
 
109
108
  import type { Metadata } from "./hooks.ts";
110
109
 
111
- let _shell: string | null = null;
112
-
113
- export function buildHtmlShell(): string {
114
- if (_shell) return _shell;
115
- _shell = buildHtmlShellOpen() + buildMetadataChunk(null);
116
- return _shell;
117
- }
118
-
119
-
120
110
  let _shellOpen: string | null = null;
121
111
 
122
112
  /** Chunk 1: everything from <!DOCTYPE> through CSS/modulepreload links (head still open) */
123
113
  export function buildHtmlShellOpen(): string {
124
114
  if (_shellOpen) return _shellOpen;
125
- const cacheBust = isDev ? `?v=${Date.now()}` : "";
126
115
  const cssLinks = (distManifest.css ?? [])
127
116
  .map((f: string) => `<link rel="stylesheet" href="/dist/client/${f}">`)
128
117
  .join("\n ");
@@ -180,7 +169,6 @@ export function buildHtmlTail(
180
169
  csr: boolean,
181
170
  formData: any = null,
182
171
  ): string {
183
- const cacheBust = isDev ? `?v=${Date.now()}` : "";
184
172
  let out = `<script>document.getElementById('__bs__').remove()</script>`;
185
173
  out += `\n<div id="app">${body}</div>`;
186
174
  if (head) out += `\n<script>document.head.innerHTML+=${safeJsonStringify(head)}</script>`;
@@ -215,7 +203,7 @@ export function compress(body: string, contentType: string, req: Request, status
215
203
 
216
204
  // ─── Static File Detection ────────────────────────────────
217
205
 
218
- export const STATIC_EXTS = new Set([".ico", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".css", ".js", ".woff", ".woff2", ".ttf"]);
206
+ const STATIC_EXTS = new Set([".ico", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".css", ".js", ".woff", ".woff2", ".ttf"]);
219
207
 
220
208
  export function isStaticPath(path: string): boolean {
221
209
  if (path.startsWith("/dist/") || path.startsWith("/__bosbun/")) return true;
@@ -13,7 +13,7 @@ import type { RouteMatch } from "./types.ts";
13
13
  * Match a URL pathname against a single route pattern.
14
14
  * Returns extracted params if matched, null otherwise.
15
15
  */
16
- export function matchPattern(
16
+ function matchPattern(
17
17
  pattern: string,
18
18
  pathname: string,
19
19
  ): Record<string, string> | null {
package/src/core/paths.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { join, dirname, resolve } from "path";
1
+ import { join, dirname } from "path";
2
2
  import { existsSync } from "fs";
3
3
 
4
4
  // This file lives at src/core/paths.ts → package root is ../..
@@ -8,21 +8,12 @@ const BOSBUN_PKG_DIR = join(import.meta.dir, "..", "..");
8
8
  // node_modules rather than a nested node_modules/bosbun/node_modules.
9
9
  const NESTED_NM = join(BOSBUN_PKG_DIR, "node_modules");
10
10
 
11
- // Walk up from the package dir to find the nearest ancestor node_modules.
12
- // In a workspace, bosbun lives at packages/bosbun/ so we need to find
13
- // the workspace root's node_modules (not just the parent directory).
14
- function findAncestorNodeModules(from: string): string | null {
15
- let dir = resolve(from, "..");
16
- const root = dirname(dir); // stop at filesystem root
17
- while (dir !== root) {
18
- const candidate = join(dir, "node_modules");
19
- if (candidate !== NESTED_NM && existsSync(candidate)) return candidate;
20
- dir = dirname(dir);
21
- }
22
- return null;
23
- }
24
-
25
- const HOISTED_NM = findAncestorNodeModules(BOSBUN_PKG_DIR);
11
+ // When installed as a dep (node_modules/bosbun/), parent is node_modules/ itself.
12
+ // Only include it if the package is actually inside a node_modules directory
13
+ // AND the parent node_modules contains real (resolvable) packages.
14
+ const parentDir = dirname(BOSBUN_PKG_DIR); // node_modules/ when installed, packages/ in workspace
15
+ const isInstalledAsDep = parentDir.endsWith("node_modules");
16
+ const HOISTED_NM = isInstalledAsDep ? parentDir : null;
26
17
 
27
18
  /** NODE_PATH value covering both nested and hoisted dependency locations */
28
19
  export const BOSBUN_NODE_PATH = HOISTED_NM
@@ -3,7 +3,7 @@ import { join, dirname } from "path";
3
3
  // ─── Bun Build Plugin ─────────────────────────────────────
4
4
  // Resolves:
5
5
  // bosbun:routes → .bosbun/routes.ts (generated route map)
6
- // bosbun:env → .bosbun/env.server.ts (bun) or .bosbun/env.client.ts (browser)
6
+ // $env → .bosbun/env.server.ts (bun) or .bosbun/env.client.ts (browser)
7
7
  // $lib/* → src/lib/* (user library alias)
8
8
 
9
9
  export function makeBosbunPlugin(target: "browser" | "bun" = "bun") {
@@ -15,8 +15,8 @@ export function makeBosbunPlugin(target: "browser" | "bun" = "bun") {
15
15
  path: join(process.cwd(), ".bosbun", "routes.ts"),
16
16
  }));
17
17
 
18
- // bosbun:env → .bosbun/env.client.ts (browser) or .bosbun/env.server.ts (bun)
19
- build.onResolve({ filter: /^bosbun:env$/ }, () => ({
18
+ // $env → .bosbun/env.client.ts (browser) or .bosbun/env.server.ts (bun)
19
+ build.onResolve({ filter: /^\$env$/ }, () => ({
20
20
  path: join(
21
21
  process.cwd(),
22
22
  ".bosbun",
@@ -5,7 +5,7 @@ import { serverRoutes, errorPage } from "bosbun:routes";
5
5
  import type { Cookies } from "./hooks.ts";
6
6
  import { HttpError, Redirect } from "./errors.ts";
7
7
  import App from "./client/App.svelte";
8
- import { buildHtml, buildHtmlShell, buildHtmlShellOpen, buildMetadataChunk, buildHtmlTail, compress, safeJsonStringify, isDev } from "./html.ts";
8
+ import { buildHtml, buildHtmlShellOpen, buildMetadataChunk, buildHtmlTail, compress, safeJsonStringify, isDev } from "./html.ts";
9
9
  import type { Metadata } from "./hooks.ts";
10
10
 
11
11
  // ─── Timeout Helpers ─────────────────────────────────────
@@ -28,10 +28,11 @@ const METADATA_TIMEOUT = parseTimeout(process.env.METADATA_TIMEOUT, 3000);
28
28
 
29
29
  function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
30
30
  if (ms <= 0) return promise;
31
+ let timer: Timer;
31
32
  return Promise.race([
32
- promise,
33
+ promise.finally(() => clearTimeout(timer)),
33
34
  new Promise<never>((_, reject) =>
34
- setTimeout(() => reject(new LoadTimeoutError(label, ms)), ms)
35
+ timer = setTimeout(() => reject(new LoadTimeoutError(label, ms)), ms)
35
36
  ),
36
37
  ]);
37
38
  }
@@ -119,36 +120,6 @@ export async function loadRouteData(
119
120
  return { pageData: { ...pageData, params }, layoutData, csr };
120
121
  }
121
122
 
122
- // ─── SSR Renderer ────────────────────────────────────────
123
-
124
- export async function renderSSR(url: URL, locals: Record<string, any>, req: Request, cookies: Cookies) {
125
- const match = findMatch(serverRoutes, url.pathname);
126
- if (!match) return null;
127
-
128
- const { route } = match;
129
-
130
- // Kick off component imports in parallel with data loading
131
- const pageModPromise = route.pageModule();
132
- const layoutModsPromise = Promise.all(route.layoutModules.map((l: () => Promise<any>) => l()));
133
-
134
- const data = await loadRouteData(url, locals, req, cookies);
135
- if (!data) return null;
136
-
137
- const [pageMod, layoutMods] = await Promise.all([pageModPromise, layoutModsPromise]);
138
-
139
- const { body, head } = render(App, {
140
- props: {
141
- ssrMode: true,
142
- ssrPageComponent: pageMod.default,
143
- ssrLayoutComponents: layoutMods.map((m: any) => m.default),
144
- ssrPageData: data.pageData,
145
- ssrLayoutData: data.layoutData,
146
- },
147
- });
148
-
149
- return { body, head, pageData: data.pageData, layoutData: data.layoutData, csr: data.csr };
150
- }
151
-
152
123
  // ─── Metadata Loader ─────────────────────────────────────
153
124
 
154
125
  async function loadMetadata(
@@ -1,5 +1,5 @@
1
1
  import { Elysia } from "elysia";
2
- import { staticPlugin } from "@elysiajs/static";
2
+
3
3
  import { existsSync } from "fs";
4
4
  import { join, resolve as resolvePath } from "path";
5
5
 
@@ -35,14 +35,15 @@ if (existsSync(hooksPath)) {
35
35
  }
36
36
  }
37
37
 
38
+ // ─── Env Helpers ─────────────────────────────────────────
39
+
40
+ function splitCsvEnv(key: string): string[] | undefined {
41
+ return process.env[key]?.split(",").map(s => s.trim()).filter(Boolean) || undefined;
42
+ }
43
+
38
44
  // ─── CSRF Config ─────────────────────────────────────────
39
- // Parsed once at startup from CSRF_ALLOWED_ORIGINS env var.
40
- // Format: "https://x.com, https://y.com" — commas with or without spaces.
41
45
 
42
- const _csrfAllowedOrigins = process.env.CSRF_ALLOWED_ORIGINS
43
- ?.split(",")
44
- .map(s => s.trim())
45
- .filter(Boolean);
46
+ const _csrfAllowedOrigins = splitCsvEnv("CSRF_ALLOWED_ORIGINS");
46
47
 
47
48
  const CSRF_CONFIG: CsrfConfig = {
48
49
  checkOrigin: true,
@@ -56,17 +57,8 @@ if (_csrfAllowedOrigins?.length) {
56
57
  }
57
58
 
58
59
  // ─── CORS Config ──────────────────────────────────────────
59
- // Parsed once at startup from CORS_ALLOWED_ORIGINS env var.
60
- // Format: "https://x.com, https://y.com" — commas with or without spaces.
61
-
62
- const _corsAllowedOrigins = process.env.CORS_ALLOWED_ORIGINS
63
- ?.split(",")
64
- .map(s => s.trim())
65
- .filter(Boolean);
66
60
 
67
- function splitCsvEnv(key: string): string[] | undefined {
68
- return process.env[key]?.split(",").map(s => s.trim()).filter(Boolean) || undefined;
69
- }
61
+ const _corsAllowedOrigins = splitCsvEnv("CORS_ALLOWED_ORIGINS");
70
62
 
71
63
  const CORS_CONFIG: CorsConfig | null = _corsAllowedOrigins?.length
72
64
  ? {
@@ -370,8 +362,7 @@ const app = new Elysia({ serve: { maxRequestBodySize: BODY_SIZE_LIMIT } })
370
362
  else console.error("Uncaught server error:", (error as Error)?.message ?? error);
371
363
  return Response.json({ error: "Internal Server Error" }, { status: 500 });
372
364
  })
373
- .use(staticPlugin({ assets: "public", prefix: "/" }))
374
- // dist/client is served by our resolve() handler with proper cache headers
365
+ // Static files are served by resolve() with path traversal protection and security headers
375
366
  // API routes must intercept all HTTP methods before the GET catch-all
376
367
  .onBeforeHandle(async ({ request }) => {
377
368
  const url = new URL(request.url);
@@ -388,9 +379,18 @@ const app = new Elysia({ serve: { maxRequestBodySize: BODY_SIZE_LIMIT } })
388
379
  const url = new URL(request.url);
389
380
  return handleRequest(request, url);
390
381
  })
391
- .put("*", () => new Response("Not Found", { status: 404 }))
392
- .patch("*", () => new Response("Not Found", { status: 404 }))
393
- .delete("*", () => new Response("Not Found", { status: 404 }))
382
+ .put("*", ({ request }) => {
383
+ const url = new URL(request.url);
384
+ return handleRequest(request, url);
385
+ })
386
+ .patch("*", ({ request }) => {
387
+ const url = new URL(request.url);
388
+ return handleRequest(request, url);
389
+ })
390
+ .delete("*", ({ request }) => {
391
+ const url = new URL(request.url);
392
+ return handleRequest(request, url);
393
+ })
394
394
  .options("*", ({ request }) => {
395
395
  const url = new URL(request.url);
396
396
  return handleRequest(request, url);
@@ -12,17 +12,17 @@
12
12
  # (no prefix) Server only, read from process.env at runtime (secrets, DB creds)
13
13
  #
14
14
  # Import in your code:
15
- # import { PUBLIC_STATIC_APP_NAME, DB_PASSWORD } from 'bosbun:env';
15
+ # import { PUBLIC_STATIC_APP_NAME, DB_PASSWORD } from '$env';
16
16
  #
17
17
  # Framework vars (PORT, NODE_ENV, BODY_SIZE_LIMIT, CSRF_ALLOWED_ORIGINS,
18
- # CORS_*, LOAD_TIMEOUT, METADATA_TIMEOUT, PRERENDER_TIMEOUT) are NOT exposed via bosbun:env —
18
+ # CORS_*, LOAD_TIMEOUT, METADATA_TIMEOUT, PRERENDER_TIMEOUT) are NOT exposed via $env —
19
19
  # access them via process.env directly.
20
20
  # ────────────────────────────────────────────────────────────────────────────────
21
21
 
22
22
  # Public build-time constant (safe to expose to client)
23
23
  PUBLIC_STATIC_APP_NAME=My Bosbun App
24
24
 
25
- # ─── Framework vars — access via process.env (not via bosbun:env) ─────────────
25
+ # ─── Framework vars — access via process.env (not via $env) ───────────────────
26
26
 
27
27
  # Server port. Defaults to 9000 in production, 9001 in dev (proxied via :9000).
28
28
  # PORT=9000
@@ -97,6 +97,6 @@ cn("px-4 py-2", isActive && "bg-primary")
97
97
 
98
98
  ## Learn More
99
99
 
100
- - [Bosbun documentation](https://github.com/bosapi/bosbun)
100
+ - [Bosbun documentation](https://bosbun.bosapi.com)
101
101
  - [Svelte 5 docs](https://svelte.dev)
102
102
  - [Tailwind CSS v4](https://tailwindcss.com)
@@ -9,7 +9,7 @@
9
9
  "check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "bosbun": "*",
12
+ "bosbun": "^{{BOSBUN_VERSION}}",
13
13
  "svelte": "^5.20.0",
14
14
  "clsx": "^2.1.1",
15
15
  "tailwind-merge": "^3.5.0"
@@ -21,3 +21,9 @@ bun x bosbun start # run production server
21
21
  | `/api/hello` | `api/hello/+server.ts` | Multi-method JSON API |
22
22
  | `/actions-test` | `actions-test/+page.svelte` | Form actions demo |
23
23
  | `/*` | `(public)/[...catchall]/+page.svelte` | 404 catch-all |
24
+
25
+ ## Learn More
26
+
27
+ - [Bosbun documentation](https://bosbun.bosapi.com)
28
+ - [Svelte 5 docs](https://svelte.dev)
29
+ - [Tailwind CSS v4](https://tailwindcss.com)
@@ -8,7 +8,7 @@
8
8
  "start": "bosbun start"
9
9
  },
10
10
  "dependencies": {
11
- "bosbun": "*",
11
+ "bosbun": "^{{BOSBUN_VERSION}}",
12
12
  "svelte": "^5.20.0",
13
13
  "clsx": "^2.1.1",
14
14
  "tailwind-merge": "^3.5.0"