@orkestrel/terminal 0.0.14 → 0.0.16

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/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # @orkestrel/terminal
2
2
 
3
- The terminal side of a form, for the `@orkestrel` line. `@orkestrel/form` owns
4
- the document — the schema, the controls, the rules, the values, and the
5
- settle-once `answer` promise. This package owns what form has no opinion about:
6
- a key decoder, a theme, pure per-field reducers, and the surfaces one form
7
- can be answered on. The server `Terminal` implements the one driving contract
8
- against a real TTY (raw-mode stdin, live in-place re-render, a `node:readline`
9
- fallback when piped). The headless `Prompt` broker PARKS a live form until
10
- somebody elsewhere answers it. The `PromptClient` bridge carries a form parked
11
- elsewhere to this machine's keyboard over SSE. Built beside
3
+ > The terminal side of a form: a key decoder, a presentation theme, the pure per-field reducers,
4
+ > the headless broker that parks a live form until somebody elsewhere answers it, the SSE bridge
5
+ > that carries a parked form to a machine with a keyboard, and the manager that routes parked
6
+ > forms between named endpoints.
7
+
8
+ `@orkestrel/form` owns the document — the schema, the controls, the rules,
9
+ the values, and the settle-once `answer` promise — and this package declares
10
+ none of it a second time. Part of the `@orkestrel` line, built beside
12
11
  `@orkestrel/console` (the shared style engine), `@orkestrel/contract`,
13
12
  `@orkestrel/emitter`, `@orkestrel/database`, and `@orkestrel/sse`.
14
13
 
@@ -42,7 +41,7 @@ const values = await terminal.ask(
42
41
  )
43
42
  ```
44
43
 
45
- A bare return binds ABSENCE, not the empty string, so `required` refuses it and
44
+ A bare return binds absence, not the empty string, so `required` refuses it and
46
45
  the walk asks again.
47
46
 
48
47
  Park a live form and answer it from anywhere else:
@@ -56,7 +55,7 @@ prompt.emitter.on('pending', (form) => send(form)) // the wire-safe record
56
55
 
57
56
  const form = createForm({ fields: [{ control: 'text', name: 'name' }] })
58
57
  const id = prompt.park(form) // the id; you await the form's own `answer`
59
- prompt.answer(id, { name: 'Ada' }) // fills and submits the AUTHORITATIVE form
58
+ prompt.answer(id, { name: 'Ada' }) // fills and submits the authoritative form
60
59
  const values = await form.answer // { name: 'Ada' }
61
60
  ```
62
61
 
@@ -36,10 +36,11 @@ var CTRL_E = String.fromCharCode(5);
36
36
  */
37
37
  var KEY_SS3 = `${_orkestrel_console.ESC}O`;
