@orkestrel/process 0.0.4 → 0.0.6

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.
@@ -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 child exited non-zero, was killed by a signal, expired, was aborted, or
143
- * failed to spawn. `expired` and `aborted` name the ways the run ended the child rather than the
144
- * child ending itself, and only the earliest observed is recorded. `truncated` is independent of
145
- * both: it reports that a captured stream omitted output because it exceeded `limit`, which fails a
146
- * synchronous run and does not fail an asynchronous one. `ProcessInterface` carries the same name for
147
- * the same fact against a supervised child's retention bounds. A spawn fault reports the host's negative errno for `execute`. A spawn fault reports
148
- * `null` for `executeSync`.
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. */
@@ -220,8 +222,25 @@ export declare const PROCESS_BACKLOG = 10485760;
220
222
  /** The window in milliseconds a termination waits for the child's native exit after the final kill. */
221
223
  export declare const PROCESS_CONFIRMATION = 5000;
222
224
 
225
+ /**
226
+ * The default window in milliseconds the package waits for the child's read ends to close after
227
+ * the child's native exit or after a termination this package initiated, before cutting them off.
228
+ *
229
+ * @remarks
230
+ * Measured on Windows 11 with Node v24.18.1 on 2026-08-21, spawning the `tests/src/server/fixtures`
231
+ * child modes as real children and timing the host `close` event against the host `exit` event.
232
+ * Every ordinary run closed within 0.02ms of the native exit, whether the child ended itself or a
233
+ * kill ended it, and a `taskkill /F /T` that reaped a descendant while the root was still alive
234
+ * closed within 0.01ms. The only finite late close came from a descendant that outlived its root and
235
+ * then ended on its own, at 127.44ms to 193.41ms — that lag is the descendant's own remaining life
236
+ * rather than a flush cost. A descendant that never ends never closes, so the distribution has no
237
+ * finite tail to cover and this value bounds it instead: 1000ms is over five times the slowest
238
+ * finite close measured, and it keeps an unreachable descendant from stretching a shutdown.
239
+ */
240
+ export declare const PROCESS_DRAIN = 1000;
241
+
223
242
  /** 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"];
243
+ export declare const PROCESS_ERROR_CODES: readonly ["spawn", "timeout", "input", "duplicate", "protocol", "invalid"];
225
244
 
226
245
  /** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
227
246
  export declare const PROCESS_EVIDENCE = 2048;
@@ -330,25 +349,41 @@ export declare interface ProcessErrorOptions {
330
349
  *
331
350
  * @remarks
332
351
  * 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 or a process-level error. It is distinct
334
- * from the `error` handler in {@link ProcessOptions}: a listener throw is isolated by the emitter and
352
+ * `error` event carries a child fault — a failure to spawn, a process-level error, or a
353
+ * host-reported standard-input channel fault after the constructor input phase. A fault arising
354
+ * from the constructor-supplied `input` write or its closing `end` stays quiet because the package
355
+ * initiated that sequence. A surfaced standard-input fault is wrapped in a
356
+ * {@link ProcessError} coded `protocol` with the host fault as its cause. The event is distinct from
357
+ * the `error` handler in {@link ProcessOptions}: a listener throw is isolated by the emitter and
335
358
  * routed to that handler, never emitted as this `error` event.
336
359
  */
337
360
  export declare type ProcessEventMap = {
338
361
  /** A decoded standard-error chunk arrived. */
339
362
  readonly stderr: readonly [chunk: string];
340
- /** The child emitted an error — a spawn fault or process-level failure — carrying its cause. */
363
+ /** The child or its open standard-input channel reported a fault, carrying the host cause directly or through a `protocol` {@link ProcessError}. */
341
364
  readonly error: readonly [error: unknown];
342
365
  /** The child settled — its terminal state, delivered once. */
343
366
  readonly exit: readonly [exit: ProcessExit];
344
367
  };
345
368
 
