@naturalcycles/nodejs-lib 12.53.0 → 12.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -47,7 +47,7 @@ import { transformToString } from './stream/transform/transformToString';
47
47
  import { BaseWorkerClass, WorkerClassInterface } from './stream/transform/worker/baseWorkerClass';
48
48
  import { transformMultiThreaded, TransformMultiThreadedOptions } from './stream/transform/worker/transformMultiThreaded';
49
49
  import { WorkerInput, WorkerOutput } from './stream/transform/worker/transformMultiThreaded.model';
50
- import { writableForEach } from './stream/writable/writableForEach';
50
+ export * from './stream/writable/writableForEach';
51
51
  import { writableFork } from './stream/writable/writableFork';
52
52
  import { writablePushToArray } from './stream/writable/writablePushToArray';
53
53
  import { writableVoid } from './stream/writable/writableVoid';
@@ -65,5 +65,6 @@ import { anyObjectSchema, anySchema, arraySchema, oneOfSchema, binarySchema, boo
65
65
  import { JoiValidationError, JoiValidationErrorData } from './validation/joi/joi.validation.error';
66
66
  import { convert, getValidationResult, isValid, JoiValidationResult, undefinedIfInvalid, validate } from './validation/joi/joi.validation.util';
67
67
  import { sanitizeHTML, SanitizeHTMLOptions } from './validation/sanitize.util';
68
- export type { JoiValidationErrorData, JoiValidationResult, ValidationErrorItem, ExtendedJoi, SchemaTyped, AnySchema, AnySchemaTyped, ArraySchemaTyped, BooleanSchemaTyped, NumberSchemaTyped, ObjectSchemaTyped, StringSchemaTyped, IDebug, IDebugger, SlackServiceCfg, SlackMessage, SlackMessageProps, SlackApiBody, SlackMessagePrefixHook, ReadableTyped, WritableTyped, TransformTyped, PipelineFromNDJsonFileOptions, PipelineToNDJsonFileOptions, TransformJsonParseOptions, TransformToNDJsonOptions, TransformMapOptions, TransformMapSyncOptions, NDJSONStreamForEachOptions, TransformOptions, TransformLogProgressOptions, TransformMultiThreadedOptions, WorkerClassInterface, WorkerInput, WorkerOutput, TableDiffOptions, InspectAnyOptions, Got, GetGotOptions, AfterResponseHook, BeforeErrorHook, BeforeRequestHook, AjvValidationOptions, AjvSchemaCfg, AjvValidationErrorData, SanitizeHTMLOptions, };
69
- export { JoiValidationError, validate, getValidationResult, isValid, undefinedIfInvalid, convert, Joi, booleanSchema, booleanDefaultToFalseSchema, stringSchema, numberSchema, integerSchema, percentageSchema, dateStringSchema, arraySchema, binarySchema, objectSchema, oneOfSchema, anySchema, anyObjectSchema, baseDBEntitySchema, savedDBEntitySchema, idSchema, unixTimestampSchema, verSchema, emailSchema, SEM_VER_PATTERN, semVerSchema, userAgentSchema, utcOffsetSchema, ipAddressSchema, slugSchema, urlSchema, processSharedUtil, zipBuffer, gzipBuffer, unzipBuffer, gunzipBuffer, zipString, gzipString, unzipToString, gunzipToString, requireEnvKeys, requireFileToExist, LRUMemoCache, stringId, stringIdAsync, stringIdUnsafe, ALPHABET_NUMBER, ALPHABET_LOWERCASE, ALPHABET_UPPERCASE, ALPHABET_ALPHANUMERIC_LOWERCASE, ALPHABET_ALPHANUMERIC_UPPERCASE, ALPHABET_ALPHANUMERIC, md5, hash, hashAsBuffer, md5AsBuffer, stringToBase64, base64ToString, bufferToBase64, base64ToBuffer, Debug, getSecretMap, setSecretMap, loadSecretsFromEnv, loadSecretsFromJsonFile, removeSecretsFromEnv, secret, secretOptional, memoryUsage, memoryUsageFull, SlackService, slackDefaultMessagePrefixHook, readableCreate, readableFrom, readableFromArray, readableToArray, readableForEach, readableForEachSync, readableMap, readableMapToArray, _pipeline, transformBuffer, ndjsonMap, ndJsonFileRead, ndJsonFileWrite, ndjsonStreamForEach, pipelineFromNDJsonFile, pipelineToNDJsonFile, NDJsonStats, streamToNDJsonFile, transformJsonParse, bufferReviver, transformToNDJson, transformFilter, transformFilterSync, transformMap, transformMapSync, transformMapSimple, transformNoOp, writableForEach, writablePushToArray, transformSplit, transformToString, transformToArray, transformTap, transformLogProgress, transformLimit, writableVoid, writableFork, transformMultiThreaded, BaseWorkerClass, tableDiff, inspectAny, inspectAnyStringifyFn, getGot, HTTPError, TimeoutError, _chunkBuffer, Ajv, getAjv, AjvSchema, AjvValidationError, readJsonSchemas, readAjvSchemas, hasColors, sanitizeHTML, };
68
+ import { runScript, RunScriptOptions } from './script';
69
+ export type { RunScriptOptions, JoiValidationErrorData, JoiValidationResult, ValidationErrorItem, ExtendedJoi, SchemaTyped, AnySchema, AnySchemaTyped, ArraySchemaTyped, BooleanSchemaTyped, NumberSchemaTyped, ObjectSchemaTyped, StringSchemaTyped, IDebug, IDebugger, SlackServiceCfg, SlackMessage, SlackMessageProps, SlackApiBody, SlackMessagePrefixHook, ReadableTyped, WritableTyped, TransformTyped, PipelineFromNDJsonFileOptions, PipelineToNDJsonFileOptions, TransformJsonParseOptions, TransformToNDJsonOptions, TransformMapOptions, TransformMapSyncOptions, NDJSONStreamForEachOptions, TransformOptions, TransformLogProgressOptions, TransformMultiThreadedOptions, WorkerClassInterface, WorkerInput, WorkerOutput, TableDiffOptions, InspectAnyOptions, Got, GetGotOptions, AfterResponseHook, BeforeErrorHook, BeforeRequestHook, AjvValidationOptions, AjvSchemaCfg, AjvValidationErrorData, SanitizeHTMLOptions, };
70
+ export { JoiValidationError, validate, getValidationResult, isValid, undefinedIfInvalid, convert, Joi, booleanSchema, booleanDefaultToFalseSchema, stringSchema, numberSchema, integerSchema, percentageSchema, dateStringSchema, arraySchema, binarySchema, objectSchema, oneOfSchema, anySchema, anyObjectSchema, baseDBEntitySchema, savedDBEntitySchema, idSchema, unixTimestampSchema, verSchema, emailSchema, SEM_VER_PATTERN, semVerSchema, userAgentSchema, utcOffsetSchema, ipAddressSchema, slugSchema, urlSchema, processSharedUtil, zipBuffer, gzipBuffer, unzipBuffer, gunzipBuffer, zipString, gzipString, unzipToString, gunzipToString, requireEnvKeys, requireFileToExist, LRUMemoCache, stringId, stringIdAsync, stringIdUnsafe, ALPHABET_NUMBER, ALPHABET_LOWERCASE, ALPHABET_UPPERCASE, ALPHABET_ALPHANUMERIC_LOWERCASE, ALPHABET_ALPHANUMERIC_UPPERCASE, ALPHABET_ALPHANUMERIC, md5, hash, hashAsBuffer, md5AsBuffer, stringToBase64, base64ToString, bufferToBase64, base64ToBuffer, Debug, getSecretMap, setSecretMap, loadSecretsFromEnv, loadSecretsFromJsonFile, removeSecretsFromEnv, secret, secretOptional, memoryUsage, memoryUsageFull, SlackService, slackDefaultMessagePrefixHook, readableCreate, readableFrom, readableFromArray, readableToArray, readableForEach, readableForEachSync, readableMap, readableMapToArray, _pipeline, transformBuffer, ndjsonMap, ndJsonFileRead, ndJsonFileWrite, ndjsonStreamForEach, pipelineFromNDJsonFile, pipelineToNDJsonFile, NDJsonStats, streamToNDJsonFile, transformJsonParse, bufferReviver, transformToNDJson, transformFilter, transformFilterSync, transformMap, transformMapSync, transformMapSimple, transformNoOp, writablePushToArray, transformSplit, transformToString, transformToArray, transformTap, transformLogProgress, transformLimit, writableVoid, writableFork, transformMultiThreaded, BaseWorkerClass, tableDiff, inspectAny, inspectAnyStringifyFn, getGot, HTTPError, TimeoutError, _chunkBuffer, Ajv, getAjv, AjvSchema, AjvValidationError, readJsonSchemas, readAjvSchemas, hasColors, sanitizeHTML, runScript, };
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALPHABET_LOWERCASE = exports.ALPHABET_NUMBER = exports.stringIdUnsafe = exports.stringIdAsync = exports.stringId = exports.LRUMemoCache = exports.requireFileToExist = exports.requireEnvKeys = exports.gunzipToString = exports.unzipToString = exports.gzipString = exports.zipString = exports.gunzipBuffer = exports.unzipBuffer = exports.gzipBuffer = exports.zipBuffer = exports.processSharedUtil = exports.urlSchema = exports.slugSchema = exports.ipAddressSchema = exports.utcOffsetSchema = exports.userAgentSchema = exports.semVerSchema = exports.SEM_VER_PATTERN = exports.emailSchema = exports.verSchema = exports.unixTimestampSchema = exports.idSchema = exports.savedDBEntitySchema = exports.baseDBEntitySchema = exports.anyObjectSchema = exports.anySchema = exports.oneOfSchema = exports.objectSchema = exports.binarySchema = exports.arraySchema = exports.dateStringSchema = exports.percentageSchema = exports.integerSchema = exports.numberSchema = exports.stringSchema = exports.booleanDefaultToFalseSchema = exports.booleanSchema = exports.Joi = exports.convert = exports.undefinedIfInvalid = exports.isValid = exports.getValidationResult = exports.validate = exports.JoiValidationError = void 0;
4
4
  exports.transformMapSimple = exports.transformMapSync = exports.transformMap = exports.transformFilterSync = exports.transformFilter = exports.transformToNDJson = exports.bufferReviver = exports.transformJsonParse = exports.streamToNDJsonFile = exports.NDJsonStats = exports.pipelineToNDJsonFile = exports.pipelineFromNDJsonFile = exports.ndjsonStreamForEach = exports.ndJsonFileWrite = exports.ndJsonFileRead = exports.ndjsonMap = exports.transformBuffer = exports._pipeline = exports.readableMapToArray = exports.readableMap = exports.readableForEachSync = exports.readableForEach = exports.readableToArray = exports.readableFromArray = exports.readableFrom = exports.readableCreate = exports.slackDefaultMessagePrefixHook = exports.SlackService = exports.memoryUsageFull = exports.memoryUsage = exports.secretOptional = exports.secret = exports.removeSecretsFromEnv = exports.loadSecretsFromJsonFile = exports.loadSecretsFromEnv = exports.setSecretMap = exports.getSecretMap = exports.Debug = exports.base64ToBuffer = exports.bufferToBase64 = exports.base64ToString = exports.stringToBase64 = exports.md5AsBuffer = exports.hashAsBuffer = exports.hash = exports.md5 = exports.ALPHABET_ALPHANUMERIC = exports.ALPHABET_ALPHANUMERIC_UPPERCASE = exports.ALPHABET_ALPHANUMERIC_LOWERCASE = exports.ALPHABET_UPPERCASE = void 0;
5
- exports.sanitizeHTML = exports.hasColors = exports.readAjvSchemas = exports.readJsonSchemas = exports.AjvValidationError = exports.AjvSchema = exports.getAjv = exports.Ajv = exports._chunkBuffer = exports.TimeoutError = exports.HTTPError = exports.getGot = exports.inspectAnyStringifyFn = exports.inspectAny = exports.tableDiff = exports.BaseWorkerClass = exports.transformMultiThreaded = exports.writableFork = exports.writableVoid = exports.transformLimit = exports.transformLogProgress = exports.transformTap = exports.transformToArray = exports.transformToString = exports.transformSplit = exports.writablePushToArray = exports.writableForEach = exports.transformNoOp = void 0;
5
+ exports.runScript = exports.sanitizeHTML = exports.hasColors = exports.readAjvSchemas = exports.readJsonSchemas = exports.AjvValidationError = exports.AjvSchema = exports.getAjv = exports.Ajv = exports._chunkBuffer = exports.TimeoutError = exports.HTTPError = exports.getGot = exports.inspectAnyStringifyFn = exports.inspectAny = exports.tableDiff = exports.BaseWorkerClass = exports.transformMultiThreaded = exports.writableFork = exports.writableVoid = exports.transformLimit = exports.transformLogProgress = exports.transformTap = exports.transformToArray = exports.transformToString = exports.transformSplit = exports.writablePushToArray = exports.transformNoOp = void 0;
6
+ const tslib_1 = require("tslib");
6
7
  const ajv_1 = require("ajv");
7
8
  exports.Ajv = ajv_1.default;
8
9
  const got_1 = require("got");
@@ -118,8 +119,7 @@ const baseWorkerClass_1 = require("./stream/transform/worker/baseWorkerClass");
118
119
  Object.defineProperty(exports, "BaseWorkerClass", { enumerable: true, get: function () { return baseWorkerClass_1.BaseWorkerClass; } });
119
120
  const transformMultiThreaded_1 = require("./stream/transform/worker/transformMultiThreaded");
120
121
  Object.defineProperty(exports, "transformMultiThreaded", { enumerable: true, get: function () { return transformMultiThreaded_1.transformMultiThreaded; } });
121
- const writableForEach_1 = require("./stream/writable/writableForEach");
122
- Object.defineProperty(exports, "writableForEach", { enumerable: true, get: function () { return writableForEach_1.writableForEach; } });
122
+ (0, tslib_1.__exportStar)(require("./stream/writable/writableForEach"), exports);
123
123
  const writableFork_1 = require("./stream/writable/writableFork");
124
124
  Object.defineProperty(exports, "writableFork", { enumerable: true, get: function () { return writableFork_1.writableFork; } });
125
125
  const writablePushToArray_1 = require("./stream/writable/writablePushToArray");
@@ -191,3 +191,5 @@ Object.defineProperty(exports, "undefinedIfInvalid", { enumerable: true, get: fu
191
191
  Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return joi_validation_util_1.validate; } });