38
38
  /**
39
- * Holds the exact escape SEQUENCE → canonical key NAME table {@link import('./helpers.js').parseKey}
40
- * consults for the navigation / editing keys. Covers BOTH the CSI form (`ESC[A`…) and the SS3
41
- * form (`ESCOA`…) of the arrows, plus the `home` / `end` / `delete` CSI sequences (with
42
- * their numeric-tilde variants). The source of truth for the multi-byte key decode; frozen.
39
+ * Holds the exact escape sequence to canonical key name table
40
+ * {@link import('./helpers.js').parseKey} consults for the navigation and editing keys. Covers the
41
+ * CSI form (`ESC[A`…) and the SS3 form (`ESCOA`…) of the arrows, plus the `home` / `end` / `delete`
42
+ * CSI sequences with their numeric-tilde variants. The source of truth for the multi-byte key
43
+ * decode; frozen.
43
44
  *
44
45
  * @remarks
45
46
  * Terminals disagree on these: a cursor key is `ESC[A` (normal) or `ESCOA` (application mode),
@@ -65,10 +66,10 @@ var SEQUENCE_NAMES = Object.freeze({
65
66
  [`${_orkestrel_console.CSI}8~`]: "end"
66
67
  });
67
68
  /**
68
- * Holds the control BYTE (or CRLF pair) → key descriptor table {@link import('./helpers.js').parseKey}
69
- * consults for the one-byte keys and the two-byte CRLF Enter chunk. Each entry carries the
70
- * canonical `name` and whether it is a `ctrl` combination. The source of truth for that decode;
71
- * frozen.
69
+ * Holds the control byte (or CRLF pair) to key descriptor table
70
+ * {@link import('./helpers.js').parseKey} consults for the one-byte keys and the two-byte CRLF
71
+ * Enter chunk. Each entry carries the canonical `name` and whether it is a `ctrl` combination. The
72
+ * source of truth for that decode; frozen.
72
73
  *
73
74
  * @remarks
74
75
  * `return` / `newline` / `return + newline` all map to `return` (one canonical Enter name) — a
@@ -135,8 +136,9 @@ var CONTROL_NAMES = Object.freeze({
135
136
  /** Names the default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
136
137
  var DEFAULT_MASK = "*";
137
138
  /**
138
- * Holds the default glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from. Read only when
139
- * the default theme is assembled; a view reads its resolved theme and never this constant. Frozen.
139
+ * Holds the terminal-owned glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from, beside
140
+ * the console module's own success and error marks. Read only when the default theme is assembled;
141
+ * a view reads its resolved theme and never this constant. Frozen.
140
142
  *
141
143
  * @remarks
142
144
  * - `question` — the leading mark on a prompt's message line.
@@ -225,17 +227,18 @@ var DEFAULT_PROMPT_THEME = Object.freeze({
225
227
  description: (0, _orkestrel_console.freezeStyle)({ attributes: ["dim"] })
226
228
  })
227
229
  });
228
- /** Holds how long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered prompt before it expires — 5 minutes. */
230
+ /** Holds how long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered form before it expires — 5 minutes. */
229
231
  var DEFAULT_PROMPT_TIMEOUT_MS = 3e5;
230
232
  /** Holds how long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */
231
233
  var DEFAULT_RECONNECT_DELAY_MS = 2e3;
232
234
  /**
233
- * Holds the SSE `event:` names the broker emits and the {@link import('./types.js').PromptClientInterface}
234
- * dispatches on. Frozen; the source of truth for the wire event vocabulary.
235
+ * Holds the SSE `event:` names the broker emits and the
236
+ * {@link import('./types.js').PromptClientInterface} dispatches on — `pending`, `expire`, and
237
+ * `destroy`. Frozen; the source of truth for the wire event vocabulary.
235
238
  *
236
239
  * @remarks
237
240
  * - `pending` — a serialized {@link import('./types.js').PendingForm} to dispatch and answer.
238
- * - `expire` — an `{ id }` payload: the broker expired or released a parked prompt (the client drops it).
241
+ * - `expire` — an `{ id }` payload: the broker expired or released a parked form (the client drops it).
239
242
  * - `destroy` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
240
243
  */
