bosia 1.2.5 → 1.2.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 (44) hide show
  1. package/README.md +5 -6
  2. package/package.json +2 -1
  3. package/src/core/backend.ts +87 -3
  4. package/src/core/basePath.ts +30 -0
  5. package/src/core/cache.ts +378 -30
  6. package/src/core/client/App.svelte +19 -11
  7. package/src/core/client/hydrate.ts +17 -9
  8. package/src/core/client/navigation.ts +1 -1
  9. package/src/core/client/prefetch.ts +66 -41
  10. package/src/core/client/router.svelte.ts +41 -22
  11. package/src/core/cors.ts +17 -1
  12. package/src/core/csrf.ts +32 -19
  13. package/src/core/dedup.ts +51 -7
  14. package/src/core/dev.ts +11 -1
  15. package/src/core/devWsProxy.ts +144 -0
  16. package/src/core/env.ts +7 -0
  17. package/src/core/errors.ts +23 -6
  18. package/src/core/hooks.ts +1 -1
  19. package/src/core/html.ts +13 -1
  20. package/src/core/plugins/access-log.ts +128 -0
  21. package/src/core/prerender.ts +143 -6
  22. package/src/core/proxy.ts +48 -0
  23. package/src/core/renderer.ts +70 -29
  24. package/src/core/routeTypes.ts +16 -1
  25. package/src/core/server.bun.ts +6 -1
  26. package/src/core/server.ts +198 -77
  27. package/src/core/server.workers.ts +3 -2
  28. package/src/core/types/plugin.ts +2 -1
  29. package/src/core/websocket.ts +232 -0
  30. package/src/core/workersGuard.ts +18 -3
  31. package/src/lib/index.ts +1 -0
  32. package/templates/default/.env.example +20 -5
  33. package/templates/default/tsconfig.json +1 -1
  34. package/templates/demo/.env.example +20 -5
  35. package/templates/demo/src/hooks.server.ts +3 -12
  36. package/templates/demo/tsconfig.json +1 -1
  37. package/templates/shop/src/app.d.ts +0 -1
  38. package/templates/shop/src/hooks.server.ts +3 -11
  39. package/templates/shop/src/routes/+layout.server.ts +0 -1
  40. package/templates/shop/tsconfig.json +1 -1
  41. package/templates/store/src/app.d.ts +0 -1
  42. package/templates/store/src/hooks.server.ts +3 -11
  43. package/templates/store/src/routes/+layout.server.ts +0 -1
  44. package/templates/store/tsconfig.json +1 -1
