@lucania/schema 2.0.0 → 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.
@@ -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);
@@ -3,6 +3,7 @@ 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> = {
@@ -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.1",
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
+ }