@naturalcycles/nodejs-lib 15.38.0 → 15.40.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,14 +1,13 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.38.0",
4
+ "version": "15.40.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
11
  "chalk": "^5",
13
12
  "dotenv": "^17",
14
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')
@@ -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>
@@ -1,7 +1,9 @@
1
1
  import { _lazyValue } from '@naturalcycles/js-lib'
2
- import type { Options } from 'ajv'
3
- import { _, Ajv } from 'ajv'
4
- import ajvFormats from 'ajv-formats'
2
+ import { Set2 } from '@naturalcycles/js-lib/object'
3
+ import { _substringAfterLast } from '@naturalcycles/js-lib/string'
4
+ import { _, Ajv, type Options, type ValidateFunction } from 'ajv'
5
+ import type { JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
6
+ import { validTLDs } from './tlds.js'
5
7
 
6
8
  /* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
7
9
  // oxlint-disable unicorn/prefer-code-point
@@ -51,15 +53,6 @@ export function createAjv(opt?: Options): Ajv {
51
53
  ...opt,
52
54
  })
53
55
 
54
- // Add custom formats
55
- addCustomAjvFormats(ajv)
56
-
57
- // todo: review and possibly cherry-pick/vendor the formats
58
- // Adds ajv "formats"
59
- // https://ajv.js.org/guide/formats.html
60
- // @ts-expect-error types are wrong
61
- ajvFormats(ajv)
62
-
63
56
  // Adds $merge, $patch keywords
64
57
  // https://github.com/ajv-validator/ajv-merge-patch
65
58
  // Kirill: temporarily disabled, as it creates a noise of CVE warnings
@@ -70,8 +63,9 @@ export function createAjv(opt?: Options): Ajv {
70
63
  type: 'string',
71
64
  modifying: true,
72
65
  schemaType: 'object',
73
- code(cxt) {
74
- const { gen, data, schema, it } = cxt
66
+ errors: true,
67
+ code(ctx) {
68
+ const { gen, data, schema, it } = ctx
75
69
  const { parentData, parentDataProperty } = it
76
70
 
77
71
  if (schema.trim) {
@@ -118,79 +112,197 @@ export function createAjv(opt?: Options): Ajv {
118
112
  },
119
113
  })
120
114
 
121
- return ajv
122
- }
115
+ ajv.addKeyword({
116
+ keyword: 'Set2',
117
+ type: ['array', 'object'],
118
+ modifying: true,
119
+ errors: true,
120
+ schemaType: 'object',
121
+ compile(innerSchema, _parentSchema, _it) {
122
+ const validateItem: ValidateFunction = ajv.compile(innerSchema)
123
+
124
+ function validateSet(data: any, ctx: any): boolean {
125
+ let set: Set2
126
+
127
+ const isIterable = data === null || typeof data[Symbol.iterator] === 'function'
128
+
129
+ if (data instanceof Set2) {
130
+ set = data
131
+ } else if (isIterable && ctx?.parentData) {
132
+ set = new Set2(data)
133
+ } else if (isIterable && !ctx?.parentData) {
134
+ ;(validateSet as any).errors = [
135
+ {
136
+ instancePath: ctx?.instancePath ?? '',
137
+ message:
138
+ 'can only transform an Iterable into a Set2 when the schema is in an object or an array schema. This is an Ajv limitation.',
139
+ },
140
+ ]
141
+ return false
142
+ } else {
143
+ ;(validateSet as any).errors = [
144
+ {
145
+ instancePath: ctx?.instancePath ?? '',
146
+ message: 'must be a Set2 object (or optionally an Iterable in some cases)',
147
+ },
148
+ ]
149
+ return false
150
+ }
123
151
 
124
- const TS_2500 = 16725225600 // 2500-01-01
125
- const TS_2500_MILLIS = TS_2500 * 1000
126
- const TS_2000 = 946684800 // 2000-01-01
127
- const TS_2000_MILLIS = TS_2000 * 1000
152
+ let idx = 0
153
+ for (const value of set.values()) {
154
+ if (!validateItem(value)) {
155
+ ;(validateSet as any).errors = [
156
+ {
157
+ instancePath: (ctx?.instancePath ?? '') + '/' + idx,
158
+ message: `invalid set item at index ${idx}`,
159
+ params: { errors: validateItem.errors },
160
+ },
161
+ ]
162
+ return false
163
+ }
164
+ idx++
165
+ }
128
166
 
129
- const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
167
+ if (ctx?.parentData && ctx.parentDataProperty) {
168
+ ctx.parentData[ctx.parentDataProperty] = set
169
+ }
130
170
 
131
- function addCustomAjvFormats(ajv: Ajv): Ajv {
132
- return (
133
- ajv
134
- .addFormat('id', /^[a-z0-9_]{6,64}$/)
135
- .addFormat('slug', /^[a-z0-9-]+$/)
136
- .addFormat('semVer', /^[0-9]+\.[0-9]+\.[0-9]+$/)
137
- // IETF language tag (https://en.wikipedia.org/wiki/IETF_language_tag)
138
- .addFormat('languageTag', /^[a-z]{2}(-[A-Z]{2})?$/)
139
- .addFormat('countryCode', /^[A-Z]{2}$/)
140
- .addFormat('currency', /^[A-Z]{3}$/)
141
- .addFormat('unixTimestamp', {
142
- type: 'number',
143
- validate: (n: number) => {
144
- return n >= 0 && n < TS_2500
145
- },
146
- })
147
- .addFormat('unixTimestamp2000', {
148
- type: 'number',
149
- validate: (n: number) => {
150
- return n >= TS_2000 && n < TS_2500
151
- },
152
- })
153
- .addFormat('unixTimestampMillis', {
154
- type: 'number',
155
- validate: (n: number) => {
156
- return n >= 0 && n < TS_2500_MILLIS
157
- },
158
- })
159
- .addFormat('unixTimestampMillis2000', {
160
- type: 'number',
161
- validate: (n: number) => {
162
- return n >= TS_2000_MILLIS && n < TS_2500_MILLIS
171
+ return true
172
+ }
173
+
174
+ return validateSet
175
+ },
176
+ })
177
+
178
+ ajv.addKeyword({
179
+ keyword: 'Buffer',
180
+ modifying: true,
181
+ errors: true,
182
+ schemaType: 'boolean',
183
+ compile(_innerSchema, _parentSchema, _it) {
184
+ function validateBuffer(data: any, ctx: any): boolean {
185
+ let buffer: Buffer
186
+
187
+ if (data === null) return false
188
+
189
+ const isValid =
190
+ data instanceof Buffer ||
191
+ data instanceof ArrayBuffer ||
192
+ Array.isArray(data) ||
193
+ typeof data === 'string'
194
+ if (!isValid) return false
195
+
196
+ if (data instanceof Buffer) {
197
+ buffer = data
198
+ } else if (isValid && ctx?.parentData) {
199
+ buffer = Buffer.from(data as any)
200
+ } else if (isValid && !ctx?.parentData) {
201
+ ;(validateBuffer as any).errors = [
202
+ {
203
+ instancePath: ctx?.instancePath ?? '',
204
+ message:
205
+ 'can only transform data into a Buffer when the schema is in an object or an array schema. This is an Ajv limitation.',
206
+ },
207
+ ]
208
+ return false
209
+ } else {
210
+ ;(validateBuffer as any).errors = [
211
+ {
212
+ instancePath: ctx?.instancePath ?? '',
213
+ message:
214
+ 'must be a Buffer object (or optionally an Array-like object or ArrayBuffer in some cases)',
215
+ },
216
+ ]
217
+ return false
218
+ }
219
+
220
+ if (ctx?.parentData && ctx.parentDataProperty) {
221
+ ctx.parentData[ctx.parentDataProperty] = buffer
222
+ }
223
+
224
+ return true
225
+ }
226
+
227
+ return validateBuffer
228
+ },
229
+ })
230
+
231
+ ajv.addKeyword({
232
+ keyword: 'email',
233
+ type: 'string',
234
+ modifying: false,
235
+ errors: true,
236
+ schemaType: 'object',
237
+ validate: function validate(opt: JsonSchemaStringEmailOptions, data: string, _schema, ctx) {
238
+ const { checkTLD } = opt
239
+ if (!checkTLD) return true
240
+
241
+ const tld = _substringAfterLast(data, '.')
242
+ if (validTLDs.has(tld)) return true
243
+ ;(validate as any).errors = [
244
+ {
245
+ instancePath: ctx?.instancePath ?? '',
246
+ message: `has an invalid TLD`,
163
247
  },
164
- })
165
- .addFormat('utcOffset', {
166
- type: 'number',
167
- validate: (n: number) => {
168
- // min: -14 hours
169
- // max +14 hours
170
- // multipleOf 15 (minutes)
171
- return n >= -14 * 60 && n <= 14 * 60 && Number.isInteger(n)
248
+ ]
249
+ return false
250
+ },
251
+ })
252
+
253
+ ajv.addKeyword({
254
+ keyword: 'IsoDate',
255
+ type: 'string',
256
+ modifying: false,
257
+ errors: true,
258
+ schemaType: 'boolean',
259
+ validate: function validate(_opt: true, data: string, _schema, ctx) {
260
+ const isValid = isIsoDateValid(data)
261
+ if (isValid) return true
262
+ ;(validate as any).errors = [
263
+ {
264
+ instancePath: ctx?.instancePath ?? '',
265
+ message: `is an invalid IsoDate`,
172
266
  },
173
- })
174
- .addFormat('utcOffsetHours', {
175
- type: 'number',
176
- validate: (n: number) => {
177
- // min: -14 hours
178
- // max +14 hours
179
- // multipleOf 15 (minutes)
180
- return n >= -14 && n <= 14 && Number.isInteger(n)
267
+ ]
268
+ return false
269
+ },
270
+ })
271
+
272
+ ajv.addKeyword({
273
+ keyword: 'IsoDateTime',
274
+ type: 'string',
275
+ modifying: false,
276
+ errors: true,
277
+ schemaType: 'boolean',
278
+ validate: function validate(_opt: true, data: string, _schema, ctx) {
279
+ const isValid = isIsoDateTimeValid(data)
280
+ if (isValid) return true
281
+ ;(validate as any).errors = [
282
+ {
283
+ instancePath: ctx?.instancePath ?? '',
284
+ message: `is an invalid IsoDateTime`,
181
285
  },
182
- })
183
- .addFormat('IsoDate', {
184
- type: 'string',
185
- validate: isIsoDateValid,
186
- })
187
- .addFormat('IsoDateTime', {
188
- type: 'string',
189
- validate: isIsoDateTimeValid,
190
- })
191
- )
286
+ ]
287
+ return false
288
+ },
289
+ })
290
+
291
+ ajv.addKeyword({
292
+ keyword: 'errorMessages',
293
+ schemaType: 'object',
294
+ })
295
+
296
+ ajv.addKeyword({
297
+ keyword: 'hasIsOfTypeCheck',
298
+ schemaType: 'boolean',
299
+ })
300
+
301
+ return ajv
192
302
  }
193
303
 
304
+ const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
305
+
194
306
  const DASH_CODE = '-'.charCodeAt(0)
195
307
  const ZERO_CODE = '0'.charCodeAt(0)
196
308
  const PLUS_CODE = '+'.charCodeAt(0)