From 6f8bbd8632bff6e3ec7a993e8ecdd4df5304f67f Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Wed, 17 Sep 2025 23:47:10 -0700 Subject: [PATCH] Allow passing an array to `placeholder` This functionality is helpful to indicate different and more useful placeholders for each column. It also allows setting a completely blank row (without any commas) on error, which may be desirable to make output CSV row counts match input CSV row counts without clutter. --- src/errorStrategies.js | 14 +++++++++---- test/errorStrategies.test.js | 39 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/errorStrategies.js b/src/errorStrategies.js index f2b76e6..1a1fe2b 100644 --- a/src/errorStrategies.js +++ b/src/errorStrategies.js @@ -26,14 +26,20 @@ function skip() { /** * Create a `TransformationErrorFunction` that handles errors by filling the output row with a given placeholder value. + * To set a blank row without any commas, pass an empty array `[]`. To set a blank row with commas to match the number + * of columns in the rest of the CSV, pass an empty string `''`. * - * @param {string} value The placeholder value to fill the row with. This value will be used for each field in the row. + * @param {Array|string} value The placeholder value to fill the row with. If an `Array` is passed, `value` + * will be used as the literal row. If a `string` is passed, `value` will be used to fill each field in the row. * @returns {TransformationErrorFunction} A `TransformationErrorFunction` that may be passed to a `CSVTransformer`'s * `onError` option. */ function placeholder(value) { return (row) => { - if (row.length === 0) { + if (Array.isArray(value)) { + return value; + } + else if (row.length === 0) { return [value]; } else { @@ -46,7 +52,7 @@ function placeholder(value) { * @typedef {Object} RetryOptions * @property {number} [interval] The duration (in milliseconds) to wait in between each retry. The default value is `0` * (retry immediately). - * @property {string} [placeholder] A placeholder value to set if the function still errors after the final iteration. + * @property {Array|string} [placeholder] A placeholder value to set if the function still errors after the final iteration. * If unset, the final error thrown by the function is re-thrown. */ @@ -79,7 +85,7 @@ function retry(iterations, options = {}) { * @property {number} [maxExponent] If provided, the maximum exponent used to calculate the retry duration. In other words, * the `iterations` number at which the retry duration stops increasing. If `maxExponent` is unset, the retry duration * is unbounded and will increase after each retry. - * @property {string} [placeholder] A placeholder value to set if the function still errors after the final iteration. + * @property {Array|string} [placeholder] A placeholder value to set if the function still errors after the final iteration. * If unset, the final error thrown by the function is re-thrown. */ diff --git a/test/errorStrategies.test.js b/test/errorStrategies.test.js index 124446e..07f456c 100644 --- a/test/errorStrategies.test.js +++ b/test/errorStrategies.test.js @@ -54,6 +54,45 @@ suite('ErrorStrategies.skip', { concurrency: true }, () => { }); suite('ErrorStrategies.placeholder', { concurrency: true }, () => { + test('sets exact placeholder row if an array is passed', async () => { + await createCSVMockStream([ + ['header', 'header', 'header'], + ['1', '1', '1'], + ['1', '1', '1'] + ]) + .pipeThrough(new CSVTransformer(() => { throw new Error(); }, { onError: placeholder(['n/a 1', 'n/a 2', 'n/a 3']) })) + .pipeTo(csvStreamEqualWritable([ + ['header', 'header', 'header'], + ['n/a 1', 'n/a 2', 'n/a 3'], + ['n/a 1', 'n/a 2', 'n/a 3'] + ])); + }); + test('sets empty row if an empty array is passed', async () => { + await createCSVMockStream([ + ['header', 'header', 'header'], + ['1', '1', '1'], + ['1', '1', '1'] + ]) + .pipeThrough(new CSVTransformer(() => { throw new Error(); }, { onError: placeholder([]) })) + .pipeTo(csvStreamEqualWritable([ + ['header', 'header', 'header'], + [], + [] + ])); + }); + test('sets empty row matching column count if an empty string is passed', async () => { + await createCSVMockStream([ + ['header', 'header', 'header'], + ['1', '1', '1'], + ['1', '1', '1'] + ]) + .pipeThrough(new CSVTransformer(() => { throw new Error(); }, { onError: placeholder('') })) + .pipeTo(csvStreamEqualWritable([ + ['header', 'header', 'header'], + ['', '', ''], + ['', '', ''] + ])); + }); test('sets placeholders on all fields in a row', async () => { await createCSVMockStream([ ['header', 'header', 'header'], -- 2.47.3