@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 +47 -90
- package/build/builder.d.ts +1 -0
- package/build/index.js +6 -3
- package/build/schema/BaseSchema.d.ts +1 -0
- package/build/schema/ObjectSchema.d.ts +6 -4
- package/build/typing/extended.d.ts +2 -0
- package/build/typing/toolbox.d.ts +5 -1
- package/package.json +2 -2
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,
|
|
10
|
-
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
|`
|
|
14
|
-
|`boolean
|
|
15
|
-
|`
|
|
16
|
-
|`
|
|
17
|
-
|`
|
|
18
|
-
|`
|
|
19
|
-
|`
|
|
20
|
-
|`
|
|
21
|
-
|
|
22
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/build/builder.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
624
|
+
return new OrSetSchema(members.$members, required, defaultValue);
|
|
622
625
|
}
|
|
623
626
|
Schema.OrSet = OrSet;
|
|
624
627
|
function Members(...members) {
|
|
625
|
-
return {
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|