@naturalcycles/nodejs-lib 15.96.1 → 15.97.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.
@@ -184,7 +184,18 @@ export declare class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
184
184
  type(type: string): this;
185
185
  default(v: any): this;
186
186
  instanceof(of: string): this;
187
- optional(): JBuilder<OUT | undefined, true>;
187
+ /**
188
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
189
+ *
190
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
191
+ * due to how mutability works in Ajv.
192
+ *
193
+ * Make sure this `optional()` call is at the end of your call chain.
194
+ *
195
+ * When `null` is included in optionalValues, the return type becomes `JSchema`
196
+ * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
197
+ */
198
+ optional<T extends readonly (string | number | boolean | null)[] | undefined = undefined>(optionalValues?: T): T extends readonly (string | number | boolean | null)[] ? JSchema<OUT | undefined, true> : JBuilder<OUT | undefined, true>;
188
199
  nullable(): JBuilder<OUT | null, Opt>;
189
200
  /**
190
201
  * @deprecated
@@ -216,18 +227,6 @@ export declare class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
216
227
  }
217
228
  export declare class JString<OUT extends string | undefined = string, Opt extends boolean = false> extends JBuilder<OUT, Opt> {
218
229
  constructor();
219
- /**
220
- * @param optionalValues List of values that should be considered/converted as `undefined`.
221
- *
222
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
223
- * due to how mutability works in Ajv.
224
- *
225
- * Make sure this `optional()` call is at the end of your call chain.
226
- *
227
- * When `null` is included in optionalValues, the return type becomes `JSchema`
228
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
229
- */
230
- optional<T extends readonly (string | null)[] | undefined = undefined>(optionalValues?: T): T extends readonly (infer U)[] ? null extends U ? JSchema<OUT | undefined, true> : JString<OUT | undefined, true> : JString<OUT | undefined, true>;
231
230
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this;
232
231
  pattern(pattern: string, opt?: JsonBuilderRuleOpt): this;
233
232
  minLength(minLength: number): this;
@@ -277,16 +276,6 @@ export interface JsonSchemaStringEmailOptions {
277
276
  }
