@orkestrel/process 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,21 @@
1
1
  # @orkestrel/process
2
2
 
3
3
  A typed **child-process toolkit** in three tiers. `Process` supervises one
4
- child: stdout is drained eagerly and framed into lines, stderr is forwarded live
5
- and kept as a byte-bounded tail, stdin is a writable channel, and termination is
6
- a bounded `SIGTERM` → grace → `SIGKILL` sequence that awaits the real exit. `run`
7
- and `runSync` are the one-shot runners — they buffer a child to completion and
8
- settle with a `RunResult` carrying the captured output, the exit, and `failed` /
9
- `timedOut`, rejecting with a `ProcessError` by default or resolving the result
10
- when you pass `reject: false`. `ProcessManager` is a keyed registry of live
11
- children: `launch` spawns and registers by id, a settled child evicts itself with
12
- no polling, and `stop` terminates one id, a list, or every child. Every tier is
13
- observable through a typed `emitter`, and cancellation rides an `AbortSignal`.
14
- The contracts are host-independent and ship from `@orkestrel/process`; the Node
15
- engine ships from `@orkestrel/process/server`. Part of the `@orkestrel` line.
4
+ child: stdout is framed into lines under a bounded backlog, stderr is forwarded
5
+ live and kept as a byte-bounded tail, stdin is a writable channel, and
6
+ termination is bounded and reports whether the real exit arrived — `SIGTERM` then
7
+ `SIGKILL` after a grace window on a POSIX host, a whole-tree kill on Windows.
8
+ `run` and `runSync` are the one-shot runners — they buffer a child to completion
9
+ and settle with a `RunResult` carrying the captured output, the exit, and
10
+ `failed` / `expired` / `aborted` / `truncated`, rejecting with a `ProcessError`
11
+ by default or resolving the result when you pass `strict: false`.
12
+ `ProcessManager` is a keyed registry of live children: `launch` spawns and
13
+ registers by id, a settled child evicts itself with no polling, and `stop`
14
+ terminates one id, a list, or every child. Every tier is observable through a
15
+ typed `emitter`, cancellation rides an `AbortSignal`, and no spawn uses a shell,
16
+ so a metacharacter in an argument is data rather than syntax. The contracts are
17
+ host-independent and ship from `@orkestrel/process`; the Node engine ships from
18
+ `@orkestrel/process/server`. Part of the `@orkestrel` line.
16
19
 
17
20
  ## Install
18
21
 
@@ -42,7 +45,7 @@ import { createProcess } from '@orkestrel/process/server'
42
45
  const child = createProcess({
43
46
  command: { file: 'node', arguments: ['worker.js'] },
44
47
  workspace: process.cwd(),
45
- grace: 5_000, // milliseconds between SIGTERM and SIGKILL on stop
48
+ grace: 5_000, // POSIX only: milliseconds between SIGTERM and SIGKILL on stop
46
49
  })
47
50
 
48
51
  for await (const line of child.lines) console.log(line)
@@ -52,16 +55,17 @@ await child.destroy()
52
55
 
53
56
  ## Guide
54
57
 
55
- For the full surface — the supervised `Process`, the `run` / `runSync` runners,
56
- the keyed `ProcessManager`, the observable `emitter`, the `ProcessError` failure
57
- type, and the lower-level helpers — see [`guides/process.md`](guides/process.md).
58
+ For the full surface — the supervised `Process`, the `run` / `runSync` / `detach`
59
+ spawns, the keyed `ProcessManager`, the observable `emitter`, the `ProcessError`
60
+ failure type, and the lower-level helpers — see
61
+ [`guides/process.md`](guides/process.md).
58
62
 
59
63
  ## Package
60
64
 
61
65
  Two typed entry points per the `exports` field in `package.json`: the
62
66
  host-independent contracts, constants, errors, and `isProcessError` guard from
63
67
  `@orkestrel/process`, and the Node engine — `Process`, `run`, `runSync`,
64
- `ProcessManager`, and their factories and helpers — from
68
+ `detach`, `ProcessManager`, and their factories and helpers — from
65
69
  `@orkestrel/process/server`.
66
70
 
67
71
  ## License