192
192
  const sanitize_util_1 = require("./validation/sanitize.util");
193
193
  Object.defineProperty(exports, "sanitizeHTML", { enumerable: true, get: function () { return sanitize_util_1.sanitizeHTML; } });
194
+ const script_1 = require("./script");
195
+ Object.defineProperty(exports, "runScript", { enumerable: true, get: function () { return script_1.runScript; } });
@@ -1,3 +1,4 @@
1
+ import type { CommonLogger } from '@naturalcycles/js-lib';
1
2
  export interface RunScriptOptions {
2
3
  /**
3
4
  * @default false
@@ -5,6 +6,10 @@ export interface RunScriptOptions {
5
6
  * Currently it exists because of `jest --maxWorkers=1` behavior. To be investigated more..
6
7
  */
7
8
  noExit?: boolean;
9
+ /**
10
+ * Default to `console`
11
+ */
12
+ logger?: CommonLogger;
8
13
  }
9
14
  /**
10
15
  * Use it in your top-level scripts like this:
@@ -18,23 +18,24 @@ exports.runScript = void 0;
18
18
  * This function is kept light, dependency-free, exported separately.
19
19
  */
20
20
  function runScript(fn, opt = {}) {
21
+ const { logger = console, noExit } = opt;
21
22
  process.on('uncaughtException', err => {
22
- console.error('uncaughtException', err);
23
+ logger.error('uncaughtException:', err);
23
24
  });
24
25
  process.on('unhandledRejection', err => {
25
- console.error('unhandledRejection', err);
26
+ logger.error('unhandledRejection:', err);
26
27
  });
27
28
  void (async () => {
28
29
  try {
29
30
  await fn();
30
- if (!opt.noExit) {
31
+ if (!noExit) {
31
32
  setImmediate(() => process.exit(0));
32
33
  }
33
34
  }
34
35
  catch (err) {
35
- console.error('runScript failed:', err);
36
+ logger.error('runScript error:', err);
36
37
  process.exitCode = 1;
37
- if (!opt.noExit) {
38
+ if (!noExit) {
38
39
  setImmediate(() => process.exit(1));
39
40
  }
40
41
  }
@@ -1,4 +1,4 @@
1
- import { StringMap } from '@naturalcycles/js-lib';
1
+ import { AnyObject, CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib';
2
2
  import { SlackAttachmentField, SlackMessage, SlackServiceCfg } from './slack.service.model';
3
3
  /**
4
4
  * Has 2 main methods:
@@ -20,6 +20,15 @@ export declare class SlackService<CTX = any> {
20
20
  */
21
21
  log(...items: any[]): Promise<void>;
22
22
  send(input: SlackMessage<CTX> | string, ctx?: CTX): Promise<void>;
23
- kvToFields(kv: StringMap<any>): SlackAttachmentField[];
23
+ kvToFields(kv: AnyObject): SlackAttachmentField[];
24
+ /**
25
+ * Returns a CommonLogger implementation based on this SlackService instance.
26
+ */
27
+ getCommonLogger(opt: {
28
+ minLogLevel: CommonLogLevel;
29
+ logChannel?: string;
30
+ warnChannel?: string;
31
+ errorChannel?: string;
32
+ }): CommonLogger;
24
33
  }
25
34
  export declare function slackDefaultMessagePrefixHook(msg: SlackMessage): string[];
@@ -110,6 +110,21 @@ class SlackService {
110
110
  short: String(v).length < 80,
111
111
  }));
112
112
  }
113
+ /**
114
+ * Returns a CommonLogger implementation based on this SlackService instance.
115
+ */
116
+ getCommonLogger(opt) {
117
+ const { minLogLevel = 'log', logChannel, warnChannel, errorChannel } = opt;
118
+ const defaultChannel = this.cfg.defaults?.channel || DEFAULTS.channel;
119
+ const q = new js_lib_1.PQueue({
120
+ concurrency: 1,
121
+ });
122
+ return (0, js_lib_1.commonLoggerMinLevel)({
123
+ log: (...args) => q.push(() => this.send({ items: args, channel: logChannel || defaultChannel })),
124
+ warn: (...args) => q.push(() => this.send({ items: args, channel: warnChannel || defaultChannel })),
125
+ error: (...args) => q.push(() => this.send({ items: args, channel: errorChannel || defaultChannel })),
126
+ }, minLogLevel);
127
+ }
113
128
  }
114
129
  exports.SlackService = SlackService;
115
130
  function slackDefaultMessagePrefixHook(msg) {
@@ -1,4 +1,4 @@
1
- import { CommonLogger, StringMap } from '@naturalcycles/js-lib';
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import { InspectAnyOptions } from '..';
3
3
  /**
4
4
  * Properties that exists both in SlackApiBody (as per Slack API) and SlackMessage (our abstraction).
@@ -39,7 +39,7 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
39
39
  /**
40
40
  * Keys-values will be rendered as MessageAttachment with Fields
41
41
  */
42
- kv?: StringMap<any>;
42
+ kv?: AnyObject;
43
43
  /**
44
44
  * If specified - adds @name1, @name2 in the end of the message
45
45
  */
@@ -8,7 +8,7 @@ const pipelineFromNDJsonFile_1 = require("./pipelineFromNDJsonFile");
8
8
  */
9
9
  async function ndJsonFileRead(opt) {
10
10
  const res = [];
11
- await (0, pipelineFromNDJsonFile_1.pipelineFromNDJsonFile)([(0, __1.writableForEach)(r => void res.push(r))], opt);
11
+ await (0, pipelineFromNDJsonFile_1.pipelineFromNDJsonFile)([(0, __1.writablePushToArray)(res)], opt);
12
12
  return res;
13
13
  }
14
14
  exports.ndJsonFileRead = ndJsonFileRead;
@@ -0,0 +1,17 @@
1
+ import { AsyncMapper } from '@naturalcycles/js-lib';
2
+ import { TransformTyped } from '../../stream.model';
3
+ import { TransformMapOptions } from '../transformMap';
4
+ export declare function notNullishPredicate(item: any): boolean;
5
+ /**
6
+ * Like pMap, but for streams.
7
+ * Inspired by `through2`.
8
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
9
+ * Using this allows native stream .pipe() to work and use backpressure.
10
+ *
11
+ * Only works in objectMode (due to through2Concurrent).
12
+ *
13
+ * Concurrency defaults to 16.
14
+ *
15
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
16
+ */
17
+ export declare function transformMapLegacy<IN = any, OUT = IN>(mapper: AsyncMapper<IN, OUT>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformMapLegacy = exports.notNullishPredicate = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
5
+ const through2Concurrent = require("through2-concurrent");
6
+ const colors_1 = require("../../../colors");
7
+ function notNullishPredicate(item) {
8
+ return item !== undefined && item !== null;
9
+ }
10
+ exports.notNullishPredicate = notNullishPredicate;
11
+ /**
12
+ * Like pMap, but for streams.
13
+ * Inspired by `through2`.
14
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
15
+ * Using this allows native stream .pipe() to work and use backpressure.
16
+ *
17
+ * Only works in objectMode (due to through2Concurrent).
18
+ *
19
+ * Concurrency defaults to 16.
20
+ *
21
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
+ */
23
+ function transformMapLegacy(mapper, opt = {}) {
24
+ const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', logger = console, } = opt;
25
+ let index = -1;
26
+ let isRejected = false;
27
+ let errors = 0;
28
+ const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
29
+ return through2Concurrent.obj({
30
+ maxConcurrency: concurrency,
31
+ // autoDestroy: true,
32
+ async final(cb) {
33
+ // console.log('transformMap final')
34
+ logErrorStats(logger, true);
35
+ await beforeFinal?.(); // call beforeFinal if defined
36
+ if (collectedErrors.length) {
37
+ // emit Aggregated error
38
+ cb(new js_lib_1.AggregatedError(collectedErrors));
39
+ }
40
+ else {
41
+ // emit no error
42
+ cb();
43
+ }
44
+ },
45
+ }, async function transformMapFn(chunk, _encoding, cb) {
46
+ index++;
47
+ // console.log({chunk, _encoding})
48
+ // Stop processing if THROW_IMMEDIATELY mode is used
49
+ if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
50
+ return cb();
51
+ try {
52
+ const currentIndex = index; // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
53
+ const res = await mapper(chunk, currentIndex);
54
+ const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
55
+ if (passedResults.length === 0) {
56
+ cb(); // 0 results
57
+ }
58
+ else {
59
+ passedResults.forEach(r => {
60
+ this.push(r);
61
+ // cb(null, r)
62
+ });
63
+ cb(); // done processing
64
+ }
65
+ }
66
+ catch (err) {
67
+ logger.error(err);
68
+ errors++;
69
+ logErrorStats(logger);
70
+ if (onError) {
71
+ try {
72
+ onError(err, chunk);
73
+ }
74
+ catch { }
75
+ }
76
+ if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
77
+ isRejected = true;
78
+ // Emit error immediately
79
+ return cb(err);
80
+ }
81
+ if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
82
+ collectedErrors.push(err);
83
+ }
84
+ // Tell input stream that we're done processing, but emit nothing to output - not error nor result
85
+ cb();
86
+ }
87
+ });
88
+ function logErrorStats(logger, final = false) {
89
+ if (!errors)
90
+ return;
91
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
92
+ }
93
+ }
94
+ exports.transformMapLegacy = transformMapLegacy;
@@ -1,4 +1,4 @@
1
- import { AsyncMapper, AsyncPredicate, ErrorMode } from '@naturalcycles/js-lib';
1
+ import { AsyncMapper, AsyncPredicate, CommonLogger, ErrorMode } from '@naturalcycles/js-lib';
2
2
  import { TransformTyped } from '../stream.model';
3
3
  export interface TransformMapOptions<IN = any, OUT = IN> {
4
4
  /**
@@ -41,6 +41,7 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
41
41
  * If defined - called BEFORE `final()` callback is called.
42
42
  */
43
43
  beforeFinal?: () => any;
44
+ logger?: CommonLogger;
44
45
  }
45
46
  export declare function notNullishPredicate(item: any): boolean;
46
47
  /**
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformMap = exports.notNullishPredicate = void 0;
4
+ const stream_1 = require("stream");
4
5
  const js_lib_1 = require("@naturalcycles/js-lib");
5
- const through2Concurrent = require("through2-concurrent");
6
6
  const colors_1 = require("../../colors");
7
7
  function notNullishPredicate(item) {
8
8
  return item !== undefined && item !== null;
@@ -21,17 +21,23 @@ exports.notNullishPredicate = notNullishPredicate;
21
21
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
22
  */
23
23
  function transformMap(mapper, opt = {}) {
24
- const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', } = opt;
24
+ const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', logger = console, } = opt;
25
25
  let index = -1;
26
26
  let isRejected = false;
27
27
  let errors = 0;
28
28
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
29
- return through2Concurrent.obj({
30
- maxConcurrency: concurrency,
31
- // autoDestroy: true,
29
+ const q = new js_lib_1.PQueue({
30
+ concurrency,
31
+ resolveOn: 'start',
32
+ // debug: true,
33
+ });
34
+ return new stream_1.Transform({
35
+ objectMode: true,
32
36
  async final(cb) {
33
- // console.log('transformMap final')
34
- logErrorStats(true);
37
+ // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
38
+ // wait for the current inFlight jobs to complete and push their results
39
+ await q.onIdle();
40
+ logErrorStats(logger, true);
35
41
  await beforeFinal?.(); // call beforeFinal if defined
36
42
  if (collectedErrors.length) {
37
43
  // emit Aggregated error
@@ -42,53 +48,51 @@ function transformMap(mapper, opt = {}) {
42
48
  cb();
43
49
  }
44
50
  },
45
- }, async function transformMapFn(chunk, _encoding, cb) {
46
- index++;
47
- // console.log({chunk, _encoding})
48
- // Stop processing if THROW_IMMEDIATELY mode is used
49
- if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
50
- return cb();
51
- try {
52
- const currentIndex = index; // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
53
- const res = await mapper(chunk, currentIndex);
54
- const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
55
- if (passedResults.length === 0) {
56
- cb(); // 0 results
57
- }
58
- else {
59
- passedResults.forEach(r => {
60
- this.push(r);
61
- // cb(null, r)
62
- });
63
- cb(); // done processing
64
- }
65
- }
66
- catch (err) {
67
- console.error(err);
68
- errors++;
69
- logErrorStats();
70
- if (onError) {
51
+ async transform(chunk, _encoding, cb) {
52
+ index++;
53
+ // console.log('transform', {index})
54
+ // Stop processing if THROW_IMMEDIATELY mode is used
55
+ if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
56
+ return cb();
57
+ // It resolves when it is successfully STARTED execution.
58
+ // If it's queued instead - it'll wait and resolve only upon START.
59
+ await q.push(async () => {
71
60
  try {
72
- onError(err, chunk);
61
+ const currentIndex = index; // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
62
+ const res = await mapper(chunk, currentIndex);
63
+ const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
64
+ passedResults.forEach(r => this.push(r));
73
65
  }
74
- catch { }
75
- }
76
- if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
77
- isRejected = true;
78
- // Emit error immediately
79
- return cb(err);
80
- }
81
- if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
82
- collectedErrors.push(err);
83
- }
84
- // Tell input stream that we're done processing, but emit nothing to output - not error nor result
66
+ catch (err) {
67
+ logger.error(err);
68
+ errors++;
69
+ logErrorStats(logger);
70
+ if (onError) {
71
+ try {
72
+ onError(err, chunk);
73
+ }
74
+ catch { }
75
+ }
76
+ if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
77
+ isRejected = true;
78
+ // Emit error immediately
79
+ // return cb(err as Error)
80
+ return this.emit('error', err);
81
+ }
82
+ if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
83
+ collectedErrors.push(err);
84
+ }
85
+ }
86
+ });
87
+ // Resolved, which means it STARTED processing
88
+ // This means we can take more load
85
89
  cb();
86
- }
90
+ },
87
91
  });
88
- function logErrorStats(final = false) {
92
+ function logErrorStats(logger, final = false) {
89
93
  if (!errors)
90
94
  return;
91
- console.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
95
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
92
96
  }
93
97
  }
94
98
  exports.transformMap = transformMap;
@@ -1,7 +1,11 @@
1
- import { AsyncMapper } from '@naturalcycles/js-lib';
1
+ import { AsyncMapper, Mapper } from '@naturalcycles/js-lib';
2
2
  import { TransformMapOptions } from '../..';
3
3
  import { WritableTyped } from '../stream.model';
4
4
  /**
5
5
  * Just an alias to transformMap that declares OUT as void.
6
6
  */
7
7
  export declare function writableForEach<IN = any>(mapper: AsyncMapper<IN, void>, opt?: TransformMapOptions<IN, void>): WritableTyped<IN>;
8
+ /**
9
+ * Just an alias to transformMap that declares OUT as void.
10
+ */
11
+ export declare function writableForEachSync<IN = any>(mapper: Mapper<IN, void>, opt?: TransformMapOptions<IN, void>): WritableTyped<IN>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writableForEach = void 0;
3
+ exports.writableForEachSync = exports.writableForEach = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const __1 = require("../..");
6
6
  /**
@@ -10,3 +10,10 @@ function writableForEach(mapper, opt = {}) {
10
10
  return (0, __1.transformMap)(mapper, { ...opt, predicate: js_lib_1._passNothingPredicate });
11
11
  }
12
12
  exports.writableForEach = writableForEach;
13
+ /**
14
+ * Just an alias to transformMap that declares OUT as void.
15
+ */
16
+ function writableForEachSync(mapper, opt = {}) {
17
+ return (0, __1.transformMapSync)(mapper, { ...opt, predicate: js_lib_1._passNothingPredicate });
18
+ }
19
+ exports.writableForEachSync = writableForEachSync;
@@ -1,4 +1,4 @@
1
- import { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib';
1
+ import { JsonSchema, JsonSchemaBuilder, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import Ajv from 'ajv';
3
3
  import { AjvValidationError } from './ajvValidationError';
4
4
  export interface AjvValidationOptions {
@@ -37,6 +37,10 @@ export interface AjvSchemaCfg {
37
37
  * @default true
38
38
  */
39
39
  logErrors: boolean;
40
+ /**
41
+ * Default to `console`
42
+ */
43
+ logger: CommonLogger;
40
44
  /**
41
45
  * Option of Ajv.
42
46
  * If set to true - will mutate your input objects!
@@ -17,6 +17,7 @@ class AjvSchema {
17
17
  this.schema = schema;
18
18
  this.cfg = {
19
19
  logErrors: true,
20
+ logger: console,
20
21
  separator: '\n',
21
22
  ...cfg,
22
23
  ajv: cfg.ajv ||
@@ -89,7 +90,7 @@ class AjvSchema {
89
90
  const strValue = (0, index_1.inspectAny)(obj, { maxLen: 1000 });
90
91
  message = [message, 'Input: ' + strValue].join(separator);
91
92
  if (logErrors) {
92
- console.log(errors);
93
+ this.cfg.logger.error(errors);
93
94
  }
94
95
  return new ajvValidationError_1.AjvValidationError(message, (0, js_lib_1._filterNullishValues)({
95
96
  errors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "12.53.0",
3
+ "version": "12.56.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "docs-serve": "vuepress dev docs",
@@ -1,4 +1,4 @@
1
- import { StringMap, _truncate, AnyObject } from '@naturalcycles/js-lib'
1
+ import { _truncate, AnyObject } from '@naturalcycles/js-lib'
2
2
 
3
3
  export interface TableDiffOptions {
4
4
  /**
@@ -37,7 +37,7 @@ export interface TableDiffOptions {
37
37
  */
38
38
  export function tableDiff(a: AnyObject, b: AnyObject, opt: TableDiffOptions = {}): void {
39
39
  const { maxFieldLen, aTitle = 'a', bTitle = 'b' } = opt
40
- const diff: StringMap<any> = {}
40
+ const diff: AnyObject = {}
41
41
 
42
42
  if (a && b && a !== b) {
43
43
  new Set([...Object.keys(a), ...Object.keys(b)]).forEach(k => {
package/src/index.ts CHANGED
@@ -104,7 +104,7 @@ import {
104
104
  TransformMultiThreadedOptions,
105
105
  } from './stream/transform/worker/transformMultiThreaded'
106
106
  import { WorkerInput, WorkerOutput } from './stream/transform/worker/transformMultiThreaded.model'
107
- import { writableForEach } from './stream/writable/writableForEach'
107
+ export * from './stream/writable/writableForEach'
108
108
  import { writableFork } from './stream/writable/writableFork'
109
109
  import { writablePushToArray } from './stream/writable/writablePushToArray'
110
110
  import { writableVoid } from './stream/writable/writableVoid'
@@ -173,8 +173,10 @@ import {
173
173
  validate,
174
174
  } from './validation/joi/joi.validation.util'
175
175
  import { sanitizeHTML, SanitizeHTMLOptions } from './validation/sanitize.util'
176
+ import { runScript, RunScriptOptions } from './script'
176
177
 
177
178
  export type {
179
+ RunScriptOptions,
178
180
  JoiValidationErrorData,
179
181
  JoiValidationResult,
180
182
  ValidationErrorItem,
@@ -325,7 +327,6 @@ export {
325
327
  transformMapSync,
326
328
  transformMapSimple,
327
329
  transformNoOp,
328
- writableForEach,
329
330
  writablePushToArray,
330
331
  transformSplit,
331
332
  transformToString,
@@ -352,4 +353,5 @@ export {
352
353
  readAjvSchemas,
353
354
  hasColors,
354
355
  sanitizeHTML,
356
+ runScript,
355
357
  }
@@ -1,3 +1,5 @@
1
+ import type { CommonLogger } from '@naturalcycles/js-lib'
2
+
1
3
  export interface RunScriptOptions {
2
4
  /**
3
5
  * @default false
@@ -5,6 +7,11 @@ export interface RunScriptOptions {
5
7
  * Currently it exists because of `jest --maxWorkers=1` behavior. To be investigated more..
6
8
  */
7
9
  noExit?: boolean
10
+
11
+ /**
12
+ * Default to `console`
13
+ */
14
+ logger?: CommonLogger
8
15
  }
9
16
 
10
17
  /**
@@ -24,24 +31,26 @@ export interface RunScriptOptions {
24
31
  * This function is kept light, dependency-free, exported separately.
25
32
  */
26
33
  export function runScript(fn: (...args: any[]) => any, opt: RunScriptOptions = {}): void {
34
+ const { logger = console, noExit } = opt
35
+
27
36
  process.on('uncaughtException', err => {
28
- console.error('uncaughtException', err)
37
+ logger.error('uncaughtException:', err)
29
38
  })
30
39
  process.on('unhandledRejection', err => {
31
- console.error('unhandledRejection', err)
40
+ logger.error('unhandledRejection:', err)
32
41
  })
33
42
 
34
43
  void (async () => {
35
44
  try {
36
45
  await fn()
37
46
 
38
- if (!opt.noExit) {
47
+ if (!noExit) {
39
48
  setImmediate(() => process.exit(0))
40
49
  }
41
50
  } catch (err) {
42
- console.error('runScript failed:', err)
51
+ logger.error('runScript error:', err)
43
52
  process.exitCode = 1
44
- if (!opt.noExit) {
53
+ if (!noExit) {
45
54
  setImmediate(() => process.exit(1))
46
55
  }
47
56
  }
@@ -1,4 +1,4 @@
1
- import { CommonLogger, StringMap } from '@naturalcycles/js-lib'
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib'
2
2
  import { InspectAnyOptions } from '..'
3
3
 
4
4
  /**
@@ -47,7 +47,7 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
47
47
  /**
48
48
  * Keys-values will be rendered as MessageAttachment with Fields
49
49
  */
50
- kv?: StringMap<any>
50
+ kv?: AnyObject
51
51
 
52
52
  /**
53
53
  * If specified - adds @name1, @name2 in the end of the message
@@ -1,4 +1,11 @@
1
- import { _omit, StringMap } from '@naturalcycles/js-lib'
1
+ import {
2
+ _omit,
3
+ AnyObject,
4
+ CommonLogger,
5
+ commonLoggerMinLevel,
6
+ CommonLogLevel,
7
+ PQueue,
8
+ } from '@naturalcycles/js-lib'
2
9
  import { dayjs } from '@naturalcycles/time-lib'
3
10
  import got from 'got'
4
11
  import { inspectAny, InspectAnyOptions } from '..'
@@ -126,13 +133,42 @@ export class SlackService<CTX = any> {
126
133
  })
127
134
  }
128
135
 
129
- kvToFields(kv: StringMap<any>): SlackAttachmentField[] {
136
+ kvToFields(kv: AnyObject): SlackAttachmentField[] {
130
137
  return Object.entries(kv).map(([k, v]) => ({
131
138
  title: k,
132
139
  value: String(v),
133
140
  short: String(v).length < 80,
134
141
  }))
135
142
  }
143
+
144
+ /**
145
+ * Returns a CommonLogger implementation based on this SlackService instance.
146
+ */
147
+ getCommonLogger(opt: {
148
+ minLogLevel: CommonLogLevel
149
+ logChannel?: string
150
+ warnChannel?: string
151
+ errorChannel?: string
152
+ }): CommonLogger {
153
+ const { minLogLevel = 'log', logChannel, warnChannel, errorChannel } = opt
154
+ const defaultChannel = this.cfg.defaults?.channel || DEFAULTS.channel!
155
+
156
+ const q = new PQueue({
157
+ concurrency: 1,
158
+ })
159
+
160
+ return commonLoggerMinLevel(
161
+ {
162
+ log: (...args) =>
163
+ q.push(() => this.send({ items: args, channel: logChannel || defaultChannel })),
164
+ warn: (...args) =>
165
+ q.push(() => this.send({ items: args, channel: warnChannel || defaultChannel })),
166
+ error: (...args) =>
167
+ q.push(() => this.send({ items: args, channel: errorChannel || defaultChannel })),
168
+ },
169
+ minLogLevel,
170
+ )
171
+ }
136
172
  }
137
173
 
138
174
  export function slackDefaultMessagePrefixHook(msg: SlackMessage): string[] {
@@ -1,4 +1,4 @@
1
- import { writableForEach } from '../..'
1
+ import { writablePushToArray } from '../..'
2
2
  import { pipelineFromNDJsonFile, PipelineFromNDJsonFileOptions } from './pipelineFromNDJsonFile'
3
3
 
4
4
  /**
@@ -9,7 +9,7 @@ export async function ndJsonFileRead<OUT = any>(
9
9
  ): Promise<OUT[]> {
10
10
  const res: OUT[] = []
11
11
 
12
- await pipelineFromNDJsonFile([writableForEach(r => void res.push(r))], opt)
12
+ await pipelineFromNDJsonFile([writablePushToArray(res)], opt)
13
13
 
14
14
  return res
15
15
  }
@@ -0,0 +1,133 @@
1
+ import { Transform } from 'stream'
2
+ import {
3
+ AggregatedError,
4
+ AsyncMapper,
5
+ CommonLogger,
6
+ ErrorMode,
7
+ pFilter,
8
+ } from '@naturalcycles/js-lib'
9
+ import through2Concurrent = require('through2-concurrent')
10
+ import { yellow } from '../../../colors'
11
+ import { TransformTyped } from '../../stream.model'
12
+ import { TransformMapOptions } from '../transformMap'
13
+
14
+ export function notNullishPredicate(item: any): boolean {
15
+ return item !== undefined && item !== null
16
+ }
17
+
18
+ /**
19
+ * Like pMap, but for streams.
20
+ * Inspired by `through2`.
21
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
22
+ * Using this allows native stream .pipe() to work and use backpressure.
23
+ *
24
+ * Only works in objectMode (due to through2Concurrent).
25
+ *
26
+ * Concurrency defaults to 16.
27
+ *
28
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
29
+ */
30
+ export function transformMapLegacy<IN = any, OUT = IN>(
31
+ mapper: AsyncMapper<IN, OUT>,
32
+ opt: TransformMapOptions<IN, OUT> = {},
33
+ ): TransformTyped<IN, OUT> {
34
+ const {
35
+ concurrency = 16,
36
+ predicate = notNullishPredicate,
37
+ errorMode = ErrorMode.THROW_IMMEDIATELY,
38
+ flattenArrayOutput,
39
+ onError,
40
+ beforeFinal,
41
+ metric = 'stream',
42
+ logger = console,
43
+ } = opt
44
+
45
+ let index = -1
46
+ let isRejected = false
47
+ let errors = 0
48
+ const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
49
+
50
+ return through2Concurrent.obj(
51
+ {
52
+ maxConcurrency: concurrency,
53
+ // autoDestroy: true,
54
+ async final(cb) {
55
+ // console.log('transformMap final')
56
+
57
+ logErrorStats(logger, true)
58
+
59
+ await beforeFinal?.() // call beforeFinal if defined
60
+
61
+ if (collectedErrors.length) {
62
+ // emit Aggregated error
63
+ cb(new AggregatedError(collectedErrors))
64
+ } else {
65
+ // emit no error
66
+ cb()
67
+ }
68
+ },
69
+ },
70
+ async function transformMapFn(
71
+ this: Transform,
72
+ chunk: IN,
73
+ _encoding: any,
74
+ cb: (...args: any[]) => any,
75
+ ) {
76
+ index++
77
+ // console.log({chunk, _encoding})
78
+
79
+ // Stop processing if THROW_IMMEDIATELY mode is used
80
+ if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
81
+
82
+ try {
83
+ const currentIndex = index // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
84
+ const res = await mapper(chunk, currentIndex)
85
+ const passedResults = await pFilter(
86
+ flattenArrayOutput && Array.isArray(res) ? res : [res],
87
+ async r => await predicate(r, currentIndex),
88
+ )
89
+
90
+ if (passedResults.length === 0) {
91
+ cb() // 0 results
92
+ } else {
93
+ passedResults.forEach(r => {
94
+ this.push(r)
95
+ // cb(null, r)
96
+ })
97
+ cb() // done processing
98
+ }
99
+ } catch (err) {
100
+ logger.error(err)
101
+
102
+ errors++
103
+
104
+ logErrorStats(logger)
105
+
106
+ if (onError) {
107
+ try {
108
+ onError(err, chunk)
109
+ } catch {}
110
+ }
111
+
112
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
113
+ isRejected = true
114
+ // Emit error immediately
115
+ return cb(err)
116
+ }
117
+
118
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
119
+ collectedErrors.push(err as Error)
120
+ }
121
+
122
+ // Tell input stream that we're done processing, but emit nothing to output - not error nor result
123
+ cb()
124
+ }
125
+ },
126
+ )
127
+
128
+ function logErrorStats(logger: CommonLogger, final = false): void {
129
+ if (!errors) return
130
+
131
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
132
+ }
133
+ }
@@ -3,10 +3,11 @@ import {
3
3
  AggregatedError,
4
4
  AsyncMapper,
5
5
  AsyncPredicate,
6
+ CommonLogger,
6
7
  ErrorMode,
7
8
  pFilter,
9
+ PQueue,
8
10
  } from '@naturalcycles/js-lib'
9
- import through2Concurrent = require('through2-concurrent')
10
11
  import { yellow } from '../../colors'
11
12
  import { TransformTyped } from '../stream.model'
12
13
 
@@ -57,6 +58,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
57
58
  * If defined - called BEFORE `final()` callback is called.
58
59
  */
59
60
  beforeFinal?: () => any
61
+
62
+ logger?: CommonLogger
60
63
  }
61
64
 
62
65
  export function notNullishPredicate(item: any): boolean {
@@ -87,6 +90,7 @@ export function transformMap<IN = any, OUT = IN>(
87
90
  onError,
88
91
  beforeFinal,
89
92
  metric = 'stream',
93
+ logger = console,
90
94
  } = opt
91
95
 
92
96
  let index = -1
@@ -94,87 +98,88 @@ export function transformMap<IN = any, OUT = IN>(
94
98
  let errors = 0
95
99
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
96
100
 
97
- return through2Concurrent.obj(
98
- {
99
- maxConcurrency: concurrency,
100
- // autoDestroy: true,
101
- async final(cb) {
102
- // console.log('transformMap final')
101
+ const q = new PQueue({
102
+ concurrency,
103
+ resolveOn: 'start',
104
+ // debug: true,
105
+ })
103
106
 
104
- logErrorStats(true)
107
+ return new Transform({
108
+ objectMode: true,
105
109
 
106
- await beforeFinal?.() // call beforeFinal if defined
110
+ async final(cb) {
111
+ // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
107
112
 
108
- if (collectedErrors.length) {
109
- // emit Aggregated error
110
- cb(new AggregatedError(collectedErrors))
111
- } else {
112
- // emit no error
113
- cb()
114
- }
115
- },
116
- },
117
- async function transformMapFn(
118
- this: Transform,
119
- chunk: IN,
120
- _encoding: any,
121
- cb: (...args: any[]) => any,
122
- ) {
123
- index++
124
- // console.log({chunk, _encoding})
113
+ // wait for the current inFlight jobs to complete and push their results
114
+ await q.onIdle()
125
115
 
126
- // Stop processing if THROW_IMMEDIATELY mode is used
127
- if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
128
-
129
- try {
130
- const currentIndex = index // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
131
- const res = await mapper(chunk, currentIndex)
132
- const passedResults = await pFilter(
133
- flattenArrayOutput && Array.isArray(res) ? res : [res],
134
- async r => await predicate(r, currentIndex),
135
- )
136
-
137
- if (passedResults.length === 0) {
138
- cb() // 0 results
139
- } else {
140
- passedResults.forEach(r => {
141
- this.push(r)
142
- // cb(null, r)
143
- })
144
- cb() // done processing
145
- }
146
- } catch (err) {
147
- console.error(err)
116
+ logErrorStats(logger, true)
148
117
 
149
- errors++
118
+ await beforeFinal?.() // call beforeFinal if defined
150
119
 
151
- logErrorStats()
120
+ if (collectedErrors.length) {
121
+ // emit Aggregated error
122
+ cb(new AggregatedError(collectedErrors))
123
+ } else {
124
+ // emit no error
125
+ cb()
126
+ }
127
+ },
152
128
 
153
- if (onError) {
154
- try {
155
- onError(err, chunk)
156
- } catch {}
157
- }
129
+ async transform(this: Transform, chunk: IN, _encoding, cb) {
130
+ index++
131
+ // console.log('transform', {index})
158
132
 
159
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
160
- isRejected = true
161
- // Emit error immediately
162
- return cb(err)
163
- }
133
+ // Stop processing if THROW_IMMEDIATELY mode is used
134
+ if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
164
135
 
165
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
166
- collectedErrors.push(err as Error)
136
+ // It resolves when it is successfully STARTED execution.
137
+ // If it's queued instead - it'll wait and resolve only upon START.
138
+ await q.push(async () => {
139
+ try {
140
+ const currentIndex = index // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
141
+ const res = await mapper(chunk, currentIndex)
142
+ const passedResults = await pFilter(
143
+ flattenArrayOutput && Array.isArray(res) ? res : [res],
144
+ async r => await predicate(r, currentIndex),
145
+ )
146
+
147
+ passedResults.forEach(r => this.push(r))
148
+ } catch (err) {
149
+ logger.error(err)
150
+
151
+ errors++
152
+
153
+ logErrorStats(logger)
154
+
155
+ if (onError) {
156
+ try {
157
+ onError(err, chunk)
158
+ } catch {}
159
+ }
160
+
161
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
162
+ isRejected = true
163
+ // Emit error immediately
164
+ // return cb(err as Error)
165
+ return this.emit('error', err as Error)
166
+ }
167
+
168
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
169
+ collectedErrors.push(err as Error)
170
+ }
167
171
  }
172
+ })
168
173
 
169
- // Tell input stream that we're done processing, but emit nothing to output - not error nor result
170
- cb()
171
- }
174
+ // Resolved, which means it STARTED processing
175
+ // This means we can take more load
176
+ cb()
172
177
  },
173
- )
178
+ })
174
179
 
175
- function logErrorStats(final = false): void {
180
+ function logErrorStats(logger: CommonLogger, final = false): void {
176
181
  if (!errors) return
177
182
 
178
- console.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
183
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
179
184
  }
180
185
  }
@@ -1,5 +1,5 @@
1
- import { AsyncMapper, _passNothingPredicate } from '@naturalcycles/js-lib'
2
- import { transformMap, TransformMapOptions } from '../..'
1
+ import { AsyncMapper, _passNothingPredicate, Mapper } from '@naturalcycles/js-lib'
2
+ import { transformMap, TransformMapOptions, transformMapSync } from '../..'
3
3
  import { WritableTyped } from '../stream.model'
4
4
 
5
5
  /**
@@ -11,3 +11,13 @@ export function writableForEach<IN = any>(
11
11
  ): WritableTyped<IN> {
12
12
  return transformMap<IN, void>(mapper, { ...opt, predicate: _passNothingPredicate })
13
13
  }
14
+
15
+ /**
16
+ * Just an alias to transformMap that declares OUT as void.
17
+ */
18
+ export function writableForEachSync<IN = any>(
19
+ mapper: Mapper<IN, void>,
20
+ opt: TransformMapOptions<IN, void> = {},
21
+ ): WritableTyped<IN> {
22
+ return transformMapSync<IN, void>(mapper, { ...opt, predicate: _passNothingPredicate })
23
+ }
@@ -6,6 +6,7 @@ import {
6
6
  _filterNullishValues,
7
7
  _isObject,
8
8
  _substringBefore,
9
+ CommonLogger,
9
10
  } from '@naturalcycles/js-lib'
10
11
  import Ajv, { ValidateFunction } from 'ajv'
11
12
  import { inspectAny, requireFileToExist } from '../../index'
@@ -56,6 +57,11 @@ export interface AjvSchemaCfg {
56
57
  */
57
58
  logErrors: boolean
58
59
 
60
+ /**
61
+ * Default to `console`
62
+ */
63
+ logger: CommonLogger
64
+
59
65
  /**
60
66
  * Option of Ajv.
61
67
  * If set to true - will mutate your input objects!
@@ -76,6 +82,7 @@ export class AjvSchema<T = unknown> {
76
82
  private constructor(public schema: JsonSchema<T>, cfg: Partial<AjvSchemaCfg> = {}) {
77
83
  this.cfg = {
78
84
  logErrors: true,
85
+ logger: console,
79
86
  separator: '\n',
80
87
  ...cfg,
81
88
  ajv:
@@ -168,7 +175,7 @@ export class AjvSchema<T = unknown> {
168
175
  message = [message, 'Input: ' + strValue].join(separator)
169
176
 
170
177
  if (logErrors) {
171
- console.log(errors)
178
+ this.cfg.logger.error(errors)
172
179
  }
173
180
 
174
181
  return new AjvValidationError(