bosbun 0.0.7 → 0.0.8-rc.1
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 +37 -19
- package/package.json +3 -4
- package/src/cli/create.ts +5 -1
- package/src/core/build.ts +1 -1
- package/src/core/client/App.svelte +2 -1
- package/src/core/client/router.svelte.ts +3 -2
- package/src/core/csrf.ts +1 -1
- package/src/core/dev.ts +31 -0
- package/src/core/html.ts +2 -14
- package/src/core/matcher.ts +1 -1
- package/src/core/paths.ts +7 -16
- package/src/core/renderer.ts +4 -33
- package/src/core/server.ts +22 -22
- package/templates/default/README.md +1 -1
- package/templates/default/package.json +1 -1
- package/templates/demo/README.md +6 -0
- package/templates/demo/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bosbun
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Full documentation: [bosbun.bosapi.com](https://bosbun.bosapi.com)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
+
# Start development
|
|
28
|
+
bun run dev
|
|
18
29
|
|
|
30
|
+
# Build for production
|
|
31
|
+
bun run build
|
|
32
|
+
bun run start
|
|
19
33
|
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.8-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "A
|
|
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://
|
|
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")
|
|
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
|
@@ -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"
|
|
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
|
|
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
|
-
|
|
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
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 ──────────────────────────────────────
|
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
|
-
|
|
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;
|
package/src/core/matcher.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// the
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
package/src/core/renderer.ts
CHANGED
|
@@ -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,
|
|
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(
|
package/src/core/server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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("*", () =>
|
|
392
|
-
|
|
393
|
-
|
|
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);
|
|
@@ -97,6 +97,6 @@ cn("px-4 py-2", isActive && "bg-primary")
|
|
|
97
97
|
|
|
98
98
|
## Learn More
|
|
99
99
|
|
|
100
|
-
- [Bosbun documentation](https://
|
|
100
|
+
- [Bosbun documentation](https://bosbun.bosapi.com)
|
|
101
101
|
- [Svelte 5 docs](https://svelte.dev)
|
|
102
102
|
- [Tailwind CSS v4](https://tailwindcss.com)
|
package/templates/demo/README.md
CHANGED
|
@@ -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)
|