@@ -1,12 +1,20 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  //#region src/core/constants.ts
4
- /** The default cooperative window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */
4
+ /** The default cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */
5
5
  var PROCESS_GRACE = 5e3;
6
+ /** The window in milliseconds a termination waits for the child's native exit after the final kill. */
7
+ var PROCESS_CONFIRMATION = 5e3;
6
8
  /** The default maximum retained stderr tail in bytes for a supervised {@link ProcessInterface}. */
7
9
  var PROCESS_EVIDENCE = 2048;
10
+ /** The default soft high-water mark in bytes for a supervised {@link ProcessInterface} line backlog. */
11
+ var PROCESS_BACKLOG = 10485760;
8
12
  /** The default maximum captured bytes for a one-shot run's stdout and stderr, each. */
9
13
  var PROCESS_OUTPUT = 10485760;
14
+ /** The largest timer delay in milliseconds the host schedules without truncating it to one. */
15
+ var PROCESS_TIMER = 2147483647;
16
+ /** The executable extensions a Windows lookup applies when the environment declares no `PATHEXT`. */
17
+ var PROCESS_PATHEXT = ".COM;.EXE;.BAT;.CMD";
10
18
  //#endregion
11
19
  //#region src/core/errors.ts
12
20
  /** A child-process failure with a stable machine-readable category. */
@@ -58,16 +66,54 @@ function createDuplicateError(id) {
58
66
  });
59
67
  }
60
68
  /**
69
+ * Creates the failure raised when a launch is attempted on a registry that is being destroyed.
70
+ *
71
+ * @param id - The id the refused launch asked for
72
+ * @returns A typed protocol failure
73
+ */
74
+ function createProtocolError(id) {
75
+ return new ProcessError(`Process '${id}' cannot launch: the registry is destroyed`, {
76
+ code: "protocol",
77
+ context: { id }
78
+ });
79
+ }
80
+ /**
81
+ * Creates the failure raised when a public input is refused before anything is spawned.
82
+ *
83
+ * @param subject - The rejected input named as the caller wrote it, such as `option 'grace'`
84
+ * @param value - The rejected value, carried as `context.value`
85
+ * @returns A typed validation failure
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * createInvalidError("option 'grace'", -1).code // 'invalid'
90
+ * ```
91
+ */
92
+ function createInvalidError(subject, value) {
93
+ return new ProcessError(`Invalid ${subject}`, {
94
+ code: "invalid",
95
+ context: { value }
96
+ });
97
+ }
98
+ /**
61
99
  * Creates the failure raised when a run does not complete successfully and rejection is requested.
62
100
  *
101
+ * @remarks
102
+ * The category is `timeout` only when the run's own timeout elapsed; every other failure, including
103
+ * an abort and an output overflow, is a `spawn` failure carrying its {@link RunResult}.
104
+ *
63
105
  * @param result - The buffered run outcome that failed
64
- * @param cause - The underlying spawn error, when one ended the run
106
+ * @param cause - The underlying host fault, when one ended the run
65
107
  * @returns A typed run failure carrying its {@link RunResult}
66
108
  */
