Tynder
TypeScript friendly Data validator for JavaScript.
Validate data in browsers, node.js back-end servers, and various language platforms by simply writing the schema once in TypeScript with extended syntax.
Features
- Define the schema with TypeScript-like DSL.
- Validate data against the defined schema.
- End user friendly custom validation error message.
- Create subset by cherrypicking fields from original data with the defined schema.
- Apply the patch data to the original data.
- Generate type definition or schema files using CLI / API.
- TypeScript
- JSON Schema
- C# (experimental)
- Protocol Buffers 3 (experimental)
- GraphQL (experimental)
Table of contents
- Get started
- Playground
- Install
- Define schema with TypeScript-like DSL
- Load pre-compiled schema and type definitions
- Define schema with functional API
- DSL syntax
- Customize error messages
- CLI subcommands and options
- Limitations
- License
Get started
- tynder-express-react-ts-esm-quickstart
- A boilerplate for React client + Express server project using Tynder data validation library.
- Tynder Schema Converter Chrome Extension
Playground
- TypeScript (Tynder DSL) → JSON Schema | C# | GraphQL | Protobuf Converter
- Convert schema from
Tynder DSL
to JSON Schema, C#, GraphQL and Protobuf.
- Convert schema from
- TypeScript (Tynder DSL) Schema Validator
- Validate data against the schema.
Install
npm install --save tynder
NOTICE:
Use withwebpack >= 5
If you get the error:
Module not found: Error: Can't resolve '(importing/path/to/filename)' in '(path/to/node_modules/path/to/dirname)' Did you mean '(filename).js'?`
Add following setting to your
webpack.config.js
.test: /\.m?js/resolve:fullySpecified: falseOn
webpack >= 5
, the extension in the request is mandatory for it to be fully specified if the origin is a '.mjs' file or a '.js' file where the package.json contains '"type": "module"'.
NOTICE:
To use without webpack on Node.js, enabling ES Modules.
Add flags:
node --experimental-modules \--es-module-specifier-resolution=node \--experimental-json-modules \app.mjsUse
import
statement:
;;;Add package.json
{ "type": "module" }
or{ "type": "commonjs" }
to your source directories.See tynder-express-react-ts-esm-quickstart and Node.js Documentation - ECMAScript Modules.
Define schema with TypeScript-like DSL
Schema:
/// @tynder-external RegExp, Date, Map, Set /** doc comment */; ; /** doc comment */ /** doc comment */ // Custom error message id // line comment/* block comment */
Default file extension is *.tss
.
Compile using CLI commands:
# Compile schema and output as JSON files. tynder compile --indir path/to/schema/tynder --outdir path/to/schema/_compiled# Compile schema and output as JavaScript|TypeScript files. tynder compile-as-ts --indir path/to/schema/tynder --outdir path/to/schema/_compiled# Compile schema and generate TypeScript type definition files. tynder gen-ts --indir path/to/schema/tynder --outdir path/to/typescript-src# Compile schema and generate JSON Schema files. tynder gen-json-schema --indir path/to/schema/tynder --outdir path/to/schema/json-schema# Compile schema and generate JSON Schema as JavaScript|TypeScript files. tynder gen-json-schema-as-ts --indir path/to/schema/tynder --outdir path/to/schema/json-schema# Compile schema and generate C# type definition files. tynder gen-csharp --indir path/to/schema/tynder --outdir path/to/schema/csharp# Compile schema and generate Protocol Buffers 3 type definition files. tynder gen-proto3 --indir path/to/schema/tynder --outdir path/to/schema/proto3# Compile schema and generate GraphQL type definition files. tynder gen-graphql --indir path/to/schema/tynder --outdir path/to/schema/graphql
Compile using API:
; ;
Validating:
;;; ; // {value: {a: 'x', b: 3}} ; // null ; ; if validated3 === null
Cherrypicking and patching:
;;;;; ;; try catch e
Load pre-compiled schema and type definitions
From object (import)
...;; // type definitions (.d.ts) from './path/to/schema-compiled/my-schema'; // pre-compiled schema (.ts) // `MySchema` is auto generated string const enum. ; ;; if validated
From object (require JSON file)
...;; // type definitions (.d.ts) // import { createRequireFromPath } from 'module';// import { fileURLToPath } from 'url';// const require = createRequireFromPath(fileURLToPath(import.meta.url)); ; // pre-compiled schema (.json) ;; if validated
or
...;; // type definitions (.d.ts); // pre-compiled schema (.json) ; ;; if validated
From text
...;; // type definitions (.d.ts); ; // pre-compiled schema (.json) ;; if validated
Type-safe Cherrypicking and patching:
// Load pre-compiled schema and type definitions... ; ; try catch e
Type guards
; ... ; if isTypeunknownInput, getTypemySchema, 'A', ctx && unknownInput.a.length > 0 else
; ... ; try catch e
Define schema with functional API
; ; /*Equivalent to following type definition: interface P { e?: string; f?: string; g: string[3..10]; [propName: /^[a-z][0-9]$/]?: string;}type Q = { x: 10, y: 10, p: 10, } & { x: 10, y: 10, q: 10, };type R = { w: 10, z: 10, } - { w: 10, };interface S extends P, Q, R { a: 10; b?: 20; @msgId('MyType-c') c: 'aaa'; d: [10, 20, ...<string, 3..10>, 50];}type MyType = S | 10 | 20 | 30 | string | 50;*/ ;
DSL syntax
Type
;;
Interface
Named interface
Unnamed literal interface
;
Optional member
;
Additional properties
; ; ; ; ; ;
Only string
, number
, and RegExp
are allowed for the propName
type.
Type decoration
Decorate to interface member
Decorate to type component
;
@range(minValue: number | string, maxValue: number | string)
- Check value range.
- minValue <= data <= maxValue
@minValue(minValue: number | string)
- Check value range.
- minValue <= data
@maxValue(maxValue: number | string)
- Check value range.
- data <= maxValue
@greaterThan(minValue: number | string)
- Check value range.
- minValue < data
@lessThan(maxValue: number | string)
- Check value range.
- data < maxValue
@minLength(minLength: number)
- Check value range.
- minLength <= data.length
@maxLength(maxLength: number)
- Check value range.
- data.length <= maxLength
@match(pattern: RegExp)
- Check value text pattern.
- RegExp flags are allowed.
- e.g.:
/^[\u{3000}-\u{301C}]+$/u
- e.g.:
- RegExp flags are allowed.
- pattern.test(data)
- Check value text pattern.
@stereotype(stereotype: string)
- Perform custom validation.
-
WARNING: In the JSON schema output, this is stripped.
-
- Perform custom validation.
@constraint(constraintName: string, args: any)
- Perform custom constraint.
-
WARNING: In the JSON schema output, this is stripped.
@constraint('unique', fields?: string[])
- Check unique.
@constraint('unique-non-null', fields?: string[])
- Check unique (null field is always unique).
-
- Perform custom constraint.
@forceCast
- Validate after forcibly casting to the assertion's type.
-
WARNING: In the JSON schema output, this is stripped.
-
- Validate after forcibly casting to the assertion's type.
@recordType
- If the decorated member field of object is validated, the union type is determined.
- Use to receive reasonable validation error messages.
;// If data {kind: 'foo', ...} is passed,// the union type will be determined as `Foo`.- If the decorated member field of object is validated, the union type is determined.
@meta
- User defined custom properties (meta informations).
- Output to the compiled schema.
- User defined custom properties (meta informations).
@msg(messages: string | ErrorMessages)
- Set custom error message.
@msgId(messageId: string)
- Set custom error message id.
Date / Datetime stereotypes
...; ; ;; ; ;
Stereotypes
date
- date (UTC timezone)
lcdate
- date (local timezone)
datetime
- datetime (UTC timezone)
lcdatetime
- datetime (local timezone)
Formula syntax
Expression =
ISODateAndDatetime |
("=" , DateTimeFormula , {whitespace, DateTimeFormula}) ;
DateTimeFormula =
ISODateAndDatetime |
("current" | "now") |
"today"
("@" | "+" | "-") , NaturalNumber ,
("yr" | "mo" | ("days" | "day") |
"hr" | "min" | "sec" | "ms") |
"first-date-of-yr" |
"last-date-of-yr" |
"first-date-of-mo" |
"last-date-of-mo" |
"first-date-of-fy", "(", NaturalNumber1To12, ")" ;
Formula examples
- This month (date)
@range('=today first-date-of-mo', '=today last-date-of-mo')
- This month (datetime)
@minValue('=today first-date-of-mo') @lessThan('=today last-date-of-mo +1day')
- Next month (date)
@range('=today first-date-of-mo +1mo', '=today @1day +1mo last-date-of-mo')
- Next month (datetime)
@minValue('=today first-date-of-mo +1mo') @lessThan('=today @1day +1mo last-date-of-mo +1day')
- This year (date)
@range('=today first-date-of-yr', '=today last-date-of-yr')
- This year (datetime)
@minValue('=today first-date-of-yr') @lessThan('=today last-date-of-yr +1day')
- Next year (date)
@range('=today first-date-of-yr +1yr', '=today @1day +1yr last-date-of-yr')
- Next year (datetime)
@minValue('=today first-date-of-yr +1yr') @lessThan('=today @1day +1yr last-date-of-yr +1day')
- This fiscal year (date)
@range('=today first-date-of-fy(9)', '=today first-date-of-fy(9) +1yr -1day')
- Fiscal year beginning in September
- This fiscal year (datetime)
@minValue('=today first-date-of-fy(9)') @lessThan('=today first-date-of-fy(9) +1yr')
- Fiscal year beginning in September
- Next fiscal year (date)
@range('=today first-date-of-fy(9) +1yr', '=today first-date-of-fy(9) +2yr -1day')
- Fiscal year beginning in September
- Next fiscal year (datetime)
@minValue('=today first-date-of-fy(9) +1yr') @lessThan('=today first-date-of-fy(9) +2yr')
- Fiscal year beginning in September
Unique constraint
...; ;
Enum
Primitive types
/** Primitive types */; /** Null-like types */; /** Placeholder types */;
Value types
See Literals > Type literals
section.
Array type component (Repeated type component)
Simple array type
;
Complex array type
;
Simple array type with quantity assertion
; // 10 <= data.length <= 20; // 10 <= data.length; // data.length <= 20; // data.length === 10
Complex array type with quantity assertion
; // 10 <= data.length <= 20; // 10 <= data.length; // data.length <= 20; // data.length === 10
Sequence type component (Tuple type component)
Fixed length
;
Flex length
; // Zero or once; // Zero or more; // With quantity assertion
WARNING: In the JSON schema output, this translates into a simplified array assertion.
Referencing other interface members
NOTE:
- This syntax is incompatible with TypeScript.
- Generated TypeScript type definition is
userName: Foo['name'];
.- Tynder compiler does not allow
userName: Foo['name'];
.
Type operators
P & Q
- Intersection type
- Result type has all the members of P and Q.
P | Q
- Union type
- Match to P or Q type.
P - Q
- Subtraction type
- Result type has the members of P that is NOT exist in Q.
Pick<T,K>
- e.g.
Pick<Foo, 'a' | 'b' | 'c'>
- Picked type
- Result type has the members of T that is exist in K.
- e.g.
Omit<T,K>
- e.g.
Omit<Foo, 'a' | 'b' | 'c'>
- Picked type
- Result type has the members of T that is NOT exist in K.
- e.g.
Partial<T>
- All the member of result type are optioonal.
Partial<{a: string}>
is equivalent to{a?: string}
.
Export
;
Import
This statement is passed through to the generated codes.
;;;
Declared types
declare ;declare declare declare declare ;declare declare declare
Declared variables
This statement is passed through to the generated codes.
declare ;declare ;declare ; declare ;declare ;declare ;
External
This statement is removed from the generated code.
Untyped external statement
Define the external (ambient) symbols as any
type.
external P, Q, R;
or
/// @tynder-external P, Q, R
or
/* @tynder-external P, Q, R */
Typed external statement
external P: string, Q: P | string, R: ;
or
/// @tynder-external P: string[], Q: P | string, R: {a: string}[]
or
/* @tynder-external P: string[], Q: P | string, R: {a: string}[]*/
Pass-through code block
This comment body is passed through to the generated codes.
// Nominal type declare ;/* @tynder-pass-throughexport type PhoneNumberString = string & { [phoneNumberString]: never };*/external PhoneNumberString: string;
Comments
// ↓↓↓ directive line comment ↓↓↓// @tynder-external P, Q, R/// @tynder-external S, T // ↓↓↓ directive block comment ↓↓↓/* @tynder-external U, V */ /** doc comment */; /** doc comment */ /** doc comment */ // line comment# line comment /* block comment *//* block comment */
Doc comments are preserved.
Literals
Type literals
;
Value literals
; // RegExp; // number; // string; // object
Directives
/// @tynder-external P, Q, R
@tynder-external
type [, ...]- Declare external types as
any
.
- Declare external types as
/* @tynder-pass-throughexport type PhoneNumberString = string & { [phoneNumberString]: never };*/
@tynder-pass-through
body- This comment body is passed through to the generated codes.
Generics
Generics actual parameters are removed.
DSL:
/// @tynder-external Map, Set
TypeScript generated type definition:
NOTE: Generic interfaces and generic types cannot be defined.
-
e.g.
Customize error messages
Customize message of items
// Custom error message id
Default error messages
;
Change default messages
;;;; ; ; ; if validated3 === null
Precedence is "Default messages
< ctx.errorMessages
< @msg()
".
Keyword substitutions
%{expectedType}
%{type}
%{expectedValue}
%{value}
%{repeatQty}
%{minValue}
%{maxValue}
%{pattern}
%{minLength}
%{maxLength}
%{name}
%{parentType}
%{dataPath}
%{addtionalProps}
CLI subcommands and options
Usage:
tynder subcommand options...
Subcommands:
help
Show this help.
compile
Compile schema and output as JSON files.
* default input file extension is *.tss
* default output file extension is *.json
compile-as-ts
Compile schema and output as JavaScript|TypeScript files.
* default input file extension is *.tss
* default output file extension is *.ts
Generated code is:
const schema = {...};
export default schema;
gen-ts
Compile schema and generate TypeScript type definition files.
* default input file extension is *.tss
* default output file extension is *.d.ts
gen-json-schema
Compile schema and generate 'JSON Schema' files.
* default input file extension is *.tss
* default output file extension is *.json
gen-json-schema-as-ts
Compile schema and generate 'JSON Schema'
as JavaScript|TypeScript files.
* default input file extension is *.tss
* default output file extension is *.ts
Generated code is:
const schema = {...};
export default schema;
gen-csharp
Compile schema and generate 'C#' type definition files.
* default input file extension is *.tss
* default output file extension is *.cs
gen-proto3
Compile schema and generate 'Protocol Buffers 3' type definition files.
* default input file extension is *.tss
* default output file extension is *.proto
gen-graphql
Compile schema and generate 'GraphQL' type definition files.
* default input file extension is *.tss
* default output file extension is *.graphql
Options:
--indir dirname
Input directory
--outdir dirname
Output directory
--inext fileExtensionName
Input files' extension
--outext fileExtensionName
Output files' extension
Example:
tynder compile --indir path/to/schema/tynder --outdir path/to/schema/_compiled
Limitations
- Generics actual parameters are removed.
- Except
Array<T,quantity?>
,Pick<T,K>
,Omit<T,K>
andPartial<T>
.
- Except
License
ISC
Copyright (c) 2019-2020 Shellyl_N and Authors.