@lucania/schema 2.0.2 → 2.0.3

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.
package/README.md CHANGED
@@ -1,27 +1,30 @@
1
1
  # Schema
2
- | TECHNICAL | |
3
- |:-:|-|
4
- | _noun_ | _a representation of a plan or theory in the form of an outline or model._|
2
+
3
+ | TECHNICAL | |
4
+ | :-------: | -------------------------------------------------------------------------- |
5
+ | _noun_ | _a representation of a plan or theory in the form of an outline or model._ |
5
6
 
6
7
  This library allows you to specify the schema for your data, and get compile time typings _(help from your IDE)_ and runtime validation _(throw errors if your data isn't in the right format)_.
7
8
 
8
9
  With this library, you create objects that serve as a blueprint for what your data should look like. These objects are called `Schema`s.
9
10
 
10
- |_Schema Type_|Description|Usage|Example Data|
11
- |:-:|:-|:-|:-|
12
- |`StringSchema`|Used to represent a string.|`$.String(required?: boolean, default?: StringSource)`|`"Moaaz"`, `"The cow jumped over the moon."`|
13
- |`NumberSchema`|Used to represent a number.|`$.Number(required?: boolean, default?: NumberSource)`|`-30`, `0`, `10`
14
- |`BooleanSchema`|Used to represent a boolean.|`$.Boolean(required?: boolean, default?: BooleanSource)`|`true`, `false`
15
- |`DateSchema`|Used to represent a Date.|`$.Date(required?: boolean, default?: DateSource)`|`new Date(2000, 9, 29)`, `new Date("1998-09-04")`
16
- |`ObjectSchema`|Used to represent an Object.|`$.Object(subschema: { <key>: Schema }, required?: boolean, default?: ObjectSource)`|`<depends on subschema>`, `{ name: "Jeremy", age: 23 }`, `{ make: "Toyota", model: "Sienna", year: 2011 }`
17
- |`ArraySchema`|Used to represent an array|`$.Array(subschema: Schema, required?: boolean, default?: ArraySource)`|`<depends on subschema>`, `[]`, `[1, 2, 3]`, `["Ben", "Amit", "Dean"]`
18
- |`EnumerationSchema`|Used to represent an enumeration value, otherwise known as a set of possible strings values.|`$.Enumeration(members: Members, required?: boolean, default?: EnumerationSource)`|`<depends on members>`, `"MAGENTA"`, `"CA"`, `"male"`
19
- |`OrSetSchema`|Used to represent a value that should be validated by one of many possible schemas. This is used to represent a value that is allowed to be multiple types.|`$.OrSet(members: Members, required?: boolean, default?: OrSetSource)`|`<depends on members>`
20
- |`DynamicObjectSchema`|Used to represent an object that could have many different keys.|`$.DynamicObject(subschema: Schema, required?: boolean, default?: DynamicObjectSource)`|`{ <any key>: <depends on subschema> }`
21
- |`AnySchema`|Used to represent a value that could be any type.|`$.Any(required?: boolean, default?: AnySource)`|`1`, `"Omar"`, `false`, `{}`, `[]`, `<any type>`
11
+ | _Schema Type_ | Description | Usage | Example Data |
12
+ | :-------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
13
+ | `StringSchema` | Used to represent a string. | `$.String(required?: boolean, default?: StringSource)` | `"Moaaz"`, `"The cow jumped over the moon."` |
14
+ | `NumberSchema` | Used to represent a number. | `$.Number(required?: boolean, default?: NumberSource)` | `-30`, `0`, `10` |
15
+ | `BooleanSchema` | Used to represent a boolean. | `$.Boolean(required?: boolean, default?: BooleanSource)` | `true`, `false` |
16
+ | `DateSchema` | Used to represent a Date. | `$.Date(required?: boolean, default?: DateSource)` | `new Date(2000, 9, 29)`, `new Date("1998-09-04")` |
17
+ | `ObjectSchema` | Used to represent an Object. | `$.Object(subschema: { <key>: Schema }, required?: boolean, default?: ObjectSource)` | `<depends on subschema>`, `{ name: "Jeremy", age: 23 }`, `{ make: "Toyota", model: "Sienna", year: 2011 }` |
18
+ | `ArraySchema` | Used to represent an array | `$.Array(subschema: Schema, required?: boolean, default?: ArraySource)` | `<depends on subschema>`, `[]`, `[1, 2, 3]`, `["Ben", "Amit", "Dean"]` |
19
+ | `EnumerationSchema` | Used to represent an enumeration value, otherwise known as a set of possible strings values. | `$.Enumeration(members: Members, required?: boolean, default?: EnumerationSource)` | `<depends on members>`, `"MAGENTA"`, `"CA"`, `"male"` |
20
+ | `OrSetSchema` | Used to represent a value that should be validated by one of many possible schemas. This is used to represent a value that is allowed to be multiple types. | `$.OrSet(members: Members, required?: boolean, default?: OrSetSource)` | `<depends on members>` |
21
+ | `DynamicObjectSchema` | Used to represent an object that could have many different keys. | `$.DynamicObject(subschema: Schema, required?: boolean, default?: DynamicObjectSource)` | `{ <any key>: <depends on subschema> }` |
22
+ | `AnySchema` | Used to represent a value that could be any type. | `$.Any(required?: boolean, default?: AnySource)` | `1`, `"Omar"`, `false`, `{}`, `[]`, `<any type>` |
22
23
 
23
24
  ## Validation
25
+
24
26
  The simplest possible validation:
27
+
25
28
  ```typescript
26
29
  import { $ } from "@lucania/schema";
27
30
 
@@ -38,7 +41,9 @@ const dataOfNumberType = numberSchema.validate(dataOfUnknownType);
38
41
  With this collection of Schemas, we can now start building more complex data structures. There are multiple way to represent different scenarios. This is done with the following constructs.
39
42
 
40
43
  ## Creating Hierarchical Schema
44
+
41
45
  These schemas can come together to allow you to define a blueprint for more complex data structures.
46
+
42
47
  ```typescript
43
48
  import { $ } from "@lucania/schema";
44
49
 
@@ -58,7 +63,6 @@ const PersonSchema = $.Object({
58
63
  age: $.Number(true).min(16, "You must be at least 16 years old!").max(100, "You must be at most 100 years old!"),
59
64
  weapon: WeaponSchema
60
65
  });
61
-
62
66
  ```
63
67
 
64
68
  ## Using Schema
@@ -71,6 +75,52 @@ import fs from "fs";
71
75
  const personData = JSON.parse(fs.readFileSync("person.json", "utf8"));
72
76
  const person = PersonSchema.validate(personData);
73
77
  ```
78
+
74
79
  `person` now has the following compile-time type annotation based on [`PersonSchema`](#creating-hierarchical-schema).
75
80
 
76
- At runtime, the object parsed from the `person.json` file will be validated to match this generated typing. If it does not match, a `Schema.ValidationError` will be thrown.
81
+ At runtime, the object parsed from the `person.json` file will be validated to match this generated typing. If it does not match, a `Schema.ValidationError` will be thrown.
82
+
83
+ ## Additional Validation Passes
84
+
85
+ Sometimes it's necessary to validate not only a type, but also specifics about the value. I.E. a value is a `$.Number` _and_ is between 0 and 100. You can add additional type-specific validation passes (I.E. `.clamp(...)`) or custom ones (`.custom(...)`):
86
+
87
+ ```typescript
88
+ const numberData: any = ...;
89
+
90
+ const ScoreSchema = $.Number().clamp(0, 100).custom((data, pass) => {
91
+ pass.assert(data % 2 === 0, "Your score must be even!");
92
+ return data;
93
+ });
94
+
95
+ // The above custom() validation pass can alternately be defined using the .ensure() shorthand.
96
+
97
+ const ScoreSchema = $.Number().clamp(0, 100).ensure((data, pass) => data % 2 === 0, "Your score must be even!");
98
+
99
+ const number: number = ScoreSchema.validate(numberData);
100
+ ```
101
+
102
+ ## Custom Schema Types
103
+
104
+ You can define your own Schema types by creating a subclass of `BaseSchema`. But first, `BaseSchema` relies on 4 generic parameters for TypeScript's type checker to understand your Schema at compile time. These parameters are as follows:
105
+
106
+ - Source - Represents the typing for a source input to your schema.
107
+ - Model - Represents the typing for an output value from your schema.
108
+ - Required - Represents the presence optionality of your schema's Source/Model. (Is `undefined` a valid Source and Model)
109
+ - Default - Represents the typing for default values.
110
+
111
+ Typically, when developing your own Schema types, you'll hardcode Source and Model for your specific Schema's requirements, but pass over `Required` and `Default` generics to `BaseSchema` to allow them to be inferred from your Schema definitions.
112
+
113
+ ```typescript
114
+ export type CowModel = { name: string, numberOfSpots: number };
115
+
116
+ export type CowSource = string | CowModel;
117
+
118
+ export class CowSchema<
119
+ Required extends boolean, // Taking in "Required" generic.
120
+ Default extends DefaultValue<CowSource> // Taking in "Default" generic.
121
+
122
+ // Handing over hardcoded Source, Model and CowSchema generics to BaseSchema.
123
+ > extends BaseSchema<CowSource, CowModel, Required, Default> { ... }
124
+ ```
125
+
126
+ These declarations are enough to have the TypeScript type checker understand the compile-time typing for `CowSchema`. Next, lets implement our runtime checks.
package/build/index.js CHANGED
@@ -51,12 +51,10 @@
51
51
  }
52
52
 
53
53
  class BaseSchema {
54
- type;
55
54
  _required;
56
55
  _default;
57
56
  _additionalValidationPasses;
58
- constructor(type, required, defaultValue, additionalValidationPasses) {
59
- this.type = type;
57
+ constructor(required, defaultValue, additionalValidationPasses) {
60
58
  this._required = required;
61
59
  this._default = defaultValue;
62
60
  this._additionalValidationPasses = additionalValidationPasses === undefined ? {
@@ -82,16 +80,18 @@
82
80
  if (BaseSchema.getType(result) !== this.type) {
83
81
  result = this.convert(result, pass);
84
82
  }
83
+ result = this._validate(result, pass);
85
84
  result = this._executeAdditionalValidator(result, pass, "afterConversion");
86
85
  }
87
86
  else {
88
- result = this._executeAdditionalValidator(result, pass, "afterConversion");
89
87
  if (this._required) {
90
88
  throw pass.getError(pass.path.length > 0 ? `Missing required value at "${pass.path.join(".")}".` : "Missing required value.");
91
89
  }
92
90
  else {
93
91
  result = undefined;
94
92
  }
93
+ result = this._validate(result, pass);
94
+ result = this._executeAdditionalValidator(result, pass, "afterConversion");
95
95
  }
96
96
  if (this._required || result !== undefined) {
97
97
  result = this._executeAdditionalValidator(result, pass, "afterAll");
@@ -102,6 +102,15 @@
102
102
  this._additionalValidationPasses[type].push(additionalValidator);
103
103
  return this;
104
104
  }
105
+ /**
106
+ * Ensures a condition is true during the associated schema's validation pass.
107
+ *
108
+ * @note See {@link EnsureValidator EnsureValidator}.
109
+ *
110
+ * @param ensureValidator A validator to ensure a condition is passed.
111
+ * @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
112
+ * @returns The schema instance for chaining.
113
+ */
105
114
  ensure(ensureValidator, message) {
106
115
  this.custom((value, pass) => {
107
116
  pass.assert(ensureValidator(value, pass), message === undefined ? `Failed to ensure value.` : message);
@@ -132,6 +141,13 @@
132
141
  _getValueDisplay(value) {
133
142
  return String(value);
134
143
  }
144
+ getJsonSchema() {
145
+ console.warn(`"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`);
146
+ return {
147
+ type: "unknown",
148
+ description: `"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`,
149
+ };
150
+ }
135
151
  _getJsonSchemaDescription() {
136
152
  const pass = new ValidationPass(this, this._default);
137
153
  const descriptionPieces = [];
@@ -198,8 +214,9 @@
198
214
  }
199
215
 
200
216
  class AnySchema extends BaseSchema {
201
- constructor(required, defaultValue) {
202
- super("any", required, defaultValue);
217
+ get type() { return "any"; }
218
+ _validate(source, pass) {
219
+ return source;
203
220
  }
204
221
  convert(value, pass) {
205
222
  return value;
@@ -212,16 +229,28 @@
212
229
  class ArraySchema extends BaseSchema {
213
230
  subschema;
214
231
  constructor(subschema, required, defaultValue) {
215
- super("array", required, defaultValue);
232
+ super(required, defaultValue);
216
233
  this.subschema = subschema;
217
234
  }
218
- validate(source, pass) {
219
- pass = this._ensurePass(source, pass);
220
- const result = super.validate(source, pass);
221
- if (result !== undefined) {
222
- for (const key in result) {
223
- const nestedValue = result[key];
224
- result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
235
+ get type() { return "array"; }
236
+ // public validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass?: ValidationPass):
237
+ // ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default> {
238
+ // pass = this._ensurePass(source, pass);
239
+ // const result: any = super.validate(source, pass);
240
+ // if (result !== undefined) {
241
+ // for (const key in result) {
242
+ // const nestedValue = result[key];
243
+ // result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
244
+ // }
245
+ // }
246
+ // return result;
247
+ // }
248
+ _validate(source, pass) {
249
+ const result = [];
250
+ if (source !== undefined) {
251
+ for (const key in source) {
252
+ const nestedValue = source[key];
253
+ result[key] = this.subschema.validate(source[key], pass.next([...pass.path, key], this.subschema, nestedValue));
225
254
  }
226
255
  }
227
256
  return result;
@@ -247,8 +276,9 @@
247
276
  }
248
277
 
249
278
  class BooleanSchema extends BaseSchema {
250
- constructor(required, defaultValue) {
251
- super("boolean", required, defaultValue);
279
+ get type() { return "boolean"; }
280
+ _validate(source, pass) {
281
+ return source;
252
282
  }
253
283
  convert(value, pass) {
254
284
  if (typeof value === "number") {
@@ -279,8 +309,9 @@
279
309
 
280
310
  const StandardDate = globalThis.Date;
281
311
  class DateSchema extends BaseSchema {
282
- constructor(required, defaultValue) {
283
- super("Date", required, defaultValue);
312
+ get type() { return "Date"; }
313
+ _validate(source, pass) {
314
+ return source;
284
315
  }
285
316
  convert(value, pass) {
286
317
  if (typeof value === "string") {
@@ -338,12 +369,24 @@
338
369
  class DynamicObjectSchema extends BaseSchema {
339
370
  subschema;
340
371
  constructor(subschema, required, defaultValue) {
341
- super("object", required, defaultValue);
372
+ super(required, defaultValue);
342
373
  this.subschema = subschema;
343
374
  }
344
- validate(source, pass) {
345
- pass = this._ensurePass(source, pass);
346
- const result = super.validate(source, pass);
375
+ get type() { return "object"; }
376
+ // public validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass?: ValidationPass):
377
+ // ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default> {
378
+ // pass = this._ensurePass(source, pass);
379
+ // const result: any = super.validate(source, pass);
380
+ // if (result !== undefined) {
381
+ // for (const key in result) {
382
+ // const nestedValue = result[key];
383
+ // result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
384
+ // }
385
+ // }
386
+ // return result;
387
+ // }
388
+ _validate(source, pass) {
389
+ const result = source;
347
390
  if (result !== undefined) {
348
391
  for (const key in result) {
349
392
  const nestedValue = result[key];
@@ -372,12 +415,19 @@
372
415
  class EnumerationSchema extends BaseSchema {
373
416
  members;
374
417
  constructor(members, required, defaultValue) {
375
- super("string", required, defaultValue);
418
+ super(required, defaultValue);
376
419
  this.members = members;
377
420
  }
378
- validate(source, pass) {
379
- pass = this._ensurePass(source, pass);
380
- const result = super.validate(source, pass);
421
+ get type() { return "string"; }
422
+ // public validate(source: SourceValue<Members[number], Required, Default>, pass?: ValidationPass):
423
+ // ModelValue<Members[number], Members[number], Required, Default> {
424
+ // pass = this._ensurePass(source, pass);
425
+ // const result: any = super.validate(source, pass);
426
+ // pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
427
+ // return result;
428
+ // }
429
+ _validate(source, pass) {
430
+ const result = source;
381
431
  pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
382
432
  return result;
383
433
  }
@@ -394,8 +444,9 @@
394
444
  }
395
445
 
396
446
  class NumberSchema extends BaseSchema {
397
- constructor(required, defaultValue) {
398
- super("number", required, defaultValue);
447
+ get type() { return "number"; }
448
+ _validate(source, pass) {
449
+ return source;
399
450
  }
400
451
  convert(value, pass) {
401
452
  if (typeof value === "bigint") {
@@ -449,22 +500,40 @@
449
500
  class ObjectSchema extends BaseSchema {
450
501
  subschema;
451
502
  constructor(subschema, required, defaultValue) {
452
- super("object", required, defaultValue);
503
+ super(required, defaultValue);
453
504
  this.subschema = subschema;
454
505
  }
455
- validate(source, pass) {
456
- pass = this._ensurePass(source, pass);
457
- const result = super.validate(source, pass);
458
- if (result !== undefined) {
506
+ get type() { return "object"; }
507
+ // public validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass?: ValidationPass):
508
+ // ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default> {
509
+ // pass = this._ensurePass(source, pass);
510
+ // const input: any = super.validate(source, pass);
511
+ // let output: any = input;
512
+ // if (typeof input === "object" && input !== null) {
513
+ // output = {};
514
+ // for (const key in this.subschema) {
515
+ // const nestedSchema = this.subschema[key];
516
+ // const nestedValue = input[key];
517
+ // output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
518
+ // }
519
+ // }
520
+ // return output;
521
+ // }
522
+ _validate(source, pass) {
523
+ const input = source;
524
+ let output = input;
525
+ if (typeof input === "object" && input !== null) {
526
+ output = {};
459
527
  for (const key in this.subschema) {
460
528
  const nestedSchema = this.subschema[key];
461
- const nestedValue = result[key];
462
- result[key] = this.subschema[key].validate(result[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
529
+ const nestedValue = input[key];
530
+ output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
463
531
  }
464
532
  }
465
- return result;
533
+ return output;
466
534
  }
467
535
  convert(value, pass) {
536
+ pass.assert(typeof value === "object", `Unable to convert ${ObjectSchema.getType(value)} to object.`);
468
537
  const model = {};
469
538
  for (const key in this.subschema) {
470
539
  const nestedSchema = this.subschema[key];
@@ -504,12 +573,36 @@
504
573
  class OrSetSchema extends BaseSchema {
505
574
  schemas;
506
575
  constructor(schemas, required, defaultValue) {
507
- super("string", required, defaultValue);
576
+ super(required, defaultValue);
508
577
  this.schemas = schemas;
509
578
  }
510
- validate(source, pass) {
511
- pass = this._ensurePass(source, pass);
512
- let result = super.validate(source, pass);
579
+ get type() { return "string"; }
580
+ // public validate(source: SourceValue<OrSetSchemaSource<MemberSchemas>, Required, Default>, pass?: ValidationPass):
581
+ // ModelValue<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default> {
582
+ // pass = this._ensurePass(source, pass);
583
+ // let result: any = super.validate(source, pass);
584
+ // if (result !== undefined) {
585
+ // let done = false;
586
+ // const failureMessages: string[] = [];
587
+ // for (let i = 0; i < this.schemas.length && !done; i++) {
588
+ // const schema = this.schemas[i];
589
+ // try {
590
+ // result = schema.validate(result, pass);
591
+ // done = true;
592
+ // } catch (error) {
593
+ // if (error instanceof Error) {
594
+ // failureMessages.push(`Schema #${i + 1}: ${error.message}`);
595
+ // } else {
596
+ // failureMessages.push(`Schema #${i + 1}: ${String(error)}`);
597
+ // }
598
+ // pass.assert(failureMessages.length !== this.schemas.length, `Supplied value didn't match any schemas in or-set.\n${failureMessages.join("\n")}`);
599
+ // }
600
+ // }
601
+ // }
602
+ // return result;
603
+ // }
604
+ _validate(source, pass) {
605
+ let result = source;
513
606
  if (result !== undefined) {
514
607
  let done = false;
515
608
  const failureMessages = [];
@@ -541,13 +634,14 @@
541
634
  }
542
635
 
543
636
  class StringSchema extends BaseSchema {
544
- constructor(required, defaultValue, additionalValidationPasses) {
545
- super("string", required, defaultValue, additionalValidationPasses);
546
- }
637
+ get type() { return "string"; }
547
638
  // Not sure if these will be needed with constructor option. If they are required, it adds a lot of boilerplate to all of the Schema classes.
548
639
  // public required() { return new StringSchema(true, this._default, this._additionalValidationPasses); }
549
640
  // public optional() { return new StringSchema(false, this._default, this._additionalValidationPasses); }
550
641
  // public default<Default extends DefaultValue<StringSource>>(defaultValue: Default) { return new StringSchema(this._required, defaultValue, this._additionalValidationPasses); }
642
+ _validate(source, pass) {
643
+ return source;
644
+ }
551
645
  convert(value, pass) {
552
646
  if (value instanceof Date) {
553
647
  return value.toISOString();
@@ -563,8 +657,8 @@
563
657
  length(minimum, maximum, messageA, messageB) {
564
658
  return this.custom((model, pass) => {
565
659
  messageB = messageB === undefined ? messageA : messageB;
566
- pass.assert(model.length > minimum, messageA === undefined ? `String "${model}: failed minimum length check. (${minimum})` : messageA);
567
- pass.assert(model.length < maximum, messageB === undefined ? `String "${model}: failed maximum length check. (${maximum})` : messageB);
660
+ pass.assert(model.length >= minimum, messageA === undefined ? `String "${model}: failed minimum length check. (${minimum})` : messageA);
661
+ pass.assert(model.length <= maximum, messageB === undefined ? `String "${model}: failed maximum length check. (${maximum})` : messageB);
568
662
  return model;
569
663
  }, "afterAll");
570
664
  }
@@ -1,8 +1,9 @@
1
- import { DefaultValue } from "../typing/toolbox";
1
+ import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
2
  import { ValidationPass } from "../error/ValidationPass";
3
3
  import { BaseSchema } from "./BaseSchema";
4
4
  export declare class AnySchema<Required extends boolean, Default extends DefaultValue<any>> extends BaseSchema<any, any, Required, Default> {
5
- constructor(required: Required, defaultValue: Default);
5
+ get type(): string;
6
+ protected _validate(source: SourceValue<any, Required, Default>, pass: ValidationPass): ModelValue<any, any, Required, Default>;
6
7
  convert(value: any, pass: ValidationPass): any;
7
8
  getJsonSchema(): object;
8
9
  }
@@ -7,7 +7,8 @@ export type ArrayModel<Subschema extends BaseSchemaAny> = (Subschema extends Bas
7
7
  export declare class ArraySchema<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<ArraySource<Subschema>>> extends BaseSchema<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default> {
8
8
  readonly subschema: Subschema;
9
9
  constructor(subschema: Subschema, required: Required, defaultValue: Default);
10
- validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default>;
10
+ get type(): string;
11
+ protected _validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default>;
11
12
  convert(value: ArraySource<Subschema>, pass: ValidationPass): ArrayModel<Subschema>;
12
13
  getJsonSchema(): object;
13
14
  }
@@ -1,16 +1,26 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
2
  import type { AdditionalValidationPasses, AdditionalValidator, AdditionalValidatorAfterType, AdditionalValidatorBeforeType, DefaultValue, EnsureValidator, ModelValue, SourceValue } from "../typing/toolbox";
3
3
  export declare abstract class BaseSchema<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> {
4
- readonly type: string;
5
4
  protected readonly _required: Required;
6
5
  protected readonly _default: Default;
7
6
  protected readonly _additionalValidationPasses: AdditionalValidationPasses<Source, Model>;
8
- constructor(type: string, required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Source, Model>);
7
+ constructor(required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Source, Model>);
8
+ abstract get type(): string;
9
9
  validate(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ModelValue<Source, Model, Required, Default>;
10
+ protected abstract _validate(source: SourceValue<Source, Required, Default>, pass: ValidationPass): ModelValue<Source, Model, Required, Default>;
10
11
  abstract convert(value: Source, pass: ValidationPass): Model;
11
12
  custom(additionalValidator: AdditionalValidator<SourceValue<Source, Required, Default>>, type: AdditionalValidatorBeforeType): this;
12
13
  custom(additionalValidator: AdditionalValidator<Model>, type: AdditionalValidatorAfterType): this;
13
14
  custom(additionalValidator: AdditionalValidator<Model>): this;
15
+ /**
16
+ * Ensures a condition is true during the associated schema's validation pass.
17
+ *
18
+ * @note See {@link EnsureValidator EnsureValidator}.
19
+ *
20
+ * @param ensureValidator A validator to ensure a condition is passed.
21
+ * @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
22
+ * @returns The schema instance for chaining.
23
+ */
14
24
  ensure(ensureValidator: EnsureValidator<Model>, message?: string): this;
15
25
  isRequired(): Required;
16
26
  hasDefault(): boolean;
@@ -19,7 +29,7 @@ export declare abstract class BaseSchema<Source, Model, Required extends boolean
19
29
  };
20
30
  getDefault(pass: ValidationPass): Source;
21
31
  protected _getValueDisplay(value: Model): string;
22
- abstract getJsonSchema(): object;
32
+ getJsonSchema(): object;
23
33
  protected _getJsonSchemaDescription(): string;
24
34
  protected _ensurePass(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ValidationPass;
25
35
  private _executeAdditionalValidator;
@@ -1,9 +1,10 @@
1
- import { DefaultValue } from "../typing/toolbox";
1
+ import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
2
  import { ValidationPass } from "../error/ValidationPass";
3
3
  import { BaseSchema } from "./BaseSchema";
4
4
  export type BooleanSource = boolean | number | string | null | undefined;
5
5
  export declare class BooleanSchema<Required extends boolean, Default extends DefaultValue<BooleanSource>> extends BaseSchema<BooleanSource, boolean, Required, Default> {
6
- constructor(required: Required, defaultValue: Default);
6
+ get type(): string;
7
+ protected _validate(source: SourceValue<BooleanSource, Required, Default>, pass: ValidationPass): ModelValue<BooleanSource, boolean, Required, Default>;
7
8
  convert(value: BooleanSource, pass: ValidationPass): boolean;
8
9
  getJsonSchema(): object;
9
10
  }
@@ -1,11 +1,12 @@
1
- import { DefaultValue } from "../typing/toolbox";
1
+ import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
2
  import { ValidationPass } from "../error/ValidationPass";
3
3
  import { BaseSchema } from "./BaseSchema";
4
4
  type StandardDate = globalThis.Date;
5
5
  declare const StandardDate: DateConstructor;
6
6
  export type DateSource = string | number | StandardDate;
7
7
  export declare class DateSchema<Required extends boolean, Default extends DefaultValue<DateSource>> extends BaseSchema<DateSource, StandardDate, Required, Default> {
8
- constructor(required: Required, defaultValue: Default);
8
+ get type(): string;
9
+ protected _validate(source: SourceValue<DateSource, Required, Default>, pass: ValidationPass): ModelValue<DateSource, Date, Required, Default>;
9
10
  convert(value: DateSource, pass: ValidationPass): StandardDate;
10
11
  before(date: Date, message?: string): this;
11
12
  after(date: Date, message?: string): this;
@@ -11,7 +11,8 @@ export type DynamicObjectModel<Subschema extends BaseSchemaAny> = ({
11
11
  export declare class DynamicObjectSchema<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<DynamicObjectSource<Subschema>>> extends BaseSchema<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default> {
12
12
  readonly subschema: Subschema;
13
13
  constructor(subschema: Subschema, required: Required, defaultValue: Default);
14
- validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default>;
14
+ get type(): string;
15
+ _validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default>;
15
16
  convert(source: DynamicObjectSource<Subschema>, pass: ValidationPass): DynamicObjectModel<Subschema>;
16
17
  getJsonSchema(): object;
17
18
  }
@@ -4,7 +4,8 @@ import { BaseSchema } from "./BaseSchema";
4
4
  export declare class EnumerationSchema<Members extends string[], Required extends boolean, Default extends DefaultValue<Members[number]>> extends BaseSchema<Members[number], Members[number], Required, Default> {
5
5
  readonly members: Members;
6
6
  constructor(members: Members, required: Required, defaultValue: Default);
7
- validate(source: SourceValue<Members[number], Required, Default>, pass?: ValidationPass): ModelValue<Members[number], Members[number], Required, Default>;
7
+ get type(): string;
8
+ _validate(source: SourceValue<Members[number], Required, Default>, pass: ValidationPass): ModelValue<Members[number], Members[number], Required, Default>;
8
9
  convert(value: Members[number], pass: ValidationPass): Members[number];
9
10
  getJsonSchema(): object;
10
11
  }
@@ -1,9 +1,10 @@
1
- import { DefaultValue } from "../typing/toolbox";
1
+ import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
2
  import { ValidationPass } from "../error/ValidationPass";
3
3
  import { BaseSchema } from "./BaseSchema";
4
4
  export type NumberSource = number | bigint | string | boolean | null | undefined | Date;
5
5
  export declare class NumberSchema<Required extends boolean, Default extends DefaultValue<NumberSource>> extends BaseSchema<NumberSource, number, Required, Default> {
6
- constructor(required: Required, defaultValue: Default);
6
+ get type(): string;
7
+ protected _validate(source: SourceValue<NumberSource, Required, Default>, pass: ValidationPass): ModelValue<NumberSource, number, Required, Default>;
7
8
  convert(value: NumberSource, pass: ValidationPass): number;
8
9
  min(minimum: number, message?: string): this;
9
10
  max(maximum: number, message?: string): this;
@@ -18,7 +18,8 @@ export type ObjectModel<Subschema extends ObjectSubschema> = (Merge<{
18
18
  export declare class ObjectSchema<Subschema extends ObjectSubschema, Required extends boolean, Default extends DefaultValue<ObjectSource<Subschema>>> extends BaseSchema<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default> {
19
19
  readonly subschema: Subschema;
20
20
  constructor(subschema: Subschema, required: Required, defaultValue: Default);
21
- validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default>;
21
+ get type(): string;
22
+ protected _validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass: ValidationPass): ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default>;
22
23
  convert(value: ObjectSource<Subschema>, pass: ValidationPass): ObjectModel<Subschema>;
23
24
  extend<ExtensionSubschema extends ObjectSubschema, ExtensionDefault extends DefaultValue<ObjectSource<ExtensionSubschema>>>(schema: ObjectSchema<ExtensionSubschema, Required, ExtensionDefault>): ObjectSchema<Subschema & ExtensionSubschema, Required, any>;
24
25
  getJsonSchema(): object;
@@ -11,7 +11,8 @@ export type OrSetSchemaModel<MemberSchemas extends BaseSchemaAny[]> = ({
11
11
  export declare class OrSetSchema<MemberSchemas extends BaseSchemaAny[], Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchemas>>> extends BaseSchema<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default> {
12
12
  readonly schemas: MemberSchemas;
13
13
  constructor(schemas: MemberSchemas, required: Required, defaultValue: Default);
14
- validate(source: SourceValue<OrSetSchemaSource<MemberSchemas>, Required, Default>, pass?: ValidationPass): ModelValue<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default>;
14
+ get type(): string;
15
+ _validate(source: SourceValue<OrSetSchemaSource<MemberSchemas>, Required, Default>, pass: ValidationPass): ModelValue<OrSetSchemaSource<MemberSchemas>, OrSetSchemaModel<MemberSchemas>, Required, Default>;
15
16
  convert(value: OrSetSchemaSource<MemberSchemas>, pass: ValidationPass): OrSetSchemaModel<MemberSchemas>;
16
17
  getJsonSchema(): object;
17
18
  }
@@ -1,9 +1,10 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
- import { AdditionalValidationPasses, DefaultValue } from "../typing/toolbox";
2
+ import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
3
3
  import { BaseSchema } from "./BaseSchema";
4
4
  export type StringSource = string | number | boolean | null | undefined | Date;
5
5
  export declare class StringSchema<Required extends boolean, Default extends DefaultValue<StringSource>> extends BaseSchema<StringSource, string, Required, Default> {
6
- constructor(required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<StringSource, string>);
6
+ get type(): string;
7
+ protected _validate(source: SourceValue<StringSource, Required, Default>, pass: ValidationPass): ModelValue<StringSource, string, Required, Default>;
7
8
  convert(value: StringSource, pass: ValidationPass): string;
8
9
  length(minimum: number, maximum: number): this;
9
10
  length(minimum: number, maximum: number, message: string): this;
@@ -21,9 +21,21 @@ export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversio
21
21
  * - afterAll: Validator executed after "afterConversion" and as the last task in the validation pipeline.
22
22
  */
23
23
  export type AdditionalValidatorType = AdditionalValidatorBeforeType | AdditionalValidatorAfterType;
24
+ /**
25
+ * Represents a custom pass allowing any additional value specificity code, or value augmentations.
26
+ *
27
+ * @note Use "{@link ValidationPass pass}.assert" and/or "throw {@link ValidationPass pass}.getError" to build your value specificity checks.
28
+ *
29
+ * @param {Type} data The data being validated.
30
+ * @param {ValidationPass} pass A reference to the current validation pass. Used to interact with the validation pass.
31
+ * @returns `data` with any desired augmentations.
32
+ */
24
33
  export type AdditionalValidator<Type> = (data: Type, pass: ValidationPass) => Type;
25
34
  /**
26
- * Represents a pass to ensure that your data meets a condition. Return true if your data is ensured to meet condition, false otherwise.
35
+ * Represents a pass to ensure that your data meets conditions.
36
+ * @param data The data being validated.
37
+ * @param pass A reference to the current validation pass. Used to interact with the validation pass.
38
+ * @returns `true` if your data meets the desired conditions, `false` otherwise.
27
39
  */
28
40
  export type EnsureValidator<Type> = (data: Type, pass: ValidationPass) => boolean;
29
41
  export type AdditionalValidationPasses<Source, Model> = {
@@ -34,4 +46,7 @@ export type AdditionalValidationPasses<Source, Model> = {
34
46
  afterConversion: AdditionalValidator<Model>[];
35
47
  afterAll: AdditionalValidator<Model>[];
36
48
  };
37
- export type Merge<ObjectA, ObjectB> = (keyof ObjectA extends never ? ObjectB : keyof ObjectB extends never ? ObjectA : ObjectA & ObjectB);
49
+ /**
50
+ * Intersects two types.
51
+ */
52
+ export type Merge<A, B> = (keyof A extends never ? B : keyof B extends never ? A : A & B);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucania/schema",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "A schema module for compile-time and runtime type checking.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "homepage": "https://github.com/lucania-software/schema#readme",
31
31
  "devDependencies": {
32
- "@babel/preset-env": "^7.23.5",
33
- "@rollup/plugin-typescript": "^11.1.5",
34
- "@types/node": "^20.10.4",
35
- "nodemon": "^3.0.2",
36
- "rollup": "^4.6.1",
37
- "tslib": "^2.6.2",
38
- "typescript": "^5.3.3"
32
+ "@babel/preset-env": "^7.24.7",
33
+ "@rollup/plugin-typescript": "^11.1.6",
34
+ "@types/node": "^20.14.2",
35
+ "nodemon": "^3.1.3",
36
+ "rollup": "^4.18.0",
37
+ "tslib": "^2.6.3",
38
+ "typescript": "^5.4.5"
39
39
  }
40
40
  }