@lucania/schema 2.0.0 → 2.0.2

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,90 +1,64 @@
1
1
  # Schema
2
- ---
3
2
  | TECHNICAL | |
4
3
  |:-:|-|
5
4
  | _noun_ | _a representation of a plan or theory in the form of an outline or model._|
6
5
 
7
6
  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)_.
8
7
 
9
- With this library, everything is built from "Schema Primitives" _(henceforth referred to as "Primitives")_, out of the box, all of the JavaScript primitive types are implemented, but this set of _Primitives_ can be extended for your use case. The JavaScript `Date` is also a supported _Primitive_. All of your schema objects will be built from this extensible set of _Primitives_.
10
-
11
- |_Primitives_|
12
- |:-:|
13
- |`undefined`|
14
- |`boolean`|
15
- |`number`|
16
- |`bigint`|
17
- |`string`|
18
- |`symbol`|
19
- |`any`|
20
- |`Date`|
21
-
22
- ## Validation with a Primitive
8
+ 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
+ |_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>`
22
+
23
+ ## Validation
24
+ The simplest possible validation:
23
25
  ```typescript
24
- import { Schema } from "@lucania/schema";
26
+ import { $ } from "@lucania/schema";
27
+
28
+ // Create a Schema to represent a number.
29
+ const numberSchema = $.Number();
25
30
 
31
+ // Some data with an unknown type, likely coming from a file, network or untyped library.
26
32
  const dataOfUnknownType: any = 123;
27
- const dataOfNumberType = Schema.validate("number", dataOfUnknownType);
28
- ```
29
33
 
30
- With _Primitives_, you can now start building more complex data structures. There are multiple way to represent different scenarios. This is done with the following constructs.
34
+ // Using our number Schema to validate that our untyped data is what we expect it to be, a number. "dataOfNumberType" now has the "number" type.
35
+ const dataOfNumberType = numberSchema.validate(dataOfUnknownType);
36
+ ```
31
37
 
32
- | _Constructs_ |_Structure_|_TLDR_|
33
- |:-:|:-:|:-:|
34
- |`Meta`|`{ type: <Primitive or another Construct>, required: boolean, default?: <default value>, validate: <validator> }`|A meta object to describe the nature of a section within a schema.|
35
- |`Array`|`[ <Primitive or another Construct> ]`|Represents an array within a schema.|
36
- |`Hierarchy`|`{ [ <Any keys needed> ]: <Primitive or another Construct> }`|Represents nested object hierarchy within a schema.|
37
- |`Compound`|`[ <Primitive or another Construct>, "or", <Primitive or another Construct> ]`| Represents union types. Currently can be used with "and" or "or". |
38
- |`Dynamic`|`{ $: <Primitive or another Construct> }`|Represents a object with dynamic keys. (Produces the TypeScript type `{ [Key: string]: any }`)|
38
+ 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
39
 
40
- ## Creating Schema
41
- These primitives and constructs can come together to allow you to define your own schema.
40
+ ## Creating Hierarchical Schema
41
+ These schemas can come together to allow you to define a blueprint for more complex data structures.
42
42
  ```typescript
43
- import { Schema } from "@lucania/schema";
44
-
45
- /* Schema Definition */
46
- const PersonSchema = Schema.build({
47
- name: /* ← Hierarchy ↓ */ {
48
- first: /* ← Meta ↓ */ {
49
- type: "string" /* ← Primitive */,
50
- required: true
51
- },
52
- middle: { type: "string", required: false },
53
- last: { type: "string", required: true }
54
- },
55
- favoriteNumber: ["bigint" /* ← Primitive */, "or", "number"] /* ← Compound */,
56
- age: {
57
- type: "number",
58
- required: false,
59
- validate: (age: number, rawData: any) => {
60
- const birthDate = Schema.validate({ type: "Date", required: false }, rawData.birthDate);
61
- if (birthDate === undefined) {
62
- return undefined;
63
- } else {
64
- const now = new Date();
65
- const expectedAge = (now.getTime() - birthDate.getTime()) / 1000 / 60 / 60 / 24 / 365.25;
66
- Schema.assert(Math.floor(expectedAge) === age);
67
- return age;
68
- }
69
- }
70
- },
71
- birthDate: {
72
- type: "Date",
73
- required: false
74
- }
43
+ import { $ } from "@lucania/schema";
44
+
45
+ const WeaponSchema = $.Object({
46
+ damage: $.Number(true).clamp(0, 100),
47
+ forged: $.Date(false),
48
+ affixtures: $.DynamicObject($.String())
75
49
  });
76
50
 
77
- const WeaponSchema = Schema.build({
78
- damage: "number" /* ← Primitive */,
79
- owners: ["string"] /* ← Array */,
80
- forged: /* ← Meta ↓ */ {
81
- type: "Date",
82
- required: false
83
- },
84
- affixtures: /* Dynamic */ {
85
- $: "string"
86
- }
51
+ const PersonSchema = $.Object({
52
+ name: $.Object({
53
+ first: $.String(true),
54
+ middle: $.String(false),
55
+ last: $.String(true)
56
+ }),
57
+ favoriteNumber: $.Number(true).ensure((data, pass) => data % 2 === 0, "Your favorite number must be a multiple of 2!"),
58
+ age: $.Number(true).min(16, "You must be at least 16 years old!").max(100, "You must be at most 100 years old!"),
59
+ weapon: WeaponSchema
87
60
  });
61
+
88
62
  ```
