@cosyte/cli 0.0.6 → 0.1.1
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/CHANGELOG.md +326 -2
- package/README.md +118 -53
- package/dist/bin/cosyte-mcp.cjs +461 -106
- package/dist/bin/cosyte-mcp.cjs.map +1 -1
- package/dist/bin/cosyte-mcp.mjs +461 -106
- package/dist/bin/cosyte-mcp.mjs.map +1 -1
- package/dist/bin/cosyte.cjs +824 -140
- package/dist/bin/cosyte.cjs.map +1 -1
- package/dist/bin/cosyte.mjs +825 -141
- package/dist/bin/cosyte.mjs.map +1 -1
- package/dist/index.cjs +592 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +515 -281
- package/dist/index.d.ts +515 -281
- package/dist/index.mjs +574 -104
- package/dist/index.mjs.map +1 -1
- package/dist/io-C9bSG2zF.d.cts +381 -0
- package/dist/io-C9bSG2zF.d.ts +381 -0
- package/dist/mcp.cjs +435 -103
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +70 -9
- package/dist/mcp.d.ts +70 -9
- package/dist/mcp.mjs +435 -103
- package/dist/mcp.mjs.map +1 -1
- package/package.json +30 -15
- package/dist/io-B2EobRzC.d.cts +0 -57
- package/dist/io-B2EobRzC.d.ts +0 -57
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `cosyte` CLI **exit-code contract**: a designed, documented surface that CI pipelines and
|
|
5
|
+
* shell scripts branch on. `0` is the conventional success status; the codes from `65` up are the
|
|
6
|
+
* values the Unix `sysexits.h` header assigns to the constants named beside them; `1` and `2` are
|
|
7
|
+
* this CLI's own, because that header defines no constant for either.
|
|
8
|
+
*
|
|
9
|
+
* The load-bearing rule: **the CLI never prints a reassuring line and exits `0` on input it could not
|
|
10
|
+
* handle.** An undetectable format, an unreadable file, a parser that throws, or a consumer that
|
|
11
|
+
* closed the output stream before the output arrived each map to a distinct, stable non-zero code,
|
|
12
|
+
* never a silent success.
|
|
13
|
+
*
|
|
14
|
+
* | Code | Name | Meaning |
|
|
15
|
+
* |------|------------|----------------------------------------------------------------------|
|
|
16
|
+
* | `0` | `OK` | success: the operation completed; `validate` found the input **valid** |
|
|
17
|
+
* | `1` | `INVALID` | operation-level failure: `validate` found the input **invalid**, or `redact` could not de-identify every locus (this CLI's own value; a real, expected CI signal: the tool worked, the operation did not complete) |
|
|
18
|
+
* | `2` | `USAGE` | usage error: unknown command, bad flag, missing argument (this CLI's own value) |
|
|
19
|
+
* | `65` | `DATAERR` | data error: input could not be parsed / format not detected (EX_DATAERR) |
|
|
20
|
+
* | `66` | `NOINPUT` | no input: the file does not exist or is unreadable (EX_NOINPUT) |
|
|
21
|
+
* | `69` | `UNAVAILABLE` | a required capability is not wired for this input: e.g. `redact` on a format `@cosyte/deid` has no adapter for (EX_UNAVAILABLE) |
|
|
22
|
+
* | `70` | `SOFTWARE` | internal error: an unexpected exception, i.e. a bug (EX_SOFTWARE) |
|
|
23
|
+
* | `74` | `IOERR` | output error: the output stream closed before the CLI had finished writing to it, the shape a downstream consumer that exits early produces (EX_IOERR) |
|
|
24
|
+
*
|
|
25
|
+
* The load-bearing `validate` rule: a **parseable-but-invalid** message is exit `1`, never exit `0`.
|
|
26
|
+
* The CLI must never print a reassuring line and exit green on a bad message. Exit `65` is reserved
|
|
27
|
+
* for input that could not be **parsed** at all (a distinct signal from "parsed, but does not
|
|
28
|
+
* conform").
|
|
29
|
+
*
|
|
30
|
+
* @packageDocumentation
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The stable exit-code map. Adding a code is a documented, tested change to the CLI's contract;
|
|
34
|
+
* renaming or repurposing one is a breaking change.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { EXIT } from "@cosyte/cli";
|
|
39
|
+
*
|
|
40
|
+
* process.exitCode = EXIT.OK; // => 0
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const EXIT: {
|
|
44
|
+
/** Success: the operation completed; `validate` found the input **valid**. */
|
|
45
|
+
readonly OK: 0;
|
|
46
|
+
/** Operation-level failure: `validate` found the input **invalid** (parseable but non-conformant),
|
|
47
|
+
* or `redact` could not de-identify every locus: a real, expected CI signal that the tool worked
|
|
48
|
+
* and the operation did not complete. Never emitted for unparseable input (that is `DATAERR`). */
|
|
49
|
+
readonly INVALID: 1;
|
|
50
|
+
/** Usage error: unknown command, bad flag, missing argument. A value specific to this CLI, not a
|
|
51
|
+
* `sysexits.h` constant. */
|
|
52
|
+
readonly USAGE: 2;
|
|
53
|
+
/** Data error: input could not be parsed or its format could not be detected (`EX_DATAERR`). */
|
|
54
|
+
readonly DATAERR: 65;
|
|
55
|
+
/** No input: the named file does not exist or is unreadable (`EX_NOINPUT`). */
|
|
56
|
+
readonly NOINPUT: 66;
|
|
57
|
+
/** Unavailable: a required capability is not wired for this input (e.g. `redact` on a format
|
|
58
|
+
* `@cosyte/deid` has no adapter for, or with that optional library absent), a distinct non-zero
|
|
59
|
+
* signal that is never a fake success (`EX_UNAVAILABLE`). */
|
|
60
|
+
readonly UNAVAILABLE: 69;
|
|
61
|
+
/** Internal error: an unexpected exception (a bug), distinct from a handled bad input (`EX_SOFTWARE`). */
|
|
62
|
+
readonly SOFTWARE: 70;
|
|
63
|
+
/** Output error: the stream the CLI was writing to closed before the output had been delivered, so
|
|
64
|
+
* the consumer holds nothing or holds only part of the answer. The shape a downstream consumer
|
|
65
|
+
* that exits early produces (a pager quit, a `head` that has seen enough). A handled condition the
|
|
66
|
+
* consumer owns, never a defect in the CLI, so it is deliberately distinct from `SOFTWARE`
|
|
67
|
+
* (`EX_IOERR`). */
|
|
68
|
+
readonly IOERR: 74;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* A value from {@link EXIT}: the exit code a CLI invocation resolves to.
|
|
72
|
+
*/
|
|
73
|
+
type ExitCode = (typeof EXIT)[keyof typeof EXIT];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The result of running a CLI invocation, as data: decoupled from `process` so the whole command
|
|
77
|
+
* tree is testable. The thin `bin` entry writes {@link RunResult.stdout} to `process.stdout`,
|
|
78
|
+
* {@link RunResult.stderr} to `process.stderr`, and exits with {@link RunResult.exit}.
|
|
79
|
+
*
|
|
80
|
+
* The two channels carry the CLI's PHI posture: **stdout is the data channel** (the
|
|
81
|
+
* user's explicit request: the parsed model), while **stderr is value-free**: codes and positions
|
|
82
|
+
* only, never an input value.
|
|
83
|
+
*
|
|
84
|
+
* @packageDocumentation
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A fully-resolved CLI invocation.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { EXIT, type RunResult } from "@cosyte/cli";
|
|
93
|
+
*
|
|
94
|
+
* const r: RunResult = { stdout: "{}\n", stderr: "", exit: EXIT.OK };
|
|
95
|
+
* r.exit; // => 0
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
interface RunResult {
|
|
99
|
+
/** The data channel: the parsed model or requested output. May contain the user's data by design. */
|
|
100
|
+
readonly stdout: string;
|
|
101
|
+
/** The diagnostic channel is **value-free**: codes, positions, file paths, format names only. */
|
|
102
|
+
readonly stderr: string;
|
|
103
|
+
/** The exit code the invocation resolves to. */
|
|
104
|
+
readonly exit: ExitCode;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The CLI's **value-free diagnostic channel**. Every error the CLI raises on its own behalf carries a
|
|
109
|
+
* stable `CLI_*` code, a fixed exit code, and a **PHI-safe** message built only from positional /
|
|
110
|
+
* structural context: a segment or field index, a byte offset, a file path, a format name, a warning
|
|
111
|
+
* code. **A diagnostic never echoes an input value** (a name, a DOB, an MRN, a result). This is the
|
|
112
|
+
* parsers' "warning = code + position, never a value" rule applied at the CLI edge.
|
|
113
|
+
*
|
|
114
|
+
* @packageDocumentation
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Stable **CLI diagnostic code** registry: errors the CLI owns (routing, I/O, argument handling),
|
|
119
|
+
* distinct from the wrapped library's own warning/issue codes which are passed through unchanged.
|
|
120
|
+
* Each code is its own value (`key === value`) so the set survives `Object.values(...)`; renaming or
|
|
121
|
+
* removing one is a **breaking change** because scripts branch on the stderr text.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { CLI_CODES } from "@cosyte/cli";
|
|
126
|
+
*
|
|
127
|
+
* if (diagnostic.code === CLI_CODES.CLI_FORMAT_UNDETECTED) {
|
|
128
|
+
* // ask the user for --format
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare const CLI_CODES: {
|
|
133
|
+
/** A required argument was missing or a flag was invalid. Exit `2`. */
|
|
134
|
+
readonly CLI_USAGE: "CLI_USAGE";
|
|
135
|
+
/** The named file does not exist or could not be read. Exit `66`. */
|
|
136
|
+
readonly CLI_NO_INPUT: "CLI_NO_INPUT";
|
|
137
|
+
/** The input was empty, no bytes to detect or parse. Exit `65`. */
|
|
138
|
+
readonly CLI_EMPTY_INPUT: "CLI_EMPTY_INPUT";
|
|
139
|
+
/** No format signature matched; the CLI will not guess. Names the candidates, never the bytes. Exit `65`. */
|
|
140
|
+
readonly CLI_FORMAT_UNDETECTED: "CLI_FORMAT_UNDETECTED";
|
|
141
|
+
/** More than one format signature matched; the CLI will not guess. Names the candidates, never the bytes. Exit `65`. */
|
|
142
|
+
readonly CLI_FORMAT_AMBIGUOUS: "CLI_FORMAT_AMBIGUOUS";
|
|
143
|
+
/** A recognised format whose parser does not support the requested operation (e.g. `parse` on a
|
|
144
|
+
* format wired only for `inspect`). Names the format + operation, never the bytes. Exit `65`. */
|
|
145
|
+
readonly CLI_FORMAT_UNSUPPORTED: "CLI_FORMAT_UNSUPPORTED";
|
|
146
|
+
/** The optional parser package for a recognised format is not installed. The six breadth parsers
|
|
147
|
+
* (dicom/x12/ccda/ncpdp/astm/mllp) are `optionalDependencies`: normally installed, but if
|
|
148
|
+
* one is absent the CLI degrades to this value-free signal rather than crashing. Exit `69`. */
|
|
149
|
+
readonly CLI_PARSER_UNAVAILABLE: "CLI_PARSER_UNAVAILABLE";
|
|
150
|
+
/** The wrapped parser rejected the input. Positional context only, never the offending bytes. Exit `65`. */
|
|
151
|
+
readonly CLI_PARSE_FAILED: "CLI_PARSE_FAILED";
|
|
152
|
+
/** The input is larger than the documented size limit the CLI reads in one invocation. Raised
|
|
153
|
+
* against the running byte count while the input is read, so the refusal always arrives before an
|
|
154
|
+
* allocation ceiling could. Names the limit, never the input. Exit `65`, never `70`. */
|
|
155
|
+
readonly CLI_INPUT_TOO_LARGE: "CLI_INPUT_TOO_LARGE";
|
|
156
|
+
/** The output stream could not be written: a consumer that closes the pipe ends the invocation
|
|
157
|
+
* here rather than as an unhandled platform error, whether it went away part way through a record
|
|
158
|
+
* stream or before a single-result write was delivered. Names no input, and never the platform's
|
|
159
|
+
* own error text. Exit `74`. */
|
|
160
|
+
readonly CLI_OUTPUT_WRITE_FAILED: "CLI_OUTPUT_WRITE_FAILED";
|
|
161
|
+
/** The BYO ConceptMap supplied to `map-codes` is not valid JSON or not a loadable FHIR ConceptMap.
|
|
162
|
+
* Names the stable terminology-loader code, never the map's bytes. Exit `65`. */
|
|
163
|
+
readonly CLI_MAP_INVALID: "CLI_MAP_INVALID";
|
|
164
|
+
/** A command whose ground-layer library does not cover the resolved format (e.g. `redact` on a
|
|
165
|
+
* format `@cosyte/deid` ships no adapter for). Never a fake success: a distinct, value-free
|
|
166
|
+
* "unavailable" signal. Exit `69`. */
|
|
167
|
+
readonly CLI_NOT_IMPLEMENTED: "CLI_NOT_IMPLEMENTED";
|
|
168
|
+
/** The delegated de-identifier reported that it could not handle every element of the input (a
|
|
169
|
+
* fail-closed `blocked` locus, or its own fatal). The run is **not** a de-identified copy, so no
|
|
170
|
+
* output is emitted: an operation-level failure, never a bug. Names loci and codes, never a
|
|
171
|
+
* value. Exit `1`. */
|
|
172
|
+
readonly CLI_DEID_INCOMPLETE: "CLI_DEID_INCOMPLETE";
|
|
173
|
+
/** An unexpected internal error (a bug). Exit `70`. */
|
|
174
|
+
readonly CLI_INTERNAL: "CLI_INTERNAL";
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* A value from {@link CLI_CODES}: the stable code carried by a {@link CliError}.
|
|
178
|
+
*/
|
|
179
|
+
type CliCode = (typeof CLI_CODES)[keyof typeof CLI_CODES];
|
|
180
|
+
/**
|
|
181
|
+
* A typed, PHI-safe CLI error. Its {@link CliError.message | message} is **value-free by
|
|
182
|
+
* construction**: callers must build it from codes, indices, offsets, format names, and file paths,
|
|
183
|
+
* never from an input value. It carries the {@link ExitCode} the invocation resolves to.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* import { CliError, CLI_CODES, EXIT } from "@cosyte/cli";
|
|
188
|
+
*
|
|
189
|
+
* throw new CliError(CLI_CODES.CLI_NO_INPUT, EXIT.NOINPUT, "cannot read the named file");
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare class CliError extends Error {
|
|
193
|
+
/** The stable diagnostic code. */
|
|
194
|
+
readonly code: CliCode;
|
|
195
|
+
/** The exit code this error resolves the invocation to. */
|
|
196
|
+
readonly exit: ExitCode;
|
|
197
|
+
/**
|
|
198
|
+
* @param code - A stable {@link CliCode}.
|
|
199
|
+
* @param exit - The {@link ExitCode} to resolve to.
|
|
200
|
+
* @param message - A **value-free** explanation (positional/structural context only, never PHI).
|
|
201
|
+
*/
|
|
202
|
+
constructor(code: CliCode, exit: ExitCode, message: string);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Render a {@link CliError} as a single value-free `stderr` line: `cosyte: <CODE>: <message>`. The
|
|
206
|
+
* message is already value-free by the {@link CliError} contract; this only prefixes the tool name
|
|
207
|
+
* and the code so scripts and humans can branch on a stable token.
|
|
208
|
+
*
|
|
209
|
+
* @param err - The CLI error to render.
|
|
210
|
+
* @returns The stderr line (no trailing newline).
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { CliError, CLI_CODES, EXIT, formatDiagnostic } from "@cosyte/cli";
|
|
214
|
+
*
|
|
215
|
+
* formatDiagnostic(new CliError(CLI_CODES.CLI_USAGE, EXIT.USAGE, "missing <file> argument"));
|
|
216
|
+
* // => "cosyte: CLI_USAGE: missing <file> argument"
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
declare function formatDiagnostic(err: CliError): string;
|
|
220
|
+
/**
|
|
221
|
+
* Resolve a {@link CliError} into a value-free {@link RunResult}: empty `stdout` (nothing reaches the
|
|
222
|
+
* data channel on an error), the rendered diagnostic on `stderr`, and the error's exit code. The
|
|
223
|
+
* single place the CLI turns an owned error into a result, so every command and the dispatcher render
|
|
224
|
+
* errors identically.
|
|
225
|
+
*
|
|
226
|
+
* @param err - The CLI error to resolve.
|
|
227
|
+
* @returns The value-free {@link RunResult}.
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* import { CliError, CLI_CODES, EXIT, errorResult } from "@cosyte/cli";
|
|
231
|
+
*
|
|
232
|
+
* errorResult(new CliError(CLI_CODES.CLI_USAGE, EXIT.USAGE, "missing <file>")).exit; // => 2
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function errorResult(err: CliError): RunResult;
|
|
236
|
+
/**
|
|
237
|
+
* Coerce an unknown thrown value into a {@link CliError}. A {@link CliError} passes through; anything
|
|
238
|
+
* else becomes a `CLI_INTERNAL` / {@link EXIT.SOFTWARE} error whose message is a **fixed, value-free
|
|
239
|
+
* string**: the original message is deliberately discarded so a parser exception that embedded input
|
|
240
|
+
* bytes can never reach stderr.
|
|
241
|
+
*
|
|
242
|
+
* @param err - The caught value.
|
|
243
|
+
* @returns A {@link CliError} safe to render to stderr.
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* import { toCliError } from "@cosyte/cli";
|
|
247
|
+
*
|
|
248
|
+
* toCliError(new Error("boom")).code; // => "CLI_INTERNAL"
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
declare function toCliError(err: unknown): CliError;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Input I/O for the CLI: read the bytes to operate on from a **file argument** or from **stdin**
|
|
255
|
+
* (`-`), with a value-free failure mode. The CLI never writes a temp file and never logs to a file;
|
|
256
|
+
* this module only *reads*.
|
|
257
|
+
*
|
|
258
|
+
* Reading comes in two shapes. The **whole-input** readers ({@link readFileBytes},
|
|
259
|
+
* {@link readStreamBytes}) hand a command one buffer, which is what a single-message command wants.
|
|
260
|
+
* The **chunk** readers ({@link fileChunks}, {@link streamChunks}) hand it an async stream of chunks,
|
|
261
|
+
* which is what a multi-record command wants: it can emit a record's output before the rest of the
|
|
262
|
+
* input has arrived, and the CLI's own size limit can be enforced against the **running total**
|
|
263
|
+
* rather than after an oversized input has already been assembled.
|
|
264
|
+
*
|
|
265
|
+
* The reader functions are injected into the command layer as {@link RunDeps} so the whole dispatch
|
|
266
|
+
* path is testable without touching `process`: the real `process.stdin` wiring lives in the thin
|
|
267
|
+
* `bin` entry, and so does the one function in {@link RunDeps} that writes rather than reads.
|
|
268
|
+
*
|
|
269
|
+
* @packageDocumentation
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The injectable I/O side-effects the dispatcher needs. Kept tiny and pure-ish so tests drive the
|
|
274
|
+
* CLI end to end with in-memory fakes and no real filesystem, stdin or stdout.
|
|
275
|
+
*
|
|
276
|
+
* The two whole-input readers are required; the chunk readers and the output sink are **optional**,
|
|
277
|
+
* and a caller that omits them still gets identical output. They are the difference between reading
|
|
278
|
+
* a stream as it arrives and being handed one buffer: with them, a multi-record `parse` emits each
|
|
279
|
+
* record as it is produced and refuses an over-limit input while it is still arriving; without them,
|
|
280
|
+
* the same code path runs over a single chunk and returns its output on the result's `stdout` as
|
|
281
|
+
* before.
|
|
282
|
+
*/
|
|
283
|
+
interface RunDeps {
|
|
284
|
+
/** Read a file's bytes, or raise a `CLI_NO_INPUT` {@link CliError} if it cannot be read. */
|
|
285
|
+
readonly readFile: (path: string) => Promise<Uint8Array>;
|
|
286
|
+
/** Read all of stdin's bytes. */
|
|
287
|
+
readonly readStdin: () => Promise<Uint8Array>;
|
|
288
|
+
/** Optional: open a file as a stream of byte chunks (see {@link fileChunks}). */
|
|
289
|
+
readonly openFile?: (path: string) => AsyncIterable<Uint8Array>;
|
|
290
|
+
/** Optional: open stdin as a stream of byte chunks (see {@link streamChunks}). */
|
|
291
|
+
readonly openStdin?: () => AsyncIterable<Uint8Array>;
|
|
292
|
+
/**
|
|
293
|
+
* Optional: write one piece of the data channel **as it is produced**. When present, a multi-record
|
|
294
|
+
* `parse` writes each record's NDJSON line through here instead of accumulating them, and the
|
|
295
|
+
* returned result's `stdout` is empty because the output has already been delivered. May throw (a
|
|
296
|
+
* consumer that closed the pipe); the caller turns that into a value-free
|
|
297
|
+
* `CLI_OUTPUT_WRITE_FAILED`.
|
|
298
|
+
*/
|
|
299
|
+
readonly writeStdout?: (chunk: string) => void;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Read a file into bytes, mapping any read failure to a **value-free** `CLI_NO_INPUT` /
|
|
303
|
+
* {@link EXIT.NOINPUT} error. The path is structural context (the user supplied it), so it may appear
|
|
304
|
+
* in the message; the file *contents* never do.
|
|
305
|
+
*
|
|
306
|
+
* A file whose size already exceeds `limit` is refused **before it is read**, with the value-free
|
|
307
|
+
* over-limit data error, so an oversized file is never allocated in the first place. The size is read
|
|
308
|
+
* from the **open descriptor** rather than from the path, so what is measured and what is read are the
|
|
309
|
+
* same file even if the path is replaced in between.
|
|
310
|
+
*
|
|
311
|
+
* @param path - The file path to read.
|
|
312
|
+
* @param limit - The maximum number of bytes to accept. Defaults to {@link MAX_INPUT_BYTES}.
|
|
313
|
+
* @returns The file bytes.
|
|
314
|
+
* @throws {CliError} `CLI_NO_INPUT` (exit `66`) when the file is missing, a directory, or unreadable;
|
|
315
|
+
* `CLI_INPUT_TOO_LARGE` (exit `65`) when it is larger than `limit`.
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts throws
|
|
318
|
+
* import { readFileBytes } from "@cosyte/cli";
|
|
319
|
+
*
|
|
320
|
+
* await readFileBytes("/no/such/file"); // throws CliError CLI_NO_INPUT
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
declare function readFileBytes(path: string, limit?: number): Promise<Uint8Array>;
|
|
324
|
+
/**
|
|
325
|
+
* Drain a readable stream (e.g. `process.stdin`) into a single byte buffer, refusing an over-limit
|
|
326
|
+
* stream against the **running total**: the read is abandoned the moment the accumulated size passes
|
|
327
|
+
* `limit`, so the bytes past it are never allocated and no platform allocation ceiling is ever
|
|
328
|
+
* reached.
|
|
329
|
+
*
|
|
330
|
+
* @param stream - The readable stream to drain.
|
|
331
|
+
* @param limit - The maximum number of bytes to accept. Defaults to {@link MAX_INPUT_BYTES}.
|
|
332
|
+
* @returns The concatenated bytes.
|
|
333
|
+
* @throws {CliError} `CLI_INPUT_TOO_LARGE` (exit `65`) as soon as the running total passes `limit`.
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* import { Readable } from "node:stream";
|
|
337
|
+
* import { readStreamBytes } from "@cosyte/cli";
|
|
338
|
+
*
|
|
339
|
+
* const bytes = await readStreamBytes(Readable.from([Buffer.from("MSH|")]));
|
|
340
|
+
* bytes.length; // => 4
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
declare function readStreamBytes(stream: Readable, limit?: number): Promise<Uint8Array>;
|
|
344
|
+
/**
|
|
345
|
+
* Open a readable stream (e.g. `process.stdin`) as a stream of byte **chunks**, in arrival order.
|
|
346
|
+
* Unlike {@link readStreamBytes} this never concatenates: the consumer decides what to keep, which is
|
|
347
|
+
* what lets a multi-record command emit a record before the rest of the input has been read.
|
|
348
|
+
*
|
|
349
|
+
* @param stream - The readable stream to iterate.
|
|
350
|
+
* @returns An async iterable of the stream's chunks, string chunks encoded as UTF-8.
|
|
351
|
+
* @example
|
|
352
|
+
* ```ts
|
|
353
|
+
* import { Readable } from "node:stream";
|
|
354
|
+
* import { streamChunks } from "@cosyte/cli";
|
|
355
|
+
*
|
|
356
|
+
* const seen: number[] = [];
|
|
357
|
+
* for await (const c of streamChunks(Readable.from([Buffer.from("MS"), Buffer.from("H|")]))) {
|
|
358
|
+
* seen.push(c.length);
|
|
359
|
+
* }
|
|
360
|
+
* seen.length; // => 2
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
declare function streamChunks(stream: Readable): AsyncGenerator<Uint8Array>;
|
|
364
|
+
/**
|
|
365
|
+
* Open a file as a stream of byte **chunks**, with the same value-free failure mode as
|
|
366
|
+
* {@link readFileBytes}: a missing, unreadable or non-file path is a `CLI_NO_INPUT` error, never a
|
|
367
|
+
* raw filesystem exception.
|
|
368
|
+
*
|
|
369
|
+
* @param path - The file path to read.
|
|
370
|
+
* @returns An async iterable of the file's chunks.
|
|
371
|
+
* @throws {CliError} `CLI_NO_INPUT` (exit `66`) when the file cannot be opened or read.
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts throws
|
|
374
|
+
* import { fileChunks } from "@cosyte/cli";
|
|
375
|
+
*
|
|
376
|
+
* for await (const chunk of fileChunks("/no/such/file")) chunk.length; // throws CLI_NO_INPUT
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
declare function fileChunks(path: string): AsyncGenerator<Uint8Array>;
|
|
380
|
+
|
|
381
|
+
export { type CliCode as C, EXIT as E, type RunDeps as R, CliError as a, type RunResult as b, CLI_CODES as c, type ExitCode as d, errorResult as e, fileChunks as f, formatDiagnostic as g, readStreamBytes as h, readFileBytes as r, streamChunks as s, toCliError as t };
|