278
277
  export declare class JIsoDate<Opt extends boolean = false> extends JBuilder<IsoDate, Opt> {
279
278
  constructor();
280
- /**
281
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
282
- *
283
- * This `null` feature only works when the current schema is nested in an object or array schema,
284
- * due to how mutability works in Ajv.
285
- *
286
- * When `null` is passed, the return type becomes `JSchema`
287
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
288
- */
289
- optional<N extends null | undefined = undefined>(nullValue?: N): N extends null ? JSchema<IsoDate | undefined, true> : JBuilder<IsoDate | undefined, true>;
290
279
  before(date: string): this;
291
280
  sameOrBefore(date: string): this;
292
281
  after(date: string): this;
@@ -303,18 +292,6 @@ export interface JsonSchemaIsoMonthOptions {
303
292
  }
304
293
  export declare class JNumber<OUT extends number | undefined = number, Opt extends boolean = false> extends JBuilder<OUT, Opt> {
305
294
  constructor();
306
- /**
307
- * @param optionalValues List of values that should be considered/converted as `undefined`.
308
- *
309
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
310
- * due to how mutability works in Ajv.
311
- *
312
- * Make sure this `optional()` call is at the end of your call chain.
313
- *
314
- * When `null` is included in optionalValues, the return type becomes `JSchema`
315
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
316
- */
317
- optional<T extends readonly (number | null)[] | undefined = undefined>(optionalValues?: T): T extends readonly (infer U)[] ? null extends U ? JSchema<OUT | undefined, true> : JNumber<OUT | undefined, true> : JNumber<OUT | undefined, true>;
318
295
  integer(): this;
319
296
  branded<B extends number>(): JNumber<B, Opt>;
320
297
  multipleOf(multipleOf: number): this;
@@ -347,26 +324,9 @@ export declare class JNumber<OUT extends number | undefined = number, Opt extend
347
324
  }
348
325
  export declare class JBoolean<OUT extends boolean | undefined = boolean, Opt extends boolean = false> extends JBuilder<OUT, Opt> {
349
326
  constructor();
350
- /**
351
- * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
352
- *
353
- * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
354
- * due to how mutability works in Ajv.
355
- */
356
- optional(optionalValue?: boolean): JBoolean<OUT | undefined, true>;
357
327
  }
358
328
  export declare class JObject<OUT extends AnyObject, Opt extends boolean = false> extends JBuilder<OUT, Opt> {
359
329
  constructor(props?: AnyObject, opt?: JObjectOpts);
360
- /**
361
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
362
- *
363
- * This `null` feature only works when the current schema is nested in an object or array schema,
364
- * due to how mutability works in Ajv.
365
- *
366
- * When `null` is passed, the return type becomes `JSchema`
367
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
368
- */
369
- optional<N extends null | undefined = undefined>(nullValue?: N): N extends null ? JSchema<OUT | undefined, true> : JBuilder<OUT | undefined, true>;
370
330
  /**
371
331
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
372
332
  */
@@ -401,35 +361,17 @@ interface JObjectOpts {
401
361
  patternProperties?: StringMap<JsonSchema<any>>;
402
362
  keySchema?: JsonSchema;
403
363
  }
404
- export declare class JObjectInfer<PROPS extends Record<string, JBuilder<any, any>>, Opt extends boolean = false> extends JBuilder<Expand<{
405
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
364
+ export declare class JObjectInfer<PROPS extends Record<string, JSchema<any, any>>, Opt extends boolean = false> extends JBuilder<Expand<{
365
+ [K in keyof PROPS as PROPS[K] extends JSchema<any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JSchema<infer OUT, any> ? OUT : never;
406
366
  } & {
407
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
367
+ [K in keyof PROPS as PROPS[K] extends JSchema<any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JSchema<infer OUT, any> ? OUT : never;
408
368
  }>, Opt> {
409
369
  constructor(props?: PROPS);
410
- /**
411
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
412
- *
413
- * This `null` feature only works when the current schema is nested in an object or array schema,
414
- * due to how mutability works in Ajv.
415
- *
416
- * When `null` is passed, the return type becomes `JSchema`
417
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
418
- */
419
- optional<N extends null | undefined = undefined>(nullValue?: N): N extends null ? JSchema<Expand<{
420
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
421
- } & {
422
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
423
- }> | undefined, true> : JBuilder<Expand<{
424
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
425
- } & {
426
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt> ? IsOpt extends true ? K : never : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never;
427
- }> | undefined, true>;
428
370
  /**
429
371
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
430
372
  */
431
373
  allowAdditionalProperties(): this;
432
- extend<NEW_PROPS extends Record<string, JBuilder<any, any>>>(props: NEW_PROPS): JObjectInfer<{
374
+ extend<NEW_PROPS extends Record<string, JSchema<any, any>>>(props: NEW_PROPS): JObjectInfer<{
433
375
  [K in keyof PROPS | keyof NEW_PROPS]: K extends keyof NEW_PROPS ? NEW_PROPS[K] : K extends keyof PROPS ? PROPS[K] : never;
434
376
  }, Opt>;
435
377
  /**
@@ -466,7 +408,7 @@ declare function object(props: AnyObject): never;
466
408
  declare function object<OUT extends AnyObject>(props: {
467
409
  [K in keyof Required<OUT>]-?: JSchema<OUT[K], any>;
468
410
  }): JObject<OUT, false>;
469
- declare function objectInfer<P extends Record<string, JBuilder<any, any>>>(props: P): JObjectInfer<P, false>;
411
+ declare function objectInfer<P extends Record<string, JSchema<any, any>>>(props: P): JObjectInfer<P, false>;
470
412
  declare function objectDbEntity(props: AnyObject): never;
471
413
  declare function objectDbEntity<OUT extends BaseDBEntity, EXTRA_KEYS extends Exclude<keyof OUT, keyof BaseDBEntity> = Exclude<keyof OUT, keyof BaseDBEntity>>(props: {
472
414
  [K in EXTRA_KEYS]-?: BuilderFor<OUT[K]>;
@@ -405,9 +405,58 @@ export class JBuilder extends JSchema {
405
405
  instanceof(of) {
406
406
  return this.cloneAndUpdateSchema({ type: 'object', instanceof: of });
407
407
  }
408
- optional() {
409
- const clone = this.cloneAndUpdateSchema({ optionalField: true });
410
- return clone;
408
+ /**
409
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
410
+ *
411
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
412
+ * due to how mutability works in Ajv.
413
+ *
414
+ * Make sure this `optional()` call is at the end of your call chain.
415
+ *
416
+ * When `null` is included in optionalValues, the return type becomes `JSchema`
417
+ * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
418
+ */
419
+ optional(optionalValues) {
420
+ if (!optionalValues?.length) {
421
+ const clone = this.cloneAndUpdateSchema({ optionalField: true });
422
+ return clone;
423
+ }
424
+ const builtSchema = this.build();
425
+ // When optionalValues is just [null], use a simple null-wrapping structure.
426
+ // If the schema already has anyOf with a null branch (from nullable()),
427
+ // inject optionalValues directly into it.
428
+ if (optionalValues.length === 1 && optionalValues[0] === null) {
429
+ if (builtSchema.anyOf) {
430
+ const nullBranch = builtSchema.anyOf.find(b => b.type === 'null');
431
+ if (nullBranch) {
432
+ nullBranch.optionalValues = [null];
433
+ return new JSchema({ ...builtSchema, optionalField: true });
434
+ }
435
+ }
436
+ // Wrap with null type branch
437
+ return new JSchema({
438
+ anyOf: [{ type: 'null', optionalValues: [null] }, builtSchema],
439
+ optionalField: true,
440
+ });
441
+ }
442
+ // General case: create anyOf with current schema + alternatives.
443
+ // Preserve the original type for Ajv strict mode (optionalValues keyword requires a type).
444
+ const alternativesSchema = j.enum(optionalValues).build();
445
+ const innerSchema = {
446
+ ...(builtSchema.type ? { type: builtSchema.type } : {}),
447
+ anyOf: [builtSchema, alternativesSchema],
448
+ optionalValues: [...optionalValues],
449
+ };
450
+ // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
451
+ // so we must allow `null` values to be parsed by Ajv,
452
+ // but the typing should not reflect that.
453
+ if (optionalValues.includes(null)) {
454
+ return new JSchema({
455
+ anyOf: [{ type: 'null', optionalValues: [...optionalValues] }, innerSchema],
456
+ optionalField: true,
457
+ });
458
+ }
459
+ return new JSchema({ ...innerSchema, optionalField: true });
411
460
  }
412
461
  nullable() {
413
462
  return new JBuilder({
@@ -468,40 +517,6 @@ export class JString extends JBuilder {
468
517
  type: 'string',
469
518
  });
470
519
  }
471
- /**
472
- * @param optionalValues List of values that should be considered/converted as `undefined`.
473
- *
474
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
475
- * due to how mutability works in Ajv.
476
- *
477
- * Make sure this `optional()` call is at the end of your call chain.
478
- *
479
- * When `null` is included in optionalValues, the return type becomes `JSchema`
480
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
481
- */
482
- optional(optionalValues) {
483
- if (!optionalValues) {
484
- return super.optional();
485
- }
486
- _typeCast(optionalValues);
487
- let newBuilder = new JString().optional();
488
- const alternativesSchema = j.enum(optionalValues);
489
- Object.assign(newBuilder.getSchema(), {
490
- anyOf: [this.build(), alternativesSchema.build()],
491
- optionalValues,
492
- });
493
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
494
- // so we must allow `null` values to be parsed by Ajv,
495
- // but the typing should not reflect that.
496
- // We also cannot accept more rules attached, since we're not building a StringSchema anymore.
497
- if (optionalValues.includes(null)) {
498
- newBuilder = new JSchema({
499
- anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
500
- optionalField: true,
501
- });
502
- }
503
- return newBuilder;
504
- }
505
520
  regex(pattern, opt) {
506
521
  _assert(!pattern.flags, `Regex flags are not supported by JSON Schema. Received: /${pattern.source}/${pattern.flags}`);
507
522
  return this.pattern(pattern.source, opt);
@@ -626,28 +641,6 @@ export class JIsoDate extends JBuilder {
626
641
  IsoDate: {},
627
642
  });
628
643
  }
629
- /**
630
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
631
- *
632
- * This `null` feature only works when the current schema is nested in an object or array schema,
633
- * due to how mutability works in Ajv.
634
- *
635
- * When `null` is passed, the return type becomes `JSchema`
636
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
637
- */
638
- optional(nullValue) {
639
- if (nullValue === undefined) {
640
- return super.optional();
641
- }
642
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
643
- // so we must allow `null` values to be parsed by Ajv,
644
- // but the typing should not reflect that.
645
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
646
- return new JSchema({
647
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
648
- optionalField: true,
649
- });
650
- }
651
644
  before(date) {
652
645
  return this.cloneAndUpdateSchema({ IsoDate: { before: date } });
653
646
  }
@@ -677,40 +670,6 @@ export class JNumber extends JBuilder {
677
670
  type: 'number',
678
671
  });
679
672
  }
680
- /**
681
- * @param optionalValues List of values that should be considered/converted as `undefined`.
682
- *
683
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
684
- * due to how mutability works in Ajv.
685
- *
686
- * Make sure this `optional()` call is at the end of your call chain.
687
- *
688
- * When `null` is included in optionalValues, the return type becomes `JSchema`
689
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
690
- */
691
- optional(optionalValues) {
692
- if (!optionalValues) {
693
- return super.optional();
694
- }
695
- _typeCast(optionalValues);
696
- let newBuilder = new JNumber().optional();
697
- const alternativesSchema = j.enum(optionalValues);
698
- Object.assign(newBuilder.getSchema(), {
699
- anyOf: [this.build(), alternativesSchema.build()],
700
- optionalValues,
701
- });
702
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
703
- // so we must allow `null` values to be parsed by Ajv,
704
- // but the typing should not reflect that.
705
- // We also cannot accept more rules attached, since we're not building a NumberSchema anymore.
706
- if (optionalValues.includes(null)) {
707
- newBuilder = new JSchema({
708
- anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
709
- optionalField: true,
710
- });
711
- }
712
- return newBuilder;
713
- }
714
673
  integer() {
715
674
  return this.cloneAndUpdateSchema({ type: 'integer' });
716
675
  }
@@ -811,24 +770,6 @@ export class JBoolean extends JBuilder {
811
770
  type: 'boolean',
812
771
  });
813
772
  }
814
- /**
815
- * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
816
- *
817
- * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
818
- * due to how mutability works in Ajv.
819
- */
820
- optional(optionalValue) {
821
- if (typeof optionalValue === 'undefined') {
822
- return super.optional();
823
- }
824
- const newBuilder = new JBoolean().optional();
825
- const alternativesSchema = j.enum([optionalValue]);
826
- Object.assign(newBuilder.getSchema(), {
827
- anyOf: [this.build(), alternativesSchema.build()],
828
- optionalValues: [optionalValue],
829
- });
830
- return newBuilder;
831
- }
832
773
  }
833
774
  export class JObject extends JBuilder {
834
775
  constructor(props, opt) {
@@ -844,28 +785,6 @@ export class JObject extends JBuilder {
844
785
  if (props)
845
786
  addPropertiesToSchema(this.schema, props);
846
787
  }
847
- /**
848
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
849
- *
850
- * This `null` feature only works when the current schema is nested in an object or array schema,
851
- * due to how mutability works in Ajv.
852
- *
853
- * When `null` is passed, the return type becomes `JSchema`
854
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
855
- */
856
- optional(nullValue) {
857
- if (nullValue === undefined) {
858
- return super.optional();
859
- }
860
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
861
- // so we must allow `null` values to be parsed by Ajv,
862
- // but the typing should not reflect that.
863
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
864
- return new JSchema({
865
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
866
- optionalField: true,
867
- });
868
- }
869
788
  /**
870
789
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
871
790
  */
@@ -926,29 +845,6 @@ export class JObjectInfer extends JBuilder {
926
845
  if (props)
927
846
  addPropertiesToSchema(this.schema, props);
928
847
  }
929
- /**
930
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
931
- *
932
- * This `null` feature only works when the current schema is nested in an object or array schema,
933
- * due to how mutability works in Ajv.
934
- *
935
- * When `null` is passed, the return type becomes `JSchema`
936
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
937
- */
938
- // @ts-expect-error override adds optional parameter which is compatible but TS can't verify complex mapped types
939
- optional(nullValue) {
940
- if (nullValue === undefined) {
941
- return super.optional();
942
- }
943
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
944
- // so we must allow `null` values to be parsed by Ajv,
945
- // but the typing should not reflect that.
946
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
947
- return new JSchema({
948
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
949
- optionalField: true,
950
- });
951
- }
952
848
  /**
953
849
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
954
850
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.96.1",
4
+ "version": "15.97.1",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@standard-schema/spec": "^1",
@@ -567,9 +567,68 @@ export class JBuilder<OUT, Opt> extends JSchema<OUT, Opt> {
567
567
  return this.cloneAndUpdateSchema({ type: 'object', instanceof: of })
568
568
  }
569
569
 
570
- optional(): JBuilder<OUT | undefined, true> {
571
- const clone = this.cloneAndUpdateSchema({ optionalField: true })
572
- return clone as unknown as JBuilder<OUT | undefined, true>
570
+ /**
571
+ * @param optionalValues List of values that should be considered/converted as `undefined`.
572
+ *
573
+ * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
574
+ * due to how mutability works in Ajv.
575
+ *
576
+ * Make sure this `optional()` call is at the end of your call chain.
577
+ *
578
+ * When `null` is included in optionalValues, the return type becomes `JSchema`
579
+ * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
580
+ */
581
+ optional<T extends readonly (string | number | boolean | null)[] | undefined = undefined>(
582
+ optionalValues?: T,
583
+ ): T extends readonly (string | number | boolean | null)[]
584
+ ? JSchema<OUT | undefined, true>
585
+ : JBuilder<OUT | undefined, true> {
586
+ if (!optionalValues?.length) {
587
+ const clone = this.cloneAndUpdateSchema({ optionalField: true })
588
+ return clone as any
589
+ }
590
+
591
+ const builtSchema = this.build()
592
+
593
+ // When optionalValues is just [null], use a simple null-wrapping structure.
594
+ // If the schema already has anyOf with a null branch (from nullable()),
595
+ // inject optionalValues directly into it.
596
+ if (optionalValues.length === 1 && optionalValues[0] === null) {
597
+ if (builtSchema.anyOf) {
598
+ const nullBranch = builtSchema.anyOf.find(b => b.type === 'null')
599
+ if (nullBranch) {
600
+ nullBranch.optionalValues = [null]
601
+ return new JSchema({ ...builtSchema, optionalField: true }) as any
602
+ }
603
+ }
604
+
605
+ // Wrap with null type branch
606
+ return new JSchema({
607
+ anyOf: [{ type: 'null', optionalValues: [null] }, builtSchema],
608
+ optionalField: true,
609
+ }) as any
610
+ }
611
+
612
+ // General case: create anyOf with current schema + alternatives.
613
+ // Preserve the original type for Ajv strict mode (optionalValues keyword requires a type).
614
+ const alternativesSchema = j.enum(optionalValues).build()
615
+ const innerSchema: JsonSchema = {
616
+ ...(builtSchema.type ? { type: builtSchema.type } : {}),
617
+ anyOf: [builtSchema, alternativesSchema],
618
+ optionalValues: [...optionalValues],
619
+ }
620
+
621
+ // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
622
+ // so we must allow `null` values to be parsed by Ajv,
623
+ // but the typing should not reflect that.
624
+ if (optionalValues.includes(null)) {
625
+ return new JSchema({
626
+ anyOf: [{ type: 'null', optionalValues: [...optionalValues] }, innerSchema],
627
+ optionalField: true,
628
+ }) as any
629
+ }
630
+
631
+ return new JSchema({ ...innerSchema, optionalField: true }) as any
573
632
  }
574
633
 
575
634
  nullable(): JBuilder<OUT | null, Opt> {
@@ -643,51 +702,6 @@ export class JString<
643
702
  })
644
703
  }
645
704
 
646
- /**
647
- * @param optionalValues List of values that should be considered/converted as `undefined`.
648
- *
649
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
650
- * due to how mutability works in Ajv.
651
- *
652
- * Make sure this `optional()` call is at the end of your call chain.
653
- *
654
- * When `null` is included in optionalValues, the return type becomes `JSchema`
655
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
656
- */
657
- override optional<T extends readonly (string | null)[] | undefined = undefined>(
658
- optionalValues?: T,
659
- ): T extends readonly (infer U)[]
660
- ? null extends U
661
- ? JSchema<OUT | undefined, true>
662
- : JString<OUT | undefined, true>
663
- : JString<OUT | undefined, true> {
664
- if (!optionalValues) {
665
- return super.optional() as any
666
- }
667
-
668
- _typeCast<(string | null)[]>(optionalValues)
669
-
670
- let newBuilder: JSchema<OUT | undefined, true> = new JString<OUT, Opt>().optional()
671
- const alternativesSchema = j.enum(optionalValues)
672
- Object.assign(newBuilder.getSchema(), {
673
- anyOf: [this.build(), alternativesSchema.build()],
674
- optionalValues,
675
- })
676
-
677
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
678
- // so we must allow `null` values to be parsed by Ajv,
679
- // but the typing should not reflect that.
680
- // We also cannot accept more rules attached, since we're not building a StringSchema anymore.
681
- if (optionalValues.includes(null)) {
682
- newBuilder = new JSchema({
683
- anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
684
- optionalField: true,
685
- })
686
- }
687
-
688
- return newBuilder as any
689
- }
690
-
691
705
  regex(pattern: RegExp, opt?: JsonBuilderRuleOpt): this {
692
706
  _assert(
693
707
  !pattern.flags,
@@ -846,32 +860,6 @@ export class JIsoDate<Opt extends boolean = false> extends JBuilder<IsoDate, Opt
846
860
  })
847
861
  }
848
862
 
849
- /**
850
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
851
- *
852
- * This `null` feature only works when the current schema is nested in an object or array schema,
853
- * due to how mutability works in Ajv.
854
- *
855
- * When `null` is passed, the return type becomes `JSchema`
856
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
857
- */
858
- override optional<N extends null | undefined = undefined>(
859
- nullValue?: N,
860
- ): N extends null ? JSchema<IsoDate | undefined, true> : JBuilder<IsoDate | undefined, true> {
861
- if (nullValue === undefined) {
862
- return super.optional() as any
863
- }
864
-
865
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
866
- // so we must allow `null` values to be parsed by Ajv,
867
- // but the typing should not reflect that.
868
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
869
- return new JSchema({
870
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
871
- optionalField: true,
872
- }) as any
873
- }
874
-
875
863
  before(date: string): this {
876
864
  return this.cloneAndUpdateSchema({ IsoDate: { before: date } })
877
865
  }
@@ -920,51 +908,6 @@ export class JNumber<
920
908
  })
