@orkestrel/toolbox 0.0.11 → 0.0.12

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.
@@ -5,24 +5,23 @@ let _orkestrel_contract = require("@orkestrel/contract");
5
5
  let _orkestrel_server = require("@orkestrel/server");
6
6
  //#region src/server/constants.ts
7
7
  /**
8
- * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
8
+ * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
9
9
  * mounts its GET (SSE) + POST (answer) routes under.
10
10
  */
11
11
  var TERMINAL_ROUTES_PATH = "/terminals/:name";
12
12
  /**
13
- * The default SSE keepalive interval (in milliseconds)
13
+ * Holds the default SSE keepalive interval (in milliseconds)
14
14
  * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `
15
15
  * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an
16
16
  * otherwise-idle stream.
17
17
  */
18
18
  var TERMINAL_KEEPALIVE_MS = 15e3;
19
19
  //#endregion
20
- //#region src/server/routes/TerminalConnection.ts
20
+ //#region src/server/terminals/TerminalConnection.ts
21
21
  /**
22
- * Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
22
+ * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
23
23
  *
24
- * Internal to this module: `TerminalRoutes` builds the `accepts` predicate from its own token
25
- * option and opens the stream, so no consumer can construct one. Reach this behaviour through
24
+ * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through
26
25
  * `createTerminalRoutes`.
27
26
  */
