@naturalcycles/nodejs-lib 15.7.0 → 15.9.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,4 +1,4 @@
1
1
  /**
2
2
  * CommonLogger that logs to process.stdout directly (bypassing console.log).
3
3
  */
4
- export declare const stdoutLogger: import("@naturalcycles/js-lib/log").CommonLogger;
4
+ export declare const stdoutLogger: any;
@@ -1,9 +1,19 @@
1
+ import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
1
2
  import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema';
2
3
  import type { Ajv } from 'ajv';
3
4
  import { AjvValidationError } from './ajvValidationError.js';
4
5
  export interface AjvValidationOptions {
5
- objectName?: string;
6
- objectId?: string;
6
+ /**
7
+ * Defaults to false.
8
+ *
9
+ * If set to true - AJV will mutate the input in case it needs to apply transformations
10
+ * (strip unknown properties, convert types, etc).
11
+ *
12
+ * If false - it will deep-clone the input to prevent its mutation. Will return the cloned/mutated object.
13
+ */
14
+ mutateInput?: boolean;
15
+ inputName?: string;
16
+ inputId?: string;
7
17
  }
8
18
  export interface AjvSchemaCfg {
9
19
  /**
@@ -11,11 +21,7 @@ export interface AjvSchemaCfg {
11
21
  * AjvSchema default (not the same as Ajv defaults) parameters
12
22
  */
13
23
  ajv: Ajv;
14
- /**
15
- * Dependent schemas to pass to Ajv instance constructor.
16
- * Simpler than instantiating and passing ajv instance yourself.
17
- */
18
- objectName?: string;
24
+ inputName?: string;
19
25
  /**
20
26
  * Option of Ajv.
21
27
  * If set to true - will mutate your input objects!
@@ -53,7 +59,6 @@ export declare class AjvSchema<T = unknown> {
53
59
  */
54
60
  static create<T>(schema: JsonSchemaBuilder<T> | JsonSchema<T> | AjvSchema<T>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<T>;
55
61
  readonly cfg: AjvSchemaCfg;
56
- private getValidateFunction;
57
62
  /**
58
63
  * It returns the original object just for convenience.
59
64
  * Reminder: Ajv will MUTATE your object under 2 circumstances:
@@ -62,7 +67,9 @@ export declare class AjvSchema<T = unknown> {
62
67
  *
63
68
  * Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
64
69
  */
65
- validate(obj: T, opt?: AjvValidationOptions): T;
66
- isValid(obj: T): boolean;
67
- getValidationError(obj: T, opt?: AjvValidationOptions): AjvValidationError | undefined;
70
+ validate(input: T, opt?: AjvValidationOptions): T;
71
+ isValid(input: T, opt?: AjvValidationOptions): boolean;
72
+ getValidationResult(input: T, opt?: AjvValidationOptions): ValidationFunctionResult<T, AjvValidationError>;
73
+ getValidationFunction(): ValidationFunction<T, AjvValidationError>;
74
+ private getAJVValidateFunction;
68
75
  }
@@ -1,6 +1,6 @@
1
- import { _isObject, _lazyValue } from '@naturalcycles/js-lib';
1
+ import { _isObject, _lazyValue, } from '@naturalcycles/js-lib';
2
2
  import { JsonSchemaAnyBuilder } from '@naturalcycles/js-lib/json-schema';
3
- import { _filterNullishValues } from '@naturalcycles/js-lib/object';
3
+ import { _deepCopy, _filterNullishValues } from '@naturalcycles/js-lib/object';
4
4
  import { _substringBefore } from '@naturalcycles/js-lib/string';
5
5
  import { _inspect } from '../../string/inspect.js';
6
6
  import { AjvValidationError } from './ajvValidationError.js';
@@ -19,22 +19,11 @@ export class AjvSchema {
19
19
  lazy: false,
20
20
  ...cfg,
21
21
  ajv: cfg.ajv || getAjv(),
22
- // ajv:
23
- // cfg.ajv ||
24
- // getAjv({
25
- // schemas: cfg.schemas?.map(s => {
26
- // if (s instanceof AjvSchema) return s.schema
27
- // if (s instanceof JsonSchemaAnyBuilder) return s.build()
28
- // return s as JsonSchema
29
- // }),
30
- // coerceTypes: cfg.coerceTypes || false,
31
- // // verbose: true,
32
- // }),
33
- // Auto-detecting "ObjectName" from $id of the schema (e.g "Address.schema.json")
34
- objectName: cfg.objectName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
22
+ // Auto-detecting "InputName" from $id of the schema (e.g "Address.schema.json")
23
+ inputName: cfg.inputName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
35
24
  };
36
25
  if (!cfg.lazy) {
37
- this.getValidateFunction(); // compile eagerly
26
+ this.getAJVValidateFunction(); // compile eagerly
38
27
  }
39
28
  }