921
909
  }
922
910
 
923
- /**
924
- * @param optionalValues List of values that should be considered/converted as `undefined`.
925
- *
926
- * This `optionalValues` feature only works when the current schema is nested in an object or array schema,
927
- * due to how mutability works in Ajv.
928
- *
929
- * Make sure this `optional()` call is at the end of your call chain.
930
- *
931
- * When `null` is included in optionalValues, the return type becomes `JSchema`
932
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
933
- */
934
- override optional<T extends readonly (number | null)[] | undefined = undefined>(
935
- optionalValues?: T,
936
- ): T extends readonly (infer U)[]
937
- ? null extends U
938
- ? JSchema<OUT | undefined, true>
939
- : JNumber<OUT | undefined, true>
940
- : JNumber<OUT | undefined, true> {
941
- if (!optionalValues) {
942
- return super.optional() as any
943
- }
944
-
945
- _typeCast<(number | null)[]>(optionalValues)
946
-
947
- let newBuilder: JSchema<OUT | undefined, true> = new JNumber<OUT, Opt>().optional()
948
- const alternativesSchema = j.enum(optionalValues)
949
- Object.assign(newBuilder.getSchema(), {
950
- anyOf: [this.build(), alternativesSchema.build()],
951
- optionalValues,
952
- })
953
-
954
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
955
- // so we must allow `null` values to be parsed by Ajv,
956
- // but the typing should not reflect that.
957
- // We also cannot accept more rules attached, since we're not building a NumberSchema anymore.
958
- if (optionalValues.includes(null)) {
959
- newBuilder = new JSchema({
960
- anyOf: [{ type: 'null', optionalValues }, newBuilder.build()],
961
- optionalField: true,
962
- })
963
- }
964
-
965
- return newBuilder as any
966
- }
967
-
968
911
  integer(): this {
969
912
  return this.cloneAndUpdateSchema({ type: 'integer' })
970
913
  }
