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/server/webRouter.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { HMR_CLIENT_SCRIPT } from "./hmr/clientScript";
|
|
|
22
22
|
import type { HmrWsData, HmrWsHub } from "./hmr/wsHub";
|
|
23
23
|
import { ImageOptimizer } from "./imageOptimizer";
|
|
24
24
|
import { createDefaultRobotsTxt } from "./robots";
|
|
25
|
-
import { type RscRedirectMethod, type RscRedirectStatus, RscWorker } from "./rscWorkerHost";
|
|
25
|
+
import { type RscRedirectMethod, type RscRedirectStatus, type RscRenderResult, RscWorker } from "./rscWorkerHost";
|
|
26
26
|
import { createDefaultSitemapXml, getSitemapBasePath } from "./sitemap";
|
|
27
27
|
import { SsrFromRscRenderer } from "./ssrFromRscRenderer";
|
|
28
28
|
import { createSystemPageResponse, getSystemPageHomeHref } from "./systemPages";
|
|
@@ -57,6 +57,72 @@ export function createRscStreamResponse(stream: BodyInit, status = 200): Respons
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export function cacheHtmlWhileStreaming(
|
|
61
|
+
stream: ReadableStream<Uint8Array>,
|
|
62
|
+
onComplete: (html: string) => void,
|
|
63
|
+
): ReadableStream<Uint8Array> {
|
|
64
|
+
const chunks: Uint8Array[] = [];
|
|
65
|
+
let byteLength = 0;
|
|
66
|
+
const decoder = new TextDecoder();
|
|
67
|
+
|
|
68
|
+
return stream.pipeThrough(
|
|
69
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
70
|
+
transform(chunk, controller) {
|
|
71
|
+
chunks.push(chunk.slice());
|
|
72
|
+
byteLength += chunk.byteLength;
|
|
73
|
+
controller.enqueue(chunk);
|
|
74
|
+
},
|
|
75
|
+
flush() {
|
|
76
|
+
const body = new Uint8Array(byteLength);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const chunk of chunks) {
|
|
79
|
+
body.set(chunk, offset);
|
|
80
|
+
offset += chunk.byteLength;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
onComplete(decoder.decode(body));
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function cancelStreamForHeadResponse(stream: ReadableStream<Uint8Array>, reason: unknown): void {
|
|
92
|
+
void stream.cancel(reason).catch(() => {
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function createRscNavigationStreamResponse(
|
|
97
|
+
result: Extract<RscRenderResult, { type: "stream" }>,
|
|
98
|
+
): Promise<Response> {
|
|
99
|
+
|
|
100
|
+
const chunks: Uint8Array[] = [];
|
|
101
|
+
let byteLength = 0;
|
|
102
|
+
const reader = result.stream.getReader();
|
|
103
|
+
try {
|
|
104
|
+
while (true) {
|
|
105
|
+
const { value, done } = await reader.read();
|
|
106
|
+
if (done) break;
|
|
107
|
+
chunks.push(value);
|
|
108
|
+
byteLength += value.byteLength;
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
reader.releaseLock();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const lateControl = await result.lateControl;
|
|
115
|
+
if (lateControl?.type === "redirect")
|
|
116
|
+
return createRscRedirectResponse(lateControl.location, lateControl.method, lateControl.status);
|
|
117
|
+
const body = new Uint8Array(byteLength);
|
|
118
|
+
let offset = 0;
|
|
119
|
+
for (const chunk of chunks) {
|
|
120
|
+
body.set(chunk, offset);
|
|
121
|
+
offset += chunk.byteLength;
|
|
122
|
+
}
|
|
123
|
+
return createRscStreamResponse(body, result.status ?? 200);
|
|
124
|
+
}
|
|
125
|
+
|
|
60
126
|
export function normalizeRscTargetUrlForHostBasePath(
|
|
61
127
|
targetUrl: URL,
|
|
62
128
|
options: {
|
|
@@ -288,13 +354,14 @@ export class WebRouter {
|
|
|
288
354
|
});
|
|
289
355
|
const result = await this.#rsc.renderWithMeta(rscReq, {
|
|
290
356
|
clientManifest: manifest.clientManifest,
|
|
357
|
+
signal: req.signal,
|
|
291
358
|
});
|
|
292
359
|
if (result.type === "redirect")
|
|
293
360
|
return createRscRedirectResponse(result.location, result.method, result.status);
|
|
294
361
|
if (result.type === "not-found") return WebRouter.#rscNotFoundResponse();
|
|
295
362
|
if (result.status && result.status >= 500)
|
|
296
363
|
return this.#renderRscErrorResponse("__rsc", "Internal Server Error");
|
|
297
|
-
return
|
|
364
|
+
return createRscNavigationStreamResponse(result);
|
|
298
365
|
} catch (err) {
|
|
299
366
|
return this.#renderRscErrorResponse("__rsc", err);
|
|
300
367
|
}
|
|
@@ -389,6 +456,7 @@ export class WebRouter {
|
|
|
389
456
|
}
|
|
390
457
|
const rscResult = await this.#rsc.renderWithMeta(req, {
|
|
391
458
|
clientManifest: manifest.clientManifest,
|
|
459
|
+
signal: req.signal,
|
|
392
460
|
});
|
|
393
461
|
if (rscResult.type === "redirect")
|
|
394
462
|
return Response.redirect(new URL(rscResult.location, url.origin), rscResult.status);
|
|
@@ -402,20 +470,33 @@ export class WebRouter {
|
|
|
402
470
|
extraBootstrapInline: !this.#prodMode ? HMR_CLIENT_SCRIPT : undefined,
|
|
403
471
|
importmap: this.#artifact.vendorMap,
|
|
404
472
|
theme: themeCookieExists ? undefined : (rscResult.theme ?? "system"),
|
|
473
|
+
lateControl: rscResult.lateControl,
|
|
474
|
+
onCancel: (reason) => {
|
|
475
|
+
rscResult.cancel(reason);
|
|
476
|
+
},
|
|
405
477
|
});
|
|
406
478
|
const responseStatus = rscResult.status ?? 200;
|
|
407
479
|
const responseHeaders = WebRouter.#htmlResponseHeaders(responseStatus);
|
|
480
|
+
if (req.method === "HEAD") {
|
|
481
|
+
const headers = new Headers(responseHeaders);
|
|
482
|
+
if (htmlCacheKey && responseStatus === 200) headers.set("X-Akan-Cache", "MISS");
|
|
483
|
+
cancelStreamForHeadResponse(htmlStream, new Error("HEAD response does not consume body"));
|
|
484
|
+
return new Response(null, { status: responseStatus, headers });
|
|
485
|
+
}
|
|
408
486
|
if (htmlCacheKey && responseStatus === 200) {
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
return new Response(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
487
|
+
const headers = new Headers(responseHeaders);
|
|
488
|
+
headers.set("X-Akan-Cache", "MISS");
|
|
489
|
+
return new Response(
|
|
490
|
+
cacheHtmlWhileStreaming(htmlStream, (html) => {
|
|
491
|
+
this.#setCachedHtml(htmlCacheKey, html);
|
|
492
|
+
}),
|
|
493
|
+
{
|
|
494
|
+
status: responseStatus,
|
|
495
|
+
headers,
|
|
415
496
|
},
|
|
416
|
-
|
|
497
|
+
);
|
|
417
498
|
}
|
|
418
|
-
return new Response(
|
|
499
|
+
return new Response(htmlStream, {
|
|
419
500
|
status: responseStatus,
|
|
420
501
|
headers: responseHeaders,
|
|
421
502
|
});
|
package/service/ipcTypes.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface AkanMetricsReport {
|
|
|
57
57
|
rscWorkerLastRecycleReason?: string;
|
|
58
58
|
rscPendingRenderCount?: number;
|
|
59
59
|
rscQueuedSendCount?: number;
|
|
60
|
+
rscHostPendingChunkOverflowCount?: number;
|
|
60
61
|
rscRenderCount?: number;
|
|
61
62
|
rscInFlightRenderCount?: number;
|
|
62
63
|
rscLastRenderedPath?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], string, never, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
1
|
+
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], string, never, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { BaseEndpoint } from "akanjs/signal";
|
|
2
2
|
export declare const dictionary: {
|
|
3
|
-
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
3
|
+
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, never>;
|
|
4
4
|
};
|
|
5
|
-
export declare const Err: import("./trans.d.ts").ErrConstructor<never>, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
6
|
-
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
7
|
-
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
8
|
-
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
9
|
-
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
10
|
-
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
11
|
-
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "
|
|
5
|
+
export declare const Err: import("./trans.d.ts").ErrConstructor<never>, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, data?: import("./trans.d.ts").TranslationData) => string, msg: {
|
|
6
|
+
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
7
|
+
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
8
|
+
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
9
|
+
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
10
|
+
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
11
|
+
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "save" | "password" | "remove" | "ok" | "connecting" | "failed" | "cancel" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "unauthorized" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "actions" | "new">, __Error_Key__: never;
|
|
@@ -2,7 +2,20 @@ import { type AkanI18nConfig } from "akanjs/common";
|
|
|
2
2
|
import type { AkanTheme } from "akanjs/fetch";
|
|
3
3
|
import type { AkanMetricsReport } from "akanjs/service";
|
|
4
4
|
import type { ClientManifest } from "./artifact.d.ts";
|
|
5
|
+
import type { SsrLateRedirect } from "./ssrTypes.d.ts";
|
|
5
6
|
import type { BaseBuildArtifact, CssAsset } from "./types.d.ts";
|
|
7
|
+
export interface RscPending {
|
|
8
|
+
onChunk: (data: Uint8Array) => void;
|
|
9
|
+
onEnd: () => void;
|
|
10
|
+
onError: (message: string) => void;
|
|
11
|
+
onMeta?: (meta: {
|
|
12
|
+
theme?: AkanTheme;
|
|
13
|
+
status?: number;
|
|
14
|
+
}) => void;
|
|
15
|
+
onRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
|
|
16
|
+
onLateRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
|
|
17
|
+
onNotFound?: () => void;
|
|
18
|
+
}
|
|
6
19
|
export type RscRedirectMethod = "replace" | "push";
|
|
7
20
|
export type RscRedirectStatus = 303 | 307 | 308;
|
|
8
21
|
export type RscRenderResult = {
|
|
@@ -10,6 +23,8 @@ export type RscRenderResult = {
|
|
|
10
23
|
stream: ReadableStream<Uint8Array>;
|
|
11
24
|
theme?: AkanTheme;
|
|
12
25
|
status?: number;
|
|
26
|
+
lateControl: Promise<SsrLateRedirect | null>;
|
|
27
|
+
cancel: (reason?: unknown) => void;
|
|
13
28
|
} | {
|
|
14
29
|
type: "redirect";
|
|
15
30
|
location: string;
|
|
@@ -18,6 +33,19 @@ export type RscRenderResult = {
|
|
|
18
33
|
} | {
|
|
19
34
|
type: "not-found";
|
|
20
35
|
};
|
|
36
|
+
export declare function getRscHostMaxPendingChunks(value?: string | undefined): number;
|
|
37
|
+
export declare function nextRscHostPendingChunkCount(currentPendingChunks: number, desiredSize: number | null): number;
|
|
38
|
+
export declare function isRscHostPendingChunkOverflow(pendingChunks: number, maxPendingChunks: number): boolean;
|
|
39
|
+
export declare function createIdempotentRscRenderCancel(onCancel: (reason?: unknown) => void): (reason?: unknown) => void;
|
|
40
|
+
export declare function createRscHostRenderStream(input: {
|
|
41
|
+
setPending: (pending: RscPending) => void;
|
|
42
|
+
deletePending: () => void;
|
|
43
|
+
sendRenderOrQueue: () => void;
|
|
44
|
+
cancelRender: (reason?: unknown) => void;
|
|
45
|
+
maxPendingChunks?: number;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
onPendingChunkOverflow?: () => void;
|
|
48
|
+
}): Promise<RscRenderResult>;
|
|
21
49
|
export interface RscWorkerReloadInput {
|
|
22
50
|
clientManifest: ClientManifest;
|
|
23
51
|
cssAssets?: Record<string, CssAsset>;
|
|
@@ -64,6 +92,7 @@ export declare class RscWorker {
|
|
|
64
92
|
render(req: Request): ReadableStream<Uint8Array>;
|
|
65
93
|
renderWithMeta(req: Request, options?: {
|
|
66
94
|
clientManifest?: ClientManifest;
|
|
95
|
+
signal?: AbortSignal;
|
|
67
96
|
}): Promise<RscRenderResult>;
|
|
68
97
|
kill(): void;
|
|
69
98
|
getMetrics(): AkanMetricsReport;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type CachedRscReplayMessage = {
|
|
2
|
+
type: "meta";
|
|
3
|
+
requestId: string;
|
|
4
|
+
theme?: string;
|
|
5
|
+
status?: number;
|
|
6
|
+
} | {
|
|
7
|
+
type: "chunk";
|
|
8
|
+
requestId: string;
|
|
9
|
+
data: Uint8Array;
|
|
10
|
+
} | {
|
|
11
|
+
type: "end";
|
|
12
|
+
requestId: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function replayCachedRscResult(input: {
|
|
15
|
+
requestId: string;
|
|
16
|
+
chunks: readonly Uint8Array[];
|
|
17
|
+
theme?: string;
|
|
18
|
+
status?: number;
|
|
19
|
+
send: (message: CachedRscReplayMessage) => void;
|
|
20
|
+
isCancelled: () => boolean;
|
|
21
|
+
yieldEveryChunks?: number;
|
|
22
|
+
yieldToHost?: () => Promise<void>;
|
|
23
|
+
}): Promise<boolean>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SsrChunkRegistryStats, SsrFromRscInput } from "./ssrTypes.d.ts";
|
|
1
|
+
import type { SsrChunkRegistryStats, SsrFromRscInput, SsrLateRedirect } from "./ssrTypes.d.ts";
|
|
2
2
|
export declare class SsrChunkRegistry<T> {
|
|
3
3
|
#private;
|
|
4
4
|
readonly maxEntries: number;
|
|
@@ -12,6 +12,23 @@ export type InlineRscChunk = readonly [1, string] | readonly [3, string];
|
|
|
12
12
|
export declare function encodeInlineRscChunk(chunk: Uint8Array): InlineRscChunk;
|
|
13
13
|
export declare function htmlEscapeJsonString(value: string): string;
|
|
14
14
|
export declare function createInlineRscScript(chunk: Uint8Array): string;
|
|
15
|
+
export declare function createSoftRedirectScript(redirect: SsrLateRedirect): string;
|
|
16
|
+
export declare function sanitizeFlightForClientStream(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
|
|
17
|
+
export declare class ExpectedLateRedirectStderrSuppressor {
|
|
18
|
+
#private;
|
|
19
|
+
private constructor();
|
|
20
|
+
static start(lateControl?: Promise<SsrLateRedirect | null>): ExpectedLateRedirectStderrSuppressor | null;
|
|
21
|
+
stop(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare function interleaveRscScriptsWithHtml(htmlStream: ReadableStream<Uint8Array>, rscClientStream: ReadableStream<Uint8Array>, options?: {
|
|
24
|
+
bootstrapModuleScripts?: string;
|
|
25
|
+
lateControl?: Promise<SsrLateRedirect | null>;
|
|
26
|
+
maxPendingRscScripts?: number;
|
|
27
|
+
onPendingRscScriptsSize?: (size: number) => void;
|
|
28
|
+
onComplete?: () => void;
|
|
29
|
+
onCancel?: (reason?: unknown) => void;
|
|
30
|
+
request?: Request;
|
|
31
|
+
}): ReadableStream<Uint8Array>;
|
|
15
32
|
export declare class SsrFromRscRenderer {
|
|
16
33
|
#private;
|
|
17
34
|
static getChunkRegistryStats(): SsrChunkRegistryStats;
|
|
@@ -18,6 +18,12 @@ export interface SsrChunkRegistryStats {
|
|
|
18
18
|
ssrChunkCacheHitCount: number;
|
|
19
19
|
ssrChunkEvictionCount: number;
|
|
20
20
|
}
|
|
21
|
+
export interface SsrLateRedirect {
|
|
22
|
+
type: "redirect";
|
|
23
|
+
location: string;
|
|
24
|
+
method: "replace" | "push";
|
|
25
|
+
status: 303 | 307 | 308;
|
|
26
|
+
}
|
|
21
27
|
export interface SsrFromRscInput {
|
|
22
28
|
request?: Request;
|
|
23
29
|
rscStream: ReadableStream<Uint8Array>;
|
|
@@ -44,4 +50,6 @@ export interface SsrFromRscInput {
|
|
|
44
50
|
importmap?: Record<string, string>;
|
|
45
51
|
theme?: AkanTheme;
|
|
46
52
|
injectThemeInitScript?: boolean;
|
|
53
|
+
lateControl?: Promise<SsrLateRedirect | null>;
|
|
54
|
+
onCancel?: (reason?: unknown) => void;
|
|
47
55
|
}
|
|
@@ -2,10 +2,15 @@ import { type AkanI18nConfig } from "akanjs/common";
|
|
|
2
2
|
import type { AkanMetricsReport } from "akanjs/service";
|
|
3
3
|
import { type BuilderRpc, type RouteSeedIndex } from "./artifact.d.ts";
|
|
4
4
|
import type { HmrWsData, HmrWsHub } from "./hmr/wsHub.d.ts";
|
|
5
|
-
import { type RscRedirectMethod, type RscRedirectStatus, RscWorker } from "./rscWorkerHost.d.ts";
|
|
5
|
+
import { type RscRedirectMethod, type RscRedirectStatus, type RscRenderResult, RscWorker } from "./rscWorkerHost.d.ts";
|
|
6
6
|
import type { BaseBuildArtifact, HttpRoutes, RenderState } from "./types.d.ts";
|
|
7
7
|
export declare function createRscRedirectResponse(location: string, method: RscRedirectMethod, status?: RscRedirectStatus): Response;
|
|
8
8
|
export declare function createRscStreamResponse(stream: BodyInit, status?: number): Response;
|
|
9
|
+
export declare function cacheHtmlWhileStreaming(stream: ReadableStream<Uint8Array>, onComplete: (html: string) => void): ReadableStream<Uint8Array>;
|
|
10
|
+
export declare function cancelStreamForHeadResponse(stream: ReadableStream<Uint8Array>, reason: unknown): void;
|
|
11
|
+
export declare function createRscNavigationStreamResponse(result: Extract<RscRenderResult, {
|
|
12
|
+
type: "stream";
|
|
13
|
+
}>): Promise<Response>;
|
|
9
14
|
export declare function normalizeRscTargetUrlForHostBasePath(targetUrl: URL, options: {
|
|
10
15
|
basePath: string | null;
|
|
11
16
|
basePaths?: readonly string[];
|
|
@@ -61,6 +61,7 @@ export interface AkanMetricsReport {
|
|
|
61
61
|
rscWorkerLastRecycleReason?: string;
|
|
62
62
|
rscPendingRenderCount?: number;
|
|
63
63
|
rscQueuedSendCount?: number;
|
|
64
|
+
rscHostPendingChunkOverflowCount?: number;
|
|
64
65
|
rscRenderCount?: number;
|
|
65
66
|
rscInFlightRenderCount?: number;
|
|
66
67
|
rscLastRenderedPath?: string;
|
package/ui/Link/SsrLink.tsx
CHANGED
|
@@ -61,8 +61,6 @@ export default function SsrLink({
|
|
|
61
61
|
if (!router.isInitialized || !rscNavigationReady) return;
|
|
62
62
|
event.preventDefault();
|
|
63
63
|
try {
|
|
64
|
-
Logger.log(`pathChange-start:${requestHref}`);
|
|
65
|
-
window.parent.postMessage({ type: "pathChange", href: requestHref }, "*");
|
|
66
64
|
if (replace) router.replace(href, { scrollToTop });
|
|
67
65
|
else router.push(href, { scrollToTop });
|
|
68
66
|
} catch (error) {
|