@bquery/bquery 1.9.0 → 1.11.0
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/README.md +221 -40
- package/dist/{a11y-_9X-kt-_.js → a11y-DgUQ8-fI.js} +3 -3
- package/dist/{a11y-_9X-kt-_.js.map → a11y-DgUQ8-fI.js.map} +1 -1
- package/dist/a11y.es.mjs +1 -1
- package/dist/{component-L3-JfOFz.js → component-D8ydhe58.js} +20 -19
- package/dist/{component-L3-JfOFz.js.map → component-D8ydhe58.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/concurrency/errors.d.ts +29 -0
- package/dist/concurrency/errors.d.ts.map +1 -0
- package/dist/concurrency/high-level.d.ts +85 -0
- package/dist/concurrency/high-level.d.ts.map +1 -0
- package/dist/concurrency/index.d.ts +19 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/internal.d.ts +26 -0
- package/dist/concurrency/internal.d.ts.map +1 -0
- package/dist/concurrency/pipeline.d.ts +30 -0
- package/dist/concurrency/pipeline.d.ts.map +1 -0
- package/dist/concurrency/pool.d.ts +48 -0
- package/dist/concurrency/pool.d.ts.map +1 -0
- package/dist/concurrency/reactive.d.ts +107 -0
- package/dist/concurrency/reactive.d.ts.map +1 -0
- package/dist/concurrency/rpc.d.ts +46 -0
- package/dist/concurrency/rpc.d.ts.map +1 -0
- package/dist/concurrency/support.d.ts +23 -0
- package/dist/concurrency/support.d.ts.map +1 -0
- package/dist/concurrency/task.d.ts +31 -0
- package/dist/concurrency/task.d.ts.map +1 -0
- package/dist/concurrency/types.d.ts +343 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency-BU1wPEsZ.js +826 -0
- package/dist/concurrency-BU1wPEsZ.js.map +1 -0
- package/dist/concurrency.es.mjs +29 -0
- package/dist/{constraints-D5RHQLmP.js → constraints-Dlbx_m1b.js} +1 -1
- package/dist/{constraints-D5RHQLmP.js.map → constraints-Dlbx_m1b.js.map} +1 -1
- package/dist/core-CongXJuo.js +87 -0
- package/dist/core-CongXJuo.js.map +1 -0
- package/dist/{core-EMYSLzaT.js → core-tOP6QOrY.js} +2 -2
- package/dist/{core-EMYSLzaT.js.map → core-tOP6QOrY.js.map} +1 -1
- package/dist/core.es.mjs +1 -1
- package/dist/{custom-directives-Dr4C5lVV.js → custom-directives-5DlKqvd2.js} +1 -1
- package/dist/{custom-directives-Dr4C5lVV.js.map → custom-directives-5DlKqvd2.js.map} +1 -1
- package/dist/{devtools-BhB2iDPT.js → devtools-QosAqo0T.js} +2 -2
- package/dist/{devtools-BhB2iDPT.js.map → devtools-QosAqo0T.js.map} +1 -1
- package/dist/devtools.es.mjs +1 -1
- package/dist/{dnd-NwZBYh4l.js → dnd-d2OU4len.js} +1 -1
- package/dist/{dnd-NwZBYh4l.js.map → dnd-d2OU4len.js.map} +1 -1
- package/dist/dnd.es.mjs +1 -1
- package/dist/effect-Cc51IH91.js +87 -0
- package/dist/effect-Cc51IH91.js.map +1 -0
- package/dist/{env-CTdvLaH2.js → env-PvwYHnJq.js} +1 -1
- package/dist/{env-CTdvLaH2.js.map → env-PvwYHnJq.js.map} +1 -1
- package/dist/{forms-UhAeJEoO.js → forms-BLx4ZzT7.js} +41 -40
- package/dist/{forms-UhAeJEoO.js.map → forms-BLx4ZzT7.js.map} +1 -1
- package/dist/forms.es.mjs +1 -1
- package/dist/full.d.ts +6 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +282 -214
- package/dist/full.iife.js +108 -20
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +108 -20
- package/dist/full.umd.js.map +1 -1
- package/dist/{i18n-kuF6Ekj6.js → i18n--p7PM-9r.js} +3 -3
- package/dist/{i18n-kuF6Ekj6.js.map → i18n--p7PM-9r.js.map} +1 -1
- package/dist/i18n.es.mjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.mjs +320 -252
- package/dist/match-CrZRVC4z.js +174 -0
- package/dist/match-CrZRVC4z.js.map +1 -0
- package/dist/media/observers.d.ts.map +1 -1
- package/dist/{media-D4zLj9t-.js → media-gjbWNq50.js} +3 -3
- package/dist/{media-D4zLj9t-.js.map → media-gjbWNq50.js.map} +1 -1
- package/dist/media.es.mjs +1 -1
- package/dist/{motion-BJsAuULb.js → motion-BBMso9Ir.js} +1 -1
- package/dist/{motion-BJsAuULb.js.map → motion-BBMso9Ir.js.map} +1 -1
- package/dist/motion.es.mjs +1 -1
- package/dist/{mount-B-JvH6Y0.js → mount-0A9qtcRJ.js} +11 -10
- package/dist/{mount-B-JvH6Y0.js.map → mount-0A9qtcRJ.js.map} +1 -1
- package/dist/{platform-Dw2gE3zI.js → platform-BPHIXbw8.js} +17 -16
- package/dist/{platform-Dw2gE3zI.js.map → platform-BPHIXbw8.js.map} +1 -1
- package/dist/platform.es.mjs +1 -1
- package/dist/{plugin-C2WuC8SF.js → plugin-SZEirbwq.js} +2 -2
- package/dist/{plugin-C2WuC8SF.js.map → plugin-SZEirbwq.js.map} +1 -1
- package/dist/plugin.es.mjs +1 -1
- package/dist/reactive/watch.d.ts.map +1 -1
- package/dist/reactive/websocket.d.ts +6 -3
- package/dist/reactive/websocket.d.ts.map +1 -1
- package/dist/{reactive-BjpLkclt.js → reactive-BAd2hfl8.js} +436 -449
- package/dist/reactive-BAd2hfl8.js.map +1 -0
- package/dist/reactive.es.mjs +42 -40
- package/dist/readonly-C0ZwS1Tf.js +35 -0
- package/dist/readonly-C0ZwS1Tf.js.map +1 -0
- package/dist/{registry-B08iilIh.js → registry-jpUQHf4E.js} +1 -1
- package/dist/{registry-B08iilIh.js.map → registry-jpUQHf4E.js.map} +1 -1
- package/dist/router-C4weu0QL.js +333 -0
- package/dist/router-C4weu0QL.js.map +1 -0
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-B1V4JswB.js → sanitize-DOMkRO9G.js} +12 -7
- package/dist/{sanitize-B1V4JswB.js.map → sanitize-DOMkRO9G.js.map} +1 -1
- package/dist/security.es.mjs +1 -1
- package/dist/server/create-server.d.ts +25 -0
- package/dist/server/create-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/types.d.ts +396 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server-QdyKtCS1.js +349 -0
- package/dist/server-QdyKtCS1.js.map +1 -0
- package/dist/server.es.mjs +6 -0
- package/dist/ssr/adapters.d.ts +74 -0
- package/dist/ssr/adapters.d.ts.map +1 -0
- package/dist/ssr/async.d.ts +40 -0
- package/dist/ssr/async.d.ts.map +1 -0
- package/dist/ssr/config.d.ts +60 -0
- package/dist/ssr/config.d.ts.map +1 -0
- package/dist/ssr/context.d.ts +73 -0
- package/dist/ssr/context.d.ts.map +1 -0
- package/dist/ssr/defer-brand.d.ts +5 -0
- package/dist/ssr/defer-brand.d.ts.map +1 -0
- package/dist/ssr/escape.d.ts +17 -0
- package/dist/ssr/escape.d.ts.map +1 -0
- package/dist/ssr/expression.d.ts +44 -0
- package/dist/ssr/expression.d.ts.map +1 -0
- package/dist/ssr/hash.d.ts +39 -0
- package/dist/ssr/hash.d.ts.map +1 -0
- package/dist/ssr/head.d.ts +102 -0
- package/dist/ssr/head.d.ts.map +1 -0
- package/dist/ssr/html-parser.d.ts +58 -0
- package/dist/ssr/html-parser.d.ts.map +1 -0
- package/dist/ssr/index.d.ts +49 -43
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/mismatch.d.ts +60 -0
- package/dist/ssr/mismatch.d.ts.map +1 -0
- package/dist/ssr/render-async.d.ts +84 -0
- package/dist/ssr/render-async.d.ts.map +1 -0
- package/dist/ssr/render.d.ts.map +1 -1
- package/dist/ssr/renderer.d.ts +25 -0
- package/dist/ssr/renderer.d.ts.map +1 -0
- package/dist/ssr/resumability.d.ts +65 -0
- package/dist/ssr/resumability.d.ts.map +1 -0
- package/dist/ssr/router-bridge.d.ts +101 -0
- package/dist/ssr/router-bridge.d.ts.map +1 -0
- package/dist/ssr/runtime.d.ts +63 -0
- package/dist/ssr/runtime.d.ts.map +1 -0
- package/dist/ssr/serialize.d.ts.map +1 -1
- package/dist/ssr/store-snapshot.d.ts +87 -0
- package/dist/ssr/store-snapshot.d.ts.map +1 -0
- package/dist/ssr/strategies.d.ts +43 -0
- package/dist/ssr/strategies.d.ts.map +1 -0
- package/dist/ssr/suspense.d.ts +47 -0
- package/dist/ssr/suspense.d.ts.map +1 -0
- package/dist/ssr/types.d.ts +17 -0
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr-Bt6BQA3J.js +2127 -0
- package/dist/ssr-Bt6BQA3J.js.map +1 -0
- package/dist/ssr.es.mjs +42 -7
- package/dist/{store-CY6sjTW3.js → store-DnXuu6Li.js} +6 -6
- package/dist/{store-CY6sjTW3.js.map → store-DnXuu6Li.js.map} +1 -1
- package/dist/store.es.mjs +2 -2
- package/dist/storybook.es.mjs +1 -1
- package/dist/{testing-UjAtu9aQ.js → testing-CeMUwrRD.js} +7 -7
- package/dist/{testing-UjAtu9aQ.js.map → testing-CeMUwrRD.js.map} +1 -1
- package/dist/testing.es.mjs +1 -1
- package/dist/{untrack-D0fnO5k2.js → untrack-bjWDNdyE.js} +11 -10
- package/dist/{untrack-D0fnO5k2.js.map → untrack-bjWDNdyE.js.map} +1 -1
- package/dist/view.es.mjs +12 -11
- package/package.json +24 -15
- package/src/concurrency/errors.ts +57 -0
- package/src/concurrency/high-level.ts +387 -0
- package/src/concurrency/index.ts +63 -0
- package/src/concurrency/internal.ts +100 -0
- package/src/concurrency/pipeline.ts +133 -0
- package/src/concurrency/pool.ts +450 -0
- package/src/concurrency/reactive.ts +339 -0
- package/src/concurrency/rpc.ts +380 -0
- package/src/concurrency/support.ts +44 -0
- package/src/concurrency/task.ts +318 -0
- package/src/concurrency/types.ts +431 -0
- package/src/full.ts +164 -0
- package/src/index.ts +6 -0
- package/src/media/observers.ts +5 -8
- package/src/reactive/watch.ts +10 -9
- package/src/reactive/websocket.ts +31 -8
- package/src/server/create-server.ts +754 -0
- package/src/server/index.ts +33 -0
- package/src/server/types.ts +490 -0
- package/src/ssr/adapters.ts +330 -0
- package/src/ssr/async.ts +125 -0
- package/src/ssr/config.ts +86 -0
- package/src/ssr/context.ts +245 -0
- package/src/ssr/defer-brand.ts +3 -0
- package/src/ssr/escape.ts +25 -0
- package/src/ssr/expression.ts +669 -0
- package/src/ssr/hash.ts +71 -0
- package/src/ssr/head.ts +240 -0
- package/src/ssr/html-parser.ts +387 -0
- package/src/ssr/index.ts +136 -43
- package/src/ssr/mismatch.ts +110 -0
- package/src/ssr/render-async.ts +286 -0
- package/src/ssr/render.ts +130 -59
- package/src/ssr/renderer.ts +453 -0
- package/src/ssr/resumability.ts +142 -0
- package/src/ssr/router-bridge.ts +177 -0
- package/src/ssr/runtime.ts +131 -0
- package/src/ssr/serialize.ts +1 -27
- package/src/ssr/store-snapshot.ts +209 -0
- package/src/ssr/strategies.ts +245 -0
- package/src/ssr/suspense.ts +504 -0
- package/src/ssr/types.ts +18 -0
- package/dist/core-DdtZHzsS.js +0 -168
- package/dist/core-DdtZHzsS.js.map +0 -1
- package/dist/reactive-BjpLkclt.js.map +0 -1
- package/dist/router-BieVwgci.js +0 -492
- package/dist/router-BieVwgci.js.map +0 -1
- package/dist/ssr-CrGSJySz.js +0 -248
- package/dist/ssr-CrGSJySz.js.map +0 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rendering context.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates everything a render path may need to know about the incoming
|
|
5
|
+
* request and to produce response-side metadata (head tags, asset hints,
|
|
6
|
+
* nonces, etc.). The context is propagated explicitly through the public
|
|
7
|
+
* async APIs (`renderToStringAsync`, `renderToStream`, `renderToResponse`)
|
|
8
|
+
* and is available to template loaders.
|
|
9
|
+
*
|
|
10
|
+
* @module bquery/ssr
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { generateNonce } from '../security/csp';
|
|
14
|
+
import { isPrototypePollutionKey } from '../core/utils/object';
|
|
15
|
+
import { createAssetManager, createHeadManager, type AssetManager, type HeadManager } from './head';
|
|
16
|
+
|
|
17
|
+
/** Options for `createSSRContext()`. */
|
|
18
|
+
export interface CreateSSRContextOptions {
|
|
19
|
+
/** Pre-built request to use as the source of URL/headers/cookies/etc. */
|
|
20
|
+
request?: Request;
|
|
21
|
+
/** Override the URL (defaults to `request.url` or `'http://localhost/'`). */
|
|
22
|
+
url?: string | URL;
|
|
23
|
+
/** Override the user agent string. */
|
|
24
|
+
userAgent?: string;
|
|
25
|
+
/** Override the request locale; defaults to `Accept-Language` parsing. */
|
|
26
|
+
locale?: string;
|
|
27
|
+
/** Provide an `AbortSignal` for cancellation. Default: `request.signal`. */
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
/** Pre-computed CSP nonce. If omitted and `crypto.getRandomValues` exists, a fresh nonce is generated. */
|
|
30
|
+
nonce?: string;
|
|
31
|
+
/** Render mode hint — used by streaming/string renderers for diagnostics. */
|
|
32
|
+
mode?: 'string' | 'stream';
|
|
33
|
+
/** Optional error sink invoked for non-fatal render errors. */
|
|
34
|
+
onError?: (error: unknown) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Public SSR context shape. */
|
|
38
|
+
export interface SSRContext {
|
|
39
|
+
/** The originating `Request` (may be a synthetic one if none was provided). */
|
|
40
|
+
request: Request;
|
|
41
|
+
/** Parsed URL of the request. */
|
|
42
|
+
url: URL;
|
|
43
|
+
/** Request headers (via `Request.headers`). */
|
|
44
|
+
headers: Headers;
|
|
45
|
+
/** Resolved cookie map from `Cookie` header. */
|
|
46
|
+
cookies: Record<string, string>;
|
|
47
|
+
/** Best-effort user agent string. */
|
|
48
|
+
userAgent: string;
|
|
49
|
+
/** Best-effort locale parsed from `Accept-Language`. */
|
|
50
|
+
locale: string;
|
|
51
|
+
/** Cancellation signal — render paths must respect it. */
|
|
52
|
+
signal: AbortSignal;
|
|
53
|
+
/** CSP nonce applied to all generated `<script>` tags. */
|
|
54
|
+
nonce: string;
|
|
55
|
+
/** Render mode hint. */
|
|
56
|
+
mode: 'string' | 'stream';
|
|
57
|
+
/** Head manager — call `head.add(...)`, `head.render()`, or read `head.state()`. */
|
|
58
|
+
head: HeadManager;
|
|
59
|
+
/** Asset manifest — call `assets.preload()`/`module()`/`style()`. */
|
|
60
|
+
assets: AssetManager;
|
|
61
|
+
/** Status code suggested by render paths (loaders, error boundaries). */
|
|
62
|
+
status: number;
|
|
63
|
+
/** Outgoing response headers (used by `renderToResponse()`). */
|
|
64
|
+
responseHeaders: Headers;
|
|
65
|
+
/** Reports a non-fatal error. */
|
|
66
|
+
reportError(error: unknown): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const parseCookies = (header: string): Record<string, string> => {
|
|
70
|
+
const out = Object.create(null) as Record<string, string>;
|
|
71
|
+
if (!header) return out;
|
|
72
|
+
for (const pair of header.split(/;\s*/)) {
|
|
73
|
+
const idx = pair.indexOf('=');
|
|
74
|
+
if (idx === -1) continue;
|
|
75
|
+
const name = pair.slice(0, idx).trim();
|
|
76
|
+
const value = pair.slice(idx + 1).trim();
|
|
77
|
+
if (!name || isPrototypePollutionKey(name)) continue;
|
|
78
|
+
try {
|
|
79
|
+
out[name] = decodeURIComponent(value);
|
|
80
|
+
} catch {
|
|
81
|
+
out[name] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const parseLocale = (acceptLanguage: string | null): string => {
|
|
88
|
+
if (!acceptLanguage) return 'en';
|
|
89
|
+
// Pick the highest-quality entry. Header parsing is intentionally minimal.
|
|
90
|
+
const entries = acceptLanguage
|
|
91
|
+
.split(',')
|
|
92
|
+
.map((entry) => {
|
|
93
|
+
const [tag, ...params] = entry.trim().split(';');
|
|
94
|
+
const qParam = params.find((p) => p.trim().startsWith('q='));
|
|
95
|
+
const q = qParam ? Number.parseFloat(qParam.split('=')[1]) : 1;
|
|
96
|
+
return { tag: tag.trim(), q: Number.isFinite(q) ? q : 0 };
|
|
97
|
+
})
|
|
98
|
+
.filter((e) => e.tag);
|
|
99
|
+
if (!entries.length) return 'en';
|
|
100
|
+
entries.sort((a, b) => b.q - a.q);
|
|
101
|
+
return entries[0].tag;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const safeNonce = (): string => {
|
|
105
|
+
try {
|
|
106
|
+
return generateNonce();
|
|
107
|
+
} catch {
|
|
108
|
+
// Runtime lacks `crypto.getRandomValues` / `btoa` — fall back to empty.
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const createHeadersFallback = (): Headers => {
|
|
114
|
+
const store = new Map<string, string[]>();
|
|
115
|
+
const api = {
|
|
116
|
+
append(name: string, value: string): void {
|
|
117
|
+
const key = String(name).toLowerCase();
|
|
118
|
+
const existing = store.get(key);
|
|
119
|
+
if (existing) existing.push(String(value));
|
|
120
|
+
else store.set(key, [String(value)]);
|
|
121
|
+
},
|
|
122
|
+
delete(name: string): void {
|
|
123
|
+
store.delete(String(name).toLowerCase());
|
|
124
|
+
},
|
|
125
|
+
get(name: string): string | null {
|
|
126
|
+
const values = store.get(String(name).toLowerCase());
|
|
127
|
+
return values ? values.join(', ') : null;
|
|
128
|
+
},
|
|
129
|
+
has(name: string): boolean {
|
|
130
|
+
return store.has(String(name).toLowerCase());
|
|
131
|
+
},
|
|
132
|
+
set(name: string, value: string): void {
|
|
133
|
+
store.set(String(name).toLowerCase(), [String(value)]);
|
|
134
|
+
},
|
|
135
|
+
forEach(
|
|
136
|
+
callback: (value: string, key: string, parent: Headers) => void,
|
|
137
|
+
thisArg?: unknown
|
|
138
|
+
): void {
|
|
139
|
+
for (const [key, values] of store) {
|
|
140
|
+
callback.call(thisArg, values.join(', '), key, api as unknown as Headers);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
*entries(): IterableIterator<[string, string]> {
|
|
144
|
+
for (const [key, values] of store) {
|
|
145
|
+
yield [key, values.join(', ')];
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
[Symbol.iterator](): IterableIterator<[string, string]> {
|
|
149
|
+
return api.entries();
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
return api as unknown as Headers;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const createAbortSignalFallback = (): AbortSignal =>
|
|
156
|
+
({
|
|
157
|
+
aborted: false,
|
|
158
|
+
onabort: null,
|
|
159
|
+
reason: undefined,
|
|
160
|
+
addEventListener() {
|
|
161
|
+
/* no-op */
|
|
162
|
+
},
|
|
163
|
+
removeEventListener() {
|
|
164
|
+
/* no-op */
|
|
165
|
+
},
|
|
166
|
+
dispatchEvent() {
|
|
167
|
+
return false;
|
|
168
|
+
},
|
|
169
|
+
throwIfAborted() {
|
|
170
|
+
/* no-op */
|
|
171
|
+
},
|
|
172
|
+
}) as unknown as AbortSignal;
|
|
173
|
+
|
|
174
|
+
const hasHeadersApi = (value: unknown): value is Headers =>
|
|
175
|
+
typeof value === 'object' && value !== null && typeof (value as Headers).get === 'function';
|
|
176
|
+
|
|
177
|
+
const hasAbortSignalApi = (value: unknown): value is AbortSignal =>
|
|
178
|
+
typeof value === 'object' && value !== null && typeof (value as AbortSignal).aborted === 'boolean';
|
|
179
|
+
|
|
180
|
+
const createHeadersLike = (): Headers =>
|
|
181
|
+
typeof Headers === 'function' ? new Headers() : createHeadersFallback();
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a fully populated SSR context.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* const ctx = createSSRContext({ request });
|
|
189
|
+
* const { html } = await renderToStringAsync(template, data, { context: ctx });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export const createSSRContext = (options: CreateSSRContextOptions = {}): SSRContext => {
|
|
193
|
+
// SSRContext relies on Web `Request`/`Headers`/`AbortSignal`. All target
|
|
194
|
+
// runtimes (Node ≥ 24, Deno, Bun, browsers) provide these natively. The
|
|
195
|
+
// structural fallback below only exists so the helper does not throw in
|
|
196
|
+
// exotic embedded runtimes; downstream code that expects real `Request`
|
|
197
|
+
// methods should ensure the runtime ships them.
|
|
198
|
+
const fallbackRequestUrl =
|
|
199
|
+
options.url instanceof URL
|
|
200
|
+
? options.url.toString()
|
|
201
|
+
: typeof options.url === 'string'
|
|
202
|
+
? new URL(options.url, 'http://localhost/').toString()
|
|
203
|
+
: 'http://localhost/';
|
|
204
|
+
const request =
|
|
205
|
+
options.request ??
|
|
206
|
+
(typeof Request === 'function'
|
|
207
|
+
? new Request(fallbackRequestUrl)
|
|
208
|
+
: ({
|
|
209
|
+
url: fallbackRequestUrl,
|
|
210
|
+
headers: createHeadersFallback(),
|
|
211
|
+
signal: createAbortSignalFallback(),
|
|
212
|
+
} as unknown as Request));
|
|
213
|
+
|
|
214
|
+
const urlSource = options.url ?? request.url;
|
|
215
|
+
const url =
|
|
216
|
+
urlSource instanceof URL ? urlSource : new URL(String(urlSource), 'http://localhost/');
|
|
217
|
+
|
|
218
|
+
const headers = hasHeadersApi(request.headers) ? request.headers : createHeadersLike();
|
|
219
|
+
const cookies = parseCookies(headers.get('cookie') ?? '');
|
|
220
|
+
const userAgent = options.userAgent ?? headers.get('user-agent') ?? '';
|
|
221
|
+
const locale = options.locale ?? parseLocale(headers.get('accept-language'));
|
|
222
|
+
const signal = options.signal ?? (hasAbortSignalApi(request.signal) ? request.signal : createAbortSignalFallback());
|
|
223
|
+
const nonce = options.nonce ?? safeNonce();
|
|
224
|
+
|
|
225
|
+
const ctx: SSRContext = {
|
|
226
|
+
request,
|
|
227
|
+
url,
|
|
228
|
+
headers,
|
|
229
|
+
cookies,
|
|
230
|
+
userAgent,
|
|
231
|
+
locale,
|
|
232
|
+
signal,
|
|
233
|
+
nonce,
|
|
234
|
+
mode: options.mode ?? 'string',
|
|
235
|
+
head: createHeadManager(),
|
|
236
|
+
assets: createAssetManager(),
|
|
237
|
+
status: 200,
|
|
238
|
+
responseHeaders: createHeadersLike(),
|
|
239
|
+
reportError(error) {
|
|
240
|
+
options.onError?.(error);
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
return ctx;
|
|
245
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared escaping helpers for SSR inline script payloads and HTML attributes.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/ssr
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Escapes a string for safe embedding in an inline `<script>` body.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const escapeForScript = (str: string): string =>
|
|
13
|
+
str
|
|
14
|
+
.replace(/</g, '\\u003c')
|
|
15
|
+
.replace(/>/g, '\\u003e')
|
|
16
|
+
.replace(/\//g, '\\u002f')
|
|
17
|
+
.replace(/\u2028/g, '\\u2028')
|
|
18
|
+
.replace(/\u2029/g, '\\u2029');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Escapes a string for safe embedding in an HTML attribute value.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export const escapeForHtmlAttribute = (str: string): string =>
|
|
25
|
+
str.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|