@@ -1092,27 +1035,6 @@ export class JBoolean<
1092
1035
  type: 'boolean',
1093
1036
  })
1094
1037
  }
1095
-
1096
- /**
1097
- * @param optionalValue One of the two possible boolean values that should be considered/converted as `undefined`.
1098
- *
1099
- * This `optionalValue` feature only works when the current schema is nested in an object or array schema,
1100
- * due to how mutability works in Ajv.
1101
- */
1102
- override optional(optionalValue?: boolean): JBoolean<OUT | undefined, true> {
1103
- if (typeof optionalValue === 'undefined') {
1104
- return super.optional() as unknown as JBoolean<OUT | undefined, true>
1105
- }
1106
-
1107
- const newBuilder = new JBoolean<OUT, Opt>().optional()
1108
- const alternativesSchema = j.enum([optionalValue])
1109
- Object.assign(newBuilder.getSchema(), {
1110
- anyOf: [this.build(), alternativesSchema.build()],
1111
- optionalValues: [optionalValue],
1112
- })
1113
-
1114
- return newBuilder
1115
- }
1116
1038
  }
1117
1039
 
1118
1040
  export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends JBuilder<
@@ -1133,32 +1055,6 @@ export class JObject<OUT extends AnyObject, Opt extends boolean = false> extends
1133
1055
  if (props) addPropertiesToSchema(this.schema, props)
