@naturalcycles/nodejs-lib 12.48.1 → 12.51.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.
@@ -1 +1,6 @@
1
- export declare function hasColors(): boolean;
1
+ /**
2
+ * Based on: https://github.com/sindresorhus/yoctocolors/pull/5
3
+ *
4
+ * @experimental
5
+ */
6
+ export declare const hasColors: boolean;
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hasColors = void 0;
4
- const supporsColorLib = require('supports-color');
5
- function hasColors() {
6
- if (process.env['NO_COLOR'])
7
- return false; // https://no-color.org/
8
- return !!supporsColorLib.stdout;
9
- }
10
- exports.hasColors = hasColors;
4
+ const tty = require("tty");
5
+ /**
6
+ * Based on: https://github.com/sindresorhus/yoctocolors/pull/5
7
+ *
8
+ * @experimental
9
+ */
10
+ exports.hasColors = !process.env['NO_COLOR'] && tty.WriteStream.prototype.hasColors();
@@ -5,8 +5,6 @@ const fs = require("fs");
5
5
  const __1 = require("..");
6
6
  const crypto_util_1 = require("./crypto.util");
7
7
  let loaded = false;
8
- // it's wrapped to be able to pipe console.* to Stackdriver
9
- const getLog = () => (0, __1.Debug)('nc:nodejs-lib:secret');
10
8
  const secretMap = {};
11
9
  /**
12
10
  * Loads plaintext secrets from process.env, removes them, stores locally.
@@ -25,7 +23,7 @@ function loadSecretsFromEnv() {
25
23
  delete process.env[k];
26
24
  });
27
25
  loaded = true;
28
- getLog()(`${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(', ')}`);
26
+ console.log(`${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(', ')}`);
29
27
  }
30
28
  exports.loadSecretsFromEnv = loadSecretsFromEnv;
31
29
  /**
@@ -58,7 +56,7 @@ function loadSecretsFromJsonFile(filePath, SECRET_ENCRYPTION_KEY) {
58
56
  }
59
57
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
60
58
  loaded = true;
61
- getLog()(`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
59
+ console.log(`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
62
60
  .map(s => s.toUpperCase())
63
61
  .join(', ')}`);
64
62
  }
@@ -91,7 +89,7 @@ exports.getSecretMap = getSecretMap;
91
89
  function setSecretMap(map) {
92
90
  Object.keys(secretMap).forEach(k => delete secretMap[k]);
93
91
  Object.entries(map).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
94
- getLog()(`setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
92
+ console.log(`setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
95
93
  .map(s => s.toUpperCase())
96
94
  .join(', ')}`);
97
95
  }
@@ -19,7 +19,7 @@ export declare class SlackService<CTX = any> {
19
19
  * Allows to "log" many things at once, similar to `console.log(one, two, three).
20
20
  */
21
21
  log(...items: any[]): Promise<void>;
22
- send(msg: SlackMessage<CTX> | string, ctx?: CTX): Promise<void>;
22
+ send(input: SlackMessage<CTX> | string, ctx?: CTX): Promise<void>;
23
23
  kvToFields(kv: StringMap<any>): SlackAttachmentField[];
24
24
  }
25
25
  export declare function slackDefaultMessagePrefixHook(msg: SlackMessage): string[];
@@ -1,22 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.slackDefaultMessagePrefixHook = exports.SlackService = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
4
5
  const time_lib_1 = require("@naturalcycles/time-lib");
5
6
  const got_1 = require("got");
6
7
  const __1 = require("..");
7
8
  const GAE = !!process.env['GAE_INSTANCE'];
8
- const DEFAULTS = () => ({
9
+ const DEFAULTS = {
9
10
  username: 'bot',
10
11
  channel: '#log',
11
12
  icon_emoji: ':spider_web:',
12
13
  items: 'no text',
13
- });
14
+ };
14
15
  const INSPECT_OPT = {
15
16
  colors: false,
16
17
  includeErrorData: true,
17
18
  includeErrorStack: true,
18
19
  };
