@orkestrel/process 0.0.9 → 0.0.10
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 +13 -7
- package/dist/src/core/index.cjs +24 -13
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +155 -141
- package/dist/src/core/index.d.ts +155 -141
- package/dist/src/core/index.js +24 -13
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +134 -89
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +292 -77
- package/dist/src/server/index.d.ts +292 -77
- package/dist/src/server/index.js +134 -90
- package/dist/src/server/index.js.map +1 -1
- package/package.json +10 -10
package/dist/src/core/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { holds, isError } from "@orkestrel/contract";
|
|
2
2
|
//#region src/core/constants.ts
|
|
3
|
-
/**
|
|
3
|
+
/** Names the default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */
|
|
4
4
|
var PROCESS_GRACE = 5e3;
|
|
5
|
-
/**
|
|
5
|
+
/** Names the window in milliseconds a termination waits for the child's native exit after the final kill. */
|
|
6
6
|
var PROCESS_CONFIRMATION = 5e3;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Names the default window in milliseconds the package waits for the child's read ends to close after
|
|
9
9
|
* the child's native exit or after a termination this package initiated, before cutting them off.
|
|
10
10
|
*
|
|
11
11
|
* @remarks
|
|
@@ -20,17 +20,17 @@ var PROCESS_CONFIRMATION = 5e3;
|
|
|
20
20
|
* finite close measured, and it keeps an unreachable descendant from stretching a shutdown.
|
|
21
21
|
*/
|
|
22
22
|
var PROCESS_DRAIN = 1e3;
|
|
23
|
-
/**
|
|
23
|
+
/** Names the default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
|
|
24
24
|
var PROCESS_EVIDENCE = 2048;
|
|
25
|
-
/**
|
|
25
|
+
/** Names the default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */
|
|
26
26
|
var PROCESS_BACKLOG = 10485760;
|
|
27
|
-
/**
|
|
27
|
+
/** Names the default maximum captured bytes for a one-shot run's stdout and stderr, each. */
|
|
28
28
|
var PROCESS_OUTPUT = 10485760;
|
|
29
|
-
/**
|
|
29
|
+
/** Names the largest timer delay in milliseconds the host schedules without truncating it to one. */
|
|
30
30
|
var PROCESS_TIMER = 2147483647;
|
|
31
|
-
/**
|
|
31
|
+
/** Lists the executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */
|
|
32
32
|
var PROCESS_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
33
|
-
/**
|
|
33
|
+
/** Lists the machine-readable failure categories a {@link ProcessError} carries, in declaration order. */
|
|
34
34
|
var PROCESS_ERROR_CODES = Object.freeze([
|
|
35
35
|
"spawn",
|
|
36
36
|
"timeout",
|
|
@@ -41,15 +41,26 @@ var PROCESS_ERROR_CODES = Object.freeze([
|
|
|
41
41
|
]);
|
|
42
42
|
//#endregion
|
|
43
43
|
//#region src/core/errors.ts
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Represents a child-process failure with a stable machine-readable category.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const error = new ProcessError('git status refused', {
|
|
50
|
+
* code: 'invalid',
|
|
51
|
+
* context: { command: 'git status' },
|
|
52
|
+
* })
|
|
53
|
+
* error.code // 'invalid'
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
45
56
|
var ProcessError = class extends Error {
|
|
46
57
|
name = "ProcessError";
|
|
47
58
|
code;
|
|
48
59
|
context;
|
|
49
|
-
/**
|
|
60
|
+
/** Carries the buffered run outcome, present when a one-shot run produced the failure. */
|
|
50
61
|
result;
|
|
51
62
|
/**
|
|
52
|
-
*
|
|
63
|
+
* Creates a process error.
|
|
53
64
|
*
|
|
54
65
|
* @param message - Human-readable failure description
|
|
55
66
|
* @param options - Machine-readable category, optional context, optional cause, and optional run result
|
|
@@ -74,7 +85,7 @@ var ProcessError = class extends Error {
|
|
|
74
85
|
* plain or property-only lookalike.
|
|
75
86
|
*
|
|
76
87
|
* @param value - The value to inspect
|
|
77
|
-
* @returns True
|
|
88
|
+
* @returns True if the value is a `ProcessError` instance; false otherwise
|
|
78
89
|
*
|
|
79
90
|
* @example
|
|
80
91
|
* ```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/**
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** Names the default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 5_000\n\n/** Names the window in milliseconds a termination waits for the child's native exit after the final kill. */\nexport const PROCESS_CONFIRMATION = 5_000\n\n/**\n * Names the default window in milliseconds the package waits for the child's read ends to close after\n * the child's native exit or after a termination this package initiated, before cutting them off.\n *\n * @remarks\n * Measured on Windows 11 with Node v24.18.1 on 2026-08-21, spawning the `tests/src/server/fixtures`\n * child modes as real children and timing the host `close` event against the host `exit` event.\n * Every ordinary run closed within 0.02ms of the native exit, whether the child ended itself or a\n * kill ended it, and a `taskkill /F /T` that reaped a descendant while the root was still alive\n * closed within 0.01ms. The only finite late close came from a descendant that outlived its root and\n * then ended on its own, at 127.44ms to 193.41ms — that lag is the descendant's own remaining life\n * rather than a flush cost. A descendant that never ends never closes, so the distribution has no\n * finite tail to cover and this value bounds it instead: 1000ms is over five times the slowest\n * finite close measured, and it keeps an unreachable descendant from stretching a shutdown.\n */\nexport const PROCESS_DRAIN = 1_000\n\n/** Names the default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */\nexport const PROCESS_EVIDENCE = 2_048\n\n/** Names the default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */\nexport const PROCESS_BACKLOG = 10_485_760\n\n/** Names the default maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n\n/** Names the largest timer delay in milliseconds the host schedules without truncating it to one. */\nexport const PROCESS_TIMER = 2_147_483_647\n\n/** Lists the executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */\nexport const PROCESS_PATHEXT = '.COM;.EXE;.BAT;.CMD'\n\n/** Lists the machine-readable failure categories a {@link ProcessError} carries, in declaration order. */\nexport const PROCESS_ERROR_CODES = Object.freeze([\n\t'spawn',\n\t'timeout',\n\t'input',\n\t'duplicate',\n\t'protocol',\n\t'invalid',\n] as const)\n","import type {\n\tProcessErrorCode,\n\tProcessErrorContext,\n\tProcessErrorOptions,\n\tExecuteResult,\n} from './types.js'\nimport { holds, isError } from '@orkestrel/contract'\nimport { PROCESS_ERROR_CODES } from './constants.js'\n\n/**\n * Represents a child-process failure with a stable machine-readable category.\n *\n * @example\n * ```ts\n * const error = new ProcessError('git status refused', {\n * \tcode: 'invalid',\n * \tcontext: { command: 'git status' },\n * })\n * error.code // 'invalid'\n * ```\n */\nexport class ProcessError extends Error {\n\toverride readonly name = 'ProcessError'\n\treadonly code: ProcessErrorCode\n\treadonly context?: ProcessErrorContext\n\t/** Carries the buffered run outcome, present when a one-shot run produced the failure. */\n\treadonly result?: ExecuteResult\n\n\t/**\n\t * Creates a process error.\n\t *\n\t * @param message - Human-readable failure description\n\t * @param options - Machine-readable category, optional context, optional cause, and optional run result\n\t */\n\tconstructor(message: string, options: ProcessErrorOptions) {\n\t\tconst cause = options.cause\n\t\tsuper(message, cause === undefined ? undefined : { cause })\n\t\tObject.defineProperty(this, Symbol.for('@orkestrel/process.error'), { value: true })\n\t\tthis.code = options.code\n\t\tif (options.context !== undefined) this.context = options.context\n\t\tif (options.result !== undefined) this.result = options.result\n\t}\n}\n\n/**\n * Checks whether an unknown value is a {@link ProcessError}.\n *\n * @remarks\n * Recognition combines a global own-property brand with the native `Error` base, the subclass\n * prototype, the fixed name, and a code {@link PROCESS_ERROR_CODES} declares. The brand is\n * recognized across duplicate installations and ESM/CommonJS module copies at 0.0.4 or later. A copy\n * earlier than 0.0.4 stamps no brand, so an error it throws stays outside the type, and so does a\n * plain or property-only lookalike.\n *\n * @param value - The value to inspect\n * @returns True if the value is a `ProcessError` instance; false otherwise\n *\n * @example\n * ```ts\n * isProcessError(new ProcessError('spawn failed', { code: 'spawn' })) // true\n * isProcessError(new Error('spawn failed')) // false\n * ```\n */\nexport function isProcessError(value: unknown): value is ProcessError {\n\tif (!isError(value)) return false\n\treturn holds(() => {\n\t\tif (Object.getPrototypeOf(value) === Error.prototype) return false\n\t\tif (value.name !== 'ProcessError' || !('code' in value)) return false\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(\n\t\t\tvalue,\n\t\t\tSymbol.for('@orkestrel/process.error'),\n\t\t)\n\t\tif (descriptor?.value !== true) return false\n\t\tconst code: unknown = value.code\n\t\treturn PROCESS_ERROR_CODES.some((declared) => declared === code)\n\t})\n}\n\n/**\n * Creates the failure raised when a manager launch reuses a live id.\n *\n * @param id - The id already occupied by a live child\n * @returns A typed duplicate-id failure\n */\nexport function createDuplicateError(id: string): ProcessError {\n\treturn new ProcessError(`Process '${id}' is already live`, {\n\t\tcode: 'duplicate',\n\t\tcontext: { id },\n\t})\n}\n\n/**\n * Creates the failure raised when a launch is attempted on a registry that is being destroyed.\n *\n * @param id - The id the refused launch asked for\n * @returns A typed protocol failure\n */\nexport function createProtocolError(id: string): ProcessError {\n\treturn new ProcessError(`Process '${id}' cannot launch: the registry is destroyed`, {\n\t\tcode: 'protocol',\n\t\tcontext: { id },\n\t})\n}\n\n/**\n * Creates the failure raised when a public input is refused before anything is spawned.\n *\n * @param subject - The rejected input named as the caller wrote it, such as `option 'grace'`\n * @param value - The rejected value, carried as `context.value`\n * @returns A typed validation failure\n *\n * @example\n * ```ts\n * createInvalidError(\"option 'grace'\", -1).code // 'invalid'\n * ```\n */\nexport function createInvalidError(subject: string, value: unknown): ProcessError {\n\treturn new ProcessError(`Invalid ${subject}`, { code: 'invalid', context: { value } })\n}\n\n/**\n * Creates the failure raised when a run does not complete successfully and rejection is requested.\n *\n * @remarks\n * The category is `timeout` only when the run's own timeout elapsed; every other failure,\n * including an abort and an output overflow, is a `spawn` failure carrying its\n * {@link ExecuteResult}.\n *\n * @param result - The buffered run outcome that failed\n * @param cause - The underlying host fault, when one ended the run\n * @returns A typed run failure carrying its {@link ExecuteResult}\n */\nexport function createExecuteError(result: ExecuteResult, cause?: unknown): ProcessError {\n\tlet reason = `exited with code ${String(result.code)}`\n\tif (result.signal !== null) reason = `was killed by ${result.signal}`\n\tif (result.truncated) reason = 'exceeded its output limit'\n\tif (result.aborted) reason = 'was aborted'\n\tif (result.expired) reason = 'timed out'\n\treturn new ProcessError(`Command '${result.command}' ${reason}`, {\n\t\tcode: result.expired ? 'timeout' : 'spawn',\n\t\tcontext: { command: result.command, code: result.code, signal: result.signal },\n\t\t...(cause === undefined ? {} : { cause }),\n\t\tresult,\n\t})\n}\n"],"mappings":";;;AACA,IAAa,gBAAgB;;AAG7B,IAAa,uBAAuB;;;;;;;;;;;;;;;;AAiBpC,IAAa,gBAAgB;;AAG7B,IAAa,mBAAmB;;AAGhC,IAAa,kBAAkB;;AAG/B,IAAa,iBAAiB;;AAG9B,IAAa,gBAAgB;;AAG7B,IAAa,kBAAkB;;AAG/B,IAAa,sBAAsB,OAAO,OAAO;CAChD;CACA;CACA;CACA;CACA;CACA;AACD,CAAU;;;;;;;;;;;;;;;ACzBV,IAAa,eAAb,cAAkC,MAAM;CACvC,OAAyB;CACzB;CACA;;CAEA;;;;;;;CAQA,YAAY,SAAiB,SAA8B;EAC1D,MAAM,QAAQ,QAAQ;EACtB,MAAM,SAAS,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,MAAM,CAAC;EAC1D,OAAO,eAAe,MAAM,OAAO,IAAI,0BAA0B,GAAG,EAAE,OAAO,KAAK,CAAC;EACnF,KAAK,OAAO,QAAQ;EACpB,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;CACzD;AACD;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,OAAuC;CACrE,IAAI,CAAC,QAAQ,KAAK,GAAG,OAAO;CAC5B,OAAO,YAAY;EAClB,IAAI,OAAO,eAAe,KAAK,MAAM,MAAM,WAAW,OAAO;EAC7D,IAAI,MAAM,SAAS,kBAAkB,EAAE,UAAU,QAAQ,OAAO;EAKhE,IAJmB,OAAO,yBACzB,OACA,OAAO,IAAI,0BAA0B,CAElC,CAAA,EAAY,UAAU,MAAM,OAAO;EACvC,MAAM,OAAgB,MAAM;EAC5B,OAAO,oBAAoB,MAAM,aAAa,aAAa,IAAI;CAChE,CAAC;AACF;;;;;;;AAQA,SAAgB,qBAAqB,IAA0B;CAC9D,OAAO,IAAI,aAAa,YAAY,GAAG,oBAAoB;EAC1D,MAAM;EACN,SAAS,EAAE,GAAG;CACf,CAAC;AACF;;;;;;;AAQA,SAAgB,oBAAoB,IAA0B;CAC7D,OAAO,IAAI,aAAa,YAAY,GAAG,6CAA6C;EACnF,MAAM;EACN,SAAS,EAAE,GAAG;CACf,CAAC;AACF;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,SAAiB,OAA8B;CACjF,OAAO,IAAI,aAAa,WAAW,WAAW;EAAE,MAAM;EAAW,SAAS,EAAE,MAAM;CAAE,CAAC;AACtF;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,QAAuB,OAA+B;CACxF,IAAI,SAAS,oBAAoB,OAAO,OAAO,IAAI;CACnD,IAAI,OAAO,WAAW,MAAM,SAAS,iBAAiB,OAAO;CAC7D,IAAI,OAAO,WAAW,SAAS;CAC/B,IAAI,OAAO,SAAS,SAAS;CAC7B,IAAI,OAAO,SAAS,SAAS;CAC7B,OAAO,IAAI,aAAa,YAAY,OAAO,QAAQ,IAAI,UAAU;EAChE,MAAM,OAAO,UAAU,YAAY;EACnC,SAAS;GAAE,SAAS,OAAO;GAAS,MAAM,OAAO;GAAM,QAAQ,OAAO;EAAO;EAC7E,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;EACvC;CACD,CAAC;AACF"}
|
|
@@ -9,6 +9,42 @@ let _src_core = require("../core/index.cjs");
|
|
|
9
9
|
let node_readline = require("node:readline");
|
|
10
10
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
11
11
|
let node_string_decoder = require("node:string_decoder");
|
|
12
|
+
//#region src/server/cloners.ts
|
|
13
|
+
/**
|
|
14
|
+
* Takes one owned frozen snapshot of a caller's command.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Every public entry point snapshots before it validates, so the object validated is the object
|
|
18
|
+
* spawned. Each property is read exactly once, because reading one runs the caller's own getter: a
|
|
19
|
+
* command whose `file` changes between reads would otherwise validate one executable and spawn
|
|
20
|
+
* another. The argument vector and the environment record are copied and frozen, so a caller
|
|
21
|
+
* mutating either after the call cannot reach the spawn. An absent optional stays absent rather than
|
|
22
|
+
* becoming an explicit `undefined`.
|
|
23
|
+
*
|
|
24
|
+
* @param command - The caller's command, whose properties may be getters
|
|
25
|
+
* @returns A frozen command carrying the values read at this instant
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* snapshotCommand({ file: 'git', arguments: ['status'] }) // { file: 'git', arguments: ['status'] }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function snapshotCommand(command) {
|
|
33
|
+
const file = command.file;
|
|
34
|
+
const argumentsList = Object.freeze([...command.arguments]);
|
|
35
|
+
const sourceEnvironment = command.environment;
|
|
36
|
+
const environment = sourceEnvironment === void 0 ? void 0 : Object.freeze({ ...sourceEnvironment });
|
|
37
|
+
const input = command.input;
|
|
38
|
+
const isolated = command.isolated;
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
file,
|
|
41
|
+
arguments: argumentsList,
|
|
42
|
+
...environment === void 0 ? {} : { environment },
|
|
43
|
+
...input === void 0 ? {} : { input },
|
|
44
|
+
...isolated === void 0 ? {} : { isolated }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
12
48
|
//#region src/server/helpers.ts
|
|
13
49
|
/**
|
|
14
50
|
* Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
|
|
@@ -107,40 +143,6 @@ function captureChunk(chunk, room) {
|
|
|
107
143
|
return node_buffer.Buffer.from(chunk.subarray(0, room));
|
|
108
144
|
}
|
|
109
145
|
/**
|
|
110
|
-
* Takes one owned frozen snapshot of a caller's command.
|
|
111
|
-
*
|
|
112
|
-
* @remarks
|
|
113
|
-
* Every public entry point snapshots before it validates, so the object validated is the object
|
|
114
|
-
* spawned. Each property is read exactly once, because reading one runs the caller's own getter: a
|
|
115
|
-
* command whose `file` changes between reads would otherwise validate one executable and spawn
|
|
116
|
-
* another. The argument vector and the environment record are copied and frozen, so a caller
|
|
117
|
-
* mutating either after the call cannot reach the spawn. An absent optional stays absent rather than
|
|
118
|
-
* becoming an explicit `undefined`.
|
|
119
|
-
*
|
|
120
|
-
* @param command - The caller's command, whose properties may be getters
|
|
121
|
-
* @returns A frozen command carrying the values read at this instant
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* snapshotCommand({ file: 'git', arguments: ['status'] }) // { file: 'git', arguments: ['status'] }
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
function snapshotCommand(command) {
|
|
129
|
-
const file = command.file;
|
|
130
|
-
const argumentsList = Object.freeze([...command.arguments]);
|
|
131
|
-
const sourceEnvironment = command.environment;
|
|
132
|
-
const environment = sourceEnvironment === void 0 ? void 0 : Object.freeze({ ...sourceEnvironment });
|
|
133
|
-
const input = command.input;
|
|
134
|
-
const isolated = command.isolated;
|
|
135
|
-
return Object.freeze({
|
|
136
|
-
file,
|
|
137
|
-
arguments: argumentsList,
|
|
138
|
-
...environment === void 0 ? {} : { environment },
|
|
139
|
-
...input === void 0 ? {} : { input },
|
|
140
|
-
...isolated === void 0 ? {} : { isolated }
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
146
|
* Renders one command into its diagnostic command line.
|
|
145
147
|
*
|
|
146
148
|
* @param command - The executable and its argument vector
|
|
@@ -257,7 +259,7 @@ function mergeEnvironment(isolated, base, override) {
|
|
|
257
259
|
* command. An unreadable or malformed path reports `false` rather than throwing.
|
|
258
260
|
*
|
|
259
261
|
* @param target - The absolute or relative path to inspect
|
|
260
|
-
* @returns True
|
|
262
|
+
* @returns True if the path resolves to a regular file; false otherwise
|
|
261
263
|
*
|
|
262
264
|
* @example
|
|
263
265
|
* ```ts
|
|
@@ -530,7 +532,7 @@ function validateWorkspace(workspace) {
|
|
|
530
532
|
* initiated against a pid the host may have reused.
|
|
531
533
|
*
|
|
532
534
|
* @param child - The child boundary
|
|
533
|
-
* @returns True
|
|
535
|
+
* @returns True if the process has exited; false otherwise
|
|
534
536
|
*
|
|
535
537
|
* @example
|
|
536
538
|
* ```ts
|
|
@@ -586,7 +588,7 @@ function killProcess(child, signal) {
|
|
|
586
588
|
*
|
|
587
589
|
* @param pid - The root process id of the tree to end
|
|
588
590
|
* @param timeout - The milliseconds the utility is given before it is killed
|
|
589
|
-
* @returns True
|
|
591
|
+
* @returns True if the utility reported success; false otherwise (it failed, was unavailable, or was cut off)
|
|
590
592
|
*
|
|
591
593
|
* @example
|
|
592
594
|
* ```ts
|
|
@@ -637,7 +639,7 @@ function waitForExit(child, timeout) {
|
|
|
637
639
|
*
|
|
638
640
|
* @param child - The child boundary
|
|
639
641
|
* @param timeout - The milliseconds to wait before giving up
|
|
640
|
-
* @returns True
|
|
642
|
+
* @returns True if the host close arrived inside the deadline; false otherwise
|
|
641
643
|
*
|
|
642
644
|
* @example
|
|
643
645
|
* ```ts
|
|
@@ -672,7 +674,7 @@ function waitForClose(child, timeout) {
|
|
|
672
674
|
* @param child - The child boundary
|
|
673
675
|
* @param grace - The cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`
|
|
674
676
|
* @param confirm - The milliseconds the native exit is awaited after the final kill
|
|
675
|
-
* @returns True
|
|
677
|
+
* @returns True if the native exit was observed; false otherwise (`confirm` elapsed without it)
|
|
676
678
|
*
|
|
677
679
|
* @example
|
|
678
680
|
* ```ts
|
|
@@ -1030,7 +1032,7 @@ function detach(command, options) {
|
|
|
1030
1032
|
child.unref();
|
|
1031
1033
|
}
|
|
1032
1034
|
//#endregion
|
|
1033
|
-
//#region src/server/Supervisor.ts
|
|
1035
|
+
//#region src/server/processes/Supervisor.ts
|
|
1034
1036
|
/**
|
|
1035
1037
|
* Supervises one child process and reports each lifecycle moment to the face composing it.
|
|
1036
1038
|
*
|
|
@@ -1059,6 +1061,25 @@ function detach(command, options) {
|
|
|
1059
1061
|
* termination completes. `relieve` is optional because a face that never pauses the child's output
|
|
1060
1062
|
* holds no backpressure to release, and a face with nothing to do there would otherwise declare an
|
|
1061
1063
|
* empty method to say so.
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```ts
|
|
1067
|
+
* import { Supervisor } from '@orkestrel/process/server'
|
|
1068
|
+
*
|
|
1069
|
+
* const engine = new Supervisor(
|
|
1070
|
+
* { command: { file: 'node', arguments: ['--version'] }, workspace: process.cwd() },
|
|
1071
|
+
* {
|
|
1072
|
+
* chunk: () => undefined,
|
|
1073
|
+
* fault: () => undefined,
|
|
1074
|
+
* close: () => undefined,
|
|
1075
|
+
* terminal: () => undefined,
|
|
1076
|
+
* teardown: () => undefined,
|
|
1077
|
+
* },
|
|
1078
|
+
* )
|
|
1079
|
+
* const exit = await engine.exit
|
|
1080
|
+
* exit.code // 0
|
|
1081
|
+
* await engine.destroy()
|
|
1082
|
+
* ```
|
|
1062
1083
|
*/
|
|
1063
1084
|
var Supervisor = class {
|
|
1064
1085
|
#chunk;
|
|
@@ -1088,7 +1109,7 @@ var Supervisor = class {
|
|
|
1088
1109
|
#termination;
|
|
1089
1110
|
#destruction;
|
|
1090
1111
|
/**
|
|
1091
|
-
*
|
|
1112
|
+
* Spawns one child process and begins standard-error capture.
|
|
1092
1113
|
*
|
|
1093
1114
|
* @remarks
|
|
1094
1115
|
* The face's callbacks are captured before anything is read or spawned, so the first moment the
|
|
@@ -1165,36 +1186,36 @@ var Supervisor = class {
|
|
|
1165
1186
|
if (signal.aborted) this.stop();
|
|
1166
1187
|
}
|
|
1167
1188
|
}
|
|
1168
|
-
/**
|
|
1189
|
+
/** Holds the child's standard-output stream, for the composing face to attach its own consumer to. */
|
|
1169
1190
|
get stdout() {
|
|
1170
1191
|
return this.#child.stdout;
|
|
1171
1192
|
}
|
|
1172
|
-
/**
|
|
1193
|
+
/** Holds the host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
|
|
1173
1194
|
get pid() {
|
|
1174
1195
|
return this.#child.pid;
|
|
1175
1196
|
}
|
|
1176
|
-
/**
|
|
1197
|
+
/** Holds the exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
|
|
1177
1198
|
get code() {
|
|
1178
1199
|
return this.#child.exitCode;
|
|
1179
1200
|
}
|
|
1180
|
-
/**
|
|
1201
|
+
/** Holds the terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
|
|
1181
1202
|
get signal() {
|
|
1182
1203
|
return this.#child.signalCode;
|
|
1183
1204
|
}
|
|
1184
|
-
/**
|
|
1205
|
+
/** Holds the decoded byte-bounded stderr tail, frozen at the terminal moment. */
|
|
1185
1206
|
get evidence() {
|
|
1186
1207
|
return this.#tail.toString("utf8");
|
|
1187
1208
|
}
|
|
1188
|
-
/**
|
|
1209
|
+
/** Reports whether the terminal moment arrived and `exit` settled. */
|
|
1189
1210
|
get settled() {
|
|
1190
1211
|
return this.#settled;
|
|
1191
1212
|
}
|
|
1192
|
-
/**
|
|
1213
|
+
/** Reports whether termination began, including after the terminal moment. */
|
|
1193
1214
|
get stopping() {
|
|
1194
1215
|
return this.#stopping;
|
|
1195
1216
|
}
|
|
1196
1217
|
/**
|
|
1197
|
-
*
|
|
1218
|
+
* Settles at the child's own ending, awaited without the terminal moment's drain window.
|
|
1198
1219
|
*
|
|
1199
1220
|
* @remarks
|
|
1200
1221
|
* Never rejects, and resolves no value: `code` and `signal` carry the terminal facts as soon as
|
|
@@ -1204,7 +1225,7 @@ var Supervisor = class {
|
|
|
1204
1225
|
get ending() {
|
|
1205
1226
|
return this.#ending.promise;
|
|
1206
1227
|
}
|
|
1207
|
-
/**
|
|
1228
|
+
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
1208
1229
|
get exit() {
|
|
1209
1230
|
return this.#exit.promise;
|
|
1210
1231
|
}
|
|
@@ -1221,7 +1242,30 @@ var Supervisor = class {
|
|
|
1221
1242
|
* discard.
|
|
1222
1243
|
*
|
|
1223
1244
|
* @param bytes - The payload to write, already framed by the caller
|
|
1224
|
-
* @returns True
|
|
1245
|
+
* @returns True if the host accepted the bytes without reporting a fault; false otherwise (the channel was closed, destroyed, ended, failed, or the write remained unconfirmed through `delivery`)
|
|
1246
|
+
*
|
|
1247
|
+
* @example
|
|
1248
|
+
* ```ts
|
|
1249
|
+
* import { Supervisor } from '@orkestrel/process/server'
|
|
1250
|
+
*
|
|
1251
|
+
* const engine = new Supervisor(
|
|
1252
|
+
* {
|
|
1253
|
+
* command: { file: 'node', arguments: ['-e', 'process.stdin.pipe(process.stdout)'] },
|
|
1254
|
+
* workspace: process.cwd(),
|
|
1255
|
+
* writable: true,
|
|
1256
|
+
* },
|
|
1257
|
+
* {
|
|
1258
|
+
* chunk: () => undefined,
|
|
1259
|
+
* fault: () => undefined,
|
|
1260
|
+
* close: () => undefined,
|
|
1261
|
+
* terminal: () => undefined,
|
|
1262
|
+
* teardown: () => undefined,
|
|
1263
|
+
* },
|
|
1264
|
+
* )
|
|
1265
|
+
* const accepted = await engine.deliver(new TextEncoder().encode('ping\n'))
|
|
1266
|
+
* accepted // true
|
|
1267
|
+
* await engine.destroy()
|
|
1268
|
+
* ```
|
|
1225
1269
|
*/
|
|
1226
1270
|
deliver(bytes) {
|
|
1227
1271
|
const stdin = this.#child.stdin;
|
|
@@ -1272,7 +1316,7 @@ var Supervisor = class {
|
|
|
1272
1316
|
* and settles `exit` when they remain open. A cutoff reached before the native exit reports the
|
|
1273
1317
|
* `code` and `signal` the host had recorded by then, which is `null` for a child still running.
|
|
1274
1318
|
*
|
|
1275
|
-
* @returns True
|
|
1319
|
+
* @returns True if the child's native exit was observed; false otherwise (the confirmation deadline elapsed without it)
|
|
1276
1320
|
*/
|
|
1277
1321
|
stop() {
|
|
1278
1322
|
if (this.#termination !== void 0) return this.#termination;
|
|
@@ -1400,7 +1444,7 @@ var Supervisor = class {
|
|
|
1400
1444
|
}
|
|
1401
1445
|
};
|
|
1402
1446
|
//#endregion
|
|
1403
|
-
//#region src/server/Process.ts
|
|
1447
|
+
//#region src/server/processes/Process.ts
|
|
1404
1448
|
/**
|
|
1405
1449
|
* Supervises one child while keeping every observation channel aligned at termination.
|
|
1406
1450
|
*
|
|
@@ -1447,7 +1491,7 @@ var Process = class {
|
|
|
1447
1491
|
#truncated = false;
|
|
1448
1492
|
#ended = false;
|
|
1449
1493
|
/**
|
|
1450
|
-
*
|
|
1494
|
+
* Spawns one child process and begins stream capture.
|
|
1451
1495
|
*
|
|
1452
1496
|
* @param options - Command, workspace, termination, capture, stdin, and observation settings
|
|
1453
1497
|
* @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
|
|
@@ -1502,48 +1546,48 @@ var Process = class {
|
|
|
1502
1546
|
this.#reader.on("line", this.#push.bind(this));
|
|
1503
1547
|
this.#reader.once("close", this.#finish.bind(this));
|
|
1504
1548
|
}
|
|
1505
|
-
/**
|
|
1549
|
+
/** Holds the host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
|
|
1506
1550
|
get pid() {
|
|
1507
1551
|
return this.#engine.pid;
|
|
1508
1552
|
}
|
|
1509
|
-
/**
|
|
1553
|
+
/** Holds the exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
|
|
1510
1554
|
get code() {
|
|
1511
1555
|
return this.#engine.code;
|
|
1512
1556
|
}
|
|
1513
|
-
/**
|
|
1557
|
+
/** Holds the terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
|
|
1514
1558
|
get signal() {
|
|
1515
1559
|
return this.#engine.signal;
|
|
1516
1560
|
}
|
|
1517
|
-
/**
|
|
1561
|
+
/** Holds the typed lifecycle observation surface. */
|
|
1518
1562
|
get emitter() {
|
|
1519
1563
|
return this.#emitter;
|
|
1520
1564
|
}
|
|
1521
|
-
/**
|
|
1565
|
+
/** Yields the captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
|
|
1522
1566
|
get lines() {
|
|
1523
1567
|
return this.#lines;
|
|
1524
1568
|
}
|
|
1525
|
-
/**
|
|
1569
|
+
/** Holds the decoded byte-bounded stderr tail, frozen at the terminal moment. */
|
|
1526
1570
|
get evidence() {
|
|
1527
1571
|
return this.#engine.evidence;
|
|
1528
1572
|
}
|
|
1529
|
-
/**
|
|
1573
|
+
/** Reports whether the `lines` stream omitted output after a retention bound was reached. */
|
|
1530
1574
|
get truncated() {
|
|
1531
1575
|
return this.#truncated;
|
|
1532
1576
|
}
|
|
1533
|
-
/**
|
|
1577
|
+
/** Reports whether the terminal moment arrived and `exit` settled. */
|
|
1534
1578
|
get settled() {
|
|
1535
1579
|
return this.#engine.settled;
|
|
1536
1580
|
}
|
|
1537
|
-
/**
|
|
1581
|
+
/** Reports whether termination began, including after the terminal moment. */
|
|
1538
1582
|
get stopping() {
|
|
1539
1583
|
return this.#engine.stopping;
|
|
1540
1584
|
}
|
|
1541
|
-
/**
|
|
1585
|
+
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
1542
1586
|
get exit() {
|
|
1543
1587
|
return this.#engine.exit;
|
|
1544
1588
|
}
|
|
1545
1589
|
/**
|
|
1546
|
-
*
|
|
1590
|
+
* Writes one line to the open standard-input channel.
|
|
1547
1591
|
*
|
|
1548
1592
|
* @remarks
|
|
1549
1593
|
* Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
|
|
@@ -1557,7 +1601,7 @@ var Process = class {
|
|
|
1557
1601
|
* for bytes the package is about to discard.
|
|
1558
1602
|
*
|
|
1559
1603
|
* @param text - The line text without its trailing newline
|
|
1560
|
-
* @returns True
|
|
1604
|
+
* @returns True if the host accepted the bytes without reporting a fault; false otherwise (the channel was closed, destroyed, ended, failed, or the write remained unconfirmed through `delivery`)
|
|
1561
1605
|
*/
|
|
1562
1606
|
send(text) {
|
|
1563
1607
|
return this.#engine.deliver(node_buffer.Buffer.from(`${text}\n`, "utf8"));
|
|
@@ -1571,7 +1615,7 @@ var Process = class {
|
|
|
1571
1615
|
* and settles `exit` when they remain open. A cutoff reached before the native exit reports the
|
|
1572
1616
|
* `code` and `signal` the host had recorded by then, which is `null` for a child still running.
|
|
1573
1617
|
*
|
|
1574
|
-
* @returns True
|
|
1618
|
+
* @returns True if the child's native exit was observed; false otherwise (the confirmation deadline elapsed without it)
|
|
1575
1619
|
*/
|
|
1576
1620
|
stop() {
|
|
1577
1621
|
return this.#engine.stop();
|
|
@@ -1689,9 +1733,9 @@ var Process = class {
|
|
|
1689
1733
|
}
|
|
1690
1734
|
};
|
|
1691
1735
|
//#endregion
|
|
1692
|
-
//#region src/server/ProcessManager.ts
|
|
1736
|
+
//#region src/server/processes/ProcessManager.ts
|
|
1693
1737
|
/**
|
|
1694
|
-
*
|
|
1738
|
+
* Represents a keyed registry of live supervised child processes.
|
|
1695
1739
|
*
|
|
1696
1740
|
* @remarks
|
|
1697
1741
|
* A child launched under an id joins the registry and emits `launch`; when it settles it removes
|
|
@@ -1727,7 +1771,7 @@ var ProcessManager = class {
|
|
|
1727
1771
|
#destroying = false;
|
|
1728
1772
|
#ending;
|
|
1729
1773
|
/**
|
|
1730
|
-
*
|
|
1774
|
+
* Constructs a process registry.
|
|
1731
1775
|
*
|
|
1732
1776
|
* @param options - Initial fleet-level observation hooks and listener-error handling
|
|
1733
1777
|
*/
|
|
@@ -1739,16 +1783,16 @@ var ProcessManager = class {
|
|
|
1739
1783
|
...error === void 0 ? {} : { error }
|
|
1740
1784
|
});
|
|
1741
1785
|
}
|
|
1742
|
-
/**
|
|
1786
|
+
/** Holds the typed fleet-level observation surface. */
|
|
1743
1787
|
get emitter() {
|
|
1744
1788
|
return this.#emitter;
|
|
1745
1789
|
}
|
|
1746
|
-
/**
|
|
1790
|
+
/** Counts the live children. */
|
|
1747
1791
|
get count() {
|
|
1748
1792
|
return this.#children.size;
|
|
1749
1793
|
}
|
|
1750
1794
|
/**
|
|
1751
|
-
*
|
|
1795
|
+
* Returns the live child under `id`, or `undefined` when none is.
|
|
1752
1796
|
*
|
|
1753
1797
|
* @param id - The registry key
|
|
1754
1798
|
* @returns The child, or `undefined`
|
|
@@ -1757,7 +1801,7 @@ var ProcessManager = class {
|
|
|
1757
1801
|
return this.#children.get(id);
|
|
1758
1802
|
}
|
|
1759
1803
|
/**
|
|
1760
|
-
*
|
|
1804
|
+
* Returns a snapshot of every live child.
|
|
1761
1805
|
*
|
|
1762
1806
|
* @returns The live children in launch order
|
|
1763
1807
|
*/
|
|
@@ -1765,7 +1809,7 @@ var ProcessManager = class {
|
|
|
1765
1809
|
return [...this.#children.values()];
|
|
1766
1810
|
}
|
|
1767
1811
|
/**
|
|
1768
|
-
*
|
|
1812
|
+
* Spawns and registers one child under `id`.
|
|
1769
1813
|
*
|
|
1770
1814
|
* @remarks
|
|
1771
1815
|
* {@link Process} reads every option before it spawns, so a caller's own option getter runs while
|
|
@@ -1803,7 +1847,7 @@ var ProcessManager = class {
|
|
|
1803
1847
|
return this.#stopMany(target);
|
|
1804
1848
|
}
|
|
1805
1849
|
/**
|
|
1806
|
-
*
|
|
1850
|
+
* Stops every child, then destroys the registry emitter last.
|
|
1807
1851
|
*
|
|
1808
1852
|
* @remarks
|
|
1809
1853
|
* Always resolves, and refuses a later `launch` with a {@link createProtocolError}. Each child is
|
|
@@ -1856,7 +1900,7 @@ var ProcessManager = class {
|
|
|
1856
1900
|
}
|
|
1857
1901
|
};
|
|
1858
1902
|
//#endregion
|
|
1859
|
-
//#region src/server/Session.ts
|
|
1903
|
+
//#region src/server/processes/Session.ts
|
|
1860
1904
|
/**
|
|
1861
1905
|
* Supervises one child and publishes its standard output as raw bytes.
|
|
1862
1906
|
*
|
|
@@ -1893,7 +1937,7 @@ var Session = class {
|
|
|
1893
1937
|
#engine;
|
|
1894
1938
|
#output;
|
|
1895
1939
|
/**
|
|
1896
|
-
*
|
|
1940
|
+
* Spawns one child process and begins byte capture.
|
|
1897
1941
|
*
|
|
1898
1942
|
* @param options - Command, workspace, termination, evidence, stdin, and observation settings
|
|
1899
1943
|
* @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
|
|
@@ -1931,39 +1975,39 @@ var Session = class {
|
|
|
1931
1975
|
});
|
|
1932
1976
|
this.#engine.stdout.on("data", this.#output);
|
|
1933
1977
|
}
|
|
1934
|
-
/**
|
|
1978
|
+
/** Holds the host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
|
|
1935
1979
|
get pid() {
|
|
1936
1980
|
return this.#engine.pid;
|
|
1937
1981
|
}
|
|
1938
|
-
/**
|
|
1982
|
+
/** Holds the exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
|
|
1939
1983
|
get code() {
|
|
1940
1984
|
return this.#engine.code;
|
|
1941
1985
|
}
|
|
1942
|
-
/**
|
|
1986
|
+
/** Holds the terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
|
|
1943
1987
|
get signal() {
|
|
1944
1988
|
return this.#engine.signal;
|
|
1945
1989
|
}
|
|
1946
|
-
/**
|
|
1990
|
+
/** Holds the typed lifecycle observation surface. */
|
|
1947
1991
|
get emitter() {
|
|
1948
1992
|
return this.#emitter;
|
|
1949
1993
|
}
|
|
1950
|
-
/**
|
|
1994
|
+
/** Holds the decoded byte-bounded stderr tail, frozen at the terminal moment. */
|
|
1951
1995
|
get evidence() {
|
|
1952
1996
|
return this.#engine.evidence;
|
|
1953
1997
|
}
|
|
1954
|
-
/**
|
|
1998
|
+
/** Reports whether the terminal moment arrived and `exit` settled. */
|
|
1955
1999
|
get settled() {
|
|
1956
2000
|
return this.#engine.settled;
|
|
1957
2001
|
}
|
|
1958
|
-
/**
|
|
2002
|
+
/** Reports whether termination began, including after the terminal moment. `end` never turns it true. */
|
|
1959
2003
|
get stopping() {
|
|
1960
2004
|
return this.#engine.stopping;
|
|
1961
2005
|
}
|
|
1962
|
-
/**
|
|
2006
|
+
/** Settles at the child's own ending, awaited without the terminal moment's drain window. */
|
|
1963
2007
|
get ending() {
|
|
1964
2008
|
return this.#engine.ending;
|
|
1965
2009
|
}
|
|
1966
|
-
/**
|
|
2010
|
+
/** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
|
|
1967
2011
|
get exit() {
|
|
1968
2012
|
return this.#engine.exit;
|
|
1969
2013
|
}
|
|
@@ -1977,7 +2021,7 @@ var Session = class {
|
|
|
1977
2021
|
* returned promise settles.
|
|
1978
2022
|
*
|
|
1979
2023
|
* @param bytes - The payload to write, already framed by the caller
|
|
1980
|
-
* @returns True
|
|
2024
|
+
* @returns True if the host accepted the bytes without reporting a fault; false otherwise (the channel was closed, destroyed, ended, failed, or the write remained unconfirmed through `delivery`)
|
|
1981
2025
|
*/
|
|
1982
2026
|
write(bytes) {
|
|
1983
2027
|
return this.#engine.deliver(bytes);
|
|
@@ -2001,7 +2045,7 @@ var Session = class {
|
|
|
2001
2045
|
* @remarks
|
|
2002
2046
|
* Never rejects, and every call shares one termination bounded by `grace` and `drain`.
|
|
2003
2047
|
*
|
|
2004
|
-
* @returns True
|
|
2048
|
+
* @returns True if the child's native exit was observed; false otherwise (the confirmation deadline elapsed without it)
|
|
2005
2049
|
*/
|
|
2006
2050
|
stop() {
|
|
2007
2051
|
return this.#engine.stop();
|
|
@@ -2096,6 +2140,7 @@ function createProcessManager(options) {
|
|
|
2096
2140
|
exports.Process = Process;
|
|
2097
2141
|
exports.ProcessManager = ProcessManager;
|
|
2098
2142
|
exports.Session = Session;
|
|
2143
|
+
exports.Supervisor = Supervisor;
|
|
2099
2144
|
exports.buildExecutableCandidates = buildExecutableCandidates;
|
|
2100
2145
|
exports.buildExecuteResult = buildExecuteResult;
|
|
2101
2146
|
exports.buildPlatformSpawn = buildPlatformSpawn;
|