stream-transform
Version:
Object transformations implementing the Node.js `stream.Transform` API
165 lines (152 loc) • 4.15 kB
JavaScript
'use strict';
var stream = require('stream');
var util = require('util');
/*
Stream Transform
Please look at the [project documentation](https://csv.js.org/transform/) for
additional information.
*/
const Transformer = function (options = {}, handler) {
this.options = options;
if (options.consume === undefined || options.consume === null) {
this.options.consume = false;
}
this.options.objectMode = true;
if (options.parallel === undefined || options.parallel === null) {
this.options.parallel = 100;
}
if (options.params === undefined || options.params === null) {
options.params = null;
}
this.handler = handler;
stream.Transform.call(this, this.options);
this.state = {
running: 0,
started: 0,
finished: 0,
paused: false,
};
return this;
};
util.inherits(Transformer, stream.Transform);
Transformer.prototype._transform = function (chunk, _, cb) {
this.state.started++;
this.state.running++;
// Accept additionnal chunks to be processed in parallel
if (!this.state.paused && this.state.running < this.options.parallel) {
cb();
cb = null; // Cancel further callback execution
}
try {
let l = this.handler.length;
if (this.options.params !== null) {
l--;
}
if (l === 1) {
// sync
const result = this.handler.call(this, chunk, this.options.params);
if (result && result.then) {
result.then((result) => {
this.__done(null, [result], cb);
});
result.catch((err) => {
this.__done(err);
});
} else {
this.__done(null, [result], cb);
}
} else if (l === 2) {
// async
const callback = (err, ...chunks) => this.__done(err, chunks, cb);
this.handler.call(this, chunk, callback, this.options.params);
} else {
throw Error("Invalid handler arguments");
}
return false;
} catch (err) {
this.__done(err);
}
};
Transformer.prototype._flush = function (cb) {
if (this.state.running === 0) {
cb();
} else {
this._ending = function () {
cb();
};
}
};
Transformer.prototype.__done = function (err, chunks, cb) {
this.state.running--;
if (err) {
return this.destroy(err);
// return this.emit('error', err);
}
this.state.finished++;
for (let chunk of chunks) {
if (typeof chunk === "number") {
chunk = `${chunk}`;
}
// We dont push empty string
// See https://nodejs.org/api/stream.html#stream_readable_push
if (chunk !== undefined && chunk !== null && chunk !== "") {
this.state.paused = !this.push(chunk);
}
}
// Chunk has been processed
if (cb) {
cb();
}
if (this._ending && this.state.running === 0) {
this._ending();
}
};
/*
Stream Transform - sync module
Please look at the [project documentation](https://csv.js.org/transform/) for
additional information.
*/
const transform = function () {
// Import arguments normalization
let handler, records;
let options = {};
for (const i in arguments) {
const argument = arguments[i];
let type = typeof argument;
if (argument === null) {
type = "null";
} else if (type === "object" && Array.isArray(argument)) {
type = "array";
}
if (type === "array") {
records = argument;
} else if (type === "object") {
options = { ...argument };
} else if (type === "function") {
handler = argument;
} else if (type !== "null") {
throw new Error(
`Invalid Arguments: got ${JSON.stringify(argument)} at position ${i}`,
);
}
}
// Validate arguments
let expected_handler_length = 1;
if (options.params) {
expected_handler_length++;
}
if (handler.length > expected_handler_length) {
throw Error("Invalid Handler: only synchonous handlers are supported");
}
// Start transformation
const chunks = [];
const transformer = new Transformer(options, handler);
transformer.push = function (chunk) {
chunks.push(chunk);
};
for (const record of records) {
transformer._transform(record, null, function () {});
}
return chunks;
};
exports.transform = transform;