bosia 0.6.22 → 0.6.24

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/cli/add.ts +2 -2
  3. package/src/cli/block.ts +7 -0
  4. package/src/cli/create.ts +0 -1
  5. package/src/cli/feat.ts +1 -1
  6. package/src/cli/index.ts +3 -3
  7. package/src/core/dev.ts +98 -2
  8. package/src/core/html.ts +10 -4
  9. package/src/core/server.ts +21 -1
  10. package/templates/default/package.json +1 -1
  11. package/templates/default/src/app.css +2 -0
  12. package/templates/demo/package.json +1 -1
  13. package/templates/demo/src/app.css +2 -0
  14. package/templates/shop/package.json +2 -3
  15. package/templates/shop/src/app.css +2 -0
  16. package/templates/shop/src/routes/(private)/+layout.svelte +30 -0
  17. package/templates/shop/src/routes/(public)/+page.svelte +8 -0
  18. package/templates/todo/.env.example +0 -2
  19. package/templates/todo/.prettierignore +0 -7
  20. package/templates/todo/.prettierrc.json +0 -9
  21. package/templates/todo/README.md +0 -69
  22. package/templates/todo/_gitignore +0 -12
  23. package/templates/todo/bosia.config.ts +0 -10
  24. package/templates/todo/instructions.txt +0 -3
  25. package/templates/todo/package.json +0 -24
  26. package/templates/todo/public/.gitkeep +0 -0
  27. package/templates/todo/public/favicon.svg +0 -14
  28. package/templates/todo/public/logo-dark.svg +0 -14
  29. package/templates/todo/public/logo-light.svg +0 -14
  30. package/templates/todo/src/app.css +0 -132
  31. package/templates/todo/src/app.d.ts +0 -14
  32. package/templates/todo/src/app.html +0 -11
  33. package/templates/todo/src/hooks.server.ts +0 -20
  34. package/templates/todo/src/lib/utils.ts +0 -1
  35. package/templates/todo/src/routes/(public)/+page.svelte +0 -53
  36. package/templates/todo/src/routes/+error.svelte +0 -19
  37. package/templates/todo/src/routes/+layout.server.ts +0 -8
  38. package/templates/todo/src/routes/+layout.svelte +0 -6
  39. package/templates/todo/template.json +0 -6
  40. package/templates/todo/tsconfig.json +0 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.6.22",
3
+ "version": "0.6.24",
4
4
  "type": "module",
