@orkestrel/process 0.0.4 → 0.0.5
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/dist/src/core/index.cjs +1 -0
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +39 -17
- package/dist/src/core/index.d.ts +39 -17
- package/dist/src/core/index.js +1 -0
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +108 -21
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +22 -10
- package/dist/src/server/index.d.ts +22 -10
- package/dist/src/server/index.js +109 -22
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -9
package/dist/src/core/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** The default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 5_000\n\n/** 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/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */\nexport const PROCESS_EVIDENCE = 2_048\n\n/** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */\nexport const PROCESS_BACKLOG = 10_485_760\n\n/** The default maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n\n/** The largest timer delay in milliseconds the host schedules without truncating it to one. */\nexport const PROCESS_TIMER = 2_147_483_647\n\n/** The executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */\nexport const PROCESS_PATHEXT = '.COM;.EXE;.BAT;.CMD'\n\n/** 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'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/** A child-process failure with a stable machine-readable category. */\nexport class ProcessError extends Error {\n\toverride readonly name = 'ProcessError'\n\treadonly code: ProcessErrorCode\n\treadonly context?: ProcessErrorContext\n\t/** The buffered run outcome, present when a one-shot run produced the failure. */\n\treadonly result?: ExecuteResult\n\n\t/**\n\t * Create 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 only for 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;;AAGpC,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;AACD,CAAU;;;;
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** The default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 5_000\n\n/** 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/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */\nexport const PROCESS_EVIDENCE = 2_048\n\n/** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */\nexport const PROCESS_BACKLOG = 10_485_760\n\n/** The default maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n\n/** The largest timer delay in milliseconds the host schedules without truncating it to one. */\nexport const PROCESS_TIMER = 2_147_483_647\n\n/** The executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */\nexport const PROCESS_PATHEXT = '.COM;.EXE;.BAT;.CMD'\n\n/** 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/** A child-process failure with a stable machine-readable category. */\nexport class ProcessError extends Error {\n\toverride readonly name = 'ProcessError'\n\treadonly code: ProcessErrorCode\n\treadonly context?: ProcessErrorContext\n\t/** The buffered run outcome, present when a one-shot run produced the failure. */\n\treadonly result?: ExecuteResult\n\n\t/**\n\t * Create 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 only for 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;;AAGpC,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;;;;ACnBV,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,EAAA,GAAC,oBAAA,QAAA,CAAQ,KAAK,GAAG,OAAO;CAC5B,QAAA,GAAO,oBAAA,MAAA,OAAY;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"}
|
|
@@ -139,13 +139,15 @@ export declare interface ExecuteOptions {
|
|
|
139
139
|
* The settled outcome of a one-shot run: the buffered output and the terminal state.
|
|
140
140
|
*
|
|
141
141
|
* @remarks
|
|
142
|
-
* `failed` is `true` when the
|
|
143
|
-
*
|
|
144
|
-
* child ending itself, and only the earliest observed is recorded.
|
|
145
|
-
*
|
|
146
|
-
* synchronous run and does not fail an asynchronous one. `ProcessInterface` carries the
|
|
147
|
-
* the same fact against a supervised child's retention bounds.
|
|
148
|
-
* `
|
|
142
|
+
* `failed` is `true` when the run timed out, was aborted, ended on a host fault, was ended by a
|
|
143
|
+
* signal, or exited with a code other than `0`. `expired` and `aborted` name the ways the run ended
|
|
144
|
+
* the child rather than the child ending itself, and only the earliest observed is recorded.
|
|
145
|
+
* `truncated` reports that a captured stream omitted output because it exceeded `limit`, which
|
|
146
|
+
* fails a synchronous run and does not fail an asynchronous one. `ProcessInterface` carries the
|
|
147
|
+
* same name for the same fact against a supervised child's retention bounds. For a `strict: false`
|
|
148
|
+
* caller, `failed: true` with `expired`, `aborted`, and `truncated` false, `code: 0`, and
|
|
149
|
+
* `signal: null` is the residual signature that a host fault ended the run. A spawn fault reports
|
|
150
|
+
* the host's negative errno for `execute`. A spawn fault reports `null` for `executeSync`.
|
|
149
151
|
*/
|
|
150
152
|
export declare interface ExecuteResult {
|
|
151
153
|
/** The command line that was run, for diagnostics. */
|
|
@@ -221,7 +223,7 @@ export declare const PROCESS_BACKLOG = 10485760;
|
|
|
221
223
|
export declare const PROCESS_CONFIRMATION = 5000;
|
|
222
224
|
|
|
223
225
|
/** The machine-readable failure categories a {@link ProcessError} carries, in declaration order. */
|
|
224
|
-
export declare const PROCESS_ERROR_CODES: readonly ["spawn", "timeout", "duplicate", "protocol", "invalid"];
|
|
226
|
+
export declare const PROCESS_ERROR_CODES: readonly ["spawn", "timeout", "input", "duplicate", "protocol", "invalid"];
|
|
225
227
|
|
|
226
228
|
/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
|
|
227
229
|
export declare const PROCESS_EVIDENCE = 2048;
|
|
@@ -330,14 +332,18 @@ export declare interface ProcessErrorOptions {
|
|
|
330
332
|
*
|
|
331
333
|
* @remarks
|
|
332
334
|
* Declared as a `type` alias so it satisfies the emitter's `EventMap` constraint structurally. The
|
|
333
|
-
* `error` event carries a child fault — a failure to spawn
|
|
334
|
-
*
|
|
335
|
+
* `error` event carries a child fault — a failure to spawn, a process-level error, or a
|
|
336
|
+
* host-reported standard-input channel fault after the constructor input phase. A fault arising
|
|
337
|
+
* from the constructor-supplied `input` write or its closing `end` stays quiet because the package
|
|
338
|
+
* initiated that sequence. A surfaced standard-input fault is wrapped in a
|
|
339
|
+
* {@link ProcessError} coded `protocol` with the host fault as its cause. The event is distinct from
|
|
340
|
+
* the `error` handler in {@link ProcessOptions}: a listener throw is isolated by the emitter and
|
|
335
341
|
* routed to that handler, never emitted as this `error` event.
|
|
336
342
|
*/
|
|
337
343
|
export declare type ProcessEventMap = {
|
|
338
344
|
/** A decoded standard-error chunk arrived. */
|
|
339
345
|
readonly stderr: readonly [chunk: string];
|
|
340
|
-
/** The child
|
|
346
|
+
/** The child or its open standard-input channel reported a fault, carrying the host cause directly or through a `protocol` {@link ProcessError}. */
|
|
341
347
|
readonly error: readonly [error: unknown];
|
|
342
348
|
/** The child settled — its terminal state, delivered once. */
|
|
343
349
|
readonly exit: readonly [exit: ProcessExit];
|
|
@@ -387,7 +393,16 @@ export declare interface ProcessInterface {
|
|
|
387
393
|
readonly signal: string | null;
|
|
388
394
|
/** The typed lifecycle observation surface. */
|
|
389
395
|
readonly emitter: EmitterInterface<ProcessEventMap>;
|
|
390
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* The captured stdout lines, in arrival order, for one consumer, ending when the child's stdout closes.
|
|
398
|
+
*
|
|
399
|
+
* @remarks
|
|
400
|
+
* A line feed, a CRLF pair, and a bare carriage return each terminate a line, and a CRLF split
|
|
401
|
+
* across delivered chunks joins as one break. A child that redraws a progress bar with a carriage
|
|
402
|
+
* return therefore yields one line per redraw, and consecutive carriage returns yield an empty
|
|
403
|
+
* line between them. A final line written with no trailing terminator is delivered when stdout
|
|
404
|
+
* closes.
|
|
405
|
+
*/
|
|
391
406
|
readonly lines: AsyncIterable<string>;
|
|
392
407
|
/** The decoded byte-bounded stderr tail. */
|
|
393
408
|
readonly evidence: string;
|
|
@@ -399,13 +414,18 @@ export declare interface ProcessInterface {
|
|
|
399
414
|
* Write one line to the open standard-input channel.
|
|
400
415
|
*
|
|
401
416
|
* @remarks
|
|
402
|
-
* Never rejects.
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
417
|
+
* Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
|
|
418
|
+
* prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
|
|
419
|
+
* full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
|
|
420
|
+
* terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
|
|
421
|
+
* 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
|
|
422
|
+
* the write can settle `true` without a callback error or a stream error while the child remains
|
|
423
|
+
* alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
|
|
424
|
+
* that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
|
|
425
|
+
* for bytes the package is about to discard.
|
|
406
426
|
*
|
|
407
427
|
* @param text - The line text without its trailing newline
|
|
408
|
-
* @returns True when the
|
|
428
|
+
* @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
|
|
409
429
|
*/
|
|
410
430
|
send(text: string): Promise<boolean>;
|
|
411
431
|
/**
|
|
@@ -569,6 +589,8 @@ export declare interface ProcessOptions {
|
|
|
569
589
|
readonly evidence?: number;
|
|
570
590
|
/** Soft high-water mark in bytes for the unconsumed `lines` backlog; termination retains at most twice this value. Default: {@link PROCESS_BACKLOG}. */
|
|
571
591
|
readonly backlog?: number;
|
|
592
|
+
/** Milliseconds an unconfirmed {@link ProcessInterface.send} waits before resolving `false`. `0` or omitted disables the bound. */
|
|
593
|
+
readonly delivery?: number;
|
|
572
594
|
/** If `true`, stdin stays open for {@link ProcessInterface.send}; if `false` or omitted, stdin closes after any initial `input`. */
|
|
573
595
|
readonly writable?: boolean;
|
|
574
596
|
/** Aborting this signal terminates the child through the same bounded `stop`. */
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -139,13 +139,15 @@ export declare interface ExecuteOptions {
|
|
|
139
139
|
* The settled outcome of a one-shot run: the buffered output and the terminal state.
|
|
140
140
|
*
|
|
141
141
|
* @remarks
|
|
142
|
-
* `failed` is `true` when the
|
|
143
|
-
*
|
|
144
|
-
* child ending itself, and only the earliest observed is recorded.
|
|
145
|
-
*
|
|
146
|
-
* synchronous run and does not fail an asynchronous one. `ProcessInterface` carries the
|
|
147
|
-
* the same fact against a supervised child's retention bounds.
|
|
148
|
-
* `
|
|
142
|
+
* `failed` is `true` when the run timed out, was aborted, ended on a host fault, was ended by a
|
|
143
|
+
* signal, or exited with a code other than `0`. `expired` and `aborted` name the ways the run ended
|
|
144
|
+
* the child rather than the child ending itself, and only the earliest observed is recorded.
|
|
145
|
+
* `truncated` reports that a captured stream omitted output because it exceeded `limit`, which
|
|
146
|
+
* fails a synchronous run and does not fail an asynchronous one. `ProcessInterface` carries the
|
|
147
|
+
* same name for the same fact against a supervised child's retention bounds. For a `strict: false`
|
|
148
|
+
* caller, `failed: true` with `expired`, `aborted`, and `truncated` false, `code: 0`, and
|
|
149
|
+
* `signal: null` is the residual signature that a host fault ended the run. A spawn fault reports
|
|
150
|
+
* the host's negative errno for `execute`. A spawn fault reports `null` for `executeSync`.
|
|
149
151
|
*/
|
|
150
152
|
export declare interface ExecuteResult {
|
|
151
153
|
/** The command line that was run, for diagnostics. */
|
|
@@ -221,7 +223,7 @@ export declare const PROCESS_BACKLOG = 10485760;
|
|
|
221
223
|
export declare const PROCESS_CONFIRMATION = 5000;
|
|
222
224
|
|
|
223
225
|
/** The machine-readable failure categories a {@link ProcessError} carries, in declaration order. */
|
|
224
|
-
export declare const PROCESS_ERROR_CODES: readonly ["spawn", "timeout", "duplicate", "protocol", "invalid"];
|
|
226
|
+
export declare const PROCESS_ERROR_CODES: readonly ["spawn", "timeout", "input", "duplicate", "protocol", "invalid"];
|
|
225
227
|
|
|
226
228
|
/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
|
|
227
229
|
export declare const PROCESS_EVIDENCE = 2048;
|
|
@@ -330,14 +332,18 @@ export declare interface ProcessErrorOptions {
|
|
|
330
332
|
*
|
|
331
333
|
* @remarks
|
|
332
334
|
* Declared as a `type` alias so it satisfies the emitter's `EventMap` constraint structurally. The
|
|
333
|
-
* `error` event carries a child fault — a failure to spawn
|
|
334
|
-
*
|
|
335
|
+
* `error` event carries a child fault — a failure to spawn, a process-level error, or a
|
|
336
|
+
* host-reported standard-input channel fault after the constructor input phase. A fault arising
|
|
337
|
+
* from the constructor-supplied `input` write or its closing `end` stays quiet because the package
|
|
338
|
+
* initiated that sequence. A surfaced standard-input fault is wrapped in a
|
|
339
|
+
* {@link ProcessError} coded `protocol` with the host fault as its cause. The event is distinct from
|
|
340
|
+
* the `error` handler in {@link ProcessOptions}: a listener throw is isolated by the emitter and
|
|
335
341
|
* routed to that handler, never emitted as this `error` event.
|
|
336
342
|
*/
|
|
337
343
|
export declare type ProcessEventMap = {
|
|
338
344
|
/** A decoded standard-error chunk arrived. */
|
|
339
345
|
readonly stderr: readonly [chunk: string];
|
|
340
|
-
/** The child
|
|
346
|
+
/** The child or its open standard-input channel reported a fault, carrying the host cause directly or through a `protocol` {@link ProcessError}. */
|
|
341
347
|
readonly error: readonly [error: unknown];
|
|
342
348
|
/** The child settled — its terminal state, delivered once. */
|
|
343
349
|
readonly exit: readonly [exit: ProcessExit];
|
|
@@ -387,7 +393,16 @@ export declare interface ProcessInterface {
|
|
|
387
393
|
readonly signal: string | null;
|
|
388
394
|
/** The typed lifecycle observation surface. */
|
|
389
395
|
readonly emitter: EmitterInterface<ProcessEventMap>;
|
|
390
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* The captured stdout lines, in arrival order, for one consumer, ending when the child's stdout closes.
|
|
398
|
+
*
|
|
399
|
+
* @remarks
|
|
400
|
+
* A line feed, a CRLF pair, and a bare carriage return each terminate a line, and a CRLF split
|
|
401
|
+
* across delivered chunks joins as one break. A child that redraws a progress bar with a carriage
|
|
402
|
+
* return therefore yields one line per redraw, and consecutive carriage returns yield an empty
|
|
403
|
+
* line between them. A final line written with no trailing terminator is delivered when stdout
|
|
404
|
+
* closes.
|
|
405
|
+
*/
|
|
391
406
|
readonly lines: AsyncIterable<string>;
|
|
392
407
|
/** The decoded byte-bounded stderr tail. */
|
|
393
408
|
readonly evidence: string;
|
|
@@ -399,13 +414,18 @@ export declare interface ProcessInterface {
|
|
|
399
414
|
* Write one line to the open standard-input channel.
|
|
400
415
|
*
|
|
401
416
|
* @remarks
|
|
402
|
-
* Never rejects.
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
417
|
+
* Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
|
|
418
|
+
* prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
|
|
419
|
+
* full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
|
|
420
|
+
* terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
|
|
421
|
+
* 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
|
|
422
|
+
* the write can settle `true` without a callback error or a stream error while the child remains
|
|
423
|
+
* alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
|
|
424
|
+
* that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
|
|
425
|
+
* for bytes the package is about to discard.
|
|
406
426
|
*
|
|
407
427
|
* @param text - The line text without its trailing newline
|
|
408
|
-
* @returns True when the
|
|
428
|
+
* @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
|
|
409
429
|
*/
|
|
410
430
|
send(text: string): Promise<boolean>;
|
|
411
431
|
/**
|
|
@@ -569,6 +589,8 @@ export declare interface ProcessOptions {
|
|
|
569
589
|
readonly evidence?: number;
|
|
570
590
|
/** Soft high-water mark in bytes for the unconsumed `lines` backlog; termination retains at most twice this value. Default: {@link PROCESS_BACKLOG}. */
|
|
571
591
|
readonly backlog?: number;
|
|
592
|
+
/** Milliseconds an unconfirmed {@link ProcessInterface.send} waits before resolving `false`. `0` or omitted disables the bound. */
|
|
593
|
+
readonly delivery?: number;
|
|
572
594
|
/** If `true`, stdin stays open for {@link ProcessInterface.send}; if `false` or omitted, stdin closes after any initial `input`. */
|
|
573
595
|
readonly writable?: boolean;
|
|
574
596
|
/** Aborting this signal terminates the child through the same bounded `stop`. */
|
package/dist/src/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** The default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 5_000\n\n/** 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/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */\nexport const PROCESS_EVIDENCE = 2_048\n\n/** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */\nexport const PROCESS_BACKLOG = 10_485_760\n\n/** The default maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n\n/** The largest timer delay in milliseconds the host schedules without truncating it to one. */\nexport const PROCESS_TIMER = 2_147_483_647\n\n/** The executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */\nexport const PROCESS_PATHEXT = '.COM;.EXE;.BAT;.CMD'\n\n/** 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'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/** A child-process failure with a stable machine-readable category. */\nexport class ProcessError extends Error {\n\toverride readonly name = 'ProcessError'\n\treadonly code: ProcessErrorCode\n\treadonly context?: ProcessErrorContext\n\t/** The buffered run outcome, present when a one-shot run produced the failure. */\n\treadonly result?: ExecuteResult\n\n\t/**\n\t * Create 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 only for 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;;AAGpC,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;AACD,CAAU;;;;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** The default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 5_000\n\n/** 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/** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */\nexport const PROCESS_EVIDENCE = 2_048\n\n/** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */\nexport const PROCESS_BACKLOG = 10_485_760\n\n/** The default maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n\n/** The largest timer delay in milliseconds the host schedules without truncating it to one. */\nexport const PROCESS_TIMER = 2_147_483_647\n\n/** The executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */\nexport const PROCESS_PATHEXT = '.COM;.EXE;.BAT;.CMD'\n\n/** 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/** A child-process failure with a stable machine-readable category. */\nexport class ProcessError extends Error {\n\toverride readonly name = 'ProcessError'\n\treadonly code: ProcessErrorCode\n\treadonly context?: ProcessErrorContext\n\t/** The buffered run outcome, present when a one-shot run produced the failure. */\n\treadonly result?: ExecuteResult\n\n\t/**\n\t * Create 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 only for 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;;AAGpC,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;;;;ACnBV,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"}
|
|
@@ -684,8 +684,11 @@ function buildExecuteResult(input) {
|
|
|
684
684
|
* overshoot it by the line that crossed it plus the rest of its delivered chunk. Termination never
|
|
685
685
|
* reapplies backpressure, and retained lines are capped at twice `backlog`; later lines are dropped
|
|
686
686
|
* and `truncated` reports the omission. Standard error is decoded and forwarded live as the `stderr`
|
|
687
|
-
* event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries
|
|
688
|
-
* child `error` cause on a spawn fault
|
|
687
|
+
* event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries a
|
|
688
|
+
* child `error` cause on a spawn fault, a `protocol` {@link ProcessError} on a host-reported stdin
|
|
689
|
+
* fault after the constructor input phase, and the terminal `exit`, alongside the `exit` promise.
|
|
690
|
+
* A fault arising from the constructor-supplied `input` write or its closing `end` stays quiet
|
|
691
|
+
* because the package initiated that sequence. `pid`,
|
|
689
692
|
* `code`, and `signal` read the spawned child directly, so the native exit is readable before the
|
|
690
693
|
* `exit` promise settles on stdio close. `stop` ends the whole tree and reports whether the native
|
|
691
694
|
* exit was observed; `destroy` stops the child, destroys stdin so every pending `send` settles, and
|
|
@@ -711,6 +714,7 @@ var Process = class {
|
|
|
711
714
|
#grace;
|
|
712
715
|
#evidence;
|
|
713
716
|
#backlog;
|
|
717
|
+
#delivery;
|
|
714
718
|
#decoder = new node_string_decoder.StringDecoder("utf8");
|
|
715
719
|
#exit = Promise.withResolvers();
|
|
716
720
|
#lines;
|
|
@@ -718,6 +722,7 @@ var Process = class {
|
|
|
718
722
|
#waiters = [];
|
|
719
723
|
#signal;
|
|
720
724
|
#abort;
|
|
725
|
+
#writes = /* @__PURE__ */ new Map();
|
|
721
726
|
#tail = node_buffer.Buffer.alloc(0);
|
|
722
727
|
#head = 0;
|
|
723
728
|
#pending = 0;
|
|
@@ -727,6 +732,8 @@ var Process = class {
|
|
|
727
732
|
#terminating = false;
|
|
728
733
|
#closed = false;
|
|
729
734
|
#ended = false;
|
|
735
|
+
#input = 0;
|
|
736
|
+
#failure;
|
|
730
737
|
#stopping;
|
|
731
738
|
#ending;
|
|
732
739
|
/**
|
|
@@ -741,6 +748,7 @@ var Process = class {
|
|
|
741
748
|
const grace = options.grace;
|
|
742
749
|
const evidence = options.evidence;
|
|
743
750
|
const backlog = options.backlog;
|
|
751
|
+
const delivery = options.delivery;
|
|
744
752
|
const writable = options.writable;
|
|
745
753
|
const signal = options.signal;
|
|
746
754
|
const on = options.on;
|
|
@@ -750,6 +758,7 @@ var Process = class {
|
|
|
750
758
|
validateCommand(command);
|
|
751
759
|
validateWorkspace(workspace);
|
|
752
760
|
validateTimer(grace, "option 'grace'");
|
|
761
|
+
validateTimer(delivery, "option 'delivery'");
|
|
753
762
|
validateBytes(evidence, "option 'evidence'", 0);
|
|
754
763
|
validateBytes(backlog, "option 'backlog'", 1);
|
|
755
764
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
@@ -759,6 +768,7 @@ var Process = class {
|
|
|
759
768
|
this.#grace = grace ?? _src_core.PROCESS_GRACE;
|
|
760
769
|
this.#evidence = evidence ?? _src_core.PROCESS_EVIDENCE;
|
|
761
770
|
this.#backlog = backlog ?? _src_core.PROCESS_BACKLOG;
|
|
771
|
+
this.#delivery = delivery ?? 0;
|
|
762
772
|
this.#signal = signal;
|
|
763
773
|
this.#lines = Object.freeze({ [Symbol.asyncIterator]: this.#iterate.bind(this) });
|
|
764
774
|
const childEnvironment = mergeEnvironment(command.isolated === true, command.environment);
|
|
@@ -786,10 +796,16 @@ var Process = class {
|
|
|
786
796
|
this.#reader.once("close", this.#finish.bind(this));
|
|
787
797
|
this.#child.once("error", (cause) => this.#emitter.emit("error", cause));
|
|
788
798
|
this.#child.once("close", this.#close.bind(this));
|
|
789
|
-
this.#child.stdin.on("error", () =>
|
|
799
|
+
this.#child.stdin.on("error", (cause) => this.#failInputStream(cause));
|
|
790
800
|
this.#child.stderr.on("data", this.#retain.bind(this));
|
|
791
|
-
if (input !== void 0)
|
|
792
|
-
|
|
801
|
+
if (input !== void 0) {
|
|
802
|
+
this.#input += 1;
|
|
803
|
+
this.#child.stdin.write(input, this.#completeInput.bind(this));
|
|
804
|
+
}
|
|
805
|
+
if (writable !== true) {
|
|
806
|
+
this.#input += 1;
|
|
807
|
+
this.#child.stdin.end(this.#completeInput.bind(this));
|
|
808
|
+
}
|
|
793
809
|
if (signal !== void 0) {
|
|
794
810
|
this.#abort = this.#terminate.bind(this);
|
|
795
811
|
signal.addEventListener("abort", this.#abort, { once: true });
|
|
@@ -832,24 +848,34 @@ var Process = class {
|
|
|
832
848
|
* Write one line to the open standard-input channel.
|
|
833
849
|
*
|
|
834
850
|
* @remarks
|
|
835
|
-
* Never rejects.
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
851
|
+
* Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
|
|
852
|
+
* prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
|
|
853
|
+
* full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
|
|
854
|
+
* terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
|
|
855
|
+
* 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
|
|
856
|
+
* the write can settle `true` without a callback error or a stream error while the child remains
|
|
857
|
+
* alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
|
|
858
|
+
* that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
|
|
859
|
+
* for bytes the package is about to discard.
|
|
839
860
|
*
|
|
840
861
|
* @param text - The line text without its trailing newline
|
|
841
|
-
* @returns True when the
|
|
862
|
+
* @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
|
|
842
863
|
*/
|
|
843
864
|
send(text) {
|
|
844
865
|
const stdin = this.#child.stdin;
|
|
845
|
-
if (this.#closed || !stdin.writable) return Promise.resolve(false);
|
|
866
|
+
if (this.#closed || this.#terminating || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
|
|
846
867
|
const settled = Promise.withResolvers();
|
|
868
|
+
this.#writes.set(settled, void 0);
|
|
847
869
|
try {
|
|
848
|
-
stdin.write(`${text}\n`, (
|
|
849
|
-
settled.resolve(error === void 0 || error === null);
|
|
850
|
-
});
|
|
870
|
+
stdin.write(`${text}\n`, this.#confirmWrite.bind(this, settled));
|
|
851
871
|
} catch {
|
|
852
|
-
|
|
872
|
+
this.#settleWrite(settled, false);
|
|
873
|
+
return settled.promise;
|
|
874
|
+
}
|
|
875
|
+
if (this.#delivery > 0 && this.#writes.has(settled)) {
|
|
876
|
+
const timer = setTimeout(() => this.#settleWrite(settled, false), this.#delivery);
|
|
877
|
+
timer.unref();
|
|
878
|
+
this.#writes.set(settled, timer);
|
|
853
879
|
}
|
|
854
880
|
return settled.promise;
|
|
855
881
|
}
|
|
@@ -980,6 +1006,42 @@ var Process = class {
|
|
|
980
1006
|
if (this.#signal === void 0 || this.#abort === void 0) return;
|
|
981
1007
|
this.#signal.removeEventListener("abort", this.#abort);
|
|
982
1008
|
}
|
|
1009
|
+
#confirmWrite(settled, error) {
|
|
1010
|
+
if (error === void 0 || error === null) {
|
|
1011
|
+
this.#settleWrite(settled, true);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
this.#failInputCallback(error);
|
|
1015
|
+
}
|
|
1016
|
+
#completeInput() {
|
|
1017
|
+
this.#input -= 1;
|
|
1018
|
+
}
|
|
1019
|
+
#settleWrite(settled, accepted) {
|
|
1020
|
+
if (!this.#writes.has(settled)) return;
|
|
1021
|
+
const timer = this.#writes.get(settled);
|
|
1022
|
+
this.#writes.delete(settled);
|
|
1023
|
+
clearTimeout(timer);
|
|
1024
|
+
settled.resolve(accepted);
|
|
1025
|
+
}
|
|
1026
|
+
#settleWrites() {
|
|
1027
|
+
for (const settled of this.#writes.keys()) this.#settleWrite(settled, false);
|
|
1028
|
+
}
|
|
1029
|
+
#failInputStream(cause) {
|
|
1030
|
+
if (this.#child.stdin.writableEnded || this.#input > 0) {
|
|
1031
|
+
this.#settleWrites();
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
this.#failInputCallback(cause);
|
|
1035
|
+
}
|
|
1036
|
+
#failInputCallback(cause) {
|
|
1037
|
+
if (this.#failure !== void 0 || this.#terminating) return;
|
|
1038
|
+
this.#failure = cause;
|
|
1039
|
+
this.#settleWrites();
|
|
1040
|
+
this.#emitter.emit("error", new _src_core.ProcessError("The standard-input channel failed", {
|
|
1041
|
+
code: "protocol",
|
|
1042
|
+
cause
|
|
1043
|
+
}));
|
|
1044
|
+
}
|
|
983
1045
|
#capBacklog() {
|
|
984
1046
|
const limit = this.#backlog * 2;
|
|
985
1047
|
while (this.#pending > limit && this.#queue.length > this.#head) {
|
|
@@ -994,6 +1056,7 @@ var Process = class {
|
|
|
994
1056
|
this.#capBacklog();
|
|
995
1057
|
this.#relieve();
|
|
996
1058
|
const confirmed = await stopChild(this.#child, this.#grace, _src_core.PROCESS_CONFIRMATION);
|
|
1059
|
+
this.#settleWrites();
|
|
997
1060
|
this.#child.stdin.destroy();
|
|
998
1061
|
return confirmed;
|
|
999
1062
|
}
|
|
@@ -1258,9 +1321,13 @@ var Retention = class {
|
|
|
1258
1321
|
* rather than on process exit, so a descendant that inherited the child's stdio holds the run open
|
|
1259
1322
|
* after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
|
|
1260
1323
|
* the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
|
|
1261
|
-
* overrides `command.input`.
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1324
|
+
* overrides `command.input`. A host fault while writing that input terminates the run by design and
|
|
1325
|
+
* marks its result failed; a strict rejection carries the host fault as its cause. This differs
|
|
1326
|
+
* from `Process` constructor input, whose package-initiated input phase stays quiet. Unless `strict`
|
|
1327
|
+
* is `false`, a failed run rejects with a {@link ProcessError} carrying the {@link ExecuteResult};
|
|
1328
|
+
* {@link createExecuteError} constructs every rejection outside the input-fault door. An invalid
|
|
1329
|
+
* option or command string rejects before the child is spawned, because an async function cannot
|
|
1330
|
+
* throw synchronously.
|
|
1264
1331
|
*
|
|
1265
1332
|
* @param command - The executable, arguments, and optional environment and input
|
|
1266
1333
|
* @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
|
|
@@ -1304,6 +1371,7 @@ async function execute(command, options) {
|
|
|
1304
1371
|
const terminate = new AbortController();
|
|
1305
1372
|
const cleanup = new AbortController();
|
|
1306
1373
|
const finish = new AbortController();
|
|
1374
|
+
const inputFailure = new AbortController();
|
|
1307
1375
|
const outChunks = [];
|
|
1308
1376
|
const errChunks = [];
|
|
1309
1377
|
const outRetention = new Retention();
|
|
@@ -1351,7 +1419,12 @@ async function execute(command, options) {
|
|
|
1351
1419
|
confirmTimer = setTimeout(() => finish.abort(), _src_core.PROCESS_CONFIRMATION);
|
|
1352
1420
|
});
|
|
1353
1421
|
}, { once: true });
|
|
1354
|
-
|
|
1422
|
+
inputFailure.signal.addEventListener("abort", () => {
|
|
1423
|
+
if (finish.signal.aborted) return;
|
|
1424
|
+
cause = inputFailure.signal.reason;
|
|
1425
|
+
terminate.abort();
|
|
1426
|
+
}, { once: true });
|
|
1427
|
+
child.stdin.on("error", (error) => inputFailure.abort(error));
|
|
1355
1428
|
child.stdout.on("data", (chunk) => {
|
|
1356
1429
|
const retained = outRetention.retain(chunk, limit);
|
|
1357
1430
|
if (retained !== void 0) outChunks.push(retained);
|
|
@@ -1369,7 +1442,9 @@ async function execute(command, options) {
|
|
|
1369
1442
|
finish.abort();
|
|
1370
1443
|
});
|
|
1371
1444
|
child.once("close", () => finish.abort());
|
|
1372
|
-
if (text !== void 0) child.stdin.write(text)
|
|
1445
|
+
if (text !== void 0) child.stdin.write(text, (error) => {
|
|
1446
|
+
if (error !== void 0 && error !== null) inputFailure.abort(error);
|
|
1447
|
+
});
|
|
1373
1448
|
child.stdin.end();
|
|
1374
1449
|
if (timeout > 0) timeoutTimer = setTimeout(() => {
|
|
1375
1450
|
expired = true;
|
|
@@ -1389,7 +1464,19 @@ async function execute(command, options) {
|
|
|
1389
1464
|
}
|
|
1390
1465
|
}
|
|
1391
1466
|
const result = await settled.promise;
|
|
1392
|
-
if (result.failed && strict)
|
|
1467
|
+
if (result.failed && strict) {
|
|
1468
|
+
if (cause !== void 0 && cause === inputFailure.signal.reason) throw new _src_core.ProcessError(`Command '${result.command}' failed while writing standard input`, {
|
|
1469
|
+
code: "input",
|
|
1470
|
+
context: {
|
|
1471
|
+
command: result.command,
|
|
1472
|
+
code: result.code,
|
|
1473
|
+
signal: result.signal
|
|
1474
|
+
},
|
|
1475
|
+
cause: inputFailure.signal.reason,
|
|
1476
|
+
result
|
|
1477
|
+
});
|
|
1478
|
+
throw (0, _src_core.createExecuteError)(result, cause);
|
|
1479
|
+
}
|
|
1393
1480
|
return result;
|
|
1394
1481
|
}
|
|
1395
1482
|
//#endregion
|