@naturalcycles/nodejs-lib 15.91.0 → 15.92.1
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.
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getEnumType,
|
|
12
12
|
} from '@naturalcycles/js-lib'
|
|
13
13
|
import { _uniq } from '@naturalcycles/js-lib/array'
|
|
14
|
-
import { _assert } from '@naturalcycles/js-lib/error'
|
|
14
|
+
import { _assert, _try } from '@naturalcycles/js-lib/error'
|
|
15
15
|
import type { Set2 } from '@naturalcycles/js-lib/object'
|
|
16
16
|
import { _deepCopy, _filterNullishValues, _sortObject } from '@naturalcycles/js-lib/object'
|
|
17
17
|
import { _substringBefore } from '@naturalcycles/js-lib/string'
|
|
@@ -61,9 +61,9 @@ import {
|
|
|
61
61
|
* On creation - compiles ajv validation function.
|
|
62
62
|
* Provides convenient methods, error reporting, etc.
|
|
63
63
|
*/
|
|
64
|
-
export class AjvSchema<
|
|
64
|
+
export class AjvSchema<OUT> {
|
|
65
65
|
private constructor(
|
|
66
|
-
public schema: JsonSchema<
|
|
66
|
+
public schema: JsonSchema<OUT>,
|
|
67
67
|
cfg: Partial<AjvSchemaCfg> = {},
|
|
68
68
|
) {
|
|
69
69
|
this.cfg = {
|
|
@@ -82,10 +82,10 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
82
82
|
/**
|
|
83
83
|
* Shortcut for AjvSchema.create(schema, { lazy: true })
|
|
84
84
|
*/
|
|
85
|
-
static createLazy<
|
|
86
|
-
schema: SchemaHandledByAjv<
|
|
85
|
+
static createLazy<OUT>(
|
|
86
|
+
schema: SchemaHandledByAjv<OUT>,
|
|
87
87
|
cfg?: Partial<AjvSchemaCfg>,
|
|
88
|
-
): AjvSchema<
|
|
88
|
+
): AjvSchema<OUT> {
|
|
89
89
|
return AjvSchema.create(schema, {
|
|
90
90
|
lazy: true,
|
|
91
91
|
...cfg,
|
|
@@ -101,21 +101,18 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
101
101
|
* Implementation note: JsonSchemaBuilder goes first in the union type, otherwise TypeScript fails to infer <T> type
|
|
102
102
|
* correctly for some reason.
|
|
103
103
|
*/
|
|
104
|
-
static create<
|
|
105
|
-
schema: SchemaHandledByAjv<IN, OUT>,
|
|
106
|
-
cfg?: Partial<AjvSchemaCfg>,
|
|
107
|
-
): AjvSchema<IN, OUT> {
|
|
104
|
+
static create<OUT>(schema: SchemaHandledByAjv<OUT>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<OUT> {
|
|
108
105
|
if (schema instanceof AjvSchema) return schema
|
|
109
106
|
|
|
110
|
-
if (AjvSchema.isSchemaWithCachedAjvSchema<typeof schema,
|
|
111
|
-
return AjvSchema.requireCachedAjvSchema<typeof schema,
|
|
107
|
+
if (AjvSchema.isSchemaWithCachedAjvSchema<typeof schema, OUT>(schema)) {
|
|
108
|
+
return AjvSchema.requireCachedAjvSchema<typeof schema, OUT>(schema)
|
|
112
109
|
}
|
|
113
110
|
|
|
114
|
-
let jsonSchema: JsonSchema<
|
|
111
|
+
let jsonSchema: JsonSchema<OUT>
|
|
115
112
|
|
|
116
113
|
if (AjvSchema.isJsonSchemaBuilder(schema)) {
|
|
117
114
|
// oxlint-disable typescript-eslint(no-unnecessary-type-assertion)
|
|
118
|
-
jsonSchema = (schema as JsonSchemaTerminal<
|
|
115
|
+
jsonSchema = (schema as JsonSchemaTerminal<OUT, any>).build()
|
|
119
116
|
AjvSchema.requireValidJsonSchema(jsonSchema)
|
|
120
117
|
} else {
|
|
121
118
|
jsonSchema = schema
|
|
@@ -128,13 +125,13 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
128
125
|
// really upsets Ajv.
|
|
129
126
|
delete jsonSchema.optionalField
|
|
130
127
|
|
|
131
|
-
const ajvSchema = new AjvSchema<
|
|
128
|
+
const ajvSchema = new AjvSchema<OUT>(jsonSchema, cfg)
|
|
132
129
|
AjvSchema.cacheAjvSchema(schema, ajvSchema)
|
|
133
130
|
|
|
134
131
|
return ajvSchema
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
static isJsonSchemaBuilder<
|
|
134
|
+
static isJsonSchemaBuilder<OUT>(schema: unknown): schema is JsonSchemaTerminal<OUT, any> {
|
|
138
135
|
return schema instanceof JsonSchemaTerminal
|
|
139
136
|
}
|
|
140
137
|
|
|
@@ -148,13 +145,13 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
148
145
|
*
|
|
149
146
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
150
147
|
*/
|
|
151
|
-
validate(input:
|
|
148
|
+
validate(input: unknown, opt: AjvValidationOptions = {}): OUT {
|
|
152
149
|
const [err, output] = this.getValidationResult(input, opt)
|
|
153
150
|
if (err) throw err
|
|
154
151
|
return output
|
|
155
152
|
}
|
|
156
153
|
|
|
157
|
-
isValid(input:
|
|
154
|
+
isValid(input: unknown, opt?: AjvValidationOptions): boolean {
|
|
158
155
|
// todo: we can make it both fast and non-mutating by using Ajv
|
|
159
156
|
// with "removeAdditional" and "useDefaults" disabled.
|
|
160
157
|
const [err] = this.getValidationResult(input, opt)
|
|
@@ -162,8 +159,8 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
162
159
|
}
|
|
163
160
|
|
|
164
161
|
getValidationResult(
|
|
165
|
-
input:
|
|
166
|
-
opt: AjvValidationOptions
|
|
162
|
+
input: unknown,
|
|
163
|
+
opt: AjvValidationOptions = {},
|
|
167
164
|
): ValidationFunctionResult<OUT, AjvValidationError> {
|
|
168
165
|
const fn = this.getAJVValidateFunction()
|
|
169
166
|
|
|
@@ -172,14 +169,31 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
172
169
|
? input // mutate
|
|
173
170
|
: _deepCopy(input) // not mutate
|
|
174
171
|
|
|
175
|
-
|
|
172
|
+
let valid = fn(item) // mutates item, but not input
|
|
176
173
|
_typeCast<OUT>(item)
|
|
177
|
-
|
|
174
|
+
|
|
175
|
+
let output: OUT = item
|
|
176
|
+
if (valid && this.schema.postValidation) {
|
|
177
|
+
const [err, result] = _try(() => this.schema.postValidation!(output))
|
|
178
|
+
if (err) {
|
|
179
|
+
valid = false
|
|
180
|
+
;(fn as any).errors = [
|
|
181
|
+
{
|
|
182
|
+
instancePath: '',
|
|
183
|
+
message: err.message,
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
} else {
|
|
187
|
+
output = result
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (valid) return [null, output]
|
|
178
192
|
|
|
179
193
|
const errors = fn.errors!
|
|
180
194
|
|
|
181
195
|
const {
|
|
182
|
-
inputId = _isObject(input) ? input['id'
|
|
196
|
+
inputId = _isObject(input) ? (input as any)['id'] : undefined,
|
|
183
197
|
inputName = this.cfg.inputName || 'Object',
|
|
184
198
|
} = opt
|
|
185
199
|
const dataVar = [inputName, inputId].filter(Boolean).join('.')
|
|
@@ -205,10 +219,10 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
205
219
|
inputId,
|
|
206
220
|
}),
|
|
207
221
|
)
|
|
208
|
-
return [err,
|
|
222
|
+
return [err, output]
|
|
209
223
|
}
|
|
210
224
|
|
|
211
|
-
getValidationFunction(): ValidationFunction<
|
|
225
|
+
getValidationFunction(): ValidationFunction<OUT, AjvValidationError> {
|
|
212
226
|
return (input, opt) => {
|
|
213
227
|
return this.getValidationResult(input, {
|
|
214
228
|
mutateInput: opt?.mutateInput,
|
|
@@ -218,22 +232,20 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
218
232
|
}
|
|
219
233
|
}
|
|
220
234
|
|
|
221
|
-
static isSchemaWithCachedAjvSchema<Base,
|
|
235
|
+
static isSchemaWithCachedAjvSchema<Base, OUT>(
|
|
222
236
|
schema: Base,
|
|
223
|
-
): schema is WithCachedAjvSchema<Base,
|
|
237
|
+
): schema is WithCachedAjvSchema<Base, OUT> {
|
|
224
238
|
return !!(schema as any)?.[HIDDEN_AJV_SCHEMA]
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
static cacheAjvSchema<Base extends AnyObject,
|
|
241
|
+
static cacheAjvSchema<Base extends AnyObject, OUT>(
|
|
228
242
|
schema: Base,
|
|
229
|
-
ajvSchema: AjvSchema<
|
|
230
|
-
): WithCachedAjvSchema<Base,
|
|
243
|
+
ajvSchema: AjvSchema<OUT>,
|
|
244
|
+
): WithCachedAjvSchema<Base, OUT> {
|
|
231
245
|
return Object.assign(schema, { [HIDDEN_AJV_SCHEMA]: ajvSchema })
|
|
232
246
|
}
|
|
233
247
|
|
|
234
|
-
static requireCachedAjvSchema<Base,
|
|
235
|
-
schema: WithCachedAjvSchema<Base, IN, OUT>,
|
|
236
|
-
): AjvSchema<IN, OUT> {
|
|
248
|
+
static requireCachedAjvSchema<Base, OUT>(schema: WithCachedAjvSchema<Base, OUT>): AjvSchema<OUT> {
|
|
237
249
|
return schema[HIDDEN_AJV_SCHEMA]
|
|
238
250
|
}
|
|
239
251
|
|
|
@@ -254,6 +266,8 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
254
266
|
): void {
|
|
255
267
|
if (!errors) return
|
|
256
268
|
|
|
269
|
+
this.filterNullableAnyOfErrors(errors)
|
|
270
|
+
|
|
257
271
|
const { errorMessages } = this.schema
|
|
258
272
|
|
|
259
273
|
for (const error of errors) {
|
|
@@ -267,14 +281,75 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
267
281
|
error.message = errorMessage
|
|
268
282
|
} else if (errorMessages?.[error.keyword]) {
|
|
269
283
|
error.message = errorMessages[error.keyword]
|
|
284
|
+
} else {
|
|
285
|
+
const unwrapped = unwrapNullableAnyOf(this.schema)
|
|
286
|
+
if (unwrapped?.errorMessages?.[error.keyword]) {
|
|
287
|
+
error.message = unwrapped.errorMessages[error.keyword]
|
|
288
|
+
}
|
|
270
289
|
}
|
|
271
290
|
|
|
272
291
|
error.instancePath = error.instancePath.replaceAll(/\/(\d+)/g, `[$1]`).replaceAll('/', '.')
|
|
273
292
|
}
|
|
274
293
|
}
|
|
275
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Filters out noisy errors produced by nullable anyOf patterns.
|
|
297
|
+
* When `nullable()` wraps a schema in `anyOf: [realSchema, { type: 'null' }]`,
|
|
298
|
+
* AJV produces "must be null" and "must match a schema in anyOf" errors
|
|
299
|
+
* that are confusing. This method splices them out, keeping only the real errors.
|
|
300
|
+
*/
|
|
301
|
+
private filterNullableAnyOfErrors(
|
|
302
|
+
errors: ErrorObject<string, Record<string, any>, unknown>[],
|
|
303
|
+
): void {
|
|
304
|
+
// Collect exact schemaPaths to remove (anyOf aggregates) and prefixes (null branches)
|
|
305
|
+
const exactPaths: string[] = []
|
|
306
|
+
const nullBranchPrefixes: string[] = []
|
|
307
|
+
|
|
308
|
+
for (const error of errors) {
|
|
309
|
+
if (error.keyword !== 'anyOf') continue
|
|
310
|
+
|
|
311
|
+
const parentSchema = this.resolveSchemaPath(error.schemaPath)
|
|
312
|
+
if (!parentSchema) continue
|
|
313
|
+
|
|
314
|
+
const nullIndex = unwrapNullableAnyOfIndex(parentSchema)
|
|
315
|
+
if (nullIndex === -1) continue
|
|
316
|
+
|
|
317
|
+
exactPaths.push(error.schemaPath) // e.g. "#/anyOf"
|
|
318
|
+
const anyOfBase = error.schemaPath.slice(0, -'anyOf'.length)
|
|
319
|
+
nullBranchPrefixes.push(`${anyOfBase}anyOf/${nullIndex}/`) // e.g. "#/anyOf/1/"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!exactPaths.length) return
|
|
323
|
+
|
|
324
|
+
for (let i = errors.length - 1; i >= 0; i--) {
|
|
325
|
+
const sp = errors[i]!.schemaPath
|
|
326
|
+
if (exactPaths.includes(sp) || nullBranchPrefixes.some(p => sp.startsWith(p))) {
|
|
327
|
+
errors.splice(i, 1)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Navigates the schema tree using an AJV schemaPath (e.g. "#/properties/foo/anyOf")
|
|
334
|
+
* and returns the parent schema containing the last keyword.
|
|
335
|
+
*/
|
|
336
|
+
private resolveSchemaPath(schemaPath: string): JsonSchema | undefined {
|
|
337
|
+
// schemaPath looks like "#/properties/foo/anyOf" or "#/anyOf"
|
|
338
|
+
// We want the schema that contains the final keyword (e.g. "anyOf")
|
|
339
|
+
const segments = schemaPath.replace(/^#\//, '').split('/')
|
|
340
|
+
// Remove the last segment (the keyword itself, e.g. "anyOf")
|
|
341
|
+
segments.pop()
|
|
342
|
+
|
|
343
|
+
let current: any = this.schema
|
|
344
|
+
for (const segment of segments) {
|
|
345
|
+
if (!current || typeof current !== 'object') return undefined
|
|
346
|
+
current = current[segment]
|
|
347
|
+
}
|
|
348
|
+
return current as JsonSchema | undefined
|
|
349
|
+
}
|
|
350
|
+
|
|
276
351
|
private getErrorMessageForInstancePath(
|
|
277
|
-
schema: JsonSchema<
|
|
352
|
+
schema: JsonSchema<OUT> | undefined,
|
|
278
353
|
instancePath: string,
|
|
279
354
|
keyword: string,
|
|
280
355
|
): string | undefined {
|
|
@@ -284,8 +359,8 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
284
359
|
return this.traverseSchemaPath(schema, segments, keyword)
|
|
285
360
|
}
|
|
286
361
|
|
|
287
|
-
private traverseSchemaPath<
|
|
288
|
-
schema: JsonSchema<
|
|
362
|
+
private traverseSchemaPath<T>(
|
|
363
|
+
schema: JsonSchema<T>,
|
|
289
364
|
segments: string[],
|
|
290
365
|
keyword: string,
|
|
291
366
|
): string | undefined {
|
|
@@ -300,6 +375,12 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
300
375
|
return nextSchema.errorMessages[keyword]
|
|
301
376
|
}
|
|
302
377
|
|
|
378
|
+
// Check through nullable wrapper
|
|
379
|
+
const unwrapped = unwrapNullableAnyOf(nextSchema)
|
|
380
|
+
if (unwrapped?.errorMessages?.[keyword]) {
|
|
381
|
+
return unwrapped.errorMessages[keyword]
|
|
382
|
+
}
|
|
383
|
+
|
|
303
384
|
if (remainingSegments.length) {
|
|
304
385
|
return this.traverseSchemaPath(nextSchema, remainingSegments, keyword)
|
|
305
386
|
}
|
|
@@ -309,11 +390,15 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
309
390
|
|
|
310
391
|
private getChildSchema(schema: JsonSchema, segment: string | undefined): JsonSchema | undefined {
|
|
311
392
|
if (!segment) return undefined
|
|
312
|
-
|
|
313
|
-
|
|
393
|
+
|
|
394
|
+
// Unwrap nullable anyOf to find properties/items through nullable wrappers
|
|
395
|
+
const effectiveSchema = unwrapNullableAnyOf(schema) ?? schema
|
|
396
|
+
|
|
397
|
+
if (/^\d+$/.test(segment) && effectiveSchema.items) {
|
|
398
|
+
return this.getArrayItemSchema(effectiveSchema, segment)
|
|
314
399
|
}
|
|
315
400
|
|
|
316
|
-
return this.getObjectPropertySchema(
|
|
401
|
+
return this.getObjectPropertySchema(effectiveSchema, segment)
|
|
317
402
|
}
|
|
318
403
|
|
|
319
404
|
private getArrayItemSchema(schema: JsonSchema, indexSegment: string): JsonSchema | undefined {
|
|
@@ -331,15 +416,27 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
331
416
|
}
|
|
332
417
|
}
|
|
333
418
|
|
|
419
|
+
function unwrapNullableAnyOf(schema: JsonSchema): JsonSchema | undefined {
|
|
420
|
+
const nullIndex = unwrapNullableAnyOfIndex(schema)
|
|
421
|
+
if (nullIndex === -1) return undefined
|
|
422
|
+
return schema.anyOf![1 - nullIndex]!
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function unwrapNullableAnyOfIndex(schema: JsonSchema): number {
|
|
426
|
+
if (schema.anyOf?.length !== 2) return -1
|
|
427
|
+
const nullIndex = schema.anyOf.findIndex(s => s.type === 'null')
|
|
428
|
+
return nullIndex
|
|
429
|
+
}
|
|
430
|
+
|
|
334
431
|
const separator = '\n'
|
|
335
432
|
|
|
336
433
|
export const HIDDEN_AJV_SCHEMA = Symbol('HIDDEN_AJV_SCHEMA')
|
|
337
434
|
|
|
338
|
-
export type WithCachedAjvSchema<Base,
|
|
339
|
-
[HIDDEN_AJV_SCHEMA]: AjvSchema<
|
|
435
|
+
export type WithCachedAjvSchema<Base, OUT> = Base & {
|
|
436
|
+
[HIDDEN_AJV_SCHEMA]: AjvSchema<OUT>
|
|
340
437
|
}
|
|
341
438
|
|
|
342
|
-
export interface AjvValidationOptions
|
|
439
|
+
export interface AjvValidationOptions {
|
|
343
440
|
/**
|
|
344
441
|
* Defaults to true,
|
|
345
442
|
* because that's how AJV works by default,
|
|
@@ -367,7 +464,7 @@ export interface AjvValidationOptions<IN> {
|
|
|
367
464
|
* can include it "how it was" in an error message. So, for that reason we'll use
|
|
368
465
|
* `getOriginalInput()`, if it's provided.
|
|
369
466
|
*/
|
|
370
|
-
getOriginalInput?: () =>
|
|
467
|
+
getOriginalInput?: () => unknown
|
|
371
468
|
}
|
|
372
469
|
|
|
373
470
|
export interface AjvSchemaCfg {
|
|
@@ -395,10 +492,10 @@ export interface AjvSchemaCfg {
|
|
|
395
492
|
lazy?: boolean
|
|
396
493
|
}
|
|
397
494
|
|
|
398
|
-
export type SchemaHandledByAjv<
|
|
399
|
-
| JsonSchemaTerminal<
|
|
400
|
-
| JsonSchema<
|
|
401
|
-
| AjvSchema<
|
|
495
|
+
export type SchemaHandledByAjv<OUT> =
|
|
496
|
+
| JsonSchemaTerminal<OUT, any>
|
|
497
|
+
| JsonSchema<OUT>
|
|
498
|
+
| AjvSchema<OUT>
|
|
402
499
|
|
|
403
500
|
// ===== JsonSchemaBuilders ===== //
|
|
404
501
|
|
|
@@ -407,19 +504,19 @@ export const j = {
|
|
|
407
504
|
* Matches literally any value - equivalent to TypeScript's `any` type.
|
|
408
505
|
* Use sparingly, as it bypasses type validation entirely.
|
|
409
506
|
*/
|
|
410
|
-
any(): JsonSchemaAnyBuilder<any,
|
|
507
|
+
any(): JsonSchemaAnyBuilder<any, false> {
|
|
411
508
|
return new JsonSchemaAnyBuilder({})
|
|
412
509
|
},
|
|
413
510
|
|
|
414
|
-
string(): JsonSchemaStringBuilder<string,
|
|
511
|
+
string(): JsonSchemaStringBuilder<string, false> {
|
|
415
512
|
return new JsonSchemaStringBuilder()
|
|
416
513
|
},
|
|
417
514
|
|
|
418
|
-
number(): JsonSchemaNumberBuilder<number,
|
|
515
|
+
number(): JsonSchemaNumberBuilder<number, false> {
|
|
419
516
|
return new JsonSchemaNumberBuilder()
|
|
420
517
|
},
|
|
421
518
|
|
|
422
|
-
boolean(): JsonSchemaBooleanBuilder<boolean,
|
|
519
|
+
boolean(): JsonSchemaBooleanBuilder<boolean, false> {
|
|
423
520
|
return new JsonSchemaBooleanBuilder()
|
|
424
521
|
},
|
|
425
522
|
|
|
@@ -430,16 +527,16 @@ export const j = {
|
|
|
430
527
|
return j.object<AnyObject>({}).allowAdditionalProperties()
|
|
431
528
|
},
|
|
432
529
|
|
|
433
|
-
stringMap<S extends JsonSchemaTerminal<any, any
|
|
530
|
+
stringMap<S extends JsonSchemaTerminal<any, any>>(
|
|
434
531
|
schema: S,
|
|
435
|
-
): JsonSchemaObjectBuilder<StringMap<
|
|
532
|
+
): JsonSchemaObjectBuilder<StringMap<SchemaOut<S>>> {
|
|
436
533
|
const isValueOptional = schema.getSchema().optionalField
|
|
437
534
|
const builtSchema = schema.build()
|
|
438
535
|
const finalValueSchema: JsonSchema = isValueOptional
|
|
439
536
|
? { anyOf: [{ isUndefined: true }, builtSchema] }
|
|
440
537
|
: builtSchema
|
|
441
538
|
|
|
442
|
-
return new JsonSchemaObjectBuilder<StringMap<
|
|
539
|
+
return new JsonSchemaObjectBuilder<StringMap<SchemaOut<S>>>(
|
|
443
540
|
{},
|
|
444
541
|
{
|
|
445
542
|
hasIsOfTypeCheck: false,
|
|
@@ -487,21 +584,15 @@ export const j = {
|
|
|
487
584
|
withRegexKeys,
|
|
488
585
|
}),
|
|
489
586
|
|
|
490
|
-
array<
|
|
491
|
-
itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>,
|
|
492
|
-
): JsonSchemaArrayBuilder<IN, OUT, Opt> {
|
|
587
|
+
array<OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<OUT, Opt>): JsonSchemaArrayBuilder<OUT, Opt> {
|
|
493
588
|
return new JsonSchemaArrayBuilder(itemSchema)
|
|
494
589
|
},
|
|
495
590
|
|
|
496
|
-
tuple<const S extends JsonSchemaAnyBuilder<any, any
|
|
497
|
-
items: S,
|
|
498
|
-
): JsonSchemaTupleBuilder<S> {
|
|
591
|
+
tuple<const S extends JsonSchemaAnyBuilder<any, any>[]>(items: S): JsonSchemaTupleBuilder<S> {
|
|
499
592
|
return new JsonSchemaTupleBuilder<S>(items)
|
|
500
593
|
},
|
|
501
594
|
|
|
502
|
-
set<
|
|
503
|
-
itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>,
|
|
504
|
-
): JsonSchemaSet2Builder<IN, OUT, Opt> {
|
|
595
|
+
set<OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<OUT, Opt>): JsonSchemaSet2Builder<OUT, Opt> {
|
|
505
596
|
return new JsonSchemaSet2Builder(itemSchema)
|
|
506
597
|
},
|
|
507
598
|
|
|
@@ -557,18 +648,16 @@ export const j = {
|
|
|
557
648
|
* Use `anyOf` when schemas may overlap (e.g., AccountId | PartnerId with same format).
|
|
558
649
|
* Use `oneOf` when schemas are mutually exclusive.
|
|
559
650
|
*/
|
|
560
|
-
oneOf<
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
OUT = BuilderOutUnion<B>,
|
|
564
|
-
>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false> {
|
|
651
|
+
oneOf<B extends readonly JsonSchemaAnyBuilder<any, boolean>[], OUT = BuilderOutUnion<B>>(
|
|
652
|
+
items: [...B],
|
|
653
|
+
): JsonSchemaAnyBuilder<OUT, false> {
|
|
565
654
|
const schemas = items.map(b => b.build())
|
|
566
655
|
_assert(
|
|
567
656
|
schemas.every(hasNoObjectSchemas),
|
|
568
657
|
'Do not use `oneOf` validation with non-primitive types!',
|
|
569
658
|
)
|
|
570
659
|
|
|
571
|
-
return new JsonSchemaAnyBuilder<
|
|
660
|
+
return new JsonSchemaAnyBuilder<OUT, false>({
|
|
572
661
|
oneOf: schemas,
|
|
573
662
|
})
|
|
574
663
|
},
|
|
@@ -584,18 +673,16 @@ export const j = {
|
|
|
584
673
|
* Use `anyOf` when schemas may overlap (e.g., AccountId | PartnerId with same format).
|
|
585
674
|
* Use `oneOf` when schemas are mutually exclusive.
|
|
586
675
|
*/
|
|
587
|
-
anyOf<
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
OUT = BuilderOutUnion<B>,
|
|
591
|
-
>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false> {
|
|
676
|
+
anyOf<B extends readonly JsonSchemaAnyBuilder<any, boolean>[], OUT = BuilderOutUnion<B>>(
|
|
677
|
+
items: [...B],
|
|
678
|
+
): JsonSchemaAnyBuilder<OUT, false> {
|
|
592
679
|
const schemas = items.map(b => b.build())
|
|
593
680
|
_assert(
|
|
594
681
|
schemas.every(hasNoObjectSchemas),
|
|
595
682
|
'Do not use `anyOf` validation with non-primitive types!',
|
|
596
683
|
)
|
|
597
684
|
|
|
598
|
-
return new JsonSchemaAnyBuilder<
|
|
685
|
+
return new JsonSchemaAnyBuilder<OUT, false>({
|
|
599
686
|
anyOf: schemas,
|
|
600
687
|
})
|
|
601
688
|
},
|
|
@@ -612,13 +699,11 @@ export const j = {
|
|
|
612
699
|
* const schema = j.anyOfBy('success', schemaMap)
|
|
613
700
|
* ```
|
|
614
701
|
*/
|
|
615
|
-
anyOfBy<
|
|
616
|
-
|
|
617
|
-
D
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
>(propertyName: P, schemaDictionary: D): JsonSchemaAnyOfByBuilder<IN, OUT, P> {
|
|
621
|
-
return new JsonSchemaAnyOfByBuilder<IN, OUT, P>(propertyName, schemaDictionary)
|
|
702
|
+
anyOfBy<D extends Record<PropertyKey, JsonSchemaTerminal<any, any>>, OUT = AnyOfByOut<D>>(
|
|
703
|
+
propertyName: string,
|
|
704
|
+
schemaDictionary: D,
|
|
705
|
+
): JsonSchemaAnyOfByBuilder<OUT> {
|
|
706
|
+
return new JsonSchemaAnyOfByBuilder<OUT>(propertyName, schemaDictionary)
|
|
622
707
|
},
|
|
623
708
|
|
|
624
709
|
/**
|
|
@@ -629,12 +714,10 @@ export const j = {
|
|
|
629
714
|
* const schema = j.anyOfThese([successSchema, errorSchema])
|
|
630
715
|
* ```
|
|
631
716
|
*/
|
|
632
|
-
anyOfThese<
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
>(items: [...B]): JsonSchemaAnyBuilder<IN, OUT, false> {
|
|
637
|
-
return new JsonSchemaAnyBuilder<IN, OUT, false>({
|
|
717
|
+
anyOfThese<B extends readonly JsonSchemaAnyBuilder<any, boolean>[], OUT = BuilderOutUnion<B>>(
|
|
718
|
+
items: [...B],
|
|
719
|
+
): JsonSchemaAnyBuilder<OUT, false> {
|
|
720
|
+
return new JsonSchemaAnyBuilder<OUT, false>({
|
|
638
721
|
anyOfThese: items.map(b => b.build()),
|
|
639
722
|
})
|
|
640
723
|
},
|
|
@@ -669,17 +752,17 @@ const TS_2000_MILLIS = TS_2000 * 1000
|
|
|
669
752
|
With `Opt`, we can infer it as `{ foo?: string | undefined }`.
|
|
670
753
|
*/
|
|
671
754
|
|
|
672
|
-
export class JsonSchemaTerminal<
|
|
673
|
-
protected [HIDDEN_AJV_SCHEMA]: AjvSchema<any
|
|
755
|
+
export class JsonSchemaTerminal<OUT, Opt> {
|
|
756
|
+
protected [HIDDEN_AJV_SCHEMA]: AjvSchema<any> | undefined
|
|
674
757
|
protected schema: JsonSchema
|
|
675
758
|
|
|
676
759
|
constructor(schema: JsonSchema) {
|
|
677
760
|
this.schema = schema
|
|
678
761
|
}
|
|
679
762
|
|
|
680
|
-
get ajvSchema(): AjvSchema<any
|
|
763
|
+
get ajvSchema(): AjvSchema<any> {
|
|
681
764
|
if (!this[HIDDEN_AJV_SCHEMA]) {
|
|
682
|
-
this[HIDDEN_AJV_SCHEMA] = AjvSchema.create<
|
|
765
|
+
this[HIDDEN_AJV_SCHEMA] = AjvSchema.create<OUT>(this)
|
|
683
766
|
}
|
|
684
767
|
|
|
685
768
|
return this[HIDDEN_AJV_SCHEMA]
|
|
@@ -693,7 +776,7 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
693
776
|
* Produces a "clean schema object" without methods.
|
|
694
777
|
* Same as if it would be JSON.stringified.
|
|
695
778
|
*/
|
|
696
|
-
build(): JsonSchema<
|
|
779
|
+
build(): JsonSchema<OUT> {
|
|
697
780
|
_assert(
|
|
698
781
|
!(this.schema.optionalField && this.schema.default !== undefined),
|
|
699
782
|
'.optional() and .default() should not be used together - the default value makes .optional() redundant and causes incorrect type inference',
|
|
@@ -702,7 +785,7 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
702
785
|
const jsonSchema = _sortObject(
|
|
703
786
|
deepCopyPreservingFunctions(this.schema) as AnyObject,
|
|
704
787
|
JSON_SCHEMA_ORDER,
|
|
705
|
-
) as JsonSchema<
|
|
788
|
+
) as JsonSchema<OUT>
|
|
706
789
|
|
|
707
790
|
delete jsonSchema.optionalField
|
|
708
791
|
|
|
@@ -721,34 +804,49 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
721
804
|
return clone
|
|
722
805
|
}
|
|
723
806
|
|
|
724
|
-
validate(input:
|
|
807
|
+
validate(input: unknown, opt?: AjvValidationOptions): OUT {
|
|
725
808
|
return this.ajvSchema.validate(input, opt)
|
|
726
809
|
}
|
|
727
810
|
|
|
728
|
-
isValid(input:
|
|
811
|
+
isValid(input: unknown, opt?: AjvValidationOptions): boolean {
|
|
729
812
|
return this.ajvSchema.isValid(input, opt)
|
|
730
813
|
}
|
|
731
814
|
|
|
732
815
|
getValidationResult(
|
|
733
|
-
input:
|
|
734
|
-
opt: AjvValidationOptions
|
|
816
|
+
input: unknown,
|
|
817
|
+
opt: AjvValidationOptions = {},
|
|
735
818
|
): ValidationFunctionResult<OUT, AjvValidationError> {
|
|
736
819
|
return this.ajvSchema.getValidationResult(input, opt)
|
|
737
820
|
}
|
|
738
821
|
|
|
739
|
-
getValidationFunction(): ValidationFunction<
|
|
822
|
+
getValidationFunction(): ValidationFunction<OUT, AjvValidationError> {
|
|
740
823
|
return this.ajvSchema.getValidationFunction()
|
|
741
824
|
}
|
|
742
825
|
|
|
826
|
+
/**
|
|
827
|
+
* Specify a function to be called after the normal validation is finished.
|
|
828
|
+
*
|
|
829
|
+
* This function will receive the validated, type-safe data, and you can use it
|
|
830
|
+
* to do further validations, e.g. conditional validations based on certain property values,
|
|
831
|
+
* or to do data modifications either by mutating the input or returning a new value.
|
|
832
|
+
*
|
|
833
|
+
* If you throw an error from this function, it will show up as an error in the validation.
|
|
834
|
+
*/
|
|
835
|
+
postValidation<OUT2 = OUT>(fn: PostValidatonFn<OUT, OUT2>): JsonSchemaTerminal<OUT2, Opt> {
|
|
836
|
+
const clone = this.cloneAndUpdateSchema({
|
|
837
|
+
postValidation: fn,
|
|
838
|
+
})
|
|
839
|
+
return clone as unknown as JsonSchemaTerminal<OUT2, Opt>
|
|
840
|
+
}
|
|
841
|
+
|
|
743
842
|
/**
|
|
744
843
|
* @experimental
|
|
745
844
|
*/
|
|
746
|
-
in!: IN
|
|
747
845
|
out!: OUT
|
|
748
846
|
opt!: Opt
|
|
749
847
|
}
|
|
750
848
|
|
|
751
|
-
export class JsonSchemaAnyBuilder<
|
|
849
|
+
export class JsonSchemaAnyBuilder<OUT, Opt> extends JsonSchemaTerminal<OUT, Opt> {
|
|
752
850
|
protected setErrorMessage(ruleName: string, errorMessage: string | undefined): void {
|
|
753
851
|
if (_isUndefined(errorMessage)) return
|
|
754
852
|
|
|
@@ -811,12 +909,12 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
811
909
|
return this.cloneAndUpdateSchema({ type: 'object', instanceof: of })
|
|
812
910
|
}
|
|
813
911
|
|
|
814
|
-
optional(): JsonSchemaAnyBuilder<
|
|
912
|
+
optional(): JsonSchemaAnyBuilder<OUT | undefined, true> {
|
|
815
913
|
const clone = this.cloneAndUpdateSchema({ optionalField: true })
|
|
816
|
-
return clone as unknown as JsonSchemaAnyBuilder<
|
|
914
|
+
return clone as unknown as JsonSchemaAnyBuilder<OUT | undefined, true>
|
|
817
915
|
}
|
|
818
916
|
|
|
819
|
-
nullable(): JsonSchemaAnyBuilder<
|
|
917
|
+
nullable(): JsonSchemaAnyBuilder<OUT | null, Opt> {
|
|
820
918
|
return new JsonSchemaAnyBuilder({
|
|
821
919
|
anyOf: [this.build(), { type: 'null' }],
|
|
822
920
|
})
|
|
@@ -826,15 +924,15 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
826
924
|
* @deprecated
|
|
827
925
|
* The usage of this function is discouraged as it defeats the purpose of having type-safe validation.
|
|
828
926
|
*/
|
|
829
|
-
castAs<T>(): JsonSchemaAnyBuilder<T,
|
|
830
|
-
return this as unknown as JsonSchemaAnyBuilder<T,
|
|
927
|
+
castAs<T>(): JsonSchemaAnyBuilder<T, Opt> {
|
|
928
|
+
return this as unknown as JsonSchemaAnyBuilder<T, Opt>
|
|
831
929
|
}
|
|
832
930
|
|
|
833
931
|
/**
|
|
834
932
|
* Locks the given schema chain and no other modification can be done to it.
|
|
835
933
|
*/
|
|
836
|
-
final(): JsonSchemaTerminal<
|
|
837
|
-
return new JsonSchemaTerminal<
|
|
934
|
+
final(): JsonSchemaTerminal<OUT, Opt> {
|
|
935
|
+
return new JsonSchemaTerminal<OUT, Opt>(this.schema)
|
|
838
936
|
}
|
|
839
937
|
|
|
840
938
|
/**
|
|
@@ -843,11 +941,11 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
843
941
|
*
|
|
844
942
|
* You may add multiple custom validators and they will be executed in the order you added them.
|
|
845
943
|
*/
|
|
846
|
-
custom<OUT2 = OUT>(validator: CustomValidatorFn): JsonSchemaAnyBuilder<
|
|
944
|
+
custom<OUT2 = OUT>(validator: CustomValidatorFn): JsonSchemaAnyBuilder<OUT2, Opt> {
|
|
847
945
|
const { customValidations = [] } = this.schema
|
|
848
946
|
return this.cloneAndUpdateSchema({
|
|
849
947
|
customValidations: [...customValidations, validator],
|
|
850
|
-
}) as unknown as JsonSchemaAnyBuilder<
|
|
948
|
+
}) as unknown as JsonSchemaAnyBuilder<OUT2, Opt>
|
|
851
949
|
}
|
|
852
950
|
|
|
853
951
|
/**
|
|
@@ -860,19 +958,18 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
860
958
|
* This feature only works when the current schema is nested in an object or array schema,
|
|
861
959
|
* due to how mutability works in Ajv.
|
|
862
960
|
*/
|
|
863
|
-
convert<OUT2>(converter: CustomConverterFn<OUT2>): JsonSchemaAnyBuilder<
|
|
961
|
+
convert<OUT2>(converter: CustomConverterFn<OUT2>): JsonSchemaAnyBuilder<OUT2, Opt> {
|
|
864
962
|
const { customConversions = [] } = this.schema
|
|
865
963
|
return this.cloneAndUpdateSchema({
|
|
866
964
|
customConversions: [...customConversions, converter],
|
|
867
|
-
}) as unknown as JsonSchemaAnyBuilder<
|
|
965
|
+
}) as unknown as JsonSchemaAnyBuilder<OUT2, Opt>
|
|
868
966
|
}
|
|
869
967
|
}
|
|
870
968
|
|
|
871
969
|
export class JsonSchemaStringBuilder<
|
|
872
|
-
|
|
873
|
-
OUT = IN,
|
|
970
|
+
OUT extends string | undefined = string,
|
|
874
971
|
Opt extends boolean = false,
|
|
875
|
-
> extends JsonSchemaAnyBuilder<
|
|
972
|
+
> extends JsonSchemaAnyBuilder<OUT, Opt> {
|
|
876
973
|
constructor() {
|
|
877
974
|
super({
|
|
878
975
|
type: 'string',
|
|
@@ -894,17 +991,19 @@ export class JsonSchemaStringBuilder<
|
|
|
894
991
|
optionalValues?: T,
|
|
895
992
|
): T extends readonly (infer U)[]
|
|
896
993
|
? null extends U
|
|
897
|
-
? JsonSchemaTerminal<
|
|
898
|
-
: JsonSchemaStringBuilder<
|
|
899
|
-
: JsonSchemaStringBuilder<
|
|
994
|
+
? JsonSchemaTerminal<OUT | undefined, true>
|
|
995
|
+
: JsonSchemaStringBuilder<OUT | undefined, true>
|
|
996
|
+
: JsonSchemaStringBuilder<OUT | undefined, true> {
|
|
900
997
|
if (!optionalValues) {
|
|
901
998
|
return super.optional() as any
|
|
902
999
|
}
|
|
903
1000
|
|
|
904
1001
|
_typeCast<(string | null)[]>(optionalValues)
|
|
905
1002
|
|
|
906
|
-
let newBuilder: JsonSchemaTerminal<
|
|
907
|
-
|
|
1003
|
+
let newBuilder: JsonSchemaTerminal<OUT | undefined, true> = new JsonSchemaStringBuilder<
|
|
1004
|
+
OUT,
|
|
1005
|
+
Opt
|
|
1006
|
+
>().optional()
|
|
908
1007
|
const alternativesSchema = j.enum(optionalValues)
|
|
909
1008
|
Object.assign(newBuilder.getSchema(), {
|
|
910
1009
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
@@ -984,8 +1083,8 @@ export class JsonSchemaStringBuilder<
|
|
|
984
1083
|
})
|
|
985
1084
|
}
|
|
986
1085
|
|
|
987
|
-
branded<B extends string>(): JsonSchemaStringBuilder<B,
|
|
988
|
-
return this as unknown as JsonSchemaStringBuilder<B,
|
|
1086
|
+
branded<B extends string>(): JsonSchemaStringBuilder<B, Opt> {
|
|
1087
|
+
return this as unknown as JsonSchemaStringBuilder<B, Opt>
|
|
989
1088
|
}
|
|
990
1089
|
|
|
991
1090
|
/**
|
|
@@ -998,8 +1097,8 @@ export class JsonSchemaStringBuilder<
|
|
|
998
1097
|
return new JsonSchemaIsoDateBuilder()
|
|
999
1098
|
}
|
|
1000
1099
|
|
|
1001
|
-
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime
|
|
1002
|
-
return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded<IsoDateTime>()
|
|
1100
|
+
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime, Opt> {
|
|
1101
|
+
return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded<IsoDateTime>()
|
|
1003
1102
|
}
|
|
1004
1103
|
|
|
1005
1104
|
isoMonth(): JsonSchemaIsoMonthBuilder {
|
|
@@ -1052,9 +1151,9 @@ export class JsonSchemaStringBuilder<
|
|
|
1052
1151
|
* All previous expectations in the schema chain are dropped - including `.optional()` -
|
|
1053
1152
|
* because this call effectively starts a new schema chain as an `enum` validation.
|
|
1054
1153
|
*/
|
|
1055
|
-
ianaTimezone(): JsonSchemaEnumBuilder<
|
|
1154
|
+
ianaTimezone(): JsonSchemaEnumBuilder<IANATimezone, false> {
|
|
1056
1155
|
// UTC is added to assist unit-testing, which uses UTC by default (not technically a valid Iana timezone identifier)
|
|
1057
|
-
return j.enum(TIMEZONES, { msg: 'is an invalid IANA timezone' }).branded<IANATimezone>()
|
|
1156
|
+
return j.enum(TIMEZONES, { msg: 'is an invalid IANA timezone' }).branded<IANATimezone>()
|
|
1058
1157
|
}
|
|
1059
1158
|
|
|
1060
1159
|
base64Url(): this {
|
|
@@ -1073,7 +1172,6 @@ export interface JsonSchemaStringEmailOptions {
|
|
|
1073
1172
|
}
|
|
1074
1173
|
|
|
1075
1174
|
export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
|
|
1076
|
-
string | IsoDate,
|
|
1077
1175
|
IsoDate,
|
|
1078
1176
|
Opt
|
|
1079
1177
|
> {
|
|
@@ -1096,8 +1194,8 @@ export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonS
|
|
|
1096
1194
|
override optional<N extends null | undefined = undefined>(
|
|
1097
1195
|
nullValue?: N,
|
|
1098
1196
|
): N extends null
|
|
1099
|
-
? JsonSchemaTerminal<
|
|
1100
|
-
: JsonSchemaAnyBuilder<
|
|
1197
|
+
? JsonSchemaTerminal<IsoDate | undefined, true>
|
|
1198
|
+
: JsonSchemaAnyBuilder<IsoDate | undefined, true> {
|
|
1101
1199
|
if (nullValue === undefined) {
|
|
1102
1200
|
return super.optional() as any
|
|
1103
1201
|
}
|
|
@@ -1149,7 +1247,6 @@ export interface JsonSchemaIsoDateOptions {
|
|
|
1149
1247
|
}
|
|
1150
1248
|
|
|
1151
1249
|
export class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
|
|
1152
|
-
string | IsoDate,
|
|
1153
1250
|
IsoMonth,
|
|
1154
1251
|
Opt
|
|
1155
1252
|
> {
|
|
@@ -1164,10 +1261,9 @@ export class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends Json
|
|
|
1164
1261
|
export interface JsonSchemaIsoMonthOptions {}
|
|
1165
1262
|
|
|
1166
1263
|
export class JsonSchemaNumberBuilder<
|
|
1167
|
-
|
|
1168
|
-
OUT = IN,
|
|
1264
|
+
OUT extends number | undefined = number,
|
|
1169
1265
|
Opt extends boolean = false,
|
|
1170
|
-
> extends JsonSchemaAnyBuilder<
|
|
1266
|
+
> extends JsonSchemaAnyBuilder<OUT, Opt> {
|
|
1171
1267
|
constructor() {
|
|
1172
1268
|
super({
|
|
1173
1269
|
type: 'number',
|
|
@@ -1189,17 +1285,19 @@ export class JsonSchemaNumberBuilder<
|
|
|
1189
1285
|
optionalValues?: T,
|
|
1190
1286
|
): T extends readonly (infer U)[]
|
|
1191
1287
|
? null extends U
|
|
1192
|
-
? JsonSchemaTerminal<
|
|
1193
|
-
: JsonSchemaNumberBuilder<
|
|
1194
|
-
: JsonSchemaNumberBuilder<
|
|
1288
|
+
? JsonSchemaTerminal<OUT | undefined, true>
|
|
1289
|
+
: JsonSchemaNumberBuilder<OUT | undefined, true>
|
|
1290
|
+
: JsonSchemaNumberBuilder<OUT | undefined, true> {
|
|
1195
1291
|
if (!optionalValues) {
|
|
1196
1292
|
return super.optional() as any
|
|
1197
1293
|
}
|
|
1198
1294
|
|
|
1199
1295
|
_typeCast<(number | null)[]>(optionalValues)
|
|
1200
1296
|
|
|
1201
|
-
let newBuilder: JsonSchemaTerminal<
|
|
1202
|
-
|
|
1297
|
+
let newBuilder: JsonSchemaTerminal<OUT | undefined, true> = new JsonSchemaNumberBuilder<
|
|
1298
|
+
OUT,
|
|
1299
|
+
Opt
|
|
1300
|
+
>().optional()
|
|
1203
1301
|
const alternativesSchema = j.enum(optionalValues)
|
|
1204
1302
|
Object.assign(newBuilder.getSchema(), {
|
|
1205
1303
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
@@ -1224,8 +1322,8 @@ export class JsonSchemaNumberBuilder<
|
|
|
1224
1322
|
return this.cloneAndUpdateSchema({ type: 'integer' })
|
|
1225
1323
|
}
|
|
1226
1324
|
|
|
1227
|
-
branded<B extends number>(): JsonSchemaNumberBuilder<B,
|
|
1228
|
-
return this as unknown as JsonSchemaNumberBuilder<B,
|
|
1325
|
+
branded<B extends number>(): JsonSchemaNumberBuilder<B, Opt> {
|
|
1326
|
+
return this as unknown as JsonSchemaNumberBuilder<B, Opt>
|
|
1229
1327
|
}
|
|
1230
1328
|
|
|
1231
1329
|
multipleOf(multipleOf: number): this {
|
|
@@ -1301,23 +1399,19 @@ export class JsonSchemaNumberBuilder<
|
|
|
1301
1399
|
return this
|
|
1302
1400
|
}
|
|
1303
1401
|
|
|
1304
|
-
unixTimestamp(): JsonSchemaNumberBuilder<UnixTimestamp,
|
|
1402
|
+
unixTimestamp(): JsonSchemaNumberBuilder<UnixTimestamp, Opt> {
|
|
1305
1403
|
return this.integer().min(0).max(TS_2500).branded<UnixTimestamp>()
|
|
1306
1404
|
}
|
|
1307
1405
|
|
|
1308
|
-
unixTimestamp2000(): JsonSchemaNumberBuilder<UnixTimestamp,
|
|
1406
|
+
unixTimestamp2000(): JsonSchemaNumberBuilder<UnixTimestamp, Opt> {
|
|
1309
1407
|
return this.integer().min(TS_2000).max(TS_2500).branded<UnixTimestamp>()
|
|
1310
1408
|
}
|
|
1311
1409
|
|
|
1312
|
-
unixTimestampMillis(): JsonSchemaNumberBuilder<UnixTimestampMillis,
|
|
1410
|
+
unixTimestampMillis(): JsonSchemaNumberBuilder<UnixTimestampMillis, Opt> {
|
|
1313
1411
|
return this.integer().min(0).max(TS_2500_MILLIS).branded<UnixTimestampMillis>()
|
|
1314
1412
|
}
|
|
1315
1413
|
|
|
1316
|
-
unixTimestamp2000Millis(): JsonSchemaNumberBuilder<
|
|
1317
|
-
UnixTimestampMillis,
|
|
1318
|
-
UnixTimestampMillis,
|
|
1319
|
-
Opt
|
|
1320
|
-
> {
|
|
1414
|
+
unixTimestamp2000Millis(): JsonSchemaNumberBuilder<UnixTimestampMillis, Opt> {
|
|
1321
1415
|
return this.integer().min(TS_2000_MILLIS).max(TS_2500_MILLIS).branded<UnixTimestampMillis>()
|
|
1322
1416
|
}
|
|
1323
1417
|
|
|
@@ -1343,10 +1437,9 @@ export class JsonSchemaNumberBuilder<
|
|
|
1343
1437
|
}
|
|
1344
1438
|
|
|
1345
1439
|
export class JsonSchemaBooleanBuilder<
|
|
1346
|
-
|
|
1347
|
-
OUT = IN,
|
|
1440
|
+
OUT extends boolean | undefined = boolean,
|
|
1348
1441
|
Opt extends boolean = false,
|
|
1349
|
-
> extends JsonSchemaAnyBuilder<
|
|
1442
|
+
> extends JsonSchemaAnyBuilder<OUT, Opt> {
|
|
1350
1443
|
constructor() {
|
|
1351
1444
|
super({
|
|
1352
1445
|
type: 'boolean',
|
|
@@ -1359,18 +1452,12 @@ export class JsonSchemaBooleanBuilder<
|
|
|
1359
1452
|
* This `optionalValue` feature only works when the current schema is nested in an object or array schema,
|
|
1360
1453
|
* due to how mutability works in Ajv.
|
|
1361
1454
|
*/
|
|
1362
|
-
override optional(
|
|
1363
|
-
optionalValue?: boolean,
|
|
1364
|
-
): JsonSchemaBooleanBuilder<IN | undefined, OUT | undefined, true> {
|
|
1455
|
+
override optional(optionalValue?: boolean): JsonSchemaBooleanBuilder<OUT | undefined, true> {
|
|
1365
1456
|
if (typeof optionalValue === 'undefined') {
|
|
1366
|
-
return super.optional() as unknown as JsonSchemaBooleanBuilder<
|
|
1367
|
-
IN | undefined,
|
|
1368
|
-
OUT | undefined,
|
|
1369
|
-
true
|
|
1370
|
-
>
|
|
1457
|
+
return super.optional() as unknown as JsonSchemaBooleanBuilder<OUT | undefined, true>
|
|
1371
1458
|
}
|
|
1372
1459
|
|
|
1373
|
-
const newBuilder = new JsonSchemaBooleanBuilder<
|
|
1460
|
+
const newBuilder = new JsonSchemaBooleanBuilder<OUT, Opt>().optional()
|
|
1374
1461
|
const alternativesSchema = j.enum([optionalValue])
|
|
1375
1462
|
Object.assign(newBuilder.getSchema(), {
|
|
1376
1463
|
anyOf: [this.build(), alternativesSchema.build()],
|
|
@@ -1382,10 +1469,9 @@ export class JsonSchemaBooleanBuilder<
|
|
|
1382
1469
|
}
|
|
1383
1470
|
|
|
1384
1471
|
export class JsonSchemaObjectBuilder<
|
|
1385
|
-
IN extends AnyObject,
|
|
1386
1472
|
OUT extends AnyObject,
|
|
1387
1473
|
Opt extends boolean = false,
|
|
1388
|
-
> extends JsonSchemaAnyBuilder<
|
|
1474
|
+
> extends JsonSchemaAnyBuilder<OUT, Opt> {
|
|
1389
1475
|
constructor(props?: AnyObject, opt?: JsonSchemaObjectBuilderOpts) {
|
|
1390
1476
|
super({
|
|
1391
1477
|
type: 'object',
|
|
@@ -1405,7 +1491,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
1405
1491
|
const required: string[] = []
|
|
1406
1492
|
|
|
1407
1493
|
for (const [key, builder] of Object.entries(props)) {
|
|
1408
|
-
const isOptional = (builder as JsonSchemaTerminal<any, any
|
|
1494
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any>).getSchema().optionalField
|
|
1409
1495
|
if (!isOptional) {
|
|
1410
1496
|
required.push(key)
|
|
1411
1497
|
}
|
|
@@ -1432,8 +1518,8 @@ export class JsonSchemaObjectBuilder<
|
|
|
1432
1518
|
override optional<N extends null | undefined = undefined>(
|
|
1433
1519
|
nullValue?: N,
|
|
1434
1520
|
): N extends null
|
|
1435
|
-
? JsonSchemaTerminal<
|
|
1436
|
-
: JsonSchemaAnyBuilder<
|
|
1521
|
+
? JsonSchemaTerminal<OUT | undefined, true>
|
|
1522
|
+
: JsonSchemaAnyBuilder<OUT | undefined, true> {
|
|
1437
1523
|
if (nullValue === undefined) {
|
|
1438
1524
|
return super.optional() as any
|
|
1439
1525
|
}
|
|
@@ -1456,35 +1542,25 @@ export class JsonSchemaObjectBuilder<
|
|
|
1456
1542
|
return this.cloneAndUpdateSchema({ additionalProperties: true })
|
|
1457
1543
|
}
|
|
1458
1544
|
|
|
1459
|
-
extend<P extends Record<string, JsonSchemaAnyBuilder<any, any
|
|
1545
|
+
extend<P extends Record<string, JsonSchemaAnyBuilder<any, any>>>(
|
|
1460
1546
|
props: P,
|
|
1461
1547
|
): JsonSchemaObjectBuilder<
|
|
1462
|
-
RelaxIndexSignature<
|
|
1463
|
-
OptionalDbEntityFields<
|
|
1464
|
-
Override<
|
|
1465
|
-
IN,
|
|
1466
|
-
{
|
|
1467
|
-
[K in keyof P]?: P[K] extends JsonSchemaAnyBuilder<infer IN2, any, any> ? IN2 : never
|
|
1468
|
-
}
|
|
1469
|
-
>
|
|
1470
|
-
>
|
|
1471
|
-
>,
|
|
1472
1548
|
Override<
|
|
1473
1549
|
OUT,
|
|
1474
1550
|
{
|
|
1475
1551
|
// required keys
|
|
1476
|
-
[K in keyof P as P[K] extends JsonSchemaAnyBuilder<any,
|
|
1552
|
+
[K in keyof P as P[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1477
1553
|
? IsOpt extends true
|
|
1478
1554
|
? never
|
|
1479
1555
|
: K
|
|
1480
|
-
: never]: P[K] extends JsonSchemaAnyBuilder<
|
|
1556
|
+
: never]: P[K] extends JsonSchemaAnyBuilder<infer OUT2, any> ? OUT2 : never
|
|
1481
1557
|
} & {
|
|
1482
1558
|
// optional keys
|
|
1483
|
-
[K in keyof P as P[K] extends JsonSchemaAnyBuilder<any,
|
|
1559
|
+
[K in keyof P as P[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1484
1560
|
? IsOpt extends true
|
|
1485
1561
|
? K
|
|
1486
1562
|
: never
|
|
1487
|
-
: never]?: P[K] extends JsonSchemaAnyBuilder<
|
|
1563
|
+
: never]?: P[K] extends JsonSchemaAnyBuilder<infer OUT2, any> ? OUT2 : never
|
|
1488
1564
|
}
|
|
1489
1565
|
>,
|
|
1490
1566
|
false
|
|
@@ -1518,13 +1594,13 @@ export class JsonSchemaObjectBuilder<
|
|
|
1518
1594
|
* const shuSchema = fooSchema.concat(barSchema).isOfType<Shu>() // important
|
|
1519
1595
|
* ```
|
|
1520
1596
|
*/
|
|
1521
|
-
concat<
|
|
1522
|
-
other: JsonSchemaObjectBuilder<
|
|
1523
|
-
): JsonSchemaObjectBuilder<
|
|
1597
|
+
concat<OUT2 extends AnyObject>(
|
|
1598
|
+
other: JsonSchemaObjectBuilder<OUT2, any>,
|
|
1599
|
+
): JsonSchemaObjectBuilder<OUT & OUT2, false> {
|
|
1524
1600
|
const clone = this.clone()
|
|
1525
1601
|
mergeJsonSchemaObjects(clone.schema as any, other.schema as any)
|
|
1526
1602
|
_objectAssign(clone.schema, { hasIsOfTypeCheck: false })
|
|
1527
|
-
return clone as unknown as JsonSchemaObjectBuilder<
|
|
1603
|
+
return clone as unknown as JsonSchemaObjectBuilder<OUT & OUT2, false>
|
|
1528
1604
|
}
|
|
1529
1605
|
|
|
1530
1606
|
/**
|
|
@@ -1547,7 +1623,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
1547
1623
|
return this.cloneAndUpdateSchema({ maxProperties })
|
|
1548
1624
|
}
|
|
1549
1625
|
|
|
1550
|
-
exclusiveProperties(propNames: readonly (keyof
|
|
1626
|
+
exclusiveProperties(propNames: readonly (keyof OUT & string)[]): this {
|
|
1551
1627
|
const exclusiveProperties = this.schema.exclusiveProperties ?? []
|
|
1552
1628
|
return this.cloneAndUpdateSchema({ exclusiveProperties: [...exclusiveProperties, propNames] })
|
|
1553
1629
|
}
|
|
@@ -1555,42 +1631,27 @@ export class JsonSchemaObjectBuilder<
|
|
|
1555
1631
|
|
|
1556
1632
|
interface JsonSchemaObjectBuilderOpts {
|
|
1557
1633
|
hasIsOfTypeCheck?: false
|
|
1558
|
-
patternProperties?: StringMap<JsonSchema<any
|
|
1634
|
+
patternProperties?: StringMap<JsonSchema<any>>
|
|
1559
1635
|
keySchema?: JsonSchema
|
|
1560
1636
|
}
|
|
1561
1637
|
|
|
1562
1638
|
export class JsonSchemaObjectInferringBuilder<
|
|
1563
|
-
PROPS extends Record<string, JsonSchemaAnyBuilder<any, any
|
|
1639
|
+
PROPS extends Record<string, JsonSchemaAnyBuilder<any, any>>,
|
|
1564
1640
|
Opt extends boolean = false,
|
|
1565
1641
|
> extends JsonSchemaAnyBuilder<
|
|
1566
1642
|
Expand<
|
|
1567
1643
|
{
|
|
1568
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1569
|
-
? IsOpt extends true
|
|
1570
|
-
? never
|
|
1571
|
-
: K
|
|
1572
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1573
|
-
} & {
|
|
1574
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1575
|
-
? IsOpt extends true
|
|
1576
|
-
? K
|
|
1577
|
-
: never
|
|
1578
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1579
|
-
}
|
|
1580
|
-
>,
|
|
1581
|
-
Expand<
|
|
1582
|
-
{
|
|
1583
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1644
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1584
1645
|
? IsOpt extends true
|
|
1585
1646
|
? never
|
|
1586
1647
|
: K
|
|
1587
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<
|
|
1648
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1588
1649
|
} & {
|
|
1589
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1650
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1590
1651
|
? IsOpt extends true
|
|
1591
1652
|
? K
|
|
1592
1653
|
: never
|
|
1593
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<
|
|
1654
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1594
1655
|
}
|
|
1595
1656
|
>,
|
|
1596
1657
|
Opt
|
|
@@ -1611,7 +1672,7 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1611
1672
|
const required: string[] = []
|
|
1612
1673
|
|
|
1613
1674
|
for (const [key, builder] of Object.entries(props)) {
|
|
1614
|
-
const isOptional = (builder as JsonSchemaTerminal<any, any
|
|
1675
|
+
const isOptional = (builder as JsonSchemaTerminal<any, any>).getSchema().optionalField
|
|
1615
1676
|
if (!isOptional) {
|
|
1616
1677
|
required.push(key)
|
|
1617
1678
|
}
|
|
@@ -1642,33 +1703,17 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1642
1703
|
? JsonSchemaTerminal<
|
|
1643
1704
|
| Expand<
|
|
1644
1705
|
{
|
|
1645
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1646
|
-
? IsOpt extends true
|
|
1647
|
-
? never
|
|
1648
|
-
: K
|
|
1649
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1650
|
-
} & {
|
|
1651
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1652
|
-
? IsOpt extends true
|
|
1653
|
-
? K
|
|
1654
|
-
: never
|
|
1655
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1656
|
-
}
|
|
1657
|
-
>
|
|
1658
|
-
| undefined,
|
|
1659
|
-
| Expand<
|
|
1660
|
-
{
|
|
1661
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1706
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1662
1707
|
? IsOpt extends true
|
|
1663
1708
|
? never
|
|
1664
1709
|
: K
|
|
1665
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<
|
|
1710
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1666
1711
|
} & {
|
|
1667
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1712
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1668
1713
|
? IsOpt extends true
|
|
1669
1714
|
? K
|
|
1670
1715
|
: never
|
|
1671
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<
|
|
1716
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1672
1717
|
}
|
|
1673
1718
|
>
|
|
1674
1719
|
| undefined,
|
|
@@ -1677,33 +1722,17 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1677
1722
|
: JsonSchemaAnyBuilder<
|
|
1678
1723
|
| Expand<
|
|
1679
1724
|
{
|
|
1680
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1725
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1681
1726
|
? IsOpt extends true
|
|
1682
1727
|
? never
|
|
1683
1728
|
: K
|
|
1684
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer
|
|
1729
|
+
: never]: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1685
1730
|
} & {
|
|
1686
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any,
|
|
1731
|
+
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, infer IsOpt>
|
|
1687
1732
|
? IsOpt extends true
|
|
1688
1733
|
? K
|
|
1689
1734
|
: never
|
|
1690
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer
|
|
1691
|
-
}
|
|
1692
|
-
>
|
|
1693
|
-
| undefined,
|
|
1694
|
-
| Expand<
|
|
1695
|
-
{
|
|
1696
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1697
|
-
? IsOpt extends true
|
|
1698
|
-
? never
|
|
1699
|
-
: K
|
|
1700
|
-
: never]: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1701
|
-
} & {
|
|
1702
|
-
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt>
|
|
1703
|
-
? IsOpt extends true
|
|
1704
|
-
? K
|
|
1705
|
-
: never
|
|
1706
|
-
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1735
|
+
: never]?: PROPS[K] extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
1707
1736
|
}
|
|
1708
1737
|
>
|
|
1709
1738
|
| undefined,
|
|
@@ -1731,7 +1760,7 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1731
1760
|
return this.cloneAndUpdateSchema({ additionalProperties: true })
|
|
1732
1761
|
}
|
|
1733
1762
|
|
|
1734
|
-
extend<NEW_PROPS extends Record<string, JsonSchemaAnyBuilder<any, any
|
|
1763
|
+
extend<NEW_PROPS extends Record<string, JsonSchemaAnyBuilder<any, any>>>(
|
|
1735
1764
|
props: NEW_PROPS,
|
|
1736
1765
|
): JsonSchemaObjectInferringBuilder<
|
|
1737
1766
|
{
|
|
@@ -1779,8 +1808,8 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
1779
1808
|
}
|
|
1780
1809
|
}
|
|
1781
1810
|
|
|
1782
|
-
export class JsonSchemaArrayBuilder<
|
|
1783
|
-
constructor(itemsSchema: JsonSchemaAnyBuilder<
|
|
1811
|
+
export class JsonSchemaArrayBuilder<OUT, Opt> extends JsonSchemaAnyBuilder<OUT[], Opt> {
|
|
1812
|
+
constructor(itemsSchema: JsonSchemaAnyBuilder<OUT, Opt>) {
|
|
1784
1813
|
super({
|
|
1785
1814
|
type: 'array',
|
|
1786
1815
|
items: itemsSchema.build(),
|
|
@@ -1811,12 +1840,8 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
1811
1840
|
}
|
|
1812
1841
|
}
|
|
1813
1842
|
|
|
1814
|
-
export class JsonSchemaSet2Builder<
|
|
1815
|
-
|
|
1816
|
-
Set2<OUT>,
|
|
1817
|
-
Opt
|
|
1818
|
-
> {
|
|
1819
|
-
constructor(itemsSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>) {
|
|
1843
|
+
export class JsonSchemaSet2Builder<OUT, Opt> extends JsonSchemaAnyBuilder<Set2<OUT>, Opt> {
|
|
1844
|
+
constructor(itemsSchema: JsonSchemaAnyBuilder<OUT, Opt>) {
|
|
1820
1845
|
super({
|
|
1821
1846
|
type: ['array', 'object'],
|
|
1822
1847
|
Set2: itemsSchema.build(),
|
|
@@ -1832,11 +1857,7 @@ export class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<
|
|
|
1832
1857
|
}
|
|
1833
1858
|
}
|
|
1834
1859
|
|
|
1835
|
-
export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<
|
|
1836
|
-
string | any[] | ArrayBuffer | Buffer,
|
|
1837
|
-
Buffer,
|
|
1838
|
-
false
|
|
1839
|
-
> {
|
|
1860
|
+
export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<Buffer, false> {
|
|
1840
1861
|
constructor() {
|
|
1841
1862
|
super({
|
|
1842
1863
|
Buffer: true,
|
|
@@ -1845,11 +1866,10 @@ export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<
|
|
|
1845
1866
|
}
|
|
1846
1867
|
|
|
1847
1868
|
export class JsonSchemaEnumBuilder<
|
|
1848
|
-
|
|
1849
|
-
OUT extends IN = IN,
|
|
1869
|
+
OUT extends string | number | boolean | null,
|
|
1850
1870
|
Opt extends boolean = false,
|
|
1851
|
-
> extends JsonSchemaAnyBuilder<
|
|
1852
|
-
constructor(enumValues: readonly
|
|
1871
|
+
> extends JsonSchemaAnyBuilder<OUT, Opt> {
|
|
1872
|
+
constructor(enumValues: readonly OUT[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt) {
|
|
1853
1873
|
const jsonSchema: JsonSchema = { enum: enumValues }
|
|
1854
1874
|
// Specifying the base type helps in cases when we ask Ajv to coerce the types.
|
|
1855
1875
|
// Having only the `enum` in the schema does not trigger a coercion in Ajv.
|
|
@@ -1862,16 +1882,14 @@ export class JsonSchemaEnumBuilder<
|
|
|
1862
1882
|
if (opt?.msg) this.setErrorMessage('enum', opt.msg)
|
|
1863
1883
|
}
|
|
1864
1884
|
|
|
1865
|
-
branded<B extends
|
|
1866
|
-
return this as unknown as JsonSchemaEnumBuilder<B
|
|
1885
|
+
branded<B extends OUT>(): JsonSchemaEnumBuilder<B, Opt> {
|
|
1886
|
+
return this as unknown as JsonSchemaEnumBuilder<B, Opt>
|
|
1867
1887
|
}
|
|
1868
1888
|
}
|
|
1869
1889
|
|
|
1870
1890
|
export class JsonSchemaTupleBuilder<
|
|
1871
|
-
ITEMS extends JsonSchemaAnyBuilder<any, any
|
|
1872
|
-
> extends JsonSchemaAnyBuilder<
|
|
1873
|
-
private readonly _items: ITEMS
|
|
1874
|
-
|
|
1891
|
+
ITEMS extends JsonSchemaAnyBuilder<any, any>[],
|
|
1892
|
+
> extends JsonSchemaAnyBuilder<TupleOut<ITEMS>, false> {
|
|
1875
1893
|
constructor(items: ITEMS) {
|
|
1876
1894
|
super({
|
|
1877
1895
|
type: 'array',
|
|
@@ -1879,22 +1897,13 @@ export class JsonSchemaTupleBuilder<
|
|
|
1879
1897
|
minItems: items.length,
|
|
1880
1898
|
maxItems: items.length,
|
|
1881
1899
|
})
|
|
1882
|
-
|
|
1883
|
-
this._items = items
|
|
1884
1900
|
}
|
|
1885
1901
|
}
|
|
1886
1902
|
|
|
1887
|
-
export class JsonSchemaAnyOfByBuilder<
|
|
1888
|
-
IN,
|
|
1889
|
-
OUT,
|
|
1890
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1891
|
-
_P extends string = string,
|
|
1892
|
-
> extends JsonSchemaAnyBuilder<AnyOfByInput<IN, _P> | IN, OUT, false> {
|
|
1893
|
-
declare in: IN
|
|
1894
|
-
|
|
1903
|
+
export class JsonSchemaAnyOfByBuilder<OUT> extends JsonSchemaAnyBuilder<OUT, false> {
|
|
1895
1904
|
constructor(
|
|
1896
1905
|
propertyName: string,
|
|
1897
|
-
schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any
|
|
1906
|
+
schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any>>,
|
|
1898
1907
|
) {
|
|
1899
1908
|
const builtSchemaDictionary: Record<string, JsonSchema> = {}
|
|
1900
1909
|
for (const [key, schema] of Object.entries(schemaDictionary)) {
|
|
@@ -1912,17 +1921,10 @@ export class JsonSchemaAnyOfByBuilder<
|
|
|
1912
1921
|
}
|
|
1913
1922
|
}
|
|
1914
1923
|
|
|
1915
|
-
export class JsonSchemaAnyOfTheseBuilder<
|
|
1916
|
-
IN,
|
|
1917
|
-
OUT,
|
|
1918
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1919
|
-
_P extends string = string,
|
|
1920
|
-
> extends JsonSchemaAnyBuilder<AnyOfByInput<IN, _P> | IN, OUT, false> {
|
|
1921
|
-
declare in: IN
|
|
1922
|
-
|
|
1924
|
+
export class JsonSchemaAnyOfTheseBuilder<OUT> extends JsonSchemaAnyBuilder<OUT, false> {
|
|
1923
1925
|
constructor(
|
|
1924
1926
|
propertyName: string,
|
|
1925
|
-
schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any
|
|
1927
|
+
schemaDictionary: Record<PropertyKey, JsonSchemaTerminal<any, any>>,
|
|
1926
1928
|
) {
|
|
1927
1929
|
const builtSchemaDictionary: Record<string, JsonSchema> = {}
|
|
1928
1930
|
for (const [key, schema] of Object.entries(schemaDictionary)) {
|
|
@@ -1942,8 +1944,7 @@ export class JsonSchemaAnyOfTheseBuilder<
|
|
|
1942
1944
|
|
|
1943
1945
|
type EnumBaseType = 'string' | 'number' | 'other'
|
|
1944
1946
|
|
|
1945
|
-
export interface JsonSchema<
|
|
1946
|
-
readonly in?: IN
|
|
1947
|
+
export interface JsonSchema<OUT = unknown> {
|
|
1947
1948
|
readonly out?: OUT
|
|
1948
1949
|
|
|
1949
1950
|
$schema?: string
|
|
@@ -1959,15 +1960,15 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1959
1960
|
items?: JsonSchema
|
|
1960
1961
|
prefixItems?: JsonSchema[]
|
|
1961
1962
|
properties?: {
|
|
1962
|
-
[K in keyof
|
|
1963
|
+
[K in keyof OUT]: JsonSchema<OUT[K]>
|
|
1963
1964
|
}
|
|
1964
|
-
patternProperties?: StringMap<JsonSchema<any
|
|
1965
|
+
patternProperties?: StringMap<JsonSchema<any>>
|
|
1965
1966
|
required?: string[]
|
|
1966
1967
|
additionalProperties?: boolean
|
|
1967
1968
|
minProperties?: number
|
|
1968
1969
|
maxProperties?: number
|
|
1969
1970
|
|
|
1970
|
-
default?:
|
|
1971
|
+
default?: OUT
|
|
1971
1972
|
|
|
1972
1973
|
// https://json-schema.org/understanding-json-schema/reference/conditionals.html#id6
|
|
1973
1974
|
if?: JsonSchema
|
|
@@ -2028,20 +2029,21 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
2028
2029
|
precision?: number
|
|
2029
2030
|
customValidations?: CustomValidatorFn[]
|
|
2030
2031
|
customConversions?: CustomConverterFn<any>[]
|
|
2032
|
+
postValidation?: PostValidatonFn<any, OUT>
|
|
2031
2033
|
}
|
|
2032
2034
|
|
|
2033
2035
|
function object(props: AnyObject): never
|
|
2034
|
-
function object<
|
|
2035
|
-
[K in keyof Required<
|
|
2036
|
-
}): JsonSchemaObjectBuilder<
|
|
2037
|
-
|
|
2038
|
-
function object<
|
|
2039
|
-
[key in keyof
|
|
2040
|
-
}): JsonSchemaObjectBuilder<
|
|
2041
|
-
return new JsonSchemaObjectBuilder<
|
|
2036
|
+
function object<OUT extends AnyObject>(props: {
|
|
2037
|
+
[K in keyof Required<OUT>]-?: JsonSchemaTerminal<OUT[K], any>
|
|
2038
|
+
}): JsonSchemaObjectBuilder<OUT, false>
|
|
2039
|
+
|
|
2040
|
+
function object<OUT extends AnyObject>(props: {
|
|
2041
|
+
[key in keyof OUT]: JsonSchemaTerminal<OUT[key], any>
|
|
2042
|
+
}): JsonSchemaObjectBuilder<OUT, false> {
|
|
2043
|
+
return new JsonSchemaObjectBuilder<OUT, false>(props)
|
|
2042
2044
|
}
|
|
2043
2045
|
|
|
2044
|
-
function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any
|
|
2046
|
+
function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any>>>(
|
|
2045
2047
|
props: P,
|
|
2046
2048
|
): JsonSchemaObjectInferringBuilder<P, false> {
|
|
2047
2049
|
return new JsonSchemaObjectInferringBuilder<P, false>(props)
|
|
@@ -2049,24 +2051,27 @@ function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any, any
|
|
|
2049
2051
|
|
|
2050
2052
|
function objectDbEntity(props: AnyObject): never
|
|
2051
2053
|
function objectDbEntity<
|
|
2052
|
-
|
|
2053
|
-
EXTRA_KEYS extends Exclude<keyof
|
|
2054
|
+
OUT extends BaseDBEntity,
|
|
2055
|
+
EXTRA_KEYS extends Exclude<keyof OUT, keyof BaseDBEntity> = Exclude<
|
|
2056
|
+
keyof OUT,
|
|
2057
|
+
keyof BaseDBEntity
|
|
2058
|
+
>,
|
|
2054
2059
|
>(
|
|
2055
2060
|
props: {
|
|
2056
2061
|
// ✅ all non-system fields must be explicitly provided
|
|
2057
|
-
[K in EXTRA_KEYS]-?: BuilderFor<
|
|
2062
|
+
[K in EXTRA_KEYS]-?: BuilderFor<OUT[K]>
|
|
2058
2063
|
} &
|
|
2059
|
-
// ✅ if `id` differs, it
|
|
2060
|
-
(ExactMatch<
|
|
2064
|
+
// ✅ if `id` differs, it's required
|
|
2065
|
+
(ExactMatch<OUT['id'], BaseDBEntity['id']> extends true
|
|
2061
2066
|
? { id?: BuilderFor<BaseDBEntity['id']> }
|
|
2062
|
-
: { id: BuilderFor<
|
|
2063
|
-
(ExactMatch<
|
|
2067
|
+
: { id: BuilderFor<OUT['id']> }) &
|
|
2068
|
+
(ExactMatch<OUT['created'], BaseDBEntity['created']> extends true
|
|
2064
2069
|
? { created?: BuilderFor<BaseDBEntity['created']> }
|
|
2065
|
-
: { created: BuilderFor<
|
|
2066
|
-
(ExactMatch<
|
|
2070
|
+
: { created: BuilderFor<OUT['created']> }) &
|
|
2071
|
+
(ExactMatch<OUT['updated'], BaseDBEntity['updated']> extends true
|
|
2067
2072
|
? { updated?: BuilderFor<BaseDBEntity['updated']> }
|
|
2068
|
-
: { updated: BuilderFor<
|
|
2069
|
-
): JsonSchemaObjectBuilder<
|
|
2073
|
+
: { updated: BuilderFor<OUT['updated']> }),
|
|
2074
|
+
): JsonSchemaObjectBuilder<OUT, false>
|
|
2070
2075
|
|
|
2071
2076
|
function objectDbEntity(props: AnyObject): any {
|
|
2072
2077
|
return j.object({
|
|
@@ -2078,16 +2083,13 @@ function objectDbEntity(props: AnyObject): any {
|
|
|
2078
2083
|
}
|
|
2079
2084
|
|
|
2080
2085
|
function record<
|
|
2081
|
-
KS extends JsonSchemaAnyBuilder<any, any
|
|
2082
|
-
VS extends JsonSchemaAnyBuilder<any, any
|
|
2086
|
+
KS extends JsonSchemaAnyBuilder<any, any>,
|
|
2087
|
+
VS extends JsonSchemaAnyBuilder<any, any>,
|
|
2083
2088
|
Opt extends boolean = SchemaOpt<VS>,
|
|
2084
2089
|
>(
|
|
2085
2090
|
keySchema: KS,
|
|
2086
2091
|
valueSchema: VS,
|
|
2087
2092
|
): JsonSchemaObjectBuilder<
|
|
2088
|
-
Opt extends true
|
|
2089
|
-
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
2090
|
-
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
2091
2093
|
Opt extends true
|
|
2092
2094
|
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
2093
2095
|
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
@@ -2095,8 +2097,7 @@ function record<
|
|
|
2095
2097
|
> {
|
|
2096
2098
|
const keyJsonSchema = keySchema.build()
|
|
2097
2099
|
// Check if value schema is optional before build() strips the optionalField flag
|
|
2098
|
-
const isValueOptional = (valueSchema as JsonSchemaTerminal<any, any
|
|
2099
|
-
.optionalField
|
|
2100
|
+
const isValueOptional = (valueSchema as JsonSchemaTerminal<any, any>).getSchema().optionalField
|
|
2100
2101
|
const valueJsonSchema = valueSchema.build()
|
|
2101
2102
|
|
|
2102
2103
|
// When value schema is optional, wrap in anyOf to allow undefined values
|
|
@@ -2105,9 +2106,6 @@ function record<
|
|
|
2105
2106
|
: valueJsonSchema
|
|
2106
2107
|
|
|
2107
2108
|
return new JsonSchemaObjectBuilder<
|
|
2108
|
-
Opt extends true
|
|
2109
|
-
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
2110
|
-
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
2111
2109
|
Opt extends true
|
|
2112
2110
|
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
2113
2111
|
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
@@ -2121,17 +2119,10 @@ function record<
|
|
|
2121
2119
|
})
|
|
2122
2120
|
}
|
|
2123
2121
|
|
|
2124
|
-
function withRegexKeys<
|
|
2125
|
-
S extends JsonSchemaAnyBuilder<any, any, any>,
|
|
2126
|
-
Opt extends boolean = SchemaOpt<S>,
|
|
2127
|
-
>(
|
|
2122
|
+
function withRegexKeys<S extends JsonSchemaAnyBuilder<any, any>>(
|
|
2128
2123
|
keyRegex: RegExp | string,
|
|
2129
2124
|
schema: S,
|
|
2130
|
-
): JsonSchemaObjectBuilder<
|
|
2131
|
-
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
2132
|
-
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
2133
|
-
false
|
|
2134
|
-
> {
|
|
2125
|
+
): JsonSchemaObjectBuilder<StringMap<SchemaOut<S>>, false> {
|
|
2135
2126
|
if (keyRegex instanceof RegExp) {
|
|
2136
2127
|
_assert(
|
|
2137
2128
|
!keyRegex.flags,
|
|
@@ -2141,11 +2132,7 @@ function withRegexKeys<
|
|
|
2141
2132
|
const pattern = keyRegex instanceof RegExp ? keyRegex.source : keyRegex
|
|
2142
2133
|
const jsonSchema = schema.build()
|
|
2143
2134
|
|
|
2144
|
-
return new JsonSchemaObjectBuilder<
|
|
2145
|
-
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
2146
|
-
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
2147
|
-
false
|
|
2148
|
-
>([], {
|
|
2135
|
+
return new JsonSchemaObjectBuilder<StringMap<SchemaOut<S>>, false>([], {
|
|
2149
2136
|
hasIsOfTypeCheck: false,
|
|
2150
2137
|
patternProperties: {
|
|
2151
2138
|
[pattern]: jsonSchema,
|
|
@@ -2158,14 +2145,13 @@ function withRegexKeys<
|
|
|
2158
2145
|
*/
|
|
2159
2146
|
function withEnumKeys<
|
|
2160
2147
|
const T extends readonly (string | number)[] | StringEnum | NumberEnum,
|
|
2161
|
-
S extends JsonSchemaAnyBuilder<any, any
|
|
2148
|
+
S extends JsonSchemaAnyBuilder<any, any>,
|
|
2162
2149
|
K extends string | number = EnumKeyUnion<T>,
|
|
2163
2150
|
Opt extends boolean = SchemaOpt<S>,
|
|
2164
2151
|
>(
|
|
2165
2152
|
keys: T,
|
|
2166
2153
|
schema: S,
|
|
2167
2154
|
): JsonSchemaObjectBuilder<
|
|
2168
|
-
Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
|
|
2169
2155
|
Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
|
|
2170
2156
|
false
|
|
2171
2157
|
> {
|
|
@@ -2195,7 +2181,6 @@ function withEnumKeys<
|
|
|
2195
2181
|
const props = Object.fromEntries(typedValues.map(key => [key, schema])) as any
|
|
2196
2182
|
|
|
2197
2183
|
return new JsonSchemaObjectBuilder<
|
|
2198
|
-
Opt extends true ? { [P in K]?: SchemaIn<S> } : { [P in K]: SchemaIn<S> },
|
|
2199
2184
|
Opt extends true ? { [P in K]?: SchemaOut<S> } : { [P in K]: SchemaOut<S> },
|
|
2200
2185
|
false
|
|
2201
2186
|
>(props, { hasIsOfTypeCheck: false })
|
|
@@ -2256,10 +2241,6 @@ type IsAssignableRelaxed<A, B> =
|
|
|
2256
2241
|
? true
|
|
2257
2242
|
: false
|
|
2258
2243
|
|
|
2259
|
-
type OptionalDbEntityFields<T> = T extends BaseDBEntity
|
|
2260
|
-
? Omit<T, keyof BaseDBEntity> & Partial<Pick<T, keyof BaseDBEntity>>
|
|
2261
|
-
: T
|
|
2262
|
-
|
|
2263
2244
|
type ExactMatchBase<A, B> =
|
|
2264
2245
|
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
|
|
2265
2246
|
? (<T>() => T extends B ? 1 : 2) extends <T>() => T extends A ? 1 : 2
|
|
@@ -2274,29 +2255,15 @@ type ExactMatch<A, B> =
|
|
|
2274
2255
|
? true
|
|
2275
2256
|
: ExactMatchBase<Expand<StripIndexSignatureDeep<A>>, Expand<StripIndexSignatureDeep<B>>>
|
|
2276
2257
|
|
|
2277
|
-
type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any
|
|
2278
|
-
[K in keyof B]: B[K] extends JsonSchemaAnyBuilder<
|
|
2279
|
-
}[number]
|
|
2280
|
-
|
|
2281
|
-
type BuilderInUnion<B extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
2282
|
-
[K in keyof B]: B[K] extends JsonSchemaAnyBuilder<infer I, any, any> ? I : never
|
|
2258
|
+
type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any>[]> = {
|
|
2259
|
+
[K in keyof B]: B[K] extends JsonSchemaAnyBuilder<infer O, any> ? O : never
|
|
2283
2260
|
}[number]
|
|
2284
2261
|
|
|
2285
|
-
type
|
|
2286
|
-
[K in keyof D]: D[K] extends JsonSchemaTerminal<infer
|
|
2287
|
-
}[keyof D]
|
|
2288
|
-
|
|
2289
|
-
type AnyOfByOut<D extends Record<PropertyKey, JsonSchemaTerminal<any, any, any>>> = {
|
|
2290
|
-
[K in keyof D]: D[K] extends JsonSchemaTerminal<any, infer O, any> ? O : never
|
|
2262
|
+
type AnyOfByOut<D extends Record<PropertyKey, JsonSchemaTerminal<any, any>>> = {
|
|
2263
|
+
[K in keyof D]: D[K] extends JsonSchemaTerminal<infer O, any> ? O : never
|
|
2291
2264
|
}[keyof D]
|
|
2292
2265
|
|
|
2293
|
-
type
|
|
2294
|
-
|
|
2295
|
-
type AnyOfByInput<IN, P extends string, D = AnyOfByDiscriminant<IN, P>> = IN extends unknown
|
|
2296
|
-
? Omit<Partial<IN>, P> & { [K in P]?: D }
|
|
2297
|
-
: never
|
|
2298
|
-
|
|
2299
|
-
type BuilderFor<T> = JsonSchemaAnyBuilder<any, T, any>
|
|
2266
|
+
type BuilderFor<T> = JsonSchemaAnyBuilder<T, any>
|
|
2300
2267
|
|
|
2301
2268
|
interface JsonBuilderRuleOpt {
|
|
2302
2269
|
/**
|
|
@@ -2322,19 +2289,15 @@ type EnumKeyUnion<T> =
|
|
|
2322
2289
|
? T[keyof T]
|
|
2323
2290
|
: never
|
|
2324
2291
|
|
|
2325
|
-
type
|
|
2326
|
-
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
2292
|
+
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<infer OUT, any> ? OUT : never
|
|
2327
2293
|
type SchemaOpt<S> =
|
|
2328
|
-
S extends JsonSchemaAnyBuilder<any,
|
|
2329
|
-
|
|
2330
|
-
type TupleIn<T extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
2331
|
-
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<infer I, any, any> ? I : never
|
|
2332
|
-
}
|
|
2294
|
+
S extends JsonSchemaAnyBuilder<any, infer Opt> ? (Opt extends true ? true : false) : false
|
|
2333
2295
|
|
|
2334
|
-
type TupleOut<T extends readonly JsonSchemaAnyBuilder<any, any
|
|
2335
|
-
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<
|
|
2296
|
+
type TupleOut<T extends readonly JsonSchemaAnyBuilder<any, any>[]> = {
|
|
2297
|
+
[K in keyof T]: T[K] extends JsonSchemaAnyBuilder<infer O, any> ? O : never
|
|
2336
2298
|
}
|
|
2337
2299
|
|
|
2300
|
+
export type PostValidatonFn<OUT, OUT2> = (v: OUT) => OUT2
|
|
2338
2301
|
export type CustomValidatorFn = (v: any) => string | undefined
|
|
2339
2302
|
export type CustomConverterFn<OUT> = (v: any) => OUT
|
|
2340
2303
|
|