[
  {
    "path": ".eslintignore",
    "content": "dist/"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  rules: {\n    eqeqeq: 'off',\n    'no-redeclare': 'off',\n    '@typescript-eslint/no-redeclare': ['error', { ignoreDeclarationMerge: true }],\n  },\n};\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\non: [push]\njobs:\n  build:\n    name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}\n\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        node: ['10.x', '12.x', '14.x']\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v2\n\n      - name: Use Node ${{ matrix.node }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node }}\n\n      - name: Install deps and build (with cache)\n        uses: bahmutov/npm-install@v1\n\n      - name: Lint\n        run: yarn lint\n\n      - name: Test\n        run: yarn test --ci --coverage --maxWorkers=2\n\n      - name: Build\n        run: yarn build\n"
  },
  {
    "path": ".github/workflows/size.yml",
    "content": "name: size\non: [pull_request]\njobs:\n  size:\n    runs-on: ubuntu-latest\n    env:\n      CI_JOB_NUMBER: 1\n    steps:\n      - uses: actions/checkout@v1\n      - uses: andresz1/size-limit-action@v1\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\n.DS_Store\nnode_modules\ndist\ncoverage\ntodo"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n### [5.0.3](https://github.com/5alidz/tiny-schema-validator/compare/v5.0.2...v5.0.3) (2021-08-31)\n\n\n### Bug Fixes\n\n* remove unused import ([8c1247a](https://github.com/5alidz/tiny-schema-validator/commit/8c1247a5be26a40209e584c7ea856db85a9896fd))\n* report unknown keys found in data on validation ([#2](https://github.com/5alidz/tiny-schema-validator/issues/2)) ([08a4e5c](https://github.com/5alidz/tiny-schema-validator/commit/08a4e5cda8a01710d8964db9ab199fc94d6e23e1))\n* test and fix unknown keys ([aff85e1](https://github.com/5alidz/tiny-schema-validator/commit/aff85e1d3177a2db7a9c1c5947820db0a3521329))\n\n### [5.0.2](https://github.com/5alidz/tiny-schema-validator/compare/v5.0.1...v5.0.2) (2021-08-28)\n\n### [5.0.1](https://github.com/5alidz/tiny-schema-validator/compare/v5.0.0...v5.0.1) (2021-08-28)\n\n## [5.0.0](https://github.com/5alidz/tiny-schema-validator/compare/v4.0.0...v5.0.0) (2021-08-23)\n\n\n### ⚠ BREAKING CHANGES\n\n* - replace \"traverse\" with \"source\" so you can parse the schema however you want,\nalso to add \"atomic\" validations\n\n* remove traverse, and replace it with direct validation ([513c682](https://github.com/5alidz/tiny-schema-validator/commit/513c682acd3d1845acbc73c44f52ed766e5039a5))\n\n## [4.0.0](https://github.com/5alidz/tiny-schema-validator/compare/v3.1.0...v4.0.0) (2021-08-21)\n\n\n### ⚠ BREAKING CHANGES\n\n* when traversing the schema and the returned value is null, the result will contain\n'invalid-type' message\n\n### Features\n\n* add constant and union validators ([7859392](https://github.com/5alidz/tiny-schema-validator/commit/7859392dffdda3bc7adeaa6fc5f6df6085b2d5a1))\n* include the schema source on the schema api ([14c1ecf](https://github.com/5alidz/tiny-schema-validator/commit/14c1ecf2e8bf0e7e9429bb34c6c58e88199bb4c2))\n\n## [3.1.0](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.5...v3.1.0) (2021-07-18)\n\n\n### Features\n\n* add latest version of typescript ([2e8a339](https://github.com/5alidz/tiny-schema-validator/commit/2e8a339d392bb229d52f79ded5f563e1953e2dc6))\n\n### [3.0.5](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.4...v3.0.5) (2021-04-05)\n\n\n### Bug Fixes\n\n* narrow down types in case of {} ([5cf59cf](https://github.com/5alidz/tiny-schema-validator/commit/5cf59cfaf5b6b851e0adbce055dd6fb364ccc8c6))\n\n### [3.0.4](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.3...v3.0.4) (2021-04-05)\n\n\n### Bug Fixes\n\n* fix shape validators recursion even when optional ([994cef6](https://github.com/5alidz/tiny-schema-validator/commit/994cef6229b2accc49c46972e94ef5b41fe8d275))\n* move data checking outside the loop ([2d40ec1](https://github.com/5alidz/tiny-schema-validator/commit/2d40ec1a10633d5a20c738e1f44f2da9acbcd7a9))\n* optimize types for strict traverse ([4fd3d26](https://github.com/5alidz/tiny-schema-validator/commit/4fd3d26f7e6380a2d9a23ffcbb9804a1d166075b))\n* throw TypeError on produce invalid-data ([0462755](https://github.com/5alidz/tiny-schema-validator/commit/04627558e7007ac39622aa085b3380f54997a750))\n\n### [3.0.3](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.1...v3.0.3) (2021-04-02)\n\n\n### Bug Fixes\n\n* **types:** fix infered schema.embed ([ade04e0](https://github.com/5alidz/tiny-schema-validator/commit/ade04e0684dd0d4cdd1887a32c74ce6542913e90))\n\n### [3.0.2](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.1...v3.0.2) (2021-04-02)\n\n\n### Bug Fixes\n\n* **types:** fix infered schema.embed ([ade04e0](https://github.com/5alidz/tiny-schema-validator/commit/ade04e0684dd0d4cdd1887a32c74ce6542913e90))\n\n### [3.0.1](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.0...v3.0.1) (2021-03-28)\n\n## [3.0.0](https://github.com/5alidz/tiny-schema-validator/compare/v3.0.0-alpha.0...v3.0.0) (2021-03-26)\n\n\n### Bug Fixes\n\n* helper better type support & insure primitives is required ([ab37494](https://github.com/5alidz/tiny-schema-validator/commit/ab374945dd1b439701161f38b183b7c1d4fecd7a))\n\n## [3.0.0-alpha.0](https://github.com/5alidz/tiny-schema-validator/compare/v2.1.0-alpha.3...v3.0.0-alpha.0) (2021-03-26)\n\n\n### ⚠ BREAKING CHANGES\n\n* infers data type automatically for both JS & TS\n\n### Features\n\n* implement better type inference | less work for the user ([a827591](https://github.com/5alidz/tiny-schema-validator/commit/a827591a8ce525b8f32d08e99ffdb8f8f9657485))\n\n\n### Bug Fixes\n\n* fix validator circular refernce ([f922048](https://github.com/5alidz/tiny-schema-validator/commit/f922048af6faca4389e7d0abfd5c35097946e916))\n\n## [2.1.0-alpha.3](https://github.com/5alidz/tiny-schema-validator/compare/v2.1.0-alpha.2...v2.1.0-alpha.3) (2021-03-19)\n\n## [2.1.0-alpha.2](https://github.com/5alidz/tiny-schema-validator/compare/v2.1.0-alpha.1...v2.1.0-alpha.2) (2021-03-18)\n\n\n### Bug Fixes\n\n* expose validatorTypes with index.d.ts ([796990d](https://github.com/5alidz/tiny-schema-validator/commit/796990d543de176332973ef198b33e5d8a48ea1d))\n\n## [2.1.0-alpha.1](https://github.com/5alidz/tiny-schema-validator/compare/v2.1.0-alpha.0...v2.1.0-alpha.1) (2021-03-11)\n\n\n### Bug Fixes\n\n* expose path as array of strings ([f304909](https://github.com/5alidz/tiny-schema-validator/commit/f304909c9d06bf118cf9d33bc0bfa2043f8ff424))\n* remove repeated parent path ([ecd7efa](https://github.com/5alidz/tiny-schema-validator/commit/ecd7efa427156c5e56c5a225975451bf467699cc))\n\n## [2.1.0-alpha.0](https://github.com/5alidz/tiny-schema-validator/compare/v2.0.1-alpha.3...v2.1.0-alpha.0) (2021-03-10)\n\n\n### Features\n\n* expose correct path ([2fa69ae](https://github.com/5alidz/tiny-schema-validator/commit/2fa69ae08c6c95ee76afe60c07da1c060a726208))\n\n### [2.0.1-alpha.3](https://github.com/5alidz/tiny-schema-validator/compare/v2.0.1-alpha.2...v2.0.1-alpha.3) (2021-03-10)\n\n\n### Bug Fixes\n\n* expose parentkey ([d6d4302](https://github.com/5alidz/tiny-schema-validator/commit/d6d43028f858983b4f74cfeb2908693c56465ded))\n\n### [2.0.1-alpha.2](https://github.com/5alidz/tiny-schema-validator/compare/v2.0.1-alpha.1...v2.0.1-alpha.2) (2021-03-10)\n\n\n### Bug Fixes\n\n* prettier not supporting export * as ([0bc2a29](https://github.com/5alidz/tiny-schema-validator/commit/0bc2a2960cdee7c135a1fd57245c1892e2b7293d))\n* recordof traverse using index instead of key ([9606738](https://github.com/5alidz/tiny-schema-validator/commit/96067381a3115d087f2633dfa7291d238eb01243))\n\n### [2.0.1-alpha.1](https://github.com/5alidz/tiny-schema-validator/compare/v2.0.1-alpha.0...v2.0.1-alpha.1) (2021-03-06)\n\n\n### Bug Fixes\n\n* better type inference ([a233541](https://github.com/5alidz/tiny-schema-validator/commit/a233541bd1337fc289427046bac02d5b804e15a8))\n\n### [2.0.1-alpha.0](https://github.com/5alidz/tiny-schema-validator/compare/v2.0.0...v2.0.1-alpha.0) (2021-03-06)\n\n\n### Bug Fixes\n\n* infer errors types ([6ea7d1f](https://github.com/5alidz/tiny-schema-validator/commit/6ea7d1f9ac62aabff78e627d1133c947f10e0d95))\n\n## [2.0.0](https://github.com/5alidz/tiny-schema-validator/compare/v1.0.5...v2.0.0) (2021-03-04)\n\n\n### ⚠ BREAKING CHANGES\n\n* renamed recordOf -> recordof\n\n* change helpers names to match docs ([65a1f29](https://github.com/5alidz/tiny-schema-validator/commit/65a1f298323d397d7399933252b2022bdacc784a))\n\n### [1.0.5](https://github.com/5alidz/tiny-schema-validator/compare/v1.0.4...v1.0.5) (2021-03-02)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 khaled\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Tiny Schema Validator\n\nJSON schema validator with excellent type inference for JavaScript and TypeScript.\n\n[![GitHub license](https://img.shields.io/github/license/5alidz/tiny-schema-validator)](https://github.com/5alidz/tiny-schema-validator/blob/master/LICENSE) ![Minzipped size](https://img.shields.io/bundlephobia/minzip/tiny-schema-validator.svg)\n\n## Installation\n\n```sh\nnpm install tiny-schema-validator\n# or\nyarn add tiny-schema-validator\n```\n\n## Usage\n\n### Creating a schema\n\n```js\nimport { createSchema, _ } from 'tiny-schema-validator';\n\nexport const User = createSchema({\n  metadata: _.record({\n    date_created: _.number(),\n    id: _.string(),\n  }),\n  profile: _.record({\n    name: _.string({\n      maxLength: [100, 'too-long'],\n      minLength: [2, 'too-short'],\n    }),\n    age: _.number({\n      max: [150, 'too-old'],\n      min: [13, 'too-young'],\n    }),\n    email: _.string({\n      pattern: [/^[^@]+@[^@]+\\.[^@]+$/, 'invalid-email'],\n    }),\n  }),\n  payment_status: _.union(\n    _.constant('pending'),\n    _.constant('failed'),\n    _.constant('success'),\n    _.constant('canceled')\n  ),\n});\n```\n\nand in TypeScript, everything is the same, but to get the data type inferred from the schema, you can do this:\n\n```ts\n/*\n  UserType {\n    metadata: {\n      date_created: number;\n      id: string;\n    };\n    profile: {\n      name: string;\n      age: number;\n      email: string;\n    };\n    payment_status: 'pending' | 'failed' | 'success' | 'canceled';\n  }\n*/\nexport type UserType = ReturnType<typeof User.produce>;\n```\n\n### Using the schema\n\nWhen you create a schema, you will get a nice API to handle multiple use-cases in the client and the server.\n\n- `is(data: any): boolean` check if the data is valid (eager evaluation)\n- `validate(data: any): Errors` errors returned has the same shape as the schema you defined (does not throw)\n- `produce(data: any): data` throws an error when the data is invalid. otherwise, it returns data\n- `embed(config?: { optional: boolean })` embeds the schema in other schemas\n- `source` the schema itself in a parsable format\n\nexample usage:\n\n```js\nconst Person = createSchema({\n  name: _.string(),\n  age: _.number(),\n  email: _.string(),\n});\n\nconst john = { name: 'john', age: 42, email: 'john@gmail.com' };\nPerson.is({}); // false\nPerson.is(john); // true\n\nPerson.validate({}); // { name: 'invalid-type', age: 'invalid-type', email: 'invalid-type' }\nPerson.validate(john); // null\n\ntry {\n  Person.produce(undefined);\n} catch (e) {\n  console.log(e instanceof TypeError); // true\n  console.log(e.message); // \"invalid-data\"\n}\n\n// embedding the person schema\nconst GroupOfPeople = createSchema({\n  // ...\n  people: _.listof(Person.embed()),\n  // ...\n});\n```\n\n## Validators\n\nAll validators are required by default.\nAll validators are accessible with the `_` (underscore) namespace; The reason for using `_` instead of a good name like `validators` is developer experience, and you can alias it to whatever you want.\n\n```js\nimport { _ as validators } from 'tiny-schema-validator';\n```\n\nExample of all validators and corresponding Typescript types:\n\n<!-- prettier-ignore -->\n```js\nimport { _ } from 'tiny-schema-validator';\n\n// NOTE: when you call a validator you just create an object \n// containing { type: '<type of validator>', ...options }\n// this is just a shorthand for that.\n\n// simple validators.\n_.string(); // string\n_.number(); // number\n_.boolean(); // boolean\n_.constant(42); // 42\n\n// complex validators (types that accepts other types as paramater)\n_.union(\n  _.record({ id: _.string() }),\n  _.constant(1),\n  _.constant(2),\n  _.constant(3)\n); // { id: string; } | 1 | 2 | 3\n\n_.list([\n  _.number(),\n  _.string(),\n]); // [number, number]\n_.record({\n  timestamp: _.number(),\n  id: _.string(),\n}); // { timestamp: number; id: string; }\n\n_.listof(_.string()); // string[]\n_.recordof(_.string()); // Record<string, string>\n```\n\nCheck out the full validators API below:\n\n| validator | signature                       | props                                                          |\n| :-------- | ------------------------------- | :------------------------------------------------------------- |\n|           |                                 |                                                                |\n| constant  | `constant(value)`               | value: `string \\| number \\| boolean`                           |\n|           |                                 |                                                                |\n| string    | `string(options?)`              | options (optional): Object                                     |\n|           |                                 | - `optional : boolean` defaults to false                       |\n|           |                                 | - `maxLength: [length: number, error: string]`                 |\n|           |                                 | - `minLength: [length: number, error: string]`                 |\n|           |                                 | - `pattern : [pattern: RegExp, error: string]`                 |\n|           |                                 |                                                                |\n| number    | `number(options?)`              | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n|           |                                 | - `min: [number, error: string]`                               |\n|           |                                 | - `max: [number, error: string]`                               |\n|           |                                 | - `is : ['integer' \\| 'float', error: string]` default is both |\n|           |                                 |                                                                |\n| boolean   | `boolean(options?)`             | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n|           |                                 |                                                                |\n| union     | `union(...validators)`          | validators: Array of validators as paramaters                  |\n|           |                                 |                                                                |\n| list      | `list(validators[], options?)`  | validators: Array of validators                                |\n|           |                                 | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n|           |                                 |                                                                |\n| listof    | `listof(validator, options?)`   | validator: Validator                                           |\n|           |                                 | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n|           |                                 |                                                                |\n| record    | `record(shape, options?)`       | shape: `Object { [key: string]: Validator }`                   |\n|           |                                 | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n|           |                                 |                                                                |\n| recordof  | `recordof(validator, options?)` | validator: `Validator`                                         |\n|           |                                 | options(optional): Object                                      |\n|           |                                 | - `optional: boolean` default to false                         |\n\n### Custom validators\n\nTo create custom validators that do not break type inference:\n\n- use validators from `_` as building blocks for your custom validator.\n- your custom validator should define `optional` and `required` functions.\n\nExample of creating custom validators:\n\n```js\nconst alphaNumeric = (() => {\n  const config = {\n    pattern: [/^[a-zA-Z0-9]*$/, 'only-letters-and-number'],\n  };\n  return {\n    required: additional => _.string({ ...additional, ...config, optional: false }), // inferred as Required\n    optional: additional => _.string({ ...additional, ...config, optional: true }), // inferred as Optional\n  };\n})();\n\nconst Person = createSchema({\n  // ...\n  username: alphaNumeric.required({ maxLength: [20, 'username-too-long'] }),\n  // ...\n});\n```\n\n## Built-in Errors\n\n```js\n// when typeof value does not match the validator infered type\nconst TYPEERR = 'invalid-type';\n\n// when \"schema\" in createSchema(schema) is not plain object\nconst SCHEMAERR = 'invalid-schema';\n\n// when produce(data) and \"data\" failed to match the schema\n// always accompanied be TypeError, so make sure to catch it\nconst DATAERR = 'invalid-data';\n\n// when an unknown key is found in data while using record | list\n// and any keys that exists on data and not present in the schema\nconst UNKOWN_KEY_ERR = 'unknown-key';\n```\n\n## Caveats\n\n- When using the `recordof | listof | list` validators, the optional property of the validator is ignored, example:\n\n```js\n_.recordof(_.string({ optional: true /* THIS IS IGNORED */ }));\n_.list([_.number({ optional: true /* THIS IS IGNORED */ }), _.number()]);\n```\n\n- You might expect errors returned from a `list | listof` validators to be an array but it is actually an object, example:\n\n```js\nconst list = createSchema({ list: _.listof(_.string()) });\nlist.validate({ list: ['string', 42, 'string'] }); // { list: { 1: 'invalid-type' } }\n```\n\n## Recursive types\n\nCurrently, there's no easy way to create recursive types. if you think you could help, PRs are welcome\n\n## Errors while wrapping schemas\n\nif you try to wrap your schema, you will encounter this error (Type instantiation is excessively deep and possibly infinite)\nto fix it, you should unwrap your schema and re-create it inside your abstraction.\nlet's take the following example:\n\n```ts\nconst User = createSchema({\n  name: _.string(),\n  age: _.number(),\n});\n\n// your abstraction\nfunction schemaWrapper<T>(schema: T) {\n  //...\n}\n\nconst wrappedUser = schemaWrapper(User); // ERROR: Type instantiation is excessively deep and possibly infinite\n```\n\nThe fix:\n\n```ts\nimport { Schema, R, RecordOptions } from 'tiny-schema-validator';\n/*\noptionally, to infer data from the embedded schema, you do DataFrom<T>\n\nimport { DataFrom } from 'tiny-schema-validator/dist/type-utils';\n*/\n\n// extract schema with Schema.embed()\nfunction schemaWrapper<T extends Schema>(schema: R<RecordOptions<T>>) {\n  const newSchema = createSchema(schema.shape); // you can add/remove/modify passed schema here\n  // ...\n}\n\nconst wrappedUser = schemaWrapper(User.embed()); // all good no errors\n```\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"version\": \"5.0.3\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"typings\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"src\"\n  ],\n  \"engines\": {\n    \"node\": \">=10\"\n  },\n  \"scripts\": {\n    \"start\": \"tsdx watch\",\n    \"build\": \"tsdx build\",\n    \"test\": \"tsdx test\",\n    \"lint\": \"tsdx lint\",\n    \"prepare\": \"tsdx build\",\n    \"size\": \"size-limit\",\n    \"analyze\": \"size-limit --why\",\n    \"release\": \"standard-version && git push --follow-tags origin master\",\n    \"release:alpha\": \"yarn release -- --prerelease alpha\"\n  },\n  \"peerDependencies\": {},\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"tsdx lint\"\n    }\n  },\n  \"prettier\": {\n    \"printWidth\": 100,\n    \"semi\": true,\n    \"singleQuote\": true,\n    \"trailingComma\": \"es5\"\n  },\n  \"name\": \"tiny-schema-validator\",\n  \"author\": \"khaled\",\n  \"repository\": {\n    \"url\": \"https://github.com/5alidz/tiny-schema-validator\"\n  },\n  \"module\": \"dist/tiny-schema-validator.esm.js\",\n  \"size-limit\": [\n    {\n      \"path\": \"dist/tiny-schema-validator.cjs.production.min.js\",\n      \"limit\": \"10 KB\"\n    },\n    {\n      \"path\": \"dist/tiny-schema-validator.esm.js\",\n      \"limit\": \"10 KB\"\n    }\n  ],\n  \"resolutions\": {\n    \"**/typescript\": \"^4.0.5\",\n    \"**/@typescript-eslint/eslint-plugin\": \"^4.6.1\",\n    \"**/@typescript-eslint/parser\": \"^4.6.1\"\n  },\n  \"jest\": {\n    \"coverageReporters\": [\n      \"json-summary\",\n      \"text\",\n      \"lcov\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@size-limit/preset-small-lib\": \"^4.9.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.8.2\",\n    \"@typescript-eslint/parser\": \"^4.8.2\",\n    \"cz-conventional-changelog\": \"3.3.0\",\n    \"husky\": \"^4.3.0\",\n    \"size-limit\": \"^4.9.0\",\n    \"standard-version\": \"^9.1.1\",\n    \"tsdx\": \"^0.14.1\",\n    \"tslib\": \"^2.3.1\",\n    \"typescript\": \"^4.3.5\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  },\n  \"dependencies\": {\n    \"tiny-invariant\": \"^1.1.0\"\n  }\n}\n"
  },
  {
    "path": "src/constants.ts",
    "content": "export const $string = 'string';\nexport const $number = 'number';\nexport const $boolean = 'boolean';\nexport const $list = 'list';\nexport const $listof = 'listof';\nexport const $record = 'record';\nexport const $recordof = 'recordof';\nexport const $constant = 'constant';\nexport const $union = 'union';\n\nexport const TYPEERR = 'invalid-type';\nexport const SCHEMAERR = 'invalid-schema';\nexport const DATAERR = 'invalid-data';\nexport const UNKOWN_KEY_ERR = 'unknown-key';\n"
  },
  {
    "path": "src/createErrors.ts",
    "content": "import { isPlainObject, isNumber, isString, isBool, ObjectKeys, toObj, isArray } from './utils';\nimport {\n  BooleanValidator,\n  ConstantValidator,\n  ListofValidator,\n  ListValidator,\n  NumberValidator,\n  RecordofValidator,\n  RecordValidator,\n  Schema,\n  StringValidator,\n  UnionValidator,\n  Validator,\n} from './validatorTypes';\nimport { InferResult, InferCallbackResult } from './type-utils';\nimport { TYPEERR, UNKOWN_KEY_ERR } from './constants';\nimport invariant from 'tiny-invariant';\n\nfunction shouldAddToResult(res: unknown) {\n  if (\n    res == null ||\n    (isPlainObject(res) && ObjectKeys(res).length < 1) ||\n    (Array.isArray(res) && res.length < 1)\n  ) {\n    return false;\n  }\n  return true;\n}\n\nfunction shouldSkipValidation(value: unknown, validator: Validator) {\n  return value == null && Boolean(validator.optional);\n}\n\nfunction normalizeResult<T extends Record<string, any>>(result: T) {\n  return ObjectKeys(result).length <= 0 ? null : result;\n}\n\nfunction enterNode(validator: Validator, value: unknown, eager = false) {\n  const fn = validators[validator.type] as any;\n  invariant(typeof fn == 'function', 'invalid-validator-type');\n  return fn(validator, value, eager);\n}\n\nfunction parseShapeValidator(\n  validator: RecordValidator<any> | ListValidator<any[]>,\n  value: unknown,\n  eager = false\n) {\n  const shape = toObj(validator).shape;\n  const keys = ObjectKeys(shape);\n  const values = toObj(value);\n  const result: Record<string, any> = {};\n  const dataKeys = ObjectKeys(values);\n\n  for (let i = 0; i < dataKeys.length; i++) {\n    const key = dataKeys[i];\n    if (!keys.includes(key)) {\n      // we gonna sneak this one in the result without typescript knowning about it.\n      // can be fixed if we require \"data\" to be also infered\n      // (instead of using any in e.g. schema.validate(data))\n      result[key] = UNKOWN_KEY_ERR as any;\n      if (eager) return result;\n    }\n  }\n\n  for (let i = 0; i < keys.length; i++) {\n    const currentResult = enterNode(shape[keys[i]], values[keys[i]]);\n    if (shouldAddToResult(currentResult)) {\n      result[keys[i]] = currentResult;\n      if (eager) return result;\n    }\n  }\n  return normalizeResult(result);\n}\n\nfunction parseOfValidator(\n  validator: RecordofValidator<any> | ListofValidator<any>,\n  value: unknown,\n  eager = false\n) {\n  const values = toObj(value);\n  const keys = ObjectKeys(values);\n  const result: Record<string, any> = {};\n  for (let i = 0; i < keys.length; i++) {\n    const currentResult = enterNode(validator.of, values[keys[i]]);\n    if (shouldAddToResult(currentResult)) {\n      result[keys[i]] = currentResult;\n      if (eager) return result;\n    }\n  }\n  return normalizeResult(result);\n}\n\nconst validators = {\n  string(validator: StringValidator, value: unknown) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isString(value)) return TYPEERR;\n\n    const [minLength, minLengthErrMsg] = validator.minLength ? validator.minLength : [];\n    if (minLength && minLengthErrMsg && isNumber(minLength) && value.length < minLength)\n      return minLengthErrMsg;\n\n    const [maxLength, maxLengthErrMsg] = validator.maxLength ? validator.maxLength : [];\n    if (maxLength && maxLengthErrMsg && isNumber(maxLength) && value.length > maxLength)\n      return maxLengthErrMsg;\n\n    const [pattern, patterErrMsg] = validator.pattern ? validator.pattern : [];\n    if (pattern && patterErrMsg && pattern.test(value) == false) return patterErrMsg;\n\n    return null;\n  },\n  number(validator: NumberValidator, value: unknown) {\n    if (shouldSkipValidation(value, validator)) return null;\n\n    if (!isNumber(value)) return TYPEERR;\n\n    const [min, minErrMsg] = validator.min ? validator.min : [];\n    if (isNumber(min) && value < min && minErrMsg) return minErrMsg;\n\n    const [max, maxErrMsg] = validator.max ? validator.max : [];\n    if (isNumber(max) && value > max && maxErrMsg) return maxErrMsg;\n\n    const [is, isErrMsg] = validator.is ? validator.is : [];\n    if (isString(is) && isErrMsg) {\n      const isInt = Number.isInteger(value);\n      if ((isInt && is == 'float') || (!isInt && is == 'integer')) return isErrMsg;\n    }\n\n    return null;\n  },\n  boolean(validator: BooleanValidator, value: unknown) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isBool(value)) return TYPEERR;\n    return null;\n  },\n  constant<T extends string | number | boolean>(validator: ConstantValidator<T>, value: unknown) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (value === validator.value) return null;\n    return TYPEERR;\n  },\n  union<T extends Validator[]>(validator: UnionValidator<T>, value: unknown) {\n    if (shouldSkipValidation(value, validator)) return null;\n    const unionTypes = validator.of;\n    let currentResult = null;\n    for (let i = 0; i < unionTypes.length; i++) {\n      currentResult = enterNode(unionTypes[i], value);\n      if (currentResult == null) return null;\n    }\n    return TYPEERR;\n  },\n  list<T extends Validator[]>(validator: ListValidator<T>, value: unknown, eager = false) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isArray(value)) return TYPEERR;\n    return parseShapeValidator(validator, value, eager);\n  },\n  listof<T extends Validator>(validator: ListofValidator<T>, value: unknown, eager = false) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isArray(value)) return TYPEERR;\n    return parseOfValidator(validator, value, eager);\n  },\n  record<T extends Schema>(validator: RecordValidator<T>, value: unknown, eager = false) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isPlainObject(value)) return TYPEERR;\n    return parseShapeValidator(validator, value, eager);\n  },\n  recordof<T extends Validator>(validator: RecordofValidator<T>, value: unknown, eager = false) {\n    if (shouldSkipValidation(value, validator)) return null;\n    if (!isPlainObject(value)) return TYPEERR;\n    return parseOfValidator(validator, value, eager);\n  },\n};\n\nexport function createErrors<T extends Schema>(\n  schema: T,\n  _data: any,\n  eager = false\n): null | InferResult<T> {\n  const data = isPlainObject(_data) ? _data : {};\n  const result: InferResult<T> = {};\n  const schemaKeys = ObjectKeys(schema) as (keyof T)[];\n  const dataKeys = ObjectKeys(data);\n\n  // get unknown keys\n  for (let i = 0; i < dataKeys.length; i++) {\n    const key = dataKeys[i];\n    if (!schemaKeys.includes(key)) {\n      // we gonna sneak this one in the result without typescript knowning about it.\n      // can be fixed if we require \"data\" to be also infered\n      // (instead of using any in e.g. schema.validate(data: any))\n      result[key as keyof T] = UNKOWN_KEY_ERR as any;\n      if (eager) return result;\n    }\n  }\n\n  for (let i = 0; i < schemaKeys.length; i++) {\n    const schemaKey = schemaKeys[i];\n    const validator = schema[schemaKey];\n    const value = data[schemaKey as string];\n    let _result = enterNode(validator, value, eager);\n    if (shouldAddToResult(_result)) {\n      result[schemaKey] = _result as InferCallbackResult<typeof validator>;\n      if (eager) return result;\n    }\n  }\n  return normalizeResult(result);\n}\n"
  },
  {
    "path": "src/createSchema.ts",
    "content": "import { isPlainObject } from './utils';\nimport { createErrors } from './createErrors';\nimport { DATAERR, $record, SCHEMAERR } from './constants';\nimport invariant from 'tiny-invariant';\nimport { RecordValidator, Schema, R, O, RecordOptions } from './validatorTypes';\nimport { DataFrom } from './type-utils';\n\nexport function createSchema<T extends Schema>(_schema: T) {\n  invariant(isPlainObject(_schema), SCHEMAERR);\n\n  type Data = DataFrom<T>;\n  const source = Object.freeze({ ..._schema });\n\n  function validate(data: any, eager = false) {\n    return createErrors(source, data, eager);\n  }\n\n  function is(data: any): data is Data {\n    if (!isPlainObject(data)) return false;\n    return validate(data, true) == null;\n  }\n\n  function embed(): R<RecordOptions<T>>;\n  function embed(config: { optional: false }): R<RecordOptions<T>>;\n  function embed(config: { optional: true }): O<RecordOptions<T>>;\n  function embed(config = { optional: false }): RecordValidator<T> {\n    return { type: $record, shape: source, ...config };\n  }\n\n  function produce(data: any): Data {\n    if (!is(data)) throw new TypeError(DATAERR);\n    return data;\n  }\n\n  return {\n    source,\n    validate,\n    embed,\n    produce,\n    is,\n  };\n}\n"
  },
  {
    "path": "src/helpers.ts",
    "content": "import {\n  O,\n  R,\n  BooleanValidator,\n  ListValidator,\n  ListofValidator,\n  NumberValidator,\n  RecordValidator,\n  RecordofValidator,\n  Schema,\n  StringValidator,\n  Validator,\n  BooleanOptions,\n  ListOptions,\n  ListofOptions,\n  NumberOptions,\n  RecordOptions,\n  RecordofOptions,\n  StringOptions,\n  ConstantOptions,\n  UnionOptions,\n} from './validatorTypes';\nimport {\n  $boolean,\n  $constant,\n  $list,\n  $listof,\n  $number,\n  $record,\n  $recordof,\n  $string,\n  $union,\n} from './constants';\n\nexport function string(): R<StringOptions>;\nexport function string(config: Omit<StringOptions, 'type'>): R<StringOptions>;\nexport function string(config: { optional: false } & Omit<StringOptions, 'type'>): R<StringOptions>;\nexport function string(config: { optional: true } & Omit<StringOptions, 'type'>): O<StringOptions>;\n\nexport function string(\n  config?: { optional?: boolean } & Omit<StringOptions, 'type'>\n): StringValidator {\n  return {\n    type: $string,\n    optional: !!config?.optional,\n    ...config,\n  };\n}\n\nexport function number(): R<NumberOptions>;\nexport function number(config: Omit<NumberOptions, 'type'>): R<NumberOptions>;\nexport function number(config: { optional: true } & Omit<NumberOptions, 'type'>): O<NumberOptions>;\nexport function number(config: { optional: false } & Omit<NumberOptions, 'type'>): R<NumberOptions>;\n\nexport function number(\n  config?: { optional?: boolean } & Omit<NumberOptions, 'type'>\n): NumberValidator {\n  return {\n    type: $number,\n    optional: !!config?.optional,\n    ...config,\n  };\n}\n\nexport function boolean(): R<BooleanOptions>;\nexport function boolean(config: { optional: true }): O<BooleanOptions>;\nexport function boolean(config: { optional: false }): R<BooleanOptions>;\n\nexport function boolean(config?: { optional: boolean }): BooleanValidator {\n  return {\n    type: $boolean,\n    optional: !!config?.optional,\n  };\n}\n\nexport function list<T extends R<Validator>[]>(list: T): R<ListOptions<T>>;\nexport function list<T extends R<Validator>[]>(\n  list: T,\n  config: { optional: false }\n): R<ListOptions<T>>;\nexport function list<T extends R<Validator>[]>(\n  list: T,\n  config: { optional: true }\n): O<ListOptions<T>>;\n\nexport function list<T extends R<Validator>[]>(\n  list: T,\n  config?: { optional: boolean }\n): ListValidator<T> {\n  return {\n    type: $list,\n    optional: !!config?.optional,\n    shape: list.map(v => ({ ...v, optional: false })) as T,\n  };\n}\n\nexport function listof<T extends R<Validator>>(v: T): R<ListofOptions<T>>;\nexport function listof<T extends R<Validator>>(\n  v: T,\n  config: { optional: false }\n): R<ListofOptions<T>>;\nexport function listof<T extends R<Validator>>(\n  v: T,\n  config: { optional: true }\n): O<ListofOptions<T>>;\n\nexport function listof<T extends R<Validator>>(\n  v: T,\n  config?: { optional: boolean }\n): ListofValidator<T> {\n  return {\n    type: $listof,\n    optional: !!config?.optional,\n    of: { ...v, optional: false },\n  };\n}\n\nexport function record<T extends Schema>(s: T): R<RecordOptions<T>>;\nexport function record<T extends Schema>(s: T, config: { optional: false }): R<RecordOptions<T>>;\nexport function record<T extends Schema>(s: T, config: { optional: true }): O<RecordOptions<T>>;\n\nexport function record<T extends Schema>(s: T, config?: { optional: boolean }): RecordValidator<T> {\n  return {\n    type: $record,\n    optional: !!config?.optional,\n    shape: s,\n  };\n}\n\nexport function recordof<T extends R<Validator>>(v: T): R<RecordofOptions<T>>;\nexport function recordof<T extends R<Validator>>(\n  v: T,\n  config: { optional: false }\n): R<RecordofOptions<T>>;\nexport function recordof<T extends R<Validator>>(\n  v: T,\n  config: { optional: true }\n): O<RecordofOptions<T>>;\n\nexport function recordof<T extends R<Validator>>(\n  v: T,\n  config?: { optional: boolean }\n): RecordofValidator<T> {\n  return {\n    type: $recordof,\n    of: { ...v, optional: false },\n    optional: !!config?.optional,\n  };\n}\n\nexport function constant<T extends string | number | boolean>(v: T): R<ConstantOptions<T>> {\n  return {\n    type: $constant,\n    optional: false,\n    value: v,\n  };\n}\n\nexport function union<T extends R<Validator>[]>(...types: T): R<UnionOptions<T>> {\n  return {\n    type: $union,\n    optional: false,\n    of: types,\n  };\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import * as helpers from './helpers';\n\nexport * from './validatorTypes';\nexport * from './createSchema';\nexport const _ = helpers;\n"
  },
  {
    "path": "src/type-utils.ts",
    "content": "import {\n  O,\n  BooleanValidator,\n  ListValidator,\n  ListofValidator,\n  NumberValidator,\n  RecordValidator,\n  RecordofValidator,\n  StringValidator,\n  Schema,\n  ConstantValidator,\n  UnionValidator,\n  Validator,\n} from './validatorTypes';\n\ntype InferTypeWithOptional<T, U> = T extends O<T> ? U | undefined : U;\n\ntype ArrayElement<T> = T extends readonly unknown[]\n  ? T extends readonly (infer ElementType)[]\n    ? ElementType\n    : never\n  : never;\n\ntype InferDataType<T> = T extends UnionValidator<infer U>\n  ? ArrayElement<InferTypeWithOptional<T, { [K in keyof U]: InferDataType<U[K]> }>>\n  : T extends ConstantValidator<infer U>\n  ? U\n  : T extends StringValidator\n  ? InferTypeWithOptional<T, string>\n  : T extends NumberValidator\n  ? InferTypeWithOptional<T, number>\n  : T extends BooleanValidator\n  ? InferTypeWithOptional<T, boolean>\n  : T extends ListValidator<infer U>\n  ? InferTypeWithOptional<T, { [K in keyof U]: InferDataType<U[K]> }>\n  : T extends ListofValidator<infer V>\n  ? InferTypeWithOptional<T, InferDataType<V>[]>\n  : T extends RecordValidator<infer S>\n  ? InferTypeWithOptional<T, { [K in keyof S]: InferDataType<S[K]> }>\n  : T extends RecordofValidator<infer V>\n  ? InferTypeWithOptional<T, { [key: string]: InferDataType<V> }>\n  : never;\n\nexport type DataFrom<S extends Schema> = {\n  [K in keyof S]: InferDataType<S[K]>;\n};\n\nexport type InferCallbackResult<V extends Validator> = V extends\n  | StringValidator\n  | NumberValidator\n  | BooleanValidator\n  | ConstantValidator<any>\n  | UnionValidator<any>\n  ? string\n  : V extends ListValidator<infer U>\n  ? { [key in number]?: InferCallbackResult<U[key]> }\n  : V extends ListofValidator<infer U>\n  ? { [key: number]: InferCallbackResult<U> | undefined }\n  : V extends RecordValidator<infer U>\n  ? { [key in keyof U]?: InferCallbackResult<U[key]> }\n  : V extends RecordofValidator<infer U>\n  ? { [key: string]: InferCallbackResult<U> | undefined }\n  : never;\n\nexport type InferResult<S extends Schema> = {\n  [key in keyof S]?: InferCallbackResult<S[key]>;\n};\n"
  },
  {
    "path": "src/utils.ts",
    "content": "export const ObjectKeys = Object.keys.bind(Object);\nexport const isArray = (value: unknown): value is any[] => Array.isArray(value);\nexport const isBool = (value: unknown): value is boolean => typeof value == 'boolean';\nexport const isString = (value: unknown): value is string => typeof value == 'string';\nexport const isNumber = (value: unknown): value is number =>\n  typeof value == 'number' && Number.isFinite(value);\n\nexport function isPlainObject(maybeObject: any): maybeObject is Record<string, any> {\n  return (\n    typeof maybeObject == 'object' &&\n    maybeObject != null &&\n    Object.prototype.toString.call(maybeObject) == '[object Object]'\n  );\n}\n\nexport function toObj(value: any) {\n  return isArray(value) ? { ...value } : isPlainObject(value) ? value : ({} as Record<string, any>);\n}\n"
  },
  {
    "path": "src/validatorTypes.ts",
    "content": "export type O<V> = V & { optional: true };\nexport type R<V> = V & { optional: false };\nexport type V<T> = O<T> | R<T>;\n\nexport interface StringOptions {\n  type: 'string';\n  maxLength?: [number, string];\n  minLength?: [number, string];\n  pattern?: [RegExp, string];\n}\n\nexport interface NumberOptions {\n  type: 'number';\n  max?: [number, string];\n  min?: [number, string];\n  is?: ['integer' | 'float', string];\n}\n\nexport interface BooleanOptions {\n  type: 'boolean';\n}\n\nexport interface ListOptions<T> {\n  type: 'list';\n  shape: T;\n}\n\nexport interface ListofOptions<T> {\n  type: 'listof';\n  of: T;\n}\n\nexport interface RecordOptions<T> {\n  type: 'record';\n  shape: T;\n}\n\nexport interface RecordofOptions<T> {\n  type: 'recordof';\n  of: T;\n}\n\nexport interface ConstantOptions<T> {\n  type: 'constant';\n  value: T;\n}\n\nexport interface UnionOptions<T> {\n  type: 'union';\n  of: T;\n}\n\nexport type BooleanValidator = V<BooleanOptions>;\nexport type StringValidator = V<StringOptions>;\nexport type NumberValidator = V<NumberOptions>;\nexport type ListValidator<T extends Validator[]> = V<ListOptions<T>>;\nexport type ListofValidator<T extends Validator> = V<ListofOptions<T>>;\nexport type RecordValidator<T extends Schema> = V<RecordOptions<T>>;\nexport type RecordofValidator<T extends Validator> = V<RecordofOptions<T>>;\nexport type ConstantValidator<T extends string | number | boolean> = V<ConstantOptions<T>>;\nexport type UnionValidator<T extends Validator[]> = V<UnionOptions<T>>;\n\nexport type Validator =\n  | UnionValidator<any[]>\n  | ConstantValidator<any>\n  | StringValidator\n  | NumberValidator\n  | BooleanValidator\n  | ListofValidator<any>\n  | ListValidator<any[]>\n  | RecordValidator<any>\n  | RecordofValidator<any>;\n\nexport interface Schema {\n  [key: string]: Validator;\n}\n"
  },
  {
    "path": "test/index.test.ts",
    "content": "import { createSchema, _ } from '../src/index';\nimport { DATAERR, TYPEERR } from '../src/constants';\n\ndescribe('createSchema throws when', () => {\n  test('passed invalid schema', () => {\n    // @ts-expect-error\n    expect(() => createSchema(null)).toThrow();\n    // @ts-expect-error\n    expect(() => createSchema(undefined)).toThrow();\n    // @ts-expect-error\n    expect(() => createSchema([])).toThrow();\n  });\n});\n\nconst Person = createSchema({\n  is_premium: _.boolean({ optional: true }),\n  is_verified: _.boolean(),\n  name: _.string({\n    maxLength: [24, 'too-long'],\n    minLength: [2, 'too-short'],\n    pattern: [/[a-zA-Z ]/g, 'contains-symbols'],\n  }),\n  age: _.number({\n    max: [150, 'too-old'],\n    min: [13, 'too-young'],\n  }),\n  email: _.string({\n    pattern: [/^[^@]+@[^@]+\\.[^@]+$/, 'invalid-email'],\n  }),\n  tags: _.listof(_.string(), { optional: true }),\n  friends: _.recordof(_.record({ name: _.string(), id: _.string() }), { optional: true }),\n  nested_list: _.list([_.string(), _.list([_.list([_.number()])])], { optional: true }),\n  four_tags: _.list([_.string(), _.string(), _.string(), _.string(), _.list([_.number()])], {\n    optional: true,\n  }),\n  meta: _.record({\n    id: _.string({ minLength: [1, 'invalid-id'], maxLength: [1000, 'invalid-id'] }),\n    created: _.number({ is: ['integer', 'timestamp-should-be-intger'] }),\n    updated: _.number({ optional: true }),\n    nested: _.record(\n      {\n        propA: _.number(),\n        propB: _.boolean(),\n        propC: _.string(),\n      },\n      { optional: true }\n    ),\n  }),\n  payment_status: _.union(\n    _.constant('pending'),\n    _.constant('canceled'),\n    _.constant('processed'),\n    _.constant('failed')\n  ),\n});\n\n// type IPerson = ReturnType<typeof Person['produce']>;\n\ndescribe('validate', () => {\n  test('ignores optional properties when not found', () => {\n    const errors = Person.validate({\n      is_verified: true,\n      name: 'abc',\n      age: 42,\n      email: 'abc@gmail.com',\n      payment_status: 'pending',\n      meta: {\n        id: '123',\n        created: Date.now(),\n      },\n    });\n\n    expect(errors).toBe(null);\n  });\n\n  test('validates optional properties when found', () => {\n    const errors = Person.validate({\n      is_premium: 'hello world',\n      is_verified: true,\n      name: 'abc',\n      age: 42,\n      email: 'abc@gmail.com',\n      tags: {},\n      meta: {\n        id: '123',\n        created: Date.now(),\n        updated: new Date().toISOString(),\n      },\n      payment_status: 'pending',\n    });\n    expect(errors).toStrictEqual({\n      is_premium: 'invalid-type',\n      tags: 'invalid-type',\n      meta: {\n        updated: 'invalid-type',\n      },\n    });\n  });\n\n  test('emits correct error messages', () => {\n    const errors = Person.validate(\n      {\n        is_premium: 42,\n      },\n      true\n    );\n    expect(errors).toStrictEqual({ is_premium: 'invalid-type' });\n  });\n  // test('handles eager validation correctly', () => {\n  //   expect(Person.validate({}, true)).toStrictEqual({ name: TYPEERR });\n  // });\n});\n\ndescribe('produce', () => {\n  const Person = createSchema({\n    name: _.string(),\n    age: _.number(),\n    email: _.string(),\n  });\n\n  test('throws on first error', () => {\n    expect(() => Person.produce(null)).toThrow(new TypeError(DATAERR));\n    expect(() => Person.produce(undefined)).toThrow(new TypeError(DATAERR));\n    expect(() => Person.produce(34)).toThrow(new TypeError(DATAERR));\n    expect(() => Person.produce('hello world')).toThrow(new TypeError(DATAERR));\n    expect(() => {\n      return Person.produce({ name: 2, age: 42, email: 'email@example.com' });\n    }).toThrow(new TypeError(DATAERR));\n  });\n\n  test('let data throw if it matches the schema', () => {\n    const p = { name: 'john', age: 42, email: 'john@gmail.com' };\n    expect(Person.produce(p)).toStrictEqual(p);\n  });\n});\n\ndescribe('is', () => {\n  const s = createSchema({\n    a: _.record({\n      b: _.string({ optional: true }),\n      c: _.record({ d: _.number(), e: _.number({ optional: true }) }),\n    }),\n  });\n\n  test('returns false when passed incorrect data type', () => {\n    const s = createSchema({});\n    expect(s.is(undefined)).toBe(false);\n    expect(s.is(null)).toBe(false);\n    expect(s.is([])).toBe(false);\n\n    expect(s.is({})).toBe(true);\n  });\n\n  test('return correct boolean based on data', () => {\n    expect(s.is({ a: { c: { d: 42 } } })).toBe(true);\n    expect(s.is({ a: { b: 'hello', c: { e: 120, d: 42 } } })).toBe(true);\n\n    expect(s.is({ a: { b: true, c: { e: 120, d: 42 } } })).toBe(false);\n    expect(s.is({ a: { c: { d: 'hello' } } })).toBe(false);\n  });\n});\n\ndescribe('eager validation', () => {\n  const s = createSchema({\n    a: _.record({\n      b: _.string({ optional: true }),\n      c: _.record({ d: _.number(), e: _.number({ optional: true }) }),\n    }),\n  });\n\n  test('test 1', () => {\n    const errors = s.validate({ a: { b: 42, c: false } }, true);\n    expect(errors).toStrictEqual({ a: { b: TYPEERR } });\n  });\n});\n\ndescribe('reports unknown keys', () => {\n  describe('is', () => {\n    const schema = createSchema({\n      hello: _.string(),\n    });\n    test('exits when it finds an unknown-key', () => {\n      expect(schema.is({ goodbye: 42, hello: 'im valid' })).toBe(false);\n    });\n  });\n\n  describe('validate', () => {\n    const schema = createSchema({\n      o: _.record({ a: _.string() }),\n    });\n\n    test('errors contain unknown-keys error message', () => {\n      expect(schema.validate({ o: { a: '' }, x: 'reported', y: 'reported' })).toStrictEqual({\n        x: 'unknown-key',\n        y: 'unknown-key',\n      });\n      const errors = schema.validate({ o: { a: '', b: 42 }, x: 'reported' });\n      expect(errors).toStrictEqual({\n        x: 'unknown-key',\n        o: {\n          b: 'unknown-key',\n        },\n      });\n    });\n    test('handles eager validation', () => {\n      expect(schema.validate({ o: { a: '' }, x: 'reported', y: 'reported' }, true)).toStrictEqual({\n        x: 'unknown-key',\n      });\n      const errors = schema.validate({ o: { a: '', b: 42 }, x: 'reported' }, true);\n      expect(errors).toStrictEqual({\n        x: 'unknown-key',\n      });\n    });\n  });\n\n  test('is', () => {\n    const schema = createSchema({\n      opt: _.record({}, { optional: true }),\n      list: _.list([_.string(), _.number()], { optional: true }),\n      metadata: _.record({\n        propA: _.string(),\n      }),\n    });\n    expect(schema.is({ metadata: { propA: 'hello world' }, unknownKey: 42 })).toBe(false);\n    expect(schema.is({ metadata: { propA: 'hello world', extra: 42 } })).toBe(false);\n    expect(schema.is({ metadata: { propA: 'hello world' }, list: ['hello', 42], opt: {} })).toBe(\n      true\n    );\n    expect(\n      schema.is({ metadata: { propA: 'hello world' }, list: ['hello', 42], opt: { newKey: 42 } })\n    ).toBe(false);\n    expect(schema.is({ metadata: { propA: 'hello world' }, list: ['hello', 42, undefined] })).toBe(\n      false\n    );\n  });\n});\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"include\": [\".\"]\n}\n"
  },
  {
    "path": "test/validators.test.ts",
    "content": "import { createSchema, _ } from '../src/index';\nimport { TYPEERR } from '../src/constants';\n\nconst createString = (length: number, char?: string) => {\n  let s = '';\n  for (let i = 0; i < length; i++) s += char || ' ';\n  return s;\n};\n\ndescribe('string validator', () => {\n  const name = (() => {\n    const config = {\n      maxLength: [100, 'too-long'] as [number, string],\n      minLength: [10, 'too-short'] as [number, string],\n      pattern: [/[a-zA-Z]/g, 'invalid-pattern'] as [RegExp, string],\n    };\n    return {\n      optional: () => _.string({ ...config, optional: true }),\n      required: () => _.string(config),\n    };\n  })();\n  const Person1 = createSchema({\n    name: name.optional(),\n  });\n  const Person2 = createSchema({\n    name: name.required(),\n  });\n\n  test('tests type', () => {\n    expect(Person1.is({ name: 0 })).toBe(false);\n    expect(Person1.is({ name: 42 })).toBe(false);\n    expect(Person1.is({ name: {} })).toBe(false);\n    expect(Person1.is({ name: [] })).toBe(false);\n    expect(Person1.is({ name: true })).toBe(false);\n\n    expect(Person2.is({ name: 0 })).toBe(false);\n    expect(Person2.is({ name: 42 })).toBe(false);\n    expect(Person2.is({ name: {} })).toBe(false);\n    expect(Person2.is({ name: [] })).toBe(false);\n    expect(Person2.is({ name: true })).toBe(false);\n  });\n\n  test('optional', () => {\n    expect(Person1.is({ name: undefined })).toBe(true);\n    expect(Person1.is({ name: null })).toBe(true);\n\n    expect(Person1.is({ name: 0 })).toBe(false);\n    expect(Person1.is({ name: '' })).toBe(false);\n\n    expect(Person2.is({ name: undefined })).toBe(false);\n    expect(Person2.is({ name: null })).toBe(false);\n\n    expect(Person2.is({ name: 0 })).toBe(false);\n    expect(Person2.is({ name: '' })).toBe(false);\n  });\n\n  test('pattern', () => {\n    expect(Person1.is({ name: '0123456789' })).toBe(false);\n    expect(Person1.is({ name: '----------' })).toBe(false);\n    expect(Person1.is({ name: '__________' })).toBe(false);\n    expect(Person1.is({ name: 'abcdefghij' })).toBe(true);\n\n    expect(Person2.is({ name: '0123456789' })).toBe(false);\n    expect(Person2.is({ name: '----------' })).toBe(false);\n    expect(Person2.is({ name: '__________' })).toBe(false);\n    expect(Person2.is({ name: 'abcdefghij' })).toBe(true);\n  });\n\n  test('maxLength', () => {\n    expect(Person1.is({ name: '' })).toBe(false);\n    expect(Person1.is({ name: createString(100, 'a') })).toBe(true);\n    expect(Person1.is({ name: createString(101, 'a') })).toBe(false);\n  });\n\n  test('minLength', () => {\n    expect(Person1.is({ name: '' })).toBe(false);\n    expect(Person1.is({ name: createString(9, 'a') })).toBe(false);\n    expect(Person1.is({ name: createString(10, 'a') })).toBe(true);\n  });\n\n  test('emits correct error message', () => {\n    expect(Person1.validate({ name: '' })).toStrictEqual({ name: 'too-short' });\n    expect(Person1.validate({ name: createString(101, 'a') })).toStrictEqual({ name: 'too-long' });\n    expect(Person1.validate({ name: createString(11, '0') })).toStrictEqual({\n      name: 'invalid-pattern',\n    });\n  });\n});\n\ndescribe('number validator', () => {\n  const age = (() => {\n    const config = {\n      max: [10, 'too-large'] as [number, string],\n      min: [1, 'too-small'] as [number, string],\n      is: ['integer', 'wrong-type-of-number'] as ['integer', string],\n    };\n    return {\n      optional: () => _.number({ ...config, optional: true }),\n      required: () => _.number(config),\n    };\n  })();\n\n  const Person1 = createSchema({\n    age: age.optional(),\n    n: _.number({ optional: true }),\n  });\n  const Person2 = createSchema({\n    age: age.required(),\n    n: _.number({ optional: true }),\n  });\n\n  test('tests type', () => {\n    expect(Person1.is({ age: '' })).toBe(false);\n    expect(Person1.is({ age: {} })).toBe(false);\n    expect(Person1.is({ age: [] })).toBe(false);\n    expect(Person1.is({ age: true })).toBe(false);\n    expect(Person1.is({ age: Infinity, n: Infinity })).toBe(false);\n    expect(Person1.is({ age: -Infinity, n: -Infinity })).toBe(false);\n    expect(Person1.is({ age: NaN, n: NaN })).toBe(false);\n\n    expect(Person2.is({ age: '' })).toBe(false);\n    expect(Person2.is({ age: {} })).toBe(false);\n    expect(Person2.is({ age: [] })).toBe(false);\n    expect(Person2.is({ age: true })).toBe(false);\n    expect(Person1.is({ age: Infinity, n: Infinity })).toBe(false);\n    expect(Person1.is({ age: -Infinity, n: -Infinity })).toBe(false);\n    expect(Person1.is({ age: NaN, n: NaN })).toBe(false);\n  });\n\n  test('optional', () => {\n    expect(Person1.is({ age: undefined })).toBe(true);\n    expect(Person1.is({ age: null })).toBe(true);\n    expect(Person1.is({ age: false })).toBe(false);\n\n    expect(Person2.is({ age: undefined })).toBe(false);\n    expect(Person2.is({ age: null })).toBe(false);\n    expect(Person1.is({ age: false })).toBe(false);\n  });\n\n  test('max', () => {\n    expect(Person1.is({ age: 10 })).toBe(true);\n    expect(Person1.is({ age: 11 })).toBe(false);\n  });\n\n  test('min', () => {\n    expect(Person1.is({ age: 0 })).toBe(false);\n    expect(Person1.is({ age: 10 })).toBe(true);\n  });\n\n  test('is', () => {\n    expect(Person1.is({ age: 9.4 })).toBe(false);\n    expect(Person1.is({ age: 10 })).toBe(true);\n  });\n\n  test('emits correct error message', () => {\n    expect(Person1.validate({ age: 11 })).toStrictEqual({ age: 'too-large' });\n    expect(Person1.validate({ age: -1 })).toStrictEqual({ age: 'too-small' });\n    expect(Person1.validate({ age: 9.4 })).toStrictEqual({ age: 'wrong-type-of-number' });\n  });\n});\n\ndescribe('boolean validator', () => {\n  const Person1 = createSchema({\n    is: _.boolean({ optional: true }),\n  });\n  const Person2 = createSchema({\n    is: _.boolean(),\n  });\n\n  test('tests type', () => {\n    expect(Person1.is({ is: 0 })).toBe(false);\n    expect(Person1.is({ is: '' })).toBe(false);\n    expect(Person1.is({ is: {} })).toBe(false);\n    expect(Person1.is({ is: [] })).toBe(false);\n\n    expect(Person2.is({ is: 0 })).toBe(false);\n    expect(Person2.is({ is: '' })).toBe(false);\n    expect(Person2.is({ is: {} })).toBe(false);\n    expect(Person2.is({ is: [] })).toBe(false);\n\n    expect(Person1.is({ is: false })).toBe(true);\n    expect(Person1.is({ is: true })).toBe(true);\n    expect(Person2.is({ is: false })).toBe(true);\n    expect(Person2.is({ is: true })).toBe(true);\n  });\n\n  test('optional', () => {\n    expect(Person1.is({ is: undefined })).toBe(true);\n    expect(Person1.is({ is: null })).toBe(true);\n\n    expect(Person2.is({ is: undefined })).toBe(false);\n    expect(Person2.is({ is: null })).toBe(false);\n  });\n});\n\ndescribe('listof validator', () => {\n  const Person = createSchema({\n    friends: _.listof(_.string({ minLength: [2, 'too-short'] })),\n  });\n\n  test('emits correct error messages', () => {\n    expect(Person.validate({ friends: [] })).toStrictEqual(null);\n    expect(Person.validate({ friends: {} })).toStrictEqual({ friends: TYPEERR });\n    expect(Person.validate({ friends: [1, 'john'] })).toStrictEqual({ friends: { 0: TYPEERR } });\n  });\n});\n\ndescribe('list validator', () => {\n  const Person = createSchema({\n    friends: _.list([_.string(), _.number()]),\n    otherList: _.list([_.string({ minLength: [4, 'lower-bound'] })], { optional: true }),\n  });\n\n  test('handles optional property', () => {\n    expect(Person.validate({ friends: ['John', 42] })).toBe(null);\n    expect(Person.validate({ friends: ['John', 42], otherList: [] })).toStrictEqual({\n      otherList: { 0: TYPEERR },\n    });\n    expect(Person.validate({ friends: ['John', 42], otherList: ['hel'] })).toStrictEqual({\n      otherList: { 0: 'lower-bound' },\n    });\n    expect(Person.validate({ friends: ['John', 42], otherList: ['hell'] })).toStrictEqual(null);\n  });\n\n  test('emits correct error messages', () => {\n    expect(Person.validate({ friends: [] })).toStrictEqual({ friends: { 0: TYPEERR, 1: TYPEERR } });\n    expect(Person.validate({ friends: {} })).toStrictEqual({ friends: TYPEERR });\n    expect(Person.validate({ friends: [1, 'john'] })).toStrictEqual({\n      friends: { 0: TYPEERR, 1: TYPEERR },\n    });\n    expect(Person.validate({ friends: ['john', 0] })).toStrictEqual(null);\n  });\n});\n\ndescribe('record validator', () => {\n  const Person = createSchema({\n    meta: _.record({\n      id: _.string(),\n      date_created: _.number(),\n      is_verified: _.boolean(),\n    }),\n    tags: _.listof(_.string(), { optional: true }),\n  });\n\n  test('emits correct error messages', () => {\n    expect(Person.validate({})).toStrictEqual({ meta: TYPEERR });\n    expect(Person.validate({ meta: {} })).toStrictEqual({\n      meta: {\n        id: TYPEERR,\n        date_created: TYPEERR,\n        is_verified: TYPEERR,\n      },\n    });\n    expect(\n      Person.validate({ meta: { id: 123, date_created: true, is_verified: '' } })\n    ).toStrictEqual({\n      meta: {\n        id: TYPEERR,\n        date_created: TYPEERR,\n        is_verified: TYPEERR,\n      },\n    });\n    expect(\n      Person.validate({ meta: { id: null, date_created: 123, is_verified: false } })\n    ).toStrictEqual({ meta: { id: TYPEERR } });\n  });\n\n  test('handle recursive records', () => {\n    const s = createSchema({ o: _.record({ o: _.record({ x: _.record({ y: _.number() }) }) }) });\n\n    expect(s.validate({ o: { o: { x: { y: 'hello' } } } })).toStrictEqual({\n      o: { o: { x: { y: TYPEERR } } },\n    });\n  });\n});\n\ndescribe('constant validator', () => {\n  const schema = createSchema({\n    version: _.constant('v2'),\n  });\n\n  test('emits correct error message', () => {\n    expect(schema.validate({ version: 'v2' })).toStrictEqual(null);\n    expect(schema.validate({ version: 'something else' })).toStrictEqual({ version: TYPEERR });\n  });\n});\n\ndescribe('union validator', () => {\n  test('emits correct error message', () => {\n    const schema = createSchema({\n      state: _.union(_.constant('on'), _.constant('off'), _.constant('unknown')),\n    });\n    expect(schema.validate({ state: 'on' })).toStrictEqual(null);\n    expect(schema.validate({ state: 'off' })).toStrictEqual(null);\n    expect(schema.validate({ state: 'unknown' })).toStrictEqual(null);\n    expect(schema.validate({ state: 'should-error' })).toStrictEqual({ state: TYPEERR });\n  });\n\n  test('performs deep checks', () => {\n    const schema = createSchema({\n      state: _.union(\n        _.record({ prop: _.number() }),\n        _.record({ x: _.record({ nested: _.constant(42) }) })\n      ),\n    });\n    expect(schema.validate({ state: { prop: 100 } })).toStrictEqual(null);\n    expect(schema.validate({ state: { x: { nested: 42 } } })).toStrictEqual(null);\n    expect(schema.validate({ state: { x: { nested: 33 } } })).toStrictEqual({ state: TYPEERR });\n    expect(schema.validate({ state: 'should-error' })).toStrictEqual({ state: TYPEERR });\n  });\n});\n\ndescribe('recordof validator', () => {\n  const Group = createSchema({\n    people: _.recordof(\n      _.record({\n        name: _.string(),\n        age: _.number(),\n      })\n    ),\n  });\n\n  test('emits correct error messages', () => {\n    expect(Group.validate({ people: { john: { name: 'john', age: 42 } } })).toStrictEqual(null);\n    expect(\n      Group.validate({\n        people: {\n          john: { name: 'john', age: 42 },\n          sarah: { name: 'sarah', age: true },\n        },\n      })\n    ).toStrictEqual({ people: { sarah: { age: TYPEERR } } });\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs\n  \"include\": [\"src\", \"types\"],\n  \"compilerOptions\": {\n    \"module\": \"esnext\",\n    \"lib\": [\"dom\", \"esnext\"],\n    \"importHelpers\": true,\n    // output .d.ts declaration files for consumers\n    \"declaration\": true,\n    // output .js.map sourcemap files for consumers\n    \"sourceMap\": true,\n    // match output dir to input dir. e.g. dist/index instead of dist/src/index\n    \"rootDir\": \"./src\",\n    // stricter type-checking for stronger correctness. Recommended by TS\n    \"strict\": true,\n    // linter checks for common issues\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    // use Node's module resolution algorithm, instead of the legacy TS one\n    \"moduleResolution\": \"node\",\n    // transpile JSX to React.createElement\n    \"jsx\": \"react\",\n    // interop between ESM and CJS modules. Recommended by TS\n    \"esModuleInterop\": true,\n    // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS\n    \"skipLibCheck\": true,\n    // error out if import and file system have a casing mismatch. Recommended by TS\n    \"forceConsistentCasingInFileNames\": true,\n    // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`\n    \"noEmit\": true\n  }\n}\n"
  }
]