19
- const log = (0, __1.Debug)('nc:nodejs-lib:slack');
20
20
  /**
21
21
  * Has 2 main methods:
22
22
  *
@@ -33,7 +33,12 @@ class SlackService {
33
33
  constructor(cfg) {
34
34
  this.cfg = {
35
35
  messagePrefixHook: slackDefaultMessagePrefixHook,
36
+ logger: console,
36
37
  ...cfg,
38
+ inspectOptions: {
39
+ ...INSPECT_OPT,
40
+ ...cfg.inspectOptions,
41
+ },
37
42
  };
38
43
  }
39
44
  /**
@@ -45,38 +50,29 @@ class SlackService {
45
50
  items: items.length === 1 ? items[0] : items,
46
51
  });
47
52
  }
48
- async send(msg, ctx) {
49
- const { webhookUrl, messagePrefixHook } = this.cfg;
53
+ async send(input, ctx) {
54
+ const { webhookUrl, messagePrefixHook, inspectOptions } = this.cfg;
50
55
  // If String is passed as first argument - just transform it to a full SlackMessage
51
- if (typeof msg === 'string') {
52
- msg = {
53
- items: msg,
54
- };
55
- }
56
+ const msg = typeof input === 'string' ? { items: input } : input;
56
57
  if (ctx !== undefined) {
57
58
  Object.assign(msg, { ctx });
58
59
  }
59
- if (!msg.noLog) {
60
- log[msg.level || __1.DebugLogLevel.info](...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean));
61
- }
60
+ this.cfg.logger.log(...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean));
62
61
  if (!webhookUrl)
63
62
  return;
64
63
  // Transform msg.kv into msg.attachments
65
64
  if (msg.kv) {
66
- msg.attachments = [...(msg.attachments || []), { fields: this.kvToFields(msg.kv) }];
65
+ ;
66
+ (msg.attachments || (msg.attachments = [])).push({ fields: this.kvToFields(msg.kv) });
67
67
  delete msg.kv; // to not pass it all the way to Slack Api
68
68
  }
69
69
  let text;
70
- const inspectOpt = {
71
- ...INSPECT_OPT,
72
- ...msg.inspectOptions,
73
- };
74
70
  // Array has a special treatment here
75
71
  if (Array.isArray(msg.items)) {
76
- text = msg.items.map(t => (0, __1.inspectAny)(t, inspectOpt)).join('\n');
72
+ text = msg.items.map(t => (0, __1.inspectAny)(t, inspectOptions)).join('\n');
77
73
  }
78
74
  else {
79
- text = (0, __1.inspectAny)(msg.items, inspectOpt);
75
+ text = (0, __1.inspectAny)(msg.items, inspectOptions);
80
76
  }
81
77
  // Wrap in markdown-text-block if it's anything but plain String
82
78
  if (typeof msg.items !== 'string') {
@@ -88,22 +84,18 @@ class SlackService {
88
84
  const prefix = await messagePrefixHook(msg);
89
85
  if (prefix === null)
90
86
  return; // filtered out!
91
- const json = {
92
- ...DEFAULTS(),
87
+ const json = (0, js_lib_1._omit)({
88
+ ...DEFAULTS,
93
89
  ...this.cfg.defaults,
94
90
  ...msg,
95
91
  // Text with Prefix
96
92
  text: [prefix.join(': '), text].filter(Boolean).join('\n'),
97
- };
98
- // they're not needed in the json payload
99
- delete json['items'];
100
- delete json['ctx'];
101
- delete json['noLog'];
102
- json.channel = (this.cfg.channelByLevel || {})[msg.level] || json.channel;
93
+ }, ['items', 'ctx']);
103
94
  await got_1.default
104
95
  .post(webhookUrl, {
105
96
  json,
106
97
  responseType: 'text',
98
+ timeout: 90000,
107
99
  })
108
100
  .catch(err => {
109
101
  // ignore (unless throwOnError is set)
@@ -1,5 +1,5 @@
1
- import { StringMap } from '@naturalcycles/js-lib';
2
- import { DebugLogLevel, InspectAnyOptions } from '..';
1
+ import { CommonLogger, StringMap } from '@naturalcycles/js-lib';
2
+ import { InspectAnyOptions } from '..';
3
3
  /**
4
4
  * Properties that exists both in SlackApiBody (as per Slack API) and SlackMessage (our abstraction).
5
5
  */
