Description
See #693 (comment)
Object mode
The stdin
/stdout
/stderr
/stdio
option can be a generator function.
const transform = async function * (chunk) {
yield chunk.toUpperCase();
};
const {stdout} = await execa('echo', ['hello'], {stdout: transform});
The chunk
is currently typed as unknown
and the return value is an [Async]Generator<unknown, void, void>
. Additionally, the input
option is also an Iterable<Unknown>
or AsyncIterable<unknown>
.
That's because when the stream is in object mode, the chunk type can be anything. However, when not in object mode, it is string | Uint8Array
. It is technically feasible (but hard) to determine with our types whether object mode is used, and type accordingly. The following needs to be considered:
- The stream can be set with the
stdin
/stdout
/stderr
options, but also thestdio[0|1|2]
option - The
stdio
option can have additional items beyond those 3 streams - Those options can either be a single value or an array of values
- Transforms are in object mode when using
{ transform, objectMode: true }
- The stream is in object mode when its transforms are in object mode
- The stream might mix transforms in object mode and not
- Output streams are in object mode if their last transform is in object mode
- Input streams are in object mode if their first transform is in object mode
stdin
is always an input stream,stdout
/stderr
are output streams.stdio[3]
and beyond are usually output streams, unless one thestdio[*]
option uses an iterable,Uint8Array
,Readable
,ReadableStream
,0
orprocess.stdin
.
- The stream is not in object mode when it has no transforms
- The
stdin
/stdout
/stderr
/stdio
options' value can be an array of both transforms and non-transforms. The non-transforms should be ignored for the logic above. - In practice, we could use a shortcut where the type consider a stream in object mode if it uses any transform in object mode. That's because mixing multiple transforms in both object mode and not is uncommon and not always useful.
- The
input
option usesstdin
's object mode
The above looks scary, but we actually already have this logic with our types.
Lines 102 to 135 in 16e5fd6
The hard part is actually different. The main issue is that we need to type the options.std*
arguments, while at the same time infer types from that same argument. We could use an union to declare that each option can use either only unknown
but no non-object mode transforms, or only string | Uint8Array
but no object mode transforms.
All of the above also impacts the final
method. Although it does not have any argument, its return type is the same as the transform
method.
Encoding
Once this is done, we can make types even stricter by typing those as either Uint8Array
or string
(when not in object mode) instead of Uint8Array | string
. This is decided by whether either the encoding
option is binary, or whether the binary
option is set.
Please note the return value of the transform
and final
methods is always Uint8Array | string
, even when the argument is only string
or only Uint8Array
.
Activity