{"id":13683727,"url":"https://github.com/shellyln/tynder","last_synced_at":"2025-09-05T01:47:34.624Z","repository":{"id":38007480,"uuid":"229540796","full_name":"shellyln/tynder","owner":"shellyln","description":"TypeScript friendly Data validator for JavaScript.","archived":false,"fork":false,"pushed_at":"2023-03-14T17:44:24.000Z","size":2304,"stargazers_count":109,"open_issues_count":9,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-05T01:47:28.043Z","etag":null,"topics":["data-validator","schema","schema-validator","type-checker","typescript","validation-library","validator"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shellyln.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-12-22T08:45:38.000Z","updated_at":"2023-09-07T07:09:35.000Z","dependencies_parsed_at":"2024-01-06T07:58:54.613Z","dependency_job_id":"44cf7d98-dd8e-414e-86ab-5ce03bad7f37","html_url":"https://github.com/shellyln/tynder","commit_stats":{"total_commits":440,"total_committers":2,"mean_commits":220.0,"dds":0.002272727272727315,"last_synced_commit":"d3961f737343dbfdba8c13515b9bf82ba67f62f1"},"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"purl":"pkg:github/shellyln/tynder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shellyln%2Ftynder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shellyln%2Ftynder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shellyln%2Ftynder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shellyln%2Ftynder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shellyln","download_url":"https://codeload.github.com/shellyln/tynder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shellyln%2Ftynder/sbom","scorecard":{"id":817601,"data":{"date":"2025-08-11","repo":{"name":"github.com/shellyln/tynder","commit":"d3961f737343dbfdba8c13515b9bf82ba67f62f1"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/24 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":2,"reason":"dependency not pinned by hash detected -- score normalized to 2","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/shellyln/tynder/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/shellyln/tynder/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/shellyln/tynder/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:44: update your workflow using https://app.stepsecurity.io/secureworkflow/shellyln/tynder/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:54: update your workflow using https://app.stepsecurity.io/secureworkflow/shellyln/tynder/test.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/test.yml:26","Warn: npmCommand not pinned by hash: .github/workflows/test.yml:50","Info:   0 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   2 out of   4 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.6.6 not signed: https://api.github.com/repos/shellyln/tynder/releases/31197009","Warn: release artifact v0.6.5 not signed: https://api.github.com/repos/shellyln/tynder/releases/30772544","Warn: release artifact v0.6.4 not signed: https://api.github.com/repos/shellyln/tynder/releases/28971389","Warn: release artifact v0.6.3 not signed: https://api.github.com/repos/shellyln/tynder/releases/26833581","Warn: release artifact v0.6.6 does not have provenance: https://api.github.com/repos/shellyln/tynder/releases/31197009","Warn: release artifact v0.6.5 does not have provenance: https://api.github.com/repos/shellyln/tynder/releases/30772544","Warn: release artifact v0.6.4 does not have provenance: https://api.github.com/repos/shellyln/tynder/releases/28971389","Warn: release artifact v0.6.3 does not have provenance: https://api.github.com/repos/shellyln/tynder/releases/26833581"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"28 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-3rfm-jhwj-7488","Warn: Project is vulnerable to: GHSA-hhq3-ff78-jv3g","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-g4rg-993r-mgx7","Warn: Project is vulnerable to: GHSA-4rq4-32rv-6wp6","Warn: Project is vulnerable to: GHSA-64g7-mvw6-v9qj","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-hc6q-2mpp-qw7j","Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T14:35:20.511Z","repository_id":38007480,"created_at":"2025-08-23T14:35:20.511Z","updated_at":"2025-08-23T14:35:20.511Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273699718,"owners_count":25152285,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-validator","schema","schema-validator","type-checker","typescript","validation-library","validator"],"created_at":"2024-08-02T13:02:27.039Z","updated_at":"2025-09-05T01:47:29.605Z","avatar_url":"https://github.com/shellyln.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Tynder\n\n![Tynder](https://raw.githubusercontent.com/shellyln/tynder/master/docs/tynder.svg?sanitize=true)\n\nTypeScript friendly Data validator for JavaScript.\n\nValidate data in browsers, node.js back-end servers, and various language platforms by simply writing the schema once in TypeScript with extended syntax.\n\n\n[![npm](https://img.shields.io/npm/v/tynder.svg)](https://www.npmjs.com/package/tynder)\n[![GitHub release](https://img.shields.io/github/release/shellyln/tynder.svg)](https://github.com/shellyln/tynder/releases)\n[![.github/workflows/test.yml](https://github.com/shellyln/tynder/workflows/.github/workflows/test.yml/badge.svg)](https://github.com/shellyln/tynder/actions)\n[![GitHub forks](https://img.shields.io/github/forks/shellyln/tynder.svg?style=social\u0026label=Fork)](https://github.com/shellyln/tynder/fork)\n[![GitHub stars](https://img.shields.io/github/stars/shellyln/tynder.svg?style=social\u0026label=Star)](https://github.com/shellyln/tynder)\n\n\n\n## Features\n* Define the **schema with TypeScript-like DSL**.\n* **Validate** data against the defined schema.\n* End user friendly **custom validation error message**.\n* Create subset by **cherrypicking** fields from original data with the defined schema.\n* Apply the **patch** data to the original data.\n* Generate type definition or schema files using CLI / API.\n    * TypeScript\n    * JSON Schema\n    * C# (experimental)\n    * Protocol Buffers 3 (experimental)\n    * GraphQL (experimental)\n\n![write-once-use-anywhere](https://shellyln.github.io/tynder/assets/image/write-once-v3.svg)\n\n\n\n------------\n## Table of contents\n\n* [Get started](#get-started)\n* [Playground](#playground)\n* [Install](#install)\n* [Define schema with TypeScript-like DSL](#define-schema-with-typescript-like-dsl)\n* [Load pre-compiled schema and type definitions](#load-pre-compiled-schema-and-type-definitions)\n* [Define schema with functional API](#define-schema-with-functional-api)\n* [DSL syntax](#dsl-syntax)\n* [Customize error messages](#customize-error-messages)\n* [CLI subcommands and options](#cli-subcommands-and-options)\n* [Limitations](#limitations)\n* [License](#license)\n------------\n\n\n## Get started\n\n* [tynder-express-react-ts-esm-quickstart](https://github.com/shellyln/tynder-express-react-ts-esm-quickstart)\n    * A boilerplate for React client + Express server project using Tynder data validation library.\n* [Tynder Schema Converter Chrome Extension](https://github.com/shellyln/tynder-chrome-extension)\n\n## Playground\n* [TypeScript (Tynder DSL) → JSON Schema | C# | GraphQL | Protobuf Converter](https://shellyln.github.io/tynder/playground.html)\n    * Convert schema from `Tynder DSL` to JSON Schema, C#, GraphQL and Protobuf.\n* [TypeScript (Tynder DSL) Schema Validator](https://shellyln.github.io/tynder/playground2.html)\n    * Validate data against the schema.\n\n## Install\n\n```sh\nnpm install --save tynder\n```\n\n\n\u003e NOTICE:  \n\u003e Use with `webpack \u003e= 5`\n\u003e\n\u003e If you get the error:\n\u003e\n\u003e ```\n\u003e Module not found: Error: Can't resolve '(importing/path/to/filename)'\n\u003e in '(path/to/node_modules/path/to/dirname)'\n\u003e Did you mean '(filename).js'?`\n\u003e ```\n\u003e\n\u003e Add following setting to your `webpack.config.js`.\n\u003e\n\u003e ```js\n\u003e {\n\u003e     test: /\\.m?js/,\n\u003e     resolve: {\n\u003e         fullySpecified: false,\n\u003e     },\n\u003e },\n\u003e ```\n\u003e\n\u003e On `webpack \u003e= 5`, the extension in the request is mandatory for it to be fully specified\n\u003e if the origin is a '*.mjs' file or a '*.js' file where the package.json contains '\"type\": \"module\"'.\n\n\n\n\u003e NOTICE:  \n\u003e To use without webpack on Node.js, enabling ES Modules.\n\u003e * Add flags:\n\u003e     * ```bash\n\u003e       node --experimental-modules \\\n\u003e            --es-module-specifier-resolution=node \\\n\u003e            --experimental-json-modules \\\n\u003e            app.mjs\n\u003e       ```\n\u003e\n\u003e * Use `import` statement:\n\u003e     * ```ts\n\u003e       import { ValidationContext }       from 'tynder/modules/types';\n\u003e       import { deserializeFromObject }   from 'tynder/modules/serializer';\n\u003e       import { validate,\n\u003e                getType }                 from 'tynder/modules/validator';\n\u003e       ```\n\u003e * Add package.json `{ \"type\": \"module\" }` or `{ \"type\": \"commonjs\" }` to your source directories.\n\u003e\n\u003e See [tynder-express-react-ts-esm-quickstart](https://github.com/shellyln/tynder-express-react-ts-esm-quickstart) and\n\u003e [Node.js Documentation - ECMAScript Modules](https://nodejs.org/api/esm.html).\n\n\n\n## Define schema with TypeScript-like DSL\n\n### Schema:\n```ts\n/// @tynder-external RegExp, Date, Map, Set\n\n/** doc comment */\nexport type Foo = string | number;\n\ntype Boo = @range(-1, 1) number;\n\n/** doc comment */\ninterface Bar {\n    /** doc comment */\n    a?: string;                                                   // Optional field\n    /** doc comment */\n    b: Foo[] | null;                                              // Union type\n    c: string[3..5];                                              // Repeated type (with quantity)\n    d: (number | string)[..10];                                   // Complex repeated type (with quantity)\n    e: Array\u003cnumber | string, 4..\u003e;                               // Complex repeated type (with quantity)\n    f: Array\u003cArray\u003cFoo | string\u003e\u003e;                                // Complex repeated type (nested)\n    g: [string, number],                                          // Sequence type\n    h: ['zzz', ...\u003cstring | 999, 3..5\u003e, number],                  // Sequence type (with quantity)\n}\n\ninterface Baz {\n    i: {x: number, y: number, z: 'zzz'} | number;                 // Union type\n    j: {x: number} \u0026 ({y: number} \u0026 {z: number});                 // Intersection type\n    k: ({x: number, y: number, z: 'zzz'} - {z: 'zzz'}) | number;  // Subtraction type\n}\n\n/** doc comment */\n@msgId('M1111')                                                   // Custom error message id\nexport interface FooBar extends Bar, Baz {\n    /** doc comment */\n    @range(-10, 10)\n    l: number;                                                    // Ranged value (number)\n    @minValue(-10) @maxValue(10)\n    m: number;                                                    // Ranged value\n    n: @range(-10, 10) number[];                                  // Array of ranged value\n    @greaterThan(-10) @lessThan(10)\n    o: number;                                                    // Ranged value\n    p: integer;                                                   // Integer value\n    @range('AAA', 'FFF')\n    q: string;                                                    // Ranged value (string)\n    @match(/^.+$/)\n    r: string;                                                    // Pattern matched value\n    s: Foo;                                                       // Refer a defined type\n    @msgId('M1234')\n    t: number;                                                    // Custom error message id\n    @msg({\n        required: '\"%{name}\" of \"%{parentType}\" is required.',\n        typeUnmatched: '\"%{name}\" of \"%{parentType}\" should be \"%{expectedType}\".',\n    })\n    u: number;                                                    // Custom error message\n    @msg('\"%{name}\" of \"%{parentType}\" is not valid.')\n    v: number;                                                    // Custom error message\n}\n\n// line comment\n/* block comment */\n```\nDefault file extension is `*.tss`.\n\n\n### Compile using CLI commands:\n```sh\n# Compile schema and output as JSON files.\ntynder compile               --indir path/to/schema/tynder --outdir path/to/schema/_compiled\n# Compile schema and output as JavaScript|TypeScript files.\ntynder compile-as-ts         --indir path/to/schema/tynder --outdir path/to/schema/_compiled\n# Compile schema and generate TypeScript type definition files.\ntynder gen-ts                --indir path/to/schema/tynder --outdir path/to/typescript-src\n# Compile schema and generate JSON Schema files.\ntynder gen-json-schema       --indir path/to/schema/tynder --outdir path/to/schema/json-schema\n# Compile schema and generate JSON Schema as JavaScript|TypeScript files.\ntynder gen-json-schema-as-ts --indir path/to/schema/tynder --outdir path/to/schema/json-schema\n# Compile schema and generate C# type definition files.\ntynder gen-csharp            --indir path/to/schema/tynder --outdir path/to/schema/csharp\n# Compile schema and generate Protocol Buffers 3 type definition files.\ntynder gen-proto3            --indir path/to/schema/tynder --outdir path/to/schema/proto3\n# Compile schema and generate GraphQL type definition files.\ntynder gen-graphql           --indir path/to/schema/tynder --outdir path/to/schema/graphql\n```\n\n\n### Compile using API:\n```ts\nimport { compile } from 'tynder/modules/compiler';\n\nexport default const mySchema = compile(`\n    type Foo = string;\n    interface A {\n        @maxLength(4)\n        a: Foo;\n        z?: boolean;\n    }\n`);\n```\n\n\n### Validating:\n```ts\nimport { validate,\n         getType }           from 'tynder/modules/validator';\nimport { ValidationContext } from 'tynder/modules/types';\nimport default as mySchema   from './myschema';\n\n\nconst validated1 = validate({\n    a: 'x',\n    b: 3,\n}, getType(mySchema, 'A')); // {value: {a: 'x', b: 3}}\n\n\nconst validated2 = validate({\n    aa: 'x',\n    b: 3,\n}, getType(mySchema, 'A')); // null\n\n\nconst ctx3: Partial\u003cValidationContext\u003e =\n{                            // To receive the error messages, define the context as a variable.\n    checkAll: true,          // (optional) Set to true to continue validation after the first error.\n    noAdditionalProps: true, // (optional) Do not allow implicit additional properties.\n    schema: mySchema,        // (optional) Pass \"schema\" to check for recursive types.\n};\n\nconst validated3 = validate({\n    aa: 'x',\n    b: 3,\n}, getType(mySchema, 'A'), ctx3);\n\nif (validated3 === null) {\n    console.log(JSON.stringify(\n        ctx3.errors, // error messages\n        null, 2));\n}\n```\n\n\n### Cherrypicking and patching:\n```ts\nimport { getType }           from 'tynder/modules/validator';\nimport { pick,\n         patch }             from 'tynder/modules/picker';\nimport { ValidationContext } from 'tynder/modules/types';\nimport * as op               from 'tynder/modules/operators';\nimport default as mySchema   from './myschema';\n\n\nconst original = {\n    a: 'x',\n    b: 3,\n};\nconst needleType = op.picked(getType(mySchema, 'A'), 'a');\n\n\ntry {\n    const needle1 = pick(original, needleType); // {a: 'x'}\n    const unknownInput1: unknown = { // Edit the needle data\n        ...needle1,\n        a: 'y',\n        q: 1234,\n    };\n    const changed1 = patch(original, unknownInput1, needleType); // {a: 'y', b: 3}\n} catch (e) {\n    console.log(e.message);\n    console.log(e.ctx?.errors);\n}\n\n\ntry {\n    const needle2 = pick(original, needleType); // {a: 'x'}\n    const unknownInput2: unknown = { // Edit the needle data\n        ...needle2,\n        a: 'yyyyy',\n        q: 1234,\n    };\n    const changed1 = patch(original, unknownInput2, needleType); // Throws an error\n} catch (e) {\n    console.log(e.message);\n    console.log(e.ctx?.errors);\n}\n\n\ntry {\n    const ctx3: Partial\u003cValidationContext\u003e =\n    {                     // To receive the error messages, define the context as a variable.\n        checkAll: true,   // (optional) Set to true to continue validation after the first error.\n        schema: mySchema, // (optional) Pass \"schema\" to check for recursive types.\n    };\n\n    const needle3 = pick({\n        aa: 'x',\n        b: 3,\n    }, needleType, ctx3); // Throws an error\n} catch (e) {\n    console.log(e.message);\n    console.log(e.ctx?.errors);\n}\n```\n\n\n## Load pre-compiled schema and type definitions\n\n### From object (import)\n```ts\n...\nimport { deserializeFromObject } from 'tynder/modules/lib/serializer';\nimport { Foo, A }                from './path/to/schema-types/my-schema';    // type definitions (.d.ts)\nimport mySchema_,\n       { Schema as MySchema }    from './path/to/schema-compiled/my-schema'; // pre-compiled schema (.ts)\n                   // `MySchema` is auto generated string const enum.\n\nconst mySchema = deserializeFromObject(mySchema_);\n\nconst unknownInput: unknown = {a: 'x'};\nconst validated = validate\u003cA\u003e(unknownInput, getType(mySchema, MySchema.A));\n\nif (validated) {\n    const validatedInput = validated.value; // validatedInput is type-safe\n    ...\n}\n```\n\n\n### From object (require JSON file)\n```ts\n...\nimport { deserializeFromObject } from 'tynder/modules/lib/serializer';\nimport { Foo, A }                from './path/to/schema-types/my-schema'; // type definitions (.d.ts)\n\n// import { createRequireFromPath } from 'module';\n// import { fileURLToPath }         from 'url';\n// const require = createRequireFromPath(fileURLToPath(import.meta.url));\n\nconst mySchema = deserializeFromObject(\n    require('./path/to/schema-compiled/my-schema.json')); // pre-compiled schema (.json)\n\nconst unknownInput: unknown = {a: 'x'};\nconst validated = validate\u003cA\u003e(unknownInput, getType(mySchema, 'A'));\n\nif (validated) {\n    const validatedInput = validated.value; // validatedInput is type-safe\n    ...\n}\n```\nor\n```ts\n...\nimport { deserializeFromObject } from 'tynder/modules/lib/serializer';\nimport { Foo, A }                from './path/to/schema-types/my-schema';         // type definitions (.d.ts)\nimport mySchemaJson              from './path/to/schema-compiled/my-schema.json'; // pre-compiled schema (.json)\n\nconst mySchema = deserializeFromObject(mySchemaJson);\n\nconst unknownInput: unknown = {a: 'x'};\nconst validated = validate\u003cA\u003e(unknownInput, getType(mySchema, 'A'));\n\nif (validated) {\n    const validatedInput = validated.value; // validatedInput is type-safe\n    ...\n}\n```\n\n\n### From text\n```ts\n...\nimport { deserialize } from 'tynder/modules/lib/serializer';\nimport { Foo, A }      from './path/to/schema-types/my-schema'; // type definitions (.d.ts)\nimport * as fs         from 'fs';\n\nconst mySchema = deserialize(\n    fs.readFileSync('./path/to/compiled/my-schema.json', 'utf8')); // pre-compiled schema (.json)\n\nconst unknownInput: unknown = {a: 'x'};\nconst validated = validate\u003cA\u003e(unknownInput, getType(mySchema, 'A'));\n\nif (validated) {\n    const validatedInput = validated.value; // validatedInput is type-safe\n    ...\n}\n```\n\n\n### Type-safe Cherrypicking and patching:\n\n```ts\n// Load pre-compiled schema and type definitions\n...\n\ninterface Store {\n    baz: A;\n}\nconst store: Store = {\n    baz: {\n        a: 'x',\n        z: false,\n    }\n};\n\nconst needleType = op.picked(getType(mySchema, 'A'), 'a');\n\ntry {\n    const needle = pick(store.baz, needleType); // {a: 'x'}\n                                                // `needle` is RecursivePartial\u003cA\u003e\n    const unknownInput: unknown = {             // Edit the needle data\n        ...needle,\n        a: 'y',\n        q: 1234,\n    };\n    store.baz = patch(store.baz, unknownInput, needleType); // {a: 'y', z: false}\n} catch (e) {\n    console.log(e.message);\n    console.log(e.ctx?.errors);\n}\n```\n\n\n### Type guards\n\n```ts\nimport { isType,\n         getType } from 'tynder/modules/validator';\n\n...\n\nconst unknownInput: unknown = {a: 'x'};\n\nif (isType\u003cA\u003e(unknownInput, getType(mySchema, 'A'), ctx) \u0026\u0026 unknownInput.a.length \u003e 0) {\n    console.log(`ok: ${unknownInput.a.length}`);\n} else {\n    console.log('ng');\n}\n```\n\n```ts\nimport { assertType,\n         getType } from 'tynder/modules/validator';\n\n...\n\nconst unknownInput: unknown = {a: 'x'};\n\ntry {\n    assertType\u003cA\u003e(unknownInput, getType(mySchema, 'A'), ctx);\n    console.log(`ok: ${unknownInput.a.length}`);\n} catch (e) {\n    console.log('ng');\n}\n```\n\n\n## Define schema with functional API\n\n```ts\nimport { picked,\n         omit,\n         partial,\n         intersect,\n         oneOf,\n         subtract,\n         primitive,\n         regexpPatternStringType,\n         primitiveValue,\n         optional,\n         repeated,\n         sequenceOf,\n         spread,\n         enumType,\n         objectType,\n         derived,\n         symlinkType,\n         withName,\n         withTypeName,\n         withDocComment,\n         withRange,\n         withMinValue,\n         withMaxValue,\n         withGreaterThan,\n         withLessThan,\n         withMinLength,\n         withMaxLength,\n         withMatch,\n         withStereotype,\n         withStereotype,\n         withForceCast,\n         withRecordType,\n         withMeta,\n         withMsg   as $$,\n         withMsgId as $ } from 'tynder/modules/operators';\n\nconst myType =\n    oneOf(\n        derived(\n            objectType(\n                ['a', 10],\n                ['b', optional(20)],\n                ['c', $('MyType-c')(\n                        optional('aaa'))],\n                ['d', sequenceOf(\n                        10, 20,\n                        spread(primitive('string'), {min: 3, max: 10}),\n                        50)], ),\n            objectType(\n                ['e', optional(primitive('string'))],\n                ['f', primitive('string?')],\n                ['g', repeated('string', {min: 3, max: 10})],\n                [[/^[a-z][0-9]$/], optional(primitive('string'))], ),\n            intersect(\n                objectType(\n                    ['x', 10], ['y', 10], ['p', 10], ),\n                objectType(\n                    ['x', 10], ['y', 10], ['q', 10], )),\n            subtract(\n                objectType(\n                    ['w', 10], ['z', 10], ),\n                objectType(\n                    ['w', 10], ))),\n        10, 20, 30,\n        primitive('string'),\n        primitiveValue(50), );\n\n/*\nEquivalent to following type definition:\n\ninterface P {\n    e?: string;\n    f?: string;\n    g: string[3..10];\n    [propName: /^[a-z][0-9]$/]?: string;\n}\ntype Q = {\n        x: 10, y: 10, p: 10,\n    } \u0026 {\n        x: 10, y: 10, q: 10,\n    };\ntype R = {\n        w: 10, z: 10,\n    } - {\n        w: 10,\n    };\ninterface S extends P, Q, R {\n    a: 10;\n    b?: 20;\n    @msgId('MyType-c')\n    c: 'aaa';\n    d: [10, 20, ...\u003cstring, 3..10\u003e, 50];\n}\ntype MyType = S | 10 | 20 | 30 | string | 50;\n*/\n\nconst validated1 = validate({...}, myType);\n```\n\n\n\n## DSL syntax\n\n### Type\n\n```ts\ntype Foo = string;\ntype Bar = string[] | 10 | {a: boolean} | [number, string];\n```\n\n\n### Interface\n\n#### Named interface\n```ts\ninterface Foo {\n    a: string;   // Separators `;` and `,` are both allowed.\n    b?: number;\n}\n\ninterface Bar {\n    c: boolean;\n}\n\ninterface Baz extends Foo, Bar {\n    d: string[];\n}\n```\n\n#### Unnamed literal interface\n```ts\ntype A = {\n    a: string,   // Separators `;` and `,` are both allowed.\n    b?: number,\n};\n```\n\n#### Optional member\n```ts\ninterface A {\n    b?: number; // optional member\n};\n```\n\n#### Additional properties\n```ts\ntype X = {a: string, b: number};\n\ninterface A {\n    // Additional properties (Error if `propName` is unmatched)\n    [propName: string | number | /^[a-z][0-9]+$/]: number;\n};\n\ninterface B {\n    // Optional additional properties (Check type if propName matches)\n    //   -\u003e Implicit additional properties are allowed\n    //      even if `ctx.noAdditionalProps` is true.\n    [propName: string | number | /^[a-z][0-9]+$/]?: number; \n};\n\ninterface C {\n    // `propName` can be any name\n    [p: string]: X; \n};\n\ninterface D {\n    // Error if app `propName`s are unmatched\n    [propName1: /^[a-z][0-9]+$/]: number;\n    [propName2: number]: number;\n};\n\ninterface E {\n    // If optional additional properties definition(s) exist,\n    // implicit additional properties are allowed\n    // even if `ctx.noAdditionalProps` is true.\n    [propName1: /^[a-z][0-9]+$/]: number;\n    [propName2: number]: number;\n    [propName3: /^[A-F]+$/]?: number;\n};\n```\nOnly `string`, `number`, and `RegExp` are allowed for the `propName` type.\n\n\n### Type decoration\n\n#### Decorate to interface member\n```ts\ninterface A {\n    @range(-10, 10) @msgId('M1234')\n    a: number;\n}\n```\n\n#### Decorate to type component\n```ts\ntype A = @range(-10, -1) number | @range(1, 10) number;\n\ninterface B {\n    b: @range(-10, -1) number | @range(1, 10) number;\n}\n```\n\n* `@range(minValue: number | string, maxValue: number | string)`\n    * Check value range.\n    * minValue \u003c= data \u003c= maxValue\n* `@minValue(minValue: number | string)`\n    * Check value range.\n    * minValue \u003c= data\n* `@maxValue(maxValue: number | string)`\n    * Check value range.\n    * data \u003c= maxValue\n* `@greaterThan(minValue: number | string)`\n    * Check value range.\n    * minValue \u003c data\n* `@lessThan(maxValue: number | string)`\n    * Check value range.\n    * data \u003c maxValue\n* `@minLength(minLength: number)`\n    * Check value range.\n    * minLength \u003c= data.length\n* `@maxLength(maxLength: number)`\n    * Check value range.\n    * data.length \u003c= maxLength\n* `@match(pattern: RegExp)`\n    * Check value text pattern.\n      * RegExp flags are allowed.\n        * e.g.: `/^[\\u{3000}-\\u{301C}]+$/u`\n    * pattern.test(data)\n* `@stereotype(stereotype: string)`\n    * Perform custom validation.\n        * \u003e **WARNING**: In the JSON schema output, this is stripped.\n* `@constraint(constraintName: string, args: any)`\n    * Perform custom constraint.\n        * \u003e **WARNING**: In the JSON schema output, this is stripped.\n        * `@constraint('unique', fields?: string[])`\n          * Check unique.\n        * `@constraint('unique-non-null', fields?: string[])`\n          * Check unique (null field is always unique).\n        ```ts\n        interface A {\n            @constraint('unique')\n            a: string[];\n        }\n        interface B {\n            @constraint('unique', ['p', 'r'])\n            b: {p: string, q: string, r: string}[];\n        }\n        ```\n* `@forceCast`\n    * Validate after forcibly casting to the assertion's type.\n        * \u003e **WARNING**: In the JSON schema output, this is stripped.\n* `@recordType`\n    * If the decorated member field of object is validated, the union type is determined.\n      * Use to receive reasonable validation error messages.\n    ```ts\n    interface Foo {\n        @recordType kind: 'foo';\n        ...\n    }\n    interface Bar {\n        @recordType kind: 'bar';\n        ...\n    }\n    type FooBar = Foo | Bar;\n    // If data {kind: 'foo', ...} is passed,\n    // the union type will be determined as `Foo`.\n    ```\n* `@meta`\n    * User defined custom properties (meta informations).\n      * Output to the compiled schema.\n    ```ts\n    @meta({ objectId: '0ffc31e6-f534-4e49-b6d7-a3ec21f49637' })\n    interface A {\n        @meta({\n            fieldId: '82bd5832-c399-4d4c-8bc4-b76a95823ebf',\n            fieldType: 'checkbox',\n        })\n        a: ('foo' | 'bar' | 'baz')[];\n    }\n    ```\n* `@msg(messages: string | ErrorMessages)`\n    * Set custom error message.\n* `@msgId(messageId: string)`\n    * Set custom error message id.\n\n\n##### Date / Datetime stereotypes\n\n```ts\n...\nimport { stereotypes as dateStereotypes } from 'tynder/modules/stereotypes/date';\n\nconst schema = compile(`\n    interface Foo {\n        @stereotype('date')\n        @range('=today first-date-of-mo', '=today last-date-of-mo')\n        a: string;\n\n        @stereotype('date')\n        @range('2020-01-01', '2030-12-31')\n        b: string;\n\n        @stereotype('date')\n        @range('2020-01-01', '=today +2yr @12mo @31day')\n        c: string;\n    }\n`);\n\nconst ty = getType(schema, 'Foo');\nconst ctx: Partial\u003cValidationContext\u003e = {\n    checkAll: true,\n    stereotypes: new Map([\n        ...dateStereotypes,\n    ]),\n};\n\nconst d = (new Date()).toISOString().slice(0, 10);\n\nconst z = validate\u003cany\u003e({\n    a: d,\n    b: '2020-01-01',\n    c: d,\n}, ty, ctx);\n```\n\n###### Stereotypes\n\n* `date`\n  * date (UTC timezone)\n* `lcdate`\n  * date (local timezone)\n* `datetime`\n  * datetime (UTC timezone)\n* `lcdatetime`\n  * datetime (local timezone)\n\n\n###### Formula syntax\n\n```\nExpression =\n    ISODateAndDatetime |\n    (\"=\" , DateTimeFormula , {whitespace, DateTimeFormula}) ;\n\nDateTimeFormula =\n    ISODateAndDatetime |\n    (\"current\" | \"now\") |\n    \"today\"\n    (\"@\" | \"+\" | \"-\") , NaturalNumber ,\n            (\"yr\" | \"mo\"  | (\"days\" | \"day\") |\n             \"hr\" | \"min\" | \"sec\" | \"ms\") |\n    \"first-date-of-yr\" |\n    \"last-date-of-yr\" |\n    \"first-date-of-mo\" |\n    \"last-date-of-mo\" |\n    \"first-date-of-fy\", \"(\", NaturalNumber1To12, \")\" ;\n```\n\n###### Formula examples\n\n* This month (date)\n  * `@range('=today first-date-of-mo', '=today last-date-of-mo')`\n* This month (datetime)\n  * `@minValue('=today first-date-of-mo') @lessThan('=today last-date-of-mo +1day')`\n* Next month (date)\n  * `@range('=today first-date-of-mo +1mo', '=today @1day +1mo last-date-of-mo')`\n* Next month (datetime)\n  * `@minValue('=today first-date-of-mo +1mo') @lessThan('=today @1day +1mo last-date-of-mo +1day')`\n* This year (date)\n  * `@range('=today first-date-of-yr', '=today last-date-of-yr')`\n* This year (datetime)\n  * `@minValue('=today first-date-of-yr') @lessThan('=today last-date-of-yr +1day')`\n* Next year (date)\n  * `@range('=today first-date-of-yr +1yr', '=today @1day +1yr last-date-of-yr')`\n* Next year (datetime)\n  * `@minValue('=today first-date-of-yr +1yr') @lessThan('=today @1day +1yr last-date-of-yr +1day')`\n* This fiscal year (date)\n  * `@range('=today first-date-of-fy(9)', '=today first-date-of-fy(9) +1yr -1day')`\n    * Fiscal year beginning in September\n* This fiscal year (datetime)\n  * `@minValue('=today first-date-of-fy(9)') @lessThan('=today first-date-of-fy(9) +1yr')`\n    * Fiscal year beginning in September\n* Next fiscal year (date)\n  * `@range('=today first-date-of-fy(9) +1yr', '=today first-date-of-fy(9) +2yr -1day')`\n    * Fiscal year beginning in September\n* Next fiscal year (datetime)\n  * `@minValue('=today first-date-of-fy(9) +1yr') @lessThan('=today first-date-of-fy(9) +2yr')`\n    * Fiscal year beginning in September\n\n\n##### Unique constraint\n\n```ts\n...\nimport { constraints as uniqueConstraints } from 'tynder/modules/constraints/unique';\n\nconst schema = compile(`\n    interface A {\n        @constraint('unique')\n        a: string[];\n    }\n    interface B {\n        @constraint('unique', ['p', 'r'])\n        b: {p: string, q: string, r: string}[];\n    }\n`);\n\n{\n    const ty = getType(schema, 'A');\n    const ctx: Partial\u003cValidationContext\u003e = {\n        checkAll: true,\n        customConstraints: new Map([\n            ...uniqueConstraints,\n        ]),\n    };\n    const z = validate\u003cany\u003e({a: [\n        'x',\n        'y',\n        'x', // duplicated\n    ]}, ty, ctx);\n}\n{\n    const ty = getType(schema, 'B');\n    const ctx: Partial\u003cValidationContext\u003e = {\n        checkAll: true,\n        customConstraints: new Map([\n            ...uniqueConstraints,\n        ]),\n    };\n    const z = validate\u003cany\u003e({a: [\n        {p: '1', q: '2', r: '3'},\n        {p: '2', q: '3', r: '4'},\n        {p: '1', q: '4', r: '3'}, // duplicated\n    ]}, ty, ctx);\n}\n```\n\n\n\n### Enum\n\n```ts\nenum Foo {\n    A,  // 0\n    B,  // 1\n    C,  // 2\n}\n\nenum Bar {\n    A = 1,    //   1\n    B,        //   2\n    C = 100,  // 100\n}\n\nenum Baz {\n    A = 'AAA',\n    B = 'BBB',\n    C = 'CCC',\n}\n\nconst enum Qux {\n    A,\n}\n```\n\n\n### Primitive types\n\n```ts\n/** Primitive types */\ntype A = number | integer | bigint | string | boolean;\n\n/** Null-like types */\ntype B = null | undefined;\n\n/** Placeholder types */\ntype C = any | unknown | never;\n```\n\n\n### Value types\n\nSee `Literals \u003e Type literals` section.\n\n\n### Array type component (Repeated type component)\n\n#### Simple array type\n```ts\ntype A = string[];\n```\n\n#### Complex array type\n```ts\ntype A = Array\u003cboolean|number|boolean[]|{a: string}|'a'\u003e;\n```\n\n#### Simple array type with quantity assertion\n```ts\ntype A = string[10..20]; // 10 \u003c= data.length \u003c= 20\ntype B = string[10..];   // 10 \u003c= data.length\ntype C = string[..20];   //       data.length \u003c= 20\ntype D = string[10];     // data.length === 10\n```\n\n#### Complex array type with quantity assertion\n```ts\ntype A = Array\u003cboolean, 10..20\u003e; // 10 \u003c= data.length \u003c= 20\ntype B = Array\u003cboolean, 10..\u003e;   // 10 \u003c= data.length\ntype C = Array\u003cboolean, ..20\u003e;   //       data.length \u003c= 20\ntype D = Array\u003cboolean, 10\u003e;     // data.length === 10\n```\n\n\n### Sequence type component (Tuple type component)\n\n#### Fixed length\n```ts\ntype A = [string, number, 10, 20, 'a'];\n```\n\n#### Flex length\n```ts\ntype A = [string, number?, boolean?, string?];              // Zero or once\ntype B = [string, ...\u003cnumber\u003e, ...\u003cboolean\u003e, ...\u003cstring\u003e];  // Zero or more\ntype C = [string, ...\u003cnumber, 10..20\u003e,\n                  ...\u003cboolean, 10..\u003e,\n                  ...\u003cstring, ..20\u003e];                       // With quantity assertion\n```\n\n\u003e **WARNING**: In the JSON schema output, this translates into a simplified array assertion.\n\n### Referencing other interface members\n```ts\ninterface Foo {\n    @match(/^[A-Za-z]+$/)\n    name: string;\n    @match(/^[a-zA-Z0-9.!#$%\u0026'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$/)\n    email: string;\n}\n\ninterface Bar {\n    foo: Foo\n}\n\ninterface User {\n    userName: Foo.name;\n    primaryEmail: Foo.email;\n    primaryAliasName: Bar.foo.name;\n    aliasNames: Bar.foo.name[];\n}\n```\n\n\u003e NOTE:\n\u003e * This syntax is incompatible with TypeScript.\n\u003e   * Generated TypeScript type definition is `userName: Foo['name'];`.\n\u003e   * Tynder compiler does not allow `userName: Foo['name'];`.\n\n\n### Type operators\n\n* `P \u0026 Q`\n    * Intersection type\n    * Result type has all the members of P and Q.\n* `P | Q`\n    * Union type\n    * Match to P or Q type.\n* `P - Q`\n    * Subtraction type\n    * Result type has the members of P that is NOT exist in Q.\n* `Pick\u003cT,K\u003e`\n    * e.g. `Pick\u003cFoo, 'a' | 'b' | 'c'\u003e`\n    * Picked type\n    * Result type has the members of T that is exist in K.\n* `Omit\u003cT,K\u003e`\n    * e.g. `Omit\u003cFoo, 'a' | 'b' | 'c'\u003e`\n    * Picked type\n    * Result type has the members of T that is NOT exist in K.\n* `Partial\u003cT\u003e`\n    * All the member of result type are optioonal.\n    * `Partial\u003c{a: string}\u003e` is equivalent to `{a?: string}`.\n\n\n### Export\n\n```ts\nexport type Foo = string;\n\nexport interface Bar {\n    a: string;\n}\n\nexport enum Baz {\n    A,\n}\n\nexport const enum Qux {\n    A,\n}\n```\n\n\n### Import\n\nThis statement is passed through to the generated codes.\n\n```ts\nimport from 'foo';\nimport * as foo from 'foo';\nimport {a, b as bb} from 'foo';\n```\n\n\n### Declared types\n```ts\ndeclare type A = string;\ndeclare interface B {}\ndeclare enum C {}\ndeclare const enum D {}\n\nexport declare type E = string;\nexport declare interface F {}\nexport declare enum G {}\nexport declare const enum H {}\n```\n\n\n### Declared variables\n\nThis statement is passed through to the generated codes.\n\n```ts\ndeclare var a: number;\ndeclare let b: number;\ndeclare const c: number;\n\nexport declare var d: number;\nexport declare let e: number;\nexport declare const f: number;\n```\n\n\n### External\n\nThis statement is removed from the generated code.\n\n#### Untyped external statement\nDefine the external (ambient) symbols as `any` type.\n\n```ts\nexternal P, Q, R;\n```\nor\n```ts\n/// @tynder-external P, Q, R\n```\nor\n```ts\n/* @tynder-external P, Q, R */\n```\n\n#### Typed external statement\n\n```ts\nexternal P: string[],\n         Q: P | string,\n         R: {a: string}[];\n```\nor\n```ts\n/// @tynder-external P: string[], Q: P | string, R: {a: string}[]\n```\nor\n```ts\n/* @tynder-external\n    P: string[],\n    Q: P | string,\n    R: {a: string}[]\n*/\n```\n\n\n### Pass-through code block\n\nThis comment body is passed through to the generated codes.\n\n```ts\n// Nominal type\n\ndeclare const phoneNumberString: unique symbol;\n/* @tynder-pass-through\nexport type PhoneNumberString = string \u0026 { [phoneNumberString]: never };\n*/\nexternal PhoneNumberString: @match(/^[0-9]{2,4}-[0-9]{1,4}-[0-9]{4}$/) string;\n```\n\n\n### Comments\n\n```ts\n//  ↓↓↓ directive line comment ↓↓↓\n// @tynder-external P, Q, R\n/// @tynder-external S, T\n\n//  ↓↓↓ directive block comment ↓↓↓\n/* @tynder-external U, V */\n\n\n/** doc comment */\ntype Foo = string | number;\n\n/** doc comment */\ninterface Bar {\n    /** doc comment */\n    a?: string;\n}\n\n/** doc comment */\nenum Baz {\n    /** doc comment */\n    A,\n}\n\n// line comment\n# line comment\n\n/* block comment */\n/*\n   block comment\n */\n```\nDoc comments are preserved.\n\n\n### Literals\n\n#### Type literals\n```ts\ntype A = 'a' | \"b\" | `c` |\n         20 | -10 | -0.12 | -9.3+8e |\n         -10_000_000.999_999 |\n         0xff | 0o77 | 0b11 | +Infinity | -Infinity |\n         -10n | 0n | 123n |\n         true | false | null | undefined |\n         {a: string, b: 'aaa'} | [10, string];\n```\n\n#### Value literals\n```ts\ntype A = @match(/^.+$/) string;     // RegExp\ntype B = @range(10, 20) number;     // number\ntype C = @range('a', 'b') string;   // string\ntype D = @msg({\n    required: '...',\n    typeUnmatched: '...' }) number; // object\n```\n\n\n### Directives\n\n```ts\n/// @tynder-external P, Q, R\n```\n\n* `@tynder-external` _type_ [, ...]\n    * Declare external types as `any`.\n\n```ts\n/* @tynder-pass-through\nexport type PhoneNumberString = string \u0026 { [phoneNumberString]: never };\n*/\n```\n\n* `@tynder-pass-through` _body_\n    * This comment body is passed through to the generated codes.\n\n### Generics\nGenerics actual parameters are removed.\n\n#### DSL:\n```ts\n/// @tynder-external Map, Set\n\ninterface Foo {\n    a: Map\u003cstring, number\u003e;  // validator treats it as `any`.\n    b: Set\u003cstring\u003e;          // validator treats it as `any`.\n}\n```\n\n#### TypeScript generated type definition:\n\n```ts\ninterface Foo {\n    a: Map;  // generics actual parameters are removed.\n    b: Set;  // generics actual parameters are removed.\n}\n```\n\n\u003e NOTE: Generic interfaces and generic types cannot be defined.\n\n* e.g.\n\n    ```ts\n    interface Foo\u003cT\u003e { // It is not possible.\n        a: T;\n    }\n    ```\n\n\n\n## Customize error messages\n\n### Customize message of items\n\n```ts\n@msgId('M1111')                                                   // Custom error message id\nexport interface Foo {\n    @msgId('M1234')\n    s: number;                                                    // Custom error message id\n\n    @msg({\n        required: '\"%{name}\" of \"%{parentType}\" is required.',\n        typeUnmatched: '\"%{name}\" of \"%{parentType}\" should be \"%{expectedType}\".',\n    })\n    t: number;                                                    // Custom error message\n\n    @msg('\"%{name}\" of \"%{parentType}\" is not valid.')\n    u: number;                                                    // Custom error message\n}\n```\n\n\n### Default error messages\n\n```ts\nexport const defaultMessages: ErrorMessages = {\n    invalidDefinition:       '\"%{name}\" of \"%{parentType}\" type definition is invalid.',\n    required:                '\"%{name}\" of \"%{parentType}\" is required.',\n    typeUnmatched:           '\"%{name}\" of \"%{parentType}\" should be type \"%{expectedType}\".',\n    additionalPropUnmatched: '\"%{addtionalProps}\" of \"%{parentType}\" are not matched to additional property patterns.',\n    repeatQtyUnmatched:      '\"%{name}\" of \"%{parentType}\" should repeat %{repeatQty} times.',\n    sequenceUnmatched:       '\"%{name}\" of \"%{parentType}\" sequence is not matched',\n    valueRangeUnmatched:     '\"%{name}\" of \"%{parentType}\" value should be in the range %{minValue} to %{maxValue}.',\n    valuePatternUnmatched:   '\"%{name}\" of \"%{parentType}\" value should be matched to pattern \"%{pattern}\"',\n    valueLengthUnmatched:    '\"%{name}\" of \"%{parentType}\" length should be in the range %{minLength} to %{maxLength}.',\n    valueUnmatched:          '\"%{name}\" of \"%{parentType}\" value should be \"%{expectedValue}\".',\n};\n```\n\n### Change default messages\n```ts\nimport { compile }           from 'tynder/modules/compiler';\nimport { getType }           from 'tynder/modules/validator';\nimport { pick,\n         merge }             from 'tynder/modules/picker';\nimport { ValidationContext } from 'tynder/modules/types';\n\nexport default const mySchema = compile(`\n    interface A {\n        @msg({\n            required: 'Don\\'t forget \"%{name}\"!.',\n        })\n        a: string;\n    }\n`);\n\nconst ctx: Partial\u003cValidationContext\u003e = {\n    checkAll: true,\n    noAdditionalProps: true,\n    schema: mySchema,\n    errorMessages: {\n        required: '%{name}\" is requred!',\n    },\n};\n\nconst validated = validate({\n    aa: 'x',\n}, getType(mySchema, 'A'), ctx3);\n\nif (validated3 === null) {\n    console.log(JSON.stringify(\n        ctx3.errors, // error messages\n        null, 2));\n}\n```\n\nPrecedence is \"`Default messages` \u003c `ctx.errorMessages` \u003c `@msg()`\".\n\n\n### Keyword substitutions\n\n* `%{expectedType}`\n* `%{type}`\n* `%{expectedValue}`\n* `%{value}`\n* `%{repeatQty}`\n* `%{minValue}`\n* `%{maxValue}`\n* `%{pattern}`\n* `%{minLength}`\n* `%{maxLength}`\n* `%{name}`\n* `%{parentType}`\n* `%{dataPath}`\n* `%{addtionalProps}`\n\n\n\n## CLI subcommands and options\n\n```\nUsage:\n  tynder subcommand options...\n\nSubcommands:\n  help\n      Show this help.\n  compile\n      Compile schema and output as JSON files.\n          * default input file extension is *.tss\n          * default output file extension is *.json\n  compile-as-ts\n      Compile schema and output as JavaScript|TypeScript files.\n          * default input file extension is *.tss\n          * default output file extension is *.ts\n      Generated code is:\n          const schema = {...};\n          export default schema;\n  gen-ts\n      Compile schema and generate TypeScript type definition files.\n          * default input file extension is *.tss\n          * default output file extension is *.d.ts\n  gen-json-schema\n      Compile schema and generate 'JSON Schema' files.\n          * default input file extension is *.tss\n          * default output file extension is *.json\n  gen-json-schema-as-ts\n      Compile schema and generate 'JSON Schema'\n      as JavaScript|TypeScript files.\n          * default input file extension is *.tss\n          * default output file extension is *.ts\n      Generated code is:\n          const schema = {...};\n          export default schema;\n  gen-csharp\n      Compile schema and generate 'C#' type definition files.\n          * default input file extension is *.tss\n          * default output file extension is *.cs\n  gen-proto3\n      Compile schema and generate 'Protocol Buffers 3' type definition files.\n          * default input file extension is *.tss\n          * default output file extension is *.proto\n  gen-graphql\n      Compile schema and generate 'GraphQL' type definition files.\n          * default input file extension is *.tss\n          * default output file extension is *.graphql\n\nOptions:\n  --indir dirname\n      Input directory\n  --outdir dirname\n      Output directory\n  --inext fileExtensionName\n      Input files' extension\n  --outext fileExtensionName\n      Output files' extension\n```\n\nExample:\n```sh\ntynder compile --indir path/to/schema/tynder --outdir path/to/schema/_compiled\n```\n\n\n## Limitations\n* Generics actual parameters are removed.\n    * Except\n      `Array\u003cT,quantity?\u003e`,\n      [`Pick\u003cT,K\u003e`](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktk),\n      [`Omit\u003cT,K\u003e`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk) and\n      [`Partial\u003cT\u003e`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialt).\n\n\n\n## License\n[ISC](https://github.com/shellyln/tynder/blob/master/LICENSE.md)  \nCopyright (c) 2019-2020 Shellyl_N and Authors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshellyln%2Ftynder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshellyln%2Ftynder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshellyln%2Ftynder/lists"}