@naturalcycles/nodejs-lib 15.37.2 → 15.39.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,7 +1,7 @@
1
1
  import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
2
2
  import type { AnySchema, ValidationOptions } from 'joi';
3
3
  import { JoiValidationError } from './joi.validation.error.js';
4
- export declare function getJoiValidationFunction<T>(schema: AnySchema<T>): ValidationFunction<T, JoiValidationError>;
4
+ export declare function getJoiValidationFunction<T>(schema: AnySchema<T>): ValidationFunction<T, T, JoiValidationError>;
5
5
  /**
6
6
  * Validates with Joi.
7
7
  * Throws JoiValidationError if invalid.
package/package.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.37.2",
4
+ "version": "15.39.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
8
8
  "@types/jsonwebtoken": "^9",
9
9
  "@types/yargs": "^16",
10
10
  "ajv": "^8",
11
- "ajv-formats": "^3",
12
- "ajv-keywords": "^5",
13
11
  "chalk": "^5",
14
12
  "dotenv": "^17",
15
13
  "joi": "^18",
@@ -2,7 +2,7 @@ import { commonLoggerCreate } from '@naturalcycles/js-lib/log'
2
2
  import { _inspect } from '../string/inspect.js'
3
3
 
4
4
  /**
5
- * CommonLogger that logs to process.stdout directly (bypassing console.log).
5
+ * CommonLogger that logs to process.stdout directly (bypassing console.log)!
6
6
  */
