<picture>
	<source media="(prefers-color-scheme: dark)" srcset="../media/logo_dark.svg">
	<img alt="execa logo" src="../media/logo.svg" width="400">
</picture>
<br>

# 🧙 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`;
```

<hr>

[**Next**: 🔀 Piping multiple subprocesses](pipe.md)\
[**Previous**: 🤖 Binary data](binary.md)\
[**Top**: Table of contents](../readme.md#documentation)
