@lucania/schema 2.0.2 → 2.0.4

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.
@@ -1,5 +1,5 @@
1
1
  import { BaseSchemaAny } from "./typing/extended";
2
- import { DefaultValue, ModelValue, TypedMembers } from "./typing/toolbox";
2
+ import { DefaultValue, ModelValue } from "./typing/toolbox";
3
3
  import { AnySchema } from "./schema/AnySchema";
4
4
  import { ArraySchema, ArraySource } from "./schema/ArraySchema";
5
5
  import { BooleanSchema, BooleanSource } from "./schema/BooleanSchema";
@@ -33,17 +33,23 @@ export declare namespace Schema {
33
33
  function Array<Subschema extends BaseSchemaAny>(subschema: Subschema): ArraySchema<Subschema, true, undefined>;
34
34
  function Array<Subschema extends BaseSchemaAny, Required extends boolean>(subschema: Subschema, required: Required): ArraySchema<Subschema, Required, undefined>;
35
35
  function Array<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<ArraySource<Subschema>>>(subschema: Subschema, required: Required, defaultValue: Default): ArraySchema<Subschema, Required, Default>;
36
- function Enumeration<Members extends string[]>(subschema: TypedMembers<Members>): EnumerationSchema<Members, true, undefined>;
37
- function Enumeration<Members extends string[], Required extends boolean>(subschema: TypedMembers<Members>, required: Required): EnumerationSchema<Members, Required, undefined>;
38
- function Enumeration<Members extends string[], Required extends boolean, Default extends DefaultValue<Members[number]>>(subschema: TypedMembers<Members>, required: Required, defaultValue: Default): EnumerationSchema<Members, Required, Default>;
36
+ function Enumeration<Member extends string>(subschema: TypedMembers<Member>): EnumerationSchema<Member, true, undefined>;
37
+ function Enumeration<Member extends string, Required extends boolean>(subschema: TypedMembers<Member>, required: Required): EnumerationSchema<Member, Required, undefined>;
38
+ function Enumeration<Member extends string, Required extends boolean, Default extends DefaultValue<Member>>(subschema: TypedMembers<Member>, required: Required, defaultValue: Default): EnumerationSchema<Member, Required, Default>;
39
39
  function DynamicObject<Subschema extends BaseSchemaAny>(subschema: Subschema): DynamicObjectSchema<Subschema, true, undefined>;
40
40
  function DynamicObject<Subschema extends BaseSchemaAny, Required extends boolean>(subschema: Subschema, required: Required): DynamicObjectSchema<Subschema, Required, undefined>;
41
41
  function DynamicObject<Subschema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<DynamicObjectSource<Subschema>>>(subschema: Subschema, required: Required, defaultValue: Default): DynamicObjectSchema<Subschema, Required, Default>;
42
- function OrSet<MemberSchemas extends BaseSchemaAny[]>(subschema: TypedMembers<MemberSchemas>): OrSetSchema<MemberSchemas, true, undefined>;
43
- function OrSet<MemberSchemas extends BaseSchemaAny[], Required extends boolean>(subschema: TypedMembers<MemberSchemas>, required: Required): OrSetSchema<MemberSchemas, Required, undefined>;
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>;
46
- function Members<Members extends any[]>(...members: Members): TypedMembers<Members>;
42
+ function OrSet<MemberSchema extends BaseSchemaAny>(subschema: TypedMembers<MemberSchema>): OrSetSchema<MemberSchema, true, undefined>;
43
+ function OrSet<MemberSchema extends BaseSchemaAny, Required extends boolean>(subschema: TypedMembers<MemberSchema>, required: Required): OrSetSchema<MemberSchema, Required, undefined>;
44
+ function OrSet<MemberSchema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchema>>>(subschema: TypedMembers<MemberSchema>, required: Required, defaultValue: Default): OrSetSchema<MemberSchema, Required, Default>;
45
+ type TypedMembers<Member extends any> = {
46
+ $members: Member[];
47
+ };
48
+ function Members<Member extends string[]>(...members: Member): TypedMembers<Member[number]>;
49
+ function Members<Member extends number[]>(...members: Member): TypedMembers<Member[number]>;
50
+ function Members<Member extends any[]>(...members: Member): TypedMembers<Member[number]>;
51
+ function Keys<Object extends object>(object: Object): (keyof Object)[];
52
+ function Values<Object extends object>(object: Object): (Object[keyof Object])[];
47
53
  type Model<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, infer Model, infer Require, infer Default> ? (ModelValue<Source, Model, Require, Default>) : never;
48
54
  type Source<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, any, infer Require, infer Default> ? (SourceValue<Source, Require, Default>) : never;
49
55
  }
package/build/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from "./builder";
2
- export { Schema as $ } from "./builder";
3
2
  export * from "./error/ValidationError";
4
3
  export * from "./error/ValidationPass";
5
4
  export * from "./schema/AnySchema";
@@ -15,3 +14,4 @@ export * from "./schema/OrSetSchema";
15
14
  export * from "./schema/StringSchema";
16
15
  export * from "./typing/extended";
17
16
  export * from "./typing/toolbox";
17
+ export { Schema as $ } from "./builder";
package/build/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  pass;
9
9
  constructor(pass, message) {
10
10
  super(message);
11
+ this.name = ValidationError.name;
11
12
  this.pass = pass;
12
13
  }