40
29
  /**
@@ -64,7 +53,6 @@ export class AjvSchema {
64
53
  return new AjvSchema(schema, cfg);
65
54
  }
66
55
  cfg;
67
- getValidateFunction = _lazyValue(() => this.cfg.ajv.compile(this.schema));
68
56
  /**
69
57
  * It returns the original object just for convenience.
70
58
  * Reminder: Ajv will MUTATE your object under 2 circumstances:
@@ -73,32 +61,47 @@ export class AjvSchema {
73
61
  *
74
62
  * Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
75
63
  */
76
- validate(obj, opt = {}) {
77
- const err = this.getValidationError(obj, opt);
64
+ validate(input, opt = {}) {
65
+ const [err, output] = this.getValidationResult(input, opt);
78
66
  if (err)
79
67
  throw err;
80
- return obj;
68
+ return output;
81
69
  }
82
- isValid(obj) {
83
- return this.getValidateFunction()(obj);
70
+ isValid(input, opt) {
71
+ const [err] = this.getValidationResult(input, opt);
72
+ return !err;
84
73
  }
85
- getValidationError(obj, opt = {}) {
86
- if (this.isValid(obj))
87
- return;
88
- const errors = this.getValidateFunction().errors;
89
- const { objectId = _isObject(obj) ? obj['id'] : undefined, objectName = this.cfg.objectName, } = opt;
90
- const name = [objectName || 'Object', objectId].filter(Boolean).join('.');
74
+ getValidationResult(input, opt = {}) {
75
+ const fn = this.getAJVValidateFunction();
76
+ const item = opt.mutateInput || typeof input !== 'object' ? input : _deepCopy(input);
77
+ const valid = fn(item); // mutates item
78
+ if (valid)
79
+ return [null, item];
80
+ const errors = fn.errors;
81
+ const { inputId = _isObject(input) ? input['id'] : undefined, inputName = this.cfg.inputName || 'Object', } = opt;
82
+ const dataVar = [inputName, inputId].filter(Boolean).join('.');
91
83
  let message = this.cfg.ajv.errorsText(errors, {
92
- dataVar: name,
84
+ dataVar,
93
85
  separator,
94
86
  });
95
- const inputStringified = _inspect(obj, { maxLen: 4000 });
87
+ const inputStringified = _inspect(input, { maxLen: 4000 });
96
88
  message = [message, 'Input: ' + inputStringified].join(separator);
97
- return new AjvValidationError(message, _filterNullishValues({
89
+ const err = new AjvValidationError(message, _filterNullishValues({
98
90
  errors,
99
- objectName,
100
- objectId,
91
+ inputName,
92
+ inputId,
101
93
  }));
94
+ return [err, item];
102
95
  }
96
+ getValidationFunction() {
97
+ return (input, opt) => {
98
+ return this.getValidationResult(input, {
99
+ mutateInput: opt?.mutateInput,
100
+ inputName: opt?.inputName,
101
+ inputId: opt?.inputId,
102
+ });
103
+ };
104
+ }
105
+ getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile(this.schema));
103
106
  }
104
107
  const separator = '\n';
@@ -3,8 +3,8 @@ import { AppError } from '@naturalcycles/js-lib/error/error.util.js';
3
3
  import type { ErrorObject } from 'ajv';
4
4
  export interface AjvValidationErrorData extends ErrorData {
5
5
  errors: ErrorObject[];
6
- objectName?: string;
7
- objectId?: string;
6
+ inputName?: string;
7
+ inputId?: string;
8
8
  }
9
9
  export declare class AjvValidationError extends AppError<AjvValidationErrorData> {
10
10
  constructor(message: string, data: AjvValidationErrorData);
@@ -6,7 +6,7 @@ import { Ajv } from 'ajv';
6
6
  * This function should be used as much as possible,
7
7
  * to benefit from cached Ajv instance.
8
8
  */
9
- export declare const getAjv: import("@naturalcycles/js-lib/types").Lazy<Ajv>;
9
+ export declare const getAjv: any;
10
10
  /**
11
11
  * Create Ajv with modified defaults.
12
12
  *
@@ -10,7 +10,7 @@ export declare const numberSchema: NumberSchema<number>;
10
10
  export declare const numberSchemaTyped: <T>() => NumberSchema<T>;
11
11
  export declare const integerSchema: NumberSchema<number>;
12
12
  export declare const percentageSchema: NumberSchema<number>;
13
- export declare const dateStringSchema: StringSchema<import("@naturalcycles/js-lib/types").IsoDate>;
13
+ export declare const dateStringSchema: StringSchema<IsoDate>;
14
14
  export declare const binarySchema: import("joi").BinarySchema<Buffer<ArrayBufferLike>>;
15
15
  export declare const dateObjectSchema: ObjectSchema<any>;
16
16
  export declare const dateIntervalStringSchema: StringSchema<string>;
@@ -13,8 +13,8 @@ import type { ValidationErrorItem } from 'joi';
13
13
  */
14
14
  export interface JoiValidationErrorData extends ErrorData {
15
15
  joiValidationErrorItems: ValidationErrorItem[];
16
- joiValidationObjectName?: string;
17
- joiValidationObjectId?: string;
16
+ joiValidationInputName?: string;
17
+ joiValidationInputId?: string;
18
18
  /**
19
19
  * Error "annotation" is stripped in Error.message.
20
20
  * This field contains the "full" annotation.
@@ -1,9 +1,7 @@
1
+ import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
1
2
  import type { AnySchema, ValidationOptions } from 'joi';
2
3
  import { JoiValidationError } from './joi.validation.error.js';
3
- export interface JoiValidationResult<T = any> {
4
- value: T;
5
- error?: JoiValidationError;
6
- }
4
+ export declare function getJoiValidationFunction<T>(schema: AnySchema<T>): ValidationFunction<T, JoiValidationError>;
7
5
  /**
8
6
  * Validates with Joi.
9
7
  * Throws JoiValidationError if invalid.
@@ -11,7 +9,7 @@ export interface JoiValidationResult<T = any> {
11
9
  *
12
10
  * If `schema` is undefined - returns value as is.
13
11
  */
14
- export declare function validate<T>(input: any, schema?: AnySchema<T>, objectName?: string, opt?: ValidationOptions): T;
12
+ export declare function validate<T>(input: any, schema?: AnySchema<T>, inputName?: string, opt?: ValidationOptions): T;
15
13
  /**
16
14
  * Validates with Joi.
17
15
  * Returns JoiValidationResult with converted value and error (if any).
@@ -21,7 +19,7 @@ export declare function validate<T>(input: any, schema?: AnySchema<T>, objectNam
21
19
  *
22
20
  * If `schema` is undefined - returns value as is.
23
21
  */
24
- export declare function getValidationResult<T>(input: any, schema?: AnySchema<T>, objectName?: string, options?: ValidationOptions): JoiValidationResult<T>;
22
+ export declare function getValidationResult<T>(input: T, schema?: AnySchema<T>, inputName?: string, options?: ValidationOptions): ValidationFunctionResult<T, JoiValidationError>;
25
23
  /**
26
24
  * Convenience function that returns true if !error.
27
25
  */
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * "Converts" mean e.g trims all strings from leading/trailing spaces.
7
7
  */
8
- import { _hb, _isObject } from '@naturalcycles/js-lib';
8
+ import { _hb, _isObject, } from '@naturalcycles/js-lib';
9
+ import { _assert } from '@naturalcycles/js-lib/error/assert.js';
9
10
  import { _truncateMiddle } from '@naturalcycles/js-lib/string/string.util.js';
10
11
  import { JoiValidationError } from './joi.validation.error.js';
11
12
  // Strip colors in production (for e.g Sentry reporting)
@@ -31,6 +32,12 @@ const defaultOptions = {
31
32
  // stack: true,
32
33
  // }
33
34
  };
35
+ export function getJoiValidationFunction(schema) {
36
+ return (input, opt) => {
37
+ _assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Joi');
38
+ return getValidationResult(input, schema, opt?.inputName);
39
+ };
40
+ }
34
41
  /**
35
42
  * Validates with Joi.
36
43
  * Throws JoiValidationError if invalid.
@@ -38,11 +45,10 @@ const defaultOptions = {
38
45
  *
39
46
  * If `schema` is undefined - returns value as is.
40
47
  */
41
- export function validate(input, schema, objectName, opt = {}) {
42
- const { value: returnValue, error } = getValidationResult(input, schema, objectName, opt);
43
- if (error) {
48
+ export function validate(input, schema, inputName, opt = {}) {
49
+ const [error, returnValue] = getValidationResult(input, schema, inputName, opt);
50
+ if (error)
44
51
  throw error;
45
- }
46
52
  return returnValue;
47
53
  }
48
54
  /**
@@ -54,20 +60,15 @@ export function validate(input, schema, objectName, opt = {}) {
54
60
  *
55
61
  * If `schema` is undefined - returns value as is.
56
62
  */
57
- export function getValidationResult(input, schema, objectName, options = {}) {
63
+ export function getValidationResult(input, schema, inputName, options = {}) {
58
64
  if (!schema)
59
- return { value: input };
65
+ return [null, input];
60
66
  const { value, error } = schema.validate(input, {
61
67
  ...defaultOptions,
62
68
  ...options,
63
69
  });
64
- const vr = {
65
- value,
66
- };
67
- if (error) {
68
- vr.error = createError(input, error, objectName);
69
- }
70
- return vr;
70
+ const err = error ? createError(input, error, inputName) : null;
71
+ return [err, value];
71
72
  }
72
73
  /**
73
74
  * Convenience function that returns true if !error.
@@ -95,12 +96,12 @@ export function convert(input, schema) {
95
96
  const { value } = schema.validate(input, defaultOptions);
96
97
  return value;
97
98
  }
98
- function createError(value, err, objectName) {
99
+ function createError(value, err, inputName) {
99
100
  const tokens = [];
100
- const objectId = _isObject(value) ? value['id'] : undefined;
101
- if (objectId || objectName) {
102
- objectName ||= value?.constructor?.name;
103
- tokens.push('Invalid ' + [objectName, objectId].filter(Boolean).join('.'));
101
+ const inputId = _isObject(value) ? value['id'] : undefined;
102
+ if (inputId || inputName) {
103
+ inputName ||= value?.constructor?.name;
104
+ tokens.push('Invalid ' + [inputName, inputId].filter(Boolean).join('.'));
104
105
  }
105
106
  const annotation = err.annotate(stripColors);
106
107
  if (annotation.length > 100) {
@@ -123,8 +124,8 @@ function createError(value, err, objectName) {
123
124
  const msg = tokens.join('\n');
124
125
  const data = {
125
126
  joiValidationErrorItems: err.details,
126
- ...(objectName && { joiValidationObjectName: objectName }),
127
- ...(objectId && { joiValidationObjectId: objectId }),
127
+ ...(inputName && { joiValidationInputName: inputName }),
128
+ ...(inputId && { joiValidationInputId: inputId }),
128
129
  };
129
130
  // Make annotation non-enumerable, to not get it automatically printed,
130
131
  // but still accessible
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.7.0",
4
+ "version": "15.9.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/through2-concurrent": "^2",
26
- "@naturalcycles/dev-lib": "18.4.2"
26
+ "@naturalcycles/dev-lib": "19.22.0"
27
27
  },
28
28
  "exports": {
29
29
  ".": "./dist/index.js",
@@ -1,7 +1,12 @@
1
- import { _isObject, _lazyValue } from '@naturalcycles/js-lib'
1
+ import {
2
+ _isObject,
3
+ _lazyValue,
4
+ type ValidationFunction,
5
+ type ValidationFunctionResult,
6
+ } from '@naturalcycles/js-lib'
2
7
  import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema'
3
8
  import { JsonSchemaAnyBuilder } from '@naturalcycles/js-lib/json-schema'
4
- import { _filterNullishValues } from '@naturalcycles/js-lib/object'
9
+ import { _deepCopy, _filterNullishValues } from '@naturalcycles/js-lib/object'
5
10
  import { _substringBefore } from '@naturalcycles/js-lib/string'
6
11
  import type { Ajv } from 'ajv'
7
12
  import { _inspect } from '../../string/inspect.js'
@@ -9,8 +14,17 @@ import { AjvValidationError } from './ajvValidationError.js'
9
14
  import { getAjv } from './getAjv.js'
10
15
 
11
16
  export interface AjvValidationOptions {
12
- objectName?: string
13
- objectId?: string
17
+ /**
18
+ * Defaults to false.
19
+ *
20
+ * If set to true - AJV will mutate the input in case it needs to apply transformations
21
+ * (strip unknown properties, convert types, etc).
22
+ *
23
+ * If false - it will deep-clone the input to prevent its mutation. Will return the cloned/mutated object.
24
+ */
25
+ mutateInput?: boolean
26
+ inputName?: string
27
+ inputId?: string
14
28
  }
15
29
 
16
30
  export interface AjvSchemaCfg {
@@ -20,13 +34,7 @@ export interface AjvSchemaCfg {
20
34
  */
21
35
  ajv: Ajv
22
36
 
23
- /**
24
- * Dependent schemas to pass to Ajv instance constructor.
25
- * Simpler than instantiating and passing ajv instance yourself.
26
- */
27
- // schemas?: (JsonSchema | JsonSchemaBuilder | AjvSchema)[]
28
-
29
- objectName?: string
37
+ inputName?: string
30
38
 
31
39
  /**
32
40
  * Option of Ajv.
@@ -59,23 +67,12 @@ export class AjvSchema<T = unknown> {
59
67
  lazy: false,
60
68
  ...cfg,
61
69
  ajv: cfg.ajv || getAjv(),
62
- // ajv:
63
- // cfg.ajv ||
64
- // getAjv({
65
- // schemas: cfg.schemas?.map(s => {
66
- // if (s instanceof AjvSchema) return s.schema
67
- // if (s instanceof JsonSchemaAnyBuilder) return s.build()
68
- // return s as JsonSchema
69
- // }),
70
- // coerceTypes: cfg.coerceTypes || false,
71
- // // verbose: true,
72
- // }),
73
- // Auto-detecting "ObjectName" from $id of the schema (e.g "Address.schema.json")
74
- objectName: cfg.objectName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
70
+ // Auto-detecting "InputName" from $id of the schema (e.g "Address.schema.json")
71
+ inputName: cfg.inputName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
75
72
  }
76
73
 
77
74
  if (!cfg.lazy) {
78
- this.getValidateFunction() // compile eagerly
75
+ this.getAJVValidateFunction() // compile eagerly
79
76
  }
80
77
  }
81
78
 
@@ -114,8 +111,6 @@ export class AjvSchema<T = unknown> {
114
111
 
115
112
  readonly cfg: AjvSchemaCfg
116
113
 
117
- private getValidateFunction = _lazyValue(() => this.cfg.ajv.compile<T>(this.schema))
118
-
119
114
  /**
120
115
  * It returns the original object just for convenience.
121
116
  * Reminder: Ajv will MUTATE your object under 2 circumstances:
@@ -124,44 +119,66 @@ export class AjvSchema<T = unknown> {
124
119
  *
125
120
  * Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
126
121
  */
127
- validate(obj: T, opt: AjvValidationOptions = {}): T {
128
- const err = this.getValidationError(obj, opt)
122
+ validate(input: T, opt: AjvValidationOptions = {}): T {
123
+ const [err, output] = this.getValidationResult(input, opt)
129
124
  if (err) throw err
130
- return obj
125
+ return output
131
126
  }
132
127
 
133
- isValid(obj: T): boolean {
134
- return this.getValidateFunction()(obj)
128
+ isValid(input: T, opt?: AjvValidationOptions): boolean {
129
+ const [err] = this.getValidationResult(input, opt)
130
+ return !err
135
131
  }
136
132
 
137
- getValidationError(obj: T, opt: AjvValidationOptions = {}): AjvValidationError | undefined {
138
- if (this.isValid(obj)) return
133
+ getValidationResult(
134
+ input: T,
135
+ opt: AjvValidationOptions = {},
136
+ ): ValidationFunctionResult<T, AjvValidationError> {
137
+ const fn = this.getAJVValidateFunction()
138
+
139
+ const item = opt.mutateInput || typeof input !== 'object' ? input : _deepCopy(input)
139
140
 
140
- const errors = this.getValidateFunction().errors!
141
+ const valid = fn(item) // mutates item
142
+ if (valid) return [null, item]
143
+
144
+ const errors = fn.errors!
141
145
 
142
146
  const {
143
- objectId = _isObject(obj) ? (obj['id' as keyof T] as any) : undefined,
144
- objectName = this.cfg.objectName,
147
+ inputId = _isObject(input) ? (input['id' as keyof T] as any) : undefined,
148
+ inputName = this.cfg.inputName || 'Object',
145
149
  } = opt
146
- const name = [objectName || 'Object', objectId].filter(Boolean).join('.')
150
+ const dataVar = [inputName, inputId].filter(Boolean).join('.')
147
151
 
148
152
  let message = this.cfg.ajv.errorsText(errors, {
149
- dataVar: name,
153
+ dataVar,
150
154
  separator,
151
155
  })
152
156
 
153
- const inputStringified = _inspect(obj, { maxLen: 4000 })
157
+ const inputStringified = _inspect(input, { maxLen: 4000 })
154
158
  message = [message, 'Input: ' + inputStringified].join(separator)
155
159
 
156
- return new AjvValidationError(
160
+ const err = new AjvValidationError(
157
161
  message,
158
162
  _filterNullishValues({
159
163
  errors,
160
- objectName,
161
- objectId,
164
+ inputName,
165
+ inputId,
162
166
  }),
163
167
  )
168
+ return [err, item]
164
169
  }
170
+
171
+ getValidationFunction(): ValidationFunction<T, AjvValidationError> {
172
+ return (input, opt) => {
173
+ return this.getValidationResult(input, {
174
+ mutateInput: opt?.mutateInput,
175
+ inputName: opt?.inputName,
176
+ inputId: opt?.inputId,
177
+ })
178
+ }
179
+ }
180
+
181
+ private getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile<T>(this.schema))
165
182
  }
166
183
 
167
184
  const separator = '\n'
@@ -4,8 +4,8 @@ import type { ErrorObject } from 'ajv'
4
4
 
5
5
  export interface AjvValidationErrorData extends ErrorData {
6
6
  errors: ErrorObject[]
7
- objectName?: string
8
- objectId?: string
7
+ inputName?: string
8
+ inputId?: string
9
9
  }
10
10
 
11
11
  export class AjvValidationError extends AppError<AjvValidationErrorData> {
@@ -14,8 +14,8 @@ import type { ValidationErrorItem } from 'joi'
14
14
  */
15
15
  export interface JoiValidationErrorData extends ErrorData {
16
16
  joiValidationErrorItems: ValidationErrorItem[]
17
- joiValidationObjectName?: string
18
- joiValidationObjectId?: string
17
+ joiValidationInputName?: string
18
+ joiValidationInputId?: string
19
19
  /**
20
20
  * Error "annotation" is stripped in Error.message.
21
21
  * This field contains the "full" annotation.
@@ -6,18 +6,18 @@
6
6
  * "Converts" mean e.g trims all strings from leading/trailing spaces.
7
7
  */
8
8
 
9
- import { _hb, _isObject } from '@naturalcycles/js-lib'
9
+ import {
10
+ _hb,
11
+ _isObject,
12
+ type ValidationFunction,
13
+ type ValidationFunctionResult,
14
+ } from '@naturalcycles/js-lib'
15
+ import { _assert } from '@naturalcycles/js-lib/error/assert.js'
10
16
  import { _truncateMiddle } from '@naturalcycles/js-lib/string/string.util.js'
11
17
  import type { AnySchema, ValidationError, ValidationOptions } from 'joi'
12
18
  import type { JoiValidationErrorData } from './joi.validation.error.js'
13
19
  import { JoiValidationError } from './joi.validation.error.js'
14
20
 
15
- // todo: consider replacing with Tuple of [error, value]
16
- export interface JoiValidationResult<T = any> {
17
- value: T
18
- error?: JoiValidationError
19
- }
20
-
21
21
  // Strip colors in production (for e.g Sentry reporting)
22
22
  // const stripColors = process.env.NODE_ENV === 'production' || !!process.env.GAE_INSTANCE
23
23
  // Currently colors do more bad than good, so let's strip them always for now
@@ -43,6 +43,15 @@ const defaultOptions: ValidationOptions = {
43
43
  // }
44
44
  }
45
45
 
46
+ export function getJoiValidationFunction<T>(
47
+ schema: AnySchema<T>,
48
+ ): ValidationFunction<T, JoiValidationError> {
49
+ return (input, opt) => {
50
+ _assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Joi')
51
+ return getValidationResult(input, schema, opt?.inputName)
52
+ }
53
+ }
54
+
46
55
  /**
47
56
  * Validates with Joi.
48
57
  * Throws JoiValidationError if invalid.
@@ -53,15 +62,11 @@ const defaultOptions: ValidationOptions = {
53
62
  export function validate<T>(
54
63
  input: any,
55
64
  schema?: AnySchema<T>,
56
- objectName?: string,
65
+ inputName?: string,
57
66
  opt: ValidationOptions = {},
58
67
  ): T {
59
- const { value: returnValue, error } = getValidationResult(input, schema, objectName, opt)
60
-
61
- if (error) {
62
- throw error
63
- }
64
-
68
+ const [error, returnValue] = getValidationResult(input, schema, inputName, opt)
69
+ if (error) throw error
65
70
  return returnValue
66
71
  }
67
72
 
@@ -75,27 +80,20 @@ export function validate<T>(
75
80
  * If `schema` is undefined - returns value as is.
76
81
  */
77
82
  export function getValidationResult<T>(
78
- input: any,
83
+ input: T,
79
84
  schema?: AnySchema<T>,
80
- objectName?: string,
85
+ inputName?: string,
81
86
  options: ValidationOptions = {},
82
- ): JoiValidationResult<T> {
83
- if (!schema) return { value: input }
87
+ ): ValidationFunctionResult<T, JoiValidationError> {
88
+ if (!schema) return [null, input]
84
89
 
85
90
  const { value, error } = schema.validate(input, {
86
91
  ...defaultOptions,
87
92
  ...options,
88
93
  })
89
94
 
90
- const vr: JoiValidationResult<T> = {
91
- value,
92
- }
93
-
94
- if (error) {
95
- vr.error = createError(input, error, objectName)
96
- }
97
-
98
- return vr
95
+ const err = error ? createError(input, error, inputName) : null
96
+ return [err, value]
99
97
  }
100
98
 
101
99
  /**
@@ -127,15 +125,15 @@ export function convert<T>(input: any, schema?: AnySchema<T>): T {
127
125
  return value
128
126
  }
129
127
 
130
- function createError(value: any, err: ValidationError, objectName?: string): JoiValidationError {
128
+ function createError(value: any, err: ValidationError, inputName?: string): JoiValidationError {
131
129
  const tokens: string[] = []
132
130
 
133
- const objectId = _isObject(value) ? (value['id'] as string) : undefined
131
+ const inputId = _isObject(value) ? (value['id'] as string) : undefined
134
132
 
135
- if (objectId || objectName) {
136
- objectName ||= value?.constructor?.name
133
+ if (inputId || inputName) {
134
+ inputName ||= value?.constructor?.name
137
135
 
138
- tokens.push('Invalid ' + [objectName, objectId].filter(Boolean).join('.'))
136
+ tokens.push('Invalid ' + [inputName, inputId].filter(Boolean).join('.'))
139
137
  }
140
138
 
141
139
  const annotation = err.annotate(stripColors)
@@ -168,8 +166,8 @@ function createError(value: any, err: ValidationError, objectName?: string): Joi
168
166
 
169
167
  const data: JoiValidationErrorData = {
170
168
  joiValidationErrorItems: err.details,
171
- ...(objectName && { joiValidationObjectName: objectName }),
172
- ...(objectId && { joiValidationObjectId: objectId }),
169
+ ...(inputName && { joiValidationInputName: inputName }),
170
+ ...(inputId && { joiValidationInputId: inputId }),
173
171
  }
174
172
 
175
173
  // Make annotation non-enumerable, to not get it automatically printed,
@@ -1,89 +0,0 @@
1
- const started = Date.now()
2
- import { workerData, parentPort } from 'node:worker_threads'
3
- import { inspect } from 'node:util'
4
- const { workerFile, workerIndex, logEvery = 1000, metric = 'worker' } = workerData || {}
5
-
6
- if (!workerFile) {
7
- throw new Error('workerData.workerFile is required!')
8
- }
9
-
10
- // console.log(`worker#${workerIndex} created`)
11
-
12
- try {
13
- // require('esbuild-register') // alternative
14
- // require('ts-node/register/transpile-only')
15
- // require('tsx/cjs/api').register() // https://tsx.is/dev-api/register-cjs
16
- const { register } = await import('tsx/esm/api')
17
- register() // https://tsx.is/dev-api/register-esm
18
- // require('tsconfig-paths/register')
19
- } catch {} // require if exists
20
-
21
- const { WorkerClass } = await import(workerFile)
22
- const worker = new WorkerClass(workerData)
23
-
24
- console.log(`${metric}#${workerIndex} loaded in ${Date.now() - started} ms`)
25
-
26
- let errors = 0
27
- let processed = 0
28
-
29
- parentPort.on('message', async msg => {
30
- if (msg === null) {
31
- // console.log(`EXIT (null) received by ${index}, exiting`)
32
- parentPort.close()
33
-
34
- logStats(true)
35
-
36
- return
37
- }
38
-
39
- // console.log(`message received by worker ${index}: `, msg)
40
-
41
- try {
42
- const out = await worker.process(msg.payload, msg.index)
43
-
44
- parentPort.postMessage({
45
- index: msg.index,
46
- payload: out,
47
- })
48
-
49
- processed++
50
-
51
- if (processed % logEvery === 0) logStats()
52
- } catch (err) {
53
- parentPort.postMessage({
54
- index: msg.index,
55
- error: err,
56
- })
57
-
58
- errors++
59
- console.log(`${metric}#${workerIndex} errors: ${errors}`)
60
- }
61
- })
62
-
63
- const inspectOpt = {
64
- colors: true,
65
- breakLength: 120,
66
- }
67
-
68
- function logStats(final) {
69
- const { rss, heapUsed, heapTotal, external } = process.memoryUsage()
70
-
71
- console.log(
72
- inspect(
73
- {
74
- [`${metric}${workerIndex}`]: processed,
75
- errors,
76
- heapUsed: mb(heapUsed),
77
- heapTotal: mb(heapTotal),
78
- rss: mb(rss),
79
- external: mb(external),
80
- ...(final ? { final: true } : {}),
81
- },
82
- inspectOpt,
83
- ),
84
- )
85
- }
86
-
87
- function mb(b) {
88
- return Math.round(b / (1024 * 1024))
89
- }