@sharpee/channel-service 1.0.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.
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ /**
3
+ * @sharpee/channel-service/renderer — `Renderer` host implementation.
4
+ *
5
+ * Owner context: consumer-side dispatcher. Drives `CmgtPacket` and
6
+ * `TurnPacket` instances into registered `ChannelRenderer` plug-ins,
7
+ * holds the channel state store, and pumps commands back to the
8
+ * engine.
9
+ *
10
+ * Public interface (per ADR-165 §2, §3, §4, §5, §7):
11
+ * - `Renderer` class implementing the same-named interface.
12
+ * - `createRenderer(opts)` factory for ergonomic instantiation.
13
+ *
14
+ * @see ADR-165 — Renderer Architecture
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Renderer = void 0;
18
+ exports.createRenderer = createRenderer;
19
+ const json_tree_fallback_1 = require("./json-tree-fallback");
20
+ /**
21
+ * Concrete `Renderer` host (ADR-165 §2).
22
+ *
23
+ * One instance per session. Holds:
24
+ * - the registered `ChannelRenderer`s (last-write-wins per channel id),
25
+ * - the channel state store (replace-latest, append-accumulating,
26
+ * event-no-entry — ADR-165 §5),
27
+ * - the slot handles (`getSlot` / `registerSlot` per §7),
28
+ * - the current `CmgtPacket` (so `onValue` can pass the matching
29
+ * `ChannelDefinition` to the renderer),
30
+ * - the command-handler subscriber list.
31
+ *
32
+ * No DOM, no transport, no engine. Concrete consumers compose this
33
+ * with their host platform.
34
+ */
35
+ class Renderer {
36
+ renderers = new Map();
37
+ fallbackRenderers = new Map();
38
+ state = {};
39
+ slots = new Map();
40
+ commandHandlers = new Set();
41
+ currentManifest;
42
+ warn;
43
+ buildFallback;
44
+ constructor(opts = {}) {
45
+ this.warn = opts.warn ?? ((m) => {
46
+ // eslint-disable-next-line no-console
47
+ console.warn(m);
48
+ });
49
+ this.buildFallback = (0, json_tree_fallback_1.createJsonTreeFallbackFactory)({
50
+ warn: opts.fallbackWarn ?? this.warn,
51
+ output: opts.fallbackOutput,
52
+ });
53
+ }
54
+ // ────────────────────────────────────────────────────────────────
55
+ // Registration
56
+ // ────────────────────────────────────────────────────────────────
57
+ /**
58
+ * Register a `ChannelRenderer` (ADR-165 §3). Last-write-wins —
59
+ * re-registering replaces the prior renderer for that id.
60
+ */
61
+ registerRenderer(channelId, renderer) {
62
+ this.renderers.set(channelId, renderer);
63
+ }
64
+ /**
65
+ * Register a slot (ADR-165 §7). Stories that replace the
66
+ * platform-default layout call this for each region.
67
+ */
68
+ registerSlot(name, handle) {
69
+ this.slots.set(name, handle);
70
+ }
71
+ /**
72
+ * Resolve a slot handle, or `null` if not registered.
73
+ */
74
+ getSlot(name) {
75
+ return this.slots.has(name) ? this.slots.get(name) : null;
76
+ }
77
+ // ────────────────────────────────────────────────────────────────
78
+ // Command pump
79
+ // ────────────────────────────────────────────────────────────────
80
+ /**
81
+ * Subscribe to commands emitted by channel renderers.
82
+ */
83
+ onCommand(handler) {
84
+ this.commandHandlers.add(handler);
85
+ }
86
+ /**
87
+ * Emit a command from a `ChannelRenderer` (UI-gesture origin).
88
+ * All registered handlers receive the packet synchronously.
89
+ */
90
+ emitCommand(text) {
91
+ const packet = { kind: 'command', text };
92
+ for (const handler of this.commandHandlers) {
93
+ handler(packet);
94
+ }
95
+ }
96
+ // ────────────────────────────────────────────────────────────────
97
+ // Lifecycle
98
+ // ────────────────────────────────────────────────────────────────
99
+ /**
100
+ * Apply a CMGT packet (ADR-165 §4 lifecycle).
101
+ *
102
+ * 1. If a prior manifest exists, run `onDestroy` for each of its
103
+ * renderers (in registration order for determinism).
104
+ * 2. Reset the state store.
105
+ * 3. Update the current manifest.
106
+ * 4. Run `onCmgt` for each new-manifest renderer in registration
107
+ * order.
108
+ */
109
+ applyCmgt(packet) {
110
+ if (this.currentManifest) {
111
+ for (const channel of this.currentManifest.channels) {
112
+ const r = this.renderers.get(channel.id) ?? this.fallbackRenderers.get(channel.id);
113
+ r?.onDestroy?.();
114
+ }
115
+ }
116
+ this.state = {};
117
+ this.currentManifest = packet;
118
+ // Reset fallback cache so a re-mounted session re-issues the
119
+ // one-time warning if a channel still lacks a renderer.
120
+ this.fallbackRenderers.clear();
121
+ for (const channel of packet.channels) {
122
+ const r = this.resolveRenderer(channel.id);
123
+ r.onCmgt?.(channel, packet);
124
+ }
125
+ }
126
+ /**
127
+ * Dispatch a turn packet (ADR-165 §4 dispatch contract).
128
+ *
129
+ * Iterates the current manifest in registration order. For each
130
+ * manifest entry that has a payload value, updates the state
131
+ * store per mode and invokes `onValue`. Handles the `clear`
132
+ * channel specially — empties append-mode state stores and fires
133
+ * `onClear` hooks.
134
+ */
135
+ applyTurnPacket(packet) {
136
+ if (!this.currentManifest) {
137
+ this.warn('[channel-service] applyTurnPacket called before applyCmgt; ' +
138
+ 'turn packet ignored. Bootstrap order: cmgt before turn (ADR-163 §11).');
139
+ return;
140
+ }
141
+ // Check for a `clear` event in this turn packet — handle it
142
+ // first so subsequent main/append channel emissions land in an
143
+ // emptied store.
144
+ const clearValue = packet.payload['clear'];
145
+ if (clearValue !== undefined) {
146
+ this.handleClear(clearValue);
147
+ }
148
+ // Walk the manifest in registration order. The payload's own
149
+ // key order is irrelevant — manifest order is the contract.
150
+ for (const channel of this.currentManifest.channels) {
151
+ // The clear channel itself was already handled above; let
152
+ // its renderer fire normally too (visual side-effects).
153
+ if (!Object.prototype.hasOwnProperty.call(packet.payload, channel.id)) {
154
+ continue;
155
+ }
156
+ const value = packet.payload[channel.id];
157
+ this.applyChannelValue(channel, value);
158
+ }
159
+ }
160
+ /**
161
+ * Snapshot of the channel state store (deep-cloned) — for tests
162
+ * and ADR-163 AC-12 round-trip.
163
+ */
164
+ getStateSnapshot() {
165
+ return deepClone(this.state);
166
+ }
167
+ // ────────────────────────────────────────────────────────────────
168
+ // Private — dispatch helpers
169
+ // ────────────────────────────────────────────────────────────────
170
+ /**
171
+ * Update the state store per mode and invoke the renderer's
172
+ * `onValue`.
173
+ */
174
+ applyChannelValue(channel, value) {
175
+ switch (channel.mode) {
176
+ case 'replace': {
177
+ this.state[channel.id] = value;
178
+ break;
179
+ }
180
+ case 'append': {
181
+ const entries = Array.isArray(value) ? value : [value];
182
+ const existing = this.state[channel.id];
183
+ const accumulator = Array.isArray(existing) ? existing : [];
184
+ this.state[channel.id] = accumulator.concat(entries);
185
+ break;
186
+ }
187
+ case 'event': {
188
+ // Event channels do NOT write to the state store (ADR-165 §5).
189
+ break;
190
+ }
191
+ }
192
+ const renderer = this.resolveRenderer(channel.id);
193
+ renderer.onValue(value, channel);
194
+ }
195
+ /**
196
+ * Handle a `clear` channel emission (ADR-165 §4 step 2).
197
+ *
198
+ * `value.target` (when set) names a specific append-mode channel
199
+ * to clear; an empty / missing target clears every append-mode
200
+ * channel currently registered in the manifest.
201
+ */
202
+ handleClear(value) {
203
+ if (!this.currentManifest)
204
+ return;
205
+ const target = value && typeof value === 'object' && 'target' in value
206
+ ? value.target
207
+ : undefined;
208
+ for (const channel of this.currentManifest.channels) {
209
+ if (channel.mode !== 'append')
210
+ continue;
211
+ if (target !== undefined && target !== '' && channel.id !== target)
212
+ continue;
213
+ // Reset state store entry first (ADR-165 §4 says state is
214
+ // reset BEFORE onClear runs).
215
+ delete this.state[channel.id];
216
+ const renderer = this.renderers.get(channel.id);
217
+ renderer?.onClear?.(target ?? channel.id);
218
+ }
219
+ }
220
+ /**
221
+ * Resolve the renderer for a channel id. Returns the registered
222
+ * renderer when present; otherwise lazily creates and caches a
223
+ * JSON-tree fallback (so the one-time warning fires once per
224
+ * channel id, not once per emission).
225
+ */
226
+ resolveRenderer(channelId) {
227
+ const registered = this.renderers.get(channelId);
228
+ if (registered)
229
+ return registered;
230
+ let fallback = this.fallbackRenderers.get(channelId);
231
+ if (!fallback) {
232
+ fallback = this.buildFallback(channelId);
233
+ this.fallbackRenderers.set(channelId, fallback);
234
+ }
235
+ return fallback;
236
+ }
237
+ }
238
+ exports.Renderer = Renderer;
239
+ /**
240
+ * Convenience factory for `new Renderer(opts)`.
241
+ */
242
+ function createRenderer(opts = {}) {
243
+ return new Renderer(opts);
244
+ }
245
+ /**
246
+ * Deep-clone helper for `getStateSnapshot`. Uses `structuredClone`
247
+ * when available; falls back to `JSON.parse(JSON.stringify(...))`
248
+ * for compatibility with older runtimes.
249
+ */
250
+ function deepClone(value) {
251
+ if (typeof globalThis.structuredClone === 'function') {
252
+ return globalThis.structuredClone(value);
253
+ }
254
+ try {
255
+ return JSON.parse(JSON.stringify(value));
256
+ }
257
+ catch {
258
+ // Last resort — return shallow copy if value cannot round-trip.
259
+ return Array.isArray(value) ? [...value] : { ...value };
260
+ }
261
+ }
262
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../../../../../../repos/sharpee/packages/channel-service/src/renderer/renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * @sharpee/channel-service/renderer — consumer-side type contracts.
3
+ *
4
+ * Owner context: consumer side of channel-I/O. Defines the
5
+ * `ChannelRenderer` plug-in shape, the `Renderer` host that drives
6
+ * packets into renderers, and the small `SlotHandle` abstraction the
7
+ * default layouts use to address output regions.
8
+ *
9
+ * Public interface (per ADR-165 §1, §2, §5, §7):
10
+ * - `ChannelRenderer` — per-channel rendering contract; only
11
+ * `onValue` is required.
12
+ * - `Renderer` — top-level host the consumer drives.
13
+ * - `ChannelStateStore` — in-memory state per channel id (replace
14
+ * keeps latest, append accumulates, event has no entry).
15
+ * - `SlotHandle` — opaque handle the default layouts use; concrete
16
+ * consumers narrow to `HTMLElement` (DOM), terminal region (CLI),
17
+ * or whatever their output medium uses.
18
+ *
19
+ * No implementation lives here. Pure types so renderer-host packages
20
+ * (browser, CLI, multi-user) and channel-renderer authors share a
21
+ * single contract.
22
+ *
23
+ * @see ADR-165 — Renderer Architecture
24
+ */
25
+ import type { ChannelDefinition, CmgtPacket, CommandPacket, TurnPacket } from "../../if-domain/index";
26
+ /**
27
+ * Per-channel rendering logic (ADR-165 §1).
28
+ *
29
+ * A `ChannelRenderer` is registered against a channel id via
30
+ * `Renderer.registerRenderer(channelId, renderer)`. The dispatcher
31
+ * invokes `onValue` once per emission of that channel in a turn
32
+ * packet; optional hooks fire on lifecycle transitions.
33
+ *
34
+ * The interface is deliberately small. Cross-channel logic (a
35
+ * status-bar renderer reading both `location` and `score`) lives in
36
+ * the host module, not inside individual `ChannelRenderer` instances.
37
+ */
38
+ export interface ChannelRenderer {
39
+ /**
40
+ * Required. Called once per emission of this channel in a turn
41
+ * packet. The shape of `value` mirrors the channel's mode:
42
+ *
43
+ * - **replace-mode** — the latest scalar value (or `null` for
44
+ * hide / stop signals per ADR-163 §6).
45
+ * - **append-mode** — an array of new entries this turn (per
46
+ * ADR-163 §5; the renderer accumulates across turns).
47
+ * - **event-mode** — the event payload.
48
+ *
49
+ * `channel` is the manifest entry — useful for renderers that
50
+ * decide formatting based on `contentType`.
51
+ */
52
+ onValue(value: unknown, channel: ChannelDefinition): void;
53
+ /**
54
+ * Optional. Append-mode only. Invoked by the dispatcher when a
55
+ * `clear` event with a matching `target` arrives. The renderer
56
+ * is responsible for clearing its rendered output (DOM container,
57
+ * terminal region). The state store is reset by the dispatcher
58
+ * before this hook fires.
59
+ */
60
+ onClear?(target: string): void;
61
+ /**
62
+ * Optional. Invoked when CMGT is applied. Use for one-time setup
63
+ * (DOM scaffolding, asset preload, audio context init). NOT for
64
+ * state restoration — that comes via `applyTurnPacket` replays.
65
+ *
66
+ * On the first `applyCmgt` of a `Renderer`'s lifetime this is the
67
+ * only setup hook called. On subsequent invocations, `onDestroy`
68
+ * is called first, then `onCmgt` is called again to set up a
69
+ * fresh session.
70
+ */
71
+ onCmgt?(channel: ChannelDefinition, manifest: CmgtPacket): void;
72
+ /**
73
+ * Optional. Invoked when a fresh `applyCmgt` is about to reset
74
+ * this `Renderer`. Releases resources allocated in `onCmgt` (Web
75
+ * Audio context, IntersectionObserver, etc.). Symmetric with
76
+ * `onCmgt`. Not called when the host platform itself is shutting
77
+ * down (process exit, page unload) — the platform handles that.
78
+ */
79
+ onDestroy?(): void;
80
+ }
81
+ /**
82
+ * Channel state store shape (ADR-165 §5).
83
+ *
84
+ * Replace channels store the latest scalar (or `null`); append
85
+ * channels store an accumulated `unknown[]`; event channels and
86
+ * never-emitted channels have no entry.
87
+ *
88
+ * The dispatcher owns mutation. Renderers read; they do not write.
89
+ */
90
+ export interface ChannelStateStore {
91
+ [channelId: string]: unknown | unknown[] | undefined;
92
+ }
93
+ /**
94
+ * Opaque handle to a layout slot (ADR-165 §7).
95
+ *
96
+ * Concrete renderer hosts narrow to their output medium:
97
+ * - browser: `HTMLElement`
98
+ * - CLI: a small `{ write, clear }` adapter
99
+ * - canvas: a draw-group reference
100
+ *
101
+ * The `Renderer` itself is medium-agnostic — it stores and returns
102
+ * whatever the host registers.
103
+ */
104
+ export type SlotHandle = unknown;
105
+ /**
106
+ * Top-level renderer handle the consumer drives (ADR-165 §2).
107
+ *
108
+ * One instance per session. The consumer:
109
+ * 1. Constructs a `Renderer`.
110
+ * 2. Calls `registerRenderer(channelId, renderer)` for each
111
+ * channel it wants to display (platform defaults register
112
+ * during platform boot; stories register during story init).
113
+ * 3. Calls `applyCmgt(cmgt)` once when the engine emits the
114
+ * manifest.
115
+ * 4. Calls `applyTurnPacket(packet)` per turn the engine emits.
116
+ * 5. Subscribes to `onCommand(handler)` to feed commands back.
117
+ */
118
+ export interface Renderer {
119
+ /**
120
+ * Apply a CMGT packet (ADR-165 §4).
121
+ *
122
+ * On second-and-subsequent invocations:
123
+ * 1. Runs `onDestroy` for every renderer in the prior manifest.
124
+ * 2. Resets every channel state store.
125
+ * 3. Updates the current manifest.
126
+ * 4. Runs `onCmgt` for every renderer in the new manifest, in
127
+ * manifest order.
128
+ *
129
+ * On the first invocation, step 1 is skipped.
130
+ */
131
+ applyCmgt(packet: CmgtPacket): void;
132
+ /**
133
+ * Dispatch a turn packet (ADR-165 §4). Iterates the current
134
+ * manifest in registration order; for each manifest entry that
135
+ * has a payload value, updates the state store and invokes the
136
+ * registered renderer's `onValue`. `clear` events trigger the
137
+ * append-channel truncation path.
138
+ *
139
+ * Synchronous: every dispatched callback completes before the
140
+ * call returns.
141
+ */
142
+ applyTurnPacket(packet: TurnPacket): void;
143
+ /**
144
+ * Register a `ChannelRenderer` against a channel id. Last-write-
145
+ * wins per ADR-165 §3 — re-registering with the same id replaces
146
+ * the prior renderer. Platform-default renderers register first;
147
+ * story overrides replace them.
148
+ */
149
+ registerRenderer(channelId: string, renderer: ChannelRenderer): void;
150
+ /**
151
+ * Subscribe to `CommandPacket`s emitted by channel renderers.
152
+ * The consumer's host loop pumps these back to the engine.
153
+ * Multiple subscribers all receive each emission.
154
+ */
155
+ onCommand(handler: (cmd: CommandPacket) => void): void;
156
+ /**
157
+ * Emit a `CommandPacket`. Channel renderers call this when a UI
158
+ * gesture (hotspot click, drag-drop, custom widget) should
159
+ * advance the engine (ADR-163 §10).
160
+ */
161
+ emitCommand(text: string): void;
162
+ /**
163
+ * Register a slot (ADR-165 §7). Stories that replace the
164
+ * platform-default layout call this for each region of their
165
+ * custom layout.
166
+ */
167
+ registerSlot(name: string, handle: SlotHandle): void;
168
+ /**
169
+ * Resolve a slot handle, or `null` if the slot is not registered.
170
+ */
171
+ getSlot(name: string): SlotHandle | null;
172
+ /**
173
+ * Snapshot of the channel state store for testing and AC-7
174
+ * (re-emission identity round-trip). Returns a deep-cloned copy;
175
+ * the caller may mutate freely.
176
+ */
177
+ getStateSnapshot(): ChannelStateStore;
178
+ }
179
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../repos/sharpee/packages/channel-service/src/renderer/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,UAAU,EACX,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE1D;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B;;;;;;;;;OASG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IAEhE;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,IAAI,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC;CACtD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAEpC;;;;;;;;;OASG;IACH,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAE1C;;;;;OAKG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAErE;;;;OAIG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI,CAAC;IAEvD;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC;;;;OAIG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAErD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAEzC;;;;OAIG;IACH,gBAAgB,IAAI,iBAAiB,CAAC;CACvC"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * @sharpee/channel-service/renderer — consumer-side type contracts.
4
+ *
5
+ * Owner context: consumer side of channel-I/O. Defines the
6
+ * `ChannelRenderer` plug-in shape, the `Renderer` host that drives
7
+ * packets into renderers, and the small `SlotHandle` abstraction the
8
+ * default layouts use to address output regions.
9
+ *
10
+ * Public interface (per ADR-165 §1, §2, §5, §7):
11
+ * - `ChannelRenderer` — per-channel rendering contract; only
12
+ * `onValue` is required.
13
+ * - `Renderer` — top-level host the consumer drives.
14
+ * - `ChannelStateStore` — in-memory state per channel id (replace
15
+ * keeps latest, append accumulates, event has no entry).
16
+ * - `SlotHandle` — opaque handle the default layouts use; concrete
17
+ * consumers narrow to `HTMLElement` (DOM), terminal region (CLI),
18
+ * or whatever their output medium uses.
19
+ *
20
+ * No implementation lives here. Pure types so renderer-host packages
21
+ * (browser, CLI, multi-user) and channel-renderer authors share a
22
+ * single contract.
23
+ *
24
+ * @see ADR-165 — Renderer Architecture
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../../repos/sharpee/packages/channel-service/src/renderer/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @sharpee/channel-service/utils — text-content flattening helper.
3
+ *
4
+ * Owner context: platform package. Small utility used by consumers
5
+ * (renderers, debug logs, custom extractors) that need to project an
6
+ * `ITextBlock`'s decorated content tree into a plain string.
7
+ *
8
+ * Public interface:
9
+ * - `flattenContent(content)` — recursive concat of string nodes,
10
+ * stripping decoration wrappers (preserving their inner content).
11
+ *
12
+ * Originally lived inside `platform-rules.ts`; extracted here as part
13
+ * of the ADR-163 R1 rewrite (rule machinery removed; this helper kept
14
+ * because consumers may still want a one-liner string projection).
15
+ *
16
+ * @see ADR-163 — Channel-Service Platform — §6, §14
17
+ */
18
+ import type { TextContent } from "../../text-blocks/index";
19
+ /**
20
+ * Flatten a `TextContent` array to a plain string.
21
+ *
22
+ * Recursively concatenates string nodes and strips decoration wrappers
23
+ * (preserving their inner content). Side-effect-free; safe to call
24
+ * inside producer closures.
25
+ */
26
+ export declare function flattenContent(content: ReadonlyArray<TextContent>): string;
27
+ //# sourceMappingURL=flatten.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../../../../../repos/sharpee/packages/channel-service/src/utils/flatten.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAU1E"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * @sharpee/channel-service/utils — text-content flattening helper.
4
+ *
5
+ * Owner context: platform package. Small utility used by consumers
6
+ * (renderers, debug logs, custom extractors) that need to project an
7
+ * `ITextBlock`'s decorated content tree into a plain string.
8
+ *
9
+ * Public interface:
10
+ * - `flattenContent(content)` — recursive concat of string nodes,
11
+ * stripping decoration wrappers (preserving their inner content).
12
+ *
13
+ * Originally lived inside `platform-rules.ts`; extracted here as part
14
+ * of the ADR-163 R1 rewrite (rule machinery removed; this helper kept
15
+ * because consumers may still want a one-liner string projection).
16
+ *
17
+ * @see ADR-163 — Channel-Service Platform — §6, §14
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.flattenContent = flattenContent;
21
+ /**
22
+ * Flatten a `TextContent` array to a plain string.
23
+ *
24
+ * Recursively concatenates string nodes and strips decoration wrappers
25
+ * (preserving their inner content). Side-effect-free; safe to call
26
+ * inside producer closures.
27
+ */
28
+ function flattenContent(content) {
29
+ let out = '';
30
+ for (const node of content) {
31
+ if (typeof node === 'string') {
32
+ out += node;
33
+ }
34
+ else {
35
+ out += flattenContent(node.content);
36
+ }
37
+ }
38
+ return out;
39
+ }
40
+ //# sourceMappingURL=flatten.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flatten.js","sourceRoot":"","sources":["../../../../../../../repos/sharpee/packages/channel-service/src/utils/flatten.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;AAWH,wCAUC;AAjBD;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,OAAmC;IAChE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,GAAG,IAAI,IAAI,CAAC;QACd,CAAC;aAAM,CAAC;YACN,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @sharpee/channel-service/wire — client-side decoder
3
+ *
4
+ * Owner context: wire-protocol module. A small state machine the
5
+ * consumer feeds incoming packets through. Enforces the ordering
6
+ * invariants from ADR-163 §11 / AC-11:
7
+ *
8
+ * 1. The first server-bound packet a consumer accepts is `cmgt`.
9
+ * 2. `turn` packets are accepted only after `cmgt`.
10
+ * 3. `hello` and `command` are not server-bound — receiving one is a
11
+ * protocol error.
12
+ *
13
+ * The decoder does NOT render — it only validates ordering and exposes
14
+ * the negotiated manifest. Renderer dispatch is the consumer's
15
+ * concern (per ADR-165).
16
+ *
17
+ * @see ADR-163 §11 — bootstrap order invariants
18
+ */
19
+ import type { CmgtPacket, TurnPacket, WirePacket } from "../../if-domain/index";
20
+ /**
21
+ * State exposed by the decoder after each `ingest` call.
22
+ *
23
+ * - `'awaiting-cmgt'` — initial state. Consumer has dispatched a hello
24
+ * and is awaiting the server's CMGT manifest.
25
+ * - `'live'` — CMGT received; turn packets are now accepted.
26
+ * - `'error'` — protocol violation. The decoder does not recover; the
27
+ * consumer must drop the connection (or, in single-bundle, surface
28
+ * the error and reset the producer).
29
+ */
30
+ export type DecoderState = {
31
+ readonly status: 'awaiting-cmgt';
32
+ } | {
33
+ readonly status: 'live';
34
+ readonly cmgt: CmgtPacket;
35
+ } | {
36
+ readonly status: 'error';
37
+ readonly reason: string;
38
+ };
39
+ /**
40
+ * Decoder handle. The consumer reads `state` after each `ingest`.
41
+ * `lastTurn` is set when a turn is accepted and cleared when the
42
+ * decoder transitions to `error`.
43
+ */
44
+ export interface Decoder {
45
+ readonly state: DecoderState;
46
+ /**
47
+ * Most recently accepted `turn` packet, or `null` if none has been
48
+ * accepted in the current session. Reset on every `ingest`.
49
+ */
50
+ readonly lastTurn: TurnPacket | null;
51
+ /**
52
+ * Feed a packet to the decoder. After this returns, `state` reflects
53
+ * the new state. Returns the same handle for chaining.
54
+ */
55
+ ingest(packet: WirePacket): Decoder;
56
+ }
57
+ /**
58
+ * Create a fresh client-side decoder in the `awaiting-cmgt` state.
59
+ */
60
+ export declare function createDecoder(): Decoder;
61
+ //# sourceMappingURL=decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decoder.d.ts","sourceRoot":"","sources":["../../../../../../repos/sharpee/packages/channel-service/src/wire/decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE7E;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACpC;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;CACrC;AAoDD;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}