akanjs 2.2.12 → 2.2.13-rc.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/package.json +1 -1
- package/server/akanApp.ts +55 -0
- package/server/rscWorker.tsx +145 -39
- package/server/rscWorkerHost.ts +235 -66
- package/server/rscWorkerReplay.ts +35 -0
- package/server/ssrFromRscRenderer.tsx +448 -75
- package/server/ssrTypes.ts +9 -0
- package/server/webRouter.ts +91 -10
- package/service/ipcTypes.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/server/rscWorkerHost.d.ts +29 -0
- package/types/server/rscWorkerReplay.d.ts +23 -0
- package/types/server/ssrFromRscRenderer.d.ts +18 -1
- package/types/server/ssrTypes.d.ts +8 -0
- package/types/server/webRouter.d.ts +6 -1
- package/types/service/ipcTypes.d.ts +1 -0
- package/ui/Link/SsrLink.tsx +0 -2
package/package.json
CHANGED
package/server/akanApp.ts
CHANGED
|
@@ -84,6 +84,8 @@ export class AkanApp {
|
|
|
84
84
|
#logWriter: RotatingLogWriter | null = null;
|
|
85
85
|
#removeLogSink: (() => void) | null = null;
|
|
86
86
|
readonly #childOutputBuffers = new Map<string, string>();
|
|
87
|
+
readonly #childStderrBlockBuffers = new Map<string, string[]>();
|
|
88
|
+
readonly #childStderrBlockTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
87
89
|
static readonly #ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
88
90
|
#gatewayMetrics: AkanMetricsReport = {};
|
|
89
91
|
#proxyHopCount = 0;
|
|
@@ -1004,6 +1006,7 @@ export class AkanApp {
|
|
|
1004
1006
|
if (remaining) this.#writeChildOutput(idx, role, type, bufferKey, remaining);
|
|
1005
1007
|
} finally {
|
|
1006
1008
|
this.#flushChildOutput(idx, role, type, bufferKey);
|
|
1009
|
+
if (type === "stderr") this.#flushChildStderrBlock(idx, role, AkanApp.#childStderrBlockKey(idx, role));
|
|
1007
1010
|
}
|
|
1008
1011
|
}
|
|
1009
1012
|
|
|
@@ -1028,11 +1031,63 @@ export class AkanApp {
|
|
|
1028
1031
|
}
|
|
1029
1032
|
|
|
1030
1033
|
#writeChildOutputLine(idx: number, role: AkanChildRole, type: "stdout" | "stderr", line: string) {
|
|
1034
|
+
if (type === "stderr" && this.#bufferChildStderrLine(idx, role, line)) return;
|
|
1035
|
+
this.#writeChildOutputLineRaw(idx, role, type, line);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
#writeChildOutputLineRaw(idx: number, role: AkanChildRole, type: "stdout" | "stderr", line: string) {
|
|
1031
1039
|
const prefixedLine = `[child:${idx} ${role}] [${type}] ${line}`;
|
|
1032
1040
|
process[type].write(prefixedLine);
|
|
1033
1041
|
this.#logWriter?.write(`${idx}-${role}`, AkanApp.#stripAnsi(prefixedLine));
|
|
1034
1042
|
}
|
|
1035
1043
|
|
|
1044
|
+
#bufferChildStderrLine(idx: number, role: AkanChildRole, line: string): boolean {
|
|
1045
|
+
const key = AkanApp.#childStderrBlockKey(idx, role);
|
|
1046
|
+
const block = this.#childStderrBlockBuffers.get(key) ?? [];
|
|
1047
|
+
block.push(line);
|
|
1048
|
+
this.#childStderrBlockBuffers.set(key, block);
|
|
1049
|
+
|
|
1050
|
+
const existingTimer = this.#childStderrBlockTimers.get(key);
|
|
1051
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
1052
|
+
|
|
1053
|
+
if (line.trim() === "" || block.length >= 64) {
|
|
1054
|
+
this.#flushChildStderrBlock(idx, role, key);
|
|
1055
|
+
return true;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
this.#childStderrBlockTimers.set(
|
|
1059
|
+
key,
|
|
1060
|
+
setTimeout(() => this.#flushChildStderrBlock(idx, role, key), 50),
|
|
1061
|
+
);
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
#flushChildStderrBlock(idx: number, role: AkanChildRole, key: string) {
|
|
1066
|
+
const timer = this.#childStderrBlockTimers.get(key);
|
|
1067
|
+
if (timer) clearTimeout(timer);
|
|
1068
|
+
this.#childStderrBlockTimers.delete(key);
|
|
1069
|
+
|
|
1070
|
+
const block = this.#childStderrBlockBuffers.get(key);
|
|
1071
|
+
if (!block?.length) return;
|
|
1072
|
+
this.#childStderrBlockBuffers.delete(key);
|
|
1073
|
+
|
|
1074
|
+
const text = block.join("");
|
|
1075
|
+
if (AkanApp.#isBenignRsdwConnectionClosedBlock(text)) return;
|
|
1076
|
+
for (const blockLine of block) this.#writeChildOutputLineRaw(idx, role, "stderr", blockLine);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
static #childStderrBlockKey(idx: number, role: AkanChildRole): string {
|
|
1080
|
+
return `${idx}:${role}:stderr`;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
static #isBenignRsdwConnectionClosedBlock(text: string): boolean {
|
|
1084
|
+
return (
|
|
1085
|
+
text.includes('reportGlobalError(weakResponse, Error("Connection closed."))') &&
|
|
1086
|
+
text.includes("error: Connection closed.") &&
|
|
1087
|
+
text.includes("react-server-dom-webpack")
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1036
1091
|
static #stripAnsi(msg: string) {
|
|
1037
1092
|
return msg.replace(AkanApp.#ansiPattern, "");
|
|
1038
1093
|
}
|
package/server/rscWorker.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import type { ClientManifest } from "./artifact";
|
|
|
13
13
|
import { ProcessMetricsCollector } from "./processMetricsCollector";
|
|
14
14
|
import { RouteElementComposer } from "./routeElementComposer";
|
|
15
15
|
import { type PagesContext, RouteTreeBuilder } from "./routeTreeBuilder";
|
|
16
|
+
import { replayCachedRscResult } from "./rscWorkerReplay";
|
|
16
17
|
import { createSystemPageDocument, getSystemPageHomeHref } from "./systemPages";
|
|
17
18
|
|
|
18
19
|
interface InitMsg {
|
|
@@ -32,6 +33,10 @@ interface RenderMsg {
|
|
|
32
33
|
headers?: Record<string, string>;
|
|
33
34
|
clientManifest?: ClientManifest;
|
|
34
35
|
}
|
|
36
|
+
interface CancelMsg {
|
|
37
|
+
type: "cancel";
|
|
38
|
+
requestId: string;
|
|
39
|
+
}
|
|
35
40
|
interface ReloadMsg {
|
|
36
41
|
type: "reload";
|
|
37
42
|
clientManifest: ClientManifest;
|
|
@@ -44,11 +49,19 @@ interface UpdateCssAssetsMsg {
|
|
|
44
49
|
type: "updateCssAssets";
|
|
45
50
|
cssAssets: Record<string, { cssUrl: string; cssRelPath: string }>;
|
|
46
51
|
}
|
|
47
|
-
type InMsg = InitMsg | RenderMsg | ReloadMsg | UpdateCssAssetsMsg;
|
|
52
|
+
type InMsg = InitMsg | RenderMsg | CancelMsg | ReloadMsg | UpdateCssAssetsMsg;
|
|
48
53
|
type RenderControl =
|
|
49
54
|
| { type: "redirect"; location: string; method: "replace" | "push"; status: RedirectStatus }
|
|
50
55
|
| { type: "not-found" }
|
|
51
56
|
| { type: "error"; error: unknown };
|
|
57
|
+
interface FlightRenderResult {
|
|
58
|
+
chunks: Uint8Array[];
|
|
59
|
+
bytes: number;
|
|
60
|
+
chunksCount: number;
|
|
61
|
+
control: RenderControl | null;
|
|
62
|
+
lateControlSent: boolean;
|
|
63
|
+
cancelled: boolean;
|
|
64
|
+
}
|
|
52
65
|
|
|
53
66
|
interface RscRendererStats {
|
|
54
67
|
renderCount: number;
|
|
@@ -128,6 +141,8 @@ class RscRenderer {
|
|
|
128
141
|
};
|
|
129
142
|
readonly #routeStats = new Map<string, RouteRenderStats>();
|
|
130
143
|
readonly #resultCache = new Map<string, CachedRscResult>();
|
|
144
|
+
readonly #activeRenderReaders = new Map<string, ReadableStreamDefaultReader<Uint8Array>>();
|
|
145
|
+
readonly #cancelledRenderRequests = new Set<string>();
|
|
131
146
|
#resultCacheHits = 0;
|
|
132
147
|
#resultCacheMisses = 0;
|
|
133
148
|
#resultCacheBypass = 0;
|
|
@@ -158,6 +173,10 @@ class RscRenderer {
|
|
|
158
173
|
this.#logger.verbose(`received render requestId=${msg.requestId} url=${msg.url} method=${msg.method ?? "GET"}`);
|
|
159
174
|
void this.#handleRender(msg);
|
|
160
175
|
return;
|
|
176
|
+
case "cancel":
|
|
177
|
+
this.#logger.verbose(`received cancel requestId=${msg.requestId}`);
|
|
178
|
+
this.#handleCancel(msg.requestId);
|
|
179
|
+
return;
|
|
161
180
|
case "reload":
|
|
162
181
|
this.#logger.verbose(`received reload buildId=${msg.buildId}`);
|
|
163
182
|
void this.#handleReload(msg);
|
|
@@ -169,6 +188,14 @@ class RscRenderer {
|
|
|
169
188
|
}
|
|
170
189
|
}
|
|
171
190
|
|
|
191
|
+
#handleCancel(requestId: string): void {
|
|
192
|
+
this.#cancelledRenderRequests.add(requestId);
|
|
193
|
+
const reader = this.#activeRenderReaders.get(requestId);
|
|
194
|
+
if (!reader) return;
|
|
195
|
+
void reader.cancel().catch(() => {
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
172
199
|
async #handleInit(msg: InitMsg): Promise<void> {
|
|
173
200
|
const startedAt = Date.now();
|
|
174
201
|
try {
|
|
@@ -300,27 +327,42 @@ class RscRenderer {
|
|
|
300
327
|
this.#stats.totalFlightBytes += cached.bytes;
|
|
301
328
|
this.#stats.totalFlightChunks += cached.chunksCount;
|
|
302
329
|
this.#recordRouteStats(routeId, cached.bytes, this.#stats.lastRenderDurationMs);
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
330
|
+
await replayCachedRscResult({
|
|
331
|
+
requestId,
|
|
332
|
+
chunks: cached.chunks,
|
|
333
|
+
theme: cached.theme,
|
|
334
|
+
send: (message) => this.#send(message),
|
|
335
|
+
isCancelled: () => this.#cancelledRenderRequests.has(requestId),
|
|
336
|
+
});
|
|
306
337
|
return;
|
|
307
338
|
}
|
|
308
339
|
const theme = cookies().get("theme")?.value;
|
|
309
|
-
const
|
|
340
|
+
const searchParams = RouteTreeBuilder.parseSearchParams(urlObj.search);
|
|
341
|
+
let element: ReactNode;
|
|
342
|
+
if (match) element = await this.#renderMatched(urlObj, match, theme, searchParams);
|
|
343
|
+
else element = await this.#renderNotFound(urlObj);
|
|
310
344
|
this.#logger.verbose(`render[${requestId}] starting Flight stream`);
|
|
311
|
-
const result = await this.#renderFlightElement(element, msg.clientManifest ?? this.#clientManifest
|
|
345
|
+
const result = await this.#renderFlightElement(element, msg.clientManifest ?? this.#clientManifest, {
|
|
346
|
+
requestId,
|
|
347
|
+
collectChunks: cacheKey !== null,
|
|
348
|
+
status: match ? undefined : 404,
|
|
349
|
+
});
|
|
350
|
+
if (result.cancelled) return;
|
|
312
351
|
const control = result.control;
|
|
313
352
|
if (control) {
|
|
314
353
|
this.#stats.lastRenderKind = control.type;
|
|
354
|
+
if (result.lateControlSent) {
|
|
355
|
+
this.#logger.verbose(`render[${requestId}] late ${control.type} delivered after stream start`);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
315
358
|
if (!match && control.type === "error") {
|
|
316
359
|
const systemResult = await this.#renderFlightElement(
|
|
317
360
|
this.#renderSystemNotFound(urlObj),
|
|
318
361
|
msg.clientManifest ?? this.#clientManifest,
|
|
362
|
+
{ requestId, status: 404 },
|
|
319
363
|
);
|
|
364
|
+
if (systemResult.cancelled) return;
|
|
320
365
|
if (!systemResult.control) {
|
|
321
|
-
this.#send({ type: "meta", requestId, theme: getRequestTheme(), status: 404 });
|
|
322
|
-
for (const chunk of systemResult.chunks) this.#send({ type: "chunk", requestId, data: chunk });
|
|
323
|
-
this.#send({ type: "end", requestId });
|
|
324
366
|
return;
|
|
325
367
|
}
|
|
326
368
|
}
|
|
@@ -332,7 +374,7 @@ class RscRenderer {
|
|
|
332
374
|
kind: control.type,
|
|
333
375
|
route: match.pathRoute,
|
|
334
376
|
params: match.params,
|
|
335
|
-
searchParams
|
|
377
|
+
searchParams,
|
|
336
378
|
pathname: urlObj.pathname,
|
|
337
379
|
url: urlObj,
|
|
338
380
|
error: control.type === "error" ? control.error : undefined,
|
|
@@ -345,9 +387,9 @@ class RscRenderer {
|
|
|
345
387
|
return;
|
|
346
388
|
}
|
|
347
389
|
this.#stats.lastFlightBytes = result.bytes;
|
|
348
|
-
this.#stats.lastFlightChunks = result.
|
|
390
|
+
this.#stats.lastFlightChunks = result.chunksCount;
|
|
349
391
|
this.#stats.totalFlightBytes += result.bytes;
|
|
350
|
-
this.#stats.totalFlightChunks += result.
|
|
392
|
+
this.#stats.totalFlightChunks += result.chunksCount;
|
|
351
393
|
this.#stats.lastRenderDurationMs = Date.now() - startedAt;
|
|
352
394
|
const afterLoadedKeys = RouteTreeBuilder.getCacheStats().loadedModuleKeys;
|
|
353
395
|
this.#stats.lastRenderLoadedModules = afterLoadedKeys.filter((key) => !beforeLoadedKeys.includes(key));
|
|
@@ -358,17 +400,12 @@ class RscRenderer {
|
|
|
358
400
|
this.#setCachedResult(cacheKey, {
|
|
359
401
|
chunks: result.chunks,
|
|
360
402
|
bytes: result.bytes,
|
|
361
|
-
chunksCount: result.
|
|
403
|
+
chunksCount: result.chunksCount,
|
|
362
404
|
theme: responseTheme,
|
|
363
405
|
});
|
|
364
|
-
this.#send({ type: "meta", requestId, theme: responseTheme, status: match ? undefined : 404 });
|
|
365
|
-
for (const chunk of result.chunks) {
|
|
366
|
-
this.#send({ type: "chunk", requestId, data: chunk });
|
|
367
|
-
}
|
|
368
406
|
this.#logger.verbose(
|
|
369
|
-
`render[${requestId}] done chunks=${result.
|
|
407
|
+
`render[${requestId}] done chunks=${result.chunksCount} bytes=${result.bytes} in ${Date.now() - startedAt}ms`,
|
|
370
408
|
);
|
|
371
|
-
this.#send({ type: "end", requestId });
|
|
372
409
|
});
|
|
373
410
|
} catch (error) {
|
|
374
411
|
if (isAkanRedirectError(error)) {
|
|
@@ -435,6 +472,8 @@ class RscRenderer {
|
|
|
435
472
|
message: error instanceof Error ? error.message : String(error),
|
|
436
473
|
});
|
|
437
474
|
} finally {
|
|
475
|
+
this.#activeRenderReaders.delete(requestId);
|
|
476
|
+
this.#cancelledRenderRequests.delete(requestId);
|
|
438
477
|
this.#stats.inFlightRenderCount = Math.max(0, this.#stats.inFlightRenderCount - 1);
|
|
439
478
|
}
|
|
440
479
|
}
|
|
@@ -484,7 +523,12 @@ class RscRenderer {
|
|
|
484
523
|
async #renderFlightElement(
|
|
485
524
|
element: ReactNode,
|
|
486
525
|
clientManifest: ClientManifest,
|
|
487
|
-
|
|
526
|
+
options: {
|
|
527
|
+
requestId?: string;
|
|
528
|
+
collectChunks?: boolean;
|
|
529
|
+
status?: number;
|
|
530
|
+
} = {},
|
|
531
|
+
): Promise<FlightRenderResult> {
|
|
488
532
|
const controlRef: { current: RenderControl | null } = { current: null };
|
|
489
533
|
const stream = await renderToReadableStream(element, clientManifest, {
|
|
490
534
|
onError: (error) => {
|
|
@@ -506,20 +550,77 @@ class RscRenderer {
|
|
|
506
550
|
},
|
|
507
551
|
});
|
|
508
552
|
const reader = stream.getReader();
|
|
553
|
+
if (options.requestId) this.#activeRenderReaders.set(options.requestId, reader);
|
|
509
554
|
let bytes = 0;
|
|
555
|
+
let chunksCount = 0;
|
|
556
|
+
let sentMeta = false;
|
|
557
|
+
let sentChunk = false;
|
|
558
|
+
let lateControlSent = false;
|
|
510
559
|
const chunks: Uint8Array[] = [];
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
560
|
+
const sendMeta = () => {
|
|
561
|
+
if (!options.requestId || sentMeta) return;
|
|
562
|
+
sentMeta = true;
|
|
563
|
+
this.#send({ type: "meta", requestId: options.requestId, theme: getRequestTheme(), status: options.status });
|
|
564
|
+
};
|
|
565
|
+
const sendLateRedirect = () => {
|
|
566
|
+
if (!options.requestId || lateControlSent || controlRef.current?.type !== "redirect") return;
|
|
567
|
+
|
|
568
|
+
lateControlSent = true;
|
|
569
|
+
this.#send({
|
|
570
|
+
type: "late-redirect",
|
|
571
|
+
requestId: options.requestId,
|
|
572
|
+
location: controlRef.current.location,
|
|
573
|
+
method: controlRef.current.method,
|
|
574
|
+
status: controlRef.current.status,
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
try {
|
|
578
|
+
while (true) {
|
|
579
|
+
if (options.requestId && this.#cancelledRenderRequests.has(options.requestId)) {
|
|
580
|
+
await reader.cancel();
|
|
581
|
+
return { chunks, bytes, chunksCount, control: null, lateControlSent, cancelled: true };
|
|
582
|
+
}
|
|
583
|
+
const { value, done } = await reader.read();
|
|
584
|
+
if (controlRef.current && !sentChunk) {
|
|
585
|
+
await reader.cancel();
|
|
586
|
+
return { chunks, bytes, chunksCount, control: controlRef.current, lateControlSent, cancelled: false };
|
|
587
|
+
}
|
|
588
|
+
if (controlRef.current && sentChunk) sendLateRedirect();
|
|
589
|
+
if (done) break;
|
|
590
|
+
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value as ArrayBufferLike);
|
|
591
|
+
bytes += chunk.byteLength;
|
|
592
|
+
chunksCount += 1;
|
|
593
|
+
if (options.collectChunks) chunks.push(chunk);
|
|
594
|
+
if (options.requestId) {
|
|
595
|
+
sendMeta();
|
|
596
|
+
this.#send({ type: "chunk", requestId: options.requestId, data: chunk });
|
|
597
|
+
sentChunk = true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (options.requestId && this.#cancelledRenderRequests.has(options.requestId)) {
|
|
602
|
+
return { chunks, bytes, chunksCount, control: null, lateControlSent, cancelled: true };
|
|
517
603
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
604
|
+
throw error;
|
|
605
|
+
} finally {
|
|
606
|
+
if (options.requestId) this.#activeRenderReaders.delete(options.requestId);
|
|
607
|
+
reader.releaseLock();
|
|
608
|
+
}
|
|
609
|
+
if (controlRef.current && sentChunk) sendLateRedirect();
|
|
610
|
+
if (controlRef.current && !sentChunk)
|
|
611
|
+
return { chunks, bytes, chunksCount, control: controlRef.current, lateControlSent, cancelled: false };
|
|
612
|
+
if (options.requestId) {
|
|
613
|
+
sendMeta();
|
|
614
|
+
this.#send({ type: "end", requestId: options.requestId });
|
|
521
615
|
}
|
|
522
|
-
return {
|
|
616
|
+
return {
|
|
617
|
+
chunks,
|
|
618
|
+
bytes,
|
|
619
|
+
chunksCount,
|
|
620
|
+
control: lateControlSent ? controlRef.current : null,
|
|
621
|
+
lateControlSent,
|
|
622
|
+
cancelled: false,
|
|
623
|
+
};
|
|
523
624
|
}
|
|
524
625
|
|
|
525
626
|
async #trySendFallbackRender({
|
|
@@ -555,15 +656,16 @@ class RscRenderer {
|
|
|
555
656
|
digest: kind === "error" ? "AKAN_RENDER_ERROR" : undefined,
|
|
556
657
|
});
|
|
557
658
|
if (!element) return false;
|
|
558
|
-
const result = await this.#renderFlightElement(element, clientManifest
|
|
659
|
+
const result = await this.#renderFlightElement(element, clientManifest, {
|
|
660
|
+
requestId,
|
|
661
|
+
status: kind === "not-found" ? 404 : 500,
|
|
662
|
+
});
|
|
663
|
+
if (result.cancelled) return true;
|
|
559
664
|
if (result.control) return false;
|
|
560
|
-
this.#send({ type: "meta", requestId, theme: getRequestTheme(), status: kind === "not-found" ? 404 : 500 });
|
|
561
|
-
for (const chunk of result.chunks) this.#send({ type: "chunk", requestId, data: chunk });
|
|
562
|
-
this.#send({ type: "end", requestId });
|
|
563
665
|
this.#stats.lastFlightBytes = result.bytes;
|
|
564
|
-
this.#stats.lastFlightChunks = result.
|
|
666
|
+
this.#stats.lastFlightChunks = result.chunksCount;
|
|
565
667
|
this.#stats.totalFlightBytes += result.bytes;
|
|
566
|
-
this.#stats.totalFlightChunks += result.
|
|
668
|
+
this.#stats.totalFlightChunks += result.chunksCount;
|
|
567
669
|
return true;
|
|
568
670
|
} catch (fallbackError) {
|
|
569
671
|
this.#logger.error(
|
|
@@ -735,8 +837,8 @@ class RscRenderer {
|
|
|
735
837
|
url: URL,
|
|
736
838
|
match: { pathRoute: PathRoute; params: Record<string, string> },
|
|
737
839
|
theme?: string,
|
|
840
|
+
searchParams = RouteTreeBuilder.parseSearchParams(url.search),
|
|
738
841
|
): Promise<ReactNode> {
|
|
739
|
-
const searchParams = RouteTreeBuilder.parseSearchParams(url.search);
|
|
740
842
|
this.#logger.verbose(
|
|
741
843
|
`composing route element pathname=${url.pathname} search=${url.search || "(none)"} params=${JSON.stringify(match.params)}`,
|
|
742
844
|
);
|
|
@@ -745,7 +847,11 @@ class RscRenderer {
|
|
|
745
847
|
params: match.params,
|
|
746
848
|
searchParams,
|
|
747
849
|
});
|
|
748
|
-
const body = RouteElementComposer.compose({
|
|
850
|
+
const body = RouteElementComposer.compose({
|
|
851
|
+
pathRoute: match.pathRoute,
|
|
852
|
+
params: match.params,
|
|
853
|
+
searchParams,
|
|
854
|
+
});
|
|
749
855
|
return (
|
|
750
856
|
<html
|
|
751
857
|
lang={match.params.lang ?? this.#i18n.defaultLocale}
|
|
@@ -895,4 +1001,4 @@ class RscRenderer {
|
|
|
895
1001
|
}
|
|
896
1002
|
}
|
|
897
1003
|
|
|
898
|
-
new RscRenderer().start();
|
|
1004
|
+
if (import.meta.main) new RscRenderer().start();
|