5
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": [
package/src/cli/add.ts CHANGED
@@ -75,7 +75,7 @@ export async function runAdd(names: string[], flags: string[] = []) {
75
75
 
76
76
  /**
77
77
  * Resolve the full registry path for a component using the index.
78
- * - "todo" → "todo" (exact match in index)
78
+ * - "<name>" → "<name>" (exact match in index)
79
79
  * - "button" → "ui/button" (suffix match in index)
80
80
  * - "shop/cart" → "shop/cart" (explicit path used as-is)
81
81
  */
@@ -83,7 +83,7 @@ function resolveDestPath(name: string): string {
83
83
  if (name.includes("/")) return name;
84
84
 
85
85
  if (registryIndex) {
86
- // Exact match (e.g. "todo" "todo")
86
+ // Exact match (bare name present in index)
87
87
  if (registryIndex.components.includes(name)) return name;
88
88
  // Suffix match (e.g. "button" → "ui/button")
89
89
  const match = registryIndex.components.find((c) => c.endsWith(`/${name}`));
package/src/cli/block.ts CHANGED
@@ -30,6 +30,10 @@ interface BlockMeta {
30
30
  npmDeps: Record<string, string>;
31
31
  }
32
32
 
33
+ // Track already-installed blocks within a session to avoid redundant work
34
+ // when multiple features/blocks declare the same block dependency.
35
+ const installed = new Set<string>();
36
+
33
37
  export async function runAddBlock(
34
38
  name: string | undefined,
35
39
  flags: string[] = [],
@@ -58,6 +62,9 @@ export async function runAddBlock(
58
62
  await initAddRegistry(registryRoot);
59
63
  ensureUtils(resolvedOptions.cwd);
60
64
 
65
+ if (installed.has(name)) return;
66
+ installed.add(name);
67
+
61
68
  console.log(`⬡ Installing block: ${name}\n`);
62
69
 
63
70
  const meta = await readRegistryJSON<BlockMeta>(registryRoot, "blocks", name, "meta.json");
package/src/cli/create.ts CHANGED
@@ -14,7 +14,6 @@ const BOSIA_VERSION: string = BOSIA_PKG.version;
14
14
  const TEMPLATE_DESCRIPTIONS: Record<string, string> = {
15
15
  default: "Minimal starter with routing and Tailwind",
16
16
  demo: "Full-featured demo with hooks, API routes, form actions, and more",
17
- todo: "Todo app with PostgreSQL + Drizzle ORM",
18
17
  shop: "Online store starter with auth, RBAC, S3 uploads, products/orders/cart",
19
18
  };
20
19
 
package/src/cli/feat.ts CHANGED
@@ -16,7 +16,7 @@ import { recordFeature, readManifest } from "./manifest.ts";
16
16
  // ─── bun x bosia@latest feat <feature> [--local] ─────────
17
17
  // Fetches a feature scaffold from the GitHub registry (or local
18
18
  // registry with --local) and copies route/lib files, installs npm deps.
19
- // Supports nested feature dependencies (e.g. tododrizzle).
19
+ // Supports nested feature dependencies (e.g. shopauth).
20
20
 
21
21
  type FileStrategy =
22
22
  | "write" // overwrite (prompt if interactive)
package/src/cli/index.ts CHANGED
@@ -126,7 +126,7 @@ Commands:
126
126
 
127
127
  Examples:
128
128
  bun x bosia@latest create my-app
129
- bun x bosia@latest create my-app --template todo
129
+ bun x bosia@latest create my-app --template shop
130
130
  bun x bosia dev
131
131
  bun x bosia build
132
132
  bun x bosia start
@@ -137,8 +137,8 @@ Examples:
137
137
  bun x bosia@latest add button card input → install multiple at once
138
138
  bun x bosia@latest add -y button card → auto-confirm overwrites (CI / scripts)
139
139
  bun x bosia@latest add shop/cart → src/lib/components/shop/cart/
140
- bun x bosia@latest add block cards/feature-editorial
141
- bun x bosia@latest add blocks/cards/feature-editorial (alias for: add block cards/feature-editorial)
140
+ bun x bosia@latest add block cards/feature
141
+ bun x bosia@latest add blocks/cards/feature (alias for: add block cards/feature)
142
142
  bun x bosia@latest add theme editorial
143
143
  bun x bosia@latest add font "Fredoka" "https://fonts.googleapis.com/css2?family=Fredoka:wght@400;700&display=swap"
144
144
  bun x bosia@latest feat login
package/src/core/dev.ts CHANGED
@@ -42,7 +42,27 @@ const BACKOFF_SCHEDULE_MS = [500, 1_000, 2_000, 4_000, 5_000];
42
42
 
43
43
  // ─── SSE Broadcast ────────────────────────────────────────
44
44
 
45
+ // Reload-hold control (driven by titoko via /__bosia/hold + /__bosia/resume).
46
+ // While held, rebuilds keep happening (latest code stays ready) but the reload
47
+ // broadcast is suppressed; on resume a single reload is flushed if any rebuild
48
+ // fired meanwhile. Defaults to off, so a plain `bosia dev` developer never sees
49
+ // any behaviour change.
50
+ let reloadHeld = false;
51
+ let reloadQueuedWhileHeld = false;
52
+ let holdSafetyTimer: ReturnType<typeof setTimeout> | null = null;
53
+ // Safety net for a *dead* orchestrator only — NOT a task duration cap. While an
54
+ // agent run is healthy the orchestrator heartbeats `/__bosia/hold` (re-arming
55
+ // this timer), so it never fires mid-task no matter how long the task runs. It
56
+ // fires only if the heartbeats stop (titoko crash, network partition), so a
57
+ // missed resume can't freeze the preview forever. Must comfortably exceed the
58
+ // heartbeat interval so one dropped ping doesn't trip it.
59
+ const HOLD_SAFETY_MS = 90_000;
60
+
45
61
  function broadcastReload() {
62
+ if (reloadHeld) {
63
+ reloadQueuedWhileHeld = true;
64
+ return;
65
+ }
46
66
  const msg = new TextEncoder().encode("event: reload\ndata: ok\n\n");
47
67
  for (const ctrl of sseClients) {
48
68
  try {
@@ -56,6 +76,41 @@ function broadcastReload() {
56
76
  }
57
77
  }
58
78
 
79
+ // 503 body served while the inner app server is mid-restart (rebuild after an
80
+ // edit). Must be HTML carrying the SAME SSE reload client as the dev-500 page —
81
+ // the bare text/plain version had no live `/__bosia/sse` connection, so once an
82
+ // iframe landed here (e.g. a reload racing into a rebuild window) it stayed stuck
83
+ // until a manual reload. With the client, the next `broadcastReload()` once the
84
+ // app binds reloads this page automatically. Keep the literal phrase
85
+ // "App server is starting" in the body: titoko's proxy retry matches on it.
86
+ const STARTING_PAGE = `<!doctype html>
87
+ <html lang="en">
88
+ <head>
89
+ <meta charset="utf-8" />
90
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
91
+ <title>Starting…</title>
92
+ <style>
93
+ html,body{margin:0;padding:0;height:100%;background:#0a0a0a;color:#e5e5e5;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif}
94
+ .wrap{min-height:100%;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box}
95
+ .dot{display:inline-block;width:10px;height:10px;background:#16a34a;border-radius:50%;margin-right:8px;vertical-align:middle;animation:p 1.4s ease-in-out infinite}
96
+ @keyframes p{0%,100%{opacity:1}50%{opacity:.3}}
97
+ h1{font-size:16px;font-weight:600;margin:0}
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <div class="wrap"><h1><span class="dot"></span>App server is starting…</h1></div>
102
+ <script>
103
+ !function r(){
104
+ try{
105
+ var e=new EventSource("/__bosia/sse");
106
+ e.addEventListener("reload",function(){location.reload()});
107
+ e.onerror=function(){e.close();setTimeout(r,2000)};
108
+ }catch(_){setTimeout(r,2000)}
109
+ }();
110
+ </script>
111
+ </body>
112
+ </html>`;
113
+
59
114
  // ─── Build ────────────────────────────────────────────────
60
115
 
61
116
  const BUILD_SCRIPT = join(import.meta.dir, "build.ts");
@@ -235,6 +290,47 @@ const devServer = Bun.serve({
235
290
  );
236
291
  }
237
292
 
293
+ // Reload-hold control — host orchestrator (titoko) brackets an AI agent run
294
+ // so the preview reloads once when the agent finishes, not once per file
295
+ // edit. Both routes are idempotent and return small JSON.
296
+ //
297
+ // POST /__bosia/hold doubles as the heartbeat: the FIRST hold opens a fresh
298
+ // window (clears any stale queued reload); subsequent holds only re-arm the
299
+ // safety timer and MUST preserve `reloadQueuedWhileHeld`, or a heartbeat
300
+ // landing after a suppressed rebuild would drop the pending reload and
301
+ // resume would flush nothing.
302
+ if (url.pathname === "/__bosia/hold" && req.method === "POST") {
303
+ if (!reloadHeld) {
304
+ reloadHeld = true;
305
+ reloadQueuedWhileHeld = false;
306
+ }
307
+ if (holdSafetyTimer) clearTimeout(holdSafetyTimer);
308
+ holdSafetyTimer = setTimeout(() => {
309
+ holdSafetyTimer = null;
310
+ if (!reloadHeld) return;
311
+ console.warn("⏱️ Reload hold safety timeout — auto-resuming");
312
+ reloadHeld = false;
313
+ if (reloadQueuedWhileHeld) {
314
+ reloadQueuedWhileHeld = false;
315
+ broadcastReload();
316
+ }
317
+ }, HOLD_SAFETY_MS);
318
+ holdSafetyTimer.unref?.();
319
+ return Response.json({ ok: true, held: true });
320
+ }
321
+
322
+ if (url.pathname === "/__bosia/resume" && req.method === "POST") {
323
+ if (holdSafetyTimer) {
324
+ clearTimeout(holdSafetyTimer);
325
+ holdSafetyTimer = null;
326
+ }
327
+ reloadHeld = false;
328
+ const flushed = reloadQueuedWhileHeld;
329
+ reloadQueuedWhileHeld = false;
330
+ if (flushed) broadcastReload();
331
+ return Response.json({ ok: true, held: false, flushed });
332
+ }
333
+
238
334
  // Proxy everything else to the app server. Inject X-Forwarded-Host/Proto so
239
335
  // the app's CSRF origin check (gated behind TRUST_PROXY=true, also set in the
240
336
  // app env above) reconstructs the public-facing origin from the dev proxy
@@ -283,9 +379,9 @@ const devServer = Bun.serve({
283
379
  await Bun.sleep(250);
284
380
  continue;
285
381
  }
286
- return new Response("App server is starting...", {
382
+ return new Response(STARTING_PAGE, {
287
383
  status: 503,
288
- headers: { "Content-Type": "text/plain", "Retry-After": "1" },
384
+ headers: { "Content-Type": "text/html; charset=utf-8", "Retry-After": "1" },
289
385
  });
290
386
  }
291
387
  }
package/src/core/html.ts CHANGED
@@ -19,6 +19,12 @@ export const distManifest: { js: string[]; css: string[]; entry: string } = (()
19
19
  export const isDev = process.env.NODE_ENV !== "production";
20
20
  const cacheBust = isDev ? `?v=${Date.now()}` : "";
21
21
 
22
+ /** Inline theme bootstrap — runs before paint to avoid FOUC. theme ∈ light|dark|system (missing = system). */
23
+ const THEME_INIT_JS =
24
+ "try{var t=localStorage.getItem('theme');" +
25
+ "document.documentElement.classList.toggle('dark'," +
26
+ "t==='dark'||((t===null||t==='system')&&window.matchMedia('(prefers-color-scheme: dark)').matches))}catch(_){}";
27
+
22
28
  // ─── Safe JSON Serialization ──────────────────────────────
23
29
 
24
30
  /** Escapes JSON for safe embedding inside <script> tags. Prevents XSS via </script> injection. */
@@ -156,7 +162,7 @@ export function buildHtml(
156
162
  headOpenInterpolated +
157
163
  `\n ${faviconLine}${cssLinks}\n` +
158
164
  ` <link rel="stylesheet" href="/bosia-tw.css${cacheBust}">\n` +
159
- ` <script${n}>try{var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark');else document.documentElement.classList.remove('dark')}catch(_){}</script>\n` +
165
+ ` <script${n}>${THEME_INIT_JS}</script>\n` +
160
166
  ` ${fallbackTitle}${head}` +
161
167
  headCloseInterpolated +
162
168
  (body ? "" : `\n${SPINNER}`) +
@@ -175,7 +181,7 @@ export function buildHtml(
175
181
  ${head}
176
182
  ${cssLinks}
177
183
  <link rel="stylesheet" href="/bosia-tw.css${cacheBust}">
178
- <script${n}>try{var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark');else document.documentElement.classList.remove('dark')}catch(_){}</script>
184
+ <script${n}>${THEME_INIT_JS}</script>
179
185
  </head>
180
186
  <body>
181
187
  <div id="app">${body}</div>${scripts}${bodyEnd}
@@ -208,7 +214,7 @@ export function buildHtmlShellOpen(
208
214
  headOpenInterpolated +
209
215
  `\n ${faviconLine}${cssLinks}\n` +
210
216
  ` <link rel="stylesheet" href="/bosia-tw.css${cacheBust}">\n` +
211
- ` <script${n}>try{var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark');else document.documentElement.classList.remove('dark')}catch(_){}</script>\n` +
217
+ ` <script${n}>${THEME_INIT_JS}</script>\n` +
212
218
  ` <link rel="modulepreload" href="/dist/client/${distManifest.entry}${cacheBust}">`
213
219
  );
214
220
  }
@@ -220,7 +226,7 @@ export function buildHtmlShellOpen(
220
226
  ` <link rel="icon" type="image/svg+xml" href="/favicon.svg">\n` +
221
227
  ` ${cssLinks}\n` +
222
228
  ` <link rel="stylesheet" href="/bosia-tw.css${cacheBust}">\n` +
223
- ` <script${n}>try{var t=localStorage.getItem('theme');if(t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches))document.documentElement.classList.add('dark');else document.documentElement.classList.remove('dark')}catch(_){}</script>\n` +
229
+ ` <script${n}>${THEME_INIT_JS}</script>\n` +
224
230
  ` <link rel="modulepreload" href="/dist/client/${distManifest.entry}${cacheBust}">`
225
231
  );
226
232
  }
@@ -366,7 +366,7 @@ async function resolve(event: RequestEvent): Promise<Response> {
366
366
  }
367
367
  }
368
368
 
369
- const response = await handler({
369
+ const handlerResult = await handler({
370
370
  request,
371
371
  params: apiMatch.params,
372
372
  url,
@@ -374,6 +374,15 @@ async function resolve(event: RequestEvent): Promise<Response> {
374
374
  cookies,
375
375
  });
376
376
 
377
+ // Redirect returned (not thrown) — convert to a 303 Response.
378
+ if (handlerResult instanceof Redirect) {
379
+ return new Response(null, {
380
+ status: handlerResult.status,
381
+ headers: { Location: handlerResult.location },
382
+ });
383
+ }
384
+
385
+ const response = handlerResult as Response;
377
386
  const responseContentType = response.headers.get("content-type") ?? "";
378
387
  // SSE responses are long-lived pub/sub streams — caching the buffered
379
388
  // bytes would serve a stale finite snapshot to future subscribers and
@@ -423,6 +432,17 @@ async function resolve(event: RequestEvent): Promise<Response> {
423
432
 
424
433
  return response;
425
434
  } catch (err) {
435
+ // `throw redirect(303, "/")` from a +server.ts handler — turn it into
436
+ // a real 303 instead of a 500. Mirrors the page-action handler below.
437
+ if (err instanceof Redirect) {
438
+ return new Response(null, {
439
+ status: err.status,
440
+ headers: { Location: err.location },
441
+ });
442
+ }
443
+ if (err instanceof HttpError) {
444
+ return Response.json({ error: err.message }, { status: err.status });
445
+ }
426
446
  if (isDev) console.error("API route error:", err);
427
447
  else console.error("API route error:", (err as Error).message ?? err);
428
448
  if (isDev) reportDevErrorFromCatch(err);
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "bosia": "^{{BOSIA_VERSION}}",
15
- "svelte": "^5.20.0",
15
+ "svelte": "^5.56.3",
16
16
  "tailwind-merge": "^3.5.0"
17
17
  },
18
18
  "devDependencies": {
@@ -1,6 +1,8 @@
1
1
  @import "tailwindcss";
2
2
  @source "../src";
3
3
 
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
4
6
  /*
5
7
  * ─── shadcn-inspired Design Tokens ──────────────────────
6
8
  * CSS custom properties for light & dark themes.
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "bosia": "^{{BOSIA_VERSION}}",
15
- "svelte": "^5.20.0",
15
+ "svelte": "^5.56.3",
16
16
  "tailwind-merge": "^3.5.0"
17
17
  },
18
18
  "devDependencies": {
@@ -1,6 +1,8 @@
1
1
  @import "tailwindcss";
2
2
  @source "../src";
3
3
 
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
4
6
  /*
5
7
  * ─── shadcn-inspired Design Tokens ──────────────────────
6
8
  * CSS custom properties for light & dark themes.
@@ -12,10 +12,9 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "bosia": "^{{BOSIA_VERSION}}",
15
- "svelte": "^5.20.0",
15
+ "svelte": "^5.56.3",
16
16
  "tailwind-merge": "^3.5.0",
17
- "drizzle-orm": "^0.44.0",
18
- "postgres": "^3.4.0"
17
+ "drizzle-orm": "^0.44.0"
19
18
  },
20
19
  "devDependencies": {
21
20
  "@types/bun": "latest",
@@ -1,6 +1,8 @@
1
1
  @import "tailwindcss";
2
2
  @source "../src";
3
3
 
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
4
6
  /*
5
7
  * ─── shadcn-inspired Design Tokens ──────────────────────
6
8
  * CSS custom properties for light & dark themes.
@@ -1,14 +1,44 @@
1
1
  <script lang="ts">
2
2
  import { page } from "bosia/client";
3
3
  import AdminSidebar from "$lib/components/AdminSidebar.svelte";
4
+ import {
5
+ Breadcrumb,
6
+ BreadcrumbList,
7
+ BreadcrumbItem,
8
+ BreadcrumbLink,
9
+ BreadcrumbPage,
10
+ BreadcrumbSeparator,
11
+ } from "$lib/components/ui/breadcrumb";
4
12
 
5
13
  let { data, children }: { data: { user: { id: string; email: string } }; children: any } =
6
14
  $props();
15
+
16
+ const segments = $derived(page.url.pathname.split("/").filter(Boolean));
17
+ const label = (s: string) => s[0].toUpperCase() + s.slice(1);
18
+ const hrefAt = (i: number) => "/" + segments.slice(0, i + 1).join("/");
7
19
  </script>
8
20
 
9
21
  <div class="flex min-h-screen">
10
22
  <AdminSidebar currentPath={page.url.pathname} user={data.user} />
11
23
  <main class="flex-1 overflow-x-hidden p-6">
24
+ {#if segments.length > 0}
25
+ <Breadcrumb class="mb-4">
26
+ <BreadcrumbList>
27
+ {#each segments as segment, i}
28
+ <BreadcrumbItem>
29
+ {#if i === segments.length - 1}
30
+ <BreadcrumbPage>{label(segment)}</BreadcrumbPage>
31
+ {:else}
32
+ <BreadcrumbLink href={hrefAt(i)}>{label(segment)}</BreadcrumbLink>
33
+ {/if}
34
+ </BreadcrumbItem>
35
+ {#if i < segments.length - 1}
36
+ <BreadcrumbSeparator />
37
+ {/if}
38
+ {/each}
39
+ </BreadcrumbList>
40
+ </Breadcrumb>
41
+ {/if}
12
42
  {@render children()}
13
43
  </main>
14
44
  </div>
@@ -1,3 +1,11 @@
1
+ <svelte:head>
2
+ <title>Welcome to your shop</title>
3
+ <meta
4
+ name="description"
5
+ content="A Bosia shop starter — auth, RBAC, S3 uploads, products & cart."
6
+ />
7
+ </svelte:head>
8
+
1
9
  <main class="flex min-h-[80vh] flex-col items-center justify-center gap-6 p-8">
2
10
  <div class="flex flex-col items-center gap-3 text-center">
3
11
  <img src="/favicon.svg" alt="" class="size-16" />
@@ -1,2 +0,0 @@
1
- # Database
2
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/{{PROJECT_NAME}}
@@ -1,7 +0,0 @@
1
- node_modules
2
- dist
3
- build
4
- .bosia
5
- bun.lock
6
- public/bosia-tw.css
7
- bosia.json
@@ -1,9 +0,0 @@
1
- {
2
- "useTabs": true,
3
- "tabWidth": 2,
4
- "singleQuote": false,
5
- "trailingComma": "all",
6
- "printWidth": 100,
7
- "plugins": ["prettier-plugin-svelte"],
8
- "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
9
- }
@@ -1,69 +0,0 @@
1
- # {{PROJECT_NAME}}
2
-
3
- A fullstack app built with [Bosia](https://github.com/bosapi/bosia) + Drizzle ORM + PostgreSQL.
4
-
5
- ## Prerequisites
6
-
7
- - [Bun](https://bun.sh/) v1.1+
8
- - [PostgreSQL](https://www.postgresql.org/) running locally or remotely
9
-
10
- ## Getting Started
11
-
12
- ```bash
13
- # Copy env and set your DATABASE_URL
14
- cp .env.example .env
15
-
16
- # Push schema to database
17
- bun run db:push
18
-
19
- # Seed initial data
20
- bun run db:seed
21
-
22
- # Start dev server
23
- bun x bosia dev
24
- ```
25
-
26
- Visit [http://localhost:9000](http://localhost:9000) to see the app.
27
-
28
- ## Scripts
29
-
30
- | Command | Description |
31
- | --------------------- | -------------------------------------- |
32
- | `bun x bosia dev` | Start dev server with HMR |
33
- | `bun x bosia build` | Production build |
34
- | `bun x bosia start` | Start production server |
35
- | `bun run db:generate` | Generate migration from schema changes |
36
- | `bun run db:migrate` | Apply pending migrations |
37
- | `bun run db:push` | Push schema directly (dev shortcut) |
38
- | `bun run db:studio` | Open Drizzle Studio GUI |
39
- | `bun run db:seed` | Run pending seed files |
40
-
41
- ## Project Structure
42
-
43
- ```
44
- src/
45
- ├── features/
46
- │ ├── drizzle/ # DB infrastructure
47
- │ │ ├── index.ts # Connection singleton
48
- │ │ ├── schemas.ts # Schema aggregator
49
- │ │ ├── migrations/ # Drizzle Kit output
50
- │ │ └── seeds/ # Seed files + runner
51
- │ └── todo/ # Business feature
52
- │ ├── schemas/ # Table definitions
53
- │ ├── queries.ts # Typed CRUD
54
- │ └── types.ts # Inferred types
55
- ├── lib/components/todo/ # UI components
56
- └── routes/
57
- ├── todos/ # CRUD page with form actions
58
- └── api/todos/ # REST API
59
- ```
60
-
61
- ## Adding Features
62
-
63
- ```bash
64
- # Add DB support to any Bosia app
65
- bosia feat drizzle
66
-
67
- # Add the todo feature (requires drizzle)
68
- bosia feat todo
69
- ```
@@ -1,12 +0,0 @@
1
- node_modules/
2
- dist/
3
- .bosia/
4
- .DS_Store
5
- *.log
6
-
7
- # Generated Tailwind output
8
- public/bosia-tw.css
9
-
10
- # Local env overrides — never commit secrets
11
- .env*.local
12
- .env
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "bosia";
2
- import { inspector } from "bosia/plugins/inspector";
3
-
4
- export default defineConfig({
5
- plugins: [
6
- // Dev-only: Alt+click any element on the page to open its source in your editor.
7
- // Change `editor` to "cursor" or "zed" if you don't use VS Code.
8
- inspector({ editor: "code" }),
9
- ],
10
- });
@@ -1,3 +0,0 @@
1
- Update .env with your DATABASE_URL
2
- bun run db:generate
3
- bun run db:migrate
@@ -1,24 +0,0 @@
1
- {
2
- "name": "{{PROJECT_NAME}}",
3
- "private": true,
4
- "type": "module",
5
- "scripts": {
6
- "dev": "bosia dev",
7
- "build": "bosia build",
8
- "start": "bosia start",
9
- "check": "tsc --noEmit && prettier --check .",
10
- "format": "prettier --write .",
11
- "format:check": "prettier --check ."
12
- },
13
- "dependencies": {
14
- "bosia": "^{{BOSIA_VERSION}}",
15
- "svelte": "^5.20.0",
16
- "tailwind-merge": "^3.5.0"
17
- },
18
- "devDependencies": {
19
- "@types/bun": "latest",
20
- "prettier": "^3.3.0",
21
- "prettier-plugin-svelte": "^3.2.0",
22
- "typescript": "^5"
23
- }
24
- }
File without changes
@@ -1,14 +0,0 @@
1
- <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
2
- <!-- Top block -->
3
- <rect fill="currentColor" x="50" y="50" width="28" height="28" rx="6"/>
4
- <rect fill="currentColor" x="86" y="50" width="60" height="28" rx="6"/>
5
-
6
- <!-- Middle block -->
7
- <rect fill="currentColor" x="86" y="86" width="72" height="28" rx="6"/>
8
-
9
- <!-- Bottom block -->
10
- <rect fill="currentColor" x="86" y="122" width="60" height="28" rx="6"/>
11
-
12
- <!-- Connector bar on left -->
13
- <rect fill="currentColor" x="50" y="50" width="28" height="100" rx="6"/>
14
- </svg>
@@ -1,14 +0,0 @@
1
- <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
2
- <!-- Top block -->
3
- <rect fill="#f0f0f0" x="50" y="50" width="28" height="28" rx="6"/>
4
- <rect fill="#f0f0f0" x="86" y="50" width="60" height="28" rx="6"/>
5
-
6
- <!-- Middle block -->
7
- <rect fill="#f0f0f0" x="86" y="86" width="72" height="28" rx="6"/>
8
-
9
- <!-- Bottom block -->
10
- <rect fill="#f0f0f0" x="86" y="122" width="60" height="28" rx="6"/>
11
-
12
- <!-- Connector bar on left -->
13
- <rect fill="#f0f0f0" x="50" y="50" width="28" height="100" rx="6"/>
14
- </svg>
@@ -1,14 +0,0 @@
1
- <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
2
- <!-- Top block -->
3
- <rect fill="#1a1a1a" x="50" y="50" width="28" height="28" rx="6"/>
4
- <rect fill="#1a1a1a" x="86" y="50" width="60" height="28" rx="6"/>
5
-
6
- <!-- Middle block -->
7
- <rect fill="#1a1a1a" x="86" y="86" width="72" height="28" rx="6"/>
8
-
9
- <!-- Bottom block -->
10
- <rect fill="#1a1a1a" x="86" y="122" width="60" height="28" rx="6"/>
11
-
12
- <!-- Connector bar on left -->
13
- <rect fill="#1a1a1a" x="50" y="50" width="28" height="100" rx="6"/>
14
- </svg>
@@ -1,132 +0,0 @@
1
- @import "tailwindcss";
2
- @source "../src";
3
-
4
- /*
5
- * ─── shadcn-inspired Design Tokens ──────────────────────
6
- * CSS custom properties for light & dark themes.
7
- * Uses HSL values so Tailwind can apply opacity modifiers.
8
- */
9
-
10
- @theme {
11
- --color-background: hsl(var(--background));
12
- --color-foreground: hsl(var(--foreground));
13
-
14
- --color-card: hsl(var(--card));
15
- --color-card-foreground: hsl(var(--card-foreground));
16
-
17
- --color-popover: hsl(var(--popover));
18
- --color-popover-foreground: hsl(var(--popover-foreground));
19
-
20
- --color-primary: hsl(var(--primary));
21
- --color-primary-foreground: hsl(var(--primary-foreground));
22
-
23
- --color-secondary: hsl(var(--secondary));
24
- --color-secondary-foreground: hsl(var(--secondary-foreground));
25
-
26
- --color-muted: hsl(var(--muted));
27
- --color-muted-foreground: hsl(var(--muted-foreground));
28
-
29
- --color-accent: hsl(var(--accent));
30
- --color-accent-foreground: hsl(var(--accent-foreground));
31
-
32
- --color-destructive: hsl(var(--destructive));
33
- --color-destructive-foreground: hsl(var(--destructive-foreground));
34
-
35
- --color-border: hsl(var(--border));
36
- --color-input: hsl(var(--input));
37
- --color-ring: hsl(var(--ring));
38
-
39
- --radius-sm: calc(var(--radius) - 4px);
40
- --radius-md: calc(var(--radius) - 2px);
41
- --radius-lg: var(--radius);
42
- --radius-xl: calc(var(--radius) + 4px);
43
- }
44
-
45
- /* ─── Light Theme (Default) ─────────────────────────────── */
46
-
47
- :root {
48
- --background: 0 0% 100%;
49
- --foreground: 222.2 84% 4.9%;
50
-
51
- --card: 0 0% 100%;
52
- --card-foreground: 222.2 84% 4.9%;
53
-
54
- --popover: 0 0% 100%;
55
- --popover-foreground: 222.2 84% 4.9%;
56
-
57
- --primary: 222.2 47.4% 11.2%;
58
- --primary-foreground: 210 40% 98%;
59
-
60
- --secondary: 210 40% 96.1%;
61
- --secondary-foreground: 222.2 47.4% 11.2%;
62
-
63
- --muted: 210 40% 96.1%;
64
- --muted-foreground: 215.4 16.3% 46.9%;
65
-
66
- --accent: 210 40% 96.1%;
67
- --accent-foreground: 222.2 47.4% 11.2%;
68
-
69
- --destructive: 0 84.2% 60.2%;
70
- --destructive-foreground: 210 40% 98%;
71
-
72
- --border: 214.3 31.8% 91.4%;
73
- --input: 214.3 31.8% 91.4%;
74
- --ring: 222.2 84% 4.9%;
75
-
76
- --radius: 0.5rem;
77
- }
78
-
79
- /* ─── Dark Theme ─────────────────────────────────────────── */
80
-
81
- .dark {
82
- --background: 222.2 84% 4.9%;
83
- --foreground: 210 40% 98%;
84
-
85
- --card: 222.2 84% 4.9%;
86
- --card-foreground: 210 40% 98%;
87
-
88
- --popover: 222.2 84% 4.9%;
89
- --popover-foreground: 210 40% 98%;
90
-
91
- --primary: 210 40% 98%;
92
- --primary-foreground: 222.2 47.4% 11.2%;
93
-
94
- --secondary: 217.2 32.6% 17.5%;
95
- --secondary-foreground: 210 40% 98%;
96
-
97
- --muted: 217.2 32.6% 17.5%;
98
- --muted-foreground: 215 20.2% 65.1%;
99
-
100
- --accent: 217.2 32.6% 17.5%;
101
- --accent-foreground: 210 40% 98%;
102
-
103
- --destructive: 0 62.8% 30.6%;
104
- --destructive-foreground: 210 40% 98%;
105
-
106
- --border: 217.2 32.6% 17.5%;
107
- --input: 217.2 32.6% 17.5%;
108
- --ring: 212.7 26.8% 83.9%;
109
- }
110
-
111
- /* ─── Base Styles ────────────────────────────────────────── */
112
-
113
- @layer base {
114
- * {
115
- border-color: theme(--color-border);
116
- }
117
-
118
- body {
119
- background-color: theme(--color-background);
120
- color: theme(--color-foreground);
121
- font-family:
122
- "Inter",
123
- system-ui,
124
- -apple-system,
125
- BlinkMacSystemFont,
126
- "Segoe UI",
127
- Roboto,
128
- "Helvetica Neue",
129
- Arial,
130
- sans-serif;
131
- }
132
- }
@@ -1,14 +0,0 @@
1
- /// <reference types="svelte" />
2
-
3
- declare module "*.svelte" {
4
- import type { Component } from "svelte";
5
- const component: Component<Record<string, any>, Record<string, any>, any>;
6
- export default component;
7
- }
8
-
9
- declare namespace App {
10
- interface Locals {
11
- db: import("./features/drizzle").Database;
12
- requestTime: number;
13
- }
14
- }
@@ -1,11 +0,0 @@
1
- <!doctype html>
2
- <html lang="%bosia.lang%">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- %bosia.head%
7
- </head>
8
- <body>
9
- %bosia.body%
10
- </body>
11
- </html>
@@ -1,20 +0,0 @@
1
- import { sequence } from "bosia";
2
- import type { Handle } from "bosia";
3
- import { db } from "./features/drizzle";
4
-
5
- const dbHandle: Handle = async ({ event, resolve }) => {
6
- event.locals.db = db;
7
- return resolve(event);
8
- };
9
-
10
- const loggingHandle: Handle = async ({ event, resolve }) => {
11
- const start = Date.now();
12
- event.locals.requestTime = start;
13
- const res = await resolve(event);
14
- const ms = Date.now() - start;
15
- console.log(`[${event.request.method}] ${event.url.pathname} ${res.status} (${ms}ms)`);
16
- res.headers.set("X-Response-Time", `${ms}ms`);
17
- return res;
18
- };
19
-
20
- export const handle = sequence(dbHandle, loggingHandle);
@@ -1 +0,0 @@
1
- export { cn } from "bosia";
@@ -1,53 +0,0 @@
1
- <script lang="ts">
2
- import type { LayoutData } from "./$types";
3
- let { data }: { data: LayoutData } = $props();
4
- </script>
5
-
6
- <svelte:head>
7
- <title>{data.appName}</title>
8
- </svelte:head>
9
-
10
- <div class="flex min-h-screen flex-col bg-background text-foreground">
11
- <div class="flex flex-1 flex-col items-center justify-center gap-8 px-4">
12
- <div class="text-center">
13
- <img src="/favicon.svg" alt="" class="mx-auto mb-6 size-16" />
14
- <h1 class="text-4xl font-bold tracking-tight">{data.appName}</h1>
15
- <p class="mt-2 text-lg text-muted-foreground">
16
- Fullstack app powered by Bosia + Drizzle ORM + PostgreSQL
17
- </p>
18
- </div>
19
-
20
- <div class="flex gap-4">
21
- <a
22
- href="/todos"
23
- class="inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
24
- >
25
- Todo App
26
- </a>
27
- <a
28
- href="/api/todos"
29
- target="_blank"
30
- class="inline-flex items-center justify-center rounded-md border border-input bg-background px-6 py-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors"
31
- >
32
- REST API
33
- </a>
34
- </div>
35
-
36
- <div class="mt-8 grid max-w-2xl grid-cols-1 gap-4 sm:grid-cols-3">
37
- <div class="rounded-lg border border-border p-4 text-center">
38
- <p class="text-sm font-medium">Bosia</p>
39
- <p class="mt-1 text-xs text-muted-foreground">Bun + Svelte 5 + Elysia</p>
40
- </div>
41
- <div class="rounded-lg border border-border p-4 text-center">
42
- <p class="text-sm font-medium">Drizzle ORM</p>
43
- <p class="mt-1 text-xs text-muted-foreground">Type-safe SQL queries</p>
44
- </div>
45
- <div class="rounded-lg border border-border p-4 text-center">
46
- <p class="text-sm font-medium">PostgreSQL</p>
47
- <p class="mt-1 text-xs text-muted-foreground">Production-ready database</p>
48
- </div>
49
- </div>
50
- </div>
51
-
52
- <footer class="border-t py-4 text-center text-sm text-muted-foreground">Powered by Bosia</footer>
53
- </div>
@@ -1,19 +0,0 @@
1
- <script lang="ts">
2
- import type { ErrorProps } from "./$types";
3
- let { error }: ErrorProps = $props();
4
- </script>
5
-
6
- <svelte:head>
7
- <title>{error.status} — {error.message}</title>
8
- </svelte:head>
9
-
10
- <div class="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
11
- <p class="text-8xl font-bold text-gray-200">{error.status}</p>
12
- <p class="text-2xl font-semibold text-gray-700">{error.message}</p>
13
- <a
14
- href="/"
15
- class="mt-4 px-5 py-2 rounded-lg bg-gray-900 text-white text-sm hover:bg-gray-700 transition-colors"
16
- >
17
- Go home
18
- </a>
19
- </div>
@@ -1,8 +0,0 @@
1
- import type { LoadEvent } from "bosia";
2
-
3
- export async function load({ locals }: LoadEvent) {
4
- return {
5
- appName: "Bosia Todo",
6
- requestTime: (locals.requestTime as number | null) ?? null,
7
- };
8
- }
@@ -1,6 +0,0 @@
1
- <script lang="ts">
2
- import "../app.css";
3
- let { children }: { children: any } = $props();
4
- </script>
5
-
6
- {@render children()}
@@ -1,6 +0,0 @@
1
- {
2
- "features": ["todo"],
3
- "featureOptions": {
4
- "drizzle.dialect": "postgres"
5
- }
6
- }
@@ -1,22 +0,0 @@
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": [".", ".bosia/types"],
15
- "paths": {
16
- "$lib": ["./src/lib"],
17
- "$lib/*": ["./src/lib/*"]
18
- }
19
- },
20
- "include": ["src/**/*"],
21
- "exclude": ["node_modules", "dist"]
22
- }