7
7
  export const stdoutLogger = commonLoggerCreate((_level, args) => {
8
8
  process.stdout.write(args.map(a => _inspect(a)).join(' ') + '\n')
@@ -5,7 +5,7 @@ https://github.com/ai/nanoid/
5
5
 
6
6
  */
7
7
 
8
- /* eslint-disable */
8
+ // oxlint-disable no-bitwise -- NanoID implementation relies on bitwise operations
9
9
 
10
10
  import { randomFillSync } from 'node:crypto'
11
11
 
@@ -4,72 +4,25 @@ import {
4
4
  type ValidationFunction,
5
5
  type ValidationFunctionResult,
6
6
  } from '@naturalcycles/js-lib'
7
- import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema'
8
- import { JsonSchemaAnyBuilder } from '@naturalcycles/js-lib/json-schema'
7
+ import { _assert } from '@naturalcycles/js-lib/error'
9
8
  import { _deepCopy, _filterNullishValues } from '@naturalcycles/js-lib/object'
10
9
  import { _substringBefore } from '@naturalcycles/js-lib/string'
11
- import type { AnyObject } from '@naturalcycles/js-lib/types'
12
- import { z, ZodType } from '@naturalcycles/js-lib/zod'
13
- import type { Ajv } from 'ajv'
10
+ import { _typeCast, type AnyObject } from '@naturalcycles/js-lib/types'
11
+ import type { ZodType } from '@naturalcycles/js-lib/zod'
12
+ import { z } from '@naturalcycles/js-lib/zod'
13
+ import type { Ajv, ErrorObject } from 'ajv'
14
14
  import { _inspect } from '../../string/inspect.js'
15
15
  import { AjvValidationError } from './ajvValidationError.js'
16
16
  import { getAjv } from './getAjv.js'
17
-
18
- export type SchemaHandledByAjv<T> = JsonSchemaBuilder<T> | JsonSchema<T> | AjvSchema<T> | ZodType<T>
19
-
20
- export interface AjvValidationOptions {
21
- /**
22
- * Defaults to true,
23
- * because that's how AJV works by default,
24
- * and what gives it performance advantage.
25
- * (Because we have found that deep-clone is surprisingly slow,
26
- * nearly as slow as Joi validation).
27
- *
28
- * If set to true - AJV will mutate the input in case it needs to apply transformations
29
- * (strip unknown properties, convert types, etc).
30
- *
31
- * If false - it will deep-clone (using JSON.stringify+parse) the input to prevent its mutation.
32
- * Will return the cloned/mutated object.
33
- * Please note that JSON.stringify+parse has side-effects,
34
- * e.g it will transform Buffer into a weird object.
35
- */
36
- mutateInput?: boolean
37
- inputName?: string
38
- inputId?: string
39
- }
40
-
41
- export interface AjvSchemaCfg {
42
- /**
43
- * Pass Ajv instance, otherwise Ajv will be created with
44
- * AjvSchema default (not the same as Ajv defaults) parameters
45
- */
46
- ajv: Ajv
47
-
48
- inputName?: string
49
-
50
- /**
51
- * Option of Ajv.
52
- * If set to true - will mutate your input objects!
53
- * Defaults to false.
54
- *
55
- * This option is a "shortcut" to skip creating and passing Ajv instance.
56
- */
57
- // coerceTypes?: boolean
58
-
59
- /**
60
- * If true - schema will be compiled on-demand (lazily).
61
- * Default: false.
62
- */
63
- lazy?: boolean
64
- }
17
+ import { type JsonSchema, JsonSchemaAnyBuilder } from './jsonSchemaBuilder.js'
65
18
 
66
19
  /**
67
20
  * On creation - compiles ajv validation function.
68
21
  * Provides convenient methods, error reporting, etc.
69
22
  */
70
- export class AjvSchema<T = unknown> {
23
+ export class AjvSchema<IN = unknown, OUT = IN> {
71
24
  private constructor(
72
- public schema: JsonSchema<T>,
25
+ public schema: JsonSchema<IN, OUT>,
73
26
  cfg: Partial<AjvSchemaCfg> = {},
74
27
  ) {
75
28
  this.cfg = {
@@ -88,10 +41,10 @@ export class AjvSchema<T = unknown> {
88
41
  /**
89
42
  * Shortcut for AjvSchema.create(schema, { lazy: true })
90
43
  */
91
- static createLazy<T>(
92
- schema: JsonSchemaBuilder<T> | JsonSchema<T> | AjvSchema<T>,
44
+ static createLazy<IN, OUT>(
45
+ schema: SchemaHandledByAjv<IN, OUT>,
93
46
  cfg?: Partial<AjvSchemaCfg>,
94
- ): AjvSchema<T> {
47
+ ): AjvSchema<IN, OUT> {
95
48
  return AjvSchema.create(schema, {
96
49
  lazy: true,
97
50
  ...cfg,
@@ -107,46 +60,50 @@ export class AjvSchema<T = unknown> {
107
60
  * Implementation note: JsonSchemaBuilder goes first in the union type, otherwise TypeScript fails to infer <T> type
108
61
  * correctly for some reason.
109
62
  */
110
- static create<T>(schema: SchemaHandledByAjv<T>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<T> {
63
+ static create<IN, OUT = IN>(
64
+ schema: SchemaHandledByAjv<IN, OUT>,
65
+ cfg?: Partial<AjvSchemaCfg>,
66
+ ): AjvSchema<IN, OUT> {
111
67
  if (schema instanceof AjvSchema) return schema
112
68
 
113
- if (AjvSchema.isSchemaWithCachedAjvSchema<typeof schema, T>(schema)) {
114
- return AjvSchema.requireCachedAjvSchema<typeof schema, T>(schema)
69
+ if (AjvSchema.isSchemaWithCachedAjvSchema<typeof schema, IN, OUT>(schema)) {
70
+ return AjvSchema.requireCachedAjvSchema<typeof schema, IN, OUT>(schema)
115
71
  }
116
72
 
117
- let jsonSchema: JsonSchema<T>
73
+ let jsonSchema: JsonSchema<IN, OUT>
118
74
 
119
75
  if (AjvSchema.isJsonSchemaBuilder(schema)) {
120
- jsonSchema = schema.build()
121
- } else if (AjvSchema.isZodSchema(schema)) {
122
- jsonSchema = z.toJSONSchema(schema, { target: 'draft-7' }) as JsonSchema<T>
76
+ jsonSchema = (schema as JsonSchemaAnyBuilder<IN, OUT, any>).build()
77
+ AjvSchema.requireValidJsonSchema(jsonSchema)
123
78
  } else {
124
- jsonSchema = schema
79
+ jsonSchema = schema as JsonSchema<IN, OUT>
125
80
  }
126
81
 
127
- const ajvSchema = new AjvSchema<T>(jsonSchema, cfg)
82
+ const ajvSchema = new AjvSchema<IN, OUT>(jsonSchema, cfg)
128
83
  AjvSchema.cacheAjvSchema(schema, ajvSchema)
129
84
 
130
85
  return ajvSchema
131
86
  }
132
87
 
133
88
  /**
134
- * @deprecated
135
- *
136
- * Use `AjvSchema.create`
89
+ * @deprecated Use `j` to build schemas, not `z` or `zod`.
137
90
  */
138
- static createFromZod<T>(zodSchema: ZodType<T>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<T> {
139
- return AjvSchema.create(zodSchema, cfg)
91
+ static createFromZod<T extends ZodType<any, any, any>>(
92
+ schema: T,
93
+ ): AjvSchema<T['_input'], T['_output']> {
94
+ const jsonSchema = z.toJSONSchema(schema, {
95
+ target: 'draft-7',
96
+ }) as unknown as JsonSchema<T['_input'], T['_output']>
97
+
98
+ return AjvSchema.create(jsonSchema)
140
99
  }
141
100
 
142
- static isJsonSchemaBuilder<T>(schema: unknown): schema is JsonSchemaBuilder<T> {
101
+ static isJsonSchemaBuilder<IN, OUT>(
102
+ schema: unknown,
103
+ ): schema is JsonSchemaAnyBuilder<IN, OUT, any> {
143
104
  return schema instanceof JsonSchemaAnyBuilder
144
105
  }
145
106
 
146
- static isZodSchema<T>(schema: unknown): schema is ZodType<T> {
147
- return schema instanceof ZodType
148
- }
149
-
150
107
  readonly cfg: AjvSchemaCfg
151
108
 
152
109
  /**
@@ -157,13 +114,13 @@ export class AjvSchema<T = unknown> {
157
114
  *
158
115
  * Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
159
116
  */
160
- validate(input: T, opt: AjvValidationOptions = {}): T {
117
+ validate(input: IN, opt: AjvValidationOptions = {}): OUT {
161
118
  const [err, output] = this.getValidationResult(input, opt)
162
119
  if (err) throw err
163
120
  return output
164
121
  }
165
122
 
166
- isValid(input: T, opt?: AjvValidationOptions): boolean {
123
+ isValid(input: IN, opt?: AjvValidationOptions): boolean {
167
124
  // todo: we can make it both fast and non-mutating by using Ajv
168
125
  // with "removeAdditional" and "useDefaults" disabled.
169
126
  const [err] = this.getValidationResult(input, opt)
@@ -171,9 +128,9 @@ export class AjvSchema<T = unknown> {
171
128
  }
172
129
 
173
130
  getValidationResult(
174
- input: T,
131
+ input: IN,
175
132
  opt: AjvValidationOptions = {},
176
- ): ValidationFunctionResult<T, AjvValidationError> {
133
+ ): ValidationFunctionResult<OUT, AjvValidationError> {
177
134
  const fn = this.getAJVValidateFunction()
178
135
 
179
136
  const item =
@@ -182,16 +139,19 @@ export class AjvSchema<T = unknown> {
182
139
  : _deepCopy(input) // not mutate
183
140
 
184
141
  const valid = fn(item) // mutates item
142
+ _typeCast<OUT>(item)
185
143
  if (valid) return [null, item]
186
144
 
187
145
  const errors = fn.errors!
188
146
 
189
147
  const {
190
- inputId = _isObject(input) ? (input['id' as keyof T] as any) : undefined,
148
+ inputId = _isObject(input) ? input['id' as keyof IN] : undefined,
191
149
  inputName = this.cfg.inputName || 'Object',
192
150
  } = opt
193
151
  const dataVar = [inputName, inputId].filter(Boolean).join('.')
194
152
 
153
+ this.applyImprovementsOnErrorMessages(errors)
154
+
195
155
  let message = this.cfg.ajv.errorsText(errors, {
196
156
  dataVar,
197
157
  separator,
@@ -213,7 +173,7 @@ export class AjvSchema<T = unknown> {
213
173
  return [err, item]
214
174
  }
215
175
 
216
- getValidationFunction(): ValidationFunction<T, AjvValidationError> {
176
+ getValidationFunction(): ValidationFunction<IN, OUT, AjvValidationError> {
217
177
  return (input, opt) => {
218
178
  return this.getValidationResult(input, {
219
179
  mutateInput: opt?.mutateInput,
@@ -223,30 +183,109 @@ export class AjvSchema<T = unknown> {
223
183
  }
224
184
  }
225
185
 
226
- static isSchemaWithCachedAjvSchema<Base, T>(
186
+ static isSchemaWithCachedAjvSchema<Base, IN, OUT>(
227
187
  schema: Base,
228
- ): schema is WithCachedAjvSchema<Base, T> {
188
+ ): schema is WithCachedAjvSchema<Base, IN, OUT> {
229
189
  return !!(schema as any)?.[HIDDEN_AJV_SCHEMA]
230
190
  }
231
191
 
232
- static cacheAjvSchema<Base extends AnyObject, T>(
192
+ static cacheAjvSchema<Base extends AnyObject, IN, OUT>(
233
193
  schema: Base,
234
- ajvSchema: AjvSchema<T>,
235
- ): WithCachedAjvSchema<Base, T> {
194
+ ajvSchema: AjvSchema<IN, OUT>,
195
+ ): WithCachedAjvSchema<Base, IN, OUT> {
236
196
  return Object.assign(schema, { [HIDDEN_AJV_SCHEMA]: ajvSchema })
237
197
  }
238
198
 
239
- static requireCachedAjvSchema<Base, T>(schema: WithCachedAjvSchema<Base, T>): AjvSchema<T> {
199
+ static requireCachedAjvSchema<Base, IN, OUT>(
200
+ schema: WithCachedAjvSchema<Base, IN, OUT>,
201
+ ): AjvSchema<IN, OUT> {
240
202
  return schema[HIDDEN_AJV_SCHEMA]
241
203
  }
242
204
 
243
- private getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile<T>(this.schema))
205
+ private getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile(this.schema as any))
206
+
207
+ private static requireValidJsonSchema(schema: JsonSchema): void {
208
+ // For object schemas we require that it is type checked against an external type, e.g.:
209
+ // interface Foo { name: string }
210
+ // const schema = j.object({ name: j.string() }).ofType<Foo>()
211
+ _assert(
212
+ schema.type !== 'object' || schema.hasIsOfTypeCheck,
213
+ 'The schema must be type checked against a type or interface, using the `.isOfType()` helper in `j`.',
214
+ )
215
+ }
216
+
217
+ private applyImprovementsOnErrorMessages(
218
+ errors: ErrorObject<string, Record<string, any>, unknown>[] | null | undefined,
219
+ ): void {
220
+ if (!errors) return
221
+
222
+ const { errorMessages } = this.schema
223
+
224
+ for (const error of errors) {
225
+ if (errorMessages?.[error.keyword]) {
226
+ error.message = errorMessages[error.keyword]
227
+ }
228
+
229
+ error.instancePath = error.instancePath.replaceAll(/\/(\d+)/g, `[$1]`).replaceAll('/', '.')
230
+ }
231
+ }
244
232
  }
245
233
 
246
234
  const separator = '\n'
247
235
 
248
236
  export const HIDDEN_AJV_SCHEMA = Symbol('HIDDEN_AJV_SCHEMA')
249
237
 
250
- export type WithCachedAjvSchema<Base, T> = Base & {
251
- [HIDDEN_AJV_SCHEMA]: AjvSchema<T>
238
+ export type WithCachedAjvSchema<Base, IN, OUT> = Base & {
239
+ [HIDDEN_AJV_SCHEMA]: AjvSchema<IN, OUT>
240
+ }
241
+
242
+ export interface AjvValidationOptions {
243
+ /**
244
+ * Defaults to true,
245
+ * because that's how AJV works by default,
246
+ * and what gives it performance advantage.
247
+ * (Because we have found that deep-clone is surprisingly slow,
248
+ * nearly as slow as Joi validation).
249
+ *
250
+ * If set to true - AJV will mutate the input in case it needs to apply transformations
251
+ * (strip unknown properties, convert types, etc).
252
+ *
253
+ * If false - it will deep-clone (using JSON.stringify+parse) the input to prevent its mutation.
254
+ * Will return the cloned/mutated object.
255
+ * Please note that JSON.stringify+parse has side-effects,
256
+ * e.g it will transform Buffer into a weird object.
257
+ */
258
+ mutateInput?: boolean
259
+ inputName?: string
260
+ inputId?: string
261
+ }
262
+
263
+ export interface AjvSchemaCfg {
264
+ /**
265
+ * Pass Ajv instance, otherwise Ajv will be created with
266
+ * AjvSchema default (not the same as Ajv defaults) parameters
267
+ */
268
+ ajv: Ajv
269
+
270
+ inputName?: string
271
+
272
+ /**
273
+ * Option of Ajv.
274
+ * If set to true - will mutate your input objects!
275
+ * Defaults to false.
276
+ *
277
+ * This option is a "shortcut" to skip creating and passing Ajv instance.
278
+ */
279
+ // coerceTypes?: boolean
280
+
281
+ /**
282
+ * If true - schema will be compiled on-demand (lazily).
283
+ * Default: false.
284
+ */
285
+ lazy?: boolean
252
286
  }
287
+
288
+ export type SchemaHandledByAjv<IN, OUT = IN> =
289
+ | JsonSchemaAnyBuilder<IN, OUT, any>
290
+ | JsonSchema<IN, OUT>
291
+ | AjvSchema<IN, OUT>