akanjs 2.4.0-rc.1 → 2.4.0-rc.2
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/base/baseEnv.ts +5 -1
- package/package.json +1 -1
- package/server/akanApp.ts +76 -8
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0001.log +0 -4
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0002.log +0 -5
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0003.log +0 -7
- package/runtime/logs/serverGet-local-local-2026-07-21-gateway-0004.log +0 -2
package/base/baseEnv.ts
CHANGED
|
@@ -104,7 +104,11 @@ export const getEnv = (): ClientEnv => {
|
|
|
104
104
|
const clientHost =
|
|
105
105
|
process.env.AKAN_PUBLIC_CLIENT_HOST ??
|
|
106
106
|
(operationMode === "local" || side === "server" ? "localhost" : window.location.hostname);
|
|
107
|
-
const clientPort =
|
|
107
|
+
const clientPort =
|
|
108
|
+
side === "server"
|
|
109
|
+
? parseInt(process.env.AKAN_PUBLIC_CLIENT_PORT ?? (operationMode === "local" ? "8282" : "443"))
|
|
110
|
+
: parseInt(window.location.port || (window.location.protocol === "https:" ? "443" : "80"));
|
|
111
|
+
|
|
108
112
|
const clientHttpProtocol =
|
|
109
113
|
side === "client"
|
|
110
114
|
? (window.location.protocol as "http:" | "https:")
|
package/package.json
CHANGED
package/server/akanApp.ts
CHANGED
|
@@ -112,10 +112,13 @@ export class AkanApp {
|
|
|
112
112
|
this.#serverPath = AkanApp.#resolveServerPath(resolvedOptions.serverPath ?? serverPath);
|
|
113
113
|
this.#artifactDir = path.resolve(path.dirname(this.#serverPath), ".akan", "artifact");
|
|
114
114
|
this.#replica = AkanApp.#parseReplicaConfig(resolvedOptions.replica);
|
|
115
|
-
this.#runtimeDir =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
this.#runtimeDir = path.resolve(
|
|
116
|
+
resolvedOptions.runtimeDir ??
|
|
117
|
+
process.env.AKAN_RUNTIME_DIR ??
|
|
118
|
+
(process.env.NODE_ENV === "production"
|
|
119
|
+
? path.resolve(process.cwd(), "runtime")
|
|
120
|
+
: path.resolve(process.cwd(), "local", "apps", process.env.AKAN_PUBLIC_APP_NAME ?? "unknown", "runtime")),
|
|
121
|
+
);
|
|
119
122
|
this.#port = Number(resolvedOptions.port ?? process.env.PORT ?? 8282);
|
|
120
123
|
this.#wsBasePort = Number(resolvedOptions.wsBasePort ?? process.env.AKAN_WS_BASE_PORT ?? this.#port + 10_000);
|
|
121
124
|
this.#openapi = resolvedOptions.openapi;
|
|
@@ -234,9 +237,10 @@ export class AkanApp {
|
|
|
234
237
|
Promise.all([...this.#children.values()].map((child) => child.proc.exited.catch(() => undefined))),
|
|
235
238
|
new Promise((resolve) => setTimeout(resolve, AkanApp.#childShutdownWaitMs())),
|
|
236
239
|
]);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
|
|
241
|
+
const stragglers = [...this.#children.values()].filter((child) => !child.proc.killed);
|
|
242
|
+
for (const child of stragglers) child.proc.kill("SIGKILL");
|
|
243
|
+
await Promise.all(stragglers.map((child) => child.proc.exited.catch(() => undefined)));
|
|
240
244
|
this.#children.clear();
|
|
241
245
|
await this.#stopFileLogging();
|
|
242
246
|
this.#resolveStopped?.();
|
|
@@ -657,7 +661,7 @@ export class AkanApp {
|
|
|
657
661
|
async #proxyHttp(req: Request): Promise<Response> {
|
|
658
662
|
const child = this.#pickFederationChild();
|
|
659
663
|
if (!child?.upstream || child.upstream.type !== "unix") {
|
|
660
|
-
return new Response("No healthy federation child is ready", { status: 503 });
|
|
664
|
+
return this.#respondWithCrashPage(req) ?? new Response("No healthy federation child is ready", { status: 503 });
|
|
661
665
|
}
|
|
662
666
|
const url = new URL(req.url);
|
|
663
667
|
const upstreamUrl = `http://akan-child${url.pathname}${url.search}`;
|
|
@@ -697,6 +701,70 @@ export class AkanApp {
|
|
|
697
701
|
return candidate.code === "FailedToOpenSocket" || String(candidate.message ?? "").includes("FailedToOpenSocket");
|
|
698
702
|
}
|
|
699
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Dev-only: every traffic replica is in the crashed terminal state, so a bare 503 would hide the
|
|
706
|
+
* boot error from the browser. Surface it, and reload once a fixed gateway takes over the port.
|
|
707
|
+
*/
|
|
708
|
+
#respondWithCrashPage(req: Request): Response | null {
|
|
709
|
+
if (!this.#devHosted) return null;
|
|
710
|
+
const trafficChildren = [...this.#children.values()].filter((child) => child.role !== "batch");
|
|
711
|
+
if (trafficChildren.length === 0) return null;
|
|
712
|
+
if (!trafficChildren.every((child) => child.status === "crashed")) return null;
|
|
713
|
+
const detail =
|
|
714
|
+
trafficChildren.map((child) => child.lastErrorMessage ?? child.lastRestartReason).find(Boolean) ??
|
|
715
|
+
"unknown boot error";
|
|
716
|
+
const message = `Backend failed to start after ${AkanApp.#devMaxChildBootFailures} boot attempts: ${detail}`;
|
|
717
|
+
if (!req.headers.get("accept")?.includes("text/html")) {
|
|
718
|
+
return new Response(message, { status: 503, headers: { "cache-control": "no-store" } });
|
|
719
|
+
}
|
|
720
|
+
const html = `<!doctype html>
|
|
721
|
+
<html>
|
|
722
|
+
<head>
|
|
723
|
+
<meta charset="utf-8" />
|
|
724
|
+
<title>Backend failed to start</title>
|
|
725
|
+
<style>
|
|
726
|
+
body { margin: 0; padding: 48px 24px; background: #111827; color: #e5e7eb; font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
727
|
+
main { max-width: 720px; margin: 0 auto; }
|
|
728
|
+
h1 { font-size: 20px; color: #f87171; }
|
|
729
|
+
pre { padding: 16px; border-radius: 8px; background: #1f2937; color: #fca5a5; white-space: pre-wrap; word-break: break-word; }
|
|
730
|
+
p { color: #9ca3af; font-size: 14px; }
|
|
731
|
+
</style>
|
|
732
|
+
</head>
|
|
733
|
+
<body>
|
|
734
|
+
<main>
|
|
735
|
+
<h1>Backend failed to start</h1>
|
|
736
|
+
<pre>${AkanApp.#escapeHtml(detail)}</pre>
|
|
737
|
+
<p>The dev server stopped retrying after ${AkanApp.#devMaxChildBootFailures} failed boots. Fix the error and save — this page reloads automatically.</p>
|
|
738
|
+
</main>
|
|
739
|
+
<script>
|
|
740
|
+
const poll = async () => {
|
|
741
|
+
try {
|
|
742
|
+
const res = await fetch(location.href, { cache: "no-store" });
|
|
743
|
+
if (res.ok) { location.reload(); return; }
|
|
744
|
+
} catch {}
|
|
745
|
+
setTimeout(poll, 1000);
|
|
746
|
+
};
|
|
747
|
+
setTimeout(poll, 1000);
|
|
748
|
+
</script>
|
|
749
|
+
</body>
|
|
750
|
+
</html>`;
|
|
751
|
+
return new Response(html, {
|
|
752
|
+
status: 503,
|
|
753
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
static #escapeHtml(text: string) {
|
|
758
|
+
const replacements: Record<string, string> = {
|
|
759
|
+
"&": "&",
|
|
760
|
+
"<": "<",
|
|
761
|
+
">": ">",
|
|
762
|
+
'"': """,
|
|
763
|
+
"'": "'",
|
|
764
|
+
};
|
|
765
|
+
return text.replace(/[&<>"']/g, (ch) => replacements[ch] ?? ch);
|
|
766
|
+
}
|
|
767
|
+
|
|
700
768
|
/**
|
|
701
769
|
* Gateway-observed upstream round-trip time. The pure proxy overhead is this value
|
|
702
770
|
* minus the child handler time captured in the per-request trace.
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:57 AM INFO AkanApp gateway is running on port http://localhost:32283 +499ms
|
|
2
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:57 AM VERBOSE All 1 child process(es) are ready +512ms
|
|
3
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:57 AM ERROR Child 0/federation failed (exit:1); restarting in 1000ms (attempt 1) +565ms
|
|
4
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:58 AM VERBOSE All 1 child process(es) are ready +1582ms
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:58 AM INFO AkanApp gateway is running on port http://localhost:30883 +1587ms
|
|
2
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:58 AM VERBOSE All 1 child process(es) are ready +1599ms
|
|
3
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:58 AM ERROR Child 0/federation upstream is unreachable (/Users/kangminseon/github3/leading-flight-guidance/pkgs/akanjs/runtime/akan-child-39364-mru5abmz-0.sock); restarting +1740ms
|
|
4
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:58 AM ERROR Child 0/federation failed (upstream-open-failed); restarting in 1000ms (attempt 1) +1740ms
|
|
5
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:59 AM VERBOSE All 1 child process(es) are ready +2760ms
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:59 AM INFO AkanApp gateway is running on port http://localhost:24583 +2773ms
|
|
2
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:59 AM ERROR intentional-boot-failure +2783ms
|
|
3
|
-
[AkanApp] 39364 - 07/21/2026, 04:19:59 AM ERROR Child 0/federation failed (child-error); restarting in 1000ms (attempt 1) +2783ms
|
|
4
|
-
[AkanApp] 39364 - 07/21/2026, 04:20:00 AM ERROR intentional-boot-failure +3799ms
|
|
5
|
-
[AkanApp] 39364 - 07/21/2026, 04:20:00 AM ERROR Child 0/federation failed (child-error); restarting in 2000ms (attempt 2) +3799ms
|
|
6
|
-
[AkanApp] 39364 - 07/21/2026, 04:20:02 AM ERROR intentional-boot-failure +5817ms
|
|
7
|
-
[AkanApp] 39364 - 07/21/2026, 04:20:02 AM ERROR [child-crash-loop] Backend replica 0/federation failed 3 consecutive boots; waiting for a code change to retry: intentional-boot-failure +5817ms
|