13
14
  }
@@ -51,12 +52,10 @@
51
52
  }
52
53
 
53
54
  class BaseSchema {
54
- type;
55
55
  _required;
56
56
  _default;
57
57
  _additionalValidationPasses;
58
- constructor(type, required, defaultValue, additionalValidationPasses) {
59
- this.type = type;
58
+ constructor(required, defaultValue, additionalValidationPasses) {
60
59
  this._required = required;
61
60
  this._default = defaultValue;
62
61
  this._additionalValidationPasses = additionalValidationPasses === undefined ? {
@@ -82,16 +81,18 @@
82
81
  if (BaseSchema.getType(result) !== this.type) {
83
82
  result = this.convert(result, pass);
84
83
  }
84
+ result = this._validate(result, pass);
85
85
  result = this._executeAdditionalValidator(result, pass, "afterConversion");
86
86
  }
87
87
  else {
88
- result = this._executeAdditionalValidator(result, pass, "afterConversion");
89
88
  if (this._required) {
90
89
  throw pass.getError(pass.path.length > 0 ? `Missing required value at "${pass.path.join(".")}".` : "Missing required value.");
91
90
  }
92
91
  else {
93
92
  result = undefined;
94
93
  }
94
+ result = this._validate(result, pass);
95
+ result = this._executeAdditionalValidator(result, pass, "afterConversion");
95
96
  }
96
97
  if (this._required || result !== undefined) {
97
98
  result = this._executeAdditionalValidator(result, pass, "afterAll");
@@ -102,6 +103,15 @@
102
103
  this._additionalValidationPasses[type].push(additionalValidator);
103
104
  return this;
104
105
  }
106
+ /**
107
+ * Ensures a condition is true during the associated schema's validation pass.
108
+ *
109
+ * @note See {@link EnsureValidator EnsureValidator}.
110
+ *
111
+ * @param ensureValidator A validator to ensure a condition is passed.
112
+ * @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
113
+ * @returns The schema instance for chaining.
114
+ */
105
115
  ensure(ensureValidator, message) {
106
116
  this.custom((value, pass) => {
107
117
  pass.assert(ensureValidator(value, pass), message === undefined ? `Failed to ensure value.` : message);
@@ -132,6 +142,13 @@
132
142
  _getValueDisplay(value) {
133
143
  return String(value);
134
144
  }
145
+ getJsonSchema() {
146
+ console.warn(`"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`);
147
+ return {
148
+ type: "unknown",
149
+ description: `"getJsonSchema" is not implemented in the "${this.constructor.name}" schema class!`,
150
+ };
151
+ }
135
152
  _getJsonSchemaDescription() {
136
153
  const pass = new ValidationPass(this, this._default);
137
154
  const descriptionPieces = [];
@@ -166,6 +183,29 @@
166
183
  }
167
184
  return source;
168
185
  }
186
+ /**
187
+ * Converts this schema to a informational string representing this schema.
188
+ *
189
+ * @note Details about this schema will be displayed in [square brackets].
190
+ * * [R] Indicates this schema is required.
191
+ * * [D] Indicates this schema has a default value.
192
+ *
193
+ * @param level The nesting level. You needn't specify this externally.
194
+ * @returns A string representation of this schema.
195
+ */
196
+ toString(level = 0) {
197
+ const modifiers = [];
198
+ if (this._required)
199
+ modifiers.push("R");
200
+ if (this._default !== undefined)
201
+ modifiers.push("D");
202
+ if (modifiers.length > 0) {
203
+ return `${this.constructor.name}[${modifiers.join("")}]`;
204
+ }
205
+ else {
206
+ return this.constructor.name;
207
+ }
208
+ }
169
209
  /**
170
210
  * Checks to see if a value is present. (Not null or undefined)
171
211
  * @param value The value to check the presence of.
@@ -198,8 +238,9 @@
198
238
  }
199
239
 
200
240
  class AnySchema extends BaseSchema {
201
- constructor(required, defaultValue) {
202
- super("any", required, defaultValue);
241
+ get type() { return "any"; }
242
+ _validate(source, pass) {
243
+ return source;
203
244
  }
204
245
  convert(value, pass) {
205
246
  return value;
@@ -207,21 +248,36 @@
207
248
  getJsonSchema() {
208
249
  return { description: this._getJsonSchemaDescription() };
209
250
  }
251
+ clone() {
252
+ return new AnySchema(this._required, this._default, this._additionalValidationPasses);
253
+ }
210
254
  }
211
255
 
212
256
  class ArraySchema extends BaseSchema {
213
257
  subschema;
214
- constructor(subschema, required, defaultValue) {
215
- super("array", required, defaultValue);
258
+ constructor(subschema, required, defaultValue, additionalValidationPasses) {
259
+ super(required, defaultValue, additionalValidationPasses);
216
260
  this.subschema = subschema;
217
261
  }
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));
262
+ get type() { return "array"; }
263
+ // public validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass?: ValidationPass):
264
+ // ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default> {
265
+ // pass = this._ensurePass(source, pass);
266
+ // const result: any = super.validate(source, pass);
267
+ // if (result !== undefined) {
268
+ // for (const key in result) {
269
+ // const nestedValue = result[key];
270
+ // result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
271
+ // }
272
+ // }
273
+ // return result;
274
+ // }
275
+ _validate(source, pass) {
276
+ const result = [];
277
+ if (source !== undefined) {
278
+ for (const key in source) {
279
+ const nestedValue = source[key];
280
+ result[key] = this.subschema.validate(source[key], pass.next([...pass.path, key], this.subschema, nestedValue));
225
281
  }
226
282
  }
227
283
  return result;
@@ -244,11 +300,18 @@
244
300
  items: this.subschema.getJsonSchema()
245
301
  };
246
302
  }
303
+ clone() {
304
+ return new ArraySchema(this.subschema.clone(), this._required, this._default, this._additionalValidationPasses);
305
+ }
306
+ toString(level = 0) {
307
+ return `${super.toString(level)}(${this.subschema.toString(level)}[])`;
308
+ }
247
309
  }
248
310
 
249
311
  class BooleanSchema extends BaseSchema {
250
- constructor(required, defaultValue) {
251
- super("boolean", required, defaultValue);
312
+ get type() { return "boolean"; }
313
+ _validate(source, pass) {
314
+ return source;
252
315
  }
253
316
  convert(value, pass) {
254
317
  if (typeof value === "number") {
@@ -269,6 +332,9 @@
269
332
  throw pass.getError(`Unable to convert ${BaseSchema.getType(value)} to boolean.`);
270
333
  }
271
334
  }
335
+ clone() {
336
+ return new BooleanSchema(this._required, this._default, this._additionalValidationPasses);
337
+ }
272
338
  getJsonSchema() {
273
339
  return {
274
340
  type: "boolean",
@@ -279,8 +345,9 @@
279
345
 
280
346
  const StandardDate = globalThis.Date;
281
347
  class DateSchema extends BaseSchema {
282
- constructor(required, defaultValue) {
283
- super("Date", required, defaultValue);
348
+ get type() { return "Date"; }
349
+ _validate(source, pass) {
350
+ return source;
284
351
  }
285
352
  convert(value, pass) {
286
353
  if (typeof value === "string") {
@@ -327,6 +394,9 @@
327
394
  return model;
328
395
  }, "afterAll");
329
396
  }
397
+ clone() {
398
+ return new DateSchema(this._required, this._default, this._additionalValidationPasses);
399
+ }
330
400
  getJsonSchema() {
331
401
  return {
332
402
  type: "string",
@@ -337,13 +407,25 @@
337
407
 
338
408
  class DynamicObjectSchema extends BaseSchema {
339
409
  subschema;
340
- constructor(subschema, required, defaultValue) {
341
- super("object", required, defaultValue);
410
+ constructor(subschema, required, defaultValue, additionalValidationPasses) {
411
+ super(required, defaultValue, additionalValidationPasses);
342
412
  this.subschema = subschema;
343
413
  }
344
- validate(source, pass) {
345
- pass = this._ensurePass(source, pass);
346
- const result = super.validate(source, pass);
414
+ get type() { return "object"; }
415
+ // public validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass?: ValidationPass):
416
+ // ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default> {
417
+ // pass = this._ensurePass(source, pass);
418
+ // const result: any = super.validate(source, pass);
419
+ // if (result !== undefined) {
420
+ // for (const key in result) {
421
+ // const nestedValue = result[key];
422
+ // result[key] = this.subschema.validate(result[key], pass.next([...pass.path, key], this.subschema, nestedValue));
423
+ // }
424
+ // }
425
+ // return result;
426
+ // }
427
+ _validate(source, pass) {
428
+ const result = source;
347
429
  if (result !== undefined) {
348
430
  for (const key in result) {
349
431
  const nestedValue = result[key];
@@ -367,23 +449,36 @@
367
449
  additionalProperties: this.subschema.getJsonSchema()
368
450
  };
369
451
  }
452
+ clone() {
453
+ return new DynamicObjectSchema(this.subschema.clone(), this._required, this._default, this._additionalValidationPasses);
454
+ }
455
+ toString(level = 0) {
456
+ const indent = " ";
457
+ const prefix = indent.repeat(level);
458
+ return `${super.toString(level)}({\n${prefix}${indent}[string]: ${this.subschema.toString(level)}\n${prefix}})`;
459
+ }
370
460
  }
371
461
 
372
462
  class EnumerationSchema extends BaseSchema {
373
463
  members;
374
- constructor(members, required, defaultValue) {
375
- super("string", required, defaultValue);
464
+ constructor(members, required, defaultValue, additionalValidationPasses) {
465
+ super(required, defaultValue, additionalValidationPasses);
376
466
  this.members = members;
377
467
  }
378
- validate(source, pass) {
379
- pass = this._ensurePass(source, pass);
380
- const result = super.validate(source, pass);
381
- pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
468
+ get type() { return "string"; }
469
+ _validate(source, pass) {
470
+ const result = source;
471
+ if (this._required) {
472
+ pass.assert(this.members.includes(result), `"${result}" is not a valid enumeration value (Expected: ${this.members.join(", ")}).`);
473
+ }
382
474
  return result;
383
475
  }
384
476
  convert(value, pass) {
385
477
  return value;
386
478
  }
479
+ clone() {
480
+ return new EnumerationSchema([...this.members], this._required, this._default, this._additionalValidationPasses);
481
+ }
387
482
  getJsonSchema() {
388
483
  return {
389
484
  type: "string",
@@ -394,8 +489,9 @@
394
489
  }
395
490
 
396
491
  class NumberSchema extends BaseSchema {
397
- constructor(required, defaultValue) {
398
- super("number", required, defaultValue);
492
+ get type() { return "number"; }
493
+ _validate(source, pass) {
494
+ return source;
399
495
  }
400
496
  convert(value, pass) {
401
497
  if (typeof value === "bigint") {
@@ -438,6 +534,9 @@
438
534
  return model;
439
535
  }, "afterAll");
440
536
  }
537
+ clone() {
538
+ return new NumberSchema(this._required, this._default, this._additionalValidationPasses);
539
+ }
441
540
  getJsonSchema() {
442
541
  return {
443
542
  type: "number",
@@ -448,23 +547,26 @@
448
547
 
449
548
  class ObjectSchema extends BaseSchema {
450
549
  subschema;
451
- constructor(subschema, required, defaultValue) {
452
- super("object", required, defaultValue);
550
+ constructor(subschema, required, defaultValue, additionalValidationPasses) {
551
+ super(required, defaultValue, additionalValidationPasses);
453
552
  this.subschema = subschema;
454
553
  }
455
- validate(source, pass) {
456
- pass = this._ensurePass(source, pass);
457
- const result = super.validate(source, pass);
458
- if (result !== undefined) {
554
+ get type() { return "object"; }
555
+ _validate(source, pass) {
556
+ const input = source;
557
+ let output = input;
558
+ if (typeof input === "object" && input !== null) {
559
+ output = {};
459
560
  for (const key in this.subschema) {
460
561
  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));
562
+ const nestedValue = input[key];
563
+ output[key] = this.subschema[key].validate(input[key], pass.next([...pass.path, key], nestedSchema, nestedValue));
463
564
  }
464
565
  }
465
- return result;
566
+ return output;
466
567
  }
467
568
  convert(value, pass) {
569
+ pass.assert(typeof value === "object", `Unable to convert ${ObjectSchema.getType(value)} to object.`);
468
570
  const model = {};
469
571
  for (const key in this.subschema) {
470
572
  const nestedSchema = this.subschema[key];
@@ -499,25 +601,48 @@
499
601
  properties
500
602
  };
501
603
  }
604
+ clone() {
605
+ const subschema = {};
606
+ for (const key in this.subschema) {
607
+ subschema[key] = this.subschema[key].clone();
608
+ }
609
+ return new ObjectSchema(subschema, this._required, this._default, this._additionalValidationPasses);
610
+ }
611
+ toString(level = 0) {
612
+ const indent = " ";
613
+ const prefix = indent.repeat(level);
614
+ const pieces = [];
615
+ pieces.push(`${super.toString()}({\n`);
616
+ for (const schemaKey in this.subschema) {
617
+ const subschema = this.subschema[schemaKey];
618
+ pieces.push(`${prefix}${indent}${schemaKey}: ${subschema.toString(level + 1)}\n`);
619
+ }
620
+ pieces.push(`${prefix}})`);
621
+ return pieces.join("");
622
+ }
502
623
  }
503
624
 
504
625
  class OrSetSchema extends BaseSchema {
505
626
  schemas;
506
- constructor(schemas, required, defaultValue) {
507
- super("string", required, defaultValue);
627
+ constructor(schemas, required, defaultValue, additionalValidationPasses) {
628
+ super(required, defaultValue, additionalValidationPasses);
508
629
  this.schemas = schemas;
509
630
  }
510
- validate(source, pass) {
511
- pass = this._ensurePass(source, pass);
512
- let result = super.validate(source, pass);
631
+ get type() {
632
+ return "string";
633
+ }
634
+ _validate(source, pass) {
635
+ let result = source;
513
636
  if (result !== undefined) {
514
637
  let done = false;
515
638
  const failureMessages = [];
516
639
  for (let i = 0; i < this.schemas.length && !done; i++) {
517
640
  const schema = this.schemas[i];
518
641
  try {
519
- result = schema.validate(result, pass);
520
- done = true;
642
+ if (BaseSchema.getType(result) === schema.type) {
643
+ result = schema.validate(result, pass);
644
+ done = true;
645
+ }
521
646
  }
522
647
  catch (error) {
523
648
  if (error instanceof Error) {
@@ -526,28 +651,36 @@
526
651
  else {
527
652
  failureMessages.push(`Schema #${i + 1}: ${String(error)}`);
528
653
  }
529
- pass.assert(failureMessages.length !== this.schemas.length, `Supplied value didn't match any schemas in or-set.\n${failureMessages.join("\n")}`);
530
654
  }
531
655
  }
656
+ if (!done) {
657
+ failureMessages.push(`Conversions for schemas in an OrSet are disabled.`);
658
+ }
659
+ pass.assert(failureMessages.length === 0, `Provided value (${BaseSchema.getType(result)}) matched no schemas ` +
660
+ `(${this.schemas.map((schema) => schema.type).join(", ")}).\n${failureMessages.join("\n")}`);
532
661
  }
533
662
  return result;
534
663
  }
535
664
  convert(value, pass) {
536
665
  return value;
537
666
  }
667
+ clone() {
668
+ return new OrSetSchema(this.schemas.map((schema) => schema.clone()), this._required, this._default, this._additionalValidationPasses);
669
+ }
538
670
  getJsonSchema() {
539
671
  return { oneOf: this.schemas.map((schema) => schema.getJsonSchema()) };
540
672
  }
541
673
  }
542
674
 
543
675
  class StringSchema extends BaseSchema {
544
- constructor(required, defaultValue, additionalValidationPasses) {
545
- super("string", required, defaultValue, additionalValidationPasses);
546
- }
676
+ get type() { return "string"; }
547
677
  // 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
678
  // public required() { return new StringSchema(true, this._default, this._additionalValidationPasses); }
549
679
  // public optional() { return new StringSchema(false, this._default, this._additionalValidationPasses); }
550
680
  // public default<Default extends DefaultValue<StringSource>>(defaultValue: Default) { return new StringSchema(this._required, defaultValue, this._additionalValidationPasses); }
681
+ _validate(source, pass) {
682
+ return source;
683
+ }
551
684
  convert(value, pass) {
552
685
  if (value instanceof Date) {
553
686
  return value.toISOString();
@@ -560,20 +693,46 @@
560
693
  }
561
694
  return value.toString();
562
695
  }
696
+ /**
697
+ * Trims strings validated by this schema.
698
+ * @see {@link String.trim}
699
+ */
700
+ trim() {
701
+ return this.custom((model) => model.trim());
702
+ }
703
+ /**
704
+ * Makes strings validated by this schema lowercase.
705
+ */
706
+ lower() {
707
+ return this.custom((model) => model.toLowerCase());
708
+ }
709
+ /**
710
+ * Makes strings validated by this schema uppercase.
711
+ */
712
+ upper() {
713
+ return this.custom((model) => model.toUpperCase());
714
+ }
563
715
  length(minimum, maximum, messageA, messageB) {
564
716
  return this.custom((model, pass) => {
565
717
  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);
718
+ pass.assert(model.length >= minimum, messageA === undefined ? `String "${model}: failed minimum length check. (${minimum})` : messageA);
719
+ pass.assert(model.length <= maximum, messageB === undefined ? `String "${model}: failed maximum length check. (${maximum})` : messageB);
568
720
  return model;
569
721
  }, "afterAll");
570
722
  }
723
+ /**
724
+ * Ensures a string validated by this schema matches a given regular expression.
725
+ * With an optional custom error message.
726
+ */
571
727
  regex(expression, message) {
572
728
  return this.custom((model, pass) => {
573
729
  pass.assert(expression.test(model), message === undefined ? `String "${model}" failed regular expression check. (${expression})` : message);
574
730
  return model;
575
731
  }, "afterAll");
576
732
  }
733
+ clone() {
734
+ return new StringSchema(this._required, this._default, this._additionalValidationPasses);
735
+ }
577
736
  getJsonSchema() {
578
737
  return {
579
738
  type: "string",
@@ -625,9 +784,26 @@
625
784
  }
626
785
  Schema.OrSet = OrSet;
627
786
  function Members(...members) {
787
+ /*
788
+ * HACK START: The hermes JS engine doesn't use globalThis.Array when interpreting `...members`
789
+ * It uses `Array`, which is already defined in this namespace.
790
+ */
791
+ if (!globalThis.Array.isArray(members)) {
792
+ const validArrayEntries = globalThis.Object.entries(members).filter(([key]) => !isNaN(key));
793
+ members = validArrayEntries.map(([_, value]) => value);
794
+ }
795
+ /* HACK END */
628
796
  return { $members: members };
629
797
  }
630
798
  Schema.Members = Members;
799
+ function Keys(object) {
800
+ return globalThis.Object.keys(object);
801
+ }
802
+ Schema.Keys = Keys;
803
+ function Values(object) {
804
+ return globalThis.Object.values(object);
805
+ }
806
+ Schema.Values = Values;
631
807
  })(exports.Schema || (exports.Schema = {}));
632
808
 
633
809
  exports.$ = exports.Schema;
@@ -1,8 +1,10 @@
1
- import { DefaultValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { DefaultValue, ModelValue } from "../typing/toolbox";
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: ModelValue<any, any, Required, Default>, pass: ValidationPass): ModelValue<any, any, Required, Default>;
6
7
  convert(value: any, pass: ValidationPass): any;
7
8
  getJsonSchema(): object;
9
+ clone(): AnySchema<Required, Default>;
8
10
  }
@@ -1,13 +1,16 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
2
  import { BaseSchemaAny } from "../typing/extended";
3
- import type { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
3
+ import type { AdditionalValidationPasses, DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
4
4
  import { BaseSchema } from "./BaseSchema";
5
5
  export type ArraySource<Subschema extends BaseSchemaAny> = (Subschema extends BaseSchema<infer Source, any, infer Required, infer Default> ? (SourceValue<Source, Required, Default>[]) : never);
6
6
  export type ArrayModel<Subschema extends BaseSchemaAny> = (Subschema extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? (ModelValue<Source, Model, Required, Default>[]) : never);
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
- constructor(subschema: Subschema, required: Required, defaultValue: Default);
10
- validate(source: SourceValue<ArraySource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<ArraySource<Subschema>, ArrayModel<Subschema>, Required, Default>;
9
+ constructor(subschema: Subschema, required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<ArraySource<Subschema>, ArrayModel<Subschema>>);
10
+ get type(): string;
11
+ protected _validate(source: ModelValue<ArraySource<Subschema>, ArrayModel<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;
14
+ clone(): ArraySchema<Subschema, Required, Default>;
15
+ toString(level?: number): string;
13
16
  }
@@ -1,16 +1,27 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { BaseSchemaAny } from "../typing/extended";
2
3
  import type { AdditionalValidationPasses, AdditionalValidator, AdditionalValidatorAfterType, AdditionalValidatorBeforeType, DefaultValue, EnsureValidator, ModelValue, SourceValue } from "../typing/toolbox";
3
4
  export declare abstract class BaseSchema<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> {
4
- readonly type: string;
5
5
  protected readonly _required: Required;
6
6
  protected readonly _default: Default;
7
7
  protected readonly _additionalValidationPasses: AdditionalValidationPasses<Source, Model>;
8
- constructor(type: string, required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Source, Model>);
8
+ constructor(required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Source, Model>);
9
+ abstract get type(): string;
9
10
  validate(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ModelValue<Source, Model, Required, Default>;
11
+ protected abstract _validate(source: ModelValue<Source, Model, Required, Default>, pass: ValidationPass): ModelValue<Source, Model, Required, Default>;
10
12
  abstract convert(value: Source, pass: ValidationPass): Model;
11
13
  custom(additionalValidator: AdditionalValidator<SourceValue<Source, Required, Default>>, type: AdditionalValidatorBeforeType): this;
12
14
  custom(additionalValidator: AdditionalValidator<Model>, type: AdditionalValidatorAfterType): this;
13
15
  custom(additionalValidator: AdditionalValidator<Model>): this;
16
+ /**
17
+ * Ensures a condition is true during the associated schema's validation pass.
18
+ *
19
+ * @note See {@link EnsureValidator EnsureValidator}.
20
+ *
21
+ * @param ensureValidator A validator to ensure a condition is passed.
22
+ * @param message The message to use for a thrown {@link ValidationError ValidationError} if ensureValidator fails.
23
+ * @returns The schema instance for chaining.
24
+ */
14
25
  ensure(ensureValidator: EnsureValidator<Model>, message?: string): this;
15
26
  isRequired(): Required;
16
27
  hasDefault(): boolean;
@@ -19,10 +30,22 @@ export declare abstract class BaseSchema<Source, Model, Required extends boolean
19
30
  };
20
31
  getDefault(pass: ValidationPass): Source;
21
32
  protected _getValueDisplay(value: Model): string;
22
- abstract getJsonSchema(): object;
33
+ getJsonSchema(): object;
23
34
  protected _getJsonSchemaDescription(): string;
24
35
  protected _ensurePass(source: SourceValue<Source, Required, Default>, pass?: ValidationPass): ValidationPass;
25
36
  private _executeAdditionalValidator;
37
+ abstract clone(): BaseSchemaAny;
38
+ /**
39
+ * Converts this schema to a informational string representing this schema.
40
+ *
41
+ * @note Details about this schema will be displayed in [square brackets].
42
+ * * [R] Indicates this schema is required.
43
+ * * [D] Indicates this schema has a default value.
44
+ *
45
+ * @param level The nesting level. You needn't specify this externally.
46
+ * @returns A string representation of this schema.
47
+ */
48
+ toString(level?: number): string;
26
49
  /**
27
50
  * Checks to see if a value is present. (Not null or undefined)
28
51
  * @param value The value to check the presence of.
@@ -1,9 +1,11 @@
1
- import { DefaultValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { DefaultValue, ModelValue } from "../typing/toolbox";
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: ModelValue<BooleanSource, boolean, Required, Default>, pass: ValidationPass): ModelValue<BooleanSource, boolean, Required, Default>;
7
8
  convert(value: BooleanSource, pass: ValidationPass): boolean;
9
+ clone(): BooleanSchema<Required, Default>;
8
10
  getJsonSchema(): object;
9
11
  }
@@ -1,11 +1,12 @@
1
- import { DefaultValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { DefaultValue, ModelValue } from "../typing/toolbox";
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: ModelValue<DateSource, Date, 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;
@@ -17,6 +18,7 @@ export declare class DateSchema<Required extends boolean, Default extends Defaul
17
18
  * @param duration milliseconds
18
19
  */
19
20
  lessThanAgo(duration: number, message?: string): this;
21
+ clone(): DateSchema<Required, Default>;
20
22
  getJsonSchema(): object;
21
23
  }
22
24
  export {};
@@ -1,6 +1,6 @@
1
- import type { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
3
2
  import { BaseSchemaAny } from "../typing/extended";
3
+ import type { AdditionalValidationPasses, DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
4
4
  import { BaseSchema } from "./BaseSchema";
5
5
  export type DynamicObjectSource<Subschema extends BaseSchemaAny> = ({
6
6
  [Key: string]: (Subschema extends BaseSchema<infer Source, any, infer Required, infer Default> ? (SourceValue<Source, Required, Default>) : never);
@@ -10,8 +10,11 @@ export type DynamicObjectModel<Subschema extends BaseSchemaAny> = ({
10
10
  });
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
- constructor(subschema: Subschema, required: Required, defaultValue: Default);
14
- validate(source: SourceValue<DynamicObjectSource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>, Required, Default>;
13
+ constructor(subschema: Subschema, required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<DynamicObjectSource<Subschema>, DynamicObjectModel<Subschema>>);
14
+ get type(): string;
15
+ protected _validate(source: ModelValue<DynamicObjectSource<Subschema>, DynamicObjectModel<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;
18
+ clone(): DynamicObjectSchema<Subschema, Required, Default>;
19
+ toString(level?: number): string;
17
20
  }
@@ -1,10 +1,12 @@
1
- import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { AdditionalValidationPasses, DefaultValue, ModelValue } from "../typing/toolbox";
3
3
  import { BaseSchema } from "./BaseSchema";
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
- readonly members: Members;
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>;
8
- convert(value: Members[number], pass: ValidationPass): Members[number];
4
+ export declare class EnumerationSchema<Members extends string, Required extends boolean, Default extends DefaultValue<Members>> extends BaseSchema<Members, Members, Required, Default> {
5
+ readonly members: Members[];
6
+ constructor(members: Members[], required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<Members, Members>);
7
+ get type(): string;
8
+ protected _validate(source: ModelValue<Members, Members, Required, Default>, pass: ValidationPass): ModelValue<Members, Members, Required, Default>;
9
+ convert(value: Members, pass: ValidationPass): Members;
10
+ clone(): EnumerationSchema<Members, Required, Default>;
9
11
  getJsonSchema(): object;
10
12
  }
@@ -1,9 +1,10 @@
1
- import { DefaultValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
2
+ import { DefaultValue, ModelValue } from "../typing/toolbox";
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: ModelValue<NumberSource, number, 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;
@@ -11,5 +12,6 @@ export declare class NumberSchema<Required extends boolean, Default extends Defa
11
12
  clamp(minimum: number, maximum: number, message: string): this;
12
13
  clamp(minimum: number, maximum: number, tooShortMessage: string, tooLongMessage: string): this;
13
14
  validNumber(notANumber?: boolean, message?: string): this;
15
+ clone(): NumberSchema<Required, Default>;
14
16
  getJsonSchema(): object;
15
17
  }
@@ -1,6 +1,6 @@
1
- import type { DefaultValue, Merge, ModelRequirement, ModelValue, SourceRequirement, SourceValue } from "../typing/toolbox";
2
1
  import { ValidationPass } from "../error/ValidationPass";
3
2
  import { BaseSchemaAny } from "../typing/extended";
3
+ import type { AdditionalValidationPasses, DefaultValue, Merge, ModelRequirement, ModelValue, SourceRequirement, SourceValue } from "../typing/toolbox";
4
4
  import { BaseSchema } from "./BaseSchema";
5
5
  export type ObjectSubschema = {
6
6
  [Key: string]: BaseSchemaAny;
@@ -17,9 +17,12 @@ export type ObjectModel<Subschema extends ObjectSubschema> = (Merge<{
17
17
  }>);
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
- constructor(subschema: Subschema, required: Required, defaultValue: Default);
21
- validate(source: SourceValue<ObjectSource<Subschema>, Required, Default>, pass?: ValidationPass): ModelValue<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default>;
20
+ constructor(subschema: Subschema, required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<ObjectSource<Subschema>, ObjectModel<Subschema>>);
21
+ get type(): string;
22
+ protected _validate(source: ModelValue<ObjectSource<Subschema>, ObjectModel<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;
26
+ clone(): ObjectSchema<Subschema, Required, Default>;
27
+ toString(level?: number): string;
25
28
  }
@@ -1,17 +1,15 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
2
  import { BaseSchemaAny } from "../typing/extended";
3
- import { DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
3
+ import { AdditionalValidationPasses, DefaultValue, ModelValue, SourceValue } from "../typing/toolbox";
4
4
  import { BaseSchema } from "./BaseSchema";
5
- export type OrSetSchemaSource<MemberSchemas extends BaseSchemaAny[]> = {
6
- [Key in keyof MemberSchemas]: (MemberSchemas[Key] extends BaseSchema<infer Source, any, infer Required, infer Default> ? (SourceValue<Source, Required, Default>) : never);
7
- }[number];
8
- export type OrSetSchemaModel<MemberSchemas extends BaseSchemaAny[]> = ({
9
- [Key in keyof MemberSchemas]: (MemberSchemas[Key] extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? (ModelValue<Source, Model, Required, Default>) : never);
10
- })[number];
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
- readonly schemas: MemberSchemas;
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>;
15
- convert(value: OrSetSchemaSource<MemberSchemas>, pass: ValidationPass): OrSetSchemaModel<MemberSchemas>;
5
+ export type OrSetSchemaSource<MemberSchema extends BaseSchemaAny> = (MemberSchema extends BaseSchema<infer Source, any, infer Required, infer Default> ? SourceValue<Source, Required, Default> : never);
6
+ export type OrSetSchemaModel<MemberSchema extends BaseSchemaAny> = (MemberSchema extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? ModelValue<Source, Model, Required, Default> : never);
7
+ export declare class OrSetSchema<MemberSchema extends BaseSchemaAny, Required extends boolean, Default extends DefaultValue<OrSetSchemaSource<MemberSchema>>> extends BaseSchema<OrSetSchemaSource<MemberSchema>, OrSetSchemaModel<MemberSchema>, Required, Default> {
8
+ readonly schemas: MemberSchema[];
9
+ constructor(schemas: MemberSchema[], required: Required, defaultValue: Default, additionalValidationPasses?: AdditionalValidationPasses<OrSetSchemaSource<MemberSchema>, OrSetSchemaModel<MemberSchema>>);
10
+ get type(): string;
11
+ protected _validate(source: ModelValue<OrSetSchemaSource<MemberSchema>, OrSetSchemaModel<MemberSchema>, Required, Default>, pass: ValidationPass): ModelValue<OrSetSchemaSource<MemberSchema>, OrSetSchemaModel<MemberSchema>, Required, Default>;
12
+ convert(value: OrSetSchemaSource<MemberSchema>, pass: ValidationPass): OrSetSchemaModel<MemberSchema>;
13
+ clone(): OrSetSchema<MemberSchema, Required, Default>;
16
14
  getJsonSchema(): object;
17
15
  }
@@ -1,13 +1,43 @@
1
1
  import { ValidationPass } from "../error/ValidationPass";
2
- import { AdditionalValidationPasses, DefaultValue } from "../typing/toolbox";
2
+ import { DefaultValue, ModelValue } 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: ModelValue<StringSource, string, Required, Default>, pass: ValidationPass): ModelValue<StringSource, string, Required, Default>;
7
8
  convert(value: StringSource, pass: ValidationPass): string;
9
+ /**
10
+ * Trims strings validated by this schema.
11
+ * @see {@link String.trim}
12
+ */
13
+ trim(): this;
14
+ /**
15
+ * Makes strings validated by this schema lowercase.
16
+ */
17
+ lower(): this;
18
+ /**
19
+ * Makes strings validated by this schema uppercase.
20
+ */
21
+ upper(): this;
22
+ /**
23
+ * Ensures that a string validated by this schema has a minimum (inclusive) and maximum (inclusive) length.
24
+ */
8
25
  length(minimum: number, maximum: number): this;
26
+ /**
27
+ * Ensures that a string validated by this schema has a minimum (inclusive) and maximum (inclusive) length.
28
+ * With a custom error message.
29
+ */
9
30
  length(minimum: number, maximum: number, message: string): this;
31
+ /**
32
+ * Ensures that a string validated by this schema has a minimum (inclusive) and maximum (inclusive) length.
33
+ * With custom error messages for too long, and too short.
34
+ */
10
35
  length(minimum: number, maximum: number, tooShortMessage: string, tooLongMessage: string): this;
36
+ /**
37
+ * Ensures a string validated by this schema matches a given regular expression.
38
+ * With an optional custom error message.
39
+ */
11
40
  regex(expression: RegExp, message?: string): this;
41
+ clone(): StringSchema<Required, Default>;
12
42
  getJsonSchema(): object;
13
43
  }
@@ -6,9 +6,6 @@ export type SourceValue<Source, Required extends boolean, Default extends Defaul
6
6
  export type ModelRequirement<Layout extends BaseSchemaAny> = (Layout extends BaseSchema<any, any, infer Required, infer Default> ? (Required extends true ? (true) : (Default extends undefined ? false : true)) : never);
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
- export type TypedMembers<Members> = {
10
- $members: Members;
11
- };
12
9
  export type AdditionalValidatorBeforeType = ("beforeAll" | "beforeDefault" | "afterDefault");
13
10
  export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversion" | "afterAll");
14
11
  /**
@@ -21,9 +18,21 @@ export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversio
21
18
  * - afterAll: Validator executed after "afterConversion" and as the last task in the validation pipeline.
22
19
  */
23
20
  export type AdditionalValidatorType = AdditionalValidatorBeforeType | AdditionalValidatorAfterType;
21
+ /**
22
+ * Represents a custom pass allowing any additional value specificity code, or value augmentations.
23
+ *
24
+ * @note Use "{@link ValidationPass pass}.assert" and/or "throw {@link ValidationPass pass}.getError" to build your value specificity checks.
25
+ *
26
+ * @param {Type} data The data being validated.
27
+ * @param {ValidationPass} pass A reference to the current validation pass. Used to interact with the validation pass.
28
+ * @returns `data` with any desired augmentations.
29
+ */
24
30
  export type AdditionalValidator<Type> = (data: Type, pass: ValidationPass) => Type;
25
31
  /**
26
- * Represents a pass to ensure that your data meets a condition. Return true if your data is ensured to meet condition, false otherwise.
32
+ * Represents a pass to ensure that your data meets conditions.
33
+ * @param data The data being validated.
34
+ * @param pass A reference to the current validation pass. Used to interact with the validation pass.
35
+ * @returns `true` if your data meets the desired conditions, `false` otherwise.
27
36
  */
28
37
  export type EnsureValidator<Type> = (data: Type, pass: ValidationPass) => boolean;
29
38
  export type AdditionalValidationPasses<Source, Model> = {
@@ -34,4 +43,7 @@ export type AdditionalValidationPasses<Source, Model> = {
34
43
  afterConversion: AdditionalValidator<Model>[];
35
44
  afterAll: AdditionalValidator<Model>[];
36
45
  };
37
- export type Merge<ObjectA, ObjectB> = (keyof ObjectA extends never ? ObjectB : keyof ObjectB extends never ? ObjectA : ObjectA & ObjectB);
46
+ /**
47
+ * Intersects two types.
48
+ */
49
+ 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.4",
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
  }