bosia 0.6.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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": [
@@ -33,6 +33,7 @@
33
33
  "exports": {
34
34
  ".": "./src/lib/index.ts",
35
35
  "./client": "./src/lib/client.ts",
36
+ "./server": "./src/lib/server.ts",
36
37
  "./plugins/server-timing": "./src/core/plugins/server-timing.ts",
37
38
  "./plugins/inspector": "./src/core/plugins/inspector/index.ts"
38
39
  },
package/src/core/cache.ts CHANGED
@@ -28,16 +28,26 @@ function parseMaxEntries(raw: string | undefined): number {
28
28
  return n;
29
29
  }
30
30
 
31
- export const CACHE_KEYS: readonly string[] = parseCacheKeys(process.env.CACHE_KEYS);
32
- export const CACHE_MAX_ENTRIES = parseMaxEntries(process.env.CACHE_MAX_ENTRIES);
31
+ // `invalidate` / `invalidateAll` are re-exported from the public `bosia`
32
+ // barrel, so this module also evaluates in the browser bundle. Guard every
33
+ // `process.env` read — otherwise hydration throws `ReferenceError: Can't
34
+ // find variable: process` (Safari) the moment the barrel is imported.
35
+ const env: Record<string, string | undefined> =
36
+ typeof process !== "undefined" && process.env ? process.env : {};
37
+ const isServer = typeof process !== "undefined";
38
+
39
+ export const CACHE_KEYS: readonly string[] = parseCacheKeys(env.CACHE_KEYS);
40
+ export const CACHE_MAX_ENTRIES = parseMaxEntries(env.CACHE_MAX_ENTRIES);
33
41
  export const CACHE_ENABLED = CACHE_MAX_ENTRIES > 0;
34
42
 
