Inspired by typescript-is.
Transforms typescript interfaces, classes and other types into runtime validation functions. It leverages these fine libraries:
- ts-json-schema-generator - Generate JSON Schema from Typescript AST
- ajv - Validates an object against JSON Schema
- ajv-keywords - Additional validation capabilities via custom JSON Schema keywords
-
Install typesmith
npm i --save typesmith
-
Install ttypescript
npm i --save-dev ttypescript
-
Edit your tsconfig.json to use the plugin
{ "compilerOptions": { "plugins": [{ "transform": "typesmith/transformer" }] } }
-
Replace
tsc ...
commands withttsc ...
commands.
import { assertTypeFn, DateString } from "typesmith";
interface Person {
/** @minLength 1 */
firstName: string;
lastName: string;
dateOfBirth: DateString;
}
const assertPerson = assertTypeFn<Person>();
const jillSmith = { firstName: "Jill", lastName: "Smith", dateOfBirth: "1990-12-31" };
const janeDoe = { firstName: "Jane", lastName: "Doe", dateOfBirth: "1990-12-31" };
const invalidPerson = {};
// Validation Successful Result
assertPerson(jillSmith).isSuccess === true;
assertPerson(jillSmith).unwrap() === jillSmith;
assertPerson(jillSmith).getErrors() === null;
assertPerson(jillSmith).getOrElse(janeDoe) === jillSmith;
assertPerson(jillSmith).getOrElseL(errors => janeDoe) === jillSmith;
// Validation Failure Result
assertPerson(invalidPerson).isSuccess === false;
assertPerson(invalidPerson).unwrap(); // Throws Error
assertPerson(invalidPerson).getErrors().length > 0;
assertPerson(invalidPerson).getOrElse(janeDoe) === janeDoe;
assertPerson(invalidPerson).getOrElseL(errors => janeDoe) === janeDoe;
- allErrors: (default: true) returns all errors instead of returning only the first one
- removeAdditional: (default: false) remove additional properties
- useDefaults: (default: false) replace missing or undefined properties and items with the values from corresponding default keywords
- coerceTypes: (default: false) change data type of data to match type keyword
- lazyCompile: (default: true) wait to compile validation function until first use
Options are specifiable at the global and type level, EG:
import { assertTypeFn, settings, Validatable } from "typesmith";
// Globally:
settings.updateGlobalValidationOptions({ removeAdditional: true });
// Type-Level
assertTypeFn<{ name: string }>({ coerceTypes: true });
@Validatable({ coerceTypes: true }) class Foo {}
- Mixin classes:
// Crashes @Validatable() export class PersonSearchRequest extends SearchRequestOf(PersonFilters) {}
Anonymous recursive generic types:// Okay type NumberBTree = BTree<number>; assertTypeFn<NumberBTree>(); // (Works as of v0.9.8) assertTypeFn<BTree<number>>();
With AJV's coerceTypes
enabled, coerced primitives will validate but will return the original value. EG:
// Coercion of boxed value works
assertTypeFn<number>({value: "12"}).unwrap().value === 12;
// Coercion of primitive doesn't
assertTypeFn<number>("12").unwrap() === "12"
Defaults must be provided to classes with generic type paramters. EG:
// Okay
@Validate() class Foo<A = any> {}
// Crashes
@Validate() class Foo<A> {}
TODO
note: try using { DateInstance, DateTimeFlex, DateTimeString } from "typesmith"
- feat: allow strings to be coerced to other primitives without using AJV's built-in coercion since it is too lenient. EG,
false
shouldn't be coerced to0
- feat: use ajv-pack for precompiled validation functions. ajv-pack has restrictions so this would need to be optional.