@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 +4 -3
- package/dist/index.js +5 -3
- package/dist/script/index.d.ts +5 -0
- package/dist/script/index.js +6 -5
- package/dist/slack/slack.service.d.ts +11 -2
- package/dist/slack/slack.service.js +15 -0
- package/dist/slack/slack.service.model.d.ts +2 -2
- package/dist/stream/ndjson/ndJsonFileRead.js +1 -1
- package/dist/stream/transform/legacy/transformMap.d.ts +17 -0
- package/dist/stream/transform/legacy/transformMap.js +94 -0
- package/dist/stream/transform/transformMap.d.ts +2 -1
- package/dist/stream/transform/transformMap.js +52 -48
- package/dist/stream/writable/writableForEach.d.ts +5 -1
- package/dist/stream/writable/writableForEach.js +8 -1
- package/dist/validation/ajv/ajvSchema.d.ts +5 -1
- package/dist/validation/ajv/ajvSchema.js +2 -1
- package/package.json +1 -1
- package/src/diff/tableDiff.ts +2 -2
- package/src/index.ts +4 -2
- package/src/script/index.ts +14 -5
- package/src/slack/slack.service.model.ts +2 -2
- package/src/slack/slack.service.ts +38 -2
- package/src/stream/ndjson/ndJsonFileRead.ts +2 -2
- package/src/stream/transform/legacy/transformMap.ts +133 -0
- package/src/stream/transform/transformMap.ts +73 -68
- package/src/stream/writable/writableForEach.ts +12 -2
- package/src/validation/ajv/ajvSchema.ts +8 -1
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
|
-
|
|
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
|
-
|
|
69
|
-
export {
|
|
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.
|
|
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
|
-
|
|
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; } });
|
package/dist/script/index.d.ts
CHANGED
|
@@ -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:
|
package/dist/script/index.js
CHANGED
|
@@ -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
|
-
|
|
23
|
+
logger.error('uncaughtException:', err);
|
|
23
24
|
});
|
|
24
25
|
process.on('unhandledRejection', err => {
|
|
25
|
-
|
|
26
|
+
logger.error('unhandledRejection:', err);
|
|
26
27
|
});
|
|
27
28
|
void (async () => {
|
|
28
29
|
try {
|
|
29
30
|
await fn();
|
|
30
|
-
if (!
|
|
31
|
+
if (!noExit) {
|
|
31
32
|
setImmediate(() => process.exit(0));
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
catch (err) {
|
|
35
|
-
|
|
36
|
+
logger.error('runScript error:', err);
|
|
36
37
|
process.exitCode = 1;
|
|
37
|
-
if (!
|
|
38
|
+
if (!noExit) {
|
|
38
39
|
setImmediate(() => process.exit(1));
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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 {
|
|
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?:
|
|
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.
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/src/diff/tableDiff.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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
|
}
|
package/src/script/index.ts
CHANGED
|
@@ -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
|
-
|
|
37
|
+
logger.error('uncaughtException:', err)
|
|
29
38
|
})
|
|
30
39
|
process.on('unhandledRejection', err => {
|
|
31
|
-
|
|
40
|
+
logger.error('unhandledRejection:', err)
|
|
32
41
|
})
|
|
33
42
|
|
|
34
43
|
void (async () => {
|
|
35
44
|
try {
|
|
36
45
|
await fn()
|
|
37
46
|
|
|
38
|
-
if (!
|
|
47
|
+
if (!noExit) {
|
|
39
48
|
setImmediate(() => process.exit(0))
|
|
40
49
|
}
|
|
41
50
|
} catch (err) {
|
|
42
|
-
|
|
51
|
+
logger.error('runScript error:', err)
|
|
43
52
|
process.exitCode = 1
|
|
44
|
-
if (!
|
|
53
|
+
if (!noExit) {
|
|
45
54
|
setImmediate(() => process.exit(1))
|
|
46
55
|
}
|
|
47
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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?:
|
|
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 {
|
|
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:
|
|
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 {
|
|
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([
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// console.log('transformMap final')
|
|
101
|
+
const q = new PQueue({
|
|
102
|
+
concurrency,
|
|
103
|
+
resolveOn: 'start',
|
|
104
|
+
// debug: true,
|
|
105
|
+
})
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
return new Transform({
|
|
108
|
+
objectMode: true,
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
async final(cb) {
|
|
111
|
+
// console.log('transformMap final', {index}, q.inFlight, q.queueSize)
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
await beforeFinal?.() // call beforeFinal if defined
|
|
150
119
|
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} catch {}
|
|
157
|
-
}
|
|
129
|
+
async transform(this: Transform, chunk: IN, _encoding, cb) {
|
|
130
|
+
index++
|
|
131
|
+
// console.log('transform', {index})
|
|
158
132
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
this.cfg.logger.error(errors)
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
return new AjvValidationError(
|