@mandujs/core 0.54.30 → 0.54.32
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
|
@@ -18,6 +18,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
18
18
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
21
|
+
import { pathToFileURL } from "node:url";
|
|
21
22
|
import {
|
|
22
23
|
_testOnly_generateJsxDevRuntimeShimSource,
|
|
23
24
|
_testOnly_generateJsxRuntimeShimSource,
|
|
@@ -32,10 +33,9 @@ describe("JSX runtime shim sources (issues #322 / #323)", () => {
|
|
|
32
33
|
// the import statement, not comment mentions of the path.)
|
|
33
34
|
expect(source).not.toMatch(/from\s*['"]react\/jsx-dev-runtime['"]/);
|
|
34
35
|
|
|
35
|
-
// Correct: jsxDEV +
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
);
|
|
36
|
+
// Correct: jsxDEV (+ jsx/jsxs fallback inputs) come from bare 'react'
|
|
37
|
+
// (→ _react.js). Import includes jsxDEV and resolves from "react".
|
|
38
|
+
expect(source).toMatch(/import\s*\{[^}]*\bjsxDEV\b[^}]*\}\s*from\s*['"]react['"]/);
|
|
39
39
|
|
|
40
40
|
// Shim still re-exports what the import map expects.
|
|
41
41
|
expect(source).toContain("export { jsxDEV, Fragment }");
|
|
@@ -83,4 +83,58 @@ describe("JSX runtime shim sources (issues #322 / #323)", () => {
|
|
|
83
83
|
await rm(dir, { recursive: true, force: true });
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
// #323 re-regression (0.54.30): a SOURCE/static check passed but at RUNTIME
|
|
88
|
+
// `_react.js`'s jsxDEV was `undefined` (jsxDEV is a React dev-only export;
|
|
89
|
+
// a production-built _react.js drops it while keeping jsx/jsxs). The shim
|
|
90
|
+
// re-exported that undefined → "jsxDEV is not a function". Guard the VALUE:
|
|
91
|
+
// build the dev shim against a `react` stub whose jsxDEV is undefined (the
|
|
92
|
+
// exact failing condition) and assert the shim still exposes a callable
|
|
93
|
+
// jsxDEV via its jsx/jsxs fallback.
|
|
94
|
+
test("dev shim exposes a callable jsxDEV even when react.jsxDEV is undefined (runtime value)", async () => {
|
|
95
|
+
const dir = await mkdtemp(path.join(tmpdir(), "mandu-jsx-shim-rt-"));
|
|
96
|
+
try {
|
|
97
|
+
// Stub for bare 'react' = a production-built _react.js: jsx/jsxs are real
|
|
98
|
+
// functions, jsxDEV is missing.
|
|
99
|
+
const reactStub = path.join(dir, "react-stub.js");
|
|
100
|
+
await writeFile(
|
|
101
|
+
reactStub,
|
|
102
|
+
[
|
|
103
|
+
"export const jsx = (type) => ({ type, runtime: 'jsx' });",
|
|
104
|
+
"export const jsxs = (type) => ({ type, runtime: 'jsxs' });",
|
|
105
|
+
"export const Fragment = Symbol.for('react.fragment');",
|
|
106
|
+
"export const jsxDEV = undefined;",
|
|
107
|
+
].join("\n"),
|
|
108
|
+
"utf-8",
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Generate the real shim source, but resolve its bare `react` import to
|
|
112
|
+
// the stub so we can drive the undefined-jsxDEV condition.
|
|
113
|
+
const shimSource = _testOnly_generateJsxDevRuntimeShimSource().replace(
|
|
114
|
+
/from\s*['"]react['"]/g,
|
|
115
|
+
`from ${JSON.stringify(reactStub.replace(/\\/g, "/"))}`,
|
|
116
|
+
);
|
|
117
|
+
const shimSrc = path.join(dir, "_jsx-dev-runtime.src.js");
|
|
118
|
+
await writeFile(shimSrc, shimSource, "utf-8");
|
|
119
|
+
|
|
120
|
+
const result = await Bun.build({
|
|
121
|
+
entrypoints: [shimSrc],
|
|
122
|
+
target: "browser",
|
|
123
|
+
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
124
|
+
});
|
|
125
|
+
expect(result.success).toBe(true);
|
|
126
|
+
|
|
127
|
+
const outPath = path.join(dir, "_jsx-dev-runtime.built.js");
|
|
128
|
+
await writeFile(outPath, await result.outputs[0]!.text(), "utf-8");
|
|
129
|
+
const mod = await import(`${pathToFileURL(outPath).href}?t=${Date.now()}`);
|
|
130
|
+
|
|
131
|
+
// The whole point of #323's re-regression: jsxDEV must be CALLABLE.
|
|
132
|
+
expect(typeof mod.jsxDEV).toBe("function");
|
|
133
|
+
// Fallback routes static-children to jsxs, otherwise jsx.
|
|
134
|
+
expect(mod.jsxDEV("div", {}, undefined, false).runtime).toBe("jsx");
|
|
135
|
+
expect(mod.jsxDEV("div", {}, undefined, true).runtime).toBe("jsxs");
|
|
136
|
+
} finally {
|
|
137
|
+
await rm(dir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
86
140
|
});
|
package/src/bundler/build.ts
CHANGED
|
@@ -660,12 +660,24 @@ function generateJsxDevRuntimeShimSource(): string {
|
|
|
660
660
|
* Development JSX 변환용
|
|
661
661
|
*
|
|
662
662
|
* #323: import map의 'react/jsx-dev-runtime'이 다시 이 셰임을 가리키므로
|
|
663
|
-
* 'react/jsx-dev-runtime'에서 import하면 순환 self-import가
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
663
|
+
* 'react/jsx-dev-runtime'에서 import하면 순환 self-import가 된다. 대신
|
|
664
|
+
* 'react'(→ _react.js, react 전체를 인라인하는 vendor 번들)에서 가져온다.
|
|
665
|
+
*
|
|
666
|
+
* #323 재발(0.54.30): jsxDEV는 React의 **dev 전용** export다. _react.js가
|
|
667
|
+
* production NODE_ENV로 번들되면 'react/jsx-dev-runtime'이 production 변형으로
|
|
668
|
+
* 해소되어 jsxDEV 값이 undefined가 된다(jsx/jsxs는 둘 다 존재). 그 결과 dev
|
|
669
|
+
* island가 'jsxDEV is not a function'으로 전멸했다. _react.js가 어떤 NODE_ENV로
|
|
670
|
+
* 빌드되든 안전하도록, jsxDEV가 함수가 아니면 jsx/jsxs로 폴백한다(dev 경고만
|
|
671
|
+
* 잃고 렌더는 정상). isStaticChildren일 때는 jsxs로 라우팅한다.
|
|
667
672
|
*/
|
|
668
|
-
import { jsxDEV, Fragment } from 'react';
|
|
673
|
+
import { jsx, jsxs, jsxDEV as __manduReactJsxDEV, Fragment } from 'react';
|
|
674
|
+
|
|
675
|
+
const jsxDEV =
|
|
676
|
+
typeof __manduReactJsxDEV === 'function'
|
|
677
|
+
? __manduReactJsxDEV
|
|
678
|
+
: function jsxDEV(type, config, key, isStaticChildren) {
|
|
679
|
+
return (isStaticChildren ? jsxs : jsx)(type, config, key);
|
|
680
|
+
};
|
|
669
681
|
|
|
670
682
|
// Named exports
|
|
671
683
|
export { jsxDEV, Fragment };
|
package/src/runtime/server.ts
CHANGED
|
@@ -117,11 +117,12 @@ import {
|
|
|
117
117
|
resolvePrerenderedFile,
|
|
118
118
|
type PrerenderIndex,
|
|
119
119
|
} from "../bundler/prerender";
|
|
120
|
-
import {
|
|
121
|
-
buildOverlayErrorHtml,
|
|
122
|
-
buildPayloadFromError,
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
import {
|
|
121
|
+
buildOverlayErrorHtml,
|
|
122
|
+
buildPayloadFromError,
|
|
123
|
+
OVERLAY_CUSTOM_EVENT,
|
|
124
|
+
shouldInjectOverlay,
|
|
125
|
+
} from "../dev-error-overlay";
|
|
125
126
|
// Phase 18.μ — i18n dispatch. `resolveLocale()` is pure (no side effects),
|
|
126
127
|
// `createTranslator()` binds a per-request `t()` to the registry.
|
|
127
128
|
import {
|
|
@@ -209,15 +210,24 @@ export interface ServerOptions {
|
|
|
209
210
|
* 자동으로 캐싱됨 (Next.js `export const revalidate` 등가).
|
|
210
211
|
* - `false`/undefined : 캐시 비활성화
|
|
211
212
|
*/
|
|
212
|
-
cache?: boolean | CacheStore | CacheConfig;
|
|
213
|
-
/**
|
|
214
|
-
* Internal management token for local CLI/runtime control endpoints.
|
|
215
|
-
* When set, token-protected endpoints such as `/_mandu/cache` become available.
|
|
216
|
-
*/
|
|
217
|
-
managementToken?: string;
|
|
218
|
-
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
213
|
+
cache?: boolean | CacheStore | CacheConfig;
|
|
214
|
+
/**
|
|
215
|
+
* Internal management token for local CLI/runtime control endpoints.
|
|
216
|
+
* When set, token-protected endpoints such as `/_mandu/cache` become available.
|
|
217
|
+
*/
|
|
218
|
+
managementToken?: string;
|
|
219
|
+
/**
|
|
220
|
+
* Phase 18.κ — override initial `.mandu/client/*` health state when
|
|
221
|
+
* starting a dev server.
|
|
222
|
+
*/
|
|
223
|
+
clientBundleHealthy?: boolean;
|
|
224
|
+
/** Optional failure reason surfaced by dev overlay when unhealthy. */
|
|
225
|
+
clientBundleFailureReason?: string;
|
|
226
|
+
/** Optional route ID associated with the latest client-bundle failure. */
|
|
227
|
+
clientBundleFailureRouteId?: string;
|
|
228
|
+
/**
|
|
229
|
+
* Issue #192 — enable CSS View Transitions auto-inject (default `true`).
|
|
230
|
+
* When `true`, every SSR response gets
|
|
221
231
|
* `<style>@view-transition{navigation:auto}</style>` in its `<head>`,
|
|
222
232
|
* giving supported browsers a default crossfade on cross-document
|
|
223
233
|
* navigation. Pass `false` to suppress (typically wired from
|
|
@@ -533,19 +543,29 @@ type CreateAppFn = (context: AppContext) => React.ReactElement;
|
|
|
533
543
|
* 같은 프로세스에서 여러 서버를 띄울 때 핸들러가 섞이는 문제 방지
|
|
534
544
|
*/
|
|
535
545
|
export interface ServerRegistrySettings {
|
|
536
|
-
isDev: boolean;
|
|
537
|
-
hmrPort?: number;
|
|
538
|
-
bundleManifest?: BundleManifest;
|
|
539
|
-
rootDir: string;
|
|
540
|
-
publicDir: string;
|
|
541
|
-
cors?: CorsOptions | false;
|
|
542
|
-
streaming: boolean;
|
|
543
|
-
rateLimit?: NormalizedRateLimitOptions | false;
|
|
544
|
-
/**
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
|
|
546
|
+
isDev: boolean;
|
|
547
|
+
hmrPort?: number;
|
|
548
|
+
bundleManifest?: BundleManifest;
|
|
549
|
+
rootDir: string;
|
|
550
|
+
publicDir: string;
|
|
551
|
+
cors?: CorsOptions | false;
|
|
552
|
+
streaming: boolean;
|
|
553
|
+
rateLimit?: NormalizedRateLimitOptions | false;
|
|
554
|
+
/**
|
|
555
|
+
* Internal flag controlled by the CLI dev bundler lifecycle.
|
|
556
|
+
* `false` disables serving `.mandu/client/*` while the latest
|
|
557
|
+
* client bundle build is unhealthy.
|
|
558
|
+
*/
|
|
559
|
+
clientBundleHealthy?: boolean;
|
|
560
|
+
/** Last client bundle failure message emitted by the dev bundler. */
|
|
561
|
+
clientBundleFailureReason?: string;
|
|
562
|
+
/** Route ID associated with the last client bundle failure. */
|
|
563
|
+
clientBundleFailureRouteId?: string;
|
|
564
|
+
/**
|
|
565
|
+
* CSS 파일 경로 (SSR 링크 주입용)
|
|
566
|
+
* - string: 해당 경로로 <link> 주입
|
|
567
|
+
* - false: CSS 링크 주입 비활성화
|
|
568
|
+
* - undefined: false로 처리 (404 방지)
|
|
549
569
|
*/
|
|
550
570
|
cssPath?: string | false;
|
|
551
571
|
/** ISR/SWR 캐시 스토어 */
|
|
@@ -700,14 +720,17 @@ export class ServerRegistry {
|
|
|
700
720
|
readonly pageGenerateMetadata: Map<string, GenerateMetadata> = new Map();
|
|
701
721
|
readonly layoutMetadata: Map<string, Metadata | null> = new Map();
|
|
702
722
|
readonly layoutGenerateMetadata: Map<string, GenerateMetadata> = new Map();
|
|
703
|
-
settings: ServerRegistrySettings = {
|
|
704
|
-
isDev: false,
|
|
705
|
-
rootDir: process.cwd(),
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
723
|
+
settings: ServerRegistrySettings = {
|
|
724
|
+
isDev: false,
|
|
725
|
+
rootDir: process.cwd(),
|
|
726
|
+
clientBundleHealthy: true,
|
|
727
|
+
clientBundleFailureReason: undefined,
|
|
728
|
+
clientBundleFailureRouteId: undefined,
|
|
729
|
+
publicDir: "public",
|
|
730
|
+
cors: false,
|
|
731
|
+
streaming: false,
|
|
732
|
+
rateLimit: false,
|
|
733
|
+
};
|
|
711
734
|
|
|
712
735
|
registerApiHandler(routeId: string, handler: ApiHandler): void {
|
|
713
736
|
this.apiHandlers.set(routeId, handler);
|
|
@@ -3187,15 +3210,70 @@ function paramsInStaticSet(
|
|
|
3187
3210
|
if (allMatch) return true;
|
|
3188
3211
|
}
|
|
3189
3212
|
return false;
|
|
3190
|
-
}
|
|
3191
|
-
// ─── End Issue #214 ─────────────────────────────────────────────────────────
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3213
|
+
}
|
|
3214
|
+
// ─── End Issue #214 ─────────────────────────────────────────────────────────
|
|
3215
|
+
|
|
3216
|
+
function buildClientBundleFailureResponse(pathname: string, settings: ServerRegistrySettings): Response {
|
|
3217
|
+
const reason = settings.clientBundleFailureReason ?? "Client bundle build failed.";
|
|
3218
|
+
const routeId = settings.clientBundleFailureRouteId;
|
|
3219
|
+
const failureDetails = routeId ? ` Route: ${routeId}.` : "";
|
|
3220
|
+
const extension = path.extname(pathname).toLowerCase();
|
|
3221
|
+
const isCss = extension === ".css";
|
|
3222
|
+
const payload = buildPayloadFromError(
|
|
3223
|
+
new Error(`${reason}${failureDetails}`),
|
|
3224
|
+
{
|
|
3225
|
+
kind: "manual",
|
|
3226
|
+
routeId,
|
|
3227
|
+
url: pathname,
|
|
3228
|
+
},
|
|
3229
|
+
);
|
|
3230
|
+
const payloadJson = JSON.stringify(payload).replace(/</g, "\\u003c");
|
|
3231
|
+
const reasonPayload = JSON.stringify({
|
|
3232
|
+
message: reason,
|
|
3233
|
+
routeId,
|
|
3234
|
+
pathname,
|
|
3235
|
+
}).replace(/</g, "\\u003c");
|
|
3236
|
+
|
|
3237
|
+
if (isCss) {
|
|
3238
|
+
const cssBody = `/* Mandu: client bundle unavailable in dev. ${reason}${failureDetails} */`;
|
|
3239
|
+
return new Response(cssBody, {
|
|
3240
|
+
status: 503,
|
|
3241
|
+
headers: {
|
|
3242
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
3243
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
3244
|
+
},
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
const jsBody = [
|
|
3249
|
+
"(function(){",
|
|
3250
|
+
`var payload = ${payloadJson};`,
|
|
3251
|
+
"if (typeof console !== \"undefined\" && console && console.warn) {",
|
|
3252
|
+
`console.warn("Mandu: client bundle is unavailable in dev mode.", ${reasonPayload});`,
|
|
3253
|
+
"}",
|
|
3254
|
+
"if (typeof window !== \"undefined\") {",
|
|
3255
|
+
"try {",
|
|
3256
|
+
`window.dispatchEvent(new CustomEvent(${JSON.stringify(OVERLAY_CUSTOM_EVENT)}, { detail: payload }));`,
|
|
3257
|
+
"} catch (_) {}",
|
|
3258
|
+
"}",
|
|
3259
|
+
"})();",
|
|
3260
|
+
].join("");
|
|
3261
|
+
|
|
3262
|
+
return new Response(jsBody, {
|
|
3263
|
+
status: 503,
|
|
3264
|
+
headers: {
|
|
3265
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
3266
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
3267
|
+
},
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
async function handleRequestInternal(
|
|
3272
|
+
req: Request,
|
|
3273
|
+
router: Router,
|
|
3274
|
+
registry: ServerRegistry,
|
|
3275
|
+
skipMiddleware: boolean = false
|
|
3276
|
+
): Promise<Result<Response>> {
|
|
3199
3277
|
const url = new URL(req.url);
|
|
3200
3278
|
const pathname = url.pathname;
|
|
3201
3279
|
const settings = registry.settings;
|
|
@@ -3217,16 +3295,20 @@ async function handleRequestInternal(
|
|
|
3217
3295
|
// HMR signals "full reload" — user saves a file, reloads, and the old
|
|
3218
3296
|
// prerendered output wins. Production `mandu start` always runs with
|
|
3219
3297
|
// `isDev: false`, so prod behavior is unchanged.
|
|
3220
|
-
if (!settings.isDev) {
|
|
3221
|
-
const prerendered = await tryServePrerendered(pathname, settings, req.method, req);
|
|
3222
|
-
if (prerendered) {
|
|
3223
|
-
if (settings.cors && isCorsRequest(req)) {
|
|
3224
|
-
const corsOptions: CorsOptions = typeof settings.cors === 'object' ? settings.cors : {};
|
|
3225
|
-
return ok(applyCorsToResponse(prerendered, req, corsOptions));
|
|
3226
|
-
}
|
|
3227
|
-
return ok(prerendered);
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3298
|
+
if (!settings.isDev) {
|
|
3299
|
+
const prerendered = await tryServePrerendered(pathname, settings, req.method, req);
|
|
3300
|
+
if (prerendered) {
|
|
3301
|
+
if (settings.cors && isCorsRequest(req)) {
|
|
3302
|
+
const corsOptions: CorsOptions = typeof settings.cors === 'object' ? settings.cors : {};
|
|
3303
|
+
return ok(applyCorsToResponse(prerendered, req, corsOptions));
|
|
3304
|
+
}
|
|
3305
|
+
return ok(prerendered);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
if (settings.isDev && settings.clientBundleHealthy === false && pathname.startsWith("/.mandu/client/")) {
|
|
3310
|
+
return ok(buildClientBundleFailureResponse(pathname, settings));
|
|
3311
|
+
}
|
|
3230
3312
|
|
|
3231
3313
|
// ─── Phase 18.μ — i18n dispatch ─────────────────────────────────────────
|
|
3232
3314
|
// Runs AFTER γ's prerendered check (static HTML per-locale is already
|
|
@@ -3750,12 +3832,15 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
3750
3832
|
cors = false,
|
|
3751
3833
|
streaming = false,
|
|
3752
3834
|
rateLimit = false,
|
|
3753
|
-
cssPath: cssPathOption,
|
|
3754
|
-
registry = defaultRegistry,
|
|
3755
|
-
guardConfig = null,
|
|
3756
|
-
cache: cacheOption,
|
|
3757
|
-
managementToken,
|
|
3758
|
-
|
|
3835
|
+
cssPath: cssPathOption,
|
|
3836
|
+
registry = defaultRegistry,
|
|
3837
|
+
guardConfig = null,
|
|
3838
|
+
cache: cacheOption,
|
|
3839
|
+
managementToken,
|
|
3840
|
+
clientBundleHealthy = true,
|
|
3841
|
+
clientBundleFailureReason,
|
|
3842
|
+
clientBundleFailureRouteId,
|
|
3843
|
+
transitions,
|
|
3759
3844
|
prefetch,
|
|
3760
3845
|
spa,
|
|
3761
3846
|
devtools,
|
|
@@ -3857,16 +3942,19 @@ export function startServer(manifest: RoutesManifest, options: ServerOptions = {
|
|
|
3857
3942
|
});
|
|
3858
3943
|
|
|
3859
3944
|
// Registry settings 저장 (초기값)
|
|
3860
|
-
registry.settings = {
|
|
3861
|
-
isDev,
|
|
3862
|
-
hmrPort,
|
|
3863
|
-
bundleManifest,
|
|
3864
|
-
rootDir,
|
|
3865
|
-
publicDir,
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3945
|
+
registry.settings = {
|
|
3946
|
+
isDev,
|
|
3947
|
+
hmrPort,
|
|
3948
|
+
bundleManifest,
|
|
3949
|
+
rootDir,
|
|
3950
|
+
publicDir,
|
|
3951
|
+
clientBundleHealthy,
|
|
3952
|
+
clientBundleFailureReason,
|
|
3953
|
+
clientBundleFailureRouteId,
|
|
3954
|
+
cors: corsOptions,
|
|
3955
|
+
streaming,
|
|
3956
|
+
rateLimit: rateLimitOptions,
|
|
3957
|
+
cssPath,
|
|
3870
3958
|
managementToken,
|
|
3871
3959
|
transitions,
|
|
3872
3960
|
prefetch,
|
|
@@ -4158,10 +4246,19 @@ export const routeComponents = defaultRegistry.routeComponents;
|
|
|
4158
4246
|
export interface AppFetchHandlerOptions {
|
|
4159
4247
|
/** Project root (used for module path validation). Required. */
|
|
4160
4248
|
rootDir: string;
|
|
4161
|
-
/** Bundle manifest (Island hydration). Optional in pure-SSR apps. */
|
|
4162
|
-
bundleManifest?: BundleManifest;
|
|
4163
|
-
/**
|
|
4164
|
-
|
|
4249
|
+
/** Bundle manifest (Island hydration). Optional in pure-SSR apps. */
|
|
4250
|
+
bundleManifest?: BundleManifest;
|
|
4251
|
+
/**
|
|
4252
|
+
* Phase 18.κ — override initial `.mandu/client/*` health state when using
|
|
4253
|
+
* a runtime-neutral fetch handler.
|
|
4254
|
+
*/
|
|
4255
|
+
clientBundleHealthy?: boolean;
|
|
4256
|
+
/** Optional failure reason surfaced by dev overlay when unhealthy. */
|
|
4257
|
+
clientBundleFailureReason?: string;
|
|
4258
|
+
/** Optional route ID associated with the latest client-bundle failure. */
|
|
4259
|
+
clientBundleFailureRouteId?: string;
|
|
4260
|
+
/** CORS config — `true` allows all origins, object for fine-grained rules. */
|
|
4261
|
+
cors?: boolean | CorsOptions;
|
|
4165
4262
|
/** Streaming SSR toggle. Default: `false`. */
|
|
4166
4263
|
streaming?: boolean;
|
|
4167
4264
|
/** Rate limit policy. Memory-backed; edge runtimes should prefer durable stores. */
|
|
@@ -4221,11 +4318,14 @@ export function createAppFetchHandler(
|
|
|
4221
4318
|
manifest: RoutesManifest,
|
|
4222
4319
|
options: AppFetchHandlerOptions
|
|
4223
4320
|
): (req: Request) => Promise<Response> {
|
|
4224
|
-
const {
|
|
4225
|
-
rootDir,
|
|
4226
|
-
bundleManifest,
|
|
4227
|
-
|
|
4228
|
-
|
|
4321
|
+
const {
|
|
4322
|
+
rootDir,
|
|
4323
|
+
bundleManifest,
|
|
4324
|
+
clientBundleHealthy = true,
|
|
4325
|
+
clientBundleFailureReason,
|
|
4326
|
+
clientBundleFailureRouteId,
|
|
4327
|
+
cors = false,
|
|
4328
|
+
streaming = false,
|
|
4229
4329
|
rateLimit = false,
|
|
4230
4330
|
cssPath = false,
|
|
4231
4331
|
registry = defaultRegistry,
|
|
@@ -4236,15 +4336,18 @@ export function createAppFetchHandler(
|
|
|
4236
4336
|
const corsOptions: CorsOptions | false = cors === true ? {} : cors;
|
|
4237
4337
|
const rateLimitOptions = normalizeRateLimitOptions(rateLimit);
|
|
4238
4338
|
|
|
4239
|
-
registry.settings = {
|
|
4240
|
-
isDev: false,
|
|
4241
|
-
bundleManifest,
|
|
4242
|
-
rootDir,
|
|
4243
|
-
publicDir: "public",
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4339
|
+
registry.settings = {
|
|
4340
|
+
isDev: false,
|
|
4341
|
+
bundleManifest,
|
|
4342
|
+
rootDir,
|
|
4343
|
+
publicDir: "public",
|
|
4344
|
+
clientBundleHealthy,
|
|
4345
|
+
clientBundleFailureReason,
|
|
4346
|
+
clientBundleFailureRouteId,
|
|
4347
|
+
cors: corsOptions,
|
|
4348
|
+
streaming,
|
|
4349
|
+
rateLimit: rateLimitOptions,
|
|
4350
|
+
cssPath,
|
|
4248
4351
|
edge,
|
|
4249
4352
|
};
|
|
4250
4353
|
|