1134
1056
  }
1135
1057
 
1136
- /**
1137
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
1138
- *
1139
- * This `null` feature only works when the current schema is nested in an object or array schema,
1140
- * due to how mutability works in Ajv.
1141
- *
1142
- * When `null` is passed, the return type becomes `JSchema`
1143
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
1144
- */
1145
- override optional<N extends null | undefined = undefined>(
1146
- nullValue?: N,
1147
- ): N extends null ? JSchema<OUT | undefined, true> : JBuilder<OUT | undefined, true> {
1148
- if (nullValue === undefined) {
1149
- return super.optional() as any
1150
- }
1151
-
1152
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
1153
- // so we must allow `null` values to be parsed by Ajv,
1154
- // but the typing should not reflect that.
1155
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
1156
- return new JSchema({
1157
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
1158
- optionalField: true,
1159
- }) as any
1160
- }
1161
-
1162
1058
  /**
1163
1059
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
1164
1060
  */
@@ -1248,22 +1144,22 @@ interface JObjectOpts {
1248
1144
  }
1249
1145
 
1250
1146
  export class JObjectInfer<
1251
- PROPS extends Record<string, JBuilder<any, any>>,
1147
+ PROPS extends Record<string, JSchema<any, any>>,
1252
1148
  Opt extends boolean = false,