@@ -36,7 +36,6 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
36
36
  * Optional "context object", to be used by `messagePrefixHook`.
37
37
  */
38
38
  ctx?: CTX;
39
- level?: DebugLogLevel;
40
39
  /**
41
40
  * Keys-values will be rendered as MessageAttachment with Fields
42
41
  */
@@ -46,21 +45,11 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
46
45
  */
47
46
  mentions?: string[];
48
47
  /**
49
- * @default false
50
48
  * By default it ignores possible errors from slack
51
- */
52
- throwOnError?: boolean;
53
- /**
49
+ *
54
50
  * @default false
55
- * Skips logging message
56
- */
57
- noLog?: boolean;
58
- /**
59
- * Defaults to:
60
- * includeErrorData: true
61
- * includeErrorStack: true
62
51
  */
63
- inspectOptions?: InspectAnyOptions;
52
+ throwOnError?: boolean;
64
53
  }
65
54
  export interface SlackAttachmentField {
66
55
  title: string;
@@ -96,15 +85,21 @@ export interface SlackServiceCfg<CTX = any> {
96
85
  */
97
86
  webhookUrl?: string;
98
87
  defaults?: Partial<SlackMessage>;
99
- /**
100
- * Override channel when msg.level is set.
101
- * key: DebugLogLevel
102
- * value: channel name to send message to
103
- */
104
- channelByLevel?: StringMap;
105
88
  /**
106
89
  * Function to return an array of "prefix tokens" (will be joined by ': ').
107
90
  * Allows to skip (filter out) the message by returning `null`.
108
91
  */
109
92
  messagePrefixHook: SlackMessagePrefixHook<CTX>;
93
+ /**
94
+ * By default SlackService logs every message to console.log
95
+ * Pass another logger if needed.
96
+ * Pass `noopLogger` to suppress logging completely.
97
+ */
98
+ logger: CommonLogger;
99
+ /**
100
+ * Defaults to:
101
+ * includeErrorData: true
102
+ * includeErrorStack: true
103
+ */
104
+ inspectOptions: InspectAnyOptions;
110
105
  }
@@ -8,7 +8,7 @@ const time_lib_1 = require("@naturalcycles/time-lib");
8
8
  const colors_1 = require("../../colors");
9
9
  const colors_2 = require("../../colors/colors");
10
10
  const inspectOpt = {
11
- colors: (0, colors_2.hasColors)(),
11
+ colors: colors_2.hasColors,
12
12
  breakLength: 300,
13
13
  };
14
14
  /**
@@ -46,14 +46,14 @@ function transformLogProgress(opt = {}) {
46
46
  return;
47
47
  const mem = process.memoryUsage();
48
48
  const now = Date.now();
49
+ const batchedProgress = progress * batchSize;
49
50
  const lastRPS = (processedLastSecond * batchSize) / ((now - lastSecondStarted) / 1000) || 0;
50
- const rpsTotal = Math.round((progress * batchSize) / ((now - started) / 1000)) || 0;
51
+ const rpsTotal = Math.round(batchedProgress / ((now - started) / 1000)) || 0;
51
52
  lastSecondStarted = now;
52
53
  processedLastSecond = 0;
53
54
  const rps10 = Math.round(sma.push(lastRPS));
54
55
  if (mem.rss > peakRSS)
55
56
  peakRSS = mem.rss;
56
- const batchedProgress = progress * batchSize;
57
57
  console.log((0, util_1.inspect)({
58
58
  [final ? `${metric}_final` : metric]: batchedProgress,
59
59
  ...(extra ? extra(chunk, progress) : {}),
@@ -72,14 +72,14 @@ function transformLogProgress(opt = {}) {
72
72
  : {}),
73
73
  }, inspectOpt));
74
74
  if (tenx) {
75
- let perHour = Math.round((progress * 1000 * 60 * 60) / (now - started)) || 0;
75
+ let perHour = Math.round((batchedProgress * 1000 * 60 * 60) / (now - started)) || 0;
76
76
  if (perHour > 900) {
77
77
  perHour = Math.round(perHour / 1000) + 'K';
78
78
  }
79
- console.log(`${(0, colors_1.dimGrey)((0, time_lib_1.dayjs)().toPretty())} ${(0, colors_1.white)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} so far to process ${(0, colors_1.yellow)(progress)} rows, ~${(0, colors_1.yellow)(perHour)}/hour`);
79
+ console.log(`${(0, colors_1.dimGrey)((0, time_lib_1.dayjs)().toPretty())} ${(0, colors_1.white)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} so far to process ${(0, colors_1.yellow)(batchedProgress)} rows, ~${(0, colors_1.yellow)(perHour)}/hour`);
80
80
  }
81
81
  else if (final) {
82
- console.log(`${(0, colors_1.boldWhite)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} to process ${(0, colors_1.yellow)(progress)} rows with total RPS of ${(0, colors_1.yellow)(rpsTotal)}`);
82
+ console.log(`${(0, colors_1.boldWhite)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} to process ${(0, colors_1.yellow)(batchedProgress)} rows with total RPS of ${(0, colors_1.yellow)(rpsTotal)}`);
83
83
  }
84
84
  }
85
85
  }
@@ -5,20 +5,6 @@ const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  class AjvValidationError extends js_lib_1.AppError {
6
6
  constructor(message, data) {
7
7
  super(message, data);
8
- this.constructor = AjvValidationError;
9
- this.__proto__ = AjvValidationError.prototype;
10
- Object.defineProperty(this, 'name', {
11
- value: this.constructor.name,
12
- configurable: true,
13
- });
14
- if (Error.captureStackTrace) {
15
- Error.captureStackTrace(this, this.constructor);
16
- }
17
- else {
18
- Object.defineProperty(this, 'stack', {
19
- value: new Error().stack, // eslint-disable-line unicorn/error-message
20
- });
21
- }
22
8
  }
23
9
  }
24
10
  exports.AjvValidationError = AjvValidationError;
@@ -5,20 +5,6 @@ const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  class JoiValidationError extends js_lib_1.AppError {
6
6
  constructor(message, data) {
7
7
  super(message, data);
8
- this.constructor = JoiValidationError;
9
- this.__proto__ = JoiValidationError.prototype;
10
- Object.defineProperty(this, 'name', {
11
- value: this.constructor.name,
12
- configurable: true,
13
- });
14
- if (Error.captureStackTrace) {
15
- Error.captureStackTrace(this, this.constructor);
16
- }
17
- else {
18
- Object.defineProperty(this, 'stack', {
19
- value: new Error().stack, // eslint-disable-line unicorn/error-message
20
- });
21
- }
22
8
  }
23
9
  }
24
10
  exports.JoiValidationError = JoiValidationError;
@@ -130,6 +130,7 @@ function createError(value, err, objectName) {
130
130
  // Make annotation non-enumerable, to not get it automatically printed,
131
131
  // but still accessible
132
132
  Object.defineProperty(data, 'annotation', {
133
+ writable: true,
133
134
  configurable: true,
134
135
  enumerable: false,
135
136
  value: annotation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "12.48.1",
3
+ "version": "12.51.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "docs-serve": "vuepress dev docs",
@@ -37,7 +37,6 @@
37
37
  "move-file": "^2.0.0",
38
38
  "nanoid": "^3.0.0",
39
39
  "sanitize-html": "^2.5.2",
40
- "supports-color": "^8.0.0",
41
40
  "through2-concurrent": "^2.0.0",
42
41
  "yargs": "^17.0.0"
43
42
  },
@@ -1,6 +1,8 @@
1
- const supporsColorLib = require('supports-color')
1
+ import * as tty from 'tty'
2
2
 
3
- export function hasColors(): boolean {
4
- if (process.env['NO_COLOR']) return false // https://no-color.org/
5
- return !!supporsColorLib.stdout
6
- }
3
+ /**
4
+ * Based on: https://github.com/sindresorhus/yoctocolors/pull/5
5
+ *
6
+ * @experimental
7
+ */
8
+ export const hasColors = !process.env['NO_COLOR'] && tty.WriteStream.prototype.hasColors()
@@ -1,13 +1,10 @@
1
1
  import * as fs from 'fs'
2
2
  import { StringMap } from '@naturalcycles/js-lib'
3
- import { base64ToString, Debug } from '..'
3
+ import { base64ToString } from '..'
4
4
  import { decryptRandomIVBuffer } from './crypto.util'
5
5
 
6
6
  let loaded = false
7
7
 
8
- // it's wrapped to be able to pipe console.* to Stackdriver
9
- const getLog = () => Debug('nc:nodejs-lib:secret')
10
-
11
8
  const secretMap: StringMap = {}
12
9
 
13
10
  /**
@@ -29,7 +26,7 @@ export function loadSecretsFromEnv(): void {
29
26
  })
30
27
 
31
28
  loaded = true
32
- getLog()(
29
+ console.log(
33
30
  `${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(
34
31
  ', ',
35
32
  )}`,
@@ -69,7 +66,7 @@ export function loadSecretsFromJsonFile(filePath: string, SECRET_ENCRYPTION_KEY?
69
66
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
70
67
 
71
68
  loaded = true
72
- getLog()(
69
+ console.log(
73
70
  `${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
74
71
  .map(s => s.toUpperCase())
75
72
  .join(', ')}`,
@@ -105,7 +102,7 @@ export function getSecretMap(): StringMap {
105
102
  export function setSecretMap(map: StringMap): void {
106
103
  Object.keys(secretMap).forEach(k => delete secretMap[k])
107
104
  Object.entries(map).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
108
- getLog()(
105
+ console.log(
109
106
  `setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
110
107
  .map(s => s.toUpperCase())
111
108
  .join(', ')}`,
@@ -1,5 +1,5 @@
1
- import { StringMap } from '@naturalcycles/js-lib'
2
- import { DebugLogLevel, InspectAnyOptions } from '..'
1
+ import { CommonLogger, StringMap } from '@naturalcycles/js-lib'
2
+ import { InspectAnyOptions } from '..'
3
3
 
4
4
  /**
5
5
  * Properties that exists both in SlackApiBody (as per Slack API) and SlackMessage (our abstraction).
@@ -44,8 +44,6 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
44
44
  */
45
45
  ctx?: CTX
46
46
 
47
- level?: DebugLogLevel
48
-
49
47
  /**
50
48
  * Keys-values will be rendered as MessageAttachment with Fields
51
49
  */
@@ -57,23 +55,11 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
57
55
  mentions?: string[]
58
56
 
59
57
  /**
60
- * @default false
61
58
  * By default it ignores possible errors from slack
62
- */
63
- throwOnError?: boolean
64
-
65
- /**
59
+ *
66
60
  * @default false
67
- * Skips logging message
68
- */
69
- noLog?: boolean
70
-
71
- /**
72
- * Defaults to:
73
- * includeErrorData: true
74
- * includeErrorStack: true
75
61
  */
76
- inspectOptions?: InspectAnyOptions
62
+ throwOnError?: boolean
77
63
  }
