@naturalcycles/nodejs-lib 15.59.0 → 15.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _lazyValue } from '@naturalcycles/js-lib';
|
|
1
|
+
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib';
|
|
2
2
|
import { Set2 } from '@naturalcycles/js-lib/object';
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string';
|
|
4
4
|
import { Ajv } from 'ajv';
|
|
@@ -349,6 +349,25 @@ export function createAjv(opt) {
|
|
|
349
349
|
return false;
|
|
350
350
|
},
|
|
351
351
|
});
|
|
352
|
+
ajv.addKeyword({
|
|
353
|
+
keyword: 'IsoMonth',
|
|
354
|
+
type: 'string',
|
|
355
|
+
modifying: false,
|
|
356
|
+
errors: true,
|
|
357
|
+
schemaType: 'object',
|
|
358
|
+
validate: function validate(_opt, data, _schema, ctx) {
|
|
359
|
+
const isValid = isIsoMonthValid(data);
|
|
360
|
+
if (isValid)
|
|
361
|
+
return true;
|
|
362
|
+
validate.errors = [
|
|
363
|
+
{
|
|
364
|
+
instancePath: ctx?.instancePath ?? '',
|
|
365
|
+
message: `is an invalid IsoMonth`,
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
return false;
|
|
369
|
+
},
|
|
370
|
+
});
|
|
352
371
|
ajv.addKeyword({
|
|
353
372
|
keyword: 'errorMessages',
|
|
354
373
|
schemaType: 'object',
|
|
@@ -516,3 +535,21 @@ function isIsoTimezoneValid(s) {
|
|
|
516
535
|
return false; // min is -12:00
|
|
517
536
|
return true;
|
|
518
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* This is a performance optimized correct validation
|
|
540
|
+
* for ISO month formatted as "YYYY-MM".
|
|
541
|
+
*/
|
|
542
|
+
function isIsoMonthValid(s) {
|
|
543
|
+
// must be exactly "YYYY-MM"
|
|
544
|
+
if (s.length !== 7)
|
|
545
|
+
return false;
|
|
546
|
+
if (s.charCodeAt(4) !== DASH_CODE)
|
|
547
|
+
return false;
|
|
548
|
+
// fast parse numbers without substrings/Number()
|
|
549
|
+
const year = (s.charCodeAt(0) - ZERO_CODE) * 1000 +
|
|
550
|
+
(s.charCodeAt(1) - ZERO_CODE) * 100 +
|
|
551
|
+
(s.charCodeAt(2) - ZERO_CODE) * 10 +
|
|
552
|
+
(s.charCodeAt(3) - ZERO_CODE);
|
|
553
|
+
const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE);
|
|
554
|
+
return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]');
|
|
555
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Set2 } from '@naturalcycles/js-lib/object';
|
|
2
|
-
import { type AnyObject, type BaseDBEntity, type IANATimezone, type Inclusiveness, type IsoDate, type IsoDateTime, type NumberEnum, type StringEnum, type StringMap, type UnixTimestamp, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
2
|
+
import { type AnyObject, type BaseDBEntity, type IANATimezone, type Inclusiveness, type IsoDate, type IsoDateTime, type IsoMonth, type NumberEnum, type StringEnum, type StringMap, type UnixTimestamp, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
3
3
|
export declare const j: {
|
|
4
4
|
string(): JsonSchemaStringBuilder<string, string, false>;
|
|
5
5
|
number(): JsonSchemaNumberBuilder<number, number, false>;
|
|
@@ -62,12 +62,14 @@ export declare class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
62
62
|
* Same as if it would be JSON.stringified.
|
|
63
63
|
*/
|
|
64
64
|
build(): JsonSchema<IN, OUT>;
|
|
65
|
-
clone():
|
|
65
|
+
clone(): this;
|
|
66
|
+
cloneAndUpdateSchema(schema: Partial<JsonSchema>): this;
|
|
66
67
|
/**
|
|
67
68
|
* @experimental
|
|
68
69
|
*/
|
|
69
70
|
in: IN;
|
|
70
71
|
out: OUT;
|
|
72
|
+
opt: Opt;
|
|
71
73
|
}
|
|
72
74
|
export declare class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, OUT, Opt> {
|
|
73
75
|
protected setErrorMessage(ruleName: string, errorMessage: string | undefined): void;
|
|
@@ -137,6 +139,7 @@ export declare class JsonSchemaStringBuilder<IN extends string | undefined = str
|
|
|
137
139
|
*/
|
|
138
140
|
isoDate(): JsonSchemaIsoDateBuilder;
|
|
139
141
|
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt>;
|
|
142
|
+
isoMonth(): JsonSchemaIsoMonthBuilder;
|
|
140
143
|
/**
|
|
141
144
|
* Validates the string format to be JWT.
|
|
142
145
|
* Expects the JWT to be signed!
|
|
@@ -177,6 +180,11 @@ export interface JsonSchemaIsoDateOptions {
|
|
|
177
180
|
after?: string;
|
|
178
181
|
sameOrAfter?: string;
|
|
179
182
|
}
|
|
183
|
+
export declare class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<string | IsoDate, IsoMonth, Opt> {
|
|
184
|
+
constructor();
|
|
185
|
+
}
|
|
186
|
+
export interface JsonSchemaIsoMonthOptions {
|
|
187
|
+
}
|
|
180
188
|
export declare class JsonSchemaNumberBuilder<IN extends number | undefined = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
181
189
|
constructor();
|
|
182
190
|
/**
|
|
@@ -341,6 +349,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
341
349
|
Buffer?: true;
|
|
342
350
|
IsoDate?: JsonSchemaIsoDateOptions;
|
|
343
351
|
IsoDateTime?: true;
|
|
352
|
+
IsoMonth?: JsonSchemaIsoMonthOptions;
|
|
344
353
|
instanceof?: string | string[];
|
|
345
354
|
transform?: {
|
|
346
355
|
trim?: true;
|
|
@@ -147,13 +147,21 @@ export class JsonSchemaTerminal {
|
|
|
147
147
|
return jsonSchema;
|
|
148
148
|
}
|
|
149
149
|
clone() {
|
|
150
|
-
|
|
150
|
+
const cloned = Object.create(Object.getPrototypeOf(this));
|
|
151
|
+
cloned.schema = _deepCopy(this.schema);
|
|
152
|
+
return cloned;
|
|
153
|
+
}
|
|
154
|
+
cloneAndUpdateSchema(schema) {
|
|
155
|
+
const clone = this.clone();
|
|
156
|
+
_objectAssign(clone.schema, schema);
|
|
157
|
+
return clone;
|
|
151
158
|
}
|
|
152
159
|
/**
|
|
153
160
|
* @experimental
|
|
154
161
|
*/
|
|
155
162
|
in;
|
|
156
163
|
out;
|
|
164
|
+
opt;
|
|
157
165
|
}
|
|
158
166
|
export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
|
|
159
167
|
setErrorMessage(ruleName, errorMessage) {
|
|
@@ -178,48 +186,38 @@ export class JsonSchemaAnyBuilder extends JsonSchemaTerminal {
|
|
|
178
186
|
* ```
|
|
179
187
|
*/
|
|
180
188
|
isOfType() {
|
|
181
|
-
|
|
182
|
-
return this;
|
|
189
|
+
return this.cloneAndUpdateSchema({ hasIsOfTypeCheck: true });
|
|
183
190
|
}
|
|
184
191
|
$schema($schema) {
|
|
185
|
-
|
|
186
|
-
return this;
|
|
192
|
+
return this.cloneAndUpdateSchema({ $schema });
|
|
187
193
|
}
|
|
188
194
|
$schemaDraft7() {
|
|
189
|
-
this.$schema('http://json-schema.org/draft-07/schema#');
|
|
190
|
-
return this;
|
|
195
|
+
return this.$schema('http://json-schema.org/draft-07/schema#');
|
|
191
196
|
}
|
|
192
197
|
$id($id) {
|
|
193
|
-
|
|
194
|
-
return this;
|
|
198
|
+
return this.cloneAndUpdateSchema({ $id });
|
|
195
199
|
}
|
|
196
200
|
title(title) {
|
|
197
|
-
|
|
198
|
-
return this;
|
|
201
|
+
return this.cloneAndUpdateSchema({ title });
|
|
199
202
|
}
|
|
200
203
|
description(description) {
|
|
201
|
-
|
|
202
|
-
return this;
|
|
204
|
+
return this.cloneAndUpdateSchema({ description });
|
|
203
205
|
}
|
|
204
206
|
deprecated(deprecated = true) {
|
|
205
|
-
|
|
206
|
-
return this;
|
|
207
|
+
return this.cloneAndUpdateSchema({ deprecated });
|
|
207
208
|
}
|
|
208
209
|
type(type) {
|
|
209
|
-
|
|
210
|
-
return this;
|
|
210
|
+
return this.cloneAndUpdateSchema({ type });
|
|
211
211
|
}
|
|
212
212
|
default(v) {
|
|
213
|
-
|
|
214
|
-
return this;
|
|
213
|
+
return this.cloneAndUpdateSchema({ default: v });
|
|
215
214
|
}
|
|
216
215
|
instanceof(of) {
|
|
217
|
-
|
|
218
|
-
return this;
|
|
216
|
+
return this.cloneAndUpdateSchema({ type: 'object', instanceof: of });
|
|
219
217
|
}
|
|
220
218
|
optional() {
|
|
221
|
-
this.
|
|
222
|
-
return
|
|
219
|
+
const clone = this.cloneAndUpdateSchema({ optionalField: true });
|
|
220
|
+
return clone;
|
|
223
221
|
}
|
|
224
222
|
nullable() {
|
|
225
223
|
return new JsonSchemaAnyBuilder({
|
|
@@ -268,20 +266,18 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
268
266
|
return this.pattern(pattern.source, opt);
|
|
269
267
|
}
|
|
270
268
|
pattern(pattern, opt) {
|
|
269
|
+
const clone = this.cloneAndUpdateSchema({ pattern });
|
|
271
270
|
if (opt?.name)
|
|
272
|
-
|
|
271
|
+
clone.setErrorMessage('pattern', `is not a valid ${opt.name}`);
|
|
273
272
|
if (opt?.msg)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return this;
|
|
273
|
+
clone.setErrorMessage('pattern', opt.msg);
|
|
274
|
+
return clone;
|
|
277
275
|
}
|
|
278
276
|
minLength(minLength) {
|
|
279
|
-
|
|
280
|
-
return this;
|
|
277
|
+
return this.cloneAndUpdateSchema({ minLength });
|
|
281
278
|
}
|
|
282
279
|
maxLength(maxLength) {
|
|
283
|
-
|
|
284
|
-
return this;
|
|
280
|
+
return this.cloneAndUpdateSchema({ maxLength });
|
|
285
281
|
}
|
|
286
282
|
length(minLengthOrExactLength, maxLength) {
|
|
287
283
|
const maxLengthActual = maxLength ?? minLengthOrExactLength;
|
|
@@ -289,24 +285,27 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
289
285
|
}
|
|
290
286
|
email(opt) {
|
|
291
287
|
const defaultOptions = { checkTLD: true };
|
|
292
|
-
|
|
293
|
-
|
|
288
|
+
return this.cloneAndUpdateSchema({ email: { ...defaultOptions, ...opt } })
|
|
289
|
+
.trim()
|
|
290
|
+
.toLowerCase();
|
|
294
291
|
}
|
|
295
292
|
trim() {
|
|
296
|
-
|
|
297
|
-
return this;
|
|
293
|
+
return this.cloneAndUpdateSchema({ transform: { ...this.schema.transform, trim: true } });
|
|
298
294
|
}
|
|
299
295
|
toLowerCase() {
|
|
300
|
-
|
|
301
|
-
|
|
296
|
+
return this.cloneAndUpdateSchema({
|
|
297
|
+
transform: { ...this.schema.transform, toLowerCase: true },
|
|
298
|
+
});
|
|
302
299
|
}
|
|
303
300
|
toUpperCase() {
|
|
304
|
-
|
|
305
|
-
|
|
301
|
+
return this.cloneAndUpdateSchema({
|
|
302
|
+
transform: { ...this.schema.transform, toUpperCase: true },
|
|
303
|
+
});
|
|
306
304
|
}
|
|
307
305
|
truncate(toLength) {
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
return this.cloneAndUpdateSchema({
|
|
307
|
+
transform: { ...this.schema.transform, truncate: toLength },
|
|
308
|
+
});
|
|
310
309
|
}
|
|
311
310
|
branded() {
|
|
312
311
|
return this;
|
|
@@ -318,12 +317,13 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
318
317
|
* because this call effectively starts a new schema chain.
|
|
319
318
|
*/
|
|
320
319
|
isoDate() {
|
|
321
|
-
_objectAssign(this.schema, { IsoDate: {} });
|
|
322
320
|
return new JsonSchemaIsoDateBuilder();
|
|
323
321
|
}
|
|
324
322
|
isoDateTime() {
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded();
|
|
324
|
+
}
|
|
325
|
+
isoMonth() {
|
|
326
|
+
return new JsonSchemaIsoMonthBuilder();
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
329
|
* Validates the string format to be JWT.
|
|
@@ -385,29 +385,34 @@ export class JsonSchemaIsoDateBuilder extends JsonSchemaAnyBuilder {
|
|
|
385
385
|
});
|
|
386
386
|
}
|
|
387
387
|
before(date) {
|
|
388
|
-
|
|
389
|
-
return this;
|
|
388
|
+
return this.cloneAndUpdateSchema({ IsoDate: { before: date } });
|
|
390
389
|
}
|
|
391
390
|
sameOrBefore(date) {
|
|
392
|
-
|
|
393
|
-
return this;
|
|
391
|
+
return this.cloneAndUpdateSchema({ IsoDate: { sameOrBefore: date } });
|
|
394
392
|
}
|
|
395
393
|
after(date) {
|
|
396
|
-
|
|
397
|
-
return this;
|
|
394
|
+
return this.cloneAndUpdateSchema({ IsoDate: { after: date } });
|
|
398
395
|
}
|
|
399
396
|
sameOrAfter(date) {
|
|
400
|
-
|
|
401
|
-
return this;
|
|
397
|
+
return this.cloneAndUpdateSchema({ IsoDate: { sameOrAfter: date } });
|
|
402
398
|
}
|
|
403
399
|
between(fromDate, toDate, incl) {
|
|
400
|
+
let schemaPatch = {};
|
|
404
401
|
if (incl === '[)') {
|
|
405
|
-
|
|
402
|
+
schemaPatch = { IsoDate: { sameOrAfter: fromDate, before: toDate } };
|
|
406
403
|
}
|
|
407
404
|
else if (incl === '[]') {
|
|
408
|
-
|
|
405
|
+
schemaPatch = { IsoDate: { sameOrAfter: fromDate, sameOrBefore: toDate } };
|
|
409
406
|
}
|
|
410
|
-
return this;
|
|
407
|
+
return this.cloneAndUpdateSchema(schemaPatch);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
export class JsonSchemaIsoMonthBuilder extends JsonSchemaAnyBuilder {
|
|
411
|
+
constructor() {
|
|
412
|
+
super({
|
|
413
|
+
type: 'string',
|
|
414
|
+
IsoMonth: {},
|
|
415
|
+
});
|
|
411
416
|
}
|
|
412
417
|
}
|
|
413
418
|
export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
@@ -435,31 +440,25 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
|
435
440
|
return newBuilder;
|
|
436
441
|
}
|
|
437
442
|
integer() {
|
|
438
|
-
|
|
439
|
-
return this;
|
|
443
|
+
return this.cloneAndUpdateSchema({ type: 'integer' });
|
|
440
444
|
}
|
|
441
445
|
branded() {
|
|
442
446
|
return this;
|
|
443
447
|
}
|
|
444
448
|
multipleOf(multipleOf) {
|
|
445
|
-
|
|
446
|
-
return this;
|
|
449
|
+
return this.cloneAndUpdateSchema({ multipleOf });
|
|
447
450
|
}
|
|
448
451
|
min(minimum) {
|
|
449
|
-
|
|
450
|
-
return this;
|
|
452
|
+
return this.cloneAndUpdateSchema({ minimum });
|
|
451
453
|
}
|
|
452
454
|
exclusiveMin(exclusiveMinimum) {
|
|
453
|
-
|
|
454
|
-
return this;
|
|
455
|
+
return this.cloneAndUpdateSchema({ exclusiveMinimum });
|
|
455
456
|
}
|
|
456
457
|
max(maximum) {
|
|
457
|
-
|
|
458
|
-
return this;
|
|
458
|
+
return this.cloneAndUpdateSchema({ maximum });
|
|
459
459
|
}
|
|
460
460
|
exclusiveMax(exclusiveMaximum) {
|
|
461
|
-
|
|
462
|
-
return this;
|
|
461
|
+
return this.cloneAndUpdateSchema({ exclusiveMaximum });
|
|
463
462
|
}
|
|
464
463
|
lessThan(value) {
|
|
465
464
|
return this.exclusiveMax(value);
|
|
@@ -584,15 +583,13 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
584
583
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
585
584
|
*/
|
|
586
585
|
allowAdditionalProperties() {
|
|
587
|
-
|
|
588
|
-
return this;
|
|
586
|
+
return this.cloneAndUpdateSchema({ additionalProperties: true });
|
|
589
587
|
}
|
|
590
588
|
extend(props) {
|
|
591
|
-
const
|
|
592
|
-
_objectAssign(newBuilder.schema, _deepCopy(this.schema));
|
|
589
|
+
const clone = this.cloneAndUpdateSchema(_deepCopy(this.schema));
|
|
593
590
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props);
|
|
594
|
-
mergeJsonSchemaObjects(
|
|
595
|
-
return
|
|
591
|
+
mergeJsonSchemaObjects(clone.schema, incomingSchemaBuilder.schema);
|
|
592
|
+
return clone;
|
|
596
593
|
}
|
|
597
594
|
/**
|
|
598
595
|
* Extends the current schema with `id`, `created` and `updated` according to NC DB conventions.
|
|
@@ -606,12 +603,10 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
606
603
|
});
|
|
607
604
|
}
|
|
608
605
|
minProperties(minProperties) {
|
|
609
|
-
|
|
610
|
-
return this;
|
|
606
|
+
return this.cloneAndUpdateSchema({ minProperties });
|
|
611
607
|
}
|
|
612
608
|
maxProperties(maxProperties) {
|
|
613
|
-
|
|
614
|
-
return this;
|
|
609
|
+
return this.cloneAndUpdateSchema({ maxProperties });
|
|
615
610
|
}
|
|
616
611
|
}
|
|
617
612
|
export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
@@ -644,8 +639,7 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
644
639
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
645
640
|
*/
|
|
646
641
|
allowAdditionalProperties() {
|
|
647
|
-
|
|
648
|
-
return this;
|
|
642
|
+
return this.cloneAndUpdateSchema({ additionalProperties: true });
|
|
649
643
|
}
|
|
650
644
|
extend(props) {
|
|
651
645
|
const newBuilder = new JsonSchemaObjectInferringBuilder();
|
|
@@ -674,12 +668,10 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
|
|
|
674
668
|
});
|
|
675
669
|
}
|
|
676
670
|
minLength(minItems) {
|
|
677
|
-
|
|
678
|
-
return this;
|
|
671
|
+
return this.cloneAndUpdateSchema({ minItems });
|
|
679
672
|
}
|
|
680
673
|
maxLength(maxItems) {
|
|
681
|
-
|
|
682
|
-
return this;
|
|
674
|
+
return this.cloneAndUpdateSchema({ maxItems });
|
|
683
675
|
}
|
|
684
676
|
length(minItemsOrExact, maxItems) {
|
|
685
677
|
const maxItemsActual = maxItems ?? minItemsOrExact;
|
|
@@ -689,8 +681,7 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
|
|
|
689
681
|
return this.minLength(length).maxLength(length);
|
|
690
682
|
}
|
|
691
683
|
unique() {
|
|
692
|
-
|
|
693
|
-
return this;
|
|
684
|
+
return this.cloneAndUpdateSchema({ uniqueItems: true });
|
|
694
685
|
}
|
|
695
686
|
}
|
|
696
687
|
export class JsonSchemaSet2Builder extends JsonSchemaAnyBuilder {
|
|
@@ -701,12 +692,10 @@ export class JsonSchemaSet2Builder extends JsonSchemaAnyBuilder {
|
|
|
701
692
|
});
|
|
702
693
|
}
|
|
703
694
|
min(minItems) {
|
|
704
|
-
|
|
705
|
-
return this;
|
|
695
|
+
return this.cloneAndUpdateSchema({ minItems });
|
|
706
696
|
}
|
|
707
697
|
max(maxItems) {
|
|
708
|
-
|
|
709
|
-
return this;
|
|
698
|
+
return this.cloneAndUpdateSchema({ maxItems });
|
|
710
699
|
}
|
|
711
700
|
}
|
|
712
701
|
export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder {
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { _lazyValue } from '@naturalcycles/js-lib'
|
|
1
|
+
import { _isBetween, _lazyValue } from '@naturalcycles/js-lib'
|
|
2
2
|
import { Set2 } from '@naturalcycles/js-lib/object'
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
4
|
import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
5
5
|
import { Ajv, type Options, type ValidateFunction } from 'ajv'
|
|
6
6
|
import { validTLDs } from '../tlds.js'
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
JsonSchemaIsoDateOptions,
|
|
9
|
+
JsonSchemaIsoMonthOptions,
|
|
10
|
+
JsonSchemaStringEmailOptions,
|
|
11
|
+
} from './jsonSchemaBuilder.js'
|
|
8
12
|
|
|
9
13
|
/* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
|
|
10
14
|
// oxlint-disable unicorn/prefer-code-point
|
|
@@ -383,7 +387,7 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
383
387
|
modifying: false,
|
|
384
388
|
errors: true,
|
|
385
389
|
schemaType: 'boolean',
|
|
386
|
-
validate: function validate(_opt:
|
|
390
|
+
validate: function validate(_opt: JsonSchemaIsoMonthOptions, data: string, _schema, ctx) {
|
|
387
391
|
const isValid = isIsoDateTimeValid(data)
|
|
388
392
|
if (isValid) return true
|
|
389
393
|
;(validate as any).errors = [
|
|
@@ -396,6 +400,25 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
396
400
|
},
|
|
397
401
|
})
|
|
398
402
|
|
|
403
|
+
ajv.addKeyword({
|
|
404
|
+
keyword: 'IsoMonth',
|
|
405
|
+
type: 'string',
|
|
406
|
+
modifying: false,
|
|
407
|
+
errors: true,
|
|
408
|
+
schemaType: 'object',
|
|
409
|
+
validate: function validate(_opt: true, data: string, _schema, ctx) {
|
|
410
|
+
const isValid = isIsoMonthValid(data)
|
|
411
|
+
if (isValid) return true
|
|
412
|
+
;(validate as any).errors = [
|
|
413
|
+
{
|
|
414
|
+
instancePath: ctx?.instancePath ?? '',
|
|
415
|
+
message: `is an invalid IsoMonth`,
|
|
416
|
+
},
|
|
417
|
+
]
|
|
418
|
+
return false
|
|
419
|
+
},
|
|
420
|
+
})
|
|
421
|
+
|
|
399
422
|
ajv.addKeyword({
|
|
400
423
|
keyword: 'errorMessages',
|
|
401
424
|
schemaType: 'object',
|
|
@@ -577,3 +600,24 @@ function isIsoTimezoneValid(s: string): boolean {
|
|
|
577
600
|
|
|
578
601
|
return true
|
|
579
602
|
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* This is a performance optimized correct validation
|
|
606
|
+
* for ISO month formatted as "YYYY-MM".
|
|
607
|
+
*/
|
|
608
|
+
function isIsoMonthValid(s: string): boolean {
|
|
609
|
+
// must be exactly "YYYY-MM"
|
|
610
|
+
if (s.length !== 7) return false
|
|
611
|
+
if (s.charCodeAt(4) !== DASH_CODE) return false
|
|
612
|
+
|
|
613
|
+
// fast parse numbers without substrings/Number()
|
|
614
|
+
const year =
|
|
615
|
+
(s.charCodeAt(0) - ZERO_CODE) * 1000 +
|
|
616
|
+
(s.charCodeAt(1) - ZERO_CODE) * 100 +
|
|
617
|
+
(s.charCodeAt(2) - ZERO_CODE) * 10 +
|
|
618
|
+
(s.charCodeAt(3) - ZERO_CODE)
|
|
619
|
+
|
|
620
|
+
const month = (s.charCodeAt(5) - ZERO_CODE) * 10 + (s.charCodeAt(6) - ZERO_CODE)
|
|
621
|
+
|
|
622
|
+
return _isBetween(year, 1900, 2500, '[]') && _isBetween(month, 1, 12, '[]')
|
|
623
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type Inclusiveness,
|
|
20
20
|
type IsoDate,
|
|
21
21
|
type IsoDateTime,
|
|
22
|
+
type IsoMonth,
|
|
22
23
|
JWT_REGEX,
|
|
23
24
|
type NumberEnum,
|
|
24
25
|
type StringEnum,
|
|
@@ -232,8 +233,16 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
232
233
|
return jsonSchema
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
clone():
|
|
236
|
-
|
|
236
|
+
clone(): this {
|
|
237
|
+
const cloned = Object.create(Object.getPrototypeOf(this))
|
|
238
|
+
cloned.schema = _deepCopy(this.schema)
|
|
239
|
+
return cloned
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
cloneAndUpdateSchema(schema: Partial<JsonSchema>): this {
|
|
243
|
+
const clone = this.clone()
|
|
244
|
+
_objectAssign(clone.schema, schema)
|
|
245
|
+
return clone
|
|
237
246
|
}
|
|
238
247
|
|
|
239
248
|
/**
|
|
@@ -241,6 +250,7 @@ export class JsonSchemaTerminal<IN, OUT, Opt> {
|
|
|
241
250
|
*/
|
|
242
251
|
in!: IN
|
|
243
252
|
out!: OUT
|
|
253
|
+
opt!: Opt
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, OUT, Opt> {
|
|
@@ -267,58 +277,48 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> extends JsonSchemaTerminal<IN, O
|
|
|
267
277
|
* ```
|
|
268
278
|
*/
|
|
269
279
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
|
|
270
|
-
|
|
271
|
-
return this as any
|
|
280
|
+
return this.cloneAndUpdateSchema({ hasIsOfTypeCheck: true }) as any
|
|
272
281
|
}
|
|
273
282
|
|
|
274
283
|
$schema($schema: string): this {
|
|
275
|
-
|
|
276
|
-
return this
|
|
284
|
+
return this.cloneAndUpdateSchema({ $schema })
|
|
277
285
|
}
|
|
278
286
|
|
|
279
287
|
$schemaDraft7(): this {
|
|
280
|
-
this.$schema('http://json-schema.org/draft-07/schema#')
|
|
281
|
-
return this
|
|
288
|
+
return this.$schema('http://json-schema.org/draft-07/schema#')
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
$id($id: string): this {
|
|
285
|
-
|
|
286
|
-
return this
|
|
292
|
+
return this.cloneAndUpdateSchema({ $id })
|
|
287
293
|
}
|
|
288
294
|
|
|
289
295
|
title(title: string): this {
|
|
290
|
-
|
|
291
|
-
return this
|
|
296
|
+
return this.cloneAndUpdateSchema({ title })
|
|
292
297
|
}
|
|
293
298
|
|
|
294
299
|
description(description: string): this {
|
|
295
|
-
|
|
296
|
-
return this
|
|
300
|
+
return this.cloneAndUpdateSchema({ description })
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
deprecated(deprecated = true): this {
|
|
300
|
-
|
|
301
|
-
return this
|
|
304
|
+
return this.cloneAndUpdateSchema({ deprecated })
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
type(type: string): this {
|
|
305
|
-
|
|
306
|
-
return this
|
|
308
|
+
return this.cloneAndUpdateSchema({ type })
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
default(v: any): this {
|
|
310
|
-
|
|
311
|
-
return this
|
|
312
|
+
return this.cloneAndUpdateSchema({ default: v })
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
instanceof(of: string): this {
|
|
315
|
-
|
|
316
|
-
return this
|
|
316
|
+
return this.cloneAndUpdateSchema({ type: 'object', instanceof: of })
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
optional(): JsonSchemaAnyBuilder<IN | undefined, OUT | undefined, true> {
|
|
320
|
-
this.
|
|
321
|
-
return
|
|
320
|
+
const clone = this.cloneAndUpdateSchema({ optionalField: true })
|
|
321
|
+
return clone as unknown as JsonSchemaAnyBuilder<IN | undefined, OUT | undefined, true>
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
nullable(): JsonSchemaAnyBuilder<IN | null, OUT | null, Opt> {
|
|
@@ -386,20 +386,18 @@ export class JsonSchemaStringBuilder<
|
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
pattern(pattern: string, opt?: JsonBuilderRuleOpt): this {
|
|
389
|
-
|
|
390
|
-
if (opt?.
|
|
391
|
-
|
|
392
|
-
return
|
|
389
|
+
const clone = this.cloneAndUpdateSchema({ pattern })
|
|
390
|
+
if (opt?.name) clone.setErrorMessage('pattern', `is not a valid ${opt.name}`)
|
|
391
|
+
if (opt?.msg) clone.setErrorMessage('pattern', opt.msg)
|
|
392
|
+
return clone
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
minLength(minLength: number): this {
|
|
396
|
-
|
|
397
|
-
return this
|
|
396
|
+
return this.cloneAndUpdateSchema({ minLength })
|
|
398
397
|
}
|
|
399
398
|
|
|
400
399
|
maxLength(maxLength: number): this {
|
|
401
|
-
|
|
402
|
-
return this
|
|
400
|
+
return this.cloneAndUpdateSchema({ maxLength })
|
|
403
401
|
}
|
|
404
402
|
|
|
405
403
|
length(exactLength: number): this
|
|
@@ -411,28 +409,31 @@ export class JsonSchemaStringBuilder<
|
|
|
411
409
|
|
|
412
410
|
email(opt?: Partial<JsonSchemaStringEmailOptions>): this {
|
|
413
411
|
const defaultOptions: JsonSchemaStringEmailOptions = { checkTLD: true }
|
|
414
|
-
|
|
415
|
-
|
|
412
|
+
return this.cloneAndUpdateSchema({ email: { ...defaultOptions, ...opt } })
|
|
413
|
+
.trim()
|
|
414
|
+
.toLowerCase()
|
|
416
415
|
}
|
|
417
416
|
|
|
418
417
|
trim(): this {
|
|
419
|
-
|
|
420
|
-
return this
|
|
418
|
+
return this.cloneAndUpdateSchema({ transform: { ...this.schema.transform, trim: true } })
|
|
421
419
|
}
|
|
422
420
|
|
|
423
421
|
toLowerCase(): this {
|
|
424
|
-
|
|
425
|
-
|
|
422
|
+
return this.cloneAndUpdateSchema({
|
|
423
|
+
transform: { ...this.schema.transform, toLowerCase: true },
|
|
424
|
+
})
|
|
426
425
|
}
|
|
427
426
|
|
|
428
427
|
toUpperCase(): this {
|
|
429
|
-
|
|
430
|
-
|
|
428
|
+
return this.cloneAndUpdateSchema({
|
|
429
|
+
transform: { ...this.schema.transform, toUpperCase: true },
|
|
430
|
+
})
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
truncate(toLength: number): this {
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
return this.cloneAndUpdateSchema({
|
|
435
|
+
transform: { ...this.schema.transform, truncate: toLength },
|
|
436
|
+
})
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
branded<B extends string>(): JsonSchemaStringBuilder<B, B, Opt> {
|
|
@@ -446,13 +447,15 @@ export class JsonSchemaStringBuilder<
|
|
|
446
447
|
* because this call effectively starts a new schema chain.
|
|
447
448
|
*/
|
|
448
449
|
isoDate(): JsonSchemaIsoDateBuilder {
|
|
449
|
-
_objectAssign(this.schema, { IsoDate: {} })
|
|
450
450
|
return new JsonSchemaIsoDateBuilder()
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt> {
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
return this.cloneAndUpdateSchema({ IsoDateTime: true }).branded<IsoDateTime>()
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
isoMonth(): JsonSchemaIsoMonthBuilder {
|
|
458
|
+
return new JsonSchemaIsoMonthBuilder()
|
|
456
459
|
}
|
|
457
460
|
|
|
458
461
|
/**
|
|
@@ -537,33 +540,31 @@ export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonS
|
|
|
537
540
|
}
|
|
538
541
|
|
|
539
542
|
before(date: string): this {
|
|
540
|
-
|
|
541
|
-
return this
|
|
543
|
+
return this.cloneAndUpdateSchema({ IsoDate: { before: date } })
|
|
542
544
|
}
|
|
543
545
|
|
|
544
546
|
sameOrBefore(date: string): this {
|
|
545
|
-
|
|
546
|
-
return this
|
|
547
|
+
return this.cloneAndUpdateSchema({ IsoDate: { sameOrBefore: date } })
|
|
547
548
|
}
|
|
548
549
|
|
|
549
550
|
after(date: string): this {
|
|
550
|
-
|
|
551
|
-
return this
|
|
551
|
+
return this.cloneAndUpdateSchema({ IsoDate: { after: date } })
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
sameOrAfter(date: string): this {
|
|
555
|
-
|
|
556
|
-
return this
|
|
555
|
+
return this.cloneAndUpdateSchema({ IsoDate: { sameOrAfter: date } })
|
|
557
556
|
}
|
|
558
557
|
|
|
559
558
|
between(fromDate: string, toDate: string, incl: Inclusiveness): this {
|
|
559
|
+
let schemaPatch: Partial<JsonSchema> = {}
|
|
560
|
+
|
|
560
561
|
if (incl === '[)') {
|
|
561
|
-
|
|
562
|
+
schemaPatch = { IsoDate: { sameOrAfter: fromDate, before: toDate } }
|
|
562
563
|
} else if (incl === '[]') {
|
|
563
|
-
|
|
564
|
+
schemaPatch = { IsoDate: { sameOrAfter: fromDate, sameOrBefore: toDate } }
|
|
564
565
|
}
|
|
565
566
|
|
|
566
|
-
return this
|
|
567
|
+
return this.cloneAndUpdateSchema(schemaPatch)
|
|
567
568
|
}
|
|
568
569
|
}
|
|
569
570
|
|
|
@@ -574,6 +575,21 @@ export interface JsonSchemaIsoDateOptions {
|
|
|
574
575
|
sameOrAfter?: string
|
|
575
576
|
}
|
|
576
577
|
|
|
578
|
+
export class JsonSchemaIsoMonthBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
|
|
579
|
+
string | IsoDate,
|
|
580
|
+
IsoMonth,
|
|
581
|
+
Opt
|
|
582
|
+
> {
|
|
583
|
+
constructor() {
|
|
584
|
+
super({
|
|
585
|
+
type: 'string',
|
|
586
|
+
IsoMonth: {},
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export interface JsonSchemaIsoMonthOptions {}
|
|
592
|
+
|
|
577
593
|
export class JsonSchemaNumberBuilder<
|
|
578
594
|
IN extends number | undefined = number,
|
|
579
595
|
OUT = IN,
|
|
@@ -591,6 +607,7 @@ export class JsonSchemaNumberBuilder<
|
|
|
591
607
|
* This `optionalValues` feature only works when the current schema is nested in an object or array schema,
|
|
592
608
|
* due to how mutability works in Ajv.
|
|
593
609
|
*/
|
|
610
|
+
|
|
594
611
|
override optional(
|
|
595
612
|
optionalValues?: number[],
|
|
596
613
|
): JsonSchemaNumberBuilder<IN | undefined, OUT | undefined, true> {
|
|
@@ -613,8 +630,7 @@ export class JsonSchemaNumberBuilder<
|
|
|
613
630
|
}
|
|
614
631
|
|
|
615
632
|
integer(): this {
|
|
616
|
-
|
|
617
|
-
return this
|
|
633
|
+
return this.cloneAndUpdateSchema({ type: 'integer' })
|
|
618
634
|
}
|
|
619
635
|
|
|
620
636
|
branded<B extends number>(): JsonSchemaNumberBuilder<B, B, Opt> {
|
|
@@ -622,28 +638,23 @@ export class JsonSchemaNumberBuilder<
|
|
|
622
638
|
}
|
|
623
639
|
|
|
624
640
|
multipleOf(multipleOf: number): this {
|
|
625
|
-
|
|
626
|
-
return this
|
|
641
|
+
return this.cloneAndUpdateSchema({ multipleOf })
|
|
627
642
|
}
|
|
628
643
|
|
|
629
644
|
min(minimum: number): this {
|
|
630
|
-
|
|
631
|
-
return this
|
|
645
|
+
return this.cloneAndUpdateSchema({ minimum })
|
|
632
646
|
}
|
|
633
647
|
|
|
634
648
|
exclusiveMin(exclusiveMinimum: number): this {
|
|
635
|
-
|
|
636
|
-
return this
|
|
649
|
+
return this.cloneAndUpdateSchema({ exclusiveMinimum })
|
|
637
650
|
}
|
|
638
651
|
|
|
639
652
|
max(maximum: number): this {
|
|
640
|
-
|
|
641
|
-
return this
|
|
653
|
+
return this.cloneAndUpdateSchema({ maximum })
|
|
642
654
|
}
|
|
643
655
|
|
|
644
656
|
exclusiveMax(exclusiveMaximum: number): this {
|
|
645
|
-
|
|
646
|
-
return this
|
|
657
|
+
return this.cloneAndUpdateSchema({ exclusiveMaximum })
|
|
647
658
|
}
|
|
648
659
|
|
|
649
660
|
lessThan(value: number): this {
|
|
@@ -812,21 +823,24 @@ export class JsonSchemaObjectBuilder<
|
|
|
812
823
|
/**
|
|
813
824
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
814
825
|
*/
|
|
826
|
+
|
|
815
827
|
allowAdditionalProperties(): this {
|
|
816
|
-
|
|
817
|
-
return this
|
|
828
|
+
return this.cloneAndUpdateSchema({ additionalProperties: true })
|
|
818
829
|
}
|
|
819
830
|
|
|
820
831
|
extend<IN2 extends AnyObject>(
|
|
821
832
|
props: AnyObject,
|
|
822
833
|
): JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt> {
|
|
823
|
-
const
|
|
824
|
-
|
|
834
|
+
const clone = this.cloneAndUpdateSchema(_deepCopy(this.schema)) as JsonSchemaObjectBuilder<
|
|
835
|
+
IN & IN2,
|
|
836
|
+
OUT & IN2,
|
|
837
|
+
Opt
|
|
838
|
+
>
|
|
825
839
|
|
|
826
840
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder<IN2, IN2, false>(props)
|
|
827
|
-
mergeJsonSchemaObjects(
|
|
841
|
+
mergeJsonSchemaObjects(clone.schema as any, incomingSchemaBuilder.schema as any)
|
|
828
842
|
|
|
829
|
-
return
|
|
843
|
+
return clone
|
|
830
844
|
}
|
|
831
845
|
|
|
832
846
|
/**
|
|
@@ -842,13 +856,11 @@ export class JsonSchemaObjectBuilder<
|
|
|
842
856
|
}
|
|
843
857
|
|
|
844
858
|
minProperties(minProperties: number): this {
|
|
845
|
-
|
|
846
|
-
return this
|
|
859
|
+
return this.cloneAndUpdateSchema({ minProperties })
|
|
847
860
|
}
|
|
848
861
|
|
|
849
862
|
maxProperties(maxProperties: number): this {
|
|
850
|
-
|
|
851
|
-
return this
|
|
863
|
+
return this.cloneAndUpdateSchema({ maxProperties })
|
|
852
864
|
}
|
|
853
865
|
}
|
|
854
866
|
|
|
@@ -928,9 +940,9 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
928
940
|
/**
|
|
929
941
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
930
942
|
*/
|
|
943
|
+
|
|
931
944
|
allowAdditionalProperties(): this {
|
|
932
|
-
|
|
933
|
-
return this
|
|
945
|
+
return this.cloneAndUpdateSchema({ additionalProperties: true })
|
|
934
946
|
}
|
|
935
947
|
|
|
936
948
|
extend<NEW_PROPS extends Record<string, JsonSchemaAnyBuilder<any, any, any>>>(
|
|
@@ -985,13 +997,11 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
985
997
|
}
|
|
986
998
|
|
|
987
999
|
minLength(minItems: number): this {
|
|
988
|
-
|
|
989
|
-
return this
|
|
1000
|
+
return this.cloneAndUpdateSchema({ minItems })
|
|
990
1001
|
}
|
|
991
1002
|
|
|
992
1003
|
maxLength(maxItems: number): this {
|
|
993
|
-
|
|
994
|
-
return this
|
|
1004
|
+
return this.cloneAndUpdateSchema({ maxItems })
|
|
995
1005
|
}
|
|
996
1006
|
|
|
997
1007
|
length(exactLength: number): this
|
|
@@ -1006,8 +1016,7 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
1006
1016
|
}
|
|
1007
1017
|
|
|
1008
1018
|
unique(): this {
|
|
1009
|
-
|
|
1010
|
-
return this
|
|
1019
|
+
return this.cloneAndUpdateSchema({ uniqueItems: true })
|
|
1011
1020
|
}
|
|
1012
1021
|
}
|
|
1013
1022
|
|
|
@@ -1024,13 +1033,11 @@ export class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<
|
|
|
1024
1033
|
}
|
|
1025
1034
|
|
|
1026
1035
|
min(minItems: number): this {
|
|
1027
|
-
|
|
1028
|
-
return this
|
|
1036
|
+
return this.cloneAndUpdateSchema({ minItems })
|
|
1029
1037
|
}
|
|
1030
1038
|
|
|
1031
1039
|
max(maxItems: number): this {
|
|
1032
|
-
|
|
1033
|
-
return this
|
|
1040
|
+
return this.cloneAndUpdateSchema({ maxItems })
|
|
1034
1041
|
}
|
|
1035
1042
|
}
|
|
1036
1043
|
|
|
@@ -1139,6 +1146,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1139
1146
|
Buffer?: true
|
|
1140
1147
|
IsoDate?: JsonSchemaIsoDateOptions
|
|
1141
1148
|
IsoDateTime?: true
|
|
1149
|
+
IsoMonth?: JsonSchemaIsoMonthOptions
|
|
1142
1150
|
instanceof?: string | string[]
|
|
1143
1151
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
1144
1152
|
errorMessages?: StringMap<string>
|