89
63
 
90
64
  ## Using Schema
@@ -95,25 +69,8 @@ With the schema definitions created, they can be used to validate data where the
95
69
  import fs from "fs";
96
70
 
97
71
  const personData = JSON.parse(fs.readFileSync("person.json", "utf8"));
98
- const person = Schema.validate(PersonSchema, personData);
99
- ```
100
- `person` now has the following compile-time type annotation based on [`PersonSchema`](#creating-schema).
72
+ const person = PersonSchema.validate(personData);
101
73
  ```
102
- const person: {
103
- name: {
104
- first: string;
105
- last: string;
106
- } & {
107
- middle?: string | undefined;
108
- };
109
- favoriteNumber: number | bigint;
110
- }
111
- ```
112
- 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.
113
-
114
- ## To-Do
74
+ `person` now has the following compile-time type annotation based on [`PersonSchema`](#creating-hierarchical-schema).
115
75
 
116
- * Documentation:
117
- * Extending _Primitive_ set.
118
- * Defining a default value / function in Meta
119
- * Defining a validator in Meta
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.
@@ -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
@@ -109,6 +109,9 @@
109
109
  });
110
110
  return this;
111
111
  }
112
+ isRequired() {
113
+ return this._required;
114
+ }
112
115
  hasDefault() {
113
116
  return this._default !== undefined && this._default !== null;
114
117
  }
@@ -610,7 +613,7 @@
610
613
  }
611
614
  Schema.Array = Array;
612
615
  function Enumeration(members, required = true, defaultValue = undefined) {
613
- return new EnumerationSchema(members.data, required, defaultValue);
616
+ return new EnumerationSchema(members.$members, required, defaultValue);
614
617
  }
615
618
  Schema.Enumeration = Enumeration;
616
619
  function DynamicObject(subschema, required = true, defaultValue = undefined) {
@@ -618,11 +621,11 @@
618
621
  }
619
622
  Schema.DynamicObject = DynamicObject;
620
623
  function OrSet(members, required = true, defaultValue = undefined) {
621
- return new OrSetSchema(members.data, required, defaultValue);
624
+ return new OrSetSchema(members.$members, required, defaultValue);
622
625
  }
623
626
  Schema.OrSet = OrSet;
624
627
  function Members(...members) {
625
- return { data: members };
628
+ return { $members: members };
626
629
  }
627
630
  Schema.Members = Members;
628
631
  })(exports.Schema || (exports.Schema = {}));
@@ -12,6 +12,7 @@ export declare abstract class BaseSchema<Source, Model, Required extends boolean
12
12
  custom(additionalValidator: AdditionalValidator<Model>, type: AdditionalValidatorAfterType): this;
13
13
  custom(additionalValidator: AdditionalValidator<Model>): this;
14
14
  ensure(ensureValidator: EnsureValidator<Model>, message?: string): this;
15
+ isRequired(): Required;
15
16
  hasDefault(): boolean;
16
17
  isDefaultRuntimeEvaluated(): this is {
17
18
  _default: Function;
@@ -1,4 +1,4 @@
1
- import type { DefaultValue, Merge, ModelValue, SourceRequirement, SourceValue } from "../typing/toolbox";
1
+ import type { DefaultValue, Merge, ModelRequirement, ModelValue, SourceRequirement, SourceValue } from "../typing/toolbox";
2
2
  import { ValidationPass } from "../error/ValidationPass";
3
3
  import { BaseSchemaAny } from "../typing/extended";
4
4
  import { BaseSchema } from "./BaseSchema";
@@ -10,9 +10,11 @@ export type ObjectSource<Subschema extends ObjectSubschema> = (Merge<{
10
10
  }, {
11
11
  [Key in keyof Subschema as SourceRequirement<Subschema[Key]> extends false ? Key : never]?: (Subschema[Key] extends BaseSchema<infer Source, any, infer Required, infer Default> ? (SourceValue<Source, Required, Default>) : never);
12
12
  }>);
13
- export type ObjectModel<Subschema extends ObjectSubschema> = {
14
- [Key in keyof Subschema]: Subschema[Key] extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? (ModelValue<Source, Model, Required, Default>) : never;
15
- };
13
+ export type ObjectModel<Subschema extends ObjectSubschema> = (Merge<{
14
+ [Key in keyof Subschema as ModelRequirement<Subschema[Key]> extends true ? Key : never]: (Subschema[Key] extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? (ModelValue<Source, Model, Required, Default>) : never);
15
+ }, {
16
+ [Key in keyof Subschema as ModelRequirement<Subschema[Key]> extends false ? Key : never]?: (Subschema[Key] extends BaseSchema<infer Source, infer Model, infer Required, infer Default> ? (ModelValue<Source, Model, Required, Default>) : never);
17
+ }>);
16
18
  export declare class ObjectSchema<Subschema extends ObjectSubschema, Required extends boolean, Default extends DefaultValue<ObjectSource<Subschema>>> extends BaseSchema<ObjectSource<Subschema>, ObjectModel<Subschema>, Required, Default> {
17
19
  readonly subschema: Subschema;
18
20
  constructor(subschema: Subschema, required: Required, defaultValue: Default);
@@ -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>>;
@@ -3,10 +3,11 @@ import { BaseSchema } from "../schema/BaseSchema";
3
3
  import { BaseSchemaAny } from "./extended";
4
4
  export type SourceRequirement<Layout extends BaseSchemaAny> = (Layout extends BaseSchema<any, any, infer Required, infer Default> ? (Required extends true ? (Default extends undefined ? true : false) : (false)) : never);
5
5
  export type SourceValue<Source, Required extends boolean, Default extends DefaultValue<Source>> = (Required extends true ? (undefined extends Default ? (Source) : (Source | undefined)) : Source | undefined);
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);
6
7
  export type ModelValue<Source, Model, Required extends boolean, Default extends DefaultValue<Source>> = (Required extends true ? Model : Default extends undefined ? Model | undefined : Model);
7
8
  export type DefaultValue<Type> = undefined | Type | ((pass: ValidationPass) => Type);
8
9
  export type TypedMembers<Members> = {
9
- data: Members;
10
+ $members: Members;
10
11
  };
11
12
  export type AdditionalValidatorBeforeType = ("beforeAll" | "beforeDefault" | "afterDefault");
12
13
  export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversion" | "afterAll");
@@ -21,6 +22,9 @@ export type AdditionalValidatorAfterType = ("beforeConversion" | "afterConversio
21
22
  */
22
23
  export type AdditionalValidatorType = AdditionalValidatorBeforeType | AdditionalValidatorAfterType;
23
24
  export type AdditionalValidator<Type> = (data: Type, pass: ValidationPass) => Type;
25
+ /**
26
+ * Represents a pass to ensure that your data meets a condition. Return true if your data is ensured to meet condition, false otherwise.
27
+ */
24
28
  export type EnsureValidator<Type> = (data: Type, pass: ValidationPass) => boolean;
25
29
  export type AdditionalValidationPasses<Source, Model> = {
26
30
  beforeAll: AdditionalValidator<Source>[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucania/schema",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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",
@@ -37,4 +37,4 @@
37
37
  "tslib": "^2.6.2",
38
38
  "typescript": "^5.3.3"
39
39
  }
40
- }
40
+ }