@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 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
- "name": "@lambdot/console",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "exports": {
6
- ".": "./src/index.ts"
7
- },
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "dependencies": {
12
- "@lambdot/core": "workspace:*"
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, EventDef, InputPlugin, OutputContract, OutputPlugin } from "@lambdot/core";
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 output half of the console
7
- * platform pair outputs consume addresses, inputs produce them.
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
- /** The console platform's output contract: plain text. */
14
- export type ConsoleOutputs = {
15
- console: OutputContract<ConsoleAddress, string>;
16
- };
14
+ /** One line read from stdin. */
15
+ export type ConsoleLine = Message<string, ConsoleAddress>;
17
16
 
18
- /**
19
- * Events produced by the console input: one per line read from stdin.
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: one `console.line` event per line read from stdin. */
29
- export function consoleInput(): InputPlugin<ConsoleEvents, void, "console-input"> {
30
- return {
31
- role: "input",
32
- name: "console-input",
33
- apply(ctx) {
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
- void ctx.ingest("console.line", line, { platform: "console", target: "stdout" });
28
+ lines.push(message(line, { platform: "console", target: "stdout" }));
37
29
  });
38
- return () => {
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: writes plain text to stdout or stderr. */
46
- export function consoleOutput(): OutputPlugin<
47
- "console",
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
- "console-output"
45
+ void,
46
+ "console/printer"
52
47
  > {
53
- return {
54
- role: "output",
55
- name: "console-output",
56
- platform: "console",
57
- send(to, content) {
58
- const stream = to.target === "stderr" ? process.stderr : process.stdout;
59
- stream.write(`${content}\n`);
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 input and stdout/stderr output
66
- * halves. The pair stays separate (rather than one fused plugin) so the
67
- * type fold can keep enforcing registration order input and output before
68
- * the feature plugins that consume them.
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.input)
74
- * .use(cli.output);
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 input: InputPlugin<ConsoleEvents, void, "console-input">;
79
- readonly output: OutputPlugin<"console", ConsoleAddress, string, void, "console-output">;
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 input + stdout/stderr output). */
84
+ /** Build a whole console platform (stdin lines + stdout/stderr printer). */
83
85
  export function consolePlatform(): ConsolePlatform {
84
86
  return {
85
- input: consoleInput(),
86
- output: consoleOutput(),
87
+ lines: consoleLines(),
88
+ printer: consolePrinter(),
87
89
  };
88
90
  }