akanjs 2.2.10-rc.1 → 2.2.11

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # akanjs
2
2
 
3
+ ## 2.2.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 8190632: Add Akan server console support with CLI/build integration and documentation for console-oriented workflows.
8
+ - 4bce7f9: Add initial LLM discovery docs and stabilize Akan client/runtime behavior.
9
+
10
+ - Add `/llms.txt` documentation discovery for Akan docs.
11
+ - Add `wsConnect` support for automatic WebSocket connections.
12
+ - Delay client bootstrap module execution until the SSR fizz stream is ready.
13
+ - Improve route tree, HMR, fetch, store, and SSR/client runtime stability.
14
+
3
15
  ## 2.2.7
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.2.10-rc.1",
3
+ "version": "2.2.11",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -39,6 +39,19 @@ interface AkanAppPrepared {
39
39
  webProxyRunner: WebProxyRunner | null;
40
40
  }
41
41
 
42
+ export interface AkanServerConsoleInfo {
43
+ name: string;
44
+ status: AkanServer["status"];
45
+ serverMode: AkanServer["serverMode"];
46
+ env: Pick<BaseEnv, "appName" | "environment" | "operationMode" | "repoName" | "serveDomain" | "databaseMode">;
47
+ services: string[];
48
+ signals: string[];
49
+ adaptors: string[];
50
+ uses: string[];
51
+ serviceStages: string[][];
52
+ adaptorStages: string[][];
53
+ }
54
+
42
55
  export class AkanServer {
43
56
  status: "stopped" | "initializing" | "initialized" | "starting" | "running" | "stopping" = "stopped";
44
57
 
@@ -120,6 +133,29 @@ export class AkanServer {
120
133
  return this.#di.getAdaptor<T>(refName);
121
134
  }
122
135
 
136
+ inspectConsole(): AkanServerConsoleInfo {
137
+ this.#assertCanGet();
138
+ return {
139
+ name: this.name,
140
+ status: this.status,
141
+ serverMode: this.serverMode,
142
+ env: {
143
+ appName: this.env.appName,
144
+ environment: this.env.environment,
145
+ operationMode: this.env.operationMode,
146
+ repoName: this.env.repoName,
147
+ serveDomain: this.env.serveDomain,
148
+ databaseMode: this.env.databaseMode,
149
+ },
150
+ services: [...this.#di.registry.serviceCls.keys()].sort((a, b) => a.localeCompare(b)),
151
+ signals: [...this.#di.registry.serverSignalCls.keys()].sort((a, b) => a.localeCompare(b)),
152
+ adaptors: [...this.#di.registry.adaptorCls.keys()].sort((a, b) => a.localeCompare(b)),
153
+ uses: [...this.#di.registry.uses.keys()].sort((a, b) => a.localeCompare(b)),
154
+ serviceStages: this.#di.hierarchy.serviceStages.map((stage) => [...stage]),
155
+ adaptorStages: this.#di.hierarchy.adaptorStages.map((stage) => [...stage]),
156
+ };
157
+ }
158
+
123
159
  async init({ routes: initRoutes = true, web = true }: { routes?: boolean; web?: boolean } = {}) {
124
160
  if (this.status !== "stopped") throw new Error("AkanServer is not able to init. It is already running.");
125
161
  this.status = "initializing";
@@ -248,14 +284,14 @@ export class AkanServer {
248
284
  }
249
285
 
250
286
  async start({ listen, web = true }: { listen?: boolean; web?: boolean } = {}) {
251
- const isScriptCommand = process.env.AKAN_COMMAND_TYPE === "script";
252
- const shouldListen = (listen ?? !isScriptCommand) && this.serverMode !== "batch";
287
+ const isNoListenCommand = process.env.AKAN_COMMAND_TYPE === "script" || process.env.AKAN_COMMAND_TYPE === "console";
288
+ const shouldListen = (listen ?? !isNoListenCommand) && this.serverMode !== "batch";
253
289
  await this.init({ routes: shouldListen, web });
254
290
  if (!shouldListen) {
255
291
  const websocket = this.#di.getWebsocketAdaptor();
256
292
  if (websocket) SignalResolver.setLocalPublish((roomId, data) => this.#localPublish?.(roomId, data), websocket);
257
293
  this.status = "running";
258
- if (!isScriptCommand) {
294
+ if (!isNoListenCommand) {
259
295
  this.#startMetricsReporting();
260
296
  this.#di.registerSchedule(this.serverMode);
261
297
  process.on("message", (message) => this.#handleIpcMessage(message as AkanIpcMessage));
@@ -0,0 +1,189 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { inspect } from "node:util";
3
+ import type { BaseEnv } from "akanjs/base";
4
+ import type { Adaptor, Service } from "akanjs/service";
5
+ import type { ServerSignal } from "akanjs/signal";
6
+ import type { AkanServer } from "./akanServer";
7
+
8
+ type AsyncFunctionConstructor = new (...args: string[]) => (scope: object) => Promise<unknown>;
9
+
10
+ const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as AsyncFunctionConstructor;
11
+
12
+ export interface AkanConsoleOptions {
13
+ prompt?: string;
14
+ globals?: Record<string, unknown>;
15
+ input?: typeof process.stdin;
16
+ output?: typeof process.stdout;
17
+ }
18
+
19
+ export interface AkanConsoleContext extends Record<string, unknown> {
20
+ server: AkanServer;
21
+ env: AkanServer["env"];
22
+ get: AkanServer["get"];
23
+ service: <T = Service>(refName: string) => T;
24
+ signal: <T = ServerSignal>(refName: string) => T;
25
+ adaptor: <T = Adaptor>(refName: string) => T;
26
+ methods: (value: unknown) => string[];
27
+ debug: () => ReturnType<AkanServer["inspectConsole"]>;
28
+ }
29
+
30
+ export const assertAkanConsoleAllowed = (
31
+ env: Pick<BaseEnv, "environment" | "operationMode"> = {
32
+ environment: (process.env.AKAN_PUBLIC_ENV ?? "debug") as BaseEnv["environment"],
33
+ operationMode: (process.env.AKAN_PUBLIC_OPERATION_MODE ?? "cloud") as BaseEnv["operationMode"],
34
+ },
35
+ ) => {
36
+ const isProductionLike =
37
+ env.environment === "main" ||
38
+ env.operationMode === "cloud" ||
39
+ env.operationMode === "edge" ||
40
+ process.env.NODE_ENV === "production";
41
+ if (!isProductionLike || process.env.AKAN_CONSOLE === "1") return;
42
+
43
+ throw new Error(
44
+ [
45
+ "Akan console is disabled for production-like environments.",
46
+ "Run with AKAN_CONSOLE=1 only for the exec command that opens the console.",
47
+ "Example: AKAN_CONSOLE=1 bun console.js",
48
+ ].join("\n"),
49
+ );
50
+ };
51
+
52
+ export const getAkanConsoleMethods = (value: unknown): string[] => {
53
+ const names = new Set<string>();
54
+ let proto =
55
+ typeof value === "function"
56
+ ? value.prototype
57
+ : value && (typeof value === "object" || typeof value === "function")
58
+ ? Object.getPrototypeOf(value)
59
+ : null;
60
+
61
+ while (proto && proto !== Object.prototype) {
62
+ for (const name of Object.getOwnPropertyNames(proto)) {
63
+ if (name === "constructor") continue;
64
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
65
+ if (typeof descriptor?.value === "function") names.add(name);
66
+ }
67
+ proto = Object.getPrototypeOf(proto);
68
+ }
69
+
70
+ return [...names].sort((a, b) => a.localeCompare(b));
71
+ };
72
+
73
+ export const createAkanConsoleContext = (
74
+ server: AkanServer,
75
+ globals: Record<string, unknown> = {},
76
+ ): AkanConsoleContext => {
77
+ const context = {
78
+ server,
79
+ env: server.env,
80
+ get: server.get.bind(server) as AkanServer["get"],
81
+ service: server.getService.bind(server),
82
+ signal: server.getSignal.bind(server),
83
+ adaptor: server.getAdaptor.bind(server),
84
+ methods: getAkanConsoleMethods,
85
+ debug: () => server.inspectConsole(),
86
+ ...globals,
87
+ };
88
+ return context;
89
+ };
90
+
91
+ const createScope = (context: Record<string, unknown>) =>
92
+ new Proxy(context, {
93
+ has: () => true,
94
+ get(target, prop) {
95
+ if (prop === Symbol.unscopables) return undefined;
96
+ if (prop in target) return target[prop as keyof typeof target];
97
+ return (globalThis as Record<PropertyKey, unknown>)[prop];
98
+ },
99
+ set(target, prop, value) {
100
+ target[prop as keyof typeof target] = value;
101
+ return true;
102
+ },
103
+ });
104
+
105
+ export const evaluateAkanConsoleInput = async (source: string, context: Record<string, unknown>) => {
106
+ const trimmed = source.trim();
107
+ if (!trimmed) return undefined;
108
+ const scope = createScope(context);
109
+
110
+ try {
111
+ return await new AsyncFunction("scope", `with (scope) { return await (${trimmed}); }`)(scope);
112
+ } catch (error) {
113
+ if (!(error instanceof SyntaxError)) throw error;
114
+ return await new AsyncFunction("scope", `with (scope) { return await (async () => {\n${trimmed}\n})(); }`)(scope);
115
+ }
116
+ };
117
+
118
+ const printHelp = (output: typeof process.stdout) => {
119
+ output.write(
120
+ [
121
+ "Akan console commands:",
122
+ " .help Show this help",
123
+ " .globals Show available global names",
124
+ " .exit Close the console",
125
+ "",
126
+ "Examples:",
127
+ " debug()",
128
+ ' methods(service("user"))',
129
+ ' await service("user").__count()',
130
+ ' userService = service("user")',
131
+ "",
132
+ ].join("\n"),
133
+ );
134
+ };
135
+
136
+ const formatValue = (value: unknown) => {
137
+ if (value === undefined) return "";
138
+ return `${inspect(value, { colors: true, depth: 5, maxArrayLength: 100 })}\n`;
139
+ };
140
+
141
+ export const startAkanConsole = async (server: AkanServer, options: AkanConsoleOptions = {}) => {
142
+ const input = options.input ?? process.stdin;
143
+ const output = options.output ?? process.stdout;
144
+ const context = createAkanConsoleContext(server, options.globals);
145
+ const prompt = options.prompt ?? `akan:${server.name}> `;
146
+ const rl = createInterface({ input, output, terminal: true });
147
+
148
+ output.write(`Akan console started for ${server.name}. Type .help for commands.\n`);
149
+ rl.setPrompt(prompt);
150
+ rl.prompt();
151
+
152
+ rl.on("SIGINT", () => {
153
+ output.write("\n");
154
+ rl.close();
155
+ });
156
+
157
+ for await (const line of rl) {
158
+ const trimmed = line.trim();
159
+ try {
160
+ if (!trimmed) {
161
+ rl.prompt();
162
+ continue;
163
+ }
164
+ if (trimmed === ".exit" || trimmed === ".quit") {
165
+ rl.close();
166
+ break;
167
+ }
168
+ if (trimmed === ".help") {
169
+ printHelp(output);
170
+ rl.prompt();
171
+ continue;
172
+ }
173
+ if (trimmed === ".globals") {
174
+ output.write(
175
+ `${Object.keys(context)
176
+ .sort((a, b) => a.localeCompare(b))
177
+ .join(", ")}\n`,
178
+ );
179
+ rl.prompt();
180
+ continue;
181
+ }
182
+
183
+ output.write(formatValue(await evaluateAkanConsoleInput(line, context)));
184
+ } catch (error) {
185
+ output.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
186
+ }
187
+ rl.prompt();
188
+ }
189
+ };
package/server/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./akanLib";
3
3
  export * from "./akanOption";
4
4
  export * from "./akanServer";
5
5
  export * from "./artifact";
6
+ export * from "./console";
6
7
  export * from "./decorators";
7
8
  export type { ChangeBatch, ChangeKind } from "./hmr/changeBatch";
8
9
  export * from "./processMetricsCollector";
@@ -8,6 +8,18 @@ export interface AkanServerProps extends AkanLibProps {
8
8
  prefix?: string;
9
9
  websocketPrefix?: string;
10
10
  }
11
+ export interface AkanServerConsoleInfo {
12
+ name: string;
13
+ status: AkanServer["status"];
14
+ serverMode: AkanServer["serverMode"];
15
+ env: Pick<BaseEnv, "appName" | "environment" | "operationMode" | "repoName" | "serveDomain" | "databaseMode">;
16
+ services: string[];
17
+ signals: string[];
18
+ adaptors: string[];
19
+ uses: string[];
20
+ serviceStages: string[][];
21
+ adaptorStages: string[][];
22
+ }
11
23
  export declare class AkanServer {
12
24
  #private;
13
25
  status: "stopped" | "initializing" | "initialized" | "starting" | "running" | "stopping";
@@ -34,6 +46,7 @@ export declare class AkanServer {
34
46
  getService<T = Service>(refName: string): T;
35
47
  getSignal<T = ServerSignal>(refName: string): T;
36
48
  getAdaptor<T = Adaptor>(refName: string): T;
49
+ inspectConsole(): AkanServerConsoleInfo;
37
50
  init({ routes: initRoutes, web }?: {
38
51
  routes?: boolean;
39
52
  web?: boolean;
@@ -0,0 +1,25 @@
1
+ import type { BaseEnv } from "akanjs/base";
2
+ import type { Adaptor, Service } from "akanjs/service";
3
+ import type { ServerSignal } from "akanjs/signal";
4
+ import type { AkanServer } from "./akanServer.d.ts";
5
+ export interface AkanConsoleOptions {
6
+ prompt?: string;
7
+ globals?: Record<string, unknown>;
8
+ input?: typeof process.stdin;
9
+ output?: typeof process.stdout;
10
+ }
11
+ export interface AkanConsoleContext extends Record<string, unknown> {
12
+ server: AkanServer;
13
+ env: AkanServer["env"];
14
+ get: AkanServer["get"];
15
+ service: <T = Service>(refName: string) => T;
16
+ signal: <T = ServerSignal>(refName: string) => T;
17
+ adaptor: <T = Adaptor>(refName: string) => T;
18
+ methods: (value: unknown) => string[];
19
+ debug: () => ReturnType<AkanServer["inspectConsole"]>;
20
+ }
21
+ export declare const assertAkanConsoleAllowed: (env?: Pick<BaseEnv, "environment" | "operationMode">) => void;
22
+ export declare const getAkanConsoleMethods: (value: unknown) => string[];
23
+ export declare const createAkanConsoleContext: (server: AkanServer, globals?: Record<string, unknown>) => AkanConsoleContext;
24
+ export declare const evaluateAkanConsoleInput: (source: string, context: Record<string, unknown>) => Promise<unknown>;
25
+ export declare const startAkanConsole: (server: AkanServer, options?: AkanConsoleOptions) => Promise<void>;
@@ -3,6 +3,7 @@ export * from "./akanLib.d.ts";
3
3
  export * from "./akanOption.d.ts";
4
4
  export * from "./akanServer.d.ts";
5
5
  export * from "./artifact.d.ts";
6
+ export * from "./console.d.ts";
6
7
  export * from "./decorators.d.ts";
7
8
  export type { ChangeBatch, ChangeKind } from "./hmr/changeBatch.d.ts";
8
9
  export * from "./processMetricsCollector.d.ts";