67
109
  function createRunError(result, cause) {
68
- const reason = result.timedOut ? "timed out" : result.signal !== null ? `was killed by ${result.signal}` : `exited with code ${String(result.code)}`;
110
+ let reason = `exited with code ${String(result.code)}`;
111
+ if (result.signal !== null) reason = `was killed by ${result.signal}`;
112
+ if (result.truncated) reason = "exceeded its output limit";
113
+ if (result.aborted) reason = "was aborted";
114
+ if (result.expired) reason = "timed out";
69
115
  return new ProcessError(`Command '${result.command}' ${reason}`, {
70
- code: result.timedOut ? "timeout" : "spawn",
116
+ code: result.expired ? "timeout" : "spawn",
71
117
  context: {
72
118
  command: result.command,
73
119
  code: result.code,
@@ -78,11 +124,17 @@ function createRunError(result, cause) {
78
124
  });
79
125
  }
80
126
  //#endregion
127
+ exports.PROCESS_BACKLOG = PROCESS_BACKLOG;
128
+ exports.PROCESS_CONFIRMATION = PROCESS_CONFIRMATION;
81
129
  exports.PROCESS_EVIDENCE = PROCESS_EVIDENCE;
82
130
  exports.PROCESS_GRACE = PROCESS_GRACE;
83
131
  exports.PROCESS_OUTPUT = PROCESS_OUTPUT;
132
+ exports.PROCESS_PATHEXT = PROCESS_PATHEXT;
133
+ exports.PROCESS_TIMER = PROCESS_TIMER;
84
134
  exports.ProcessError = ProcessError;
85
135
  exports.createDuplicateError = createDuplicateError;
136
+ exports.createInvalidError = createInvalidError;
137
+ exports.createProtocolError = createProtocolError;
86
138
  exports.createRunError = createRunError;
87
139
  exports.isProcessError = isProcessError;
88
140
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/errors.ts"],"sourcesContent":["/** The default cooperative window in milliseconds between `SIGTERM` and `SIGKILL` during termination. */\nexport const PROCESS_GRACE = 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 maximum captured bytes for a one-shot run's stdout and stderr, each. */\nexport const PROCESS_OUTPUT = 10_485_760\n","import { holds } from '@orkestrel/contract'\nimport type {\n\tProcessErrorCode,\n\tProcessErrorContext,\n\tProcessErrorOptions,\n\tRunResult,\n} from './types.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?: RunResult\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\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 * @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\treturn holds(() => value instanceof ProcessError)\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 run does not complete successfully and rejection is requested.\n *\n * @param result - The buffered run outcome that failed\n * @param cause - The underlying spawn error, when one ended the run\n * @returns A typed run failure carrying its {@link RunResult}\n */\nexport function createRunError(result: RunResult, cause?: unknown): ProcessError {\n\tconst reason = result.timedOut\n\t\t? 'timed out'\n\t\t: result.signal !== null\n\t\t\t? `was killed by ${result.signal}`\n\t\t\t: `exited with code ${String(result.code)}`\n\treturn new ProcessError(`Command '${result.command}' ${reason}`, {\n\t\tcode: result.timedOut ? '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,mBAAmB;;AAGhC,IAAa,iBAAiB;;;;ACE9B,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,KAAK,OAAO,QAAQ;EACpB,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;CACzD;AACD;;;;;;;;;;;;;AAcA,SAAgB,eAAe,OAAuC;CACrE,QAAA,GAAO,oBAAA,MAAA,OAAY,iBAAiB,YAAY;AACjD;;;;;;;AAQA,SAAgB,qBAAqB,IAA0B;CAC9D,OAAO,IAAI,aAAa,YAAY,GAAG,oBAAoB;EAC1D,MAAM;EACN,SAAS,EAAE,GAAG;CACf,CAAC;AACF;;;;;;;;AASA,SAAgB,eAAe,QAAmB,OAA+B;CAChF,MAAM,SAAS,OAAO,WACnB,cACA,OAAO,WAAW,OACjB,iBAAiB,OAAO,WACxB,oBAAoB,OAAO,OAAO,IAAI;CAC1C,OAAO,IAAI,aAAa,YAAY,OAAO,QAAQ,IAAI,UAAU;EAChE,MAAM,OAAO,WAAW,YAAY;EACpC,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.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","import { holds } from '@orkestrel/contract'\nimport type {\n\tProcessErrorCode,\n\tProcessErrorContext,\n\tProcessErrorOptions,\n\tRunResult,\n} from './types.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?: RunResult\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\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 * @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\treturn holds(() => value instanceof ProcessError)\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, including\n * an abort and an output overflow, is a `spawn` failure carrying its {@link RunResult}.\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 RunResult}\n */\nexport function createRunError(result: RunResult, 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;;;;ACV/B,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,KAAK,OAAO,QAAQ;EACpB,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;CACzD;AACD;;;;;;;;;;;;;AAcA,SAAgB,eAAe,OAAuC;CACrE,QAAA,GAAO,oBAAA,MAAA,OAAY,iBAAiB,YAAY;AACjD;;;;;;;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;;;;;;;;;;;;AAaA,SAAgB,eAAe,QAAmB,OAA+B;CAChF,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"}