@orkestrel/toolbox 0.0.11 → 0.0.13
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 +4 -7
- package/dist/src/core/index.cjs +770 -580
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1237 -1020
- package/dist/src/core/index.d.ts +1237 -1020
- package/dist/src/core/index.js +755 -565
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +26 -28
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +30 -52
- package/dist/src/server/index.d.ts +30 -52
- package/dist/src/server/index.js +28 -29
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -24
|
@@ -5,24 +5,22 @@ let _orkestrel_contract = require("@orkestrel/contract");
|
|
|
5
5
|
let _orkestrel_server = require("@orkestrel/server");
|
|
6
6
|
//#region src/server/constants.ts
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* mounts its GET (SSE)
|
|
8
|
+
* Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
|
|
9
|
+
* mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.
|
|
10
10
|
*/
|
|
11
11
|
var TERMINAL_ROUTES_PATH = "/terminals/:name";
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* otherwise-idle stream.
|
|
13
|
+
* Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}
|
|
14
|
+
* arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,
|
|
15
|
+
* keeping intermediary proxies from timing out an otherwise-idle stream.
|
|
17
16
|
*/
|
|
18
17
|
var TERMINAL_KEEPALIVE_MS = 15e3;
|
|
19
18
|
//#endregion
|
|
20
|
-
//#region src/server/
|
|
19
|
+
//#region src/server/terminals/TerminalConnection.ts
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
21
|
+
* Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
* 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
|
|
26
24
|
* `createTerminalRoutes`.
|
|
27
25
|
*/
|
|
28
26
|
var TerminalConnection = class {
|
|
@@ -40,7 +38,7 @@ var TerminalConnection = class {
|
|
|
40
38
|
#expireHandler;
|
|
41
39
|
#tickHandler;
|
|
42
40
|
/**
|
|
43
|
-
*
|
|
41
|
+
* Creates a terminal stream connection.
|
|
44
42
|
*
|
|
45
43
|
* @param manager - Terminal manager supplying pending forms and lifecycle events
|
|
46
44
|
* @param name - Terminal endpoint streamed by this connection
|
|
@@ -65,7 +63,7 @@ var TerminalConnection = class {
|
|
|
65
63
|
this.#tickHandler = this.#tick.bind(this);
|
|
66
64
|
}
|
|
67
65
|
/**
|
|
68
|
-
*
|
|
66
|
+
* Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.
|
|
69
67
|
*
|
|
70
68
|
* @returns The SSE response
|
|
71
69
|
*/
|
|
@@ -131,18 +129,15 @@ var TerminalConnection = class {
|
|
|
131
129
|
}
|
|
132
130
|
};
|
|
133
131
|
//#endregion
|
|
134
|
-
//#region src/server/
|
|
132
|
+
//#region src/server/terminals/TerminalBridge.ts
|
|
135
133
|
/**
|
|
136
|
-
*
|
|
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.
|
|
137
136
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* import { TerminalRoutes } from '@orkestrel/toolbox/server'
|
|
141
|
-
*
|
|
142
|
-
* const routes = new TerminalRoutes(manager).routes()
|
|
143
|
-
* ```
|
|
137
|
+
* Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through
|
|
138
|
+
* `createTerminalRoutes`.
|
|
144
139
|
*/
|
|
145
|
-
var
|
|
140
|
+
var TerminalBridge = class {
|
|
146
141
|
#manager;
|
|
147
142
|
#path;
|
|
148
143
|
#token;
|
|
@@ -153,7 +148,7 @@ var TerminalRoutes = class {
|
|
|
153
148
|
#get;
|
|
154
149
|
#post;
|
|
155
150
|
/**
|
|
156
|
-
*
|
|
151
|
+
* Creates a terminal bridge.
|
|
157
152
|
*
|
|
158
153
|
* @param manager - Terminal manager bridged onto HTTP
|
|
159
154
|
* @param options - Shared route, authorization, keepalive, timer, and body-limit options
|
|
@@ -171,7 +166,7 @@ var TerminalRoutes = class {
|
|
|
171
166
|
this.#post = this.#handlePost.bind(this);
|
|
172
167
|
}
|
|
173
168
|
/**
|
|
174
|
-
*
|
|
169
|
+
* Projects the bound GET and POST route records.
|
|
175
170
|
*
|
|
176
171
|
* @returns The GET stream route followed by the POST answer route
|
|
177
172
|
*/
|
|
@@ -201,7 +196,7 @@ var TerminalRoutes = class {
|
|
|
201
196
|
if (!this.#authorized(request)) return new Response(null, { status: 401 });
|
|
202
197
|
const name = context.params.name;
|
|
203
198
|
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.
|
|
199
|
+
return new TerminalConnection(this.#manager, name, request, (0, _orkestrel_server.createStream)(), this.#accepts, this.#timer, this.#keepalive).open();
|
|
205
200
|
}
|
|
206
201
|
async #handlePost(request, context) {
|
|
207
202
|
if (!this.#authorized(request)) return new Response(null, { status: 401 });
|
|
@@ -224,16 +219,20 @@ var TerminalRoutes = class {
|
|
|
224
219
|
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
220
|
const result = this.#manager.answer(name, body.id, body.values);
|
|
226
221
|
if (result.success) return Response.json(result);
|
|
227
|
-
if (result.error.reason === "
|
|
222
|
+
if (result.error.reason === "target") return Response.json(result, { status: 404 });
|
|
228
223
|
return Response.json(result, { status: 422 });
|
|
229
224
|
}
|
|
230
225
|
};
|
|
231
226
|
//#endregion
|
|
232
227
|
//#region src/server/factories.ts
|
|
233
228
|
/**
|
|
234
|
-
*
|
|
229
|
+
* Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
|
|
235
230
|
*
|
|
236
231
|
* @remarks
|
|
232
|
+
* The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url
|
|
233
|
+
* streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer
|
|
234
|
+
* `Result` response body, and the same `x-orkestrel-token` header.
|
|
235
|
+
*
|
|
237
236
|
* Both routes share the configured `:name` path and optional token gate. The GET route replays
|
|
238
237
|
* pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The
|
|
239
238
|
* POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.
|
|
@@ -253,12 +252,11 @@ var TerminalRoutes = class {
|
|
|
253
252
|
* ```
|
|
254
253
|
*/
|
|
255
254
|
function createTerminalRoutes(manager, options) {
|
|
256
|
-
return new
|
|
255
|
+
return new TerminalBridge(manager, options).routes();
|
|
257
256
|
}
|
|
258
257
|
//#endregion
|
|
259
258
|
exports.TERMINAL_KEEPALIVE_MS = TERMINAL_KEEPALIVE_MS;
|
|
260
259
|
exports.TERMINAL_ROUTES_PATH = TERMINAL_ROUTES_PATH;
|
|
261
|
-
exports.TerminalRoutes = TerminalRoutes;
|
|
262
260
|
exports.createTerminalRoutes = createTerminalRoutes;
|
|
263
261
|
|
|
264
262
|
//# 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":[],"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) and POST (answer) routes under, `/terminals/:name`.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}\n * arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,\n * keeping intermediary proxies from timing out an 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 * The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url\n * streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer\n * `Result` response body, and the same `x-orkestrel-token` header.\n *\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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"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
2
|
-
import { TimerHandler } from '@orkestrel/terminal';
|
|
1
|
+
import type { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
2
|
+
import type { TimerHandler } from '@orkestrel/terminal';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
|
+
* The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url
|
|
9
|
+
* streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer
|
|
10
|
+
* `Result` response body, and the same `x-orkestrel-token` header.
|
|
11
|
+
*
|
|
8
12
|
* Both routes share the configured `:name` path and optional token gate. The GET route replays
|
|
9
13
|
* pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The
|
|
10
14
|
* POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.
|
|
@@ -25,72 +29,46 @@ import { TimerHandler } from '@orkestrel/terminal';
|
|
|
25
29
|
*/
|
|
26
30
|
export declare function createTerminalRoutes(manager: TerminalManagerInterface, options?: TerminalRoutesOptions): readonly TerminalRoute[];
|
|
27
31
|
|
|
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
32
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* otherwise-idle stream.
|
|
33
|
+
* Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}
|
|
34
|
+
* arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,
|
|
35
|
+
* keeping intermediary proxies from timing out an otherwise-idle stream.
|
|
36
36
|
*/
|
|
37
37
|
export declare const TERMINAL_KEEPALIVE_MS = 15000;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* mounts its GET (SSE)
|
|
40
|
+
* Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
|
|
41
|
+
* mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.
|
|
42
42
|
*/
|
|
43
43
|
export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
* plain `{ method, path, handler }` shape carrying
|
|
48
|
-
* `Route` type, so a consumer mounts it against any router that accepts a
|
|
49
|
-
* `(request, context) => Response | Promise<Response>` handler keyed by `method`
|
|
46
|
+
* Represents one structural route record {@link import('./factories.js').createTerminalRoutes}
|
|
47
|
+
* returns — a plain `{ method, path, handler }` shape carrying no dependency on
|
|
48
|
+
* `@orkestrel/router`'s own `Route` type, so a consumer mounts it against any router that accepts a
|
|
49
|
+
* two-arg `(request, context) => Response | Promise<Response>` handler keyed by `method` and
|
|
50
|
+
* `path`.
|
|
50
51
|
*/
|
|
51
52
|
export declare interface TerminalRoute {
|
|
52
|
-
readonly method:
|
|
53
|
+
readonly method: TerminalRouteMethod;
|
|
53
54
|
readonly path: string;
|
|
54
55
|
readonly handler: (request: Request, context: TerminalRouteContext) => Response | Promise<Response>;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
*
|
|
59
|
+
* Represents the minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
|
|
59
60
|
* URL-decoded `:name` path param slice a router hands a matched handler.
|
|
60
61
|
*/
|
|
61
62
|
export declare interface TerminalRouteContext {
|
|
62
63
|
readonly params: Readonly<Record<string, string>>;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
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
|
-
}
|
|
66
|
+
/** Represents the HTTP method literal a {@link TerminalRoute} declares — the same union `@orkestrel/router`'s `Method` type accepts. */
|
|
67
|
+
export declare type TerminalRouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
91
68
|
|
|
92
69
|
/**
|
|
93
|
-
*
|
|
70
|
+
* Represents the options {@link import('./factories.js').createTerminalRoutes} takes — the shared
|
|
71
|
+
* route, authorization, keepalive, timer, and body-limit configuration both routes read.
|
|
94
72
|
*
|
|
95
73
|
* @remarks
|
|
96
74
|
* - `path` — the shared `:name`-templated path both the GET (SSE) and POST (answer) routes
|
|
@@ -98,18 +76,18 @@ export declare class TerminalRoutes {
|
|
|
98
76
|
* - `token` — a {@link TerminalToken}: a string is compared for equality against the
|
|
99
77
|
* `x-orkestrel-token` header; a function receives the header's value (`undefined` when absent)
|
|
100
78
|
* and returns whether it validates, letting the consumer roll/expire tokens out-of-band.
|
|
101
|
-
* Validated at GET connect, on
|
|
79
|
+
* Validated at GET connect, on every POST, and re-validated on every keepalive tick of a live
|
|
102
80
|
* SSE stream — a stream whose presented token stops validating (rotated, expired, revoked) is
|
|
103
81
|
* torn down (the abort/self-heal teardown path, no `shutdown` frame) rather than left open
|
|
104
82
|
* forever; the client reconnects and re-authenticates. Omitted ⇒ no auth check. Because
|
|
105
83
|
* re-validation only happens on the keepalive tick, the revocation window equals the keepalive
|
|
106
84
|
* interval — a token rejected/expired between ticks keeps streaming until the next one. A
|
|
107
|
-
* validator function that
|
|
85
|
+
* validator function that throws is treated as rejection (fail-closed) at every call site.
|
|
108
86
|
* - `keepalive` — the SSE comment-ping interval in milliseconds; defaults to
|
|
109
87
|
* {@link import('./constants.js').TERMINAL_KEEPALIVE_MS}.
|
|
110
88
|
* - `timer` — the injected {@link TimerHandler} driving the keepalive interval (default the host
|
|
111
89
|
* `setTimeout`/`clearTimeout`), so a test drives the keepalive deterministically.
|
|
112
|
-
* - `limit` — the maximum POST answer body size in bytes, streamed and enforced
|
|
90
|
+
* - `limit` — the maximum POST answer body size in bytes, streamed and enforced before JSON
|
|
113
91
|
* parsing (ignoring any `Content-Length` header, so a lying header can never bypass the cap);
|
|
114
92
|
* a body exceeding it is rejected `413` and `manager.answer` is never called. Defaults to
|
|
115
93
|
* `@orkestrel/server`'s own `DEFAULT_BODY_LIMIT` (1 MiB).
|
|
@@ -123,10 +101,10 @@ export declare interface TerminalRoutesOptions {
|
|
|
123
101
|
}
|
|
124
102
|
|
|
125
103
|
/**
|
|
126
|
-
*
|
|
127
|
-
* equality against the `x-orkestrel-token` header,
|
|
128
|
-
* controls, enabling expiry
|
|
129
|
-
* time-varying)
|
|
104
|
+
* Represents the `token` gate {@link TerminalRoutesOptions} may configure — a plain string compared
|
|
105
|
+
* for equality against the `x-orkestrel-token` header, or a validator function the consumer fully
|
|
106
|
+
* controls, enabling the expiry and rotation a fixed string cannot express (a JWT `exp` check, a
|
|
107
|
+
* revocation-list lookup, anything time-varying). `undefined` disables the auth check entirely.
|
|
130
108
|
*/
|
|
131
109
|
export declare type TerminalToken = string | ((value: string | undefined) => boolean);
|
|
132
110
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
2
|
-
import { TimerHandler } from '@orkestrel/terminal';
|
|
1
|
+
import type { TerminalManagerInterface } from '@orkestrel/terminal';
|
|
2
|
+
import type { TimerHandler } from '@orkestrel/terminal';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
|
+
* The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url
|
|
9
|
+
* streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer
|
|
10
|
+
* `Result` response body, and the same `x-orkestrel-token` header.
|
|
11
|
+
*
|
|
8
12
|
* Both routes share the configured `:name` path and optional token gate. The GET route replays
|
|
9
13
|
* pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The
|
|
10
14
|
* POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.
|
|
@@ -25,72 +29,46 @@ import { TimerHandler } from '@orkestrel/terminal';
|
|
|
25
29
|
*/
|
|
26
30
|
export declare function createTerminalRoutes(manager: TerminalManagerInterface, options?: TerminalRoutesOptions): readonly TerminalRoute[];
|
|
27
31
|
|
|
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
32
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* otherwise-idle stream.
|
|
33
|
+
* Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}
|
|
34
|
+
* arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,
|
|
35
|
+
* keeping intermediary proxies from timing out an otherwise-idle stream.
|
|
36
36
|
*/
|
|
37
37
|
export declare const TERMINAL_KEEPALIVE_MS = 15000;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* mounts its GET (SSE)
|
|
40
|
+
* Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
|
|
41
|
+
* mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.
|
|
42
42
|
*/
|
|
43
43
|
export declare const TERMINAL_ROUTES_PATH = "/terminals/:name";
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
* plain `{ method, path, handler }` shape carrying
|
|
48
|
-
* `Route` type, so a consumer mounts it against any router that accepts a
|
|
49
|
-
* `(request, context) => Response | Promise<Response>` handler keyed by `method`
|
|
46
|
+
* Represents one structural route record {@link import('./factories.js').createTerminalRoutes}
|
|
47
|
+
* returns — a plain `{ method, path, handler }` shape carrying no dependency on
|
|
48
|
+
* `@orkestrel/router`'s own `Route` type, so a consumer mounts it against any router that accepts a
|
|
49
|
+
* two-arg `(request, context) => Response | Promise<Response>` handler keyed by `method` and
|
|
50
|
+
* `path`.
|
|
50
51
|
*/
|
|
51
52
|
export declare interface TerminalRoute {
|
|
52
|
-
readonly method:
|
|
53
|
+
readonly method: TerminalRouteMethod;
|
|
53
54
|
readonly path: string;
|
|
54
55
|
readonly handler: (request: Request, context: TerminalRouteContext) => Response | Promise<Response>;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
*
|
|
59
|
+
* Represents the minimal route-dispatch context a {@link TerminalRoute} handler reads — exactly the frozen,
|
|
59
60
|
* URL-decoded `:name` path param slice a router hands a matched handler.
|
|
60
61
|
*/
|
|
61
62
|
export declare interface TerminalRouteContext {
|
|
62
63
|
readonly params: Readonly<Record<string, string>>;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
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
|
-
}
|
|
66
|
+
/** Represents the HTTP method literal a {@link TerminalRoute} declares — the same union `@orkestrel/router`'s `Method` type accepts. */
|
|
67
|
+
export declare type TerminalRouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
91
68
|
|
|
92
69
|
/**
|
|
93
|
-
*
|
|
70
|
+
* Represents the options {@link import('./factories.js').createTerminalRoutes} takes — the shared
|
|
71
|
+
* route, authorization, keepalive, timer, and body-limit configuration both routes read.
|
|
94
72
|
*
|
|
95
73
|
* @remarks
|
|
96
74
|
* - `path` — the shared `:name`-templated path both the GET (SSE) and POST (answer) routes
|
|
@@ -98,18 +76,18 @@ export declare class TerminalRoutes {
|
|
|
98
76
|
* - `token` — a {@link TerminalToken}: a string is compared for equality against the
|
|
99
77
|
* `x-orkestrel-token` header; a function receives the header's value (`undefined` when absent)
|
|
100
78
|
* and returns whether it validates, letting the consumer roll/expire tokens out-of-band.
|
|
101
|
-
* Validated at GET connect, on
|
|
79
|
+
* Validated at GET connect, on every POST, and re-validated on every keepalive tick of a live
|
|
102
80
|
* SSE stream — a stream whose presented token stops validating (rotated, expired, revoked) is
|
|
103
81
|
* torn down (the abort/self-heal teardown path, no `shutdown` frame) rather than left open
|
|
104
82
|
* forever; the client reconnects and re-authenticates. Omitted ⇒ no auth check. Because
|
|
105
83
|
* re-validation only happens on the keepalive tick, the revocation window equals the keepalive
|
|
106
84
|
* interval — a token rejected/expired between ticks keeps streaming until the next one. A
|
|
107
|
-
* validator function that
|
|
85
|
+
* validator function that throws is treated as rejection (fail-closed) at every call site.
|
|
108
86
|
* - `keepalive` — the SSE comment-ping interval in milliseconds; defaults to
|
|
109
87
|
* {@link import('./constants.js').TERMINAL_KEEPALIVE_MS}.
|
|
110
88
|
* - `timer` — the injected {@link TimerHandler} driving the keepalive interval (default the host
|
|
111
89
|
* `setTimeout`/`clearTimeout`), so a test drives the keepalive deterministically.
|
|
112
|
-
* - `limit` — the maximum POST answer body size in bytes, streamed and enforced
|
|
90
|
+
* - `limit` — the maximum POST answer body size in bytes, streamed and enforced before JSON
|
|
113
91
|
* parsing (ignoring any `Content-Length` header, so a lying header can never bypass the cap);
|
|
114
92
|
* a body exceeding it is rejected `413` and `manager.answer` is never called. Defaults to
|
|
115
93
|
* `@orkestrel/server`'s own `DEFAULT_BODY_LIMIT` (1 MiB).
|
|
@@ -123,10 +101,10 @@ export declare interface TerminalRoutesOptions {
|
|
|
123
101
|
}
|
|
124
102
|
|
|
125
103
|
/**
|
|
126
|
-
*
|
|
127
|
-
* equality against the `x-orkestrel-token` header,
|
|
128
|
-
* controls, enabling expiry
|
|
129
|
-
* time-varying)
|
|
104
|
+
* Represents the `token` gate {@link TerminalRoutesOptions} may configure — a plain string compared
|
|
105
|
+
* for equality against the `x-orkestrel-token` header, or a validator function the consumer fully
|
|
106
|
+
* controls, enabling the expiry and rotation a fixed string cannot express (a JWT `exp` check, a
|
|
107
|
+
* revocation-list lookup, anything time-varying). `undefined` disables the auth check entirely.
|
|
130
108
|
*/
|
|
131
109
|
export declare type TerminalToken = string | ((value: string | undefined) => boolean);
|
|
132
110
|
|