346
- /** The observed terminal state of a child process: its exit code, or the signal that ended it. */
369
+ /**
370
+ * The observed terminal state of a child process: its exit code or the signal that ended it, and
371
+ * how its observation ended.
372
+ *
373
+ * @remarks
374
+ * This value exists only at the terminal moment, so every field on it is already final and no
375
+ * consumer can read one too early. `drained` reports whether the diagnostics are complete;
376
+ * {@link ProcessInterface.truncated} reports that the `lines` stream omitted stdout lines. They are
377
+ * independent facts about different streams, and one child reports both when a retention bound
378
+ * dropped lines and the drain bound cut stderr off.
379
+ */
347
380
  export declare interface ProcessExit {
348
381
  /** The exit code, or `null` when a signal ended the process. A spawn fault reports the host's negative errno for `Process` and `execute`. */
349
382
  readonly code: number | null;
350
383
  /** The terminating signal name, or `null` when the process exited on its own. */
351
384
  readonly signal: string | null;
385
+ /** True when the terminal moment arrived because the child's streams closed; false when the `drain` bound elapsed first and later diagnostics may have existed. */
386
+ readonly drained: boolean;
352
387
  }
353
388
 
354
389
  /**
@@ -369,14 +404,22 @@ export declare interface ProcessExit {
369
404
  * its own capture `limit`. The typed `emitter` carries the live `stderr` chunks, the child `error` cause, and the
370
405
  * terminal `exit`, alongside the `exit` promise. `stop` and `destroy` are idempotent and never reject.
371
406
  *
407
+ * The same terminal moment governs every observation surface. It arrives when the child's streams close,
408
+ * or when the `drain` bound armed by the native exit or by a termination this package initiated
409
+ * elapses first, and
410
+ * {@link ProcessExit.drained} reports which. At that moment `evidence` freezes, `lines` ends, `exit`
411
+ * settles, and `settled` turns true — together, so a consumer reading `evidence` and a consumer
412
+ * iterating `lines` never see a different child. `stop`, `destroy`, an abort of the `signal` option,
413
+ * a natural exit, and a spawn fault all reach it.
414
+ *
372
415
  * The spawn is eager, so `pid` is fixed by the time construction returns; a spawn that produced no
373
416
  * child reports `undefined` for that child's whole lifetime. An assigned id survives the exit and
374
417
  * reports no liveness on its own, because the host reuses a dead child's id. Derive liveness as
375
418
  * `pid !== undefined && code === null && signal === null`, and derive it again before every use of
376
419
  * the id. `code` and `signal` mirror the host child's own terminal record, so they carry the native
377
- * exit as soon as the host has it, while the `exit` promise settles on stdio close — a descendant
378
- * holding inherited stdio keeps that close pending past the native exit, and a supervisor inside
379
- * that window reads the terminal state here.
420
+ * exit as soon as the host has it, while the `exit` promise settles at the terminal moment — a
421
+ * descendant holding inherited stdio keeps the stream close pending past the native exit, and a
422
+ * supervisor inside that window reads the terminal state here.
380
423
  */
