@lambdot/console 0.1.0 → 0.2.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 +15 -0
- package/README.md +84 -0
- package/package.json +18 -13
- package/src/index.ts +54 -52
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`71e5732`](https://github.com/Embers-of-the-Fire/lambdot/commit/71e57321ad4ec7d1aef3651d104123f8167ec2e7), [`71e5732`](https://github.com/Embers-of-the-Fire/lambdot/commit/71e57321ad4ec7d1aef3651d104123f8167ec2e7)]:
|
|
8
|
+
- @lambdot/core@0.2.0
|
|
9
|
+
|
|
10
|
+
## 0.1.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- [#10](https://github.com/Embers-of-the-Fire/lambdot/pull/10) [`19d37e4`](https://github.com/Embers-of-the-Fire/lambdot/commit/19d37e42c7a5514fb62c8f31c65e1aa01916d355) Thanks [@Embers-of-the-Fire](https://github.com/Embers-of-the-Fire)! - Switch inter-package dependency pins from exact versions to `workspace:*` so workspace members always resolve against local sources during development; pnpm rewrites the protocol to exact versions at pack/publish time.
|
|
15
|
+
- Updated dependencies [[`19d37e4`](https://github.com/Embers-of-the-Fire/lambdot/commit/19d37e42c7a5514fb62c8f31c65e1aa01916d355)]:
|
|
16
|
+
- @lambdot/core@0.1.1
|
|
17
|
+
|
|
3
18
|
## [0.1.0](https://github.com/Embers-of-the-Fire/lambdot/compare/console-v0.0.1...console-v0.1.0) (2026-08-29)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @lambdot/console
|
|
2
|
+
|
|
3
|
+
The reference chat platform: a terminal needs no external service, so the
|
|
4
|
+
console is where the framework's contracts are exercised first. It exposes
|
|
5
|
+
the `consolePlatform` bundle — an input plugin that turns stdin lines into
|
|
6
|
+
a message stream and an output plugin that consumes a reply stream and
|
|
7
|
+
writes plain text back to the terminal.
|
|
8
|
+
|
|
9
|
+
## What it provides
|
|
10
|
+
|
|
11
|
+
- **A line stream.** `consoleLines()` emits a `Stream<ConsoleLine>`, where
|
|
12
|
+
`ConsoleLine` is `Message<string, ConsoleAddress>` — one message per line
|
|
13
|
+
read from stdin, payload the line text, minted with `message(line, ...)`.
|
|
14
|
+
The address is `{ platform: "console", target: "stdout" }`. The stream is
|
|
15
|
+
shared (broadcast), so several consumers may each subscribe to it.
|
|
16
|
+
- **An address type.** `ConsoleAddress extends Address<"console">` adds one
|
|
17
|
+
field, `target: "stdout" | "stderr"`. Replying to `message.address` writes
|
|
18
|
+
to stdout; constructing `{ platform: "console", target: "stderr" }`
|
|
19
|
+
redirects a reply to stderr.
|
|
20
|
+
- **A reply contract.** `ConsoleReply` is `Command<ConsoleAddress, string>`
|
|
21
|
+
— content is plain text, written with a trailing newline. The printer
|
|
22
|
+
consumes a `Stream<ConsoleReply>`; because streams broadcast, one reply
|
|
23
|
+
stream can feed the printer and a logger side by side.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { consolePlatform, type ConsoleLine } from "@lambdot/console";
|
|
29
|
+
import type { Stream } from "@lambdot/core";
|
|
30
|
+
import { createKernel, definePlugin, mapStream } from "@lambdot/core";
|
|
31
|
+
|
|
32
|
+
const echo = definePlugin({
|
|
33
|
+
name: "echo",
|
|
34
|
+
apply(input: { "console/lines": Stream<ConsoleLine> }) {
|
|
35
|
+
return mapStream(input["console/lines"], (event) => ({
|
|
36
|
+
address: event.address,
|
|
37
|
+
content: `echo: ${event.payload}`,
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const cli = consolePlatform();
|
|
43
|
+
|
|
44
|
+
const kernel = createKernel()
|
|
45
|
+
.use(cli.lines)
|
|
46
|
+
// identity wiring: echo's input keys already match the visible ctx
|
|
47
|
+
.use(echo)
|
|
48
|
+
.bind(cli.printer, { mapping: (ctx) => ({ replies: ctx.echo }) });
|
|
49
|
+
|
|
50
|
+
await kernel.start();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The printer is terminal — it consumes a reply stream produced by later
|
|
54
|
+
feature plugins — so it is wired last with an explicit `mapping` from the
|
|
55
|
+
feature's namespace (`ctx.echo`) to the printer's declared input
|
|
56
|
+
(`{ replies }`). Wiring the printer before the feature is a compile error:
|
|
57
|
+
the `mapping` parameter is typed as the namespaces visible so far.
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
- `consolePlatform(): ConsolePlatform` — builds both halves as
|
|
62
|
+
`{ lines, printer }`. The pair stays separate (rather than one fused
|
|
63
|
+
plugin) so feature plugins can be wired between them: the line source
|
|
64
|
+
first, the features next, the printer last.
|
|
65
|
+
- `consoleLines()` — the input half, a `Plugin<void, Stream<ConsoleLine>, void, "console/lines">`.
|
|
66
|
+
Reads stdin through `node:readline/promises` with `terminal: false` (line
|
|
67
|
+
mode, no TTY echo handling); its disposer closes the readline interface
|
|
68
|
+
and the stream.
|
|
69
|
+
- `consolePrinter()` — the output half, a
|
|
70
|
+
`Plugin<{ replies: Stream<ConsoleReply> }, void, void, "console/printer">`.
|
|
71
|
+
Pumps `replies` in the background; each command picks `process.stderr` or
|
|
72
|
+
`process.stdout` by `address.target` and writes `${content}\n`.
|
|
73
|
+
- Types: `ConsoleAddress`, `ConsoleLine`, `ConsoleReply`, `ConsolePlatform`.
|
|
74
|
+
|
|
75
|
+
## Examples
|
|
76
|
+
|
|
77
|
+
- [echo-bot](../../../examples/echo-bot) — the echo bot above, plus
|
|
78
|
+
compile-time type tests for the composition wiring.
|
|
79
|
+
- [multi-echo-bot](../../../examples/multi-echo-bot) — one echo feature
|
|
80
|
+
serving the console and a websocket platform side by side.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
Dual-licensed under [Apache-2.0](../../../LICENSE-APACHE) and [MIT](../../../LICENSE-MIT).
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
"name": "@lambdot/console",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/Embers-of-the-Fire/lambdot",
|
|
7
|
+
"directory": "packages/core/console"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@lambdot/core": "0.2.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,88 +1,90 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
|
|
3
|
-
import type { Address,
|
|
3
|
+
import type { Address, Command, Message, Plugin, Stream } from "@lambdot/core";
|
|
4
|
+
import { channel, definePlugin, message, pumpStream, shareStream } from "@lambdot/core";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Where a console message goes. Owned by the
|
|
7
|
-
* platform
|
|
7
|
+
* Where a console message goes. Owned by the printer half of the console
|
|
8
|
+
* platform — printers consume addresses, line sources produce them.
|
|
8
9
|
*/
|
|
9
10
|
export interface ConsoleAddress extends Address<"console"> {
|
|
10
11
|
readonly target: "stdout" | "stderr";
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
export type
|
|
15
|
-
console: OutputContract<ConsoleAddress, string>;
|
|
16
|
-
};
|
|
14
|
+
/** One line read from stdin. */
|
|
15
|
+
export type ConsoleLine = Message<string, ConsoleAddress>;
|
|
17
16
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
* A type alias (not an interface extending `EventMap`) so `keyof` stays
|
|
21
|
-
* exactly these kinds — an interface extending `Record` would inherit a
|
|
22
|
-
* string index signature and make every kind subscribable.
|
|
23
|
-
*/
|
|
24
|
-
export type ConsoleEvents = {
|
|
25
|
-
"console.line": EventDef<string, ConsoleAddress>;
|
|
26
|
-
};
|
|
17
|
+
/** One line to print: plain text to a console target. */
|
|
18
|
+
export type ConsoleReply = Command<ConsoleAddress, string>;
|
|
27
19
|
|
|
28
|
-
/** The input half of the console platform:
|
|
29
|
-
export function
|
|
30
|
-
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
/** The input half of the console platform: a stream of lines read from stdin. */
|
|
21
|
+
export function consoleLines(): Plugin<void, Stream<ConsoleLine>, void, "console/lines"> {
|
|
22
|
+
return definePlugin({
|
|
23
|
+
name: "console/lines",
|
|
24
|
+
apply(_input, scope) {
|
|
25
|
+
const lines = channel<ConsoleLine>();
|
|
34
26
|
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
35
27
|
rl.on("line", (line) => {
|
|
36
|
-
|
|
28
|
+
lines.push(message(line, { platform: "console", target: "stdout" }));
|
|
37
29
|
});
|
|
38
|
-
|
|
30
|
+
scope.onDispose(() => {
|
|
39
31
|
rl.close();
|
|
40
|
-
|
|
32
|
+
lines.close();
|
|
33
|
+
});
|
|
34
|
+
// Shared: several consumers (features, loggers, supervisors) may
|
|
35
|
+
// each subscribe to the line stream.
|
|
36
|
+
return shareStream(lines.stream);
|
|
41
37
|
},
|
|
42
|
-
};
|
|
38
|
+
});
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
/** The output half of the console platform:
|
|
46
|
-
export function
|
|
47
|
-
|
|
48
|
-
ConsoleAddress,
|
|
49
|
-
string,
|
|
41
|
+
/** The output half of the console platform: prints a reply stream to stdout/stderr. */
|
|
42
|
+
export function consolePrinter(): Plugin<
|
|
43
|
+
{ replies: Stream<ConsoleReply> },
|
|
50
44
|
void,
|
|
51
|
-
|
|
45
|
+
void,
|
|
46
|
+
"console/printer"
|
|
52
47
|
> {
|
|
53
|
-
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
return definePlugin({
|
|
49
|
+
name: "console/printer",
|
|
50
|
+
apply(input, scope) {
|
|
51
|
+
scope.onDispose(
|
|
52
|
+
pumpStream(
|
|
53
|
+
input.replies,
|
|
54
|
+
({ address, content }) => {
|
|
55
|
+
const stream =
|
|
56
|
+
address.target === "stderr" ? process.stderr : process.stdout;
|
|
57
|
+
stream.write(`${content}\n`);
|
|
58
|
+
},
|
|
59
|
+
(error) => scope.onError(error),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
60
62
|
},
|
|
61
|
-
};
|
|
63
|
+
});
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
|
-
* One console platform, bundled: the stdin
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
*
|
|
67
|
+
* One console platform, bundled: the stdin line source and the stdout/stderr
|
|
68
|
+
* printer. The printer is terminal — it consumes a reply stream produced by
|
|
69
|
+
* later feature plugins, so it is wired last with a mapping:
|
|
70
|
+
|
|
70
71
|
* ```ts
|
|
71
72
|
* const cli = consolePlatform();
|
|
72
73
|
* createKernel()
|
|
73
|
-
* .use(cli.
|
|
74
|
-
* .use(
|
|
74
|
+
* .use(cli.lines)
|
|
75
|
+
* .use(echo)
|
|
76
|
+
* .use(cli.printer, { mapping: (ctx) => ({ replies: ctx.echo }) });
|
|
75
77
|
* ```
|
|
76
78
|
*/
|
|
77
79
|
export interface ConsolePlatform {
|
|
78
|
-
readonly
|
|
79
|
-
readonly
|
|
80
|
+
readonly lines: Plugin<void, Stream<ConsoleLine>, void, "console/lines">;
|
|
81
|
+
readonly printer: Plugin<{ replies: Stream<ConsoleReply> }, void, void, "console/printer">;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
/** Build a whole console platform (stdin
|
|
84
|
+
/** Build a whole console platform (stdin lines + stdout/stderr printer). */
|
|
83
85
|
export function consolePlatform(): ConsolePlatform {
|
|
84
86
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
lines: consoleLines(),
|
|
88
|
+
printer: consolePrinter(),
|
|
87
89
|
};
|
|
88
90
|
}
|