Initial Commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# `control-channel`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Bidirectional control plane: the main process tells a worker-isolated
|
||||
plugin to stop or resume emitting log records. The host calls `disable`
|
||||
during plugin shutdown so a worker that is still draining its work
|
||||
queue does not keep producing records after the main-side sink has been
|
||||
torn down (design doc §5.5 — components dispose in reverse dependency
|
||||
order). Without this channel the main-side sink would either log to a
|
||||
zombie sink (silent loss with no diagnostic) or panic on a closed port.
|
||||
|
||||
Frames are simple: `{ kind: "control", action: "disable" | "enable" }`.
|
||||
There is no acknowledgement frame; the channel is fire-and-forget. The
|
||||
worker maintains a shared `ControlState` object the proxy client checks
|
||||
synchronously on every log call, so a `disable` received between two
|
||||
calls correctly suppresses the second.
|
||||
|
||||
## Features
|
||||
|
||||
- `createControlChannelSender(port)` — returns `{ disable, enable }`
|
||||
thunks usable from the main process.
|
||||
- `mountControlChannelReceiver(port, options?)` — installs a listener
|
||||
on the worker-side port; returns the shared `ControlState` plus a
|
||||
`dispose` thunk. Tolerates malformed frames (silent drop) so a
|
||||
hostile or buggy host cannot crash the worker.
|
||||
- `onChange` callback fires only on real transitions, not on repeated
|
||||
same-value frames.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
// Main side
|
||||
import { createControlChannelSender } from "@suaveplan/api-logger-worker-proxy/control-channel";
|
||||
const ctl = createControlChannelSender(mainPort);
|
||||
ctl.disable();
|
||||
// ... main-side sink torn down ...
|
||||
worker.terminate();
|
||||
```
|
||||
|
||||
```ts
|
||||
// Worker side
|
||||
import { mountControlChannelReceiver } from "@suaveplan/api-logger-worker-proxy/control-channel";
|
||||
const ctl = mountControlChannelReceiver(workerPort);
|
||||
// log.info() etc. read ctl.state.enabled synchronously
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Observe transitions to flush in-flight batches and surface a counter:
|
||||
|
||||
```ts
|
||||
import { mountControlChannelReceiver } from "@suaveplan/api-logger-worker-proxy/control-channel";
|
||||
|
||||
const ctl = mountControlChannelReceiver(port, {
|
||||
onChange: (enabled) => {
|
||||
if (!enabled) batcher.flush();
|
||||
meter.add(`logger_proxy.control_state.${enabled ? "on" : "off"}`, 1);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createControlChannelSender(port: PortLike): ControlChannelSender`
|
||||
— emits `{ v, kind: "control", action }` frames.
|
||||
- `mountControlChannelReceiver(port, options?)` —
|
||||
returns `{ state: Readonly<ControlState>, dispose }`.
|
||||
- `ControlState` — `{ enabled: boolean }`. Defaults to `true` so
|
||||
workers emit normally until told otherwise.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The receiver shares the port with `goodbye` and (incidentally) with the
|
||||
worker-side log emit path — frames whose `kind` is not `"control"` are
|
||||
ignored so the receiver composes cleanly with other listeners on the
|
||||
same port. The state object is mutated in place (not replaced) so the
|
||||
proxy client can capture a stable reference at construction time and
|
||||
not need to re-resolve a getter on the per-call hot path.
|
||||
Reference in New Issue
Block a user