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.
@@ -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 createRscStreamResponse(result.stream, result.status ?? 200);
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 html = await new Response(htmlStream).text();
410
- this.#setCachedHtml(htmlCacheKey, html);
411
- return new Response(html, {
412
- headers: {
413
- "Content-Type": "text/html; charset=utf-8",
414
- "X-Akan-Cache": "MISS",
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(req.method === "HEAD" ? null : htmlStream, {
499
+ return new Response(htmlStream, {
419
500
  status: responseStatus,
420
501
  headers: responseHeaders,
421
502
  });
@@ -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" | "cancel" | "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
+ 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" | "cancel" | "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>;
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" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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" | "success" | "edit" | "view" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "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;
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;
@@ -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) {