78
64
 
79
65
  export interface SlackAttachmentField {
@@ -120,16 +106,23 @@ export interface SlackServiceCfg<CTX = any> {
120
106
 
121
107
  defaults?: Partial<SlackMessage>
122
108
 
123
- /**
124
- * Override channel when msg.level is set.
125
- * key: DebugLogLevel
126
- * value: channel name to send message to
127
- */
128
- channelByLevel?: StringMap
129
-
130
109
  /**
131
110
  * Function to return an array of "prefix tokens" (will be joined by ': ').
132
111
  * Allows to skip (filter out) the message by returning `null`.
133
112
  */
134
113
  messagePrefixHook: SlackMessagePrefixHook<CTX>
114
+
115
+ /**
116
+ * By default SlackService logs every message to console.log
117
+ * Pass another logger if needed.
118
+ * Pass `noopLogger` to suppress logging completely.
119
+ */
120
+ logger: CommonLogger
121
+
122
+ /**
123
+ * Defaults to:
124
+ * includeErrorData: true
125
+ * includeErrorStack: true
126
+ */
127
+ inspectOptions: InspectAnyOptions
135
128
  }
@@ -1,7 +1,7 @@
1
- import { StringMap } from '@naturalcycles/js-lib'
1
+ import { _omit, StringMap } from '@naturalcycles/js-lib'
2
2
  import { dayjs } from '@naturalcycles/time-lib'
3
3
  import got from 'got'
4
- import { Debug, DebugLogLevel, inspectAny, InspectAnyOptions } from '..'
4
+ import { inspectAny, InspectAnyOptions } from '..'
5
5
  import {
6
6
  SlackApiBody,
7
7
  SlackAttachmentField,
@@ -11,12 +11,12 @@ import {
11
11
 
12
12
  const GAE = !!process.env['GAE_INSTANCE']
13
13
 
14
- const DEFAULTS = (): SlackMessage => ({
14
+ const DEFAULTS: SlackMessage = {
15
15
  username: 'bot',
16
16
  channel: '#log',
17
17
  icon_emoji: ':spider_web:',
18
18
  items: 'no text',
19
- })
19
+ }
20
20
 
21
21
  const INSPECT_OPT: InspectAnyOptions = {
22
22
  colors: false,
@@ -24,8 +24,6 @@ const INSPECT_OPT: InspectAnyOptions = {
24
24
  includeErrorStack: true,
25
25
  }
26
26
 
27
- const log = Debug('nc:nodejs-lib:slack')
28
-
29
27
  /**
30
28
  * Has 2 main methods:
31
29
  *
@@ -42,7 +40,12 @@ export class SlackService<CTX = any> {
42
40
  constructor(cfg: Partial<SlackServiceCfg<CTX>>) {
43
41
  this.cfg = {
44
42
  messagePrefixHook: slackDefaultMessagePrefixHook,
43
+ logger: console,
45
44
  ...cfg,
45
+ inspectOptions: {
46
+ ...INSPECT_OPT,
47
+ ...cfg.inspectOptions,
48
+ },
46
49
  }
47
50
  }
48
51
 
@@ -58,47 +61,34 @@ export class SlackService<CTX = any> {
58
61
  })
59
62
  }
60
63
 
61
- async send(msg: SlackMessage<CTX> | string, ctx?: CTX): Promise<void> {
62
- const { webhookUrl, messagePrefixHook } = this.cfg
64
+ async send(input: SlackMessage<CTX> | string, ctx?: CTX): Promise<void> {
65
+ const { webhookUrl, messagePrefixHook, inspectOptions } = this.cfg
63
66
 
64
67
  // If String is passed as first argument - just transform it to a full SlackMessage
65
- if (typeof msg === 'string') {
66
- msg = {
67
- items: msg,
68
- }
69
- }
68
+ const msg = typeof input === 'string' ? { items: input } : input
70
69
 
71
70
  if (ctx !== undefined) {
72
71
  Object.assign(msg, { ctx })
73
72
  }
74
73
 
75
- if (!msg.noLog) {
76
- log[msg.level || DebugLogLevel.info](
77
- ...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean),
78
- )
79
- }
74
+ this.cfg.logger.log(...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean))
80
75
 
81
76
  if (!webhookUrl) return
82
77
 
83
78
  // Transform msg.kv into msg.attachments
84
79
  if (msg.kv) {
85
- msg.attachments = [...(msg.attachments || []), { fields: this.kvToFields(msg.kv) }]
80
+ ;(msg.attachments ||= []).push({ fields: this.kvToFields(msg.kv) })
86
81
 
87
82
  delete msg.kv // to not pass it all the way to Slack Api
88
83
  }
89
84
 
90
85
  let text: string
91
86
 
92
- const inspectOpt = {
93
- ...INSPECT_OPT,
94
- ...msg.inspectOptions,
95
- }
96
-
97
87
  // Array has a special treatment here
98
88
  if (Array.isArray(msg.items)) {
99
- text = msg.items.map(t => inspectAny(t, inspectOpt)).join('\n')
89
+ text = msg.items.map(t => inspectAny(t, inspectOptions)).join('\n')
100
90
  } else {
101
- text = inspectAny(msg.items, inspectOpt)
91
+ text = inspectAny(msg.items, inspectOptions)
102
92
  }
103
93
 
104
94
  // Wrap in markdown-text-block if it's anything but plain String
@@ -113,29 +103,26 @@ export class SlackService<CTX = any> {
113
103
  const prefix = await messagePrefixHook(msg)
114
104
  if (prefix === null) return // filtered out!
115
105
 
116
- const json: SlackApiBody = {
117
- ...DEFAULTS(),
118
- ...this.cfg.defaults,
119
- ...msg,
120
- // Text with Prefix
121
- text: [prefix.join(': '), text].filter(Boolean).join('\n'),
122
- }
123
-
124
- // they're not needed in the json payload
125
- delete json['items']
126
- delete json['ctx']
127
- delete json['noLog']
128
-
129
- json.channel = (this.cfg.channelByLevel || {})[msg.level!] || json.channel
106
+ const json: SlackApiBody = _omit(
107
+ {
108
+ ...DEFAULTS,
109
+ ...this.cfg.defaults,
110
+ ...msg,
111
+ // Text with Prefix
112
+ text: [prefix.join(': '), text].filter(Boolean).join('\n'),
113
+ },
114
+ ['items', 'ctx'],
115
+ )
130
116
 
131
117
  await got
132
118
  .post(webhookUrl, {
133
119
  json,
134
120
  responseType: 'text',
121
+ timeout: 90_000,
135
122
  })
136
123
  .catch(err => {
137
124
  // ignore (unless throwOnError is set)
138
- if ((msg as SlackMessage).throwOnError) throw err
125
+ if (msg.throwOnError) throw err
139
126
  })
140
127
  }
141
128
 
@@ -104,7 +104,7 @@ export interface TransformLogProgressOptions<IN = any> extends TransformOptions
104
104
  }
105
105
 
106
106
  const inspectOpt: InspectOptions = {
107
- colors: hasColors(),
107
+ colors: hasColors,
108
108
  breakLength: 300,
109
109
  }
110
110
 
@@ -163,16 +163,15 @@ export function transformLogProgress<IN = any>(
163
163
  const mem = process.memoryUsage()
164
164
 
165
165
  const now = Date.now()
166
+ const batchedProgress = progress * batchSize
166
167
  const lastRPS = (processedLastSecond * batchSize) / ((now - lastSecondStarted) / 1000) || 0
167
- const rpsTotal = Math.round((progress * batchSize) / ((now - started) / 1000)) || 0
168
+ const rpsTotal = Math.round(batchedProgress / ((now - started) / 1000)) || 0
168
169
  lastSecondStarted = now
169
170
  processedLastSecond = 0
170
171
 
171
172
  const rps10 = Math.round(sma.push(lastRPS))
172
173
  if (mem.rss > peakRSS) peakRSS = mem.rss
173
174
 
174
- const batchedProgress = progress * batchSize
175
-
176
175
  console.log(
177
176
  inspect(
178
177
  {
@@ -197,7 +196,8 @@ export function transformLogProgress<IN = any>(
197
196
  )
198
197
 
199
198
  if (tenx) {
200
- let perHour: number | string = Math.round((progress * 1000 * 60 * 60) / (now - started)) || 0
199
+ let perHour: number | string =
200
+ Math.round((batchedProgress * 1000 * 60 * 60) / (now - started)) || 0
201
201
  if (perHour > 900) {
202
202
  perHour = Math.round(perHour / 1000) + 'K'
203
203
  }
@@ -205,12 +205,12 @@ export function transformLogProgress<IN = any>(
205
205
  console.log(
206
206
  `${dimGrey(dayjs().toPretty())} ${white(metric)} took ${yellow(
207
207
  _since(started),
208
- )} so far to process ${yellow(progress)} rows, ~${yellow(perHour)}/hour`,
208
+ )} so far to process ${yellow(batchedProgress)} rows, ~${yellow(perHour)}/hour`,
209
209
  )
210
210
  } else if (final) {
211
211
  console.log(
212
212
  `${boldWhite(metric)} took ${yellow(_since(started))} to process ${yellow(
213
- progress,
213
+ batchedProgress,
214
214
  )} rows with total RPS of ${yellow(rpsTotal)}`,
215
215
  )
216
216
  }
@@ -10,20 +10,5 @@ export interface AjvValidationErrorData extends ErrorData {
10
10
  export class AjvValidationError extends AppError<AjvValidationErrorData> {
11
11
  constructor(message: string, data: AjvValidationErrorData) {
12
12
  super(message, data)
13
-
14
- this.constructor = AjvValidationError
15
- ;(this as any).__proto__ = AjvValidationError.prototype
16
- Object.defineProperty(this, 'name', {
17
- value: this.constructor.name,
18
- configurable: true,
19
- })
20
-
21
- if (Error.captureStackTrace) {
22
- Error.captureStackTrace(this, this.constructor)
23
- } else {
24
- Object.defineProperty(this, 'stack', {
25
- value: new Error().stack, // eslint-disable-line unicorn/error-message
26
- })
27
- }
28
13
  }
29
14
  }
@@ -28,20 +28,5 @@ export interface JoiValidationErrorData extends ErrorData {
28
28
  export class JoiValidationError extends AppError<JoiValidationErrorData> {
29
29
  constructor(message: string, data: JoiValidationErrorData) {
30
30
  super(message, data)
31
-
32
- this.constructor = JoiValidationError
33
- ;(this as any).__proto__ = JoiValidationError.prototype
34
- Object.defineProperty(this, 'name', {
35
- value: this.constructor.name,
36
- configurable: true,
37
- })
38
-
39
- if (Error.captureStackTrace) {
40
- Error.captureStackTrace(this, this.constructor)
41
- } else {
42
- Object.defineProperty(this, 'stack', {
43
- value: new Error().stack, // eslint-disable-line unicorn/error-message
44
- })
45
- }
46
31
  }
47
32
  }
@@ -172,6 +172,7 @@ function createError(value: any, err: ValidationError, objectName?: string): Joi
172
172
  // Make annotation non-enumerable, to not get it automatically printed,
173
173
  // but still accessible
174
174
  Object.defineProperty(data, 'annotation', {
175
+ writable: true,
175
176
  configurable: true,
176
177
  enumerable: false,
177
178
  value: annotation,