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 +2 -1
- package/src/core/cache.ts +18 -8
- package/src/core/dev.ts +6 -0
- package/src/core/plugins/inspector/index.ts +3 -3
- package/src/core/server.ts +17 -2
- package/src/lib/index.ts +3 -1
- package/src/lib/server.ts +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.6.
|
|
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
|
-
|
|
32
|
-
|
|
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 (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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: "
|
|
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 ?? "
|
|
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: "
|
|
285
|
+
source: "server",
|
|
286
286
|
message: e?.message ?? String(error),
|
|
287
287
|
stack: e?.stack,
|
|
288
288
|
});
|
package/src/core/server.ts
CHANGED
|
@@ -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
|
-
|
|
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";
|