akanjs 2.2.11 → 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/CHANGELOG.md +7 -0
- package/client/cookie.ts +3 -13
- package/client/router.ts +11 -3
- package/fetch/requestStorage.ts +106 -39
- package/package.json +1 -1
- package/server/akanApp.ts +55 -0
- package/server/hmr/clientScript.ts +2 -8
- package/server/rscClient.tsx +17 -7
- package/server/rscHttp.ts +7 -0
- package/server/rscWorker.tsx +183 -46
- package/server/rscWorkerHost.ts +242 -70
- package/server/rscWorkerReplay.ts +35 -0
- package/server/ssrFromRscRenderer.tsx +562 -81
- package/server/ssrTypes.ts +10 -0
- package/server/webRouter.ts +128 -28
- package/service/ipcTypes.ts +2 -0
- package/types/client/router.d.ts +8 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/fetch/requestStorage.d.ts +31 -6
- package/types/server/hmr/clientScript.d.ts +1 -1
- package/types/server/rscClient.d.ts +3 -2
- package/types/server/rscHttp.d.ts +2 -0
- package/types/server/rscWorkerHost.d.ts +31 -0
- package/types/server/rscWorkerReplay.d.ts +23 -0
- package/types/server/ssrFromRscRenderer.d.ts +31 -1
- package/types/server/ssrTypes.d.ts +9 -0
- package/types/server/webRouter.d.ts +8 -1
- package/types/service/ipcTypes.d.ts +2 -0
- package/ui/Link/SsrLink.tsx +0 -2
package/server/ssrTypes.ts
CHANGED
|
@@ -16,6 +16,14 @@ export interface SsrChunkRegistryStats {
|
|
|
16
16
|
ssrChunkRegistrySize: number;
|
|
17
17
|
ssrChunkLoadCount: number;
|
|
18
18
|
ssrChunkCacheHitCount: number;
|
|
19
|
+
ssrChunkEvictionCount: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SsrLateRedirect {
|
|
23
|
+
type: "redirect";
|
|
24
|
+
location: string;
|
|
25
|
+
method: "replace" | "push";
|
|
26
|
+
status: 303 | 307 | 308;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
export interface SsrFromRscInput {
|
|
@@ -44,4 +52,6 @@ export interface SsrFromRscInput {
|
|
|
44
52
|
importmap?: Record<string, string>;
|
|
45
53
|
theme?: AkanTheme;
|
|
46
54
|
injectThemeInitScript?: boolean;
|
|
55
|
+
lateControl?: Promise<SsrLateRedirect | null>;
|
|
56
|
+
onCancel?: (reason?: unknown) => void;
|
|
47
57
|
}
|
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 { 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";
|
|
@@ -30,6 +30,99 @@ import type { BaseBuildArtifact, HttpRoutes, RenderState } from "./types";
|
|
|
30
30
|
|
|
31
31
|
const RESERVED_BASE_PATHS = new Set(["admin"]);
|
|
32
32
|
|
|
33
|
+
export function createRscRedirectResponse(
|
|
34
|
+
location: string,
|
|
35
|
+
method: RscRedirectMethod,
|
|
36
|
+
status: RscRedirectStatus = 307,
|
|
37
|
+
): Response {
|
|
38
|
+
return new Response(JSON.stringify({ type: "redirect", location, method, status }), {
|
|
39
|
+
status: 200,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
42
|
+
"Cache-Control": "no-store",
|
|
43
|
+
"X-Akan-Redirect": location,
|
|
44
|
+
"X-Akan-Redirect-Method": method,
|
|
45
|
+
"X-Akan-Redirect-Status": String(status),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createRscStreamResponse(stream: BodyInit, status = 200): Response {
|
|
51
|
+
return new Response(stream, {
|
|
52
|
+
status,
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "text/x-component; charset=utf-8",
|
|
55
|
+
"Cache-Control": "no-store",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
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
|
+
|
|
33
126
|
export function normalizeRscTargetUrlForHostBasePath(
|
|
34
127
|
targetUrl: URL,
|
|
35
128
|
options: {
|
|
@@ -261,18 +354,14 @@ export class WebRouter {
|
|
|
261
354
|
});
|
|
262
355
|
const result = await this.#rsc.renderWithMeta(rscReq, {
|
|
263
356
|
clientManifest: manifest.clientManifest,
|
|
357
|
+
signal: req.signal,
|
|
264
358
|
});
|
|
265
|
-
if (result.type === "redirect")
|
|
266
|
-
|
|
267
|
-
if (result.
|
|
359
|
+
if (result.type === "redirect")
|
|
360
|
+
return createRscRedirectResponse(result.location, result.method, result.status);
|
|
361
|
+
if (result.type === "not-found") return WebRouter.#rscNotFoundResponse();
|
|
268
362
|
if (result.status && result.status >= 500)
|
|
269
363
|
return this.#renderRscErrorResponse("__rsc", "Internal Server Error");
|
|
270
|
-
return
|
|
271
|
-
headers: {
|
|
272
|
-
"Content-Type": "text/x-component; charset=utf-8",
|
|
273
|
-
"Cache-Control": "no-store",
|
|
274
|
-
},
|
|
275
|
-
});
|
|
364
|
+
return createRscNavigationStreamResponse(result);
|
|
276
365
|
} catch (err) {
|
|
277
366
|
return this.#renderRscErrorResponse("__rsc", err);
|
|
278
367
|
}
|
|
@@ -367,8 +456,10 @@ export class WebRouter {
|
|
|
367
456
|
}
|
|
368
457
|
const rscResult = await this.#rsc.renderWithMeta(req, {
|
|
369
458
|
clientManifest: manifest.clientManifest,
|
|
459
|
+
signal: req.signal,
|
|
370
460
|
});
|
|
371
|
-
if (rscResult.type === "redirect")
|
|
461
|
+
if (rscResult.type === "redirect")
|
|
462
|
+
return Response.redirect(new URL(rscResult.location, url.origin), rscResult.status);
|
|
372
463
|
if (rscResult.type === "not-found") return this.#renderNotFoundResponse(req, url);
|
|
373
464
|
const themeCookieExists = WebRouter.#hasCookie(req, "theme");
|
|
374
465
|
const htmlStream = await new SsrFromRscRenderer().render({
|
|
@@ -379,20 +470,33 @@ export class WebRouter {
|
|
|
379
470
|
extraBootstrapInline: !this.#prodMode ? HMR_CLIENT_SCRIPT : undefined,
|
|
380
471
|
importmap: this.#artifact.vendorMap,
|
|
381
472
|
theme: themeCookieExists ? undefined : (rscResult.theme ?? "system"),
|
|
473
|
+
lateControl: rscResult.lateControl,
|
|
474
|
+
onCancel: (reason) => {
|
|
475
|
+
rscResult.cancel(reason);
|
|
476
|
+
},
|
|
382
477
|
});
|
|
383
478
|
const responseStatus = rscResult.status ?? 200;
|
|
384
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
|
+
}
|
|
385
486
|
if (htmlCacheKey && responseStatus === 200) {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
return new Response(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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,
|
|
392
496
|
},
|
|
393
|
-
|
|
497
|
+
);
|
|
394
498
|
}
|
|
395
|
-
return new Response(
|
|
499
|
+
return new Response(htmlStream, {
|
|
396
500
|
status: responseStatus,
|
|
397
501
|
headers: responseHeaders,
|
|
398
502
|
});
|
|
@@ -417,6 +521,7 @@ export class WebRouter {
|
|
|
417
521
|
ssrChunkRegistrySize: ssrStats.ssrChunkRegistrySize,
|
|
418
522
|
ssrChunkLoadCount: ssrStats.ssrChunkLoadCount,
|
|
419
523
|
ssrChunkCacheHitCount: ssrStats.ssrChunkCacheHitCount,
|
|
524
|
+
ssrChunkEvictionCount: ssrStats.ssrChunkEvictionCount,
|
|
420
525
|
httpFullSsrCount: this.#requestStats.fullSsr,
|
|
421
526
|
httpRscNavigationCount: this.#requestStats.rscNavigation,
|
|
422
527
|
httpStaticAssetCount: this.#requestStats.staticAsset,
|
|
@@ -650,15 +755,10 @@ export class WebRouter {
|
|
|
650
755
|
if (!last || last.index === undefined) return `${html}\n${snippet}`;
|
|
651
756
|
return `${html.slice(0, last.index)}${snippet}\n${html.slice(last.index)}`;
|
|
652
757
|
}
|
|
653
|
-
static #
|
|
654
|
-
return new Response(
|
|
655
|
-
status:
|
|
656
|
-
headers: {
|
|
657
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
658
|
-
"Cache-Control": "no-store",
|
|
659
|
-
"X-Akan-Redirect": location,
|
|
660
|
-
"X-Akan-Redirect-Method": method,
|
|
661
|
-
},
|
|
758
|
+
static #rscNotFoundResponse(): Response {
|
|
759
|
+
return new Response("Not Found", {
|
|
760
|
+
status: 404,
|
|
761
|
+
headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" },
|
|
662
762
|
});
|
|
663
763
|
}
|
|
664
764
|
#getProductionRouteCache() {
|
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;
|
|
@@ -85,6 +86,7 @@ export interface AkanMetricsReport {
|
|
|
85
86
|
ssrChunkRegistrySize?: number;
|
|
86
87
|
ssrChunkLoadCount?: number;
|
|
87
88
|
ssrChunkCacheHitCount?: number;
|
|
89
|
+
ssrChunkEvictionCount?: number;
|
|
88
90
|
httpFullSsrCount?: number;
|
|
89
91
|
httpRscNavigationCount?: number;
|
|
90
92
|
httpStaticAssetCount?: number;
|
package/types/client/router.d.ts
CHANGED
|
@@ -25,11 +25,17 @@ interface CSRClientRouterOption extends RouterOptions {
|
|
|
25
25
|
router: RouterInstance;
|
|
26
26
|
}
|
|
27
27
|
export type RedirectMethod = "replace" | "push";
|
|
28
|
+
export type RedirectStatus = 303 | 307 | 308;
|
|
29
|
+
export interface RedirectOptions {
|
|
30
|
+
method?: RedirectMethod;
|
|
31
|
+
status?: RedirectStatus;
|
|
32
|
+
}
|
|
28
33
|
export declare class AkanRedirectError extends Error {
|
|
29
34
|
readonly location: string;
|
|
30
35
|
readonly method: RedirectMethod;
|
|
36
|
+
readonly status: RedirectStatus;
|
|
31
37
|
readonly digest = "AKAN_REDIRECT";
|
|
32
|
-
constructor(location: string, method?: RedirectMethod);
|
|
38
|
+
constructor(location: string, method?: RedirectMethod, status?: RedirectStatus);
|
|
33
39
|
}
|
|
34
40
|
export declare class AkanNotFoundError extends Error {
|
|
35
41
|
readonly digest = "AKAN_NOT_FOUND";
|
|
@@ -53,7 +59,7 @@ declare class Router {
|
|
|
53
59
|
replace(href: string, routeOptions?: RouteOptions): never;
|
|
54
60
|
back(routeOptions?: RouteOptions): never;
|
|
55
61
|
refresh(): never;
|
|
56
|
-
redirect(href: string): never;
|
|
62
|
+
redirect(href: string, options?: RedirectOptions): never;
|
|
57
63
|
notFound(): never;
|
|
58
64
|
setLang(lang: string): never;
|
|
59
65
|
getPath(pathname?: string): 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;
|
|
@@ -1,21 +1,46 @@
|
|
|
1
|
+
export type AkanTheme = "css" | "system" | (string & {});
|
|
2
|
+
export interface AkanRequestPolicy {
|
|
3
|
+
routeId?: string;
|
|
4
|
+
rscCache?: "public" | false;
|
|
5
|
+
rscCacheTtl?: number;
|
|
6
|
+
cacheable?: boolean;
|
|
7
|
+
revalidate?: number | false;
|
|
8
|
+
tags: Set<string>;
|
|
9
|
+
}
|
|
10
|
+
export interface AkanDynamicUsage {
|
|
11
|
+
headers: boolean;
|
|
12
|
+
cookies: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AkanRequestStore {
|
|
15
|
+
request: Request;
|
|
16
|
+
theme?: AkanTheme;
|
|
17
|
+
queryCache: Map<string, Promise<unknown>>;
|
|
18
|
+
policy: AkanRequestPolicy;
|
|
19
|
+
dynamicUsage: AkanDynamicUsage;
|
|
20
|
+
}
|
|
1
21
|
export interface RequestStorage {
|
|
2
|
-
run<T>(store: Request, callback: () => T): T;
|
|
3
|
-
getStore():
|
|
22
|
+
run<T>(store: Request | AkanRequestStore, callback: () => T): T;
|
|
23
|
+
getStore(): AkanRequestStore | undefined;
|
|
4
24
|
}
|
|
5
|
-
export type AkanTheme = "css" | "system" | (string & {});
|
|
6
25
|
declare global {
|
|
7
26
|
var __AKAN_REQUEST_STORAGE__: RequestStorage | undefined;
|
|
8
|
-
var
|
|
9
|
-
var __AKAN_REQUEST_QUERY_CACHE__: WeakMap<Request, Map<string, Promise<unknown>>> | undefined;
|
|
10
|
-
var __AKAN_REQUEST_FALLBACK_STACK__: Request[] | undefined;
|
|
27
|
+
var __AKAN_REQUEST_FALLBACK_STACK__: AkanRequestStore[] | undefined;
|
|
11
28
|
}
|
|
12
29
|
export declare const requestStorage: RequestStorage | null;
|
|
30
|
+
export declare function createRequestStore(request: Request, policy?: Partial<Omit<AkanRequestPolicy, "tags">>): AkanRequestStore;
|
|
13
31
|
/** Stores theme preference on the active request when server rendering. */
|
|
14
32
|
export declare function setRequestTheme(theme: AkanTheme | undefined): void;
|
|
15
33
|
export declare function getRequestTheme(): AkanTheme | undefined;
|
|
16
34
|
export declare function pushRequestFallback(req: Request): () => void;
|
|
35
|
+
/** Returns the active server request store from AsyncLocalStorage or the fallback stack. */
|
|
36
|
+
export declare function getRequestStore(): AkanRequestStore | undefined;
|
|
17
37
|
/** Returns the active server request from AsyncLocalStorage or the fallback stack. */
|
|
18
38
|
export declare function getRequest(): Request | undefined;
|
|
39
|
+
export declare function getRequestPolicy(): AkanRequestPolicy | undefined;
|
|
40
|
+
export declare function updateRequestPolicy(patch: Partial<Omit<AkanRequestPolicy, "tags">> & {
|
|
41
|
+
tags?: Iterable<string>;
|
|
42
|
+
}): AkanRequestPolicy | undefined;
|
|
43
|
+
export declare function getRequestDynamicUsage(): AkanDynamicUsage | undefined;
|
|
19
44
|
/** Deduplicates a promise-producing query within the active request. */
|
|
20
45
|
export declare function memoizeRequestQuery<T>(key: string, factory: () => Promise<T>): Promise<T>;
|
|
21
46
|
/** Returns current request headers as a Map, or an empty Map outside a request. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const HMR_CLIENT_SCRIPT = "(function(){\n if (self.__AKAN_HMR_INSTALLED__) return;\n self.__AKAN_HMR_INSTALLED__ = true;\n var proto = location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n var url = proto + \"//\" + location.host + \"/_akan/hmr\";\n var attempts = 0;\n var socket = null;\n var lastBuildId = null;\n var refreshRuntimePromise = null;\n var refreshQueue = Promise.resolve();\n var overlayEl = null;\n var overlayLabelEl = null;\n var overlayStyleEl = null;\n var overlayTimer = null;\n var overlayHideTimer = null;\n var overlayNextToken = 1;\n var overlayJobs = {};\n self.__AKAN_HMR_PHASE__ = null;\n\n // Bun's React Fast Refresh transform can emit top-level calls to these globals\n // even when we fall back to full reload instead of applying React Refresh.\n self.$RefreshReg$ = self.$RefreshReg$ || function(){};\n self.$RefreshSig$ = self.$RefreshSig$ || function(){ return function(type){ return type; }; };\n\n function connect(){\n try { socket = new WebSocket(url); }\n catch(e){ console.error(\"[akan-hmr] ws init failed\", e); schedule(); return; }\n socket.addEventListener(\"open\", function(){ attempts = 0; });\n socket.addEventListener(\"message\", function(ev){\n var msg;\n try { msg = JSON.parse(ev.data); } catch (e){ return; }\n if (!msg || typeof msg.type !== \"string\") return;\n if (msg.type === \"hello\") {\n if (lastBuildId !== null && msg.buildId !== lastBuildId) {\n location.reload();\n return;\n }\n lastBuildId = msg.buildId;\n return;\n }\n if (msg.type === \"reload\") {\n beginHmrOverlay(\"Reloading...\", true);\n try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}\n setTimeout(function(){ location.reload(); }, 30);\n return;\n }\n if (msg.type === \"rsc-refresh\") {\n reloadForHmr(msg);\n return;\n }\n if (msg.type === \"client-refresh\") {\n reloadForHmr(msg);\n return;\n }\n if (msg.type === \"css-update\") {\n var cssUrl = selectCssUrl(msg.cssAssets);\n if (cssUrl) swapCss(cssUrl);\n else {\n beginHmrOverlay(\"Reloading...\", true);\n location.reload();\n }\n return;\n }\n if (msg.type === \"error\") { console.error(\"[akan-hmr]\", msg.message); return; }\n });\n socket.addEventListener(\"close\", function(){ socket = null; schedule(); });\n socket.addEventListener(\"error\", function(){ try { socket && socket.close(); } catch(e){} });\n }\n\n function schedule(){\n attempts = Math.min(attempts + 1, 6);\n var delay = Math.min(30000, 250 * Math.pow(2, attempts - 1));\n setTimeout(connect, delay);\n }\n\n // goseoghyeon: CSR route registry keeps stale module refs, so JS/RSC HMR uses full reload for now.\n function reloadForHmr(msg){\n try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}\n if (msg && msg.buildId != null) lastBuildId = msg.buildId;\n location.reload();\n }\n\n function ensureOverlay(){\n if (overlayEl && overlayLabelEl) return overlayEl;\n if (!overlayStyleEl) {\n overlayStyleEl = document.createElement(\"style\");\n overlayStyleEl.textContent =\n \"@keyframes akan-hmr-spin{to{transform:rotate(360deg)}}\" +\n \".__akan_hmr_overlay{position:fixed;left:16px;bottom:16px;z-index:2147483647;display:flex;align-items:center;gap:9px;padding:10px 12px;border-radius:999px;background:rgba(17,24,39,.94);color:#fff;font:500 13px/1.2 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;box-shadow:0 10px 28px rgba(0,0,0,.28);pointer-events:none;opacity:0;transform:translateY(6px);transition:opacity .15s ease,transform .15s ease;backdrop-filter:blur(8px)}\" +\n \".__akan_hmr_overlay[data-show=true]{opacity:1;transform:translateY(0)}\" +\n \".__akan_hmr_spinner{width:14px;height:14px;border:2px solid rgba(255,255,255,.32);border-top-color:#fff;border-radius:999px;animation:akan-hmr-spin .75s linear infinite;flex:none}\" +\n \"@media (prefers-reduced-motion:reduce){.__akan_hmr_overlay{transition:none}.__akan_hmr_spinner{animation:none}}\";\n document.head.appendChild(overlayStyleEl);\n }\n overlayEl = document.createElement(\"div\");\n overlayEl.className = \"__akan_hmr_overlay\";\n overlayEl.setAttribute(\"role\", \"status\");\n overlayEl.setAttribute(\"aria-live\", \"polite\");\n overlayEl.innerHTML = '<span class=\"__akan_hmr_spinner\" aria-hidden=\"true\"></span><span data-akan-hmr-label>Updating...</span>';\n overlayLabelEl = overlayEl.querySelector(\"[data-akan-hmr-label]\");\n (document.body || document.documentElement).appendChild(overlayEl);\n return overlayEl;\n }\n\n function activeOverlayTokens(){\n return Object.keys(overlayJobs);\n }\n\n function latestOverlayLabel(){\n var keys = activeOverlayTokens();\n if (keys.length === 0) return \"Updating...\";\n return overlayJobs[keys[keys.length - 1]] || \"Updating...\";\n }\n\n function showOverlayNow(){\n overlayTimer = null;\n if (activeOverlayTokens().length === 0) return;\n var el = ensureOverlay();\n if (overlayHideTimer) {\n clearTimeout(overlayHideTimer);\n overlayHideTimer = null;\n }\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n requestAnimationFrame(function(){ el.setAttribute(\"data-show\", \"true\"); });\n }\n\n function beginHmrOverlay(label, immediate){\n var token = overlayNextToken++;\n overlayJobs[token] = label || \"Updating...\";\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n if (overlayHideTimer) {\n clearTimeout(overlayHideTimer);\n overlayHideTimer = null;\n }\n if (immediate) {\n if (overlayTimer) clearTimeout(overlayTimer);\n showOverlayNow();\n } else if (!overlayTimer && (!overlayEl || overlayEl.getAttribute(\"data-show\") !== \"true\")) {\n overlayTimer = setTimeout(showOverlayNow, 120);\n }\n return token;\n }\n\n function setHmrOverlayLabel(token, label){\n if (!overlayJobs[token]) return;\n overlayJobs[token] = label || \"Updating...\";\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n }\n\n function endHmrOverlay(token){\n delete overlayJobs[token];\n if (activeOverlayTokens().length > 0) {\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n return;\n }\n if (overlayTimer) {\n clearTimeout(overlayTimer);\n overlayTimer = null;\n }\n if (!overlayEl) return;\n overlayEl.setAttribute(\"data-show\", \"false\");\n if (overlayHideTimer) clearTimeout(overlayHideTimer);\n overlayHideTimer = setTimeout(function(){\n if (overlayEl && activeOverlayTokens().length === 0 && overlayEl.parentNode) {\n overlayEl.parentNode.removeChild(overlayEl);\n overlayEl = null;\n overlayLabelEl = null;\n }\n }, 180);\n }\n\n function refreshRsc(msg){\n var started = performance.now();\n var overlayToken = beginHmrOverlay(\"Refreshing page...\");\n try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}\n if (!self.__AKAN_RSC_REFRESH__) {\n console.warn(\"[akan-hmr] RSC refresh API unavailable, falling back to full reload\");\n setHmrOverlayLabel(overlayToken, \"Reloading...\");\n setTimeout(function(){ location.reload(); }, 30);\n return;\n }\n Promise.resolve(self.__AKAN_RSC_REFRESH__({ buildId: msg.buildId })).then(function(){\n lastBuildId = msg.buildId;\n endHmrOverlay(overlayToken);\n console.debug && console.debug(\"[akan-hmr] RSC refreshed\", {\n buildId: msg.buildId,\n generation: msg.generation,\n routeIds: msg.routeIds,\n changedFiles: msg.changedFiles && msg.changedFiles.length,\n durationMs: Math.round(performance.now() - started)\n });\n }, function(err){\n console.error(\"[akan-hmr] RSC refresh failed, falling back to full reload\", err);\n setHmrOverlayLabel(overlayToken, \"Update failed, reloading...\");\n setTimeout(function(){ location.reload(); }, 250);\n });\n }\n\n function ensureRefreshRuntime(){\n if (refreshRuntimePromise) return refreshRuntimePromise;\n refreshRuntimePromise = import(\"react-refresh/runtime\").then(function(mod){\n var runtime = mod.default || mod;\n if (!self.__AKAN_REACT_REFRESH_READY__) {\n runtime.injectIntoGlobalHook(self);\n self.$RefreshReg$ = function(type, id){ runtime.register(type, id); };\n self.$RefreshSig$ = runtime.createSignatureFunctionForTransform;\n self.__AKAN_REACT_REFRESH_READY__ = true;\n self.__AKAN_REACT_REFRESH_RUNTIME__ = runtime;\n }\n return runtime;\n });\n return refreshRuntimePromise;\n }\n\n function refreshClient(msg){\n refreshQueue = refreshQueue.then(function(){ return doRefreshClient(msg); }, function(){ return doRefreshClient(msg); });\n }\n\n function setHmrPhase(phase){\n self.__AKAN_HMR_PHASE__ = phase;\n }\n\n function doRefreshClient(msg){\n var started = performance.now();\n var metadataAt = started;\n var importAt = started;\n var refreshAt = started;\n var overlayToken = beginHmrOverlay(\"Updating...\");\n var fallbackToRsc = false;\n return ensureRefreshRuntime().then(function(runtime){\n setHmrOverlayLabel(overlayToken, \"Fetching update...\");\n var endpoint = new URL(\"/_akan/hmr/client-refresh\", location.origin);\n endpoint.searchParams.set(\"url\", location.href);\n if (msg.buildId != null) endpoint.searchParams.set(\"buildId\", String(msg.buildId));\n return fetch(endpoint, { credentials: \"same-origin\", cache: \"no-store\" })\n .then(function(res){\n if (!res.ok) throw new Error(\"client-refresh metadata failed \" + res.status + \" \" + res.statusText);\n return res.json();\n })\n .then(function(info){\n metadataAt = performance.now();\n var chunks = Array.isArray(info.chunks) ? info.chunks : [];\n if (chunks.length === 0) throw new Error(\"no client chunks returned\");\n setHmrPhase(\"refresh-import\");\n setHmrOverlayLabel(overlayToken, \"Importing update...\");\n return Promise.all(chunks.map(function(chunk){ return import(chunk); })).then(function(){\n importAt = performance.now();\n setHmrPhase(\"react-refresh\");\n setHmrOverlayLabel(overlayToken, \"Applying update...\");\n try {\n runtime.performReactRefresh();\n } finally {\n setHmrPhase(null);\n }\n refreshAt = performance.now();\n lastBuildId = msg.buildId;\n console.debug && console.debug(\"[akan-hmr] React Fast Refresh applied\", {\n buildId: msg.buildId,\n generation: msg.generation,\n chunks: chunks.length,\n routeIds: info.routeIds || msg.routeIds,\n changedFiles: msg.changedFiles && msg.changedFiles.length,\n metadataMs: Math.round(metadataAt - started),\n importMs: Math.round(importAt - metadataAt),\n refreshMs: Math.round(refreshAt - importAt),\n durationMs: Math.round(refreshAt - started)\n });\n endHmrOverlay(overlayToken);\n }, function(err){\n setHmrPhase(null);\n throw err;\n });\n });\n }).catch(function(err){\n console.warn(\"[akan-hmr] React Fast Refresh failed, falling back to RSC refresh\", err);\n fallbackToRsc = true;\n endHmrOverlay(overlayToken);\n refreshRsc(msg);\n }).finally(function(){\n if (!fallbackToRsc) endHmrOverlay(overlayToken);\n });\n }\n\n function swapCss(href){\n var overlayToken = beginHmrOverlay(\"Updating styles...\");\n var link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n link.setAttribute(\"data-akan-css\", \"pending\");\n link.addEventListener(\"load\", function(){\n var prev = document.querySelectorAll(\"link[data-akan-css=active]\");\n for (var i = 0; i < prev.length; i++) prev[i].parentNode && prev[i].parentNode.removeChild(prev[i]);\n link.setAttribute(\"data-akan-css\", \"active\");\n endHmrOverlay(overlayToken);\n });\n link.addEventListener(\"error\", function(){\n if (link.parentNode) link.parentNode.removeChild(link);\n endHmrOverlay(overlayToken);\n });\n document.head.appendChild(link);\n }\n\n function selectCssUrl(cssAssets){\n if (!cssAssets || typeof cssAssets !== \"object\") return null;\n var parts = location.pathname.split(\"/\").filter(Boolean);\n for (var i = 0; i < parts.length; i++) {\n var asset = cssAssets[parts[i]];\n if (asset && asset.cssUrl) return asset.cssUrl;\n }\n return cssAssets[\"\"] && cssAssets[\"\"].cssUrl ? cssAssets[\"\"].cssUrl : null;\n }\n\n connect();\n})();";
|
|
1
|
+
export declare const HMR_CLIENT_SCRIPT = "(function(){\n if (self.__AKAN_HMR_INSTALLED__) return;\n self.__AKAN_HMR_INSTALLED__ = true;\n var proto = location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n var url = proto + \"//\" + location.host + \"/_akan/hmr\";\n var attempts = 0;\n var socket = null;\n var lastBuildId = null;\n var refreshRuntimePromise = null;\n var refreshQueue = Promise.resolve();\n var overlayEl = null;\n var overlayLabelEl = null;\n var overlayStyleEl = null;\n var overlayTimer = null;\n var overlayHideTimer = null;\n var overlayNextToken = 1;\n var overlayJobs = {};\n self.__AKAN_HMR_PHASE__ = null;\n\n // Bun's React Fast Refresh transform can emit top-level calls to these globals\n // even when we fall back to full reload instead of applying React Refresh.\n self.$RefreshReg$ = self.$RefreshReg$ || function(){};\n self.$RefreshSig$ = self.$RefreshSig$ || function(){ return function(type){ return type; }; };\n\n function connect(){\n try { socket = new WebSocket(url); }\n catch(e){ console.error(\"[akan-hmr] ws init failed\", e); schedule(); return; }\n socket.addEventListener(\"open\", function(){ attempts = 0; });\n socket.addEventListener(\"message\", function(ev){\n var msg;\n try { msg = JSON.parse(ev.data); } catch (e){ return; }\n if (!msg || typeof msg.type !== \"string\") return;\n if (msg.type === \"hello\") {\n if (lastBuildId !== null && msg.buildId !== lastBuildId) {\n location.reload();\n return;\n }\n lastBuildId = msg.buildId;\n return;\n }\n if (msg.type === \"reload\") {\n beginHmrOverlay(\"Reloading...\", true);\n try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}\n setTimeout(function(){ location.reload(); }, 30);\n return;\n }\n if (msg.type === \"rsc-refresh\") {\n refreshRsc(msg);\n return;\n }\n if (msg.type === \"client-refresh\") {\n refreshClient(msg);\n return;\n }\n if (msg.type === \"css-update\") {\n var cssUrl = selectCssUrl(msg.cssAssets);\n if (cssUrl) swapCss(cssUrl);\n else {\n beginHmrOverlay(\"Reloading...\", true);\n location.reload();\n }\n return;\n }\n if (msg.type === \"error\") { console.error(\"[akan-hmr]\", msg.message); return; }\n });\n socket.addEventListener(\"close\", function(){ socket = null; schedule(); });\n socket.addEventListener(\"error\", function(){ try { socket && socket.close(); } catch(e){} });\n }\n\n function schedule(){\n attempts = Math.min(attempts + 1, 6);\n var delay = Math.min(30000, 250 * Math.pow(2, attempts - 1));\n setTimeout(connect, delay);\n }\n\n function ensureOverlay(){\n if (overlayEl && overlayLabelEl) return overlayEl;\n if (!overlayStyleEl) {\n overlayStyleEl = document.createElement(\"style\");\n overlayStyleEl.textContent =\n \"@keyframes akan-hmr-spin{to{transform:rotate(360deg)}}\" +\n \".__akan_hmr_overlay{position:fixed;left:16px;bottom:16px;z-index:2147483647;display:flex;align-items:center;gap:9px;padding:10px 12px;border-radius:999px;background:rgba(17,24,39,.94);color:#fff;font:500 13px/1.2 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;box-shadow:0 10px 28px rgba(0,0,0,.28);pointer-events:none;opacity:0;transform:translateY(6px);transition:opacity .15s ease,transform .15s ease;backdrop-filter:blur(8px)}\" +\n \".__akan_hmr_overlay[data-show=true]{opacity:1;transform:translateY(0)}\" +\n \".__akan_hmr_spinner{width:14px;height:14px;border:2px solid rgba(255,255,255,.32);border-top-color:#fff;border-radius:999px;animation:akan-hmr-spin .75s linear infinite;flex:none}\" +\n \"@media (prefers-reduced-motion:reduce){.__akan_hmr_overlay{transition:none}.__akan_hmr_spinner{animation:none}}\";\n document.head.appendChild(overlayStyleEl);\n }\n overlayEl = document.createElement(\"div\");\n overlayEl.className = \"__akan_hmr_overlay\";\n overlayEl.setAttribute(\"role\", \"status\");\n overlayEl.setAttribute(\"aria-live\", \"polite\");\n overlayEl.innerHTML = '<span class=\"__akan_hmr_spinner\" aria-hidden=\"true\"></span><span data-akan-hmr-label>Updating...</span>';\n overlayLabelEl = overlayEl.querySelector(\"[data-akan-hmr-label]\");\n (document.body || document.documentElement).appendChild(overlayEl);\n return overlayEl;\n }\n\n function activeOverlayTokens(){\n return Object.keys(overlayJobs);\n }\n\n function latestOverlayLabel(){\n var keys = activeOverlayTokens();\n if (keys.length === 0) return \"Updating...\";\n return overlayJobs[keys[keys.length - 1]] || \"Updating...\";\n }\n\n function showOverlayNow(){\n overlayTimer = null;\n if (activeOverlayTokens().length === 0) return;\n var el = ensureOverlay();\n if (overlayHideTimer) {\n clearTimeout(overlayHideTimer);\n overlayHideTimer = null;\n }\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n requestAnimationFrame(function(){ el.setAttribute(\"data-show\", \"true\"); });\n }\n\n function beginHmrOverlay(label, immediate){\n var token = overlayNextToken++;\n overlayJobs[token] = label || \"Updating...\";\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n if (overlayHideTimer) {\n clearTimeout(overlayHideTimer);\n overlayHideTimer = null;\n }\n if (immediate) {\n if (overlayTimer) clearTimeout(overlayTimer);\n showOverlayNow();\n } else if (!overlayTimer && (!overlayEl || overlayEl.getAttribute(\"data-show\") !== \"true\")) {\n overlayTimer = setTimeout(showOverlayNow, 120);\n }\n return token;\n }\n\n function setHmrOverlayLabel(token, label){\n if (!overlayJobs[token]) return;\n overlayJobs[token] = label || \"Updating...\";\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n }\n\n function endHmrOverlay(token){\n delete overlayJobs[token];\n if (activeOverlayTokens().length > 0) {\n if (overlayLabelEl) overlayLabelEl.textContent = latestOverlayLabel();\n return;\n }\n if (overlayTimer) {\n clearTimeout(overlayTimer);\n overlayTimer = null;\n }\n if (!overlayEl) return;\n overlayEl.setAttribute(\"data-show\", \"false\");\n if (overlayHideTimer) clearTimeout(overlayHideTimer);\n overlayHideTimer = setTimeout(function(){\n if (overlayEl && activeOverlayTokens().length === 0 && overlayEl.parentNode) {\n overlayEl.parentNode.removeChild(overlayEl);\n overlayEl = null;\n overlayLabelEl = null;\n }\n }, 180);\n }\n\n function refreshRsc(msg){\n var started = performance.now();\n var overlayToken = beginHmrOverlay(\"Refreshing page...\");\n try { self.__AKAN_RSC_CLEAR_CACHE__ && self.__AKAN_RSC_CLEAR_CACHE__(); } catch(e){}\n if (!self.__AKAN_RSC_REFRESH__) {\n console.warn(\"[akan-hmr] RSC refresh API unavailable, falling back to full reload\");\n setHmrOverlayLabel(overlayToken, \"Reloading...\");\n setTimeout(function(){ location.reload(); }, 30);\n return;\n }\n Promise.resolve(self.__AKAN_RSC_REFRESH__({ buildId: msg.buildId })).then(function(){\n lastBuildId = msg.buildId;\n endHmrOverlay(overlayToken);\n console.debug && console.debug(\"[akan-hmr] RSC refreshed\", {\n buildId: msg.buildId,\n generation: msg.generation,\n routeIds: msg.routeIds,\n changedFiles: msg.changedFiles && msg.changedFiles.length,\n durationMs: Math.round(performance.now() - started)\n });\n }, function(err){\n console.error(\"[akan-hmr] RSC refresh failed, falling back to full reload\", err);\n setHmrOverlayLabel(overlayToken, \"Update failed, reloading...\");\n setTimeout(function(){ location.reload(); }, 250);\n });\n }\n\n function ensureRefreshRuntime(){\n if (refreshRuntimePromise) return refreshRuntimePromise;\n refreshRuntimePromise = import(\"react-refresh/runtime\").then(function(mod){\n var runtime = mod.default || mod;\n if (!self.__AKAN_REACT_REFRESH_READY__) {\n runtime.injectIntoGlobalHook(self);\n self.$RefreshReg$ = function(type, id){ runtime.register(type, id); };\n self.$RefreshSig$ = runtime.createSignatureFunctionForTransform;\n self.__AKAN_REACT_REFRESH_READY__ = true;\n self.__AKAN_REACT_REFRESH_RUNTIME__ = runtime;\n }\n return runtime;\n });\n return refreshRuntimePromise;\n }\n\n function refreshClient(msg){\n refreshQueue = refreshQueue.then(function(){ return doRefreshClient(msg); }, function(){ return doRefreshClient(msg); });\n }\n\n function setHmrPhase(phase){\n self.__AKAN_HMR_PHASE__ = phase;\n }\n\n function doRefreshClient(msg){\n var started = performance.now();\n var metadataAt = started;\n var importAt = started;\n var refreshAt = started;\n var overlayToken = beginHmrOverlay(\"Updating...\");\n var fallbackToRsc = false;\n return ensureRefreshRuntime().then(function(runtime){\n setHmrOverlayLabel(overlayToken, \"Fetching update...\");\n var endpoint = new URL(\"/_akan/hmr/client-refresh\", location.origin);\n endpoint.searchParams.set(\"url\", location.href);\n if (msg.buildId != null) endpoint.searchParams.set(\"buildId\", String(msg.buildId));\n return fetch(endpoint, { credentials: \"same-origin\", cache: \"no-store\" })\n .then(function(res){\n if (!res.ok) throw new Error(\"client-refresh metadata failed \" + res.status + \" \" + res.statusText);\n return res.json();\n })\n .then(function(info){\n metadataAt = performance.now();\n var chunks = Array.isArray(info.chunks) ? info.chunks : [];\n if (chunks.length === 0) throw new Error(\"no client chunks returned\");\n setHmrPhase(\"refresh-import\");\n setHmrOverlayLabel(overlayToken, \"Importing update...\");\n return Promise.all(chunks.map(function(chunk){ return import(chunk); })).then(function(){\n importAt = performance.now();\n setHmrPhase(\"react-refresh\");\n setHmrOverlayLabel(overlayToken, \"Applying update...\");\n try {\n runtime.performReactRefresh();\n } finally {\n setHmrPhase(null);\n }\n refreshAt = performance.now();\n lastBuildId = msg.buildId;\n console.debug && console.debug(\"[akan-hmr] React Fast Refresh applied\", {\n buildId: msg.buildId,\n generation: msg.generation,\n chunks: chunks.length,\n routeIds: info.routeIds || msg.routeIds,\n changedFiles: msg.changedFiles && msg.changedFiles.length,\n metadataMs: Math.round(metadataAt - started),\n importMs: Math.round(importAt - metadataAt),\n refreshMs: Math.round(refreshAt - importAt),\n durationMs: Math.round(refreshAt - started)\n });\n endHmrOverlay(overlayToken);\n }, function(err){\n setHmrPhase(null);\n throw err;\n });\n });\n }).catch(function(err){\n console.warn(\"[akan-hmr] React Fast Refresh failed, falling back to RSC refresh\", err);\n fallbackToRsc = true;\n endHmrOverlay(overlayToken);\n refreshRsc(msg);\n }).finally(function(){\n if (!fallbackToRsc) endHmrOverlay(overlayToken);\n });\n }\n\n function swapCss(href){\n var overlayToken = beginHmrOverlay(\"Updating styles...\");\n var link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = href;\n link.setAttribute(\"data-akan-css\", \"pending\");\n link.addEventListener(\"load\", function(){\n var prev = document.querySelectorAll(\"link[data-akan-css=active]\");\n for (var i = 0; i < prev.length; i++) prev[i].parentNode && prev[i].parentNode.removeChild(prev[i]);\n link.setAttribute(\"data-akan-css\", \"active\");\n endHmrOverlay(overlayToken);\n });\n link.addEventListener(\"error\", function(){\n if (link.parentNode) link.parentNode.removeChild(link);\n endHmrOverlay(overlayToken);\n });\n document.head.appendChild(link);\n }\n\n function selectCssUrl(cssAssets){\n if (!cssAssets || typeof cssAssets !== \"object\") return null;\n var parts = location.pathname.split(\"/\").filter(Boolean);\n for (var i = 0; i < parts.length; i++) {\n var asset = cssAssets[parts[i]];\n if (asset && asset.cssUrl) return asset.cssUrl;\n }\n return cssAssets[\"\"] && cssAssets[\"\"].cssUrl ? cssAssets[\"\"].cssUrl : null;\n }\n\n connect();\n})();";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
type InlineRscChunk = [1, string] | [3, string];
|
|
1
2
|
declare global {
|
|
2
|
-
var __RSC_CHUNKS__:
|
|
3
|
+
var __RSC_CHUNKS__: InlineRscChunk[] | undefined;
|
|
3
4
|
var __RSC_CLOSED__: boolean | undefined;
|
|
4
|
-
var __RSC_PUSH__: ((
|
|
5
|
+
var __RSC_PUSH__: ((type: InlineRscChunk[0], data: string) => void) | undefined;
|
|
5
6
|
var __RSC_CLOSE__: (() => void) | undefined;
|
|
6
7
|
var __AKAN_RSC_NAVIGATE__: ((href: string, options?: {
|
|
7
8
|
replace?: boolean;
|
|
@@ -2,20 +2,50 @@ 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";
|
|
20
|
+
export type RscRedirectStatus = 303 | 307 | 308;
|
|
7
21
|
export type RscRenderResult = {
|
|
8
22
|
type: "stream";
|
|
9
23
|
stream: ReadableStream<Uint8Array>;
|
|
10
24
|
theme?: AkanTheme;
|
|
11
25
|
status?: number;
|
|
26
|
+
lateControl: Promise<SsrLateRedirect | null>;
|
|
27
|
+
cancel: (reason?: unknown) => void;
|
|
12
28
|
} | {
|
|
13
29
|
type: "redirect";
|
|
14
30
|
location: string;
|
|
15
31
|
method: RscRedirectMethod;
|
|
32
|
+
status: RscRedirectStatus;
|
|
16
33
|
} | {
|
|
17
34
|
type: "not-found";
|
|
18
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>;
|
|
19
49
|
export interface RscWorkerReloadInput {
|
|
20
50
|
clientManifest: ClientManifest;
|
|
21
51
|
cssAssets?: Record<string, CssAsset>;
|
|
@@ -62,6 +92,7 @@ export declare class RscWorker {
|
|
|
62
92
|
render(req: Request): ReadableStream<Uint8Array>;
|
|
63
93
|
renderWithMeta(req: Request, options?: {
|
|
64
94
|
clientManifest?: ClientManifest;
|
|
95
|
+
signal?: AbortSignal;
|
|
65
96
|
}): Promise<RscRenderResult>;
|
|
66
97
|
kill(): void;
|
|
67
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,34 @@
|
|
|
1
|
-
import type { SsrChunkRegistryStats, SsrFromRscInput } from "./ssrTypes.d.ts";
|
|
1
|
+
import type { SsrChunkRegistryStats, SsrFromRscInput, SsrLateRedirect } from "./ssrTypes.d.ts";
|
|
2
|
+
export declare class SsrChunkRegistry<T> {
|
|
3
|
+
#private;
|
|
4
|
+
readonly maxEntries: number;
|
|
5
|
+
constructor(maxEntries?: number);
|
|
6
|
+
get size(): number;
|
|
7
|
+
get evictionCount(): number;
|
|
8
|
+
get(key: string): T | undefined;
|
|
9
|
+
set(keys: string[], value: T): void;
|
|
10
|
+
}
|
|
11
|
+
export type InlineRscChunk = readonly [1, string] | readonly [3, string];
|
|
12
|
+
export declare function encodeInlineRscChunk(chunk: Uint8Array): InlineRscChunk;
|
|
13
|
+
export declare function htmlEscapeJsonString(value: string): string;
|
|
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>;
|
|
2
32
|
export declare class SsrFromRscRenderer {
|
|
3
33
|
#private;
|
|
4
34
|
static getChunkRegistryStats(): SsrChunkRegistryStats;
|
|
@@ -16,6 +16,13 @@ export interface SsrChunkRegistryStats {
|
|
|
16
16
|
ssrChunkRegistrySize: number;
|
|
17
17
|
ssrChunkLoadCount: number;
|
|
18
18
|
ssrChunkCacheHitCount: number;
|
|
19
|
+
ssrChunkEvictionCount: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SsrLateRedirect {
|
|
22
|
+
type: "redirect";
|
|
23
|
+
location: string;
|
|
24
|
+
method: "replace" | "push";
|
|
25
|
+
status: 303 | 307 | 308;
|
|
19
26
|
}
|
|
20
27
|
export interface SsrFromRscInput {
|
|
21
28
|
request?: Request;
|
|
@@ -43,4 +50,6 @@ export interface SsrFromRscInput {
|
|
|
43
50
|
importmap?: Record<string, string>;
|
|
44
51
|
theme?: AkanTheme;
|
|
45
52
|
injectThemeInitScript?: boolean;
|
|
53
|
+
lateControl?: Promise<SsrLateRedirect | null>;
|
|
54
|
+
onCancel?: (reason?: unknown) => void;
|
|
46
55
|
}
|