@lucania/schema 1.0.8 → 2.0.1

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.
@@ -0,0 +1,48 @@
1
+ import { BaseSchemaAny } from "./typing/extended";
2
+ import { DefaultValue, ModelValue, TypedMembers } from "./typing/toolbox";
3
+ import { AnySchema } from "./schema/AnySchema";
4
+ import { ArraySchema, ArraySource } from "./schema/ArraySchema";
5
+ import { BooleanSchema, BooleanSource } from "./schema/BooleanSchema";
6
+ import { DateSchema, DateSource } from "./schema/DateSchema";
7
+ import { DynamicObjectSchema, DynamicObjectSource } from "./schema/DynamicObjectSchema";
8
+ import { EnumerationSchema } from "./schema/EnumerationSchema";
9
+ import { NumberSchema, NumberSource } from "./schema/NumberSchema";
10
+ import { ObjectSchema, ObjectSource, ObjectSubschema } from "./schema/ObjectSchema";
11
+ import { OrSetSchema, OrSetSchemaSource } from "./schema/OrSetSchema";
12
+ import { StringSchema, StringSource } from "./schema/StringSchema";
13
+ import { BaseSchema, SourceValue } from ".";
14
+ export declare namespace Schema {
15
+ function String(): StringSchema<true, undefined>;
16
+ function String<Required extends boolean>(required: Required): StringSchema<Required, undefined>;
17
+ function String<Required extends boolean, Default extends DefaultValue<StringSource>>(required: Required, defaultValue: Default): StringSchema<Required, Default>;
18
+ function Number(): NumberSchema<true, undefined>;
19
+ function Number<Required extends boolean>(required: Required): NumberSchema<Required, undefined>;
20
+ function Number<Required extends boolean, Default extends DefaultValue<NumberSource>>(required: Required, defaultValue: Default): NumberSchema<Required, Default>;
21
+ function Boolean(): BooleanSchema<true, undefined>;
22
+ function Boolean<Required extends boolean>(required: Required): BooleanSchema<Required, undefined>;
23
+ function Boolean<Required extends boolean, Default extends DefaultValue<BooleanSource>>(required: Required, defaultValue: Default): BooleanSchema<Required, Default>;
24
+ function Date(): DateSchema<true, undefined>;
25
+ function Date<Required extends boolean>(required: Required): DateSchema<Required, undefined>;
26
+ function Date<Required extends boolean, Default extends DefaultValue<DateSource>>(required: Required, defaultValue: Default): DateSchema<Required, Default>;
27
+ function Any(): AnySchema<true, undefined>;
28
+ function Any<Required extends boolean>(required: Required): AnySchema<Required, undefined>;
29
+ function Any<Required extends boolean, Default extends DefaultValue<any>>(required: Required, defaultValue: Default): AnySchema<Required, Default>;
30
+ function Object<Subschema extends ObjectSubschema>(subschema: Subschema): ObjectSchema<Subschema, true, undefined>;
31
+ function Object<Subschema extends ObjectSubschema, Required extends boolean>(subschema: Subschema, required: Required): ObjectSchema<Subschema, Required, undefined>;
32
+ function Object<Subschema extends ObjectSubschema, Required extends boolean, Default extends DefaultValue<ObjectSource<Subschema>>>(subschema: Subschema, required: Required, defaultValue: Default): ObjectSchema<Subschema, Required, Default>;
33
+ function Array<Subschema extends BaseSchemaAny>(subschema: Subschema): ArraySchema<Subschema, true, undefined>;
34
+ function Array<Subschema extends BaseSchemaAny, Required extends boolean>(subschema: Subschema, required: Required): ArraySchema<Subschema, Required, undefined>;
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>;
39
+ function DynamicObject<Subschema extends BaseSchemaAny>(subschema: Subschema): DynamicObjectSchema<Subschema, true, undefined>;
40
+ function DynamicObject<Subschema extends BaseSchemaAny, Required extends boolean>(subschema: Subschema, required: Required): DynamicObjectSchema<Subschema, Required, undefined>;
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 any[]>(...members: Members): TypedMembers<Members>;
46
+ type Model<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, infer Model, infer Require, infer Default> ? (ModelValue<Source, Model, Require, Default>) : never;
47
+ type Source<Schema extends BaseSchemaAny> = Schema extends BaseSchema<infer Source, any, infer Require, infer Default> ? (SourceValue<Source, Require, Default>) : never;
48
+ }
@@ -0,0 +1,5 @@
1
+ import { ValidationPass } from "./ValidationPass";
2
+ export declare class ValidationError extends Error {
3
+ readonly pass: ValidationPass;
4
+ constructor(pass: ValidationPass, message: string);
5
+ }
@@ -0,0 +1,17 @@
1
+ import { DefaultValue, SourceValue } from "../typing/toolbox";
2
+ import { BaseSchemaAny } from "../typing/extended";
3
+ import { ValidationError } from "./ValidationError";
4
+ export declare class ValidationPass {
5
+ readonly originalSchema: BaseSchemaAny;
6
+ readonly originalSource: SourceValue<any, boolean, DefaultValue<any>>;
7
+ private _path;
8
+ private _schema;
9
+ private _source;
10
+ constructor(originalSchema: BaseSchemaAny, originalSource: SourceValue<any, boolean, DefaultValue<any>>);
11
+ get path(): string[];
12
+ get schema(): BaseSchemaAny;
13
+ get source(): any;
14
+ next(path: string[], schema: BaseSchemaAny, source: SourceValue<any, boolean, DefaultValue<any>>): ValidationPass;
15
+ assert(condition: boolean, message: string): void;
16
+ getError(message?: string): ValidationError;
17
+ }
package/build/index.d.ts CHANGED
@@ -1,100 +1,17 @@
1
- export declare namespace Schema {
2
- interface Map {
3
- undefined: undefined;
4
- boolean: boolean;
5
- number: number;
6
- bigint: bigint;
7
- string: string;
8
- symbol: symbol;
9
- any: any;
10
- Date: Date;
11
- }
12
- type Converter<From, To> = (from: From) => To;
13
- interface ConversionMap {
14
- boolean: [number, bigint, string];
15
- number: [boolean, bigint, string, Date];
16
- bigint: [number, string];
17
- string: [boolean, number, bigint, any, Date];
18
- any: [string];
19
- Date: [number, bigint, string];
20
- }
21
- type Primitive = keyof Map;
22
- type OrCompound = [Any, "or", Any];
23
- type AndCompound = [Any, "and", Any];
24
- type Dynamic = {
25
- $: Any;
26
- };
27
- type Array = [Any];
28
- type Hierarchy = {
29
- [Key: string]: Any;
30
- };
31
- type Most = Primitive | OrCompound | AndCompound | Dynamic | Array | Hierarchy;
32
- type TypedMeta<Schema extends Most> = {
33
- type: Schema;
34
- required: boolean;
35
- validate?: Validator<Model<Schema>>;
36
- default?: Model<Schema> | (() => Model<Schema>);
37
- };
38
- type Meta = TypedMeta<Most>;
39
- type Any = Primitive | OrCompound | AndCompound | Meta | Dynamic | Array | Hierarchy;
40
- type Validator<Type> = (value: Type, data: any) => Type;
41
- type Merge<ObjectA, ObjectB> = (keyof ObjectA extends never ? ObjectB : keyof ObjectB extends never ? ObjectA : ObjectA & ObjectB);
42
- type Model<Schema extends Schema.Any> = (Schema.Any extends Schema ? unknown : Schema extends Primitive ? Map[Schema] : Schema extends OrCompound ? Model<Schema[0]> | Model<Schema[2]> : Schema extends AndCompound ? Model<Schema[0]> & Model<Schema[2]> : Schema extends Meta ? (Schema extends TypedMeta<infer MetaType> ? (Schema.Most extends MetaType ? unknown : Schema["required"] extends false ? Model<MetaType> | undefined : Model<MetaType>) : never) : Schema extends Dynamic ? {
43
- [Key: string]: Model<Schema["$"]>;
44
- } : Schema extends Array ? Model<Schema[0]>[] : Schema extends Hierarchy ? Merge<{
45
- [Key in keyof Schema as Existent<Schema[Key]> extends true ? Key : never]: Model<Schema[Key]>;
46
- }, {
47
- [Key in keyof Schema as Existent<Schema[Key]> extends true ? never : Key]?: Model<Schema[Key]>;
48
- }> : any);
49
- type Source<Schema extends Schema.Any> = (Schema.Any extends Schema ? unknown : Schema extends Primitive ? Converted<Schema> : Schema extends OrCompound ? Source<Schema[0]> | Source<Schema[2]> : Schema extends AndCompound ? Source<Schema[0]> & Source<Schema[2]> : Schema extends Meta ? Source<Schema["type"]> : Schema extends Dynamic ? {
50
- [Key: string]: Source<Schema["$"]>;
51
- } : Schema extends Array ? Source<Schema[0]>[] : Schema extends Hierarchy ? Merge<{
52
- [Key in keyof Schema as Optionality<Schema[Key]> extends true ? never : Key]: Source<Schema[Key]>;
53
- }, {
54
- [Key in keyof Schema as Optionality<Schema[Key]> extends true ? Key : never]?: Source<Schema[Key]>;
55
- }> : any);
56
- type Converted<Schema extends Schema.Primitive> = (Schema extends keyof ConversionMap ? ConversionMap[Schema][number] | Map[Schema] : Map[Schema]);
57
- type Existent<Schema extends Schema.Any> = (Schema.Any extends Schema ? unknown : Schema extends Schema.Meta ? (Schema["required"] extends true ? true : (Schema extends {
58
- default: any;
59
- } ? true : false)) : Schema extends Schema.Array ? Existent<Schema[0]> : true);
60
- type Optionality<Schema extends Schema.Any> = (Schema.Any extends Schema ? unknown : Schema extends Schema.Meta ? (Schema["required"] extends true ? (Schema extends {
61
- default: any;
62
- } ? true : false) : true) : Schema extends Schema.Array ? Optionality<Schema[0]> : false);
63
- function registerConverter<FromTypeName extends keyof ConversionMap, ToTypeName extends keyof Map>(fromTypeName: FromTypeName, toTypeName: ToTypeName, converter: Converter<Map[FromTypeName], Map[ToTypeName]>): void;
64
- function registerPrimitive<TypeName extends keyof Map>(typeName: TypeName): void;
65
- function isSchemaPrimitive(value: any): value is Schema.Primitive;
66
- function isSchemaOrCompound(value: any): value is Schema.OrCompound;
67
- function isSchemaAndCompound(value: any): value is Schema.AndCompound;
68
- function isSchemaMeta(value: any): value is Schema.Meta;
69
- function isSchemaDynamic(value: any): value is Schema.Dynamic;
70
- function isSchemaArray(value: any): value is Schema.Array;
71
- function isSchemaHierarchy(value: any): value is Schema.Hierarchy;
72
- function isSchema(value: any): value is Schema.Any;
73
- function getType(value: any): string;
74
- function build<Schema extends Schema.Any>(schema: Schema): Schema;
75
- function validate<Schema extends Any>(schema: Schema, source: Source<Schema>): Model<Schema>;
76
- function clone<Schema extends Schema.Any>(schema: Schema): Schema;
77
- function assert(condition: boolean, message?: string): asserts condition;
78
- type ValidationErrorType = "missing" | "incorrectType" | "invalidSchema" | "failedCustomValidator";
79
- class Error extends globalThis.Error {
80
- constructor(message?: string);
81
- }
82
- class ValidationError extends Error {
83
- readonly type: ValidationErrorType;
84
- readonly source: any;
85
- readonly schema: Schema.Any;
86
- readonly originalSource: any;
87
- readonly originalSchema: Schema.Any;
88
- readonly path: string[];
89
- readonly customMessage?: string;
90
- /**
91
- * @param message The error message.
92
- * @param type The type of validation error.
93
- * @param source The data that failed validation.
94
- * @param path The path to the value that failed validation.
95
- */
96
- constructor(type: ValidationErrorType, schema: Schema.Any, source: any, path: string[], originalSchema: Schema.Any, originalSource: any, customMessage?: string);
97
- static getExpectedString(schema: Schema.Any): string;
98
- static getMessage(type: ValidationErrorType, source: any, schema: Schema.Any, path: string[]): string;
99
- }
100
- }
1
+ export * from "./builder";
2
+ export { Schema as $ } from "./builder";
3
+ export * from "./error/ValidationError";
4
+ export * from "./error/ValidationPass";
5
+ export * from "./schema/AnySchema";
6
+ export * from "./schema/ArraySchema";
7
+ export * from "./schema/BaseSchema";
8
+ export * from "./schema/BooleanSchema";
9
+ export * from "./schema/DateSchema";
10
+ export * from "./schema/DynamicObjectSchema";
11
+ export * from "./schema/EnumerationSchema";
12
+ export * from "./schema/NumberSchema";
13
+ export * from "./schema/ObjectSchema";
14
+ export * from "./schema/OrSetSchema";
15
+ export * from "./schema/StringSchema";
16
+ export * from "./typing/extended";
17
+ export * from "./typing/toolbox";