Nattily Polished Marbles

    dilswer
    TypeScript icon, indicating that this package has built-in type declarations

    1.2.0 • Public • Published

    Dilswer

    Small and lightweight data validation library with TypeScript integration.

    Keep your type definitions in one place, and have but one source of truth for both the runtime validation types and the TypeScript type definitions.

    Table Of Contents

    1. Quick Start
      1. Create type definitions
      2. Create a TypeScript type from a Dilswer definition
      3. Create a validation function
      4. Create a function with a validated input
    2. Motivation
    3. Main exported functions
      1. createValidator()
      2. createTypeGuardedFunction()
      3. createValidatedFunction()
      4. ensureDataType()
      5. DataType
    4. Data Types
      1. String
      2. Number
      3. Int
      4. StringNumeral
      5. StringInt
      6. Boolean
      7. Symbol
      8. Null
      9. Undefined
      10. Function
      11. Unknown
      12. OneOf
      13. ArrayOf
      14. RecordOf
      15. SetOf
      16. Literal
      17. Enum
      18. EnumMember
    5. Utility Functions
      1. And
      2. Omit
      3. Pick
      4. Partial
      5. Required
      6. Exclude

    Quick Start

    Create type definitions

    // person-type.ts
    import { DataType } from "dilswer";
    
    export const PersonDataType = DataType.RecordOf({
      id: { type: DataType.String, required: true },
      name: { type: DataType.String },
      age: { type: DataType.Number },
      friends: { type: DataType.ArrayOf(DataType.String), required: false },
    });

    NOTE: the required attribute in a RecordOf fields is set to true by default.

    Create a TypeScript type from a Dilswer definition

    import { GetDataType } from "dilswer";
    import { PersonDataType } from "./person-type.ts";
    
    type Person = GetDataType<typeof PersonDataType>;
    
    // Result:
    // type Person = {
    //     friends?: string[];
    //     id: string;
    //     name: string;
    //     age: number;
    // }

    Create a validation function

    import { createValidator } from "dilswer";
    import { PersonDataType } from "./person-type.ts";
    
    const isPerson = createValidator(PersonDataType);
    
    // Result:
    // const isPerson: (data: unknown) => data is {
    //     friends?: string[];
    //     id: string;
    //     name: string;
    //     age: number;
    // }
    
    const person = await axios
      .get("https://my-api.io/get-person/1")
      .then((r) => r.data);
    
    if (isPerson(person)) {
      console.log("Name: ", person.name);
      // do something with person
    } else {
      console.error("`person` variable is not of expected type.");
      // handle the validation failure
    }

    Create a function with a validated input

    import { createValidator } from "dilswer";
    import { PersonDataType } from "./person-type.ts";
    
    const processPerson = createValidatedFunction(
      PersonDataType,
      (person) => {
        console.log("Processing person: ", person.name);
    
        // do something with person
    
        return "Success!";
      },
      (error) => {
        console.error("Function input is not of expected type.");
        console.error("Type expected:", error.expectedValueType);
        console.error("Received:", error.receivedValue);
        console.error("Invalid property location: ", error.fieldPath);
    
        // handle the validation failure
    
        return "Failure";
      }
    );
    
    // Result:
    // const processPerson: (data: unknown) => "Success!" | "Failure"
    
    const person = await axios
      .get("https://my-api.io/get-person/1")
      .then((r) => r.data);
    
    const result = processPerson(person); // => "Success!" or "Failure"

    Motivation

    Whenever you use some kind of a type validation library in a TypeScript project you will have to define those types twice: once as a TS type or interface and once in a format that's understood by the data validation library which will check the data types on runtime. This is a inconvenience and can sometimes lead to bugs (when you change one of the definitions but forget to do the same with the other).

    This is the problem that Dilswer is trying to solve. To have one source of truth for your type definitions. One that can be understood by both the TypeScript engine and the data validation library.

    Dilswer gives you a tool that you can use to define any kind of type, and then validate data at runtime with against it or infer a TypeScript type directly from it.

    Main exported functions

    dilswer.createValidator()

    const createValidator: <DT extends AllDataTypes>(
      dataType: DT
    ) => (data: unknown) => data is ParseDataType<DT>;

    Higher order function that generates a validator which will check the provided data against the dataType type structure definition and returns a boolean indicating if the check was successful.

    dilswer.createTypeGuardedFunction()

    const createTypeGuardedFunction: <DT extends AllDataTypes, R, ER = void>(
      dataType: DT,
      onValidationSuccess: (data: ReWrap<ParseDataType<DT>>) => R,
      onValidationError?: (error: ValidationError, data: unknown) => ER
    ) => (data: unknown) => R | ER;

    Higher order function that generates a new function which will check the provided data against the dataType type structure, and if the check is successful then the first callback onValidationSuccess is invoked with data as it's argument, otherwise the second callback onValidationError is invoked with the type validation error as it's argument (unless the callback is not specified).

    dilswer.createValidatedFunction()

    Alias for the createTypeGuardedFunction().

    dilswer.ensureDataType()

    const ensureDataType: <DT extends AllDataTypes>(
      dataType: DT,
      data: unknown
    ) => void;

    Checks the provided data against the dataType type definition and throws an ValidationError if the data does not conform to the dataType.

    dilswer.DataType

    Object containing all the dilswer runtime type definitions (like Number, String, ArrayOf(...), etc.)

    Data Types

    DataType.String

    will match any string values and translate to the standard string type in TypeScript.

    DataType.Number

    will match any number values and translate to the standard number type in TypeScript.

    DataType.Int

    will match any integer values and translate to the standard number type in TypeScript. TypeScript does not have any way of distinguishing float and integers therefore both are using the same type.

    DataType.StringNumeral

    will match any string containing only numeric values and translate to a `${number}` type in TypeScript. A value successfully validated with StringNumeral is safe to convert into a number and will never produce a NaN value.

    DataType.StringInt

    will match any string containing only numbers and translate to a `${number}` type in TypeScript. Strings with floating point numbers are not matched by this type. A value successfully validated with StringInt is safe to convert into a number and will never produce a NaN value.

    DataType.Boolean

    will match any true and false values and translate to the standard boolean type in TypeScript.

    DataType.Symbol

    will match any symbolic values and translate to the symbol type in TypeScript.

    DataType.Null

    will match only null value and translate to the standard null type in TypeScript.

    DataType.Undefined

    will match only undefined value and translate to the standard undefined type in TypeScript.

    DataType.Function

    will match any function and translate to the Function type in TypeScript.

    DataType.Unknown

    will match any value and translate to the unknown type in TypeScript.

    DataType.OneOf(...DataType's)

    will match any value matching one of the DataType's provided in the arguments and translate to an TypeScript union type.

    Example

    const foo = DataType.OneOf(DataType.String, DataType.Number);
    
    type T = GetDataType<typeof foo>; // type T = (string | number)

    DataType.ArrayOf(...DataType's)

    will match any array which contains only values matching any of the DataType's provided in the arguments and translate to the Array<...> type in TypeScript.

    Example

    const foo = DataType.ArrayOf(DataType.String, DataType.Number);
    
    type T = GetDataType<typeof foo>; // type T = (string | number)[]

    DataType.RecordOf(Record<string, FieldDescriptor>)

    will match any object which structure matches the key-value pairs of object properties and FieldDescriptor's passed to the argument.

    Example

    const foo = DataType.RecordOf({
      foo: DataType.Boolean,
      bar: { type: DataType.String },
      baz: { type: DataType.Number, required: false },
    });
    
    type T = GetDataType<typeof foo>; // type T = {foo: boolean, bar: string, baz?: number | undefined}

    DataType.SetOf(...DataType's)

    will match any Set object which contains only values matching any of the DataType's provided in the arguments and translate to the Set<...> type in TypeScript.

    Example

    const foo = DataType.SetOf(DataType.String, DataType.Number);
    
    type T = GetDataType<typeof foo>; // type T = Set<string | number>

    DataType.Literal(string | number | boolean)

    will match any value that exactly matches the passed argument and translate to the literal type of that value in TypeScript.

    Example's

    const foo = DataType.Literal("some-string-literal");
    
    type T0 = GetDataType<typeof foo>; // type T0 = "some-string-literal"
    const bar = DataType.Literal(123);
    
    type T1 = GetDataType<typeof bar>; // type T1 = 123
    const baz = DataType.Literal(true);
    
    type T2 = GetDataType<typeof baz>; // type T2 = true

    DataType.Enum(enum)

    will match any value that belongs to an TypeScript enum and translate to that enum type.

    enum MyEnum {
      A = "A",
      B = "B",
    }
    
    const foo = DataType.Enum(MyEnum);
    
    type T = GetDataType<typeof foo>; // type T = MyEnum
    
    const validate = createValidator(foo);
    
    validate(MyEnum.A); // => true
    validate(MyEnum.B); // => true

    DataType.EnumMember(enum member)

    will match any value that equals to the specified TypeScript enum member and translate to that enum member type.

    enum MyEnum {
      A = "VALUE_A",
      B = "VALUE_B",
    }
    
    const foo = DataType.EnumMember(MyEnum.A);
    
    type T = GetDataType<typeof foo>; // type T = MyEnum.A
    
    const validate = createValidator(foo);
    
    validate("VALUE_A"); // => true
    validate(MyEnum.A); // => true
    validate(MyEnum.B); // => false

    Utility Functions

    And()

    And() utility function can combine two Record Type Definitions into one. If any of the properties between the two combined Type Defs have the same key-name, the definition of the second one takes priority.

    const typeDefOne = DataType.RecordOf({
      foo: DataType.Number,
      bar: DataType.Number,
    });
    
    const typeDefTwo = DataType.RecordOf({
      bar: DataType.ArrayOf(DataType.String),
      baz: DataType.Boolean,
    });
    
    const typeDefSum = And(typeDefOne, typeDefTwo);
    // typeDefSum = {
    //    foo: number;
    //    bar: string[];
    //    baz: boolean;
    // }

    Omit()

    Omit() utility function removes specified keys from a Record Type Definition.

    const typeDefOne = DataType.RecordOf({
      foo: DataType.Number,
      bar: DataType.Number,
      baz: DataType.Number,
      qux: DataType.Number,
    });
    
    const typeDefOmitted = Omit(typeDefOne, "bar", "qux");
    // typeDefOmitted = {
    //    foo: number;
    //    baz: number;
    // }

    Pick()

    Pick() utility function removes all not specified keys from a Record Type Definition.

    const typeDefOne = DataType.RecordOf({
      foo: DataType.Number,
      bar: DataType.Number,
      baz: DataType.Number,
      qux: DataType.Number,
    });
    
    const typeDefPick = Pick(typeDefOne, "bar", "qux");
    // typeDefPick = {
    //    bar: number;
    //    qux: number;
    // }

    Partial()

    Partial() utility type makes all the Record's Type Definition keys optional.

    const typeDefOne = DataType.RecordOf({
      foo: DataType.Number,
      bar: DataType.String,
      baz: DataType.ArrayOf(DataType.Number),
    });
    
    const typeDefPartial = Partial(typeDefOne);
    // typeDefPartial = {
    //    foo?: number | undefined;
    //    bar?: string | undefined;
    //    baz?: number[] | undefined;
    // }

    Required()

    Required() utility type makes all the Record's Type Definition keys to be required (vs optional).

    const typeDefOne = DataType.RecordOf({
      foo: { type: DataType.Number, required: false },
      bar: { type: DataType.String, required: false },
      baz: { type: DataType.ArrayOf(DataType.Number), required: false },
    });
    
    const typeDefRequired = Required(typeDefOne);
    // typeDefRequired = {
    //    foo: number;
    //    bar: string;
    //    baz: number[];
    // }

    Exclude()

    Exclude() utility function removes Type Definitions from an Type Def union.

    const typeDefOne = DataType.OneOf(
      DataType.String,
      DataType.Number,
      DataType.Boolean
    );
    
    const typeDefExcluded = Exclude(typeDefOne, DataType.Number);
    // typeDefExcluded = string | boolean;

    Install

    npm i dilswer

    DownloadsWeekly Downloads

    31

    Version

    1.2.0

    License

    MIT

    Unpacked Size

    69.5 kB

    Total Files

    69

    Last publish

    Collaborators

    • ncpa0cpl