@lucania/schema 2.0.1 → 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.
@@ -42,6 +42,7 @@ export declare namespace Schema {
42
42
  function OrSet<MemberSchemas extends BaseSchemaAny[]>(subschema: TypedMembers<MemberSchemas>): OrSetSchema<MemberSchemas, true, undefined>;
43
43
  function OrSet<MemberSchemas extends BaseSchemaAny[], Required extends boolean>(subschema: TypedMembers<MemberSchemas>, required: Required): OrSetSchema<MemberSchemas, Required, undefined>;
44
44
  function OrSet<MemberSchemas extends BaseSchemaAny[], Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchemas>>>(subschema: TypedMembers<MemberSchemas>, required: Required, defaultValue: Default): OrSetSchema<MemberSchemas, Required, Default>;
45
+ function Members<Members extends string[]>(...members: Members): TypedMembers<Members>;
45
46
  function Members<Members extends any[]>(...members: Members): TypedMembers<Members>;
46
47
  type Model<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, infer Model, infer Require, infer Default> ? (ModelValue<Source, Model, Require, Default>) : never;
47
48
  type Source<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, any, infer Require, infer Default> ? (SourceValue<Source, Require, Default>) : never;
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);
@@ -109,6 +118,9 @@
109
118
  });
110
119
  return this;
111
120
  }
121
+ isRequired() {
122
+ return this._required;
123
+ }
112
124
  hasDefault() {
113
125
  return this._default !== undefined && this._default !== null;
114
126
  }
@@ -129,6 +141,13 @@
129
141
  _getValueDisplay(value) {
130
142
  return String(value);
131
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
+ }
132
151
  _getJsonSchemaDescription() {
133
152
  const pass = new ValidationPass(this, this._default);
134
153
  const descriptionPieces = [];
@@ -195,8 +214,9 @@
195
214
  }
196
215
 