28
27
  var TerminalConnection = class {
@@ -40,7 +39,7 @@ var TerminalConnection = class {
40
39
  #expireHandler;
41
40
  #tickHandler;
42
41
  /**
43
- * Create a terminal stream connection.
42
+ * Creates a terminal stream connection.
44
43
  *
45
44
  * @param manager - Terminal manager supplying pending forms and lifecycle events
46
45
  * @param name - Terminal endpoint streamed by this connection
@@ -65,7 +64,7 @@ var TerminalConnection = class {
65
64
  this.#tickHandler = this.#tick.bind(this);
66
65
  }
67
66
  /**
68
- * Open the connection by replaying pending forms, subscribing, and arming keepalive handling.
67
+ * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.
69
68
  *
70
69
  * @returns The SSE response
71
70
  */
@@ -131,18 +130,15 @@ var TerminalConnection = class {
131
130
  }
132
131
  };
133
132
  //#endregion
134
- //#region src/server/routes/TerminalRoutes.ts
133
+ //#region src/server/terminals/TerminalBridge.ts
135
134
  /**
136
- * Build and serve the terminal manager's GET stream and POST answer routes.
135
+ * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and
136
+ * the bound GET stream and POST answer handlers.
137
137
  *
138
- * @example
139
- * ```ts
140
- * import { TerminalRoutes } from '@orkestrel/toolbox/server'
141
- *
142
- * const routes = new TerminalRoutes(manager).routes()
143
- * ```
138
+ * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through
139
+ * `createTerminalRoutes`.
144
140
  */
145
- var TerminalRoutes = class {
141
+ var TerminalBridge = class {
146
142
  #manager;
147
143
  #path;
148
144
  #token;
@@ -153,7 +149,7 @@ var TerminalRoutes = class {
153
149
  #get;
154
150
  #post;
155
151
  /**
156
- * Create a terminal route owner.
152
+ * Creates a terminal bridge.
157
153
  *
158
154
  * @param manager - Terminal manager bridged onto HTTP
159
155
  * @param options - Shared route, authorization, keepalive, timer, and body-limit options
@@ -171,7 +167,7 @@ var TerminalRoutes = class {
171
167
  this.#post = this.#handlePost.bind(this);
172
168
  }
173
169
  /**
174
- * Project the bound GET and POST route records.
170
+ * Projects the bound GET and POST route records.
175
171
  *
176
172
  * @returns The GET stream route followed by the POST answer route
177
173
  */
@@ -201,7 +197,7 @@ var TerminalRoutes = class {
201
197
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
202
198
  const name = context.params.name;
203
199
  if (name === void 0 || this.#manager.terminal(name) === void 0) return new Response(null, { status: 404 });
204
- return new TerminalConnection(this.#manager, name, request, (0, _orkestrel_server.openStream)(), this.#accepts, this.#timer, this.#keepalive).open();
200
+ return new TerminalConnection(this.#manager, name, request, (0, _orkestrel_server.createStream)(), this.#accepts, this.#timer, this.#keepalive).open();
205
201
  }
206
202
  async #handlePost(request, context) {
207
203
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
@@ -224,14 +220,14 @@ var TerminalRoutes = class {
224
220
  if (!(0, _orkestrel_contract.isRecord)(body) || !(0, _orkestrel_contract.isNonEmptyString)(body.id) || !(0, _orkestrel_form.isFormValues)(body.values)) return new Response(null, { status: 422 });
225
221
  const result = this.#manager.answer(name, body.id, body.values);
226
222
  if (result.success) return Response.json(result);
227
- if (result.error.reason === "terminal") return Response.json(result, { status: 404 });
223
+ if (result.error.reason === "target") return Response.json(result, { status: 404 });
228
224
  return Response.json(result, { status: 422 });
229
225
  }
230
226
  };
231
227
  //#endregion
232
228
  //#region src/server/factories.ts
233
229
  /**
234
- * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
230
+ * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
235
231
  *
236
232
  * @remarks
237
233
  * Both routes share the configured `:name` path and optional token gate. The GET route replays
@@ -253,12 +249,11 @@ var TerminalRoutes = class {
253
249
  * ```
254
250
  */
255
251
  function createTerminalRoutes(manager, options) {
256
- return new TerminalRoutes(manager, options).routes();
252
+ return new TerminalBridge(manager, options).routes();
257
253
  }
258
254
  //#endregion
259
255
  exports.TERMINAL_KEEPALIVE_MS = TERMINAL_KEEPALIVE_MS;
260
256
  exports.TERMINAL_ROUTES_PATH = TERMINAL_ROUTES_PATH;
261
- exports.TerminalRoutes = TerminalRoutes;
262
257
  exports.createTerminalRoutes = createTerminalRoutes;
263
258
 
264
259
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#manager","#name","#request","#stream","#accepts","#timer","#keepalive","#presented","#destroyHandler","#pendingHandler","#expireHandler","#tickHandler","#destroy","#pending","#expire","#tick","#cancel","#write","#accepted","#manager","#path","#token","#keepalive","#timer","#limit","#accepts","#get","#post","#valid","#handleGet","#handlePost","#authorized"],"sources":["../../../src/server/constants.ts","../../../src/server/routes/TerminalConnection.ts","../../../src/server/routes/TerminalRoutes.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported (AGENTS §5).\n\n/**\n * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) + POST (answer) routes under.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * The default SSE keepalive interval (in milliseconds)\n * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `\n * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an\n * otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancel,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Internal to this module: `TerminalRoutes` builds the `accepts` predicate from its own token\n * option and opens the stream, so no consumer can construct one. Reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancel | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Create a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Open the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport { isNonEmptyString, isRecord } from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tDEFAULT_BODY_LIMIT,\n\topenStream,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Build and serve the terminal manager's GET stream and POST answer routes.\n *\n * @example\n * ```ts\n * import { TerminalRoutes } from '@orkestrel/toolbox/server'\n *\n * const routes = new TerminalRoutes(manager).routes()\n * ```\n */\nexport class TerminalRoutes {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Create a terminal route owner.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !Number.isFinite(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Project the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\topenStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (error instanceof ContentTooLargeError) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tlet body: unknown\n\t\ttry {\n\t\t\tbody = JSON.parse(new TextDecoder().decode(bytes))\n\t\t} catch {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'terminal') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalRoutes } from './routes/TerminalRoutes.js'\n\n/**\n * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalRoutes(manager, options).routes()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["#manager","#name","#request","#stream","#accepts","#timer","#keepalive","#presented","#destroyHandler","#pendingHandler","#expireHandler","#tickHandler","#destroy","#pending","#expire","#tick","#cancel","#write","#accepted","#manager","#path","#token","#keepalive","#timer","#limit","#accepts","#get","#post","#valid","#handleGet","#handlePost","#authorized"],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) + POST (answer) routes under.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval (in milliseconds)\n * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `\n * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an\n * otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport { isNonEmptyString, isRecord } from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !Number.isFinite(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (error instanceof ContentTooLargeError) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tlet body: unknown\n\t\ttry {\n\t\t\tbody = JSON.parse(new TextDecoder().decode(bytes))\n\t\t} catch {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { TerminalManagerInterface } from '@orkestrel/terminal';
2
2
  import { TimerHandler } from '@orkestrel/terminal';
3
3
 
4
4
  /**
5
- * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
5
+ * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
6
6
  *
7
7
  * @remarks
8
8
  * Both routes share the configured `:name` path and optional token gate. The GET route replays
@@ -25,11 +25,8 @@ import { TimerHandler } from '@orkestrel/terminal';
25
25
  */
26
26
  export declare function createTerminalRoutes(manager: TerminalManagerInterface, options?: TerminalRoutesOptions): readonly TerminalRoute[];
27
27
 
28
- /** The HTTP method literal a {@link TerminalRoute} declares — the exact 7-literal union `@orkestrel/router`'s `Method` accepts. */
29
- export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
30
-
31
28
  /**
32
- * The default SSE keepalive interval (in milliseconds)
29
+ * Holds the default SSE keepalive interval (in milliseconds)
33
30
  * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `
34
31
  * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an
35
32
  * otherwise-idle stream.
@@ -37,60 +34,37 @@ export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD
37
34
  export declare const TERMINAL_KEEPALIVE_MS = 15000;
38
35
 
39
36
  /**
40
- * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
37
+ * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
41
38
  * mounts its GET (SSE) + POST (answer) routes under.
42
39
  */
43
40
  export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
44
41
 
45
42
  /**
46
- * One structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
43
+ * Represents one structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
47
44
  * plain `{ method, path, handler }` shape carrying NO dependency on `@orkestrel/router`'s own
48
45
  * `Route` type, so a consumer mounts it against any router that accepts a two-arg
49
46
  * `(request, context) => Response | Promise<Response>` handler keyed by `method` + `path`.
50
47
  */
51
48
  export declare interface TerminalRoute {
52
- readonly method: Method;
49
+ readonly method: TerminalRouteMethod;
53
50
  readonly path: string;
54
51
  readonly handler: (request: Request, context: TerminalRouteContext) => Response | Promise<Response>;
55
52
  }
56
53
 
57
54
  /**
58
- * The minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
55
+ * Represents the minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
59
56
  * URL-decoded `:name` path param slice a router hands a matched handler.
60
57
  */
61
58
  export declare interface TerminalRouteContext {
62
59
  readonly params: Readonly<Record<string, string>>;
63
60
  }
64
61
 
65
- /**
66
- * Build and serve the terminal manager's GET stream and POST answer routes.
67
- *
68
- * @example
69
- * ```ts
70
- * import { TerminalRoutes } from '@orkestrel/toolbox/server'
71
- *
72
- * const routes = new TerminalRoutes(manager).routes()
73
- * ```
74
- */
75
- export declare class TerminalRoutes {
76
- #private;
77
- /**
78
- * Create a terminal route owner.
79
- *
80
- * @param manager - Terminal manager bridged onto HTTP
81
- * @param options - Shared route, authorization, keepalive, timer, and body-limit options
82
- */
83
- constructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions);
84
- /**
85
- * Project the bound GET and POST route records.
86
- *
87
- * @returns The GET stream route followed by the POST answer route
88
- */
89
- routes(): readonly TerminalRoute[];
90
- }
62
+ /** Represents the HTTP method literal a {@link TerminalRoute} declares — the same union `@orkestrel/router`'s `Method` type accepts. */
63
+ export declare type TerminalRouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
91
64
 
92
65
  /**
93
- * Options for {@link import('./factories.js').createTerminalRoutes}.
66
+ * Represents the options {@link import('./factories.js').createTerminalRoutes} takes — the shared
67
+ * route, authorization, keepalive, timer, and body-limit configuration both routes read.
94
68
  *
95
69
  * @remarks
96
70
  * - `path` — the shared `:name`-templated path both the GET (SSE) and POST (answer) routes
@@ -123,7 +97,7 @@ export declare interface TerminalRoutesOptions {
123
97
  }
124
98
 
125
99
  /**
126
- * The `token` gate a {@link TerminalRoutesOptions} may configure — a plain string compared for
100
+ * Represents the `token` gate {@link TerminalRoutesOptions} may configure — a plain string compared for
127
101
  * equality against the `x-orkestrel-token` header, OR a validator function the consumer fully
128
102
  * controls, enabling expiry/rotation (a JWT `exp` check, a revocation-list lookup, anything
129
103
  * time-varying) that a fixed string cannot express. `undefined` disables the auth check entirely.
@@ -2,7 +2,7 @@ import { TerminalManagerInterface } from '@orkestrel/terminal';
2
2
  import { TimerHandler } from '@orkestrel/terminal';
3
3
 
4
4
  /**
5
- * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
5
+ * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
6
6
  *
7
7
  * @remarks
8
8
  * Both routes share the configured `:name` path and optional token gate. The GET route replays
@@ -25,11 +25,8 @@ import { TimerHandler } from '@orkestrel/terminal';
25
25
  */
26
26
  export declare function createTerminalRoutes(manager: TerminalManagerInterface, options?: TerminalRoutesOptions): readonly TerminalRoute[];
27
27
 
28
- /** The HTTP method literal a {@link TerminalRoute} declares — the exact 7-literal union `@orkestrel/router`'s `Method` accepts. */
29
- export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
30
-
31
28
  /**
32
- * The default SSE keepalive interval (in milliseconds)
29
+ * Holds the default SSE keepalive interval (in milliseconds)
33
30
  * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `
34
31
  * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an
35
32
  * otherwise-idle stream.
@@ -37,60 +34,37 @@ export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD
37
34
  export declare const TERMINAL_KEEPALIVE_MS = 15000;
38
35
 
39
36
  /**
40
- * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
37
+ * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
41
38
  * mounts its GET (SSE) + POST (answer) routes under.
42
39
  */
43
40
  export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
44
41
 
45
42
  /**
46
- * One structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
43
+ * Represents one structural route record {@link import('./factories.js').createTerminalRoutes} returns — a
47
44
  * plain `{ method, path, handler }` shape carrying NO dependency on `@orkestrel/router`'s own
48
45
  * `Route` type, so a consumer mounts it against any router that accepts a two-arg
49
46
  * `(request, context) => Response | Promise<Response>` handler keyed by `method` + `path`.
50
47
  */
51
48
  export declare interface TerminalRoute {
52
- readonly method: Method;
49
+ readonly method: TerminalRouteMethod;
53
50
  readonly path: string;
54
51
  readonly handler: (request: Request, context: TerminalRouteContext) => Response | Promise<Response>;
55
52
  }
56
53
 
57
54
  /**
58
- * The minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
55
+ * Represents the minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
59
56
  * URL-decoded `:name` path param slice a router hands a matched handler.
60
57
  */
61
58
  export declare interface TerminalRouteContext {
62
59
  readonly params: Readonly<Record<string, string>>;
63
60
  }
64
61
 
65
- /**
66
- * Build and serve the terminal manager's GET stream and POST answer routes.
67
- *
68
- * @example
69
- * ```ts
70
- * import { TerminalRoutes } from '@orkestrel/toolbox/server'
71
- *
72
- * const routes = new TerminalRoutes(manager).routes()
73
- * ```
74
- */
75
- export declare class TerminalRoutes {
76
- #private;
77
- /**
78
- * Create a terminal route owner.
79
- *
80
- * @param manager - Terminal manager bridged onto HTTP
81
- * @param options - Shared route, authorization, keepalive, timer, and body-limit options
82
- */
83
- constructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions);
84
- /**
85
- * Project the bound GET and POST route records.
86
- *
87
- * @returns The GET stream route followed by the POST answer route
88
- */
89
- routes(): readonly TerminalRoute[];
90
- }
62
+ /** Represents the HTTP method literal a {@link TerminalRoute} declares — the same union `@orkestrel/router`'s `Method` type accepts. */
63
+ export declare type TerminalRouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
91
64
 
92
65
  /**
93
- * Options for {@link import('./factories.js').createTerminalRoutes}.
66
+ * Represents the options {@link import('./factories.js').createTerminalRoutes} takes — the shared
67
+ * route, authorization, keepalive, timer, and body-limit configuration both routes read.
94
68
  *
95
69
  * @remarks
96
70
  * - `path` — the shared `:name`-templated path both the GET (SSE) and POST (answer) routes
@@ -123,7 +97,7 @@ export declare interface TerminalRoutesOptions {
123
97
  }
124
98
 
125
99
  /**
126
- * The `token` gate a {@link TerminalRoutesOptions} may configure — a plain string compared for
100
+ * Represents the `token` gate {@link TerminalRoutesOptions} may configure — a plain string compared for
127
101
  * equality against the `x-orkestrel-token` header, OR a validator function the consumer fully
128
102
  * controls, enabling expiry/rotation (a JWT `exp` check, a revocation-list lookup, anything
129
103
  * time-varying) that a fixed string cannot express. `undefined` disables the auth check entirely.
@@ -1,27 +1,26 @@
1
1
  import { HEADER_TOKEN, defaultTimer, serializeExpire, serializePending } from "@orkestrel/terminal";
2
2
  import { isFormValues } from "@orkestrel/form";
3
3
  import { isNonEmptyString, isRecord } from "@orkestrel/contract";
4
- import { ContentTooLargeError, DEFAULT_BODY_LIMIT, collectRequestBody, openStream } from "@orkestrel/server";
4
+ import { ContentTooLargeError, DEFAULT_BODY_LIMIT, collectRequestBody, createStream } from "@orkestrel/server";
5
5
  //#region src/server/constants.ts
6
6
  /**
7
- * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
7
+ * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
8
8
  * mounts its GET (SSE) + POST (answer) routes under.
9
9
  */
10
10
  var TERMINAL_ROUTES_PATH = "/terminals/:name";
11
11
  /**
12
- * The default SSE keepalive interval (in milliseconds)
12
+ * Holds the default SSE keepalive interval (in milliseconds)
13
13
  * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `
14
14
  * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an
15
15
  * otherwise-idle stream.
16
16
  */
17
17
  var TERMINAL_KEEPALIVE_MS = 15e3;
18
18
  //#endregion
19
- //#region src/server/routes/TerminalConnection.ts
19
+ //#region src/server/terminals/TerminalConnection.ts
20
20
  /**
21
- * Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
21
+ * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
22
22
  *
23
- * Internal to this module: `TerminalRoutes` builds the `accepts` predicate from its own token
24
- * option and opens the stream, so no consumer can construct one. Reach this behaviour through
23
+ * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through
25
24
  * `createTerminalRoutes`.
26
25
  */
27
26
  var TerminalConnection = class {
@@ -39,7 +38,7 @@ var TerminalConnection = class {
39
38
  #expireHandler;
40
39
  #tickHandler;
41
40
  /**
42
- * Create a terminal stream connection.
41
+ * Creates a terminal stream connection.
43
42
  *
44
43
  * @param manager - Terminal manager supplying pending forms and lifecycle events
45
44
  * @param name - Terminal endpoint streamed by this connection
@@ -64,7 +63,7 @@ var TerminalConnection = class {
64
63
  this.#tickHandler = this.#tick.bind(this);
65
64
  }
66
65
  /**
67
- * Open the connection by replaying pending forms, subscribing, and arming keepalive handling.
66
+ * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.
68
67
  *
69
68
  * @returns The SSE response
70
69
  */
@@ -130,18 +129,15 @@ var TerminalConnection = class {
130
129
  }
131
130
  };
132
131
  //#endregion
133
- //#region src/server/routes/TerminalRoutes.ts
132
+ //#region src/server/terminals/TerminalBridge.ts
134
133
  /**
135
- * Build and serve the terminal manager's GET stream and POST answer routes.
134
+ * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and
135
+ * the bound GET stream and POST answer handlers.
136
136
  *
137
- * @example
138
- * ```ts
139
- * import { TerminalRoutes } from '@orkestrel/toolbox/server'
140
- *
141
- * const routes = new TerminalRoutes(manager).routes()
142
- * ```
137
+ * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through
138
+ * `createTerminalRoutes`.
143
139
  */
144
- var TerminalRoutes = class {
140
+ var TerminalBridge = class {
145
141
  #manager;
146
142
  #path;
147
143
  #token;
@@ -152,7 +148,7 @@ var TerminalRoutes = class {
152
148
  #get;
153
149
  #post;
154
150
  /**
155
- * Create a terminal route owner.
151
+ * Creates a terminal bridge.
156
152
  *
157
153
  * @param manager - Terminal manager bridged onto HTTP
158
154
  * @param options - Shared route, authorization, keepalive, timer, and body-limit options
@@ -170,7 +166,7 @@ var TerminalRoutes = class {
170
166
  this.#post = this.#handlePost.bind(this);
171
167
  }
172
168
  /**
173
- * Project the bound GET and POST route records.
169
+ * Projects the bound GET and POST route records.
174
170
  *
175
171
  * @returns The GET stream route followed by the POST answer route
176
172
  */
@@ -200,7 +196,7 @@ var TerminalRoutes = class {
200
196
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
201
197
  const name = context.params.name;
202
198
  if (name === void 0 || this.#manager.terminal(name) === void 0) return new Response(null, { status: 404 });
203
- return new TerminalConnection(this.#manager, name, request, openStream(), this.#accepts, this.#timer, this.#keepalive).open();
199
+ return new TerminalConnection(this.#manager, name, request, createStream(), this.#accepts, this.#timer, this.#keepalive).open();
204
200
  }
205
201
  async #handlePost(request, context) {
206
202
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
@@ -223,14 +219,14 @@ var TerminalRoutes = class {
223
219
  if (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) return new Response(null, { status: 422 });
224
220
  const result = this.#manager.answer(name, body.id, body.values);
225
221
  if (result.success) return Response.json(result);
226
- if (result.error.reason === "terminal") return Response.json(result, { status: 404 });
222
+ if (result.error.reason === "target") return Response.json(result, { status: 404 });
227
223
  return Response.json(result, { status: 422 });
228
224
  }
229
225
  };
230
226
  //#endregion
231
227
  //#region src/server/factories.ts
232
228
  /**
233
- * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
229
+ * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
234
230
  *
235
231
  * @remarks
236
232
  * Both routes share the configured `:name` path and optional token gate. The GET route replays
@@ -252,9 +248,9 @@ var TerminalRoutes = class {
252
248
  * ```
253
249
  */
254
250
  function createTerminalRoutes(manager, options) {
255
- return new TerminalRoutes(manager, options).routes();
251
+ return new TerminalBridge(manager, options).routes();
256
252
  }
257
253
  //#endregion
258
- export { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH, TerminalRoutes, createTerminalRoutes };
254
+ export { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH, createTerminalRoutes };
259
255
 
260
256
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#manager","#name","#request","#stream","#accepts","#timer","#keepalive","#presented","#destroyHandler","#pendingHandler","#expireHandler","#tickHandler","#destroy","#pending","#expire","#tick","#cancel","#write","#accepted","#manager","#path","#token","#keepalive","#timer","#limit","#accepts","#get","#post","#valid","#handleGet","#handlePost","#authorized"],"sources":["../../../src/server/constants.ts","../../../src/server/routes/TerminalConnection.ts","../../../src/server/routes/TerminalRoutes.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported (AGENTS §5).\n\n/**\n * The default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) + POST (answer) routes under.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * The default SSE keepalive interval (in milliseconds)\n * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `\n * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an\n * otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancel,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Own one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Internal to this module: `TerminalRoutes` builds the `accepts` predicate from its own token\n * option and opens the stream, so no consumer can construct one. Reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancel | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Create a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Open the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport { isNonEmptyString, isRecord } from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tDEFAULT_BODY_LIMIT,\n\topenStream,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Build and serve the terminal manager's GET stream and POST answer routes.\n *\n * @example\n * ```ts\n * import { TerminalRoutes } from '@orkestrel/toolbox/server'\n *\n * const routes = new TerminalRoutes(manager).routes()\n * ```\n */\nexport class TerminalRoutes {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Create a terminal route owner.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !Number.isFinite(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Project the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\topenStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (error instanceof ContentTooLargeError) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tlet body: unknown\n\t\ttry {\n\t\t\tbody = JSON.parse(new TextDecoder().decode(bytes))\n\t\t} catch {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'terminal') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalRoutes } from './routes/TerminalRoutes.js'\n\n/**\n * Build the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalRoutes(manager, options).routes()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["#manager","#name","#request","#stream","#accepts","#timer","#keepalive","#presented","#destroyHandler","#pendingHandler","#expireHandler","#tickHandler","#destroy","#pending","#expire","#tick","#cancel","#write","#accepted","#manager","#path","#token","#keepalive","#timer","#limit","#accepts","#get","#post","#valid","#handleGet","#handlePost","#authorized"],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) + POST (answer) routes under.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval (in milliseconds)\n * {@link import('./factories.js').createTerminalRoutes} arms per open connection — a `: `\n * comment ping a conforming SSE parser ignores, keeping intermediary proxies from timing out an\n * otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport { isNonEmptyString, isRecord } from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !Number.isFinite(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (error instanceof ContentTooLargeError) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tlet body: unknown\n\t\ttry {\n\t\t\tbody = JSON.parse(new TextDecoder().decode(bytes))\n\t\t} catch {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":"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"}