bosia 0.4.6 → 0.5.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/package.json +1 -1
- package/src/core/client/App.svelte +121 -5
- package/src/core/client/appState.svelte.ts +24 -37
- package/src/core/client/enhance.ts +6 -2
- package/src/core/client/hydrate.ts +51 -3
- package/src/core/client/loaderCache.ts +127 -0
- package/src/core/client/navigation.ts +59 -0
- package/src/core/client/prefetch.ts +48 -3
- package/src/core/hooks.ts +27 -0
- package/src/core/html.ts +49 -8
- package/src/core/renderer.ts +235 -28
- package/src/core/routeFile.ts +26 -0
- package/src/core/server.ts +62 -5
- package/src/lib/client.ts +1 -0
- package/src/lib/index.ts +1 -0
- package/templates/default/bosia.config.ts +10 -0
- package/templates/demo/bosia.config.ts +10 -0
- package/templates/todo/bosia.config.ts +10 -0
package/src/core/server.ts
CHANGED
|
@@ -112,6 +112,23 @@ function isValidRoutePath(path: string, origin: string): boolean {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Decode an `_invalidated` bitmask string. Char 0 = page, char i+1 = layout
|
|
117
|
+
* depth i, '1' = run, '0' = skip. Missing/extra chars default to run.
|
|
118
|
+
*/
|
|
119
|
+
function buildMaskFromBits(
|
|
120
|
+
bits: string,
|
|
121
|
+
layoutCount: number,
|
|
122
|
+
): { page: boolean; layouts: boolean[] } {
|
|
123
|
+
const page = bits[0] !== "0";
|
|
124
|
+
const layouts: boolean[] = [];
|
|
125
|
+
for (let i = 0; i < layoutCount; i++) {
|
|
126
|
+
const c = bits[i + 1];
|
|
127
|
+
layouts.push(c !== "0");
|
|
128
|
+
}
|
|
129
|
+
return { page, layouts };
|
|
130
|
+
}
|
|
131
|
+
|
|
115
132
|
/** Extract action name from URL searchParams — `?/login` → "login", no slash key → "default". */
|
|
116
133
|
function parseActionName(url: URL): string {
|
|
117
134
|
for (const key of url.searchParams.keys()) {
|
|
@@ -146,15 +163,41 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
146
163
|
return Response.json({ error: "Invalid path", status: 400 }, { status: 400 });
|
|
147
164
|
}
|
|
148
165
|
const routeUrl = new URL(routePathStr, url.origin);
|
|
166
|
+
let invalidatedBits: string | null = null;
|
|
149
167
|
for (const [key, val] of url.searchParams.entries()) {
|
|
168
|
+
if (key === "_invalidated") {
|
|
169
|
+
invalidatedBits = val;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
150
172
|
routeUrl.searchParams.append(key, val);
|
|
151
173
|
}
|
|
152
174
|
// Rewrite event.url so logging middleware sees the real page path, not /__bosia/data
|
|
153
175
|
event.url = routeUrl;
|
|
154
176
|
try {
|
|
155
177
|
const pageMatch = findMatch(serverRoutes, routeUrl.pathname);
|
|
178
|
+
// Build mask from `?_invalidated=<bits>` where char 0 = page,
|
|
179
|
+
// char i+1 = layout depth i, '1' = run, '0' = skip. Absent → run all.
|
|
180
|
+
// Mask is sized to the total layout count (matching client `layoutIds`),
|
|
181
|
+
// not the count of layout servers, so depths without a server loader
|
|
182
|
+
// still occupy a bit position and stay aligned with the client.
|
|
183
|
+
const mask = invalidatedBits
|
|
184
|
+
? buildMaskFromBits(
|
|
185
|
+
invalidatedBits,
|
|
186
|
+
pageMatch?.route
|
|
187
|
+
? ((pageMatch.route as any).layoutModules?.length ?? 0)
|
|
188
|
+
: 0,
|
|
189
|
+
)
|
|
190
|
+
: undefined;
|
|
156
191
|
const runLoad = async () => {
|
|
157
|
-
const data = await loadRouteData(
|
|
192
|
+
const data = await loadRouteData(
|
|
193
|
+
routeUrl,
|
|
194
|
+
locals,
|
|
195
|
+
request,
|
|
196
|
+
cookies,
|
|
197
|
+
null,
|
|
198
|
+
pageMatch,
|
|
199
|
+
mask,
|
|
200
|
+
);
|
|
158
201
|
|
|
159
202
|
let metadata = null;
|
|
160
203
|
if (pageMatch) {
|
|
@@ -176,12 +219,17 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
176
219
|
return { data, metadata, cookiesAccessed: (cookies as CookieJar).accessed };
|
|
177
220
|
};
|
|
178
221
|
|
|
179
|
-
// Dedup public routes by URL
|
|
180
|
-
// skip the cache to prevent cross-user data leaks.
|
|
222
|
+
// Dedup public routes by URL + mask. `(private)` scope routes (per-user
|
|
223
|
+
// content) skip the cache to prevent cross-user data leaks. The mask is
|
|
224
|
+
// part of the key so concurrent requests for the same URL with different
|
|
225
|
+
// invalidation patterns don't collapse onto each other. See dedup.ts.
|
|
226
|
+
const dedupK = invalidatedBits
|
|
227
|
+
? `${dedupKey(routeUrl)}|m=${invalidatedBits}`
|
|
228
|
+
: dedupKey(routeUrl);
|
|
181
229
|
const result =
|
|
182
230
|
pageMatch?.route.scope === "private"
|
|
183
231
|
? await runLoad()
|
|
184
|
-
: await dedup(
|
|
232
|
+
: await dedup(dedupK, runLoad);
|
|
185
233
|
|
|
186
234
|
const cookiesWereAccessed = (cookies as CookieJar).accessed || result.cookiesAccessed;
|
|
187
235
|
const cc = cookiesWereAccessed
|
|
@@ -515,12 +563,21 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
515
563
|
|
|
516
564
|
// ─── Request Entry ────────────────────────────────────────
|
|
517
565
|
|
|
566
|
+
// Set DISABLE_X_FRAME_OPTIONS=true to omit `X-Frame-Options: SAMEORIGIN`.
|
|
567
|
+
// Useful when the app is intentionally embedded as an iframe by a different origin
|
|
568
|
+
// (preview/proxy hubs, design tools, etc.). Other security headers stay on.
|
|
569
|
+
const _xfoDisabled = process.env.DISABLE_X_FRAME_OPTIONS === "true";
|
|
570
|
+
|
|
518
571
|
const SECURITY_HEADERS: Record<string, string> = {
|
|
519
572
|
"X-Content-Type-Options": "nosniff",
|
|
520
|
-
"X-Frame-Options": "SAMEORIGIN",
|
|
573
|
+
...(_xfoDisabled ? {} : { "X-Frame-Options": "SAMEORIGIN" }),
|
|
521
574
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
522
575
|
};
|
|
523
576
|
|
|
577
|
+
if (_xfoDisabled) {
|
|
578
|
+
console.log("🪟 X-Frame-Options disabled (DISABLE_X_FRAME_OPTIONS=true)");
|
|
579
|
+
}
|
|
580
|
+
|
|
524
581
|
async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
525
582
|
// Reject new non-health requests during shutdown
|
|
526
583
|
if (shuttingDown && url.pathname !== "/_health") {
|
package/src/lib/client.ts
CHANGED
package/src/lib/index.ts
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
});
|