akanjs 2.3.13 → 2.4.0-rc.1
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/client/csrTypes.ts +21 -0
- package/client/frameConfig.ts +7 -1
- package/package.json +5 -1
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0001.log +4 -0
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0002.log +5 -0
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0003.log +7 -0
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0004.log +2 -0
- package/server/akanApp.ts +128 -26
- package/server/akanServer.ts +46 -8
- package/server/lifecycle/portInUse.ts +11 -0
- package/server/routeElementComposer.tsx +22 -3
- package/server/routeTreeBuilder.ts +5 -0
- package/server/rscWorker.tsx +16 -0
- package/server/rscWorkerCache.ts +2 -0
- package/server/ssrFromRscRenderer.tsx +2 -1
- package/server/ssrTypes.ts +14 -0
- package/server/webRouter.ts +1 -0
- package/service/ipcTypes.ts +3 -1
- package/signal/guard.ts +3 -3
- package/signal/signalContext.ts +5 -0
- package/store/baseSt.ts +1 -0
- package/types/client/csrTypes.d.ts +19 -0
- package/types/server/lifecycle/portInUse.d.ts +5 -0
- package/types/server/routeElementComposer.d.ts +7 -3
- package/types/server/rscWorkerCache.d.ts +2 -0
- package/types/server/ssrTypes.d.ts +14 -0
- package/types/service/ipcTypes.d.ts +5 -1
- package/types/signal/guard.d.ts +2 -2
- package/types/signal/signalContext.d.ts +1 -0
- package/types/ui/Load/Units.d.ts +1 -1
- package/types/ui/Model/LoadView.d.ts +10 -0
- package/types/ui/Model/index.d.ts +1 -0
- package/types/ui/Model/index_.d.ts +1 -0
- package/types/webkit/index.d.ts +4 -0
- package/types/webkit/useCamera.d.ts +19 -0
- package/types/webkit/useCodepush.d.ts +16 -0
- package/types/webkit/useContact.d.ts +11 -0
- package/types/webkit/useGeoLocation.d.ts +8 -0
- package/types/webkit/usePurchase.d.ts +19 -0
- package/ui/Load/Units.tsx +2 -2
- package/ui/Model/LoadView.tsx +11 -0
- package/ui/Model/index.ts +2 -0
- package/ui/Model/index_.tsx +1 -0
- package/webkit/index.ts +4 -0
- package/webkit/useCamera.tsx +103 -0
- package/webkit/useCodepush.tsx +99 -0
- package/webkit/useContact.tsx +52 -0
- package/webkit/useGeoLocation.tsx +24 -0
- package/webkit/usePurchase.tsx +156 -0
|
@@ -2,14 +2,14 @@ import type {
|
|
|
2
2
|
Head,
|
|
3
3
|
LayoutErrorRender,
|
|
4
4
|
LayoutFallbackRoute,
|
|
5
|
-
PageConfig,
|
|
6
5
|
LayoutNotFoundRender,
|
|
6
|
+
PageConfig,
|
|
7
7
|
PathRoute,
|
|
8
8
|
ResolvedHead,
|
|
9
9
|
RouteRender,
|
|
10
10
|
} from "akanjs/client";
|
|
11
|
-
import { getExplicitPageConfigKeys, resolvePageState } from "../client/frameConfig";
|
|
12
11
|
import { Children, cloneElement, isValidElement, type ReactElement, type ReactNode, Suspense } from "react";
|
|
12
|
+
import { getExplicitPageConfigKeys, resolvePageState } from "../client/frameConfig";
|
|
13
13
|
import { resolveHeadResult } from "./metadata";
|
|
14
14
|
import { type AkanRouteSegmentState, createAkanRouteSegments, createAkanSegmentOutletKey } from "./routeState";
|
|
15
15
|
import { isAkanRscPartialCommitEnabled } from "./rscPartialCommit";
|
|
@@ -61,16 +61,19 @@ export class RouteElementComposer {
|
|
|
61
61
|
pathRoute,
|
|
62
62
|
params,
|
|
63
63
|
searchParams,
|
|
64
|
+
navKey,
|
|
64
65
|
}: {
|
|
65
66
|
pathRoute: PathRoute;
|
|
66
67
|
params: Record<string, string>;
|
|
67
68
|
searchParams: Record<string, string | string[]>;
|
|
69
|
+
navKey?: string;
|
|
68
70
|
}): ReactNode {
|
|
69
71
|
return RouteElementComposer.composeRenders({
|
|
70
72
|
renders: RouteElementComposer.#getRenderStack(pathRoute),
|
|
71
73
|
segments: isAkanRscPartialCommitEnabled() ? createAkanRouteSegments(pathRoute) : undefined,
|
|
72
74
|
params,
|
|
73
75
|
searchParams,
|
|
76
|
+
navKey,
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
|
|
@@ -79,11 +82,13 @@ export class RouteElementComposer {
|
|
|
79
82
|
params,
|
|
80
83
|
searchParams,
|
|
81
84
|
patchStartIndex,
|
|
85
|
+
navKey,
|
|
82
86
|
}: {
|
|
83
87
|
pathRoute: PathRoute;
|
|
84
88
|
params: Record<string, string>;
|
|
85
89
|
searchParams: Record<string, string | string[]>;
|
|
86
90
|
patchStartIndex: number;
|
|
91
|
+
navKey?: string;
|
|
87
92
|
}): ReactNode | null {
|
|
88
93
|
const renders = RouteElementComposer.#getRenderStack(pathRoute);
|
|
89
94
|
if (!Number.isInteger(patchStartIndex) || patchStartIndex < 0 || patchStartIndex >= renders.length) return null;
|
|
@@ -91,9 +96,18 @@ export class RouteElementComposer {
|
|
|
91
96
|
renders: renders.slice(patchStartIndex),
|
|
92
97
|
params,
|
|
93
98
|
searchParams,
|
|
99
|
+
navKey,
|
|
94
100
|
});
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
static async resolveSuffixLoadings(pathRoute: PathRoute, patchStartIndex: number): Promise<void> {
|
|
104
|
+
const renders = RouteElementComposer.#getRenderStack(pathRoute).slice(Math.max(patchStartIndex, 0));
|
|
105
|
+
|
|
106
|
+
await Promise.all(
|
|
107
|
+
renders.map((routeRender) => Promise.resolve(routeRender?.resolveLoading?.()).catch(() => undefined)),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
static async resolveHead({
|
|
98
112
|
pathRoute,
|
|
99
113
|
params,
|
|
@@ -170,18 +184,23 @@ export class RouteElementComposer {
|
|
|
170
184
|
segments,
|
|
171
185
|
params,
|
|
172
186
|
searchParams,
|
|
187
|
+
navKey,
|
|
173
188
|
}: {
|
|
174
189
|
renders: RouteRender[];
|
|
175
190
|
segments?: AkanRouteSegmentState[];
|
|
176
191
|
params: Record<string, string>;
|
|
177
192
|
searchParams: Record<string, string | string[]>;
|
|
193
|
+
navKey?: string;
|
|
178
194
|
}): ReactNode {
|
|
179
195
|
let element: ReactNode = null;
|
|
180
196
|
for (let i = renders.length - 1; i >= 0; i--) {
|
|
181
197
|
const routeRender = renders[i];
|
|
182
198
|
if (!routeRender) continue;
|
|
199
|
+
const loadingFallback = RouteElementComposer.#composeLoadingFallback(renders.slice(i), params);
|
|
200
|
+
const suspenseKey =
|
|
201
|
+
navKey && loadingFallback != null && i === renders.length - 1 ? `akan-loading:${navKey}` : undefined;
|
|
183
202
|
element = (
|
|
184
|
-
<Suspense fallback={
|
|
203
|
+
<Suspense key={suspenseKey} fallback={loadingFallback}>
|
|
185
204
|
<RouteElementComposer.AsyncRender routeRender={routeRender} params={params} searchParams={searchParams}>
|
|
186
205
|
{element}
|
|
187
206
|
</RouteElementComposer.AsyncRender>
|
|
@@ -34,6 +34,7 @@ export const defaultPageState: PageState = {
|
|
|
34
34
|
bottomInset: 0,
|
|
35
35
|
gesture: true,
|
|
36
36
|
cache: false,
|
|
37
|
+
ssr: "stream",
|
|
37
38
|
topSafeAreaColor: "transparent",
|
|
38
39
|
bottomSafeAreaColor: "transparent",
|
|
39
40
|
};
|
|
@@ -341,6 +342,10 @@ export class RouteTreeBuilder {
|
|
|
341
342
|
const loadModule = RouteTreeBuilder.#makeLazyModule(key, kind, loader);
|
|
342
343
|
const routeRender: RouteRender = {
|
|
343
344
|
isAsync: true,
|
|
345
|
+
resolveLoading: async () => {
|
|
346
|
+
const mod = await loadModule();
|
|
347
|
+
routeRender.Loading = mod.Loading as never;
|
|
348
|
+
},
|
|
344
349
|
render: async (props: LayoutProps | PageProps) => {
|
|
345
350
|
const mod = await loadModule();
|
|
346
351
|
routeRender.Loading = mod.Loading as never;
|
package/server/rscWorker.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AkanNotFoundError,
|
|
3
3
|
AkanRedirectError,
|
|
4
4
|
LayoutFallbackRoute,
|
|
5
|
+
PageState,
|
|
5
6
|
PathRoute,
|
|
6
7
|
RedirectStatus,
|
|
7
8
|
ResolvedHead,
|
|
@@ -9,6 +10,7 @@ import type {
|
|
|
9
10
|
import { type AkanI18nConfig, DEFAULT_AKAN_I18N, getBasePathFromPathname, Logger } from "akanjs/common";
|
|
10
11
|
import {
|
|
11
12
|
getRequestDynamicUsage,
|
|
13
|
+
getRequestFrameState,
|
|
12
14
|
getRequestPolicy,
|
|
13
15
|
getRequestTheme,
|
|
14
16
|
requestStorage,
|
|
@@ -214,6 +216,11 @@ export class RscRenderer {
|
|
|
214
216
|
}
|
|
215
217
|
this.#send = process.send.bind(process) as (message: unknown) => void;
|
|
216
218
|
process.on("message", (msg: InMsg) => this.#handleMessage(msg));
|
|
219
|
+
|
|
220
|
+
process.on("disconnect", () => {
|
|
221
|
+
this.#logger.warn("parent IPC channel closed; exiting rsc worker");
|
|
222
|
+
process.exit(0);
|
|
223
|
+
});
|
|
217
224
|
this.#logger.verbose(`constructed (pid=${process.pid})`);
|
|
218
225
|
}
|
|
219
226
|
|
|
@@ -525,6 +532,7 @@ export class RscRenderer {
|
|
|
525
532
|
patchStartSegment: undefined,
|
|
526
533
|
patchHeadSafe: undefined,
|
|
527
534
|
patchHeadSnapshot: undefined,
|
|
535
|
+
ssrBlocking: cached.ssrBlocking,
|
|
528
536
|
},
|
|
529
537
|
send: (message) => this.#send(message),
|
|
530
538
|
isCancelled: () => this.#cancelledRenderRequests.has(requestId),
|
|
@@ -553,9 +561,11 @@ export class RscRenderer {
|
|
|
553
561
|
else element = await this.#renderNotFound(urlObj);
|
|
554
562
|
const traceCacheKey =
|
|
555
563
|
effectivePatchDecision.status === "patch" ? (patchCacheEntry?.key ?? cacheEntry?.key) : cacheEntry?.key;
|
|
564
|
+
const ssrBlocking = getRequestFrameState<PageState>()?.ssr === "block";
|
|
556
565
|
const trace: RscTraceMetadata = {
|
|
557
566
|
...createTraceBase(effectivePatchDecision, traceCacheKey),
|
|
558
567
|
cache: cacheEntry ? "miss" : "bypass",
|
|
568
|
+
ssrBlocking,
|
|
559
569
|
};
|
|
560
570
|
this.#logger.verbose(`render[${requestId}] starting Flight stream`);
|
|
561
571
|
const result = await this.#renderFlightElement(element, msg.clientManifest ?? this.#clientManifest, {
|
|
@@ -597,6 +607,7 @@ export class RscRenderer {
|
|
|
597
607
|
routeId,
|
|
598
608
|
tags: cacheState.tags,
|
|
599
609
|
theme: getRequestTheme(),
|
|
610
|
+
ssrBlocking,
|
|
600
611
|
cacheState,
|
|
601
612
|
patch: createCachedRscPatchMetadata({
|
|
602
613
|
targetRouterState,
|
|
@@ -616,6 +627,7 @@ export class RscRenderer {
|
|
|
616
627
|
routeId,
|
|
617
628
|
tags: cacheState.tags,
|
|
618
629
|
theme: getRequestTheme(),
|
|
630
|
+
ssrBlocking,
|
|
619
631
|
cacheState,
|
|
620
632
|
},
|
|
621
633
|
storeTtl,
|
|
@@ -1243,6 +1255,7 @@ export class RscRenderer {
|
|
|
1243
1255
|
pathRoute,
|
|
1244
1256
|
params: match.params,
|
|
1245
1257
|
searchParams,
|
|
1258
|
+
navKey: url.pathname + url.search,
|
|
1246
1259
|
});
|
|
1247
1260
|
return (
|
|
1248
1261
|
<html
|
|
@@ -1283,11 +1296,14 @@ export class RscRenderer {
|
|
|
1283
1296
|
basePath: this.#getBasePath(url),
|
|
1284
1297
|
});
|
|
1285
1298
|
setRequestFrameState(pathRoute.pageState);
|
|
1299
|
+
|
|
1300
|
+
await RouteElementComposer.resolveSuffixLoadings(pathRoute, patchStartIndex);
|
|
1286
1301
|
return RouteElementComposer.composeSuffix({
|
|
1287
1302
|
pathRoute,
|
|
1288
1303
|
params: match.params,
|
|
1289
1304
|
searchParams,
|
|
1290
1305
|
patchStartIndex,
|
|
1306
|
+
navKey: url.pathname + url.search,
|
|
1291
1307
|
});
|
|
1292
1308
|
}
|
|
1293
1309
|
|
package/server/rscWorkerCache.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface CachedRscResult {
|
|
|
16
16
|
routeId?: string;
|
|
17
17
|
tags?: string[];
|
|
18
18
|
theme?: string;
|
|
19
|
+
/** Whether the matched route opted into blocking SSR (`pageConfig.ssr: "block"`). */
|
|
20
|
+
ssrBlocking?: boolean;
|
|
19
21
|
cacheState: RouteCacheRenderState;
|
|
20
22
|
patch?: CachedRscPatchMetadata;
|
|
21
23
|
}
|
|
@@ -594,12 +594,13 @@ export class SsrFromRscRenderer {
|
|
|
594
594
|
? `${SsrFromRscRenderer.#clientBootstrap}\n${input.extraBootstrapInline}`
|
|
595
595
|
: SsrFromRscRenderer.#clientBootstrap;
|
|
596
596
|
|
|
597
|
+
const waitForAllReady = input.waitForAllReady || process.env.AKAN_SSR_WAIT_FOR_ALL_READY === "1";
|
|
597
598
|
const renderHtml = async () => {
|
|
598
599
|
const root = await thenable;
|
|
599
600
|
const stream = await renderToReadableStream(root, {
|
|
600
601
|
bootstrapScriptContent: bootstrap,
|
|
601
602
|
});
|
|
602
|
-
await stream.allReady;
|
|
603
|
+
if (waitForAllReady) await stream.allReady;
|
|
603
604
|
return stream;
|
|
604
605
|
};
|
|
605
606
|
const requestContext = input.requestStore ?? input.request;
|
package/server/ssrTypes.ts
CHANGED
|
@@ -42,6 +42,13 @@ export interface RscTraceMetadata {
|
|
|
42
42
|
patchHeadSafe?: boolean;
|
|
43
43
|
patchHeadSnapshot?: string;
|
|
44
44
|
routeState?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Resolved from the matched route's `pageConfig.ssr === "block"`. Rides the
|
|
47
|
+
* trace so the host (which never resolves pageConfig itself) can decide
|
|
48
|
+
* whether the full-document SSR pass buffers until every Suspense boundary
|
|
49
|
+
* resolves. Absent/`false` means stream the shell first.
|
|
50
|
+
*/
|
|
51
|
+
ssrBlocking?: boolean;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
export interface SsrFromRscInput {
|
|
@@ -73,4 +80,11 @@ export interface SsrFromRscInput {
|
|
|
73
80
|
injectThemeInitScript?: boolean;
|
|
74
81
|
lateControl?: Promise<SsrLateRedirect | null>;
|
|
75
82
|
onCancel?: (reason?: unknown) => void;
|
|
83
|
+
/**
|
|
84
|
+
* When true, buffer the whole document until every Suspense boundary resolves
|
|
85
|
+
* (`stream.allReady`) before emitting a byte, matching `pageConfig.ssr:
|
|
86
|
+
* "block"`. Defaults to shell-first streaming so `Loading` fallbacks surface.
|
|
87
|
+
* The `AKAN_SSR_WAIT_FOR_ALL_READY=1` env var forces blocking globally.
|
|
88
|
+
*/
|
|
89
|
+
waitForAllReady?: boolean;
|
|
76
90
|
}
|
package/server/webRouter.ts
CHANGED
|
@@ -578,6 +578,7 @@ export class WebRouter {
|
|
|
578
578
|
importmap: this.#artifact.vendorMap,
|
|
579
579
|
theme: themeCookieExists ? undefined : (rscResult.theme ?? "system"),
|
|
580
580
|
lateControl: rscResult.lateControl,
|
|
581
|
+
waitForAllReady: rscResult.trace?.ssrBlocking ?? false,
|
|
581
582
|
onCancel: (reason: unknown) => {
|
|
582
583
|
rscResult.cancel(reason);
|
|
583
584
|
},
|
package/service/ipcTypes.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type AkanChildRole = "all" | "federation" | "batch";
|
|
2
2
|
|
|
3
|
-
export type AkanChildStatus = "starting" | "ready" | "healthy" | "draining" | "unhealthy" | "exited";
|
|
3
|
+
export type AkanChildStatus = "starting" | "ready" | "healthy" | "draining" | "unhealthy" | "exited" | "crashed";
|
|
4
4
|
|
|
5
5
|
export type AkanUpstream = { type: "unix"; socketPath: string } | { type: "tcp"; host: string; port: number };
|
|
6
6
|
|
|
@@ -110,6 +110,8 @@ export type AkanIpcMessage =
|
|
|
110
110
|
replicaIdx: number;
|
|
111
111
|
role: AkanChildRole;
|
|
112
112
|
upstream?: AkanUpstream;
|
|
113
|
+
/** Actual websocket upstream the child bound; may differ from the preferred port when it was in use. */
|
|
114
|
+
wsUpstream?: Extract<AkanUpstream, { type: "tcp" }>;
|
|
113
115
|
healthPath?: string;
|
|
114
116
|
}
|
|
115
117
|
| { type: "backend-ready"; pid: number }
|
package/signal/guard.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Cls } from "akanjs/base";
|
|
1
|
+
import type { Cls, PromiseOrObject } from "akanjs/base";
|
|
2
2
|
import type { SignalContext } from "./signalContext";
|
|
3
3
|
|
|
4
4
|
export interface Guard {
|
|
5
|
-
canPass(context: SignalContext): boolean
|
|
5
|
+
canPass(context: SignalContext): PromiseOrObject<boolean>;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export type GuardCls<Name extends string = string> = Cls<Guard, { readonly name: Name }>;
|
|
@@ -11,7 +11,7 @@ export type GuardCls<Name extends string = string> = Cls<Guard, { readonly name:
|
|
|
11
11
|
export const guard = <T extends string>(name: T): GuardCls<T> => {
|
|
12
12
|
return class Guard {
|
|
13
13
|
static name = name;
|
|
14
|
-
canPass(context: SignalContext): boolean {
|
|
14
|
+
canPass(context: SignalContext): PromiseOrObject<boolean> {
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
};
|
package/signal/signalContext.ts
CHANGED
|
@@ -99,6 +99,11 @@ export class SignalContext<
|
|
|
99
99
|
}
|
|
100
100
|
return instance as T;
|
|
101
101
|
}
|
|
102
|
+
getService<T>(refName: string): T {
|
|
103
|
+
const service = this.#live.service.get(refName);
|
|
104
|
+
if (!service) throw new Exception.Error(`Service "${refName}" not found in live registry`);
|
|
105
|
+
return service as T;
|
|
106
|
+
}
|
|
102
107
|
async init() {
|
|
103
108
|
if (this.trace) {
|
|
104
109
|
const start = performance.now();
|
package/store/baseSt.ts
CHANGED
|
@@ -44,6 +44,7 @@ export class BaseStore extends store("base" as const, () => ({
|
|
|
44
44
|
bottomInset: 0,
|
|
45
45
|
gesture: true,
|
|
46
46
|
cache: false,
|
|
47
|
+
ssr: "stream",
|
|
47
48
|
topSafeAreaColor: "var(--color-base-100, Canvas)",
|
|
48
49
|
bottomSafeAreaColor: "var(--color-base-100, Canvas)",
|
|
49
50
|
} as PageState,
|
|
@@ -10,6 +10,19 @@ export type PageSafeAreaConfig = boolean | "top" | "bottom" | {
|
|
|
10
10
|
bottom?: boolean;
|
|
11
11
|
android?: "auto" | "edge-to-edge" | "none";
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Server-render strategy for the initial full-document SSR pass.
|
|
15
|
+
* - `"stream"` (default): flush the shell — including any `Loading` Suspense
|
|
16
|
+
* fallback — as soon as it is ready, then stream the resolved page. Redirects
|
|
17
|
+
* decided in the shell still become real HTTP redirects; redirects thrown
|
|
18
|
+
* inside suspended content degrade to soft (client) redirects.
|
|
19
|
+
* - `"block"`: buffer the whole document until every Suspense boundary resolves
|
|
20
|
+
* before sending a byte. The `Loading` fallback never reaches the browser, but
|
|
21
|
+
* a non-redirect error thrown in slow content can still yield a clean error
|
|
22
|
+
* page. Use only for routes that need that guarantee and do not care about SEO
|
|
23
|
+
* or first-paint of the fallback.
|
|
24
|
+
*/
|
|
25
|
+
export type SsrRenderMode = "stream" | "block";
|
|
13
26
|
/** Per-page CSR configuration for transition, safe-area, and gesture behavior. */
|
|
14
27
|
export interface PageConfig {
|
|
15
28
|
transition?: TransitionType;
|
|
@@ -20,6 +33,8 @@ export interface PageConfig {
|
|
|
20
33
|
bottomInset?: number | boolean;
|
|
21
34
|
gesture?: boolean;
|
|
22
35
|
cache?: boolean;
|
|
36
|
+
/** Initial full-document SSR strategy. Defaults to `"stream"`. */
|
|
37
|
+
ssr?: SsrRenderMode;
|
|
23
38
|
/**
|
|
24
39
|
* Opt in to guarded RSC page suffix commits when the page does not require
|
|
25
40
|
* head/metadata updates and the retained route chain head is invariant for
|
|
@@ -37,6 +52,7 @@ export interface CsrState {
|
|
|
37
52
|
bottomInset: number;
|
|
38
53
|
gesture: boolean;
|
|
39
54
|
cache: boolean;
|
|
55
|
+
ssr: SsrRenderMode;
|
|
40
56
|
topSafeAreaColor?: string;
|
|
41
57
|
bottomSafeAreaColor?: string;
|
|
42
58
|
}
|
|
@@ -98,6 +114,9 @@ export interface RouteRender {
|
|
|
98
114
|
render: LayoutRender | PageRender;
|
|
99
115
|
isAsync?: boolean;
|
|
100
116
|
Loading?: LayoutLoadingRender | PageLoadingRender;
|
|
117
|
+
/** Loads the module and populates `Loading` without running `render`/`resolveHead`.
|
|
118
|
+
* Used by the patch (suffix) compose path, which never calls `resolveHead`. */
|
|
119
|
+
resolveLoading?: () => void | Promise<void>;
|
|
101
120
|
NotFound?: LayoutNotFoundRender;
|
|
102
121
|
Error?: LayoutErrorRender;
|
|
103
122
|
resolveNotFound?: () => PromiseOrObject<LayoutNotFoundRender | undefined>;
|
|
@@ -11,17 +11,20 @@ export declare class RouteElementComposer {
|
|
|
11
11
|
route: PathRoute | LayoutFallbackRoute;
|
|
12
12
|
basePath?: string | null;
|
|
13
13
|
}): Promise<import("akanjs/client").PageState>;
|
|
14
|
-
static compose({ pathRoute, params, searchParams, }: {
|
|
14
|
+
static compose({ pathRoute, params, searchParams, navKey, }: {
|
|
15
15
|
pathRoute: PathRoute;
|
|
16
16
|
params: Record<string, string>;
|
|
17
17
|
searchParams: Record<string, string | string[]>;
|
|
18
|
+
navKey?: string;
|
|
18
19
|
}): ReactNode;
|
|
19
|
-
static composeSuffix({ pathRoute, params, searchParams, patchStartIndex, }: {
|
|
20
|
+
static composeSuffix({ pathRoute, params, searchParams, patchStartIndex, navKey, }: {
|
|
20
21
|
pathRoute: PathRoute;
|
|
21
22
|
params: Record<string, string>;
|
|
22
23
|
searchParams: Record<string, string | string[]>;
|
|
23
24
|
patchStartIndex: number;
|
|
25
|
+
navKey?: string;
|
|
24
26
|
}): ReactNode | null;
|
|
27
|
+
static resolveSuffixLoadings(pathRoute: PathRoute, patchStartIndex: number): Promise<void>;
|
|
25
28
|
static resolveHead({ pathRoute, params, searchParams, }: {
|
|
26
29
|
pathRoute: PathRoute;
|
|
27
30
|
params: Record<string, string>;
|
|
@@ -41,11 +44,12 @@ export declare class RouteElementComposer {
|
|
|
41
44
|
error?: unknown;
|
|
42
45
|
digest?: string;
|
|
43
46
|
}): Promise<ReactNode | null>;
|
|
44
|
-
static composeRenders({ renders, segments, params, searchParams, }: {
|
|
47
|
+
static composeRenders({ renders, segments, params, searchParams, navKey, }: {
|
|
45
48
|
renders: RouteRender[];
|
|
46
49
|
segments?: AkanRouteSegmentState[];
|
|
47
50
|
params: Record<string, string>;
|
|
48
51
|
searchParams: Record<string, string | string[]>;
|
|
52
|
+
navKey?: string;
|
|
49
53
|
}): ReactNode;
|
|
50
54
|
static renderAsync({ routeRender, children, params, searchParams, }: {
|
|
51
55
|
routeRender: RouteRender;
|
|
@@ -8,6 +8,8 @@ export interface CachedRscResult {
|
|
|
8
8
|
routeId?: string;
|
|
9
9
|
tags?: string[];
|
|
10
10
|
theme?: string;
|
|
11
|
+
/** Whether the matched route opted into blocking SSR (`pageConfig.ssr: "block"`). */
|
|
12
|
+
ssrBlocking?: boolean;
|
|
11
13
|
cacheState: RouteCacheRenderState;
|
|
12
14
|
patch?: CachedRscPatchMetadata;
|
|
13
15
|
}
|
|
@@ -40,6 +40,13 @@ export interface RscTraceMetadata {
|
|
|
40
40
|
patchHeadSafe?: boolean;
|
|
41
41
|
patchHeadSnapshot?: string;
|
|
42
42
|
routeState?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Resolved from the matched route's `pageConfig.ssr === "block"`. Rides the
|
|
45
|
+
* trace so the host (which never resolves pageConfig itself) can decide
|
|
46
|
+
* whether the full-document SSR pass buffers until every Suspense boundary
|
|
47
|
+
* resolves. Absent/`false` means stream the shell first.
|
|
48
|
+
*/
|
|
49
|
+
ssrBlocking?: boolean;
|
|
43
50
|
}
|
|
44
51
|
export interface SsrFromRscInput {
|
|
45
52
|
request?: Request;
|
|
@@ -70,4 +77,11 @@ export interface SsrFromRscInput {
|
|
|
70
77
|
injectThemeInitScript?: boolean;
|
|
71
78
|
lateControl?: Promise<SsrLateRedirect | null>;
|
|
72
79
|
onCancel?: (reason?: unknown) => void;
|
|
80
|
+
/**
|
|
81
|
+
* When true, buffer the whole document until every Suspense boundary resolves
|
|
82
|
+
* (`stream.allReady`) before emitting a byte, matching `pageConfig.ssr:
|
|
83
|
+
* "block"`. Defaults to shell-first streaming so `Loading` fallbacks surface.
|
|
84
|
+
* The `AKAN_SSR_WAIT_FOR_ALL_READY=1` env var forces blocking globally.
|
|
85
|
+
*/
|
|
86
|
+
waitForAllReady?: boolean;
|
|
73
87
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type AkanChildRole = "all" | "federation" | "batch";
|
|
2
|
-
export type AkanChildStatus = "starting" | "ready" | "healthy" | "draining" | "unhealthy" | "exited";
|
|
2
|
+
export type AkanChildStatus = "starting" | "ready" | "healthy" | "draining" | "unhealthy" | "exited" | "crashed";
|
|
3
3
|
export type AkanUpstream = {
|
|
4
4
|
type: "unix";
|
|
5
5
|
socketPath: string;
|
|
@@ -122,6 +122,10 @@ export type AkanIpcMessage = {
|
|
|
122
122
|
replicaIdx: number;
|
|
123
123
|
role: AkanChildRole;
|
|
124
124
|
upstream?: AkanUpstream;
|
|
125
|
+
/** Actual websocket upstream the child bound; may differ from the preferred port when it was in use. */
|
|
126
|
+
wsUpstream?: Extract<AkanUpstream, {
|
|
127
|
+
type: "tcp";
|
|
128
|
+
}>;
|
|
125
129
|
healthPath?: string;
|
|
126
130
|
} | {
|
|
127
131
|
type: "backend-ready";
|
package/types/signal/guard.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Cls } from "akanjs/base";
|
|
1
|
+
import type { Cls, PromiseOrObject } from "akanjs/base";
|
|
2
2
|
import type { SignalContext } from "./signalContext.d.ts";
|
|
3
3
|
export interface Guard {
|
|
4
|
-
canPass(context: SignalContext): boolean
|
|
4
|
+
canPass(context: SignalContext): PromiseOrObject<boolean>;
|
|
5
5
|
}
|
|
6
6
|
export type GuardCls<Name extends string = string> = Cls<Guard, {
|
|
7
7
|
readonly name: Name;
|
|
@@ -30,6 +30,7 @@ export declare class SignalContext<Ctx extends HttpExecutionContext | WebSocketE
|
|
|
30
30
|
middleware: Map<string, MiddlewareCls>;
|
|
31
31
|
});
|
|
32
32
|
getAdaptor<T extends Adaptor>(adaptorCls: AdaptorCls<T>): T;
|
|
33
|
+
getService<T>(refName: string): T;
|
|
33
34
|
init(): Promise<this>;
|
|
34
35
|
exec(): Promise<Response | undefined>;
|
|
35
36
|
static try(endpoint: Adaptor, endpointInfo: EndpointInfo, key: string, fn: () => Promise<Response | undefined>): Promise<Response | undefined>;
|
package/types/ui/Load/Units.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface DefaultProps<L extends {
|
|
|
13
13
|
loading?: ReactNode;
|
|
14
14
|
filter?: (item: L, idx: number) => boolean;
|
|
15
15
|
sort?: (a: L, b: L) => number;
|
|
16
|
-
renderEmpty?: null | (() => ReactNode);
|
|
16
|
+
renderEmpty?: null | (() => ReactNode) | false;
|
|
17
17
|
renderItem?: (item: L, idx: number) => ReactNode;
|
|
18
18
|
renderList?: (list: DataList<L>) => ReactNode;
|
|
19
19
|
reverse?: boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ClientView } from "akanjs/fetch";
|
|
2
|
+
interface LoadViewProps<T extends string, Model extends {
|
|
3
|
+
id: string;
|
|
4
|
+
}> {
|
|
5
|
+
view: ClientView<T, Model>;
|
|
6
|
+
}
|
|
7
|
+
export default function LoadView<T extends string, Light extends {
|
|
8
|
+
id: string;
|
|
9
|
+
}>({ view }: LoadViewProps<T, Light>): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -8,6 +8,7 @@ export declare const Model: {
|
|
|
8
8
|
Remove: typeof import("./Remove.d.ts").default;
|
|
9
9
|
RemoveWrapper: typeof import("./RemoveWrapper.d.ts").default;
|
|
10
10
|
LoadInit: typeof import("./LoadInit.d.ts").default;
|
|
11
|
+
LoadView: typeof import("./LoadView.d.ts").default;
|
|
11
12
|
ViewWrapper: typeof import("./ViewWrapper.d.ts").default;
|
|
12
13
|
ViewEditModal: typeof import("./ViewEditModal.d.ts").default;
|
|
13
14
|
Edit: typeof import("./Edit.d.ts").default;
|
|
@@ -7,6 +7,7 @@ export declare const NewWrapper: typeof import("./NewWrapper.d.ts").default;
|
|
|
7
7
|
export declare const EditWrapper: typeof import("./EditWrapper.d.ts").default;
|
|
8
8
|
export declare const RemoveWrapper: typeof import("./RemoveWrapper.d.ts").default;
|
|
9
9
|
export declare const LoadInit: typeof import("./LoadInit.d.ts").default;
|
|
10
|
+
export declare const LoadView: typeof import("./LoadView.d.ts").default;
|
|
10
11
|
export declare const ViewWrapper: typeof import("./ViewWrapper.d.ts").default;
|
|
11
12
|
export declare const ViewEditModal: typeof import("./ViewEditModal.d.ts").default;
|
|
12
13
|
export declare const Edit: typeof import("./Edit.d.ts").default;
|
package/types/webkit/index.d.ts
CHANGED
|
@@ -3,9 +3,13 @@ export { createRobotPage } from "./createRobotPage.d.ts";
|
|
|
3
3
|
export { createSitemapPage } from "./createSitemapPage.d.ts";
|
|
4
4
|
export { lazy } from "./lazy.d.ts";
|
|
5
5
|
export type * from "./types.d.ts";
|
|
6
|
+
export { useCamera } from "./useCamera.d.ts";
|
|
7
|
+
export { useCodepush } from "./useCodepush.d.ts";
|
|
8
|
+
export { useContact } from "./useContact.d.ts";
|
|
6
9
|
export { useCsrValues } from "./useCsrValues.d.ts";
|
|
7
10
|
export { useDebounce } from "./useDebounce.d.ts";
|
|
8
11
|
export { useFetch, useFetchFn } from "./useFetch.d.ts";
|
|
12
|
+
export { useGeoLocation } from "./useGeoLocation.d.ts";
|
|
9
13
|
export { useHistory } from "./useHistory.d.ts";
|
|
10
14
|
export { useInterval } from "./useInterval.d.ts";
|
|
11
15
|
export { useLocation } from "./useLocation.d.ts";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CapacitorPermissionState } from "akanjs/client/capacitor";
|
|
2
|
+
type PermissionStatus = {
|
|
3
|
+
camera: CapacitorPermissionState;
|
|
4
|
+
photos: CapacitorPermissionState;
|
|
5
|
+
};
|
|
6
|
+
/** Capacitor camera/photos hook with permission checks and app-settings fallback. */
|
|
7
|
+
export declare const useCamera: () => {
|
|
8
|
+
permissions: PermissionStatus;
|
|
9
|
+
getPhoto: (src?: "prompt" | "camera" | "photos") => Promise<{
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
dataUrl?: string;
|
|
12
|
+
} | undefined>;
|
|
13
|
+
pickImage: () => Promise<{
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
photos: unknown[];
|
|
16
|
+
}>;
|
|
17
|
+
checkPermission: (type: "photos" | "camera" | "all") => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ProtoAppInfo, ProtoFile } from "akanjs/constant";
|
|
2
|
+
export declare const useCodepush: ({ serverUrl }: {
|
|
3
|
+
serverUrl: string;
|
|
4
|
+
}) => {
|
|
5
|
+
update: boolean;
|
|
6
|
+
version: string;
|
|
7
|
+
initialize: () => Promise<void>;
|
|
8
|
+
checkNewRelease: () => Promise<{
|
|
9
|
+
release: ProtoAppInfo & {
|
|
10
|
+
appBuild: string;
|
|
11
|
+
};
|
|
12
|
+
bundleFile: ProtoFile;
|
|
13
|
+
} | undefined>;
|
|
14
|
+
codepush: () => Promise<void>;
|
|
15
|
+
statManager: () => Promise<void>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CapacitorPermissionState } from "akanjs/client/capacitor";
|
|
2
|
+
type PermissionStatus = {
|
|
3
|
+
contacts: CapacitorPermissionState;
|
|
4
|
+
};
|
|
5
|
+
/** Capacitor contacts hook with permission checks and contact loading helpers. */
|
|
6
|
+
export declare const useContact: () => {
|
|
7
|
+
permissions: PermissionStatus;
|
|
8
|
+
getContacts: () => Promise<unknown[]>;
|
|
9
|
+
checkPermission: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Capacitor geolocation hook with permission checks and current position lookup. */
|
|
2
|
+
export declare const useGeoLocation: () => {
|
|
3
|
+
checkPermission: () => Promise<{
|
|
4
|
+
geolocation: string;
|
|
5
|
+
coarseLocation: string;
|
|
6
|
+
}>;
|
|
7
|
+
getPosition: () => Promise<unknown>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "cordova-plugin-purchase/www/store";
|
|
2
|
+
export type PlatformType = "android" | "ios" | "all";
|
|
3
|
+
export interface ProductType {
|
|
4
|
+
id: string;
|
|
5
|
+
type: keyof typeof CdvPurchase.ProductType;
|
|
6
|
+
}
|
|
7
|
+
export type CdvProductType = CdvPurchase.ProductType;
|
|
8
|
+
export declare const usePurchase: ({ platform, productInfo, url, onPay, onSubscribe, }: {
|
|
9
|
+
platform: PlatformType;
|
|
10
|
+
productInfo: ProductType[];
|
|
11
|
+
url: string;
|
|
12
|
+
onPay?: (transaction: CdvPurchase.Transaction) => void | Promise<void>;
|
|
13
|
+
onSubscribe?: (transaction: CdvPurchase.Transaction) => void | Promise<void>;
|
|
14
|
+
}) => {
|
|
15
|
+
isLoading: boolean;
|
|
16
|
+
products: CdvPurchase.Product[];
|
|
17
|
+
purchaseProduct: (product: CdvPurchase.Product) => Promise<void>;
|
|
18
|
+
restorePurchases: () => Promise<void>;
|
|
19
|
+
};
|
package/ui/Load/Units.tsx
CHANGED
|
@@ -23,7 +23,7 @@ interface DefaultProps<L extends { id: string }> {
|
|
|
23
23
|
loading?: ReactNode;
|
|
24
24
|
filter?: (item: L, idx: number) => boolean;
|
|
25
25
|
sort?: (a: L, b: L) => number;
|
|
26
|
-
renderEmpty?: null | (() => ReactNode);
|
|
26
|
+
renderEmpty?: null | (() => ReactNode) | false;
|
|
27
27
|
renderItem?: (item: L, idx: number) => ReactNode;
|
|
28
28
|
renderList?: (list: DataList<L>) => ReactNode;
|
|
29
29
|
reverse?: boolean;
|
|
@@ -172,7 +172,7 @@ function Render<RefName extends string, Light extends { id: string }>({
|
|
|
172
172
|
if (renderList)
|
|
173
173
|
return (
|
|
174
174
|
<>
|
|
175
|
-
{modelDataList.length ? (
|
|
175
|
+
{modelDataList.length || renderEmpty === false ? (
|
|
176
176
|
<ContainerWrapper
|
|
177
177
|
containerRef={containerRef}
|
|
178
178
|
className={clsx(className, {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { ClientView } from "akanjs/fetch";
|
|
3
|
+
|
|
4
|
+
import { Load } from "../Load";
|
|
5
|
+
|
|
6
|
+
interface LoadViewProps<T extends string, Model extends { id: string }> {
|
|
7
|
+
view: ClientView<T, Model>;
|
|
8
|
+
}
|
|
9
|
+
export default function LoadView<T extends string, Light extends { id: string }>({ view }: LoadViewProps<T, Light>) {
|
|
10
|
+
return <Load.View view={view} renderView={() => null} loading={null} />;
|
|
11
|
+
}
|