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 +12 -0
- package/package.json +1 -1
- package/server/akanServer.ts +39 -3
- package/server/console.ts +189 -0
- package/server/index.ts +1 -0
- package/types/server/akanServer.d.ts +13 -0
- package/types/server/console.d.ts +25 -0
- package/types/server/index.d.ts +1 -0
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
package/server/akanServer.ts
CHANGED
|
@@ -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
|
|
252
|
-
const shouldListen = (listen ?? !
|
|
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 (!
|
|
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>;
|
package/types/server/index.d.ts
CHANGED
|
@@ -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";
|