{"_id":"@ayonli/type-guard","_rev":"2-6ba0d276dabd8bccd80d194598416f57","name":"@ayonli/type-guard","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@ayonli/type-guard","version":"0.1.0","keywords":["typing","type-checking","schema"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","_id":"@ayonli/type-guard@0.1.0","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"homepage":"https://github.com/ayonli/type-guard#readme","bugs":{"url":"https://github.com/ayonli/type-guard/issues"},"dist":{"shasum":"f5e0fa55bd298a142e8a09a25e3eb10b2b915cf9","tarball":"https://registry.npmjs.org/@ayonli/type-guard/-/type-guard-0.1.0.tgz","fileCount":6,"integrity":"sha512-dK4sSj31lNV0WVxcak34nBk55bE/pIYGzIadUwccd6xErlQ9qVWRsV8oBY4dZ9RFq1BhRXISzKFw8LQJWBqhZw==","signatures":[{"sig":"MEYCIQCv6kl+ezV2/Xip6m5cMjBZV+H+LCW4CeGvGklfBqj9nwIhALrumZ7tzXiu/rqAb2dPFC+o9pWbFr2vhBKwP0Gqxa42","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":326109},"main":"dist/index.js","types":"src/index.ts","gitHead":"5c91066e1e459618aae381602e6555737722f7dd","scripts":{"test":"npx mocha -r ts-node/register test/*.ts","build":"npx tsc","prepack":"npm run build","test-es":"cross-env TS_NODE_PROJECT=es.tsconfig.json npx mocha -r ts-node/register test/*.ts"},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"repository":{"url":"git+https://github.com/ayonli/type-guard.git","type":"git"},"_npmVersion":"9.5.0","description":"Runtime type checking for JavaScript variables and functions.","directories":{},"_nodeVersion":"18.14.2","dependencies":{"@hyurl/utils":"^0.2.26"},"_hasShrinkwrap":false,"devDependencies":{"dotry":"^1.2.1","mocha":"^10.2.0","ts-node":"^10.9.1","cross-env":"^7.0.3","typescript":"^5.0.4","@types/node":"^18.15.3","@types/mocha":"^10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/type-guard_0.1.0_1693102876490_0.6953047408920494","host":"s3://npm-registry-packages"}},"0.1.1":{"name":"@ayonli/type-guard","version":"0.1.1","keywords":["typing","type-checking","schema"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","_id":"@ayonli/type-guard@0.1.1","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"homepage":"https://github.com/ayonli/type-guard#readme","bugs":{"url":"https://github.com/ayonli/type-guard/issues"},"dist":{"shasum":"4d23f004eb5297aad1aaf1510665d9c95c20dac1","tarball":"https://registry.npmjs.org/@ayonli/type-guard/-/type-guard-0.1.1.tgz","fileCount":12,"integrity":"sha512-a6wQS/hCNyqxcbqEiqKFhHdTRyoE5b1rMJAFABGQF0ucOrJJnr/E1e5kC0qAQdUB1itAnGjrUzrvFQqRK2zxpQ==","signatures":[{"sig":"MEUCIQCHJh3Bpco/EAIiNPU8vOTecjeeuYZDXvlzkACg76P87wIgB0+v2hj5Rqa6KzcVkHkwFD0chQsBFJ33NB7sxIHMYHE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":526314},"main":"./cjs/index.js","types":"./cjs/index.d.ts","module":"./esm/index.js","exports":{"types":"./cjs/index.d.ts","import":"./esm/index.js","require":"./cjs/index.js"},"gitHead":"a9cf709ab075cc424b991dea98ddaf192ea610b1","scripts":{"test":"mocha -r ts-node/register test/*.ts","build":"tsc --module esnext --outDir esm && tsc --module commonjs --outDir cjs --declaration && node postbuild.js","prepack":"npm run build"},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"repository":{"url":"git+https://github.com/ayonli/type-guard.git","type":"git"},"_npmVersion":"10.5.1","description":"Runtime type checking for JavaScript variables and functions.","directories":{},"_nodeVersion":"22.0.0","dependencies":{"@ayonli/jsext":"^0.9.55"},"_hasShrinkwrap":false,"devDependencies":{"mocha":"^10.2.0","tslib":"^2.6.2","ts-node":"^10.9.1","typescript":"^5.0.4","@types/node":"^18.15.3","@types/mocha":"^10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/type-guard_0.1.1_1722758425427_0.1587400093873832","host":"s3://npm-registry-packages"}},"0.1.2":{"name":"@ayonli/type-guard","version":"0.1.2","description":"Runtime type checking for JavaScript variables and functions.","main":"./cjs/index.js","module":"./esm/index.js","types":"./cjs/index.d.ts","exports":{"require":"./cjs/index.js","import":"./esm/index.js","types":"./cjs/index.d.ts"},"scripts":{"build":"tsc --module esnext --outDir esm && tsc --module commonjs --outDir cjs --declaration && node postbuild.js","prepack":"npm run build","test":"mocha -r ts-node/register test/*.ts"},"repository":{"type":"git","url":"git+https://github.com/ayonli/type-guard.git"},"keywords":["typing","type-checking","schema"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","bugs":{"url":"https://github.com/ayonli/type-guard/issues"},"homepage":"https://github.com/ayonli/type-guard#readme","devDependencies":{"@types/mocha":"^10.0.1","@types/node":"^18.15.3","mocha":"^10.2.0","ts-node":"^10.9.1","tslib":"^2.6.2","typescript":"^5.0.4"},"dependencies":{"@ayonli/jsext":"^0.9.56"},"_id":"@ayonli/type-guard@0.1.2","gitHead":"adda20ff3839d67fe2ec416c260d30caf305eba0","_nodeVersion":"22.0.0","_npmVersion":"10.5.1","dist":{"integrity":"sha512-KO88xtSdtn7aC+0u4HV7tPuucGuhE/s4Sb8RR3BAJVE5FltwY3UPIKNYC8I/w9I6FjlRqllFQkgm7QSIZKgNqw==","shasum":"39f03727563ac98b23da3ee3166c9f2cad92faee","tarball":"https://registry.npmjs.org/@ayonli/type-guard/-/type-guard-0.1.2.tgz","fileCount":11,"unpackedSize":526121,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF5g6ar8yzox4Jw0uyqydTM0vcThLGxr1diUYJtp7jDiAiEAq60gkeReeBywOYtML+IMK6KLs/19kqqnED+Y2U7Hg+E="}]},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"directories":{},"maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/type-guard_0.1.2_1722790773027_0.6844646648437862"},"_hasShrinkwrap":false}},"time":{"created":"2023-08-27T02:21:16.393Z","modified":"2024-08-04T16:59:33.335Z","0.1.0":"2023-08-27T02:21:16.721Z","0.1.1":"2024-08-04T08:00:25.596Z","0.1.2":"2024-08-04T16:59:33.177Z"},"bugs":{"url":"https://github.com/ayonli/type-guard/issues"},"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","homepage":"https://github.com/ayonli/type-guard#readme","keywords":["typing","type-checking","schema"],"repository":{"type":"git","url":"git+https://github.com/ayonli/type-guard.git"},"description":"Runtime type checking for JavaScript variables and functions.","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"readme":"# Type Guard\n\nRuntime type checking for JavaScript variables and functions.\n\n*Inspired by TypeScript, JSDoc and JSON Schema.*\n\n## Table of Contents\n\n- [Install](#install)\n- [But Why?](#but-why)\n    - [Vulnerable Code](#vulnerable-code)\n    - [Optimized Code](#optimized-code)\n- [Concept](#concept)\n- [Extends JavaScript Basic Type Constructors](#extends-javascript-basic-type-constructors)\n    - [Core Types](#core-types)\n    - [Additional Types](#additional-types)\n- [Core Features](#core-features)\n- [String Specials](#string-specials)\n- [Number Specials](#number-specials)\n- [BigInt Specials](#bigint-specials)\n- [Array Specials](#array-specials)\n- [CustomType Specials](#customtype-specials)\n- [as()](#as)\n    - [Example of CustomType](#example-of-customtype)\n    - [Example of UnionType and TupleType](#example-of-uniontype-and-tupletype)\n- [Decorators](#decorators)\n- [Validation](#validation)\n    - [Dealing With TS2589 Error](#dealing-with-ts2589-error)\n- [Set Warning Handler](#set-warning-handler)\n- [Advanced Usage](#advanced-usage)\n    - [Extending Types or Intersection Types](#extending-types-or-intersection-types)\n    - [Generic Types](#generic-types)\n- [Utility Functions](#utility-functions)\n    - [Utility Types](#utility-types)\n- [Working with Common Functions](#working-with-common-functions)\n- [Working with JSON Schema](#working-with-json-schema)\n    - [JSON Schema for Functions](#json-schema-for-functions)\n\n## Install\n\n```sh\nnpm i @ayonli/type-guard\n```\n\n\n## But Why?\n\nWhy do we need this? Since we already have TypeScript.\n\nWell, TypeScript can only guarantee your code during compile-time, but imaging\nyour function is called remotely, especially when designing an API. Just look\nthis piece of code:\n\n### Vulnerable Code\n\n```ts\nexport default class ExampleApi {\n    async sum(data: { num1: number; num2: number; }) {\n        return { result: data.num1 + data.num2 };\n    }\n}\n```\n\nLooks perfect. However, what if an HTTP client is trying to call the function\nwith malformed input:\n\n```http\nPOST /api/ExampleApi.echo\n\nContent-Type: application/json\n\n{\"num1\":\"100\",\"num2\":\"200\"}\n```\n\nThings could turn out ugly. We cannot guarantee what kind of data the client\nmay provide, especially when it's in a different programming language.\n\nSo how do we solve this? This is where Type-Guard comes in.\n\n### Optimized Code\n\n```ts\nimport { param, returns } from \"@ayonli/type-guard\";\n\nexport default class ExampleApi {\n    @param({ num1: Number, num2: Number }, \"data\")\n    @returns({ result: Number })\n    async sum(data: { num1: number; num2: number; }) {\n        return { result: data.num1 + data.num2 };\n    }\n}\n```\n\nNow the program will run properly.\n\n## Concept\n\nType-Guard provides a security layer for the data provided by unknown input\nsource. It converts any thing it recognizes, and report errors when they are not\ncompatible.\n\n## Extends JavaScript Basic Type Constructors\n\nType-Guard adds a set of keywords to the general type constructors like\n`String`, `Number`, `Boolean`, etc. for type constraints. We can group them to\nform schema-like type definitions, and reuse them anywhere we want.\n\n### Core Types\n\n- `String`\n- `Number` and `BigInt`\n- `Boolean`\n- `Date`\n- `Object` for objects (exclude array)\n- `Array` for an array of any type of items\n- `{}` and `[]` literals are used to define deep structures\n  - Specifically, an empty `[]` literal serves as the same as `Array`\n\n\n### Additional Types\n\nThese types can be imported from this package:\n\n- `Dict(Key, Value)` the value is of `Record<K, V>`\n- `Any` the value can be anything (except `null` and `undefined`)\n- `Void` the value is `null` or `undefined`. This type are mainly for functions\n    that takes no argument or returns nothing. For example:\n    - `@param(Void)` will constrain the method to take no argument.\n    - `@returns(Void)` will constrain the method to return nothing.\n\nAnd these abstract types can be created via the wrapper function `as()`:\n\n- `CustomType`\n- `UnionType`\n- `TupleType`\n\n## Core Features\n\n- `optional` Marks the current variable/property/parameter as optional.\n- `required` Marks the current variable/property/parameter as required. NOTE: by\n    default, the variable/property/parameter is required, this option is used to\n    remark an optional type when it is reused somewhere else.\n- `default(value)` Sets the default value for the current\n    variable/property/parameter when it is omitted. NOTE: this function will\n    implicitly force `optional`.\n- `remarks(note: string)` Adds a remark message to the variable/property/parameter.\n    This could be useful when generating JSON Schema.\n- `deprecated(message?: string)` Marks the current variable/property/parameter\n    as deprecated and provide a message.\n- `alternatives(...props: string[])` Sets the current property and other\n    properties to be alternatives, and only one of them are required. This\n    function must be used along with `optional` keyword and only have to be\n    set on one of the alternative properties.\n- `associates(...props: string[])` Sets the current property to be associated\n    with other properties, if this property is set, all others must be provided\n    as well.\n\n*All these keywords are chainable, we can use several of them to form a*\n*specific constraint.*\n\n```ts\nimport { as, Any, Dict, Void } from \"@ayonli/type-guard\";\n\nconst Str1 = String.optional; // an optional string\nconst Str2 = String.default(\"\"); // an optional string with default value ''\n\nconst Structure = {\n    str1: Str1, // reuse the type definition\n    str2: Str2.required, // Str2 is optional, but we want 'str2' to be required\n    num1: Number.optional.deprecated(\"use 'num2' instead\"), // deprecated with message\n    num2: Number.optional.alternatives(\"num3\"), // 'num2' and 'num3' are alternatives\n    num3: BigInt.optional.associates(\"bool1\"), // 'num3' must be paired with 'bool1'\n    bool1: Boolean,\n    bool2: Boolean.default(false), // optional boolean type with default value: false\n    date1: Date,\n    date2: Date.optional,\n    obj1: { // deep structures are supported and unlimited\n        // ...\n    },\n    obj2: as({ // use as().optional to form an optional object literal structure\n        // ...\n    }).optional,\n    arr1: [String].default([]), // array can use these features directly\n    arr2: [String, Number, BigInt], // array of many types: (string | number | bigint)[]\n    arr3: [], // array of any types: any[]\n    arr4: Array(String), // is the same as [String]\n    arr5: Array, // is the same as []\n    union1: as(String, Number), // as() can be used to form Union Types: string | number\n    tuple1: as([String, Number] as const), // as() can be used to form tuples: [string, number]\n    my1: MyClass, // custom types are supported\n    my2: as(MyClass).optional, // use as().optional to form an optional custom type\n    any: Any, // any type of value\n    obj: Object, // object type\n    dict1: Dict(String, Number), // equivalent to Record<string, number> in TypeScript\n    dict2: Dict(String.enum([\"foo\", \"bar\"] as const), String), // Record<\"foo\" | \"bar\", number>\n    nil: Void, // void type: null or undefined\n    const1: \"hello\" as const, // string constant\n    const2: 100 as const, // number constant\n};\n```\n\n*Each reference of the keywords will create a new constraint, so they can be*\n*reused without worrying about context pollution. In the above example,*\n*`Str2.required` will create a new string constraint and leave the `Str2` untouched.*\n\n## String Specials\n\nApart from the standard core features, the `String` constructor includes the\nfollowing additional properties and methods.\n\n- `minLength(length: number)` Sets the minimal length of the text.\n- `maxLength(length: number)` Sets the maximal length of the text.\n- `trim` Removes the leading and tailing spaces of the text.\n- `spaceless` Removes all spaces of the text.\n- `lowercase` Converts the text to lower-case style.\n- `uppercase` Converts the text to upper-case style.\n- `enum(values: string[])` Sets the enum options of which the text\n    can be.\n- `match(pattern)` Sets a pattern to test whether the text fulfills the\n    requirements, or sets a custom function to do the test. `pattern` can be\n    one of this values:\n    - `email` Standard email address with latin characters.\n    - `phone` Telephone numbers with various formats.\n    - `ip` IPv4 address.\n    - `url` Standard URL address with latin characters.\n    - `hostname` Standard hostname with latin characters.\n    - `date` Date string with format: `YYYY-MM-DD`.\n    - `time` Time string with format: `HH:mm:ss` or `HH:mm`.\n    - `datetime` Datetime string with format: `YYYY-MM-DD HH:mm:ss`.\n    - a regular expression\n    - a function with signature `(value: string) => boolean`.\n\n```ts\nconst Str1 = String.minLength(1).maxLength(100); // string of limited length\nconst Str2 = String.trim; // string that trims leading and tailing spaces\nconst Str3 = String.match(\"phone\").spaceless; // phone number with no spaces\nconst Str4 = String.match(\"email\").lowercase; // email should be lowercased\nconst Str5 = String.enum([\"A\", \"B\", \"C\"] as const); // enum values/union types: 'A' | 'B' | 'C'\n// Pay attention to the `as const` part, this gives us more hints in TypeScript.\n```\n\n## Number Specials\n\nApart from the core features, the `Number` constructor includes the following\nadditional properties and methods.\n\n- `integer` Restrains the number to be an integer.\n- `min(value: number)` Sets the minimal value of the number.\n- `max(value: number)` Sets the maximal value of the number.\n- `enum(values: number[])` Sets the enum options of which the number can be.\n\n```ts\nconst Num1 = Number.integer; // number of integer\nconst Num2 = Number.min(1).max(100); // number of limit range\nconst Num3 = Number.enum([-1, 0, 1] as const); // enum values/union types: -1 | 0 | 1\n// Pay attention to the `as const` part, this gives us more hints in TypeScript.\n```\n\n## BigInt Specials\n\nApart from the core features, the `BigInt` constructor includes the following\nadditional properties and methods.\n\n- `min(value: bigint)` Sets the minimal value of the number.\n- `max(value: bigint)` Sets the maximal value of the number.\n- `enum(values: bigint[])` Sets the enum options of which the number can be.\n\n```ts\nconst Int1 = BigInt.min(1n).max(100n); // number of limit range\nconst Int2 = BigInt.enum([-1n, 0n, 1n] as const); // enum values/union types: -1n | 0n | 1n\n// Pay attention to the `as const` part, this gives us more hints in TypeScript.\n```\n\n## Array Specials\n\nApart from the core features, the array includes the following additional\nproperties and methods.\n\n- `guard(transform: (data: any, path: string, warnings: ValidationWarning[]) => any)`\n    Defines a function that transforms the input data to the desired type.\n- `minItems(count: number)` Sets the minimum items of the array.\n- `maxItems(count: number)` Sets the maximum items of the array.\n- `uniqueItems` Restrains the array to have unique items.\n\n```ts\nconst Arr1 = [String].minItems(1).maxItems(10).uniqueItems;\n```\n\n## CustomType Specials\n\nApart from the core features, the CustomType include the following additional\nproperties and methods.\n\n- `guard(transform: (data: any, path: string, warnings: ValidationWarning[]) => any)`\n    Defines a function that transforms the input data to the desired type.\n\n## `as()`\n\nBy default, any class (aka, type constructors) and object literals can be\ndirectly used for type checking, but they lacks the ability to be optional,\nsetting default values, or use any other features that general types support.\n\nBy wrapping them in the `as()` function, which returns a `CustomType`, we can\nbring the additional features to any type constructors we want.\n\n### Example of CustomType\n\n```ts\nimport { as } from \"@ayonli/type-guard\";\n\nclass Avatar {\n    constructor(data: any) {\n        Object.assign(this, data);\n    }\n}\n\nconst Type = {\n    buf: as(Buffer).optional,\n    avatar: as(Avatar).guard(data => data instanceof Avatar ? data : new Avatar(data)),\n    obj: as({\n        foo: String.optional,\n        bar: Number.optional,\n    }).default({}),\n};\n```\n\n`as()` function is also used to create union types and tuples.\n\n### Example of UnionType and TupleType\n\n```ts\nimport { as } from \"@ayonli/type-guard\";\n\nconst Type = {\n    union: as(String, Number), // string | number\n    tuple: as([String, Number] as const), // [string, number]\n};\n```\n\n## Decorators\n\nThere are two decorators for most use cases, as long as you're coding in\nTypeScript or with Babel.\n\n- `@param(type: any, name?: string, remarks?: string)`\n- `@param(name: string, type: any, remarks?: string)` A decorator that\n    restrains the input arguments of the method.\n    - `type` The type of the argument, can be a class, a type constructor\n        (including `as()`), an object or array literal that specifies deep\n        structure.\n    - `name` The argument name, used to address where the error is reported.\n    - `remarks` The remark message of the parameter. Useful when generating JSON\n        Schema.\n\n    NOTE: the order of using `@param()` must consist with order of which the\n    parameter is present.\n\n    Specifically, `@param(Void)` will constrain the method to take no argument.\n- `@returns(type: any, remarks?: string)` A decorator that restrains the\n    return value of the method.\n    - `type` The type of the return value, can be a class, a type constructor\n        (including `as()`), an object or array literal that specifies deep\n        structure.\n    - `remark` the remark message of the return value.  Useful when generating\n        JSON Schema.\n\n    NOTE: if the method returns a Promise, this function restrains the resolved\n    value instead.\n\n    Specifically, `@returns(Void)` will constrain the method to return nothing.\n\nThere are also other non-frequently used decorators:\n\n- `@throws(type: any)` A decorator that restrains the thrown error of the \n    method. \n    - `type` The type of the thrown error, usually a class or a string.\n- `@remarks(note: string)` A decorator that adds remark message to the method.\n    Useful when generating JSON Schema.\n- `@deprecated(message?: string)` A decorator that deprecates the method and\n    emit a warning message when the method is called.\n    - `message` The warning message, can be used to provide suggestions.\n\n```ts\nimport { param, returns, deprecated } from \"@ayonli/type-guard\";\n\nexport default class ExampleApi {\n    @param(\"data\", { num1: Number, num2: Number })\n    @returns({ result: Number })\n    async sum(data: { num1: number; num2: number; }) {\n        return { result: data.num1 + data.num2 };\n    }\n\n    @deprecated(\"use sum() instead\")\n    @param(Number, \"num1\")\n    @param(Number, \"num2\")\n    @returns(Number)\n    async oldSum(num1: number, num2: number) {\n        return this.sum({ num1, num2 });\n    }\n}\n```\n\n## Validation\n\nOf course, decorators will work without any configuration (except enabling it in\n`tsconfig.json`). In other scenarios, we can use the `validate()` function to\ncheck the value we want.\n\n- `validate(value: any, type: any, variable?: string, options?)`\n    - `value` The input value that needs to be validated.\n    - `type` Can be a class, a type constructor (including `as()`), an object or\n        array literal that specifies deep structure.\n    - `variable` The variable name that the input value is assigned to, useful\n        for reporting errors. If not specified, `$` will be used.\n    - `options`\n        - `strict?: boolean` Use strict mode, will disable any implicit type\n            conversion.\n        - `suppress?: boolean` Suppress non-critical errors as warnings, or\n            suppress unknown property/item removing warnings (when enabled).\n        - `warnings?: ValidationWarning[]` A list used to\n            store all the warnings occurred during the validation process.\n        - `removeUnknownItems?: boolean` Remove unknown properties in the object\n            or the items that exceed the length limit of the array.\n\nNOTE: Both `@param()` and `@returns()` will set `removeUnknownItems` to `true`.\n`@returns()` sets `suppress` as well.\n\n```ts \nimport { validate, as } from \"@ayonli/type-guard\";\n\nconst str = \"Hello, World!\";\nvalidate(str, String, \"str\"); // => \"Hello, World!\";\nvalidate(str, Number, \"str\"); // throw type error\n\nconst num = 123;\nvalidate(num, Number, \"num\"); // => 123\nvalidate(num, String, \"num\"); // => \"123\"\nvalidate(num, String, \"num\", { strict: true}); // throw type error\n\nclass MyClass {}\n\nconst obj = { str: \"Hello, World!\", num: [123] };\nvalidate(obj, {\n    str: String,\n    num: [Number],\n    bool: Boolean.default(false),\n    date: Date.optional,\n    // @ts-ignore\n    deep: as({\n        buf: as(Buffer).default(Buffer.from(\"\")),\n        deeper: [{\n           foo1: Uint8Array,\n           bar2: MyClass,\n        }].optional\n    }).optional,\n}, \"obj\"); // => { str: \"Hello, World!\", num: [123], bool: false }\n```\n\n### Dealing With TS2589 Error\n\nSometimes when calling the `as()` function, the `validate()` function,\nthe `def()` function, or using the `ExtractInstanceType<T>` (the real reason)\nutility type, the TypeScript compiler may throw an error:\n\n```\nerror TS2589: Type instantiation is excessively deep and possibly infinite.\n```\n\nThis error just says that the compiler detects some recursive type inference is\nvery deep (exceeding the limit of 50 recursions, as mentioned\n[here](https://github.com/microsoft/TypeScript/issues/34933#issuecomment-552500444)),\nand to prevent possible infinite call stack (which is not), it stops the\ncompilation process, we can simply use `// @ts-ignore` directive to bypass the\nerror and continue, just as the above example shows.\n\n## Set Warning Handler\n\nAll decorators emit warnings during validation process, by default, warnings are\nlogged to the stdout/console, but if the function is called as an HTTP API, we\nmay want to attach the warnings to the response so the client can adjust its\ncalls.\n\n```ts\nimport { setWarningHandler } from \"@ayonli/type-guard\";\n\nexport default class ApiController {\n    // All API controllers are inherited from the base ApiController.\n}\n\nfunction isApiResponse(returns: any) {\n    return returns\n        && typeof returns === \"object\"\n        && typeof returns[\"code\"] === \"number\"\n        && (\"data\" in returns || \"message\" in returns);\n}\n\nsetWarningHandler(function (this, warnings, returns) {\n    if (warnings.length && (this instanceof ApiController) && isApiResponse(returns)) {\n        returns[\"warnings\"] ??= [];\n        (returns[\"warnings\"] as string[]).push(...warnings.map(item => item.message));\n    } else {\n        for (const { message } of warnings) {\n            console.warn(message);\n        }\n    }\n});\n```\n\n## Advanced Usage\n\n### Extending Types or Intersection Types\n\nSince the type structure is just a plain object, you can reuse them via spread\nsyntax (`...`) to extend types.\n\n```ts\nconst BaseType = {\n    str: String,\n    num: Number,\n};\n\nconst ChildType = {\n    ...BaseType,\n    bool: Boolean,\n    date: Date,\n};\n\n// Which is similar to:\n\ntype BaseType = {\n    str: string;\n    num: number;\n}\n\ntype ChildType = BaseType & {\n    bool: boolean;\n    date: Date;\n};\n\n// or:\ninterface BaseType {\n    str: string;\n    num: number;\n}\n\ninterface ChildType extends BaseType {\n    bool: boolean;\n    date: Date;\n}\n```\n\n### Generic Types\n\nEven more, you can use functions to achieve generic types.\n\n```ts\nimport { as, param, returns } from \"@ayonli/type-guard\";\n\nexport type ApiResponse<T> = {\n    code: number;\n    data?: T;\n    message?: string;\n    warnings?: string[];\n}\nexport function ApiResponse<T>(data: T) {\n    return {\n        code: Number,\n        data: !Array.isArray(data)\n            ? as(data).optional // as() can wrap anything, actually\n            : data.optional, // but do not wrap an array since as() treats it as tuple\n        message: String.optional,\n        warnings: [String].optional,\n    };\n}\n\nclass ExampleApi extends ApiController {\n    @param(Number, \"num1\")\n    @param(Number, \"num2\")\n    @param(Number.optional, \"num3\")\n    @returns(ApiResponse(Number))\n    async sum(num1: number, num2: number, num3?: number): Promise<ApiResponse<number>> {\n        return {\n            code: 0,\n            data: num1 + num2 + (num3 || 0),\n        };\n    }\n}\n```\n\n## Utility Functions\n\nThis package also comes with several utility functions which we can use to\nachieve similar functionalities of their TypeScript equivalents.\n\n- `partial(type: T extends (Record<string, unknown> | DictType<IndexableType, unknown>))`\n- `required(type: T extends Record<string, unknown>)`\n- `optional<T extends Record<string, unknown>, K extends keyof T>(type: T, props: K[])`\n- `ensured<T extends Record<string, unknown>, K extends keyof T>(type: T, props: K[])`\n\nAnd we can also use `pick` and `omit` from [JsExt](https://github.com/ayonli/jsext).\n\n```ts\nimport { partial, required, optional, ensured } from \"@ayonli/type-guard\";\nimport { pick, omit } from \"@ayonli/jsext/object\";\n\nconst Type = {\n    foo: String,\n    bar: Number,\n};\n\nconst Type1 = partial(Type); // => { foo: String.optional, bar: Number.optional }\n\nconst Type2 = required(Type1);\n// => { foo: String.optional.required, bar: Number.optional.required }\n\nconst Type3 = optional(Type, [\"bar\"]); // => { foo: String, bar: Number.optional }\n\nconst Type4 = ensured(Type1, [\"bar\"]);\n// => { foo: String.optional, bar: Number.optional.required }\n\nconst Type5 = pick(Type, [\"foo\"]); // => { foo: String }\n\nconst Type6 = omit(Type, [\"foo\"]); // => { bar: Number }\n```\n\n### Utility Types\n\nFor better TypeScript integration, there is also a useful utility type that we\ncan use to infer type from a JavaScript type definition.\n\n- `ExtractInstanceType<T>`\n\n```ts\nimport { ExtractInstanceType } from \"@ayonli/type-guard\";\n\ntype MyStringType = ExtractInstanceType<typeof String>;\n// will resolve in: string\n\nconst MyStringEnum = String.enum([\"A\", \"B\", \"C\"] as const);\ntype MyStringEnum = ExtractInstanceType<typeof MyStringEnum>;\n// will resolve in: \"A\" | \"B\" | \"C\";\n\nconst Struct = {\n    foo: String,\n    bar: Number.optional,\n    deep: {\n        foo1: Date,\n        bar2: Object.optional,\n    },\n};\ntype Struct = ExtractInstanceType<typeof Struct>;\n// will resolve in: { foo: string; bar?: number; deep: { foo1: Date; bar2?: object; } }\n```\n\n## Working with Common Functions\n\nWell, decorators only work on class methods, if we want to use type validation\nin common functions, there are two ways to do so:\n\n1. Use `decorate()` function to simulate decorator features on a function.\n2. Use `def()` function to create a wrapped function with type checking features.\n\n```ts\nimport { decorate, def, param, returns } from \"@ayonli/type-guard\";\n\nconst sum = decorate(\n    param(\"num1\", Number),\n    param(\"num2\", Number),\n    param(\"num3\", Number.optional),\n    returns(Number)\n)(function sum(num1: number, num2: number, num3?: number) {\n    return num1 + num2 + (num3 ?? 0);\n});\n\nconst sum2 = def(\n    ({ num1, num2, num3 }) => { // the actual function\n        return num1 + num2 + (num3 ?? 0);\n    },\n    [{ num1: Number, num2: Number, num3: Number.optional }] as const, // parameters\n    Number // returns\n);\n```\n\n## Working with JSON Schema\n\nThe type definition can be easily converted to JSON Schema, and exported to\nother clients or languages for wider adoption.\n\n```ts\nimport { getJSONSchema } from \"@ayonli/type-guard\";\n\nconst Article = {\n    id: Number.remarks(\"The ID of article\"),\n    title: String.remarks(\"The title of the article\"),\n    content: String.remarks(\"The content of the article\"),\n    status: String.enum([\"created\", \"published\", \"archived\"] as const).remarks(\"The status of the article\"),\n    tags: [String].optional.remarks(\"The tags of the article\"),\n};\n\nconst ArticleSchema = getJSONSchema(Article, { // and JSON schema\n    $id: \"https://myapi.com/article.schema.json\",\n    title: \"Article\",\n    description: \"\",\n});\n// will generate something like this:\n// {\n//     \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n//     \"$id\": \"https://myapi.com/article.schema.json\",\n//     \"title\": \"Article\",\n//     \"type\": \"object\",\n//     \"description\": \"\",\n//     \"properties\": {\n//         \"id\": {\n//             \"type\": \"number\",\n//             \"description\": \"The ID of article\",\n//             \"enum\": null\n//         },\n//         \"title\": {\n//             \"type\": \"string\",\n//             \"description\": \"The title of the article\",\n//             \"enum\": null,\n//             \"minLength\": 0\n//         },\n//         \"content\": {\n//             \"type\": \"string\",\n//             \"description\": \"The content of the article\",\n//             \"enum\": null,\n//             \"minLength\": 0\n//         },\n//         \"status\": {\n//             \"type\": \"string\",\n//             \"description\": \"The status of the article\",\n//             \"enum\": [\n//                 \"created\",\n//                 \"published\",\n//                 \"archived\"\n//             ],\n//             \"minLength\": 0\n//         },\n//         \"tags\": {\n//             \"type\": \"array\",\n//             \"description\": \"The tags of the article\",\n//             \"items\": {\n//                 \"type\": \"string\"\n//             },\n//             \"minItems\": 0,\n//             \"uniqueItems\": false\n//         }\n//     },\n//     \"required\": [\n//         \"id\",\n//         \"title\",\n//         \"content\",\n//         \"status\"\n//     ]\n// }\n```\n\n### JSON Schema for Functions\n\nAs we've used decorators to add constraint features to class methods, it would be\nmuch better if we can annotate the method via plain JSON Schema as an API.\nThis's why this package also added a `getJSONSchema()` function to the\n`Function.prototype`, which retrieves a super schema of the function design.\n\n```ts\nimport { ExtractInstanceType, remarks, param, returns } from \"@ayonli/type-guard\";\n\nconst Article = {\n    id: Number.remarks(\"The ID of article\"),\n    title: String.remarks(\"The title of the article\"),\n    content: String.remarks(\"The content of the article\"),\n    status: String.enum([\"created\", \"published\", \"archived\"] as const).remarks(\"The status of the article\"),\n    tags: [String].optional.remarks(\"The tags of the article\"),\n};\ntype Article = ExtractInstanceType<typeof Article>;\n\nclass ArticleController {\n    @remarks(\"Create a new article\")\n    @param(Article, \"article\")\n    @returns(Article)\n    async create(article: Article) {\n        return article;\n    }\n}\n\nconsole.log(JSON.stringify(ArticleController.prototype.create.getJSONSchema(), null, \"    \"));\n// will output something like this:\n// {\n//     \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n//     \"$id\": \"ArticleController.create\",\n//     \"title\": \"ArticleController.create\",\n//     \"type\": \"function\",\n//     \"description\": \"Create a new article\",\n//     \"parameters\": {\n//         \"article\": {\n//             \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n//             \"$id\": \"ArticleController.create.parameters.article\",\n//             \"title\": \"ArticleController.create.parameters.article\",\n//             \"type\": \"object\",\n//             \"description\": \"\",\n//             \"properties\": {\n//                 // refer to the previous example\n//             },\n//             \"required\": [\n//                 \"id\",\n//                 \"title\",\n//                 \"content\",\n//                 \"status\"\n//             ]\n//         }\n//     },\n//     \"returns\": {\n//         \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n//         \"$id\": \"ArticleController.create.returns\",\n//         \"title\": \"ArticleController.create.returns\",\n//         \"type\": \"object\",\n//         \"description\": \"\",\n//         \"properties\": {\n//             // refer to the previous example\n//         },\n//         \"required\": [\n//             \"id\",\n//             \"title\",\n//             \"content\",\n//             \"status\"\n//         ]\n//     }\n// }\n```\n","readmeFilename":"README.md"}