197
216
  class AnySchema extends BaseSchema {
198
- constructor(required, defaultValue) {
199
- super("any", required, defaultValue);
217
+ get type() { return "any"; }
218
+ _validate(source, pass) {
219
+ return source;
200
220
  }
201
221
  convert(value, pass) {
202
222
  return value;
@@ -209,16 +229,28 @@
209
229
  class ArraySchema extends BaseSchema {
210
230
  subschema;
211
231
  constructor(subschema, required, defaultValue) {
212
- super("array", required, defaultValue);
232
+ super(required, defaultValue);
213
233
  this.subschema = subschema;
214
234
  }
215
- validate(source, pass) {
216
- pass = this._ensurePass(source, pass);
217
- const result = super.validate(source, pass);
218
- if (result !== undefined) {
219
- for (const key in result) {
220
- const nestedValue = result[key];
221
- 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));
222
254
  }
223
255
  }
224
256
  return result;
@@ -244,8 +276,9 @@
244
276
  }
245
277
 
246
278
  class BooleanSchema extends BaseSchema {
247
- constructor(required, defaultValue) {
248
- super("boolean", required, defaultValue);
279
+ get type() { return "boolean"; }
280
+ _validate(source, pass) {
281
+ return source;
249
282
  }
250
283
  convert(value, pass) {
251
284
  if (typeof value === "number") {
@@ -276,8 +309,9 @@
276
309
 
277
310
  const StandardDate = globalThis.Date;
278
311
  class DateSchema extends BaseSchema {
279
- constructor(required, defaultValue) {
280
- super("Date", required, defaultValue);
312
+ get type() { return "Date"; }
313
+ _validate(source, pass) {
314
+ return source;
281
315
  }
282
316
  convert(value, pass) {
283
317
  if (typeof value === "string") {
@@ -335,12 +369,24 @@
335
369
  class DynamicObjectSchema extends BaseSchema {
336
370
  subschema;
337
371
  constructor(subschema, required, defaultValue) {
338
- super("object", required, defaultValue);
372
+ super(required, defaultValue);
339
373
  this.subschema = subschema;
340
374
  }
341
- validate(source, pass) {
342
- pass = this._ensurePass(source, pass);
343
- 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;
344
390
  if (result !== undefined) {
345
391
  for (const key in result) {
346
392
  const nestedValue = result[key];
@@ -369,12 +415,19 @@
369
415
  class EnumerationSchema extends BaseSchema {
370
416
  members;
371
417
  constructor(members, required, defaultValue) {
372
- super("string", required, defaultValue);
418
+ super(required, defaultValue);
373
419
  this.members = members;
374
420
  }
375
- validate(source, pass) {
376
- pass = this._ensurePass(source, pass);
377
- 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;
378
431
  pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
379
432
  return result;
380
433
  }
@@ -391,8 +444,9 @@
391
444
  }
392
445
 
393
446
  class NumberSchema extends BaseSchema {
394
- constructor(required, defaultValue) {
395
- super("number", required, defaultValue);
447
+ get type() { return "number"; }
448
+ _validate(source, pass) {
449
+ return source;
396
450
  }
397
451
  convert(value, pass) {
398
452
  if (typeof value === "bigint") {
@@ -446,22 +500,40 @@
446
500
  class ObjectSchema extends BaseSchema {
447
501
  subschema;
448
502
  constructor(subschema, required, defaultValue) {
449
- super("object", required, defaultValue);
503
+ super(required, defaultValue);
450
504
  this.subschema = subschema;
451
505
  }
452
- validate(source, pass) {
453
- pass = this._ensurePass(source, pass);
454
- const result = super.validate(source, pass);
455
- 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 = {};
456
527
  for (const key in this.subschema) {
457
528
  const nestedSchema = this.subschema[key];
458
- const nestedValue = result[key];
459
- 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));
460
531
  }
461
532
  }
462
- return result;
533
+ return output;
463
534
  }
464
535
  convert(value, pass) {
536
+ pass.assert(typeof value === "object", `Unable to convert ${ObjectSchema.getType(value)} to object.`);
465
537
  const model = {};
466
538
  for (const key in this.subschema) {
467
539
  const nestedSchema = this.subschema[key];
@@ -501,12 +573,36 @@
501
573
  class OrSetSchema extends BaseSchema {
502
574
  schemas;
503
575
  constructor(schemas, required, defaultValue) {
504
- super("string", required, defaultValue);
576
+ super(required, defaultValue);
505
577
  this.schemas = schemas;
506
578
  }
507
- validate(source, pass) {
508
- pass = this._ensurePass(source, pass);
509
- 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;
510
606
  if (result !== undefined) {
511
607
  let done = false;
512
608
  const failureMessages = [];
@@ -538,13 +634,14 @@
538
634
  }
539
635
 
540
636
  class StringSchema extends BaseSchema {
541
- constructor(required, defaultValue, additionalValidationPasses) {
542
- super("string", required, defaultValue, additionalValidationPasses);
543
- }
637
+ get type() { return "string"; }
544
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.
545
639
  // public required() { return new StringSchema(true, this._default, this._additionalValidationPasses); }
546
640
  // public optional() { return new StringSchema(false, this._default, this._additionalValidationPasses); }
547
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
+ }
548
645
  convert(value, pass) {
549
646
  if (value instanceof Date) {
550
647
  return value.toISOString();
@@ -560,8 +657,8 @@
560
657
  length(minimum, maximum, messageA, messageB) {
561
658
  return this.custom((model, pass) => {
562
659
  messageB = messageB === undefined ? messageA : messageB;
563
- pass.assert(model.length > minimum, messageA === undefined ? `String "${model}: failed minimum length check. (${minimum})` : messageA);
564
- 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);
565
662
  return model;
566
663
  }, "afterAll");
567
664
  }
@@ -610,7 +707,7 @@
610
707
  }
611
708
  Schema.Array = Array;
612
709
  function Enumeration(members, required = true, defaultValue = undefined) {
613
- return new EnumerationSchema(members.data, required, defaultValue);
710
+ return new EnumerationSchema(members.$members, required, defaultValue);
614
711
  }
615
712
  Schema.Enumeration = Enumeration;
616
713
  function DynamicObject(subschema, required = true, defaultValue = undefined) {
@@ -618,11 +715,11 @@
618
715
  }
619
716
  Schema.DynamicObject = DynamicObject;
620
717
  function OrSet(members, required = true, defaultValue = undefined) {
621
- return new OrSetSchema(members.data, required, defaultValue);
718
+ return new OrSetSchema(members.$members, required, defaultValue);
622
719
  }
623
720
  Schema.OrSet = OrSet;
624
721
  function Members(...members) {
625
- return { data: members };
722
+ return { $members: members };
626
723
  }
627
724
  Schema.Members = Members;
628
725
  })(exports.Schema || (exports.Schema = {}));
@@ -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,24 +1,35 @@
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;
25
+ isRequired(): Required;
15
26
  hasDefault(): boolean;
16
27
  isDefaultRuntimeEvaluated(): this is {
17
28
  _default: Function;
18
29
  };
19
30
  getDefault(pass: ValidationPass): Source;
20
31
  protected _getValueDisplay(value: Model): string;
21
- abstract getJsonSchema(): object;
32
+ getJsonSchema(): object;
22
33
  protected _getJsonSchemaDescription(): string;
23
34
  protected _ensurePass(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ValidationPass;
24
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;
@@ -1,3 +1,5 @@
1
1
  import { BaseSchema } from "../schema/BaseSchema";
2
+ import { ObjectSchema } from "../schema/ObjectSchema";
2
3
  import { DefaultValue } from "./toolbox";
3
4
  export type BaseSchemaAny = BaseSchema<any, any, boolean, DefaultValue<any>>;
5
+ export type ObjectSchemaAny = ObjectSchema<any, boolean, DefaultValue<any>>;
@@ -7,7 +7,7 @@ export type ModelRequirement<Layout extends BaseSchemaAny> = (Layout extends Bas
7
7
  export type ModelValue<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> = (Required extends true ? Model : Default extends undefined ? Model | undefined : Model);
8
8
  export type DefaultValue<Type> = undefined | Type | ((pass: ValidationPass) => Type);
9
9
  export type TypedMembers<Members> = {
10
- data: Members;
10
+ $members: Members;
11
11
  };
12
12
  export type AdditionalValidatorBeforeType = ("beforeAll" | "beforeDefault" | "afterDefault");
13
13
  export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversion" | "afterAll");
@@ -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.1",
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
  }