# 🧙 Transforms
## Summary
Transforms map or filter the input or output of a subprocess. They are defined by passing a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) or a [transform options object](api.md#transform-options) to the [`stdin`](api.md#optionsstdin), [`stdout`](api.md#optionsstdout), [`stderr`](api.md#optionsstderr) or [`stdio`](api.md#optionsstdio) option. It can be [`async`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*).
```js
import {execa} from 'execa';
const transform = function * (line) {
const prefix = line.includes('error') ? 'ERROR' : 'INFO';
yield `${prefix}: ${line}`;
};
const {stdout} = await execa({stdout: transform})`echo HELLO`;
console.log(stdout); // INFO: HELLO
```
## Difference with iteration
Transforms operate one `line` at a time, just like [`subprocess.iterable()`](lines.md#progressive-splitting). However, unlike iteration, transforms:
- Modify the subprocess' [output](api.md#resultstdout) and [streams](api.md#subprocessstdout).
- Can apply to the subprocess' input.
- Are defined using a [generator function](#summary), [`Duplex`](#duplextransform-streams) stream, Node.js [`Transform`](#duplextransform-streams) stream or web [`TransformStream`](#duplextransform-streams).
## Filtering
`yield` can be called 0, 1 or multiple times. Not calling `yield` enables filtering a specific line.
```js
const transform = function * (line) {
if (!line.includes('secret')) {
yield line;
}
};
const {stdout} = await execa({stdout: transform})`echo ${'This is a secret'}`;
console.log(stdout); // ''
```
## Object mode
By default, [`stdout`](api.md#optionsstdout) and [`stderr`](api.md#optionsstderr)'s transforms must return a string or an `Uint8Array`. However, if the [`objectMode`](api.md#transformoptionsobjectmode) transform option is `true`, any type can be returned instead, except `null` or `undefined`. The subprocess' [`result.stdout`](api.md#resultstdout)/[`result.stderr`](api.md#resultstderr) will be an array of values.
```js
const transform = function * (line) {
yield JSON.parse(line);
};
const {stdout} = await execa({stdout: {transform, objectMode: true}})`node jsonlines-output.js`;
for (const data of stdout) {
console.log(stdout); // {...object}
}
```
[`stdin`](api.md#optionsstdin) can also use `objectMode: true`.
```js
const transform = function * (line) {
yield JSON.stringify(line);
};
const input = [{event: 'example'}, {event: 'otherExample'}];
await execa({stdin: [input, {transform, objectMode: true}]})`node jsonlines-input.js`;
```
## Sharing state
State can be shared between calls of the [`transform`](api.md#transformoptionstransform) and [`final`](api.md#transformoptionsfinal) functions.
```js
let count = 0;
// Prefix line number
const transform = function * (line) {
yield `[${count++}] ${line}`;
};
```
## Finalizing
To create additional lines after the last one, a [`final`](api.md#transformoptionsfinal) generator function can be used.
```js
let count = 0;
const transform = function * (line) {
count += 1;
yield line;
};
const final = function * () {
yield `Number of lines: ${count}`;
};
const {stdout} = await execa({stdout: {transform, final}})`npm run build`;
console.log(stdout); // Ends with: 'Number of lines: 54'
```
## Duplex/Transform streams
A [`Duplex`](https://nodejs.org/api/stream.html#class-streamduplex) stream, Node.js [`Transform`](https://nodejs.org/api/stream.html#class-streamtransform) stream or web [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) can be used instead of a generator function.
Like generator functions, web `TransformStream` can be passed either directly or as a [`{transform}` plain object](api.md#transform-options). But `Duplex` and `Transform` must always be passed as a `{transform}` plain object.
The [`objectMode`](#object-mode) transform option can be used, but not the [`binary`](api.md#transformoptionsbinary) nor [`preserveNewlines`](api.md#transformoptionspreservenewlines) options.
```js
import {createGzip} from 'node:zlib';
import {execa} from 'execa';
const {stdout} = await execa({
stdout: {transform: createGzip()},
encoding: 'buffer',
})`npm run build`;
console.log(stdout); // `stdout` is compressed with gzip
```
```js
const {stdout} = await execa({
stdout: new CompressionStream('gzip'),
encoding: 'buffer',
})`npm run build`;
console.log(stdout); // `stdout` is compressed with gzip
```
## Combining
The [`stdin`](api.md#optionsstdin), [`stdout`](api.md#optionsstdout), [`stderr`](api.md#optionsstderr) and [`stdio`](api.md#optionsstdio) options can accept [an array of values](output.md#multiple-targets). While this is not specific to transforms, this can be useful with them too. For example, the following transform impacts the value printed by `'inherit'`.
```js
await execa({stdout: [transform, 'inherit']})`npm run build`;
```
This also allows using multiple transforms.
```js
await execa({stdout: [transform, otherTransform]})`npm run build`;
```
Or saving to archives.
```js
await execa({stdout: [new CompressionStream('gzip'), {file: './output.gz'}]})`npm run build`;
```
[**Next**: 🔀 Piping multiple subprocesses](pipe.md)\
[**Previous**: 🤖 Binary data](binary.md)\
[**Top**: Table of contents](../readme.md#documentation)