381
424
  export declare interface ProcessInterface {
382
425
  /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
@@ -387,29 +430,88 @@ export declare interface ProcessInterface {
387
430
  readonly signal: string | null;
388
431
  /** The typed lifecycle observation surface. */
389
432
  readonly emitter: EmitterInterface<ProcessEventMap>;
390
- /** The captured stdout lines, in arrival order, for one consumer, ending when the child's stdout closes. */
433
+ /**
434
+ * The captured stdout lines, in arrival order, for one consumer, ending at the terminal moment.
435
+ *
436
+ * @remarks
437
+ * A line feed, a CRLF pair, and a bare carriage return each terminate a line, and a CRLF split
438
+ * across delivered chunks joins as one break. A child that redraws a progress bar with a carriage
439
+ * return therefore yields one line per redraw, and consecutive carriage returns yield an empty
440
+ * line between them. A final line written with no trailing terminator is delivered when stdout
441
+ * closes.
442
+ *
443
+ * The stream ends at the terminal moment rather than throwing there, so teardown is not an error
444
+ * path: a pending `next()` resolves `done: true` and a `for await` loop exits normally. Lines
445
+ * already framed and queued are delivered before that end, so a consumer that stops a chatty
446
+ * child still reads what the child had produced, and only bytes that would have arrived after the
447
+ * terminal moment are lost — whether that moment came from the read ends closing, from the
448
+ * `drain` bound after a native exit, or from a requested termination. An unframed trailing partial
449
+ * written before an undrained cutoff is not promised, because only the stream's own end flushes
450
+ * one. A read started after the end yields nothing further.
451
+ */
391
452
  readonly lines: AsyncIterable<string>;
392
- /** The decoded byte-bounded stderr tail. */
453
+ /**
454
+ * The decoded byte-bounded stderr tail.
455
+ *
456
+ * @remarks
457
+ * The live tail before the terminal moment and the frozen value after it, on every path — a
458
+ * natural exit, `stop`, `destroy`, an abort of the `signal` option, and a spawn fault. Every read
459
+ * after that moment returns the same string, so a consumer needs no private copy and cannot
460
+ * observe the tail moving under it. When {@link ProcessExit.drained} is false the frozen value is
461
+ * the tail as of the cutoff, and later diagnostics may have existed.
462
+ */
393
463
  readonly evidence: string;
394
464
  /** True when the `lines` stream omitted output because a retention bound was reached. */
395
465
  readonly truncated: boolean;
396
- /** The terminal child state, observed once from the close event. */
466
+ /**
467
+ * True after the `exit` promise settled.
468
+ *
469
+ * @remarks
470
+ * The terminal moment has arrived: `evidence` is frozen, `lines` has ended, and the
471
+ * {@link ProcessExit} value exists. Reached on every path, including a spawn that produced no
472
+ * child.
473
+ */
474
+ readonly settled: boolean;
475
+ /**
476
+ * True after a termination began.
477
+ *
478
+ * @remarks
479
+ * Monotonic. It turns true when `stop`, `destroy`, or an abort of the `signal` option begins a
480
+ * termination, and it stays true from then on, including after `settled` turns true. It reports
481
+ * that a termination was initiated, not that one is in flight, because the initiation is the fact
482
+ * a consumer acts on: a child that was asked to end is not a child to send new work to. A child
483
+ * that exited on its own reports `false` here with `settled` true.
484
+ */
485
+ readonly stopping: boolean;
486
+ /**
487
+ * The terminal child state, delivered once.
488
+ *
489
+ * @remarks
490
+ * Never rejects. It settles at the terminal moment: when the child's streams close, or when the
491
+ * `drain` bound elapsed first. The native exit arms that bound as well as an initiated
492
+ * termination, so a child nobody terminates still settles it within `drain` of ending.
493
+ */
397
494
  readonly exit: Promise<ProcessExit>;
398
495
  /**
399
496
  * Write one line to the open standard-input channel.
400
497
  *
401
498
  * @remarks
402
- * Never rejects. The promise settles when the host reports the line handled, so a line written
403
- * to a child that is not reading stays pending until the child drains it or the channel closes.
404
- * Every terminal teardown path destroys stdin, so a pending write always settles by teardown. A
405
- * caller that needs its own deadline arms a timer and calls `stop`.
499
+ * Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
500
+ * prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
501
+ * full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
502
+ * terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
503
+ * 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
504
+ * the write can settle `true` without a callback error or a stream error while the child remains
505
+ * alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
506
+ * that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
507
+ * for bytes the package is about to discard.
406
508
  *
407
509
  * @param text - The line text without its trailing newline
408
- * @returns True when the line reached the host without error; false when the channel was closed, destroyed, ended, or the write failed
510
+ * @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
511
  */
410
512
  send(text: string): Promise<boolean>;
411
513
  /**
412
- * Terminate the child process tree and await its observed exit.
514
+ * Terminate the child process tree, await its observed exit, and reach the terminal moment.
413
515
  *
414
516
  * @remarks
415
517
  * Never rejects. On Windows the whole tree is killed immediately through `taskkill`, with a
@@ -417,16 +519,25 @@ export declare interface ProcessInterface {
417
519
  * `SIGKILL` after `grace`. No signal is initiated after the child's native exit is observed; the
418
520
  * window between initiating a signal and the host delivering it belongs to the operating system.
419
521
  *
522
+ * Observation ends here as well as under `destroy`, bounded by `drain`: `evidence` freezes,
523
+ * `lines` ends, and `exit` settles. A caller that stops a child and keeps reading it therefore
524
+ * reaches the end of the stream instead of waiting on a child it already ended, and needs no
525
+ * second call to release it.
526
+ *
420
527
  * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
421
528
  */
422
529
  stop(): Promise<boolean>;
423
530
  /**
424
- * Stop the child, close its standard-input channel, and destroy the observation emitter.
531
+ * Stop the child, close its standard-input channel, reach the terminal moment, and destroy the
532
+ * observation emitter.
425
533
  *
426
534
  * @remarks
427
- * Always resolves, including when termination was never confirmed. Resolving does not imply the
428
- * child's stdio has closed: a descendant holding an inherited pipe keeps the close event pending,
429
- * so `exit` and `lines` can still be outstanding after this barrier settles.
535
+ * Always resolves, including when termination was never confirmed. The barrier settles after the
536
+ * terminal moment, so `evidence` is frozen, `lines` has ended, and `exit` has settled by the time
537
+ * a caller resumes. The wait for the child's streams is bounded by `drain`, so a descendant
538
+ * holding an inherited pipe cannot hold this barrier open, and {@link ProcessExit.drained} reports
539
+ * which way the moment arrived. The emitter is destroyed after the frozen state exists, so a
540
+ * consumer watching the `stderr` event and a consumer reading `evidence` end on the same bytes.
430
541
  *
431
542
  * @returns The stable barrier shared by every call
432
543
  */
@@ -483,11 +594,11 @@ export declare interface ProcessManagerInterface {
483
594
  * @remarks
484
595
  * Every option is read before the child is spawned, so a caller's own option getter runs while
485
596
  * nothing has started and a throw from one strands no process. A getter that begins `destroy`
486
- * without throwing is the one remaining race, and it leaves a bounded residual: the child is
487
- * already spawned, so the launch is refused with `protocol` and that child is torn down
488
- * asynchronously, bounded by `grace` plus the confirmation window. The `protocol` refusal throws
489
- * synchronously before the `destroy` barrier settles. The barrier does not cover that child's
490
- * asynchronous teardown.
597
+ * without throwing is the one remaining race: the child is already spawned, so the launch is
598
+ * refused with `protocol` and that child is destroyed rather than adopted, its teardown bounded
599
+ * by `grace` plus the confirmation window. The `protocol` refusal throws synchronously, and the
600
+ * `destroy` barrier covers that teardown, so the refused child reaches its terminal moment before
601
+ * the barrier resolves.
491
602
  *
492
603
  * @param id - The registry key, unique among live children
493
604
  * @param options - The child construction options
@@ -546,8 +657,17 @@ export declare interface ProcessManagerOptions {
546
657
  *
547
658
  * @remarks
548
659
  * `grace` is the cooperative window between `SIGTERM` and `SIGKILL` on a POSIX host; Windows has no
549
- * cooperative phase, so the value is unused there. There is no completion deadline — a caller that
550
- * wants one arms its own timer and calls `stop`. `backlog` bounds the unconsumed `lines` backlog.
660
+ * cooperative phase, so the value is unused there. There is no completion deadline for a running
661
+ * child: nothing here ends a child that is still working, the `exit` promise carries no deadline of
662
+ * its own, and a caller that wants one arms its own timer and calls `stop`. `drain` does not weaken
663
+ * that. It bounds a single window — between the child's ending and the release of that child's read
664
+ * ends — so it cannot end a running child. The child's native exit arms it, and so does the return
665
+ * of a termination this package initiated through `stop`, `destroy`, or an abort of `signal`; a
666
+ * termination whose confirmation window elapsed can therefore reach the cutoff while the child still
667
+ * runs. The bound exists because a descendant holding an inherited pipe defers the host's stream
668
+ * close for that descendant's whole life, which the package cannot reach and cannot outwait: without
669
+ * the bound, a child that ended would hand the caller an unbounded wait.
670
+ * `backlog` bounds the unconsumed `lines` backlog.
551
671
  * During termination, retained lines are capped at twice `backlog`; later lines are dropped without
552
672
  * pausing stdout, and {@link ProcessInterface.truncated} reports the omission. `on` installs initial
553
673
  * {@link ProcessEventMap} listeners and `error` receives isolated listener failures. Every numeric
@@ -565,10 +685,14 @@ export declare interface ProcessOptions {
565
685
  readonly workspace: string;
566
686
  /** Cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`. Default: {@link PROCESS_GRACE}. */
567
687
  readonly grace?: number;
688
+ /** Milliseconds the package waits for the child's read ends to close after the native exit or an initiated termination, before cutting them off; `0` cuts them off as soon as the bound is armed. Default: {@link PROCESS_DRAIN}. */
689
+ readonly drain?: number;
568
690
  /** Maximum retained stderr tail in bytes. Default: {@link PROCESS_EVIDENCE}. */
569
691
  readonly evidence?: number;
570
692
  /** Soft high-water mark in bytes for the unconsumed `lines` backlog; termination retains at most twice this value. Default: {@link PROCESS_BACKLOG}. */
571
693
  readonly backlog?: number;
694
+ /** Milliseconds an unconfirmed {@link ProcessInterface.send} waits before resolving `false`. `0` or omitted disables the bound. */
695
+ readonly delivery?: number;
572
696
  /** If `true`, stdin stays open for {@link ProcessInterface.send}; if `false` or omitted, stdin closes after any initial `input`. */
573
697
  readonly writable?: boolean;
574
698
  /** Aborting this signal terminates the child through the same bounded `stop`. */
@@ -4,6 +4,22 @@ import { holds, isError } from "@orkestrel/contract";
4
4
  var PROCESS_GRACE = 5e3;
5
5
  /** The window in milliseconds a termination waits for the child's native exit after the final kill. */
6
6
  var PROCESS_CONFIRMATION = 5e3;
7
+ /**
8
+ * The default window in milliseconds the package waits for the child's read ends to close after
9
+ * the child's native exit or after a termination this package initiated, before cutting them off.
10
+ *
11
+ * @remarks
12
+ * Measured on Windows 11 with Node v24.18.1 on 2026-08-21, spawning the `tests/src/server/fixtures`
13
+ * child modes as real children and timing the host `close` event against the host `exit` event.
14
+ * Every ordinary run closed within 0.02ms of the native exit, whether the child ended itself or a
15
+ * kill ended it, and a `taskkill /F /T` that reaped a descendant while the root was still alive
16
+ * closed within 0.01ms. The only finite late close came from a descendant that outlived its root and
17
+ * then ended on its own, at 127.44ms to 193.41ms — that lag is the descendant's own remaining life
18
+ * rather than a flush cost. A descendant that never ends never closes, so the distribution has no
19
+ * finite tail to cover and this value bounds it instead: 1000ms is over five times the slowest
20
+ * finite close measured, and it keeps an unreachable descendant from stretching a shutdown.
21
+ */
22
+ var PROCESS_DRAIN = 1e3;
7
23
  /** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
8
24
  var PROCESS_EVIDENCE = 2048;
9
25
  /** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */
@@ -18,6 +34,7 @@ var PROCESS_PATHEXT = ".COM;.EXE;.BAT;.CMD";
18
34
  var PROCESS_ERROR_CODES = Object.freeze([
19
35
  "spawn",
20
36
  "timeout",
37
+ "input",
21
38
  "duplicate",
22
39
  "protocol",
23
40
  "invalid"
@@ -147,6 +164,6 @@ function createExecuteError(result, cause) {
147
164
  });
148
165
  }
149
166
  //#endregion
150
- export { PROCESS_BACKLOG, PROCESS_CONFIRMATION, PROCESS_ERROR_CODES, PROCESS_EVIDENCE, PROCESS_GRACE, PROCESS_OUTPUT, PROCESS_PATHEXT, PROCESS_TIMER, ProcessError, createDuplicateError, createExecuteError, createInvalidError, createProtocolError, isProcessError };
167
+ export { PROCESS_BACKLOG, PROCESS_CONFIRMATION, PROCESS_DRAIN, PROCESS_ERROR_CODES, PROCESS_EVIDENCE, PROCESS_GRACE, PROCESS_OUTPUT, PROCESS_PATHEXT, PROCESS_TIMER, ProcessError, createDuplicateError, createExecuteError, createInvalidError, createProtocolError, isProcessError };
151
168
 
152
169
  //# sourceMappingURL=index.js.map
@@ -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;;;;AClBV,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"}
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/**\n * 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/** 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;;;;;;;;;;;;;;;;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;;;;ACpCV,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"}