1253
1149
  > extends JBuilder<
1254
1150
  Expand<
1255
1151
  {
1256
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1152
+ [K in keyof PROPS as PROPS[K] extends JSchema<any, infer IsOpt>
1257
1153
  ? IsOpt extends true
1258
1154
  ? never
1259
1155
  : K
1260
- : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1156
+ : never]: PROPS[K] extends JSchema<infer OUT, any> ? OUT : never
1261
1157
  } & {
1262
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1158
+ [K in keyof PROPS as PROPS[K] extends JSchema<any, infer IsOpt>
1263
1159
  ? IsOpt extends true
1264
1160
  ? K
1265
1161
  : never
1266
- : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1162
+ : never]?: PROPS[K] extends JSchema<infer OUT, any> ? OUT : never
1267
1163
  }
1268
1164
  >,
1269
1165
  Opt
@@ -1279,71 +1175,6 @@ export class JObjectInfer<
1279
1175
  if (props) addPropertiesToSchema(this.schema, props)
1280
1176
  }
1281
1177
 
1282
- /**
1283
- * @param nullValue Pass `null` to have `null` values be considered/converted as `undefined`.
1284
- *
1285
- * This `null` feature only works when the current schema is nested in an object or array schema,
1286
- * due to how mutability works in Ajv.
1287
- *
1288
- * When `null` is passed, the return type becomes `JSchema`
1289
- * (no further chaining allowed) because the schema is wrapped in an anyOf structure.
1290
- */
1291
- // @ts-expect-error override adds optional parameter which is compatible but TS can't verify complex mapped types
1292
- override optional<N extends null | undefined = undefined>(
1293
- nullValue?: N,
1294
- ): N extends null
1295
- ? JSchema<
1296
- | Expand<
1297
- {
1298
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1299
- ? IsOpt extends true
1300
- ? never
1301
- : K
1302
- : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1303
- } & {
1304
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1305
- ? IsOpt extends true
1306
- ? K
1307
- : never
1308
- : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1309
- }
1310
- >
1311
- | undefined,
1312
- true
1313
- >
1314
- : JBuilder<
1315
- | Expand<
1316
- {
1317
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1318
- ? IsOpt extends true
1319
- ? never
1320
- : K
1321
- : never]: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1322
- } & {
1323
- [K in keyof PROPS as PROPS[K] extends JBuilder<any, infer IsOpt>
1324
- ? IsOpt extends true
1325
- ? K
1326
- : never
1327
- : never]?: PROPS[K] extends JBuilder<infer OUT, any> ? OUT : never
1328
- }
1329
- >
1330
- | undefined,
1331
- true
1332
- > {
1333
- if (nullValue === undefined) {
1334
- return super.optional() as any
1335
- }
1336
-
1337
- // When `null` is specified, we want `null` to be stripped and the value to become `undefined`,
1338
- // so we must allow `null` values to be parsed by Ajv,
1339
- // but the typing should not reflect that.
1340
- // We also cannot accept more rules attached, since we're not building an ObjectSchema anymore.
1341
- return new JSchema({
1342
- anyOf: [{ type: 'null', optionalValues: [null] }, this.build()],
1343
- optionalField: true,
1344
- }) as any
1345
- }
1346
-
1347
1178
  /**
1348
1179
  * When set, the validation will not strip away properties that are not specified explicitly in the schema.
1349
1180
  */
@@ -1352,7 +1183,7 @@ export class JObjectInfer<
1352
1183
  return this.cloneAndUpdateSchema({ additionalProperties: true })
1353
1184
  }
1354
1185
 
1355
- extend<NEW_PROPS extends Record<string, JBuilder<any, any>>>(
1186
+ extend<NEW_PROPS extends Record<string, JSchema<any, any>>>(
1356
1187
  props: NEW_PROPS,
1357
1188
  ): JObjectInfer<
1358
1189
  {
@@ -1495,7 +1326,7 @@ function object<OUT extends AnyObject>(props: {
1495
1326
  return new JObject<OUT, false>(props)
1496
1327
  }
1497
1328
 
1498
- function objectInfer<P extends Record<string, JBuilder<any, any>>>(
1329
+ function objectInfer<P extends Record<string, JSchema<any, any>>>(
1499
1330
  props: P,
1500
1331
  ): JObjectInfer<P, false> {
1501
1332
  return new JObjectInfer<P, false>(props)