@@ -0,0 +1,232 @@
1
+ // ─── WebSockets (Bun only) ────────────────────────────────
2
+ // A `+server.ts` exports `websocket = { upgrade?, open, message, close, … }`.
3
+ // resolve() runs `upgrade()` after the hooks, then hands the request to Bun's
4
+ // `server.upgrade()`. Bun.serve takes one websocket handler for the whole
5
+ // server, so the upgrade carries the route's handlers in an envelope; open()
6
+ // keeps them per socket and sets `ws.data` to what upgrade() returned.
7
+ //
8
+ // Subprotocols are not selectable yet: `upgrade()` returns only data. To add
9
+ // it, let the export declare `protocols: string[]` and send the first one the
10
+ // client offers as `Sec-WebSocket-Protocol` in server.upgrade's headers. The
11
+ // dev proxy already passes it through.
12
+
13
+ import { AsyncLocalStorage } from "node:async_hooks";
14
+
15
+ import type { Server, ServerWebSocket, WebSocketHandler as BunWebSocketHandler } from "bun";
16
+
17
+ import type { RequestEvent } from "./hooks.ts";
18
+
19
+ /** What a `+server.ts` exports as `websocket`. `ws` is Bun's ServerWebSocket. */
20
+ export interface WebSocketHandler<T extends object = {}, E = RequestEvent> {
21
+ /**
22
+ * Runs after `hooks.server.ts`, so `event.locals` is set. Return the
23
+ * connection's `ws.data` (a fresh object per connection), or a Response to
24
+ * refuse. A thrown `error()` / `redirect()` answers like an API handler.
25
+ */
26
+ upgrade?(event: E): T | Response | void | Promise<T | Response | void>;
27
+ open?(ws: ServerWebSocket<T>): unknown;
28
+ /** `string` for text frames, `Buffer` for binary frames. */
29
+ message?(ws: ServerWebSocket<T>, message: string | Buffer): unknown;
30
+ /** The socket can take more data again after `send()` returned -1. */
31
+ drain?(ws: ServerWebSocket<T>): unknown;
32
+ close?(ws: ServerWebSocket<T>, code: number, reason: string): unknown;
33
+ ping?(ws: ServerWebSocket<T>, data: Buffer): unknown;
34
+ pong?(ws: ServerWebSocket<T>, data: Buffer): unknown;
35
+ }
36
+
37
+ export interface WebSocketOptions {
38
+ maxPayloadLength: number;
39
+ idleTimeout: number;
40
+ backpressureLimit: number;
41
+ perMessageDeflate: boolean;
42
+ }
43
+
44
+ type Socket = ServerWebSocket<any>;
45
+
46
+ /** What server.upgrade() carries until open() unwraps it. */
47
+ class Envelope {
48
+ constructor(
49
+ readonly handler: WebSocketHandler<any, any>,
50
+ readonly data: object,
51
+ ) {}
52
+ }
53
+
54
+ // Per socket, not on ws.data: one object returned by two routes (a cached
55
+ // session user, say) must not carry either route's handlers.
56
+ const routeOf = new WeakMap<Socket, WebSocketHandler<any, any>>();
57
+
58
+ // Sec-WebSocket-Key too: a stray Upgrade header on a plain GET (a proxy, a
59
+ // scanner) must reach the GET handler, not run upgrade() and then 400.
60
+ export function isUpgradeRequest(request: Request): boolean {
61
+ return (
62
+ request.method === "GET" &&
63
+ request.headers.get("upgrade")?.toLowerCase() === "websocket" &&
64
+ request.headers.has("sec-websocket-key")
65
+ );
66
+ }
67
+
68
+ // One per upgrade request, set by BosiaApp.fetch. It holds the Request Bun gave
69
+ // (server.upgrade() needs that one, and a hook may pass resolve() a new one)
70
+ // and whether the upgrade happened — a per-request flag, so a hook that
71
+ // rebuilds or mutates the returned Response can't hide or leak it.
72
+ interface UpgradeContext {
73
+ request?: Request;
74
+ server?: Server<any>;
75
+ upgraded: boolean;
76
+ /** Set by open(), which Bun runs inside server.upgrade(). */
77
+ socket?: Socket;
78
+ }
79
+
80
+ const upgrades = new AsyncLocalStorage<UpgradeContext>();
81
+
82
+ export async function runUpgrade<T>(
83
+ request: Request,
84
+ server: Server<any>,
85
+ fn: () => Promise<T>,
86
+ ): Promise<T> {
87
+ const ctx: UpgradeContext = { request, server, upgraded: false };
88
+ try {
89
+ return await upgrades.run(ctx, fn);
90
+ } finally {
91
+ // A timer or pool started during this request keeps the context: it must
92
+ // not hold the Request, nor see isUpgraded() in its own later fetches.
93
+ ctx.request = ctx.server = ctx.socket = undefined;
94
+ ctx.upgraded = false;
95
+ }
96
+ }
97
+
98
+ /** True once this request has been upgraded: Bun already sent the 101. */
99
+ export function isUpgraded(): boolean {
100
+ return upgrades.getStore()?.upgraded === true;
101
+ }
102
+
103
+ /** A hook threw after the 101: close the socket, as a handler error would. */
104
+ export function closeUpgraded(): void {
105
+ const ws = upgrades.getStore()?.socket;
106
+ if (ws) closeEach([ws], 1011, "Internal error");
107
+ }
108
+
109
+ let closing = false;
110
+
111
+ // Bun answers without a protocol, so a browser that asked for one fails the
112
+ // handshake with no error on the server. Warn once per route, not per connect.
113
+ const warnedProtocol = new WeakSet<WebSocketHandler<any, any>>();
114
+
115
+ function warnProtocol(handler: WebSocketHandler<any, any>, request: Request): void {
116
+ const asked = request.headers.get("sec-websocket-protocol");
117
+ if (!asked || warnedProtocol.has(handler)) return;
118
+ warnedProtocol.add(handler);
119
+ console.warn(
120
+ `[WebSocket] ${new URL(request.url).pathname}: the client asked for Sec-WebSocket-Protocol ` +
121
+ `"${asked}", but Bosia can't pick one yet. Browsers fail this handshake; drop the ` +
122
+ `protocols argument from new WebSocket().`,
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Upgrade the current request with `data` as `ws.data`. Returns a placeholder
128
+ * Response (Bun ignores it; isUpgraded() is the signal), or the Response to
129
+ * send instead (bad handshake, a runtime without WebSockets).
130
+ */
131
+ export function upgradeRequest(
132
+ handler: WebSocketHandler<any, any>,
133
+ data: object,
134
+ headers: Headers,
135
+ ): Response {
136
+ const ctx = upgrades.getStore();
137
+ if (!ctx?.request || !ctx.server) {
138
+ return new Response("WebSockets are not supported here", { status: 501 });
139
+ }
140
+ warnProtocol(handler, ctx.request);
141
+ if (!ctx.server.upgrade(ctx.request, { data: new Envelope(handler, data) as any, headers })) {
142
+ return new Response("Bad WebSocket handshake", { status: 400 });
143
+ }
144
+ ctx.upgraded = true;
145
+ return new Response(null, { status: 200 });
146
+ }
147
+
148
+ // ─── Shared Bun handlers ─────────────────────────────────
149
+
150
+ const sockets = new Set<Socket>();
151
+
152
+ function fail(ws: Socket, err: unknown): void {
153
+ console.error("WebSocket handler error:", err);
154
+ closeEach([ws], 1011, "Internal error");
155
+ }
156
+
157
+ // A user handler's throw or rejection closes that socket with 1011; it must
158
+ // never reach the prod `uncaughtException` handler, which exits the process.
159
+ //
160
+ // Bun runs open() inside server.upgrade(), so in the upgrade request's async
161
+ // context; exit() it, so a timer or callback started in open() doesn't hold
162
+ // that request or see isUpgraded() in its own in-process fetches. (Other
163
+ // events run outside it; exit() is then a no-op.)
164
+ function run(ws: Socket, name: Exclude<keyof WebSocketHandler, "upgrade">, args: unknown[]): void {
165
+ upgrades.exit(() => call(ws, name, args));
166
+ }
167
+
168
+ function call(ws: Socket, name: Exclude<keyof WebSocketHandler, "upgrade">, args: unknown[]): void {
169
+ const handler = routeOf.get(ws);
170
+ const fn = handler?.[name] as ((...a: unknown[]) => unknown) | undefined;
171
+ if (!fn) return;
172
+ try {
173
+ const out = fn.call(handler, ws, ...args);
174
+ if (out instanceof Promise) out.catch((err) => fail(ws, err));
175
+ } catch (err) {
176
+ fail(ws, err);
177
+ }
178
+ }
179
+
180
+ export const websocketHandlers: BunWebSocketHandler<any> = {
181
+ open(ws) {
182
+ // The very object upgrade() returned becomes ws.data (an app may keep it
183
+ // in a registry).
184
+ const envelope = ws.data as Envelope;
185
+ routeOf.set(ws, envelope.handler);
186
+ ws.data = envelope.data;
187
+ sockets.add(ws);
188
+ const ctx = upgrades.getStore();
189
+ if (ctx) ctx.socket = ws;
190
+ run(ws, "open", []);
191
+ // An upgrade still in its async upgrade() during the shutdown sweep opens
192
+ // after it: the app has seen open() and gets close(), so it can clean up.
193
+ if (closing) ws.close(1001, "Server shutting down");
194
+ },
195
+ message(ws, message) {
196
+ run(ws, "message", [message]);
197
+ },
198
+ drain(ws) {
199
+ run(ws, "drain", []);
200
+ },
201
+ close(ws, code, reason) {
202
+ sockets.delete(ws);
203
+ run(ws, "close", [code, reason]);
204
+ },
205
+ ping(ws, data) {
206
+ run(ws, "ping", [data]);
207
+ },
208
+ pong(ws, data) {
209
+ run(ws, "pong", [data]);
210
+ },
211
+ };
212
+
213
+ /** Shutdown: close every open socket with 1001 (clients reconnect elsewhere),
214
+ * and any that opens from here on. */
215
+ export function closeAllSockets(): void {
216
+ closing = true;
217
+ closeEach(sockets, 1001, "Server shutting down");
218
+ }
219
+
220
+ export function closeEach(
221
+ list: Iterable<ServerWebSocket<any>>,
222
+ code: number,
223
+ reason: string,
224
+ ): void {
225
+ for (const ws of [...list]) {
226
+ try {
227
+ ws.close(code, reason);
228
+ } catch {
229
+ /* already closed */
230
+ }
231
+ }
232
+ }
@@ -19,6 +19,7 @@ export interface WorkersGuardHit {
19
19
  }
20
20
 
21
21
  const SERVER_FILE = /(^|\/)(hooks\.server\.(ts|js)|\+server\.(ts|js)|\+[^/]*\.server\.(ts|js))$/;
22
+ const API_FILE = /(^|\/)\+server\.(ts|js)$/;
22
23
  const LIB_SERVER = /^lib\/server\/.*\.(ts|js)$/;
23
24
  const FS_MODULES = new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
24
25
 
@@ -54,13 +55,27 @@ export function findWorkersIncompatible(srcDir = "./src"): WorkersGuardHit[] {
54
55
  const lines = source.split("\n");
55
56
 
56
57
  let imports: { path: string }[] = [];
58
+ let exports: string[] = [];
57
59
  try {
58
- imports = new Bun.Transpiler({ loader: rel.endsWith(".js") ? "js" : "ts" }).scan(
59
- source,
60
- ).imports;
60
+ ({ imports, exports } = new Bun.Transpiler({
61
+ loader: rel.endsWith(".js") ? "js" : "ts",
62
+ }).scan(source));
61
63
  } catch {
62
64
  continue; // syntax error — the real build reports it better
63
65
  }
66
+ // Any export form (`const`, `let`, `export { websocket }`) shows up here.
67
+ if (API_FILE.test(rel) && exports.includes("websocket")) {
68
+ const i = Math.max(
69
+ 0,
70
+ lines.findIndex((l) => /\bwebsocket\b/.test(l) && /\bexport\b/.test(l)),
71
+ );
72
+ hits.push({
73
+ file,
74
+ line: i + 1,
75
+ col: 1,
76
+ message: "exports `websocket` — WebSockets are Bun-only for now",
77
+ });
78
+ }
64
79
  for (const { path } of imports) {
65
80
  const message = badImport(path);
66
81
  if (!message) continue;
package/src/lib/index.ts CHANGED
@@ -34,6 +34,7 @@ export type {
34
34
  PlatformEnv,
35
35
  } from "../core/hooks.ts";
36
36
  export type { CsrfConfig } from "../core/csrf.ts";
37
+ export type { WebSocketHandler } from "../core/websocket.ts";
37
38
  export type { CorsConfig } from "../core/cors.ts";
38
39
  export { defineConfig } from "../core/types/plugin.ts";
39
40
  export type {
@@ -48,6 +48,20 @@ PUBLIC_STATIC_APP_NAME=My Bosia App
48
48
  #
49
49
  # Max cache entries (LRU). Set to 0 to disable the cache entirely. Defaults to 500.
50
50
  # CACHE_MAX_ENTRIES=500
51
+ # Memory for all cached responses together, in bytes (0 = entry count only).
52
+ # CACHE_MAX_BYTES=104857600
53
+ # Query params left out of the page cache key: a link with ?utm_source=… or
54
+ # ?fbclid=… gets the cached page of the same URL without them, instead of a new
55
+ # render per ad click. Comma-separated; `name*` matches a prefix; `none` turns
56
+ # it off. Without that cached page, or when load()/metadata() reads one of
57
+ # these params, the full URL is cached on its own, as before.
58
+ # Defaults to the list below. Setting it replaces the list.
59
+ # CACHE_IGNORE_PARAMS=utm_*,fbclid,gclid,gbraid,wbraid,dclid,msclkid,twclid,ttclid,li_fat_id,igshid,yclid,mc_cid,mc_eid,_ga,_gl
60
+ # How long a request waits for another one building the same cached response
61
+ # before building it itself, in ms, so one that hangs can't block its URL.
62
+ # 0 or Infinity = wait for it. Defaults to 60000 (60s), or METADATA_TIMEOUT +
63
+ # LOAD_TIMEOUT + 10s when that is longer.
64
+ # CACHE_WAIT_TIMEOUT=60000
51
65
 
52
66
  # Static files and prerendered pages are kept in memory after their first
53
67
  # request (Bun only; ignored on Cloudflare Workers). Total budget in bytes,
@@ -56,13 +70,14 @@ PUBLIC_STATIC_APP_NAME=My Bosia App
56
70
  # Largest file kept in memory, in bytes. Defaults to 1048576 (1MB).
57
71
  # ASSET_CACHE_MAX_FILE_BYTES=1048576
58
72
 
59
- # Timeout for load() functions (layout + page) in milliseconds. Defaults to 5000 (5s).
60
- # Set to 0 or Infinity to disable.
61
- # LOAD_TIMEOUT=5000
73
+ # Timeout for load() functions (layout + page) in milliseconds. Defaults to 30000 (30s).
74
+ # A page's time includes its `await parent()`. Bun drops a silent connection after
75
+ # IDLE_TIMEOUT (10s): raise it too for slower loaders. Set to 0 or Infinity to disable.
76
+ # LOAD_TIMEOUT=30000
62
77
 
63
- # Timeout for metadata() functions in milliseconds. Defaults to 3000 (3s).
78
+ # Timeout for metadata() functions in milliseconds. Defaults to 10000 (10s).
64
79
  # Set to 0 or Infinity to disable.
65
- # METADATA_TIMEOUT=3000
80
+ # METADATA_TIMEOUT=10000
66
81
 
67
82
  # Timeout for prerender fetches during build in milliseconds. Defaults to 5000 (5s).
68
83
  # Set to 0 or Infinity to disable.
@@ -9,7 +9,7 @@
9
9
  "allowImportingTsExtensions": true,
10
10
  "noEmit": true,
11
11
  "verbatimModuleSyntax": true,
12
- "types": ["bun-types"],
12
+ "types": ["bun"],
13
13
  "lib": ["dom", "dom.iterable", "esnext"],
14
14
  "rootDirs": [".", ".bosia/types"],
15
15
  "paths": {
@@ -15,6 +15,20 @@ BODY_SIZE_LIMIT=512K
15
15
  # Per-user pages stay isolated. Disable per-route with `export const cache = false`.
16
16
  # CACHE_KEYS=session,sid,auth,token,jwt,Authorization
17
17
  # CACHE_MAX_ENTRIES=500
18
+ # Memory for all cached responses together, in bytes (0 = entry count only).
19
+ # CACHE_MAX_BYTES=104857600
20
+ # Query params left out of the page cache key: a link with ?utm_source=… or
21
+ # ?fbclid=… gets the cached page of the same URL without them, instead of a new
22
+ # render per ad click. Comma-separated; `name*` matches a prefix; `none` turns
23
+ # it off. Without that cached page, or when load()/metadata() reads one of
24
+ # these params, the full URL is cached on its own, as before.
25
+ # Defaults to the list below. Setting it replaces the list.
26
+ # CACHE_IGNORE_PARAMS=utm_*,fbclid,gclid,gbraid,wbraid,dclid,msclkid,twclid,ttclid,li_fat_id,igshid,yclid,mc_cid,mc_eid,_ga,_gl
27
+ # How long a request waits for another one building the same cached response
28
+ # before building it itself, in ms, so one that hangs can't block its URL.
29
+ # 0 or Infinity = wait for it. Defaults to 60000 (60s), or METADATA_TIMEOUT +
30
+ # LOAD_TIMEOUT + 10s when that is longer.
31
+ # CACHE_WAIT_TIMEOUT=60000
18
32
 
19
33
  # Static files and prerendered pages are kept in memory after their first
20
34
  # request (Bun only; ignored on Cloudflare Workers). Total budget in bytes,
@@ -59,13 +73,14 @@ CORS_ALLOWED_ORIGINS=
59
73
  # Default: 86400 (24 hours)
60
74
  # CORS_MAX_AGE=86400
61
75
 
62
- # Timeout for load() functions (layout + page) in milliseconds. Defaults to 5000 (5s).
63
- # Set to 0 or Infinity to disable.
64
- # LOAD_TIMEOUT=5000
76
+ # Timeout for load() functions (layout + page) in milliseconds. Defaults to 30000 (30s).
77
+ # A page's time includes its `await parent()`. Bun drops a silent connection after
78
+ # IDLE_TIMEOUT (10s): raise it too for slower loaders. Set to 0 or Infinity to disable.
79
+ # LOAD_TIMEOUT=30000
65
80
 
66
- # Timeout for metadata() functions in milliseconds. Defaults to 3000 (3s).
81
+ # Timeout for metadata() functions in milliseconds. Defaults to 10000 (10s).
67
82
  # Set to 0 or Infinity to disable.
68
- # METADATA_TIMEOUT=3000
83
+ # METADATA_TIMEOUT=10000
69
84
 
70
85
  # Timeout for prerender fetches during build in milliseconds. Defaults to 5000 (5s).
71
86
  # Set to 0 or Infinity to disable.
@@ -1,4 +1,3 @@
1
- import { sequence } from "bosia";
2
1
  import type { Handle } from "bosia";
3
2
 
4
3
  // Sets locals that every loader and API handler can read
@@ -8,14 +7,6 @@ const authHandle: Handle = async ({ event, resolve }) => {
8
7
  return resolve(event);
9
8
  };
10
9
 
11
- // Logs each request with method, path, status, and duration
12
- const loggingHandle: Handle = async ({ event, resolve }) => {
13
- const start = Date.now();
14
- const res = await resolve(event);
15
- const ms = Date.now() - start;
16
- console.log(`[${event.request.method}] ${event.url.pathname} ${res.status} (${ms}ms)`);
17
- res.headers.set("X-Response-Time", `${ms}ms`);
18
- return res;
19
- };
20
-
21
- export const handle = sequence(authHandle, loggingHandle);
10
+ // Need an access log? Let nginx/Caddy keep it, or add `accessLog()` from
11
+ // "bosia/plugins/access-log" to bosia.config.ts.
12
+ export const handle = authHandle;
@@ -9,7 +9,7 @@
9
9
  "allowImportingTsExtensions": true,
10
10
  "noEmit": true,
11
11
  "verbatimModuleSyntax": true,
12
- "types": ["bun-types"],
12
+ "types": ["bun"],
13
13
  "lib": ["dom", "dom.iterable", "esnext"],
14
14
  "rootDirs": [".", ".bosia/types"],
15
15
  "paths": {
@@ -9,6 +9,5 @@ declare module "*.svelte" {
9
9
  declare namespace App {
10
10
  interface Locals {
11
11
  db: import("./features/drizzle").Database;
12
- requestTime: number;
13
12
  }
14
13
  }
@@ -18,14 +18,6 @@ const guardHandle: Handle = async ({ event, resolve }) => {
18
18
  return resolve(event);
19
19
  };
20
20
 
21
- const loggingHandle: Handle = async ({ event, resolve }) => {
22
- const start = Date.now();
23
- event.locals.requestTime = start;
24
- const res = await resolve(event);
25
- const ms = Date.now() - start;
26
- console.log(`[${event.request.method}] ${event.url.pathname} ${res.status} (${ms}ms)`);
27
- res.headers.set("X-Response-Time", `${ms}ms`);
28
- return res;
29
- };
30
-
31
- export const handle = sequence(dbHandle, authHandle, guardHandle, loggingHandle);
21
+ // Need an access log? Let nginx/Caddy keep it, or add `accessLog()` from
22
+ // "bosia/plugins/access-log" to bosia.config.ts.
23
+ export const handle = sequence(dbHandle, authHandle, guardHandle);
@@ -3,7 +3,6 @@ import type { LoadEvent } from "bosia";
3
3
  export async function load({ locals }: LoadEvent) {
4
4
  return {
5
5
  appName: "Bosia Shop",
6
- requestTime: (locals.requestTime as number | null) ?? null,
7
6
  user: locals.user ?? null,
8
7
  };
9
8
  }
@@ -9,7 +9,7 @@
9
9
  "allowImportingTsExtensions": true,
10
10
  "noEmit": true,
11
11
  "verbatimModuleSyntax": true,
12
- "types": ["bun-types"],
12
+ "types": ["bun"],
13
13
  "lib": ["dom", "dom.iterable", "esnext"],
14
14
  "rootDirs": [".", ".bosia/types"],
15
15
  "paths": {
@@ -9,6 +9,5 @@ declare module "*.svelte" {
9
9
  declare namespace App {
10
10
  interface Locals {
11
11
  db: import("./features/drizzle").Database;
12
- requestTime: number;
13
12
  }
14
13
  }
@@ -18,14 +18,6 @@ const guardHandle: Handle = async ({ event, resolve }) => {
18
18
  return resolve(event);
19
19
  };
20
20
 
21
- const loggingHandle: Handle = async ({ event, resolve }) => {
22
- const start = Date.now();
23
- event.locals.requestTime = start;
24
- const res = await resolve(event);
25
- const ms = Date.now() - start;
26
- console.log(`[${event.request.method}] ${event.url.pathname} ${res.status} (${ms}ms)`);
27
- res.headers.set("X-Response-Time", `${ms}ms`);
28
- return res;
29
- };
30
-
31
- export const handle = sequence(dbHandle, authHandle, guardHandle, loggingHandle);
21
+ // Need an access log? Let nginx/Caddy keep it, or add `accessLog()` from
22
+ // "bosia/plugins/access-log" to bosia.config.ts.
23
+ export const handle = sequence(dbHandle, authHandle, guardHandle);
@@ -3,7 +3,6 @@ import type { LoadEvent } from "bosia";
3
3
  export async function load({ locals }: LoadEvent) {
4
4
  return {
5
5
  appName: "Bosia Shop",
6
- requestTime: (locals.requestTime as number | null) ?? null,
7
6
  user: locals.user ?? null,
8
7
  };
9
8
  }
@@ -9,7 +9,7 @@
9
9
  "allowImportingTsExtensions": true,
10
10
  "noEmit": true,
11
11
  "verbatimModuleSyntax": true,
12
- "types": ["bun-types"],
12
+ "types": ["bun"],
13
13
  "lib": ["dom", "dom.iterable", "esnext"],
14
14
  "rootDirs": [".", ".bosia/types"],
15
15
  "paths": {