35
- if (CACHE_ENABLED) {
36
- console.log(
37
- `💾 Response cache: max ${CACHE_MAX_ENTRIES} entries, identity keys [${CACHE_KEYS.join(", ")}]`,
38
- );
39
- } else {
40
- console.log("💾 Response cache: disabled (CACHE_MAX_ENTRIES=0)");
43
+ if (isServer) {
44
+ if (CACHE_ENABLED) {
45
+ console.log(
46
+ `💾 Response cache: max ${CACHE_MAX_ENTRIES} entries, identity keys [${CACHE_KEYS.join(", ")}]`,
47
+ );
48
+ } else {
49
+ console.log("💾 Response cache: disabled (CACHE_MAX_ENTRIES=0)");
50
+ }
41
51
  }
42
52
 
43
53
  // ─── Entry shape ─────────────────────────────────────────
package/src/core/dev.ts CHANGED
@@ -247,6 +247,12 @@ const devServer = Bun.serve({
247
247
  const forwardedHeaders = new Headers(req.headers);
248
248
  forwardedHeaders.set("x-forwarded-host", reqUrl.host);
249
249
  forwardedHeaders.set("x-forwarded-proto", reqUrl.protocol.replace(":", ""));
250
+ // Force inner app to respond uncompressed. Bun's `fetch()` auto-decodes
251
+ // gzip/br bodies but leaves the original `Content-Encoding` header on
252
+ // the Response, so passing it through made Safari throw -1015 ("cannot
253
+ // decode raw data") on every HTML navigation. Identity on the dev wire
254
+ // is fine — it's localhost.
255
+ forwardedHeaders.set("accept-encoding", "identity");
250
256
 
251
257
  // HMR-driven reloads can land on the proxy before the freshly-respawned
252
258
  // inner has bound APP_PORT. Retry for a few seconds on idempotent HTML
@@ -20,7 +20,7 @@ export interface InspectorOptions {
20
20
  interface ServerError {
21
21
  id: string;
22
22
  ts: number;
23
- source: "elysia" | "uncaught" | "rejection";
23
+ source: "server" | "uncaught" | "rejection";
24
24
  message: string;
25
25
  stack?: string;
26
26
  file?: string;
@@ -147,7 +147,7 @@ function installProcessListeners() {
147
147
  function installGlobalReporter() {
148
148
  globalThis.__BOSIA_REPORT_ERROR__ = (e) => {
149
149
  pushServerError({
150
- source: e.source ?? "elysia",
150
+ source: e.source ?? "server",
151
151
  message: e.message,
152
152
  stack: e.stack,
153
153
  });
@@ -282,7 +282,7 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
282
282
  chained = chained.onError(({ error }) => {
283
283
  const e = error as Error;
284
284
  pushServerError({
285
- source: "elysia",
285
+ source: "server",
286
286
  message: e?.message ?? String(error),
287
287
  stack: e?.stack,
288
288
  });
@@ -432,16 +432,31 @@ async function resolve(event: RequestEvent): Promise<Response> {
432
432
  cookies,
433
433
  });
434
434
 
435
+ const responseContentType = response.headers.get("content-type") ?? "";
436
+ // SSE responses are long-lived pub/sub streams — caching the buffered
437
+ // bytes would serve a stale finite snapshot to future subscribers and
438
+ // bypass the handler's subscribe() call entirely. Skip them.
439
+ const isEventStream = responseContentType.toLowerCase().includes("text/event-stream");
440
+
441
+ // Respect handler opt-out via Cache-Control. Standard HTTP semantics:
442
+ // no-store / private / no-cache all signal "don't reuse this response".
443
+ const cacheControl = (response.headers.get("cache-control") ?? "").toLowerCase();
444
+ const noStore =
445
+ cacheControl.includes("no-store") ||
446
+ cacheControl.includes("private") ||
447
+ cacheControl.includes("no-cache");
448
+
435
449
  if (
436
450
  apiCacheable &&
437
451
  apiCacheKey &&
438
452
  response.status === 200 &&
453
+ !isEventStream &&
454
+ !noStore &&
439
455
  (cookies as CookieJar).outgoing.length === 0
440
456
  ) {
441
457
  const cloned = response.clone();
442
458
  const extraHeaders = captureCacheableHeaders(response.headers);
443
- const contentType =
444
- response.headers.get("content-type") ?? "application/octet-stream";
459
+ const contentType = responseContentType || "application/octet-stream";
445
460
  const keyForWrite = apiCacheKey;
446
461
  queueMicrotask(async () => {
447
462
  try {
package/src/lib/index.ts CHANGED
@@ -6,7 +6,9 @@
6
6
  export { cn, getServerTime } from "./utils.ts";
7
7
  export { sequence } from "../core/hooks.ts";
8
8
  export { error, redirect, fail } from "../core/errors.ts";
9
- export { invalidate, invalidateAll } from "../core/cache.ts";
9
+ // `invalidate` / `invalidateAll` (server response-cache eviction) live in
10
+ // "bosia/server" — they touch server-process state and pulling them into
11
+ // the shared barrel leaks `process.env` reads into client bundles.
10
12
  export type { HttpError, Redirect, RedirectOptions, ActionFailure } from "../core/errors.ts";
11
13
  export type {
12
14
  RequestEvent,
@@ -0,0 +1,12 @@
1
+ // ─── Bosia Server API ─────────────────────────────────────
2
+ // Server-only helpers — import from "bosia/server".
3
+ // Kept separate from "bosia" because these modules touch server-process
4
+ // state (the response-cache Map, `process.env`) and have no meaning in
5
+ // the browser. Importing them through the shared `bosia` barrel would
6
+ // drag `process.env` reads into client bundles via the lib re-export
7
+ // graph (see ROADMAP v0.6.0 entry on the Safari hydration ReferenceError).
8
+ //
9
+ // Usage in user apps (form actions, +server.ts, hooks):
10
+ // import { invalidate, invalidateAll } from "bosia/server";
11
+
12
+ export { invalidate, invalidateAll } from "../core/cache.ts";