241
244
  var SSE_EVENTS = Object.freeze({
@@ -243,9 +246,12 @@ var SSE_EVENTS = Object.freeze({
243
246
  expire: "expire",
244
247
  destroy: "destroy"
245
248
  });
246
- /** Names the auth-token request header the {@link import('./types.js').PromptClientInterface} sends. */
249
+ /**
250
+ * Names the auth-token request header the {@link import('./types.js').PromptClientInterface} sends
251
+ * when a `token` is configured — `x-orkestrel-token`.
252
+ */
247
253
  var HEADER_TOKEN = "x-orkestrel-token";
248
- /** Names the `Accept` header value that opens the broker's SSE stream. */
254
+ /** Names the `Accept` header value that opens the broker's SSE stream — `text/event-stream`. */
249
255
  var ACCEPT_EVENT_STREAM = "text/event-stream";
250
256
  /**
251
257
  * Sets the maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
@@ -287,7 +293,7 @@ var TerminalError = class extends Error {
287
293
  }
288
294
  };
289
295
  /**
290
- * Narrows an unknown caught value to a {@link TerminalError}.
296
+ * Narrows an unknown caught value to a {@link TerminalError}, so a caller can branch on its `code`.
291
297
  *
292
298
  * @param value - The value to test (typically a `catch` binding or a rejected prompt call)
293
299
  * @returns True if `value` is a {@link TerminalError}; false otherwise
@@ -304,7 +310,7 @@ var TerminalError = class extends Error {
304
310
  * ```
305
311
  */
306
312
  function isTerminalError(value) {
307
- return value instanceof TerminalError;
313
+ return (0, _orkestrel_contract.isInstance)(value, TerminalError);
308
314
  }
309
315
  //#endregion
310
316
  //#region src/core/validators.ts
@@ -316,7 +322,8 @@ function isTerminalError(value) {
316
322
  */
317
323
  var isPendingFormStatus = (0, _orkestrel_contract.literalOf)("pending", "answered", "expired");
318
324
  /**
319
- * Narrows an unknown wire value to a {@link PendingForm} envelope.
325
+ * Narrows an unknown wire value to a {@link PendingForm} envelope — the envelope alone, because the
326
+ * form package's `parseForm` owns the schema payload.
320
327
  *
321
328
  * @remarks
322
329
  * This guard checks the transport record and proves only that `schema` is a record. The Form
@@ -336,7 +343,8 @@ function isPendingForm(value) {
336
343
  }, ["from", "to"])(value);
337
344
  }
338
345
  /**
339
- * Narrows an unknown value to a transport-neutral {@link WireEvent}.
346
+ * Narrows an unknown value to a transport-neutral {@link WireEvent} — the guard a consumer's own
347
+ * transport applies to an inbound frame.
340
348
  *
341
349
  * @param value - The candidate wire event
342
350
  * @returns True if the value carries an event name, serialized data, and an optional id; false otherwise
@@ -347,8 +355,8 @@ var isWireEvent = (0, _orkestrel_contract.recordOf)({
347
355
  id: _orkestrel_contract.isString
348
356
  }, ["id"]);
349
357
  /**
350
- * Narrows an unknown value to a {@link TerminalSnapshot} — the read boundary a store applies to an
351
- * untrusted persisted row.
358
+ * Narrows an unknown value to a {@link TerminalSnapshot} — a non-empty `id` and an optional numeric
359
+ * `timeout`, the read boundary a store applies to an untrusted persisted row.
352
360
  *
353
361
  * @param value - The candidate snapshot read back from storage
354
362
  * @returns True if the value carries a non-empty `id` and an optional numeric `timeout`; false otherwise
@@ -361,16 +369,16 @@ var isTerminalSnapshot = (0, _orkestrel_contract.recordOf)({
361
369
  //#region src/core/helpers.ts
362
370
  /**
363
371
  * Decodes one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
364
- * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF
365
- * pair ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a
366
- * single printable character. An unrecognized sequence carries NO `name`, with the raw `sequence`
372
+ * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF pair
373
+ * ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a single
374
+ * printable character. An unrecognized sequence carries no `name`, with the raw `sequence`
367
375
  * preserved.
368
376
  *
369
377
  * @remarks
370
378
  * - **Single control byte.** A one-character control input (`return` / `backspace` / `tab` /
371
379
  * `escape` / `space`, or a Ctrl combo `c` / `d` / `u` / `a` / `e`), or the two-byte `\r\n`
372
380
  * CRLF pair, is looked up in {@link CONTROL_NAMES}, carrying its `ctrl` flag.
373
- * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in BOTH the
381
+ * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in both the
374
382
  * `ESC[A` and `ESCOA` forms, plus `home` / `end` / `delete`) is looked up in
375
383
  * {@link SEQUENCE_NAMES} and flagged `meta`.
376
384
  * - **Printable character.** A single printable character becomes `name` = that character, with
@@ -425,8 +433,8 @@ function parseKey(input) {
425
433
  };
426
434
  }
427
435
  /**
428
- * Checks whether a single character is a printable (non-control) character — used by
429
- * {@link parseKey}'s char fallback.
436
+ * Checks whether a single character is printable — the fallback test {@link parseKey} applies after
437
+ * the control bytes and the escape sequences, so the C0 controls and DEL are excluded.
430
438
  *
431
439
  * @param character - The single character to test
432
440
  * @returns True if the character is at or above space and is not DEL; false otherwise
@@ -488,7 +496,8 @@ function sanitizeDisplayText(text) {
488
496
  return (0, _orkestrel_console.stripControls)((0, _orkestrel_console.strip)(text)).replaceAll(" ", "").replaceAll("\n", "").replaceAll("\r", "");
489
497
  }
490
498
  /**
491
- * Sanitizes every terminal-readable string in a parsed form schema.
499
+ * Sanitizes every terminal-readable string in a parsed form schema, keeping every identity and
500
+ * answer string verbatim and dropping field metadata.
492
501
  *
493
502
  * @remarks
494
503
  * Display strings pass through {@link sanitizeDisplayText}: labels, help, placeholders, masks,
@@ -639,7 +648,8 @@ function renderSubmitHeader(styler, theme, message) {
639
648
  return `${styler.render(theme.roles.success, theme.icons.success)} ${styler.render(theme.roles.message, message)}`;
640
649
  }
641
650
  /**
642
- * Renders the styled failure line (`✖ message`) a form driver appends for a refused field.
651
+ * Renders the styled failure line (`✖ message`) a form driver writes for each refused field before
652
+ * it asks that field again.
643
653
  *
644
654
  * @param styler - The console styler that renders each role
645
655
  * @param theme - The resolved prompt theme
@@ -650,7 +660,8 @@ function renderErrorLine(styler, theme, message) {
650
660
  return `${styler.render(theme.roles.error, theme.icons.error)} ${styler.render(theme.roles.error, message)}`;
651
661
  }
652
662
  /**
653
- * Builds the initial text-field key state.
663
+ * Builds the initial text-field reducer state — the sanitized label, the declared default, the
664
+ * styler, and the resolved theme.
654
665
  *
655
666
  * @param field - The text field to render
656
667
  * @param styler - The styler used to render the view
@@ -667,7 +678,8 @@ function createInputState(field, styler = (0, _orkestrel_console.createStyler)()
667
678
  };
668
679
  }
669
680
  /**
670
- * Renders a text-field key state as a styled view.
681
+ * Renders a text-field reducer state as a styled view — the header, the pointer, and the typed
682
+ * value, or the default shown as a hint while nothing is typed.
671
683
  *
672
684
  * @param state - The text field's current reducer state
673
685
  * @returns The rendered single-line view — header, pointer, and the typed value or the default
@@ -722,7 +734,8 @@ function reduceInput(state, key) {
722
734
  };
723
735
  }
724
736
  /**
725
- * Builds the initial password-field key state.
737
+ * Builds the initial password-field reducer state — the text-field state, plus the mask glyph each
738
+ * typed character renders as.
726
739
  *
727
740
  * @param field - The password field to render
728
741
  * @param styler - The styler used to render the view
@@ -739,7 +752,8 @@ function createPasswordState(field, styler = (0, _orkestrel_console.createStyler
739
752
  };
740
753
  }
741
754
  /**
742
- * Renders a password-field key state as a styled view.
755
+ * Renders a password-field reducer state as a styled view, with the value replaced by the mask
756
+ * repeated so the secret is never echoed.
743
757
  *
744
758
  * @param state - The password field's current reducer state
745
759
  * @returns The rendered view, with the mask repeated in place of the typed value
@@ -786,7 +800,8 @@ function reducePassword(state, key) {
786
800
  };
787
801
  }
788
802
  /**
789
- * Builds the initial confirm-field key state.
803
+ * Builds the initial confirm-field reducer state — the sanitized label and the declared default
804
+ * answer.
790
805
  *
791
806
  * @param field - The confirm field to render
792
807
  * @param styler - The styler used to render the view
@@ -802,7 +817,8 @@ function createConfirmState(field, styler = (0, _orkestrel_console.createStyler)
802
817
  };
803
818
  }
804
819
  /**
805
- * Renders a confirm-field key state as a styled view. The selected role paints the default letter.
820
+ * Renders a confirm-field reducer state as a styled view — the header and the yes/no group, with
821
+ * the default letter capitalized and painted by the `selected` role.
806
822
  *
807
823
  * @param state - The confirm field's current reducer state
808
824
  * @returns The rendered view — the header and the yes/no group with the default capitalized
@@ -845,7 +861,8 @@ function reduceConfirm(state, key) {
845
861
  };
846
862
  }
847
863
  /**
848
- * Builds the initial select-field key state.
864
+ * Builds the initial select-field reducer state — the offered choices, with the focus pre-placed on
865
+ * the declared default.
849
866
  *
850
867
  * @param field - The select field to render
851
868
  * @param styler - The styler used to render the view
@@ -864,7 +881,8 @@ function createSelectState(field, styler = (0, _orkestrel_console.createStyler)(
864
881
  };
865
882
  }
866
883
  /**
867
- * Renders a select-field key state as a multi-line styled view.
884
+ * Renders a select-field reducer state as a multi-line styled view — one row per choice, with the
885
+ * focused row marked and its help shown.
868
886
  *
869
887
  * @param state - The select field's current reducer state
870
888
  * @returns The rendered view — the header, then one row per choice with the focused row marked
@@ -878,7 +896,7 @@ function renderSelectView(state) {
878
896
  }
879
897
  /**
880
898
  * Advances a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
881
- * reducer. `up` / `down` (and `k` / `j`) move the focus, WRAPPING at the ends; return submits the
899
+ * reducer. `up` / `down` (and `k` / `j`) move the focus, wrapping at the ends; return submits the
882
900
  * focused choice's `value`; ctrl-c cancels. An empty choice list can never submit (a higher layer
883
901
  * guards against it); any other key is ignored.
884
902
  *
@@ -937,7 +955,8 @@ function reduceSelect(state, key) {
937
955
  };
938
956
  }
939
957
  /**
940
- * Builds the initial checkbox-field key state.
958
+ * Builds the initial checkbox-field reducer state — the offered choices, with every value in the
959
+ * field's `default` list pre-checked.
941
960
  *
942
961
  * @param field - The checkbox field to render
943
962
  * @param styler - The styler used to render the view
@@ -960,7 +979,8 @@ function createCheckboxState(field, styler = (0, _orkestrel_console.createStyler
960
979
  };
961
980
  }
962
981
  /**
963
- * Renders a checkbox-field key state as a multi-line styled view.
982
+ * Renders a checkbox-field reducer state as a multi-line styled view — one box per choice, and the
983
+ * selected count beneath them.
964
984
  *
965
985
  * @param state - The checkbox field's current reducer state
966
986
  * @returns The rendered view — the header, one box per choice, and the selected count
@@ -1048,7 +1068,7 @@ function reduceCheckbox(state, key) {
1048
1068
  }
1049
1069
  /**
1050
1070
  * Toggles `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion
1051
- * list.
1071
+ * list; the primitive {@link reduceCheckbox} calls.
1052
1072
  *
1053
1073
  * @param indices - The ticked indices, in tick order
1054
1074
  * @param index - The index to add when absent, or drop when present
@@ -1058,7 +1078,8 @@ function toggleIndex(indices, index) {
1058
1078
  return indices.includes(index) ? indices.filter((i) => i !== index) : [...indices, index];
1059
1079
  }
1060
1080
  /**
1061
- * Builds the initial editor-field key state.
1081
+ * Builds the initial editor-field reducer state — the committed lines empty, and the declared
1082
+ * default held for a finish with nothing typed.
1062
1083
  *
1063
1084
  * @param field - The editor field to render
1064
1085
  * @param styler - The styler used to render the view
@@ -1076,7 +1097,8 @@ function createEditorState(field, styler = (0, _orkestrel_console.createStyler)(
1076
1097
  };
1077
1098
  }
1078
1099
  /**
1079
- * Renders an editor-field key state as a multi-line styled view with its finish hint.
1100
+ * Renders an editor-field reducer state as a multi-line styled view — the finish hint, the
1101
+ * committed lines, and the line in progress.
1080
1102
  *
1081
1103
  * @param state - The editor field's current reducer state
1082
1104
  * @returns The rendered view — the hinted header, the committed lines, and the line in progress
@@ -1089,8 +1111,8 @@ function renderEditorView(state) {
1089
1111
  /**
1090
1112
  * Advances an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
1091
1113
  * reducer. Printable characters extend the current line; backspace shrinks it; return commits the
1092
- * current line and starts a fresh one; ctrl-d FINISHES (joining all lines, falling back to the
1093
- * default when empty); ctrl-c cancels. The form validates the candidate after the driver fills it.
1114
+ * current line and starts a fresh one; ctrl-d finishes, joining every line and falling back to the
1115
+ * default when empty; ctrl-c cancels. The form validates the candidate after the driver fills it.
1094
1116
  *
1095
1117
  * @param state - The editor field's current reducer state
1096
1118
  * @param key - The decoded keypress to apply
@@ -1142,9 +1164,10 @@ function reduceEditor(state, key) {
1142
1164
  };
1143
1165
  }
1144
1166
  /**
1145
- * Applies a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input /
1146
- * password / editor. A printable key appends its character; `backspace` drops the last character;
1147
- * `space` appends a space; ctrl-u clears the line.
1167
+ * Applies a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input,
1168
+ * password, and editor. A printable key appends its character; `backspace` drops the last
1169
+ * character; `space` appends a space; ctrl-u clears the line; a key that edits nothing returns
1170
+ * `undefined`.
1148
1171
  *
1149
1172
  * @param value - The buffer the field holds so far
1150
1173
  * @param key - The decoded keypress to apply
@@ -1192,11 +1215,13 @@ function globalFetch(input, init) {
1192
1215
  * @returns True if the value is a host `Error` or `DOMException` named `AbortError`; false otherwise
1193
1216
  */
1194
1217
  function isAbortError(error) {
1195
- return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
1218
+ return (0, _orkestrel_contract.isError)(error) && error.name === "AbortError";
1196
1219
  }
1197
1220
  /**
1198
- * Checks whether `url` is an INSECURE remote endpoint — a plain `http://` URL whose host is NOT a
1199
- * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input.
1221
+ * Checks whether `url` is an insecure remote endpoint — a plain `http://` URL whose host is not a
1222
+ * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input;
1223
+ * the {@link import('./PromptClient.js').PromptClient} warns once when a `token` would cross such
1224
+ * an endpoint in cleartext.
1200
1225
  *
1201
1226
  * @remarks
1202
1227
  * A loopback host (`localhost`, `127.0.0.1`, `[::1]`) over `http://` is exempt (local
@@ -1223,7 +1248,8 @@ function isInsecureRemote(url) {
1223
1248
  return hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "[::1]";
1224
1249
  }
1225
1250
  /**
1226
- * Serializes a parked {@link PendingForm} into a {@link WireEvent}.
1251
+ * Serializes a parked {@link PendingForm} into a `pending` {@link WireEvent}, whose frame `id` is
1252
+ * the form's own id.
1227
1253
  *
1228
1254
  * @param form - The parked form's wire-safe record
1229
1255
  * @returns The `pending` frame — the JSON-stringified record as `data`, and the form's own `id`
@@ -1236,7 +1262,8 @@ function serializePending(form) {
1236
1262
  };
1237
1263
  }
1238
1264
  /**
1239
- * Serializes a parked prompt's expiry or release into a {@link WireEvent}.
1265
+ * Serializes a parked form's expiry or release into an `expire` {@link WireEvent}, whose `data` is
1266
+ * the JSON `{ id }` payload.
1240
1267
  *
1241
1268
  * @param id - The id of the parked form that expired or was released
1242
1269
  * @returns The `expire` frame, carrying the JSON-stringified `{ id }` payload as `data`
@@ -1248,7 +1275,8 @@ function serializeExpire(id) {
1248
1275
  };
1249
1276
  }
1250
1277
  /**
1251
- * Serializes the {@link WireEvent} a broker or manager sends when it is going away.
1278
+ * Serializes the `destroy` {@link WireEvent} a broker or manager sends when it is going away, which
1279
+ * carries no payload.
1252
1280
  *
1253
1281
  * @returns The `destroy` frame, whose `data` is empty because the signal carries no payload
1254
1282
  */
@@ -1261,8 +1289,9 @@ function serializeDestroy() {
1261
1289
  //#endregion
1262
1290
  //#region src/core/Prompt.ts
1263
1291
  /**
1264
- * Implements the headless form broker. It parks live forms, exposes their serialized schemas, and applies
1265
- * remote answers to the authoritative form.
1292
+ * Implements the headless form broker. It parks live forms, exposes their serialized schemas,
1293
+ * applies remote answers to the authoritative form, and abandons a parked form on timeout, release,
1294
+ * or teardown.
1266
1295
  *
1267
1296
  * @remarks
1268
1297
  * A parked record carries one call to `serializeForm`. A failed fill or submit leaves the record
@@ -1419,7 +1448,7 @@ var Prompt = class {
1419
1448
  if ((0, _orkestrel_form.isFieldError)(error.context)) return [error.context];
1420
1449
  const named = error.context?.field;
1421
1450
  return [{
1422
- field: typeof named === "string" ? named : this.#field(form, field),
1451
+ field: (0, _orkestrel_contract.isString)(named) ? named : this.#field(form, field),
1423
1452
  message: error.message
1424
1453
  }];
1425
1454
  }
@@ -1452,8 +1481,9 @@ var Prompt = class {
1452
1481
  //#endregion
1453
1482
  //#region src/core/PromptClient.ts
1454
1483
  /**
1455
- * Implements the SSE form bridge. It ingests serialized forms from a remote broker, renders them through a
1456
- * local terminal, and posts answers back without blocking the event stream.
1484
+ * Implements the SSE form bridge. It ingests serialized forms from a remote broker without waiting
1485
+ * on a render, drives one form at a time through a local terminal, posts each answer back, and asks
1486
+ * again when the authoritative form refuses one.
1457
1487
  *
1458
1488
  * @remarks
1459
1489
  * - **Connect + reconnect.** {@link connect} opens the SSE stream and reconnects after a transport
@@ -1465,8 +1495,9 @@ var Prompt = class {
1465
1495
  * - **Safe local form.** The rendering copy omits every wire `pattern`, because Form compiles a
1466
1496
  * pattern during local evaluation. The broker's parked form retains it and remains authoritative.
1467
1497
  * - **Refusal retry.** A structured `rejected` response seeds a new rendering form with the values
1468
- * just submitted, applies every {@link FieldError} through `invalidate`, and asks again. No retry
1469
- * counter truncates the loop; acceptance, expiry, and the broker's own teardown are its bounds.
1498
+ * the refused attempt submitted, applies every {@link FieldError} through `invalidate`, and asks
1499
+ * again. No retry counter truncates the loop; acceptance, expiry, and the broker's own teardown
1500
+ * are its bounds.
1470
1501
  * - **Replay safety.** A replayed id is skipped while it is queued, rendering, or posting. Once an
1471
1502
  * attempt ends, a later delivery of that id may be rendered again.
1472
1503
  *
@@ -1757,24 +1788,24 @@ var PromptClient = class {
1757
1788
  //#endregion
1758
1789
  //#region src/core/TerminalManager.ts
1759
1790
  /**
1760
- * Registers named {@link PromptInterface} brokers (one per endpoint), so several parties can
1761
- * `ask` forms of each other by NAME with a `from` → `to` attribution edge on every parked form,
1762
- * and a transitive DEADLOCK check across all in-flight asks.
1791
+ * Registers named {@link PromptInterface} brokers, one per endpoint, so several parties can `ask`
1792
+ * forms of each other by name with a `from` → `to` attribution edge on every parked form, and
1793
+ * refuses `DEADLOCK` on a transitive cycle across every in-flight ask.
1763
1794
  *
1764
1795
  * @remarks
1765
1796
  * - **Registry.** `add(name, options?)` mints (or, if `name` is already mounted, returns the
1766
- * EXISTING broker UNCHANGED — idempotent, never clobbers a live/parked endpoint). Every mounted
1797
+ * existing broker unchanged — idempotent, never clobbers a live/parked endpoint). Every mounted
1767
1798
  * broker's `pending` / `answer` / `expire` events are re-emitted on the manager, attributed by
1768
1799
  * `name`.
1769
- * - **`ask`.** The target must already be mounted via {@link add} — `ask` never auto-adds it;
1800
+ * - **`ask`.** The target must already be mounted through {@link add} — `ask` never auto-adds it;
1770
1801
  * rejects `TARGET` for an unknown `to` (listing the known names). Rejects `DEADLOCK` when parking
1771
- * `from → to` would close a cycle over the CURRENT in-flight edge set (walked transitively);
1802
+ * `from → to` would close a cycle over the current in-flight edge set (walked transitively);
1772
1803
  * otherwise parks the caller's live form through the target's broker and returns that form's own
1773
1804
  * `answer` promise. Edge cleanup never alters the value or rejection the caller observes.
1774
1805
  * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked
1775
1806
  * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's
1776
1807
  * configured `timeout`.
1777
- * - **Removal.** `remove` drops one endpoint, a batch (the array overload declared FIRST), or every
1808
+ * - **Removal.** `remove` drops one endpoint, a batch (the array overload declared first), or every
1778
1809
  * endpoint when called without an argument. It destroys each broker, which expires every form
1779
1810
  * still parked on it. `destroy` is idempotent.
1780
1811
  *
@@ -2001,9 +2032,10 @@ var TerminalManager = class {
2001
2032
  //#region src/core/stores/MemoryTerminalStore.ts
2002
2033
  /**
2003
2034
  * Implements the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
2004
- * {@link TerminalSnapshot}s keyed by endpoint id, the DEFAULT store
2005
- * {@link import('../factories.js').createMemoryTerminalStore} builds. The EXACT twin of
2006
- * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}.
2035
+ * {@link TerminalSnapshot} records keyed by endpoint id, the default store
2036
+ * {@link import('../factories.js').createMemoryTerminalStore} builds and the exact twin of
2037
+ * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}. It carries no idle expiry and
2038
+ * no eviction.
2007
2039
  *
2008
2040
  * @remarks
2009
2041
  * A plain `Map<string, TerminalSnapshot>` — the snapshot is already pure, self-contained CONFIG-only
@@ -2066,10 +2098,11 @@ var MemoryTerminalStore = class {
2066
2098
  //#endregion
2067
2099
  //#region src/core/stores/DatabaseTerminalStore.ts
2068
2100
  /**
2069
- * Implements a {@link TerminalStoreInterface} backed by one table of the `databases` layer — an endpoint's
2070
- * durable CONFIG state IS a row, so persistence reduces to keyed point-access (`get` / `set` /
2071
- * `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
2072
- * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}.
2101
+ * Implements a {@link TerminalStoreInterface} backed by one table of the `databases` layer — an
2102
+ * endpoint's durable config state is a row, so persistence reduces to keyed point-access (`get` /
2103
+ * `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
2104
+ * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. A stored `snapshot` is narrowed
2105
+ * with {@link import('../validators.js').isTerminalSnapshot} on read.
2073
2106
  *
2074
2107
  * @remarks
2075
2108
  * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
@@ -2183,9 +2216,10 @@ function createPrompt(options) {
2183
2216
  return new Prompt(options);
2184
2217
  }
2185
2218
  /**
2186
- * Creates the SSE prompt {@link PromptClientInterface} BRIDGE — it connects to a remote broker's SSE
2187
- * endpoint, dispatches each received form to a local {@link import('./types.js').TerminalInterface},
2188
- * and POSTs the answer back. Universal — `fetch` / SSE are web-standard.
2219
+ * Creates the SSE prompt {@link PromptClientInterface} bridge — it connects to a remote broker's
2220
+ * SSE endpoint, dispatches each received form to a local
2221
+ * {@link import('./types.js').TerminalInterface}, and POSTs the answer back. Universal — `fetch`
2222
+ * and SSE are web standards.
2189
2223
  *
2190
2224
  * @param options - See {@link PromptClientOptions} (`url` + `terminal` required)
2191
2225
  * @returns A {@link PromptClientInterface}
@@ -2209,9 +2243,9 @@ function createPromptClient(options) {
2209
2243
  return new PromptClient(options);
2210
2244
  }
2211
2245
  /**
2212
- * Creates the multi-endpoint {@link TerminalManager} — a named registry of
2213
- * {@link PromptInterface} brokers so several parties can `ask` prompts of each other by name,
2214
- * with a transitive DEADLOCK check across every in-flight ask.
2246
+ * Creates the multi-endpoint {@link TerminalManager} — a named registry of {@link PromptInterface}
2247
+ * brokers so several parties can `ask` forms of each other by name, with a transitive cycle check
2248
+ * that refuses `DEADLOCK` across every in-flight ask.
2215
2249
  *
2216
2250
  * @param options - See {@link TerminalManagerOptions}
2217
2251
  * @returns A {@link TerminalManager}
@@ -2246,7 +2280,8 @@ function createMemoryTerminalStore() {
2246
2280
  /**
2247
2281
  * Creates a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
2248
2282
  * driver-pluggable twin of {@link createMemoryTerminalStore}, storing each endpoint's config
2249
- * snapshot as one opaque JSON column.
2283
+ * snapshot as one opaque JSON column. The default driver is an in-memory `@orkestrel/database`
2284
+ * driver.
2250
2285
  *
2251
2286
  * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
2252
2287
  * @returns A {@link TerminalStoreInterface}