[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".gitattributes",
    "content": "# Set default behavior to automatically convert line endings\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n     - main\n     - next\n     - 'v*'\n    paths-ignore:\n      - 'docs/**'\n      - '*.md'\n  pull_request:\n    paths-ignore:\n      - 'docs/**'\n      - '*.md'\n\n# This allows a subsequently queued workflow run to interrupt previous runs\nconcurrency:\n    group: \"${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}\"\n    cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    permissions:\n      contents: write\n      pull-requests: write\n    uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6\n    with:\n      lint: true\n      license-check: true\n"
  },
  {
    "path": ".github/workflows/lock-threads.yml",
    "content": "name: Lock Threads\n\non:\n  schedule:\n    - cron: '0 0 1 * *'\n  workflow_dispatch:\n\nconcurrency:\n  group: lock\n\npermissions:\n  contents: read\n\njobs:\n  lock-threads:\n    permissions:\n      issues: write\n      pull-requests: write\n    uses: fastify/workflows/.github/workflows/lock-threads.yml@v6\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n.cache\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# Vim swap files\n*.swp\n\n# macOS files\n.DS_Store\n\n# Clinic\n.clinic\n\n# lock files\nbun.lockb\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\n\n# editor files\n.vscode\n.idea\n\n#tap files\n.tap/\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged"
  },
  {
    "path": ".npmignore",
    "content": ".editorconfig\n.gitignore\n.husky\n.npmignore\n.nvmrc\n.prettierrc\n.travis.yml\nyarn.lock\n.idea\ncoverage\n"
  },
  {
    "path": ".npmrc",
    "content": "ignore-scripts=true\npackage-lock=false\n"
  },
  {
    "path": ".nvmrc",
    "content": "v14.19.0\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-present The Fastify team <https://github.com/fastify/fastify#team>\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.\n"
  },
  {
    "path": "README.md",
    "content": "# fluent-json-schema\n\nA fluent API to generate JSON schemas (draft-07) for Node.js and browser. Framework agnostic.\n\n[![view on npm](https://img.shields.io/npm/v/fluent-json-schema.svg)](https://www.npmjs.org/package/fluent-json-schema)\n[![CI](https://github.com/fastify/fluent-json-schema/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fluent-json-schema/actions/workflows/ci.yml)\n[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)\n\n## Features\n\n- Fluent schema implements JSON Schema draft-07 standards\n- Faster and shorter way to write a JSON Schema via a [fluent API](https://en.wikipedia.org/wiki/Fluent_interface)\n- Runtime errors for invalid options or keywords misuse\n- JavaScript constants can be used in the JSON schema (e.g. _enum_, _const_, _default_ ) avoiding discrepancies between model and schema\n- TypeScript definitions\n- Coverage 100%\n\n## Install\n\n    npm i fluent-json-schema\n\nor\n\n    yarn add fluent-json-schema\n\n## Usage\n\n```javascript\nconst S = require('fluent-json-schema')\n\nconst ROLES = {\n  ADMIN: 'ADMIN',\n  USER: 'USER',\n}\n\nconst schema = S.object()\n  .id('http://foo/user')\n  .title('My First Fluent JSON Schema')\n  .description('A simple user')\n  .prop('email', S.string().format(S.FORMATS.EMAIL).required())\n  .prop('password', S.string().minLength(8).required())\n  .prop('role', S.string().enum(Object.values(ROLES)).default(ROLES.USER))\n  .prop(\n    'birthday',\n    S.raw({ type: 'string', format: 'date', formatMaximum: '2020-01-01' }) // formatMaximum is an AJV custom keywords\n  )\n  .definition(\n    'address',\n    S.object()\n      .id('#address')\n      .prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable\n      .prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger  nullable\n      .prop('country', S.string())\n      .prop('city', S.string())\n      .prop('zipcode', S.string())\n      .required(['line1', 'country', 'city', 'zipcode'])\n  )\n  .prop('address', S.ref('#address'))\n\nconsole.log(JSON.stringify(schema.valueOf(), undefined, 2))\n```\n\nSchema generated:\n\n```json\n{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"definitions\": {\n    \"address\": {\n      \"type\": \"object\",\n      \"$id\": \"#address\",\n      \"properties\": {\n        \"line1\": {\n          \"anyOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"line2\": {\n          \"type\": \"string\",\n          \"nullable\": true\n        },\n        \"country\": {\n          \"type\": \"string\"\n        },\n        \"city\": {\n          \"type\": \"string\"\n        },\n        \"zipcode\": {\n          \"type\": \"string\"\n        }\n      },\n      \"required\": [\"line1\", \"country\", \"city\", \"zipcode\"]\n    }\n  },\n  \"type\": \"object\",\n  \"$id\": \"http://foo/user\",\n  \"title\": \"My First Fluent JSON Schema\",\n  \"description\": \"A simple user\",\n  \"properties\": {\n    \"email\": {\n      \"type\": \"string\",\n      \"format\": \"email\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"minLength\": 8\n    },\n    \"birthday\": {\n      \"type\": \"string\",\n      \"format\": \"date\",\n      \"formatMaximum\": \"2020-01-01\"\n    },\n    \"role\": {\n      \"type\": \"string\",\n      \"enum\": [\"ADMIN\", \"USER\"],\n      \"default\": \"USER\"\n    },\n    \"address\": {\n      \"$ref\": \"#address\"\n    }\n  },\n  \"required\": [\"email\", \"password\"]\n}\n```\n\n## TypeScript\n\n### CommonJS\n\nWith `\"esModuleInterop\": true` activated in the `tsconfig.json`:\n\n```typescript\nimport S from 'fluent-json-schema'\n\nconst schema = S.object()\n  .prop('foo', S.string())\n  .prop('bar', S.number())\n  .valueOf()\n```\n\nWith `\"esModuleInterop\": false` in the `tsconfig.json`:\n\n```typescript\nimport * as S from 'fluent-json-schema'\n\nconst schema = S.object()\n  .prop('foo', S.string())\n  .prop('bar', S.number())\n  .valueOf()\n```\n\n### ESM\n\nA named export is also available to work with native ESM modules:\n\n```typescript\nimport { S } from 'fluent-json-schema'\n\nconst schema = S.object()\n  .prop('foo', S.string())\n  .prop('bar', S.number())\n  .valueOf()\n```\n\n## Validation\n\nFluent schema **does not** validate a JSON schema. However, many libraries can do that for you.\nBelow are a few examples using [AJV](https://ajv.js.org/):\n\n    npm i ajv\n\nor\n\n    yarn add ajv\n\n### Validate an empty model\n\nSnippet:\n\n```javascript\nconst ajv = new Ajv({ allErrors: true })\nconst validate = ajv.compile(schema.valueOf())\nlet user = {}\nlet valid = validate(user)\nconsole.log({ valid }) //=> {valid: false}\nconsole.log(validate.errors) //=> {valid: false}\n```\n\nOutput:\n\n```\n{valid: false}\nerrors: [\n  {\n    keyword: 'required',\n    dataPath: '',\n    schemaPath: '#/required',\n    params: { missingProperty: 'email' },\n    message: \"should have required property 'email'\",\n  },\n  {\n    keyword: 'required',\n    dataPath: '',\n    schemaPath: '#/required',\n    params: { missingProperty: 'password' },\n    message: \"should have required property 'password'\",\n  },\n]\n\n```\n\n### Validate a partially filled model\n\nSnippet:\n\n```javascript\nuser = { email: 'test', password: 'password' }\nvalid = validate(user)\nconsole.log({ valid })\nconsole.log(validate.errors)\n```\n\nOutput:\n\n```\n{valid: false}\nerrors:\n[ { keyword: 'format',\n    dataPath: '.email',\n    schemaPath: '#/properties/email/format',\n    params: { format: 'email' },\n    message: 'should match format \"email\"' } ]\n\n```\n\n### Validate a model with a wrong format attribute\n\nSnippet:\n\n```javascript\nuser = { email: 'test@foo.com', password: 'password' }\nvalid = validate(user)\nconsole.log({ valid })\nconsole.log('errors:', validate.errors)\n```\n\nOutput:\n\n```\n{valid: false}\nerrors: [ { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'country' },\n    message: 'should have required property \\'country\\'' },\n  { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'city' },\n    message: 'should have required property \\'city\\'' },\n  { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'zipcoce' },\n    message: 'should have required property \\'zipcode\\'' } ]\n```\n\n### Valid model\n\nSnippet:\n\n```javascript\nuser = { email: 'test@foo.com', password: 'password' }\nvalid = validate(user)\nconsole.log({ valid })\n```\n\nOutput:\n\n    {valid: true}\n\n## Extend schema\n\nNormally inheritance with JSON Schema is achieved with `allOf`. However, when `.additionalProperties(false)` is used the validator won't\nunderstand which properties come from the base schema. `S.extend` creates a schema merging the base into the new one so\nthat the validator knows all the properties because it evaluates only a single schema.\nFor example, in a CRUD API `POST /users` could use the `userBaseSchema` rather than `GET /users` or `PATCH /users` use the `userSchema`\nwhich contains the `id`, `createdAt`, and `updatedAt` generated server side.\n\n```js\nconst S = require('fluent-json-schema')\nconst userBaseSchema = S.object()\n  .additionalProperties(false)\n  .prop('username', S.string())\n  .prop('password', S.string())\n\nconst userSchema = S.object()\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n  .extend(userBaseSchema)\n\nconsole.log(userSchema)\n```\n\n## Selecting certain properties of your schema\n\nIn addition to extending schemas, it is also possible to reduce them into smaller schemas. This comes in handy\nwhen you have a large Fluent Schema, and would like to re-use some of its properties.\n\nSelect only properties you want to keep.\n\n```js\nconst S = require('fluent-json-schema')\nconst userSchema = S.object()\n  .prop('username', S.string())\n  .prop('password', S.string())\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n\nconst loginSchema = userSchema.only(['username', 'password'])\n```\n\nOr remove properties you dont want to keep.\n\n```js\nconst S = require('fluent-json-schema')\nconst personSchema = S.object()\n  .prop('name', S.string())\n  .prop('age', S.number())\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n\nconst bodySchema = personSchema.without(['createdAt', 'updatedAt'])\n```\n\n### Detect Fluent Schema objects\n\nEvery Fluent Schema object contains a boolean `isFluentSchema`. In this way, you can write your own utilities that understand the Fluent Schema API and improve the user experience of your tool.\n\n```js\nconst S = require('fluent-json-schema')\nconst schema = S.object().prop('foo', S.string()).prop('bar', S.number())\nconsole.log(schema.isFluentSchema) // true\n```\n\n## Documentation\n\n- [Full API Documentation](./docs/API.md).\n- [JSON schema draft-07 reference](https://json-schema.org/draft-07/draft-handrews-json-schema-01).\n\n## Acknowledgments\n\nThanks to [Matteo Collina](https://twitter.com/matteocollina) for pushing me to implement this utility! 🙏\n\n## Related projects\n\n- JSON Schema [Draft 7](http://json-schema.org/specification-links.html#draft-7)\n- [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) (despite referring to draft 6 the guide is still good for grasping the main concepts)\n- [AJV](https://ajv.js.org/) JSON Schema validator\n- [jsonschema.net](https://www.jsonschema.net/) an online JSON Schema visual editor (it does not support advanced features)\n\n## License\n\nLicensed under [MIT](./LICENSE).\n"
  },
  {
    "path": "docs/API.md",
    "content": "## Functions\n\n<dl>\n<dt><a href=\"#ArraySchema\">ArraySchema([options])</a> ⇒ <code><a href=\"#ArraySchema\">ArraySchema</a></code></dt>\n<dd><p>Represents a ArraySchema.</p>\n</dd>\n<dt><a href=\"#items\">items(items)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.\nIf &quot;items&quot; is a schema, validation succeeds if all elements in the array successfully validate against that schema.\nIf &quot;items&quot; is an array of schemas, validation succeeds if each element of the instance validates against the schema at the same position, if any.\nOmitting this keyword has the same behavior as an empty schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.1\">reference</a></p>\n</dd>\n<dt><a href=\"#additionalItems\">additionalItems(items)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.2\">reference</a></p>\n</dd>\n<dt><a href=\"#contains\">contains(value)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>An array instance is valid against &quot;contains&quot; if at least one of its elements is valid against the given schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.6\">reference</a></p>\n</dd>\n<dt><a href=\"#uniqueItems\">uniqueItems(boolean)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>If this keyword has boolean value false, the instance validates successfully.\nIf it has boolean value true, the instance validates successfully if all of its elements are unique.\nOmitting this keyword has the same behavior as a value of false.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.5\">reference</a></p>\n</dd>\n<dt><a href=\"#minItems\">minItems(min)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>An array instance is valid against &quot;minItems&quot; if its size is greater than, or equal to, the value of this keyword.\nOmitting this keyword has the same behavior as a value of 0.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.4\">reference</a></p>\n</dd>\n<dt><a href=\"#maxItems\">maxItems(max)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>An array instance is valid against &quot;minItems&quot; if its size is greater than, or equal to, the value of this keyword.\nOmitting this keyword has the same behavior as a value of 0.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.3\">reference</a></p>\n</dd>\n<dt><a href=\"#BaseSchema\">BaseSchema([options])</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>Represents a BaseSchema.</p>\n</dd>\n<dt><a href=\"#id\">id(id)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.</p>\n<p><a href=\"https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2\">reference</a></p>\n</dd>\n<dt><a href=\"#title\">title(title)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It can be used to decorate a user interface with information about the data produced by this user interface. A title will preferably be short.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1\">reference</a></p>\n</dd>\n<dt><a href=\"#description\">description(description)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It can be used to decorate a user interface with information about the data\nproduced by this user interface. A description provides explanation about\nthe purpose of the instance described by the schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1\">reference</a></p>\n</dd>\n<dt><a href=\"#examples\">examples(examples)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of this keyword MUST be an array.\nThere are no restrictions placed on the values within the array.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.4\">reference</a></p>\n</dd>\n<dt><a href=\"#ref\">ref(ref)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value must be a valid id e.g. #properties/foo</p>\n</dd>\n<dt><a href=\"#enum\">enum(values)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.2\">reference</a></p>\n</dd>\n<dt><a href=\"#const\">const(value)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of this keyword MAY be of any type, including null.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.3\">reference</a></p>\n</dd>\n<dt><a href=\"#default\">default(defaults)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>There are no restrictions placed on the value of this keyword.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.2\">reference</a></p>\n</dd>\n<dt><a href=\"#readOnly\">readOnly(isReadOnly)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of readOnly can be left empty to indicate the property is readOnly.\nIt takes an optional boolean which can be used to explicitly set readOnly true/false.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3\">reference</a></p>\n</dd>\n<dt><a href=\"#writeOnly\">writeOnly(isWriteOnly)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of writeOnly can be left empty to indicate the property is writeOnly.\nIt takes an optional boolean which can be used to explicitly set writeOnly true/false.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3\">reference</a></p>\n</dd>\n<dt><a href=\"#deprecated\">deprecated(isDeprecated)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>The value of deprecated can be left empty to indicate the property is deprecated.\nIt takes an optional boolean which can be used to explicitly set deprecated true/false.</p>\n<p><a href=\"https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3\">reference</a></p>\n</dd>\n<dt><a href=\"#required\">required()</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>Required has to be chained to a property:\nExamples:</p>\n<ul>\n<li>S.prop(&#39;prop&#39;).required()</li>\n<li>S.prop(&#39;prop&#39;, S.number()).required()</li>\n<li>S.required([&#39;foo&#39;, &#39;bar&#39;])</li>\n</ul>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.3\">reference</a></p>\n</dd>\n<dt><a href=\"#not\">not(not)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>This keyword&#39;s value MUST be a valid JSON Schema.\nAn instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.4\">reference</a></p>\n</dd>\n<dt><a href=\"#anyOf\">anyOf(schemas)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.2\">reference</a></p>\n</dd>\n<dt><a href=\"#allOf\">allOf(schemas)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.1\">reference</a></p>\n</dd>\n<dt><a href=\"#oneOf\">oneOf(schemas)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.3\">reference</a></p>\n</dd>\n<dt><a href=\"#ifThen\">ifThen(ifClause, thenClause)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>This validation outcome of this keyword&#39;s subschema has no direct effect on the overall validation result.\nRather, it controls which of the &quot;then&quot; or &quot;else&quot; keywords are evaluated.\nWhen &quot;if&quot; is present, and the instance successfully validates against its subschema, then\nvalidation succeeds against this keyword if the instance also successfully validates against this keyword&#39;s subschema.</p>\n</dd>\n<dt><a href=\"#ifThenElse\">ifThenElse(ifClause, thenClause, elseClause)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>When &quot;if&quot; is present, and the instance fails to validate against its subschema,\nthen validation succeeds against this keyword if the instance successfully validates against this keyword&#39;s subschema.</p>\n</dd>\n<dt><a href=\"#raw\">raw(fragment)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>Because the differences between JSON Schemas and Open API (Swagger)\nit can be handy to arbitrary modify the schema injecting a fragment</p>\n<ul>\n<li>Examples:</li>\n</ul>\n<ul>\n<li>S.number().raw({ nullable:true })</li>\n<li>S.string().format(&#39;date&#39;).raw({ formatMaximum: &#39;2020-01-01&#39; })</li>\n</ul>\n</dd>\n<dt><a href=\"#valueOf\">valueOf([options])</a> ⇒ <code><a href=\"#object\">object</a></code></dt>\n<dd><p>It returns all the schema values</p>\n</dd>\n<dt><a href=\"#BooleanSchema\">BooleanSchema([options])</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Represents a BooleanSchema.</p>\n</dd>\n<dt><a href=\"#S\">S([options])</a> ⇒ <code><a href=\"#S\">S</a></code></dt>\n<dd><p>Represents a S.</p>\n</dd>\n<dt><a href=\"#string\">string()</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Set a property to type string</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3\">reference</a></p>\n</dd>\n<dt><a href=\"#number\">number()</a> ⇒ <code><a href=\"#NumberSchema\">NumberSchema</a></code></dt>\n<dd><p>Set a property to type number</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric\">reference</a></p>\n</dd>\n<dt><a href=\"#integer\">integer()</a> ⇒ <code><a href=\"#IntegerSchema\">IntegerSchema</a></code></dt>\n<dd><p>Set a property to type integer</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric\">reference</a></p>\n</dd>\n<dt><a href=\"#boolean\">boolean()</a> ⇒ <code><a href=\"#BooleanSchema\">BooleanSchema</a></code></dt>\n<dd><p>Set a property to type boolean</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7\">reference</a></p>\n</dd>\n<dt><a href=\"#array\">array()</a> ⇒ <code><a href=\"#ArraySchema\">ArraySchema</a></code></dt>\n<dd><p>Set a property to type array</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4\">reference</a></p>\n</dd>\n<dt><a href=\"#object\">object()</a> ⇒ <code><a href=\"#ObjectSchema\">ObjectSchema</a></code></dt>\n<dd><p>Set a property to type object</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5\">reference</a></p>\n</dd>\n<dt><a href=\"#null\">null()</a> ⇒ <code><a href=\"#NullSchema\">NullSchema</a></code></dt>\n<dd><p>Set a property to type null</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#general\">reference</a></p>\n</dd>\n<dt><a href=\"#mixed\">mixed(types)</a> ⇒ <code><a href=\"#MixedSchema\">MixedSchema</a></code></dt>\n<dd><p>A mixed schema is the union of multiple types (e.g. [&#39;string&#39;, &#39;integer&#39;]</p>\n</dd>\n<dt><a href=\"#raw\">raw(fragment)</a> ⇒ <code><a href=\"#BaseSchema\">BaseSchema</a></code></dt>\n<dd><p>Because the differences between JSON Schemas and Open API (Swagger)\nit can be handy to arbitrary modify the schema injecting a fragment</p>\n<ul>\n<li>Examples:</li>\n</ul>\n<ul>\n<li>S.raw({ nullable:true, format: &#39;date&#39;, formatMaximum: &#39;2020-01-01&#39; })</li>\n<li>S.string().format(&#39;date&#39;).raw({ formatMaximum: &#39;2020-01-01&#39; })</li>\n</ul>\n</dd>\n<dt><a href=\"#IntegerSchema\">IntegerSchema([options])</a> ⇒ <code><a href=\"#NumberSchema\">NumberSchema</a></code></dt>\n<dd><p>Represents a NumberSchema.</p>\n</dd>\n<dt><a href=\"#MixedSchema\">MixedSchema([options])</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Represents a MixedSchema.</p>\n</dd>\n<dt><a href=\"#NullSchema\">NullSchema([options])</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Represents a NullSchema.</p>\n</dd>\n<dt><a href=\"#null\">null()</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>Set a property to type null</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.1\">reference</a></p>\n</dd>\n<dt><a href=\"#NumberSchema\">NumberSchema([options])</a> ⇒ <code><a href=\"#NumberSchema\">NumberSchema</a></code></dt>\n<dd><p>Represents a NumberSchema.</p>\n</dd>\n<dt><a href=\"#minimum\">minimum(min)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>It represents  an inclusive lower limit for a numeric instance.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.4\">reference</a></p>\n</dd>\n<dt><a href=\"#exclusiveMinimum\">exclusiveMinimum(min)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>It represents an exclusive lower limit for a numeric instance.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.5\">reference</a></p>\n</dd>\n<dt><a href=\"#maximum\">maximum(max)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>It represents  an inclusive upper limit for a numeric instance.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.2\">reference</a></p>\n</dd>\n<dt><a href=\"#exclusiveMaximum\">exclusiveMaximum(max)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>It represents an exclusive upper limit for a numeric instance.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.3\">reference</a></p>\n</dd>\n<dt><a href=\"#multipleOf\">multipleOf(multiple)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>It&#39;s strictly greater than 0.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.1\">reference</a></p>\n</dd>\n<dt><a href=\"#ObjectSchema\">ObjectSchema([options])</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Represents a ObjectSchema.</p>\n</dd>\n<dt><a href=\"#id\">id(id)</a></dt>\n<dd><p>It defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.\nCalling <code>id</code>  on an ObjectSchema will alway set the id on the root of the object rather than in its &quot;properties&quot;, which\ndiffers from other schema types.</p>\n<p><a href=\"https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2\">reference</a></p>\n</dd>\n<dt><a href=\"#additionalProperties\">additionalProperties(value)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>This keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\nValidation with &quot;additionalProperties&quot; applies only to the child values of instance names that do not match any names in &quot;properties&quot;,\nand do not match any regular expression in &quot;patternProperties&quot;.\nFor all such properties, validation succeeds if the child instance validates against the &quot;additionalProperties&quot; schema.\nOmitting this keyword has the same behavior as an empty schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.6\">reference</a></p>\n</dd>\n<dt><a href=\"#maxProperties\">maxProperties(max)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>An object instance is valid against &quot;maxProperties&quot; if its number of properties is less than, or equal to, the value of this keyword.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.1\">reference</a></p>\n</dd>\n<dt><a href=\"#minProperties\">minProperties(min)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>An object instance is valid against &quot;minProperties&quot; if its number of properties is greater than, or equal to, the value of this keyword.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.2\">reference</a></p>\n</dd>\n<dt><a href=\"#patternProperties\">patternProperties(opts)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>Each property name of this object SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\nEach property value of this object MUST be a valid JSON Schema.\nThis keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\nValidation of the primitive instance type against this keyword always succeeds.\nValidation succeeds if, for each instance name that matches any regular expressions that appear as a property name in this keyword&#39;s value, the child instance for that name successfully validates against each schema that corresponds to a matching regular expression.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.5\">reference</a></p>\n</dd>\n<dt><a href=\"#dependencies\">dependencies(opts)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>This keyword specifies rules that are evaluated if the instance is an object and contains a certain property.\nThis keyword&#39;s value MUST be an object. Each property specifies a dependency. Each dependency value MUST be an array or a valid JSON Schema.\nIf the dependency value is a subschema, and the dependency key is a property in the instance, the entire instance must validate against the dependency value.\nIf the dependency value is an array, each element in the array, if any, MUST be a string, and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.7\">reference</a></p>\n</dd>\n<dt><a href=\"#dependentRequired\">dependentRequired(opts)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>The value of &quot;properties&quot; MUST be an object. Each dependency value MUST be an array.\nEach element in the array MUST be a string and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.</p>\n<p><a href=\"https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.5.4\">reference</a></p>\n</dd>\n<dt><a href=\"#dependentSchemas\">dependentSchemas(opts)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>The value of &quot;properties&quot; MUST be an object. The dependency value MUST be a valid JSON Schema.\nEach dependency key is a property in the instance and the entire instance must validate against the dependency value.</p>\n<p><a href=\"https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.2.4\">reference</a></p>\n</dd>\n<dt><a href=\"#propertyNames\">propertyNames(value)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>If the instance is an object, this keyword validates if every property name in the instance validates against the provided schema.\nNote the property name that the schema is testing will always be a string.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.8\">reference</a></p>\n</dd>\n<dt><a href=\"#prop\">prop(name, props)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>The value of &quot;properties&quot; MUST be an object. Each value of this object MUST be a valid JSON Schema.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.4\">reference</a></p>\n</dd>\n<dt><a href=\"#only\">only(properties)</a> ⇒ <code><a href=\"#ObjectSchema\">ObjectSchema</a></code></dt>\n<dd><p>Returns an object schema with only a subset of keys provided. If called on an ObjectSchema with an\n<code>$id</code>, it will be removed and the return value will be considered a new schema.</p>\n</dd>\n<dt><a href=\"#without\">without(properties)</a> ⇒ <code><a href=\"#ObjectSchema\">ObjectSchema</a></code></dt>\n<dd><p>Returns an object schema without a subset of keys provided. If called on an ObjectSchema with an\n<code>$id</code>, it will be removed and the return value will be considered a new schema.</p>\n</dd>\n<dt><a href=\"#definition\">definition(name, props)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>The &quot;definitions&quot; keywords provides a standardized location for schema authors to inline re-usable JSON Schemas into a more general schema.\nThere are no restrictions placed on the values within the array.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.9\">reference</a></p>\n</dd>\n<dt><a href=\"#RawSchema\">RawSchema(schema)</a> ⇒ <code>FluentSchema</code></dt>\n<dd><p>Represents a raw JSON Schema that will be parsed</p>\n</dd>\n<dt><a href=\"#StringSchema\">StringSchema([options])</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>Represents a StringSchema.</p>\n</dd>\n<dt><a href=\"#minLength\">minLength(min)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword.\nThe length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.2\">reference</a></p>\n</dd>\n<dt><a href=\"#maxLength\">maxLength(max)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword.\nThe length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.1\">reference</a></p>\n</dd>\n<dt><a href=\"#format\">format(format)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>A string value can be RELATIVE_JSON_POINTER, JSON_POINTER, UUID, REGEX, IPV6, IPV4, HOSTNAME, EMAIL, URL, URI_TEMPLATE, URI_REFERENCE, URI, TIME, DATE, DATE_TIME, ISO_TIME, ISO_DATE_TIME.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.7.3\">reference</a></p>\n</dd>\n<dt><a href=\"#pattern\">pattern(pattern)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\n A string instance is considered valid if the regular expression matches the instance successfully.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.3\">reference</a></p>\n</dd>\n<dt><a href=\"#contentEncoding\">contentEncoding(encoding)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>If the instance value is a string, this property defines that the string SHOULD\n be interpreted as binary data and decoded using the encoding named by this property.\n RFC 2045, Sec 6.1 [RFC2045] lists the possible values for this property.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.3\">reference</a></p>\n</dd>\n<dt><a href=\"#contentMediaType\">contentMediaType(mediaType)</a> ⇒ <code><a href=\"#StringSchema\">StringSchema</a></code></dt>\n<dd><p>The value of this property must be a media type, as defined by RFC 2046 [RFC2046].\n This property defines the media type of instances which this schema defines.</p>\n<p><a href=\"https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.4\">reference</a></p>\n</dd>\n</dl>\n\n<a name=\"ArraySchema\"></a>\n\n## ArraySchema([options]) ⇒ [<code>ArraySchema</code>](#ArraySchema)\nRepresents a ArraySchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>StringSchema</code>](#StringSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"items\"></a>\n\n## items(items) ⇒ <code>FluentSchema</code>\nThis keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.\nIf \"items\" is a schema, validation succeeds if all elements in the array successfully validate against that schema.\nIf \"items\" is an array of schemas, validation succeeds if each element of the instance validates against the schema at the same position, if any.\nOmitting this keyword has the same behavior as an empty schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| items | <code>FluentSchema</code> \\| <code>Array.&lt;FluentSchema&gt;</code> |\n\n<a name=\"additionalItems\"></a>\n\n## additionalItems(items) ⇒ <code>FluentSchema</code>\nThis keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| items | <code>FluentSchema</code> \\| [<code>boolean</code>](#boolean) |\n\n<a name=\"contains\"></a>\n\n## contains(value) ⇒ <code>FluentSchema</code>\nAn array instance is valid against \"contains\" if at least one of its elements is valid against the given schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.6)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| value | <code>FluentSchema</code> |\n\n<a name=\"uniqueItems\"></a>\n\n## uniqueItems(boolean) ⇒ <code>FluentSchema</code>\nIf this keyword has boolean value false, the instance validates successfully.\nIf it has boolean value true, the instance validates successfully if all of its elements are unique.\nOmitting this keyword has the same behavior as a value of false.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.5)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| boolean | [<code>boolean</code>](#boolean) |\n\n<a name=\"minItems\"></a>\n\n## minItems(min) ⇒ <code>FluentSchema</code>\nAn array instance is valid against \"minItems\" if its size is greater than, or equal to, the value of this keyword.\nOmitting this keyword has the same behavior as a value of 0.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| min | [<code>number</code>](#number) |\n\n<a name=\"maxItems\"></a>\n\n## maxItems(max) ⇒ <code>FluentSchema</code>\nAn array instance is valid against \"minItems\" if its size is greater than, or equal to, the value of this keyword.\nOmitting this keyword has the same behavior as a value of 0.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| max | [<code>number</code>](#number) |\n\n<a name=\"BaseSchema\"></a>\n\n## BaseSchema([options]) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nRepresents a BaseSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>BaseSchema</code>](#BaseSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"id\"></a>\n\n## id(id) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.\n\n[reference](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2)\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| id | [<code>string</code>](#string) | an #id |\n\n<a name=\"title\"></a>\n\n## title(title) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt can be used to decorate a user interface with information about the data produced by this user interface. A title will preferably be short.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| title | [<code>string</code>](#string) |\n\n<a name=\"description\"></a>\n\n## description(description) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt can be used to decorate a user interface with information about the data\nproduced by this user interface. A description provides explanation about\nthe purpose of the instance described by the schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| description | [<code>string</code>](#string) |\n\n<a name=\"examples\"></a>\n\n## examples(examples) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of this keyword MUST be an array.\nThere are no restrictions placed on the values within the array.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| examples | [<code>string</code>](#string) |\n\n<a name=\"ref\"></a>\n\n## ref(ref) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value must be a valid id e.g. #properties/foo\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| ref | [<code>string</code>](#string) |\n\n<a name=\"enum\"></a>\n\n## enum(values) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| values | [<code>array</code>](#array) |\n\n<a name=\"const\"></a>\n\n## const(value) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of this keyword MAY be of any type, including null.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.3)\n\n**Kind**: global function\n\n| Param |\n| --- |\n| value |\n\n<a name=\"default\"></a>\n\n## default(defaults) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThere are no restrictions placed on the value of this keyword.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.2)\n\n**Kind**: global function\n\n| Param |\n| --- |\n| defaults |\n\n<a name=\"readOnly\"></a>\n\n## readOnly(isReadOnly) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of readOnly can be left empty to indicate the property is readOnly.\nIt takes an optional boolean which can be used to explicitly set readOnly true/false.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| isReadOnly | [<code>boolean</code>](#boolean) \\| <code>undefined</code> |\n\n<a name=\"writeOnly\"></a>\n\n## writeOnly(isWriteOnly) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of writeOnly can be left empty to indicate the property is writeOnly.\nIt takes an optional boolean which can be used to explicitly set writeOnly true/false.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| isWriteOnly | [<code>boolean</code>](#boolean) \\| <code>undefined</code> |\n\n<a name=\"deprecated\"></a>\n\n## deprecated(isDeprecated) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThe value of deprecated can be left empty to indicate the property is deprecated.\nIt takes an optional boolean which can be used to explicitly set deprecated true/false.\n\n[reference](https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| isDeprecated | <code>Boolean</code> |\n\n<a name=\"required\"></a>\n\n## required() ⇒ <code>FluentSchema</code>\nRequired has to be chained to a property:\nExamples:\n- S.prop('prop').required()\n- S.prop('prop', S.number()).required()\n- S.required(['foo', 'bar'])\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.3)\n\n**Kind**: global function\n<a name=\"not\"></a>\n\n## not(not) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThis keyword's value MUST be a valid JSON Schema.\nAn instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| not | <code>FluentSchema</code> |\n\n<a name=\"anyOf\"></a>\n\n## anyOf(schemas) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| schemas | [<code>array</code>](#array) |\n\n<a name=\"allOf\"></a>\n\n## allOf(schemas) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| schemas | [<code>array</code>](#array) |\n\n<a name=\"oneOf\"></a>\n\n## oneOf(schemas) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nIt MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| schemas | [<code>array</code>](#array) |\n\n<a name=\"ifThen\"></a>\n\n## ifThen(ifClause, thenClause) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nThis validation outcome of this keyword's subschema has no direct effect on the overall validation result.\nRather, it controls which of the \"then\" or \"else\" keywords are evaluated.\nWhen \"if\" is present, and the instance successfully validates against its subschema, then\nvalidation succeeds against this keyword if the instance also successfully validates against this keyword's subschema.\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| ifClause | [<code>BaseSchema</code>](#BaseSchema) | [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.1) |\n| thenClause | [<code>BaseSchema</code>](#BaseSchema) | [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.2) |\n\n<a name=\"ifThenElse\"></a>\n\n## ifThenElse(ifClause, thenClause, elseClause) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nWhen \"if\" is present, and the instance fails to validate against its subschema,\nthen validation succeeds against this keyword if the instance successfully validates against this keyword's subschema.\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| ifClause | [<code>BaseSchema</code>](#BaseSchema) | [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.1) |\n| thenClause | [<code>BaseSchema</code>](#BaseSchema) | [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.2) |\n| elseClause | [<code>BaseSchema</code>](#BaseSchema) | [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.3) |\n\n<a name=\"raw\"></a>\n\n## raw(fragment) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nBecause the differences between JSON Schemas and Open API (Swagger)\nit can be handy to arbitrary modify the schema injecting a fragment\n\n* Examples:\n- S.number().raw({ nullable:true })\n- S.string().format('date').raw({ formatMaximum: '2020-01-01' })\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| fragment | [<code>string</code>](#string) | an arbitrary JSON Schema to inject [reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.3) |\n\n<a name=\"valueOf\"></a>\n\n## valueOf([options]) ⇒ [<code>object</code>](#object)\nIt returns all the schema values\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.isRoot] | [<code>boolean</code>](#boolean) | <code>true</code> | Is a root level schema |\n\n<a name=\"BooleanSchema\"></a>\n\n## BooleanSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)\nRepresents a BooleanSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>StringSchema</code>](#StringSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"S\"></a>\n\n## S([options]) ⇒ [<code>S</code>](#S)\nRepresents a S.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>S</code>](#S) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"string\"></a>\n\n## string() ⇒ [<code>StringSchema</code>](#StringSchema)\nSet a property to type string\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3)\n\n**Kind**: global function\n<a name=\"number\"></a>\n\n## number() ⇒ [<code>NumberSchema</code>](#NumberSchema)\nSet a property to type number\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric)\n\n**Kind**: global function\n<a name=\"integer\"></a>\n\n## integer() ⇒ [<code>IntegerSchema</code>](#IntegerSchema)\nSet a property to type integer\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric)\n\n**Kind**: global function\n<a name=\"boolean\"></a>\n\n## boolean() ⇒ [<code>BooleanSchema</code>](#BooleanSchema)\nSet a property to type boolean\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7)\n\n**Kind**: global function\n<a name=\"array\"></a>\n\n## array() ⇒ [<code>ArraySchema</code>](#ArraySchema)\nSet a property to type array\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4)\n\n**Kind**: global function\n<a name=\"object\"></a>\n\n## object() ⇒ [<code>ObjectSchema</code>](#ObjectSchema)\nSet a property to type object\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5)\n\n**Kind**: global function\n<a name=\"null\"></a>\n\n## null() ⇒ [<code>NullSchema</code>](#NullSchema)\nSet a property to type null\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#general)\n\n**Kind**: global function\n<a name=\"mixed\"></a>\n\n## mixed(types) ⇒ [<code>MixedSchema</code>](#MixedSchema)\nA mixed schema is the union of multiple types (e.g. ['string', 'integer']\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| types | [<code>Array.&lt;string&gt;</code>](#string) |\n\n<a name=\"raw\"></a>\n\n## raw(fragment) ⇒ [<code>BaseSchema</code>](#BaseSchema)\nBecause the differences between JSON Schemas and Open API (Swagger)\nit can be handy to arbitrary modify the schema injecting a fragment\n\n* Examples:\n- S.raw({ nullable:true, format: 'date', formatMaximum: '2020-01-01' })\n- S.string().format('date').raw({ formatMaximum: '2020-01-01' })\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| fragment | [<code>string</code>](#string) | an arbitrary JSON Schema to inject |\n\n<a name=\"IntegerSchema\"></a>\n\n## IntegerSchema([options]) ⇒ [<code>NumberSchema</code>](#NumberSchema)\nRepresents a NumberSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>NumberSchema</code>](#NumberSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"MixedSchema\"></a>\n\n## MixedSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)\nRepresents a MixedSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>MixedSchema</code>](#MixedSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"NullSchema\"></a>\n\n## NullSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)\nRepresents a NullSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>StringSchema</code>](#StringSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"null\"></a>\n\n## null() ⇒ <code>FluentSchema</code>\nSet a property to type null\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.1)\n\n**Kind**: global function\n<a name=\"NumberSchema\"></a>\n\n## NumberSchema([options]) ⇒ [<code>NumberSchema</code>](#NumberSchema)\nRepresents a NumberSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>NumberSchema</code>](#NumberSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"minimum\"></a>\n\n## minimum(min) ⇒ <code>FluentSchema</code>\nIt represents  an inclusive lower limit for a numeric instance.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| min | [<code>number</code>](#number) |\n\n<a name=\"exclusiveMinimum\"></a>\n\n## exclusiveMinimum(min) ⇒ <code>FluentSchema</code>\nIt represents an exclusive lower limit for a numeric instance.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.5)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| min | [<code>number</code>](#number) |\n\n<a name=\"maximum\"></a>\n\n## maximum(max) ⇒ <code>FluentSchema</code>\nIt represents  an inclusive upper limit for a numeric instance.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| max | [<code>number</code>](#number) |\n\n<a name=\"exclusiveMaximum\"></a>\n\n## exclusiveMaximum(max) ⇒ <code>FluentSchema</code>\nIt represents an exclusive upper limit for a numeric instance.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| max | [<code>number</code>](#number) |\n\n<a name=\"multipleOf\"></a>\n\n## multipleOf(multiple) ⇒ <code>FluentSchema</code>\nIt's strictly greater than 0.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| multiple | [<code>number</code>](#number) |\n\n<a name=\"ObjectSchema\"></a>\n\n## ObjectSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)\nRepresents a ObjectSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>StringSchema</code>](#StringSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"id\"></a>\n\n## id(id)\nIt defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.\nCalling `id`  on an ObjectSchema will alway set the id on the root of the object rather than in its \"properties\", which\ndiffers from other schema types.\n\n[reference](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2)\n\n**Kind**: global function\n\n| Param | Type | Description |\n| --- | --- | --- |\n| id | [<code>string</code>](#string) | an #id |\n\n<a name=\"additionalProperties\"></a>\n\n## additionalProperties(value) ⇒ <code>FluentSchema</code>\nThis keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\nValidation with \"additionalProperties\" applies only to the child values of instance names that do not match any names in \"properties\",\nand do not match any regular expression in \"patternProperties\".\nFor all such properties, validation succeeds if the child instance validates against the \"additionalProperties\" schema.\nOmitting this keyword has the same behavior as an empty schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.6)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| value | <code>FluentSchema</code> \\| [<code>boolean</code>](#boolean) |\n\n<a name=\"maxProperties\"></a>\n\n## maxProperties(max) ⇒ <code>FluentSchema</code>\nAn object instance is valid against \"maxProperties\" if its number of properties is less than, or equal to, the value of this keyword.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| max | [<code>number</code>](#number) |\n\n<a name=\"minProperties\"></a>\n\n## minProperties(min) ⇒ <code>FluentSchema</code>\nAn object instance is valid against \"minProperties\" if its number of properties is greater than, or equal to, the value of this keyword.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| min | [<code>number</code>](#number) |\n\n<a name=\"patternProperties\"></a>\n\n## patternProperties(opts) ⇒ <code>FluentSchema</code>\nEach property name of this object SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\nEach property value of this object MUST be a valid JSON Schema.\nThis keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\nValidation of the primitive instance type against this keyword always succeeds.\nValidation succeeds if, for each instance name that matches any regular expressions that appear as a property name in this keyword's value, the child instance for that name successfully validates against each schema that corresponds to a matching regular expression.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.5)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| opts | [<code>object</code>](#object) |\n\n<a name=\"dependencies\"></a>\n\n## dependencies(opts) ⇒ <code>FluentSchema</code>\nThis keyword specifies rules that are evaluated if the instance is an object and contains a certain property.\nThis keyword's value MUST be an object. Each property specifies a dependency. Each dependency value MUST be an array or a valid JSON Schema.\nIf the dependency value is a subschema, and the dependency key is a property in the instance, the entire instance must validate against the dependency value.\nIf the dependency value is an array, each element in the array, if any, MUST be a string, and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.7)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| opts | [<code>object</code>](#object) |\n\n<a name=\"dependentRequired\"></a>\n\n## dependentRequired(opts) ⇒ <code>FluentSchema</code>\nThe value of \"properties\" MUST be an object. Each dependency value MUST be an array.\nEach element in the array MUST be a string and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.\n\n[reference](https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.5.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| opts | [<code>object</code>](#object) |\n\n<a name=\"dependentSchemas\"></a>\n\n## dependentSchemas(opts) ⇒ <code>FluentSchema</code>\nThe value of \"properties\" MUST be an object. The dependency value MUST be a valid JSON Schema.\nEach dependency key is a property in the instance and the entire instance must validate against the dependency value.\n\n[reference](https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.2.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| opts | [<code>object</code>](#object) |\n\n<a name=\"propertyNames\"></a>\n\n## propertyNames(value) ⇒ <code>FluentSchema</code>\nIf the instance is an object, this keyword validates if every property name in the instance validates against the provided schema.\nNote the property name that the schema is testing will always be a string.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.8)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| value | <code>FluentSchema</code> |\n\n<a name=\"prop\"></a>\n\n## prop(name, props) ⇒ <code>FluentSchema</code>\nThe value of \"properties\" MUST be an object. Each value of this object MUST be a valid JSON Schema.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| name | [<code>string</code>](#string) |\n| props | <code>FluentSchema</code> |\n\n<a name=\"only\"></a>\n\n## only(properties) ⇒ [<code>ObjectSchema</code>](#ObjectSchema)\nReturns an object schema with only a subset of keys provided. If called on an ObjectSchema with an\n`$id`, it will be removed and the return value will be considered a new schema.\n\n**Kind**: global function\n\n| Param | Description |\n| --- | --- |\n| properties | a list of properties you want to keep |\n\n<a name=\"without\"></a>\n\n## without(properties) ⇒ [<code>ObjectSchema</code>](#ObjectSchema)\nReturns an object schema without a subset of keys provided. If called on an ObjectSchema with an\n`$id`, it will be removed and the return value will be considered a new schema.\n\n**Kind**: global function\n\n| Param | Description |\n| --- | --- |\n| properties | a list of properties you dont want to keep |\n\n<a name=\"definition\"></a>\n\n## definition(name, props) ⇒ <code>FluentSchema</code>\nThe \"definitions\" keywords provides a standardized location for schema authors to inline re-usable JSON Schemas into a more general schema.\nThere are no restrictions placed on the values within the array.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.9)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| name | [<code>string</code>](#string) |\n| props | <code>FluentSchema</code> |\n\n<a name=\"RawSchema\"></a>\n\n## RawSchema(schema) ⇒ <code>FluentSchema</code>\nRepresents a raw JSON Schema that will be parsed\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| schema | <code>Object</code> |\n\n<a name=\"StringSchema\"></a>\n\n## StringSchema([options]) ⇒ [<code>StringSchema</code>](#StringSchema)\nRepresents a StringSchema.\n\n**Kind**: global function\n\n| Param | Type | Default | Description |\n| --- | --- | --- | --- |\n| [options] | <code>Object</code> |  | Options |\n| [options.schema] | [<code>StringSchema</code>](#StringSchema) |  | Default schema |\n| [options.generateIds] | [<code>boolean</code>](#boolean) | <code>false</code> | generate the id automatically e.g. #properties.foo |\n\n<a name=\"minLength\"></a>\n\n## minLength(min) ⇒ [<code>StringSchema</code>](#StringSchema)\nA string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword.\nThe length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.2)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| min | [<code>number</code>](#number) |\n\n<a name=\"maxLength\"></a>\n\n## maxLength(max) ⇒ [<code>StringSchema</code>](#StringSchema)\nA string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword.\nThe length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.1)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| max | [<code>number</code>](#number) |\n\n<a name=\"format\"></a>\n\n## format(format) ⇒ [<code>StringSchema</code>](#StringSchema)\nA string value can be RELATIVE_JSON_POINTER, JSON_POINTER, UUID, REGEX, IPV6, IPV4, HOSTNAME, EMAIL, URL, URI_TEMPLATE, URI_REFERENCE, URI, TIME, DATE, DATE_TIME, ISO_TIME, ISO_DATE_TIME.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.7.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| format | [<code>string</code>](#string) |\n\n<a name=\"pattern\"></a>\n\n## pattern(pattern) ⇒ [<code>StringSchema</code>](#StringSchema)\nThis string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\n A string instance is considered valid if the regular expression matches the instance successfully.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| pattern | [<code>string</code>](#string) |\n\n<a name=\"contentEncoding\"></a>\n\n## contentEncoding(encoding) ⇒ [<code>StringSchema</code>](#StringSchema)\nIf the instance value is a string, this property defines that the string SHOULD\n be interpreted as binary data and decoded using the encoding named by this property.\n RFC 2045, Sec 6.1 [RFC2045] lists the possible values for this property.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.3)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| encoding | [<code>string</code>](#string) |\n\n<a name=\"contentMediaType\"></a>\n\n## contentMediaType(mediaType) ⇒ [<code>StringSchema</code>](#StringSchema)\nThe value of this property must be a media type, as defined by RFC 2046 [RFC2046].\n This property defines the media type of instances which this schema defines.\n\n[reference](https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.4)\n\n**Kind**: global function\n\n| Param | Type |\n| --- | --- |\n| mediaType | [<code>string</code>](#string) |\n\n"
  },
  {
    "path": "eslint.config.js",
    "content": "'use strict'\n\nmodule.exports = require('neostandard')({\n  ignores: require('neostandard').resolveIgnoresFromGitignore(),\n  ts: true\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"fluent-json-schema\",\n  \"version\": \"6.0.0\",\n  \"description\": \"JSON Schema fluent API\",\n  \"main\": \"src/FluentJSONSchema.js\",\n  \"type\": \"commonjs\",\n  \"types\": \"types/FluentJSONSchema.d.ts\",\n  \"keywords\": [\n    \"JSON\",\n    \"schema\",\n    \"jsonschema\",\n    \"json schema\",\n    \"validation\",\n    \"json schema builder\",\n    \"json schema validation\"\n  ],\n  \"license\": \"MIT\",\n  \"author\": \"Lorenzo Sicilia <lorenzo.sicilia@gmail.com>\",\n  \"contributors\": [\n    \"Matteo Collina <hello@matteocollina.com>\"\n  ],\n  \"homepage\": \"https://github.com/fastify/fluent-json-schema#readme\",\n  \"funding\": [\n    {\n      \"type\": \"github\",\n      \"url\": \"https://github.com/sponsors/fastify\"\n    },\n    {\n      \"type\": \"opencollective\",\n      \"url\": \"https://opencollective.com/fastify\"\n    }\n  ],\n  \"bugs\": {\n    \"url\": \"https://github.com/fastify/fluent-json-schema/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fastify/fluent-json-schema.git\"\n  },\n  \"scripts\": {\n    \"lint\": \"eslint\",\n    \"lint:fix\": \"eslint --fix\",\n    \"test\": \"npm run test:unit && npm run test:typescript\",\n    \"test:unit\": \"c8 --100 node --test\",\n    \"test:typescript\": \"tsd\",\n    \"doc\": \"jsdoc2md ./src/*.js > docs/API.md\"\n  },\n  \"devDependencies\": {\n    \"ajv\": \"^8.12.0\",\n    \"ajv-formats\": \"^3.0.1\",\n    \"c8\": \"^11.0.0\",\n    \"eslint\": \"^9.17.0\",\n    \"jsdoc-to-markdown\": \"^9.0.0\",\n    \"lodash.merge\": \"^4.6.2\",\n    \"neostandard\": \"^0.13.0\",\n    \"tsd\": \"^0.33.0\"\n  },\n  \"dependencies\": {\n    \"@fastify/deepmerge\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/ArraySchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst { setAttribute, isFluentSchema, FluentSchemaError } = require('./utils')\n\nconst initialState = {\n  // $schema: 'http://json-schema.org/draft-07/schema#',\n  type: 'array',\n  definitions: [],\n  properties: [],\n  required: []\n}\n\n/**\n * Represents a ArraySchema.\n * @param {Object} [options] - Options\n * @param {StringSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {ArraySchema}\n */\n// https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1\n// Factory Functions for Mixin Composition withBaseSchema\nconst ArraySchema = ({ schema = initialState, ...options } = {}) => {\n  options = {\n    generateIds: false,\n    factory: ArraySchema,\n    ...options\n  }\n  return {\n    ...BaseSchema({ ...options, schema }),\n\n    /**\n     * This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.\n     * If \"items\" is a schema, validation succeeds if all elements in the array successfully validate against that schema.\n     * If \"items\" is an array of schemas, validation succeeds if each element of the instance validates against the schema at the same position, if any.\n     * Omitting this keyword has the same behavior as an empty schema.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.1|reference}\n     * @param {FluentSchema|FluentSchema[]} items\n     * @returns {FluentSchema}\n     */\n\n    items: items => {\n      if (\n        !isFluentSchema(items) &&\n        !(\n          Array.isArray(items) &&\n          items.filter(v => isFluentSchema(v)).length > 0\n        )\n      ) { throw new FluentSchemaError(\"'items' must be a S or an array of S\") }\n      if (Array.isArray(items)) {\n        const values = items.map(v => {\n          const { $schema, ...rest } = v.valueOf()\n          return rest\n        })\n        return setAttribute({ schema, ...options }, ['items', values, 'array'])\n      }\n      const { $schema, ...rest } = items.valueOf()\n      return setAttribute({ schema, ...options }, [\n        'items',\n        { ...rest },\n        'array'\n      ])\n    },\n\n    /**\n     * This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.2|reference}\n     * @param {FluentSchema|boolean} items\n     * @returns {FluentSchema}\n     */\n\n    additionalItems: items => {\n      if (typeof items !== 'boolean' && !isFluentSchema(items)) {\n        throw new FluentSchemaError(\n          \"'additionalItems' must be a boolean or a S\"\n        )\n      }\n      if (items === false) {\n        return setAttribute({ schema, ...options }, [\n          'additionalItems',\n          false,\n          'array'\n        ])\n      }\n      const { $schema, ...rest } = items.valueOf()\n      return setAttribute({ schema, ...options }, [\n        'additionalItems',\n        { ...rest },\n        'array'\n      ])\n    },\n\n    /**\n     * An array instance is valid against \"contains\" if at least one of its elements is valid against the given schema.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.6|reference}\n     * @param {FluentSchema} value\n     * @returns {FluentSchema}\n     */\n\n    contains: value => {\n      if (!isFluentSchema(value)) { throw new FluentSchemaError(\"'contains' must be a S\") }\n      const {\n        $schema,\n        definitions,\n        properties,\n        required,\n        ...rest\n      } = value.valueOf()\n      return setAttribute({ schema, ...options }, [\n        'contains',\n        { ...rest },\n        'array'\n      ])\n    },\n\n    /**\n     * If this keyword has boolean value false, the instance validates successfully.\n     * If it has boolean value true, the instance validates successfully if all of its elements are unique.\n     * Omitting this keyword has the same behavior as a value of false.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.5|reference}\n     * @param {boolean} boolean\n     * @returns {FluentSchema}\n     */\n\n    uniqueItems: boolean => {\n      if (typeof boolean !== 'boolean') { throw new FluentSchemaError(\"'uniqueItems' must be a boolean\") }\n      return setAttribute({ schema, ...options }, [\n        'uniqueItems',\n        boolean,\n        'array'\n      ])\n    },\n\n    /**\n     * An array instance is valid against \"minItems\" if its size is greater than, or equal to, the value of this keyword.\n     * Omitting this keyword has the same behavior as a value of 0.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.4|reference}\n     * @param {number} min\n     * @returns {FluentSchema}\n     */\n\n    minItems: min => {\n      if (!Number.isInteger(min)) { throw new FluentSchemaError(\"'minItems' must be a integer\") }\n      return setAttribute({ schema, ...options }, ['minItems', min, 'array'])\n    },\n\n    /**\n     * An array instance is valid against \"minItems\" if its size is greater than, or equal to, the value of this keyword.\n     * Omitting this keyword has the same behavior as a value of 0.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4.3|reference}\n     * @param {number} max\n     * @returns {FluentSchema}\n     */\n\n    maxItems: max => {\n      if (!Number.isInteger(max)) { throw new FluentSchemaError(\"'maxItems' must be a integer\") }\n      return setAttribute({ schema, ...options }, ['maxItems', max, 'array'])\n    }\n  }\n}\n\nmodule.exports = {\n  ArraySchema,\n  default: ArraySchema\n}\n"
  },
  {
    "path": "src/ArraySchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { ArraySchema } = require('./ArraySchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('ArraySchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(ArraySchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      ArraySchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(ArraySchema().valueOf(), {\n        type: 'array'\n      })\n    })\n\n    it('from S', () => {\n      assert.deepStrictEqual(S.array().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'array'\n      })\n    })\n  })\n\n  describe('keywords:', () => {\n    describe('items', () => {\n      it('valid object', () => {\n        assert.deepStrictEqual(ArraySchema().items(S.number()).valueOf(), {\n          type: 'array',\n          items: { type: 'number' }\n        })\n      })\n      it('valid array', () => {\n        assert.deepStrictEqual(\n          ArraySchema().items([S.number(), S.string()]).valueOf(),\n          {\n            type: 'array',\n            items: [{ type: 'number' }, { type: 'string' }]\n          }\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().items(''),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'items' must be a S or an array of S\"\n        )\n      })\n    })\n\n    describe('additionalItems', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          ArraySchema()\n            .items([S.number(), S.string()])\n            .additionalItems(S.string())\n            .valueOf(),\n          {\n            type: 'array',\n            items: [{ type: 'number' }, { type: 'string' }],\n            additionalItems: { type: 'string' }\n          }\n        )\n      })\n      it('false', () => {\n        assert.deepStrictEqual(\n          ArraySchema()\n            .items([S.number(), S.string()])\n            .additionalItems(false)\n            .valueOf(),\n          {\n            type: 'array',\n            items: [{ type: 'number' }, { type: 'string' }],\n            additionalItems: false\n          }\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().additionalItems(''),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'additionalItems' must be a boolean or a S\"\n        )\n      })\n    })\n\n    describe('contains', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(ArraySchema().contains(S.string()).valueOf(), {\n          type: 'array',\n          contains: { type: 'string' }\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().contains('').valueOf(),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'contains' must be a S\"\n        )\n      })\n    })\n\n    describe('uniqueItems', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(ArraySchema().uniqueItems(true).valueOf(), {\n          type: 'array',\n          uniqueItems: true\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().uniqueItems('invalid').valueOf(),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'uniqueItems' must be a boolean\"\n        )\n      })\n    })\n\n    describe('minItems', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(ArraySchema().minItems(3).valueOf(), {\n          type: 'array',\n          minItems: 3\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().minItems('3').valueOf(),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minItems' must be a integer\"\n        )\n      })\n    })\n\n    describe('maxItems', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(ArraySchema().maxItems(5).valueOf(), {\n          type: 'array',\n          maxItems: 5\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => ArraySchema().maxItems('5').valueOf(),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maxItems' must be a integer\"\n        )\n      })\n    })\n\n    describe('raw', () => {\n      it('allows to add a custom attribute', () => {\n        const schema = ArraySchema().raw({ customKeyword: true }).valueOf()\n\n        assert.deepStrictEqual(schema, {\n          type: 'array',\n          customKeyword: true\n        })\n      })\n    })\n\n    describe('default array in an object', () => {\n      it('valid', () => {\n        const value = []\n        assert.deepStrictEqual(\n          S.object().prop('p1', ArraySchema().default(value)).valueOf()\n            .properties.p1.default,\n          value\n        )\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/BaseSchema.js",
    "content": "'use strict'\nconst {\n  flat,\n  omit,\n  isFluentSchema,\n  last,\n  isBoolean,\n  isUniq,\n  patchIdsWithParentId,\n  REQUIRED,\n  setAttribute,\n  setRaw,\n  setComposeType,\n  FluentSchemaError,\n  FLUENT_SCHEMA\n} = require('./utils')\n\nconst initialState = {\n  properties: [],\n  required: []\n}\n\n/**\n * Represents a BaseSchema.\n * @param {Object} [options] - Options\n * @param {BaseSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {BaseSchema}\n */\n\nconst BaseSchema = (\n  { schema = initialState, ...options } = {\n    generateIds: false,\n    factory: BaseSchema\n  }\n) => ({\n  [FLUENT_SCHEMA]: true,\n  isFluentSchema: true,\n  isFluentJSONSchema: true,\n\n  /**\n   * It defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.\n   *\n   * {@link https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2|reference}\n   * @param {string} id - an #id\n   * @returns {BaseSchema}\n   */\n\n  id: id => {\n    if (!id) {\n      throw new FluentSchemaError(\n        'id should not be an empty fragment <#> or an empty string <> (e.g. #myId)'\n      )\n    }\n    return setAttribute({ schema, ...options }, ['$id', id, 'any'])\n  },\n\n  /**\n   * It can be used to decorate a user interface with information about the data produced by this user interface. A title will preferably be short.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1|reference}\n   * @param {string} title\n   * @returns {BaseSchema}\n   */\n\n  title: title => {\n    return setAttribute({ schema, ...options }, ['title', title, 'any'])\n  },\n\n  /**\n   * It can be used to decorate a user interface with information about the data\n   * produced by this user interface. A description provides explanation about\n   * the purpose of the instance described by the schema.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.1|reference}\n   * @param {string} description\n   * @returns {BaseSchema}\n   */\n  description: description => {\n    return setAttribute({ schema, ...options }, [\n      'description',\n      description,\n      'any'\n    ])\n  },\n\n  /**\n   * The value of this keyword MUST be an array.\n   * There are no restrictions placed on the values within the array.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.4|reference}\n   * @param {string} examples\n   * @returns {BaseSchema}\n   */\n\n  examples: examples => {\n    if (!Array.isArray(examples)) {\n      throw new FluentSchemaError(\n        \"'examples' must be an array e.g. ['1', 'one', 'foo']\"\n      )\n    }\n    return setAttribute({ schema, ...options }, ['examples', examples, 'any'])\n  },\n\n  /**\n   * The value must be a valid id e.g. #properties/foo\n   *\n   * @param {string} ref\n   * @returns {BaseSchema}\n   */\n\n  ref: ref => {\n    return setAttribute({ schema, ...options }, ['$ref', ref, 'any'])\n  },\n\n  /**\n   * The value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.2|reference}\n   * @param {array} values\n   * @returns {BaseSchema}\n   */\n\n  enum: values => {\n    if (!Array.isArray(values)) {\n      throw new FluentSchemaError(\n        \"'enums' must be an array with at least an element e.g. ['1', 'one', 'foo']\"\n      )\n    }\n    return setAttribute({ schema, ...options }, ['enum', values, 'any'])\n  },\n\n  /**\n   * The value of this keyword MAY be of any type, including null.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.3|reference}\n   * @param value\n   * @returns {BaseSchema}\n   */\n\n  const: value => {\n    return setAttribute({ schema, ...options }, ['const', value, 'any'])\n  },\n\n  /**\n   * There are no restrictions placed on the value of this keyword.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.2|reference}\n   * @param defaults\n   * @returns {BaseSchema}\n   */\n\n  default: defaults => {\n    return setAttribute({ schema, ...options }, ['default', defaults, 'any'])\n  },\n\n  /**\n   * The value of readOnly can be left empty to indicate the property is readOnly.\n   * It takes an optional boolean which can be used to explicitly set readOnly true/false.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3|reference}\n   * @param {boolean|undefined} isReadOnly\n   * @returns {BaseSchema}\n   */\n\n  readOnly: isReadOnly => {\n    const value = isReadOnly !== undefined ? isReadOnly : true\n    return setAttribute({ schema, ...options }, ['readOnly', value, 'boolean'])\n  },\n\n  /**\n   * The value of writeOnly can be left empty to indicate the property is writeOnly.\n   * It takes an optional boolean which can be used to explicitly set writeOnly true/false.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.10.3|reference}\n   * @param {boolean|undefined} isWriteOnly\n   * @returns {BaseSchema}\n   */\n\n  writeOnly: isWriteOnly => {\n    const value = isWriteOnly !== undefined ? isWriteOnly : true\n    return setAttribute({ schema, ...options }, ['writeOnly', value, 'boolean'])\n  },\n\n  /**\n   * The value of deprecated can be left empty to indicate the property is deprecated.\n   * It takes an optional boolean which can be used to explicitly set deprecated true/false.\n   *\n   * {@link https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3|reference}\n   * @param {Boolean} isDeprecated\n   * @returns {BaseSchema}\n   */\n  deprecated: (isDeprecated) => {\n    if (isDeprecated && !isBoolean(isDeprecated)) throw new FluentSchemaError(\"'deprecated' must be a boolean value\")\n    const value = isDeprecated !== undefined ? isDeprecated : true\n    return setAttribute({ schema, ...options }, ['deprecated', value, 'boolean'])\n  },\n\n  /**\n   * Required has to be chained to a property:\n   * Examples:\n   * - S.prop('prop').required()\n   * - S.prop('prop', S.number()).required()\n   * - S.required(['foo', 'bar'])\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.3|reference}\n   * @returns {FluentSchema}\n   */\n  required: props => {\n    const currentProp = last(schema.properties)\n    const required = Array.isArray(props)\n      ? [...schema.required, ...props]\n      : currentProp\n        ? [...schema.required, currentProp.name]\n        : [REQUIRED]\n\n    if (!isUniq(required)) {\n      throw new FluentSchemaError(\"'required' has repeated keys, check your calls to .required()\")\n    }\n\n    return options.factory({\n      schema: { ...schema, required },\n      ...options\n    })\n  },\n\n  /**\n   * This keyword's value MUST be a valid JSON Schema.\n   * An instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.4|reference}\n   * @param {FluentSchema} not\n   * @returns {BaseSchema}\n   */\n  not: not => {\n    if (!isFluentSchema(not)) { throw new FluentSchemaError(\"'not' must be a BaseSchema\") }\n    const notSchema = omit(not.valueOf(), ['$schema', 'definitions'])\n\n    return BaseSchema({\n      schema: {\n        ...schema,\n        not: patchIdsWithParentId({\n          schema: notSchema,\n          ...options,\n          parentId: '#not'\n        })\n      },\n      ...options\n    })\n  },\n  // return setAttribute({ schema, ...options }, ['defaults', defaults, 'any'])\n\n  /**\n   * It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.2|reference}\n   * @param {array} schemas\n   * @returns {BaseSchema}\n   */\n\n  anyOf: schemas => setComposeType({ prop: 'anyOf', schemas, schema, options }),\n\n  /**\n   * It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.1|reference}\n   * @param {array} schemas\n   * @returns {BaseSchema}\n   */\n\n  allOf: schemas => setComposeType({ prop: 'allOf', schemas, schema, options }),\n\n  /**\n   * It MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7.3|reference}\n   * @param {array} schemas\n   * @returns {BaseSchema}\n   */\n\n  oneOf: schemas => setComposeType({ prop: 'oneOf', schemas, schema, options }),\n\n  /**\n   * @private set a property to a type. Use string number etc.\n   * @returns {BaseSchema}\n   */\n  as: type => setAttribute({ schema, ...options }, ['type', type]),\n\n  /**\n   * This validation outcome of this keyword's subschema has no direct effect on the overall validation result.\n   * Rather, it controls which of the \"then\" or \"else\" keywords are evaluated.\n   * When \"if\" is present, and the instance successfully validates against its subschema, then\n   * validation succeeds against this keyword if the instance also successfully validates against this keyword's subschema.\n   *\n   * @param {BaseSchema} ifClause\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.1|reference}\n   * @param {BaseSchema} thenClause\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.2|reference}\n   * @returns {BaseSchema}\n   */\n\n  ifThen: (ifClause, thenClause) => {\n    if (!isFluentSchema(ifClause)) { throw new FluentSchemaError(\"'ifClause' must be a BaseSchema\") }\n    if (!isFluentSchema(thenClause)) { throw new FluentSchemaError(\"'thenClause' must be a BaseSchema\") }\n\n    const ifClauseSchema = omit(ifClause.valueOf(), [\n      '$schema',\n      'definitions',\n      'type'\n    ])\n    const thenClauseSchema = omit(thenClause.valueOf(), [\n      '$schema',\n      'definitions',\n      'type'\n    ])\n\n    return options.factory({\n      schema: {\n        ...schema,\n        if: patchIdsWithParentId({\n          schema: ifClauseSchema,\n          ...options,\n          parentId: '#if'\n        }),\n        then: patchIdsWithParentId({\n          schema: thenClauseSchema,\n          ...options,\n          parentId: '#then'\n        })\n      },\n      ...options\n    })\n  },\n\n  /**\n   * When \"if\" is present, and the instance fails to validate against its subschema,\n   * then validation succeeds against this keyword if the instance successfully validates against this keyword's subschema.\n   *\n   * @param {BaseSchema} ifClause\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.1|reference}\n   * @param {BaseSchema} thenClause\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.2|reference}\n   * @param {BaseSchema} elseClause\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.6.3|reference}\n   * @returns {BaseSchema}\n   */\n\n  ifThenElse: (ifClause, thenClause, elseClause) => {\n    if (!isFluentSchema(ifClause)) { throw new FluentSchemaError(\"'ifClause' must be a BaseSchema\") }\n    if (!isFluentSchema(thenClause)) { throw new FluentSchemaError(\"'thenClause' must be a BaseSchema\") }\n    if (!isFluentSchema(elseClause)) {\n      throw new FluentSchemaError(\n        \"'elseClause' must be a BaseSchema or a false boolean value\"\n      )\n    }\n    const ifClauseSchema = omit(ifClause.valueOf(), [\n      '$schema',\n      'definitions',\n      'type'\n    ])\n    const thenClauseSchema = omit(thenClause.valueOf(), [\n      '$schema',\n      'definitions',\n      'type'\n    ])\n    const elseClauseSchema = omit(elseClause.valueOf(), [\n      '$schema',\n      'definitions',\n      'type'\n    ])\n\n    return options.factory({\n      schema: {\n        ...schema,\n        if: patchIdsWithParentId({\n          schema: ifClauseSchema,\n          ...options,\n          parentId: '#if'\n        }),\n        then: patchIdsWithParentId({\n          schema: thenClauseSchema,\n          ...options,\n          parentId: '#then'\n        }),\n        else: patchIdsWithParentId({\n          schema: elseClauseSchema,\n          ...options,\n          parentId: '#else'\n        })\n      },\n      ...options\n    })\n  },\n\n  /**\n   * Because the differences between JSON Schemas and Open API (Swagger)\n   * it can be handy to arbitrary modify the schema injecting a fragment\n   *\n   * * Examples:\n   * - S.number().raw({ nullable:true })\n   * - S.string().format('date').raw({ formatMaximum: '2020-01-01' })\n   *\n   * @param {string} fragment an arbitrary JSON Schema to inject\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.3|reference}\n   * @returns {BaseSchema}\n   */\n  raw: fragment => {\n    return setRaw({ schema, ...options }, fragment)\n  },\n\n  /**\n   * @private It returns the internal schema data structure\n   * @returns {object}\n   */\n  // TODO LS if we implement S.raw() we can drop this hack because from a JSON we can rebuild a fluent-json-schema\n  _getState: () => {\n    return schema\n  },\n\n  /**\n   * It returns all the schema values\n   *\n   * @param {Object} [options] - Options\n   * @param {boolean} [options.isRoot = true] - Is a root level schema\n   * @returns {object}\n   */\n  valueOf: ({ isRoot } = { isRoot: true }) => {\n    const { properties, definitions, required, $schema, ...rest } = schema\n\n    if (isRoot && required && !required.every((v) => typeof v === 'string')) {\n      throw new FluentSchemaError(\"'required' has called on root-level schema, check your calls to .required()\")\n    }\n\n    return Object.assign(\n      $schema ? { $schema } : {},\n      Object.keys(definitions || []).length > 0\n        ? { definitions: flat(definitions) }\n        : undefined,\n      { ...omit(rest, ['if', 'then', 'else']) },\n      Object.keys(properties || []).length > 0\n        ? { properties: flat(properties) }\n        : undefined,\n      required && required.length > 0 ? { required } : undefined,\n      schema.if ? { if: schema.if } : undefined,\n      schema.then ? { then: schema.then } : undefined,\n      schema.else ? { else: schema.else } : undefined\n    )\n  }\n})\n\nmodule.exports = {\n  BaseSchema,\n  default: BaseSchema\n}\n"
  },
  {
    "path": "src/BaseSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { BaseSchema } = require('./BaseSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('BaseSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(BaseSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      BaseSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  it('Expose legacy plain boolean', () => {\n    assert.notStrictEqual(BaseSchema().isFluentSchema, undefined)\n  })\n\n  it('Expose plain boolean', () => {\n    assert.notStrictEqual(BaseSchema().isFluentJSONSchema, undefined)\n  })\n\n  describe('factory', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(BaseSchema().valueOf(), {})\n    })\n\n    describe('factory', () => {\n      it('default', () => {\n        const title = 'title'\n        assert.deepStrictEqual(BaseSchema().title(title).valueOf(), {\n          title\n        })\n      })\n\n      it('override', () => {\n        const title = 'title'\n        assert.deepStrictEqual(\n          BaseSchema({ factory: BaseSchema }).title(title).valueOf(),\n          {\n            title\n          }\n        )\n      })\n    })\n  })\n\n  describe('keywords (any):', () => {\n    describe('id', () => {\n      const value = 'customId'\n      it('to root', () => {\n        assert.strictEqual(BaseSchema().id(value).valueOf().$id, value)\n      })\n\n      it('nested', () => {\n        assert.strictEqual(\n          S.object().prop('foo', BaseSchema().id(value).required()).valueOf()\n            .properties.foo.$id,\n          value\n        )\n      })\n\n      it('invalid', () => {\n        assert.throws(\n          () => BaseSchema().id(''),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              'id should not be an empty fragment <#> or an empty string <> (e.g. #myId)'\n        )\n      })\n    })\n\n    describe('title', () => {\n      const value = 'title'\n      it('adds to root', () => {\n        assert.strictEqual(BaseSchema().title(value).valueOf().title, value)\n      })\n    })\n\n    describe('description', () => {\n      it('add to root', () => {\n        const value = 'description'\n        assert.strictEqual(\n          BaseSchema().description(value).valueOf().description,\n          value\n        )\n      })\n    })\n\n    describe('examples', () => {\n      it('adds to root', () => {\n        const value = ['example']\n        assert.deepStrictEqual(\n          BaseSchema().examples(value).valueOf().examples,\n          value\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'examples'\n        assert.throws(\n          () => BaseSchema().examples(value).valueOf().examples,\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'examples' must be an array e.g. ['1', 'one', 'foo']\"\n        )\n      })\n    })\n\n    describe('required', () => {\n      it('in line valid', () => {\n        const prop = 'foo'\n        assert.deepStrictEqual(\n          S.object().prop(prop).required().valueOf().required,\n          [prop]\n        )\n      })\n      it('nested valid', () => {\n        const prop = 'foo'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.string().required().minLength(3)).valueOf()\n            .required,\n          [prop]\n        )\n      })\n\n      describe('unique keys on required', () => {\n        it('repeated calls to required()', () => {\n          assert.throws(\n            () => S.object().prop('A', S.string()).required().required(),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'required' has repeated keys, check your calls to .required()\"\n          )\n        })\n        it('repeated props on appendRequired()', () => {\n          assert.throws(\n            () =>\n              S.object()\n                .prop('A', S.string().required())\n                .prop('A', S.string().required()),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'required' has repeated keys, check your calls to .required()\"\n          )\n        })\n      })\n\n      it('root-level required', () => {\n        assert.throws(\n          () => S.object().required().valueOf(),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'required' has called on root-level schema, check your calls to .required()\"\n        )\n      })\n\n      describe('array', () => {\n        it('simple', () => {\n          const required = ['foo', 'bar']\n          assert.deepStrictEqual(S.required(required).valueOf(), {\n            required\n          })\n        })\n        it('nested', () => {\n          assert.deepStrictEqual(\n            S.object()\n              .prop('foo', S.string())\n              .prop('bar', S.string().required())\n              .required(['foo'])\n              .valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              properties: { bar: { type: 'string' }, foo: { type: 'string' } },\n              required: ['bar', 'foo'],\n              type: 'object'\n            }\n          )\n        })\n      })\n    })\n\n    describe('deprecated', () => {\n      it('valid', () => {\n        assert.strictEqual(\n          BaseSchema().deprecated(true).valueOf().deprecated,\n          true\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () =>\n            BaseSchema().deprecated('somethingNotBoolean').valueOf().deprecated,\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'deprecated' must be a boolean value\"\n        )\n      })\n      it('valid with no value', () => {\n        assert.strictEqual(BaseSchema().deprecated().valueOf().deprecated, true)\n      })\n      it('can be set to false', () => {\n        assert.strictEqual(\n          BaseSchema().deprecated(false).valueOf().deprecated,\n          false\n        )\n      })\n      it('property', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('foo', S.string())\n            .prop('bar', S.string().deprecated())\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              bar: { type: 'string', deprecated: true },\n              foo: { type: 'string' }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('object', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('foo', S.string())\n            .prop(\n              'bar',\n              S.object()\n                .deprecated()\n                .prop('raz', S.string())\n                .prop('iah', S.number())\n            )\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              foo: { type: 'string' },\n              bar: {\n                type: 'object',\n                deprecated: true,\n                properties: {\n                  raz: { $id: undefined, type: 'string' },\n                  iah: { $id: undefined, type: 'number' }\n                }\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('object property', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('foo', S.string())\n            .prop(\n              'bar',\n              S.object()\n                .prop('raz', S.string().deprecated())\n                .prop('iah', S.number())\n            )\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              foo: { type: 'string' },\n              bar: {\n                type: 'object',\n                properties: {\n                  raz: { $id: undefined, type: 'string', deprecated: true },\n                  iah: { $id: undefined, type: 'number' }\n                }\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('array', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('foo', S.string())\n            .prop('bar', S.array().deprecated().items(S.number()))\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            type: 'object',\n            properties: {\n              foo: { type: 'string' },\n              bar: {\n                type: 'array',\n                deprecated: true,\n                items: { type: 'number' }\n              }\n            }\n          }\n        )\n      })\n      it('array item', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('foo', S.string())\n            .prop(\n              'bar',\n              S.array().items([\n                S.object().prop('zoo', S.string()).prop('biz', S.string()),\n                S.object()\n                  .deprecated()\n                  .prop('zal', S.string())\n                  .prop('boz', S.string())\n              ])\n            )\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            type: 'object',\n            properties: {\n              foo: { type: 'string' },\n              bar: {\n                type: 'array',\n                items: [\n                  {\n                    type: 'object',\n                    properties: {\n                      zoo: { type: 'string' },\n                      biz: { type: 'string' }\n                    }\n                  },\n                  {\n                    type: 'object',\n                    deprecated: true,\n                    properties: {\n                      zal: { type: 'string' },\n                      boz: { type: 'string' }\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        )\n      })\n    })\n\n    describe('enum', () => {\n      it('valid', () => {\n        const value = ['VALUE']\n        assert.deepStrictEqual(BaseSchema().enum(value).valueOf().enum, value)\n      })\n\n      it('invalid', () => {\n        const value = 'VALUE'\n        assert.throws(\n          () => BaseSchema().enum(value).valueOf().examples,\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'enums' must be an array with at least an element e.g. ['1', 'one', 'foo']\"\n        )\n      })\n    })\n\n    describe('const', () => {\n      it('valid', () => {\n        const value = 'VALUE'\n        assert.strictEqual(BaseSchema().const(value).valueOf().const, value)\n      })\n    })\n\n    describe('default', () => {\n      it('valid', () => {\n        const value = 'VALUE'\n        assert.strictEqual(BaseSchema().default(value).valueOf().default, value)\n      })\n    })\n\n    describe('readOnly', () => {\n      it('valid', () => {\n        assert.strictEqual(BaseSchema().readOnly(true).valueOf().readOnly, true)\n      })\n      it('valid with no value', () => {\n        assert.strictEqual(BaseSchema().readOnly().valueOf().readOnly, true)\n      })\n      it('can be set to false', () => {\n        assert.strictEqual(\n          BaseSchema().readOnly(false).valueOf().readOnly,\n          false\n        )\n      })\n    })\n\n    describe('writeOnly', () => {\n      it('valid', () => {\n        assert.strictEqual(\n          BaseSchema().writeOnly(true).valueOf().writeOnly,\n          true\n        )\n      })\n      it('valid with no value', () => {\n        assert.strictEqual(BaseSchema().writeOnly().valueOf().writeOnly, true)\n      })\n      it('can be set to false', () => {\n        assert.strictEqual(\n          BaseSchema().writeOnly(false).valueOf().writeOnly,\n          false\n        )\n      })\n    })\n\n    describe('ref', () => {\n      it('base', () => {\n        const ref = 'myRef'\n        assert.deepStrictEqual(BaseSchema().ref(ref).valueOf(), { $ref: ref })\n      })\n\n      it('S', () => {\n        const ref = 'myRef'\n        assert.deepStrictEqual(S.ref(ref).valueOf(), {\n          $ref: ref\n        })\n      })\n    })\n  })\n\n  describe('combining keywords:', () => {\n    describe('allOf', () => {\n      it('base', () => {\n        assert.deepStrictEqual(\n          BaseSchema()\n            .allOf([BaseSchema().id('foo')])\n            .valueOf(),\n          {\n            allOf: [{ $id: 'foo' }]\n          }\n        )\n      })\n      it('S', () => {\n        assert.deepStrictEqual(S.allOf([S.id('foo')]).valueOf(), {\n          allOf: [{ $id: 'foo' }]\n        })\n      })\n      describe('invalid', () => {\n        it('not an array', () => {\n          assert.throws(\n            () => BaseSchema().allOf('test'),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'allOf' must be a an array of FluentSchema rather than a 'string'\"\n          )\n        })\n        it('not an array of FluentSchema', () => {\n          assert.throws(\n            () => BaseSchema().allOf(['test']),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'allOf' must be a an array of FluentSchema rather than a 'object'\"\n          )\n        })\n      })\n    })\n\n    describe('anyOf', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          BaseSchema()\n            .anyOf([BaseSchema().id('foo')])\n            .valueOf(),\n          {\n            anyOf: [{ $id: 'foo' }]\n          }\n        )\n      })\n      it('S nested', () => {\n        assert.deepStrictEqual(\n          S.object()\n            .prop('prop', S.anyOf([S.string(), S.null()]))\n            .valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: { anyOf: [{ type: 'string' }, { type: 'null' }] }\n            },\n            type: 'object'\n          }\n        )\n      })\n\n      it('S nested required', () => {\n        assert.deepStrictEqual(\n          S.object().prop('prop', S.anyOf([]).required()).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: { prop: {} },\n            required: ['prop'],\n            type: 'object'\n          }\n        )\n      })\n\n      describe('invalid', () => {\n        it('not an array', () => {\n          assert.throws(\n            () => BaseSchema().anyOf('test'),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'anyOf' must be a an array of FluentSchema rather than a 'string'\"\n          )\n        })\n        it('not an array of FluentSchema', () => {\n          assert.throws(\n            () => BaseSchema().anyOf(['test']),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'anyOf' must be a an array of FluentSchema rather than a 'object'\"\n          )\n        })\n      })\n    })\n\n    describe('oneOf', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          BaseSchema()\n            .oneOf([BaseSchema().id('foo')])\n            .valueOf(),\n          {\n            oneOf: [{ $id: 'foo' }]\n          }\n        )\n      })\n      describe('invalid', () => {\n        it('not an array', () => {\n          assert.throws(\n            () => BaseSchema().oneOf('test'),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'oneOf' must be a an array of FluentSchema rather than a 'string'\"\n          )\n        })\n        it('not an array of FluentSchema', () => {\n          assert.throws(\n            () => BaseSchema().oneOf(['test']),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'oneOf' must be a an array of FluentSchema rather than a 'object'\"\n          )\n        })\n      })\n    })\n\n    describe('not', () => {\n      describe('valid', () => {\n        it('simple', () => {\n          assert.deepStrictEqual(\n            BaseSchema().not(S.string().maxLength(10)).valueOf(),\n            {\n              not: { type: 'string', maxLength: 10 }\n            }\n          )\n        })\n\n        it('complex', () => {\n          assert.deepStrictEqual(\n            BaseSchema()\n              .not(BaseSchema().anyOf([BaseSchema().id('foo')]))\n              .valueOf(),\n            {\n              not: { anyOf: [{ $id: 'foo' }] }\n            }\n          )\n        })\n\n        // .prop('notTypeKey', S.not(S.string().maxLength(10))) => notTypeKey: { not: { type: 'string', \"maxLength\": 10 } }\n      })\n\n      it('invalid', () => {\n        assert.throws(\n          () => BaseSchema().not(undefined),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'not' must be a BaseSchema\"\n        )\n      })\n    })\n  })\n\n  describe('ifThen', () => {\n    describe('valid', () => {\n      it('returns a schema', () => {\n        const id = 'http://foo.com/user'\n        const schema = BaseSchema()\n          .id(id)\n          .title('A User')\n          .ifThen(BaseSchema().id(id), BaseSchema().description('A User desc'))\n          .valueOf()\n\n        assert.deepStrictEqual(schema, {\n          $id: 'http://foo.com/user',\n          title: 'A User',\n          if: { $id: 'http://foo.com/user' },\n          then: { description: 'A User desc' }\n        })\n      })\n\n      it('appends a prop after the clause', () => {\n        const id = 'http://foo.com/user'\n        const schema = S.object()\n          .id(id)\n          .title('A User')\n          .prop('bar')\n          .ifThen(\n            S.object().prop('foo', S.null()),\n            S.object().prop('bar', S.string().required())\n          )\n          .prop('foo')\n          .valueOf()\n\n        assert.deepStrictEqual(schema, {\n          $schema: 'http://json-schema.org/draft-07/schema#',\n          type: 'object',\n          $id: 'http://foo.com/user',\n          title: 'A User',\n          properties: { bar: {}, foo: {} },\n          if: { properties: { foo: { $id: undefined, type: 'null' } } },\n          then: {\n            properties: { bar: { $id: undefined, type: 'string' } },\n            required: ['bar']\n          }\n        })\n      })\n    })\n\n    describe('invalid', () => {\n      it('ifClause', () => {\n        assert.throws(\n          () =>\n            BaseSchema().ifThen(\n              undefined,\n              BaseSchema().description('A User desc')\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'ifClause' must be a BaseSchema\"\n        )\n      })\n      it('thenClause', () => {\n        assert.throws(\n          () => BaseSchema().ifThen(BaseSchema().id('id'), undefined),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'thenClause' must be a BaseSchema\"\n        )\n      })\n    })\n  })\n\n  describe('ifThenElse', () => {\n    describe('valid', () => {\n      it('returns a schema', () => {\n        const id = 'http://foo.com/user'\n        const schema = BaseSchema()\n          .id(id)\n          .title('A User')\n          .ifThenElse(\n            BaseSchema().id(id),\n            BaseSchema().description('then'),\n            BaseSchema().description('else')\n          )\n          .valueOf()\n\n        assert.deepStrictEqual(schema, {\n          $id: 'http://foo.com/user',\n          title: 'A User',\n          if: { $id: 'http://foo.com/user' },\n          then: { description: 'then' },\n          else: { description: 'else' }\n        })\n      })\n\n      it('appends a prop after the clause', () => {\n        const id = 'http://foo.com/user'\n        const schema = S.object()\n          .id(id)\n          .title('A User')\n          .prop('bar')\n          .ifThenElse(\n            S.object().prop('foo', S.null()),\n            S.object().prop('bar', S.string().required()),\n            S.object().prop('bar', S.string())\n          )\n          .prop('foo')\n          .valueOf()\n\n        assert.deepStrictEqual(schema, {\n          $schema: 'http://json-schema.org/draft-07/schema#',\n          type: 'object',\n          $id: 'http://foo.com/user',\n          title: 'A User',\n          properties: { bar: {}, foo: {} },\n          if: { properties: { foo: { $id: undefined, type: 'null' } } },\n          then: {\n            properties: { bar: { $id: undefined, type: 'string' } },\n            required: ['bar']\n          },\n          else: { properties: { bar: { $id: undefined, type: 'string' } } }\n        })\n      })\n\n      describe('invalid', () => {\n        it('ifClause', () => {\n          assert.throws(\n            () =>\n              BaseSchema().ifThenElse(\n                undefined,\n                BaseSchema().description('then'),\n                BaseSchema().description('else')\n              ),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message === \"'ifClause' must be a BaseSchema\"\n          )\n        })\n        it('thenClause', () => {\n          assert.throws(\n            () =>\n              BaseSchema().ifThenElse(\n                BaseSchema().id('id'),\n                undefined,\n                BaseSchema().description('else')\n              ),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message === \"'thenClause' must be a BaseSchema\"\n          )\n        })\n        it('elseClause', () => {\n          assert.throws(\n            () =>\n              BaseSchema().ifThenElse(\n                BaseSchema().id('id'),\n                BaseSchema().description('then'),\n                undefined\n              ),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'elseClause' must be a BaseSchema or a false boolean value\"\n          )\n        })\n      })\n    })\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const schema = BaseSchema()\n        .title('foo')\n        .raw({ customKeyword: true })\n        .valueOf()\n\n      assert.deepStrictEqual(schema, {\n        title: 'foo',\n        customKeyword: true\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/BooleanSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\n\nconst initialState = {\n  type: 'boolean'\n}\n\n/**\n * Represents a BooleanSchema.\n * @param {Object} [options] - Options\n * @param {StringSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {StringSchema}\n */\n\nconst BooleanSchema = ({ schema = initialState, ...options } = {}) => {\n  options = {\n    generateIds: false,\n    factory: BaseSchema,\n    ...options\n  }\n  return {\n    ...BaseSchema({ ...options, schema })\n  }\n}\n\nmodule.exports = {\n  BooleanSchema,\n  default: BooleanSchema\n}\n"
  },
  {
    "path": "src/BooleanSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { BooleanSchema } = require('./BooleanSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('BooleanSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(BooleanSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      BooleanSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(BooleanSchema().valueOf(), {\n        type: 'boolean'\n      })\n    })\n    it('from S', () => {\n      assert.deepStrictEqual(S.boolean().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'boolean'\n      })\n    })\n  })\n\n  it('sets a null type to the prop', () => {\n    assert.strictEqual(\n      S.object().prop('prop', S.boolean()).valueOf().properties.prop.type,\n      'boolean'\n    )\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const schema = BooleanSchema().raw({ customKeyword: true }).valueOf()\n\n      assert.deepStrictEqual(schema, {\n        type: 'boolean',\n        customKeyword: true\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/FluentJSONSchema.js",
    "content": "'use strict'\n\nconst { FORMATS, TYPES, FluentSchemaError } = require('./utils')\n\nconst { BaseSchema } = require('./BaseSchema')\nconst { NullSchema } = require('./NullSchema')\nconst { BooleanSchema } = require('./BooleanSchema')\nconst { StringSchema } = require('./StringSchema')\nconst { NumberSchema } = require('./NumberSchema')\nconst { IntegerSchema } = require('./IntegerSchema')\nconst { ObjectSchema } = require('./ObjectSchema')\nconst { ArraySchema } = require('./ArraySchema')\nconst { MixedSchema } = require('./MixedSchema')\nconst { RawSchema } = require('./RawSchema')\n\nconst initialState = {\n  $schema: 'http://json-schema.org/draft-07/schema#',\n  definitions: [],\n  properties: [],\n  required: []\n}\n\n/**\n * Represents a S.\n * @param {Object} [options] - Options\n * @param {S} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {S}\n */\n\nconst S = (\n  { schema = initialState, ...options } = {\n    generateIds: false,\n    factory: BaseSchema\n  }\n) => ({\n  ...BaseSchema({ ...options, schema }),\n\n  /**\n   * Set a property to type string\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3|reference}\n   * @returns {StringSchema}\n   */\n\n  string: () =>\n    StringSchema({\n      ...options,\n      schema,\n      factory: StringSchema\n    }).as('string'),\n\n  /**\n   * Set a property to type number\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric|reference}\n   * @returns {NumberSchema}\n   */\n\n  number: () =>\n    NumberSchema({\n      ...options,\n      schema,\n      factory: NumberSchema\n    }).as('number'),\n\n  /**\n   * Set a property to type integer\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#numeric|reference}\n   * @returns {IntegerSchema}\n   */\n\n  integer: () =>\n    IntegerSchema({\n      ...options,\n      schema,\n      factory: IntegerSchema\n    }).as('integer'),\n\n  /**\n   * Set a property to type boolean\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.7|reference}\n   * @returns {BooleanSchema}\n   */\n\n  boolean: () =>\n    BooleanSchema({\n      ...options,\n      schema,\n      factory: BooleanSchema\n    }).as('boolean'),\n\n  /**\n   * Set a property to type array\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.4|reference}\n   * @returns {ArraySchema}\n   */\n\n  array: () =>\n    ArraySchema({\n      ...options,\n      schema,\n      factory: ArraySchema\n    }).as('array'),\n\n  /**\n   * Set a property to type object\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5|reference}\n   * @returns {ObjectSchema}\n   */\n\n  object: baseSchema =>\n    ObjectSchema({\n      ...options,\n      schema: baseSchema || schema,\n      factory: ObjectSchema\n    }).as('object'),\n\n  /**\n   * Set a property to type null\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#general|reference}\n   * @returns {NullSchema}\n   */\n\n  null: () =>\n    NullSchema({\n      ...options,\n      schema,\n      factory: NullSchema\n    }).null(),\n\n  /**\n   * A mixed schema is the union of multiple types (e.g. ['string', 'integer']\n   *\n   * @param {Array.<string>} types\n   * @returns {MixedSchema}\n   */\n\n  mixed: types => {\n    if (\n      !Array.isArray(types) ||\n      (Array.isArray(types) &&\n        types.filter(t => !Object.values(TYPES).includes(t)).length > 0)\n    ) {\n      throw new FluentSchemaError(\n        `Invalid 'types'. It must be an array of types. Valid types are ${Object.values(\n          TYPES\n        ).join(' | ')}`\n      )\n    }\n\n    return MixedSchema({\n      ...options,\n      schema: {\n        ...schema,\n        type: types\n      },\n      factory: MixedSchema\n    })\n  },\n\n  /**\n   * Because the differences between JSON Schemas and Open API (Swagger)\n   * it can be handy to arbitrary modify the schema injecting a fragment\n   *\n   * * Examples:\n   * - S.raw({ nullable:true, format: 'date', formatMaximum: '2020-01-01' })\n   * - S.string().format('date').raw({ formatMaximum: '2020-01-01' })\n   *\n   * @param {string} fragment an arbitrary JSON Schema to inject\n   * @returns {BaseSchema}\n   */\n\n  raw: fragment => {\n    return RawSchema(fragment)\n  }\n})\n\nconst fluentSchema = {\n  ...BaseSchema(),\n  FORMATS,\n  TYPES,\n  FluentSchemaError,\n  withOptions: S,\n  string: () => S().string(),\n  mixed: types => S().mixed(types),\n  object: () => S().object(),\n  array: () => S().array(),\n  boolean: () => S().boolean(),\n  integer: () => S().integer(),\n  number: () => S().number(),\n  null: () => S().null(),\n  raw: fragment => S().raw(fragment)\n}\nmodule.exports = fluentSchema\nmodule.exports.default = fluentSchema\nmodule.exports.S = fluentSchema\n"
  },
  {
    "path": "src/FluentSchema.integration.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst Ajv = require('ajv')\n\nconst basic = require('./schemas/basic')\nconst S = require('./FluentJSONSchema')\n\n// TODO pick some ideas from here:https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/tests/draft7\n\ndescribe('S', () => {\n  it('compiles', () => {\n    const ajv = new Ajv()\n    const schema = S.valueOf()\n    const validate = ajv.compile(schema)\n    const valid = validate({})\n    assert.ok(valid)\n  })\n\n  describe('basic', () => {\n    const ajv = new Ajv()\n    const schema = S.object()\n      .prop('username', S.string())\n      .prop('password', S.string())\n      .valueOf()\n    const validate = ajv.compile(schema)\n\n    it('valid', () => {\n      const valid = validate({\n        username: 'username',\n        password: 'password'\n      })\n      assert.ok(valid)\n    })\n\n    it('invalid', () => {\n      const valid = validate({\n        username: 'username',\n        password: 1\n      })\n      assert.deepStrictEqual(validate.errors, [\n        {\n          instancePath: '/password',\n          keyword: 'type',\n          message: 'must be string',\n          params: { type: 'string' },\n          schemaPath: '#/properties/password/type'\n        }\n      ])\n      assert.ok(!valid)\n    })\n  })\n\n  describe('ifThen', () => {\n    const ajv = new Ajv()\n    const schema = S.object()\n      .prop('prop', S.string().maxLength(5))\n      .ifThen(\n        S.object().prop('prop', S.string().maxLength(5)),\n        S.object().prop('extraProp', S.string()).required()\n      )\n      .valueOf()\n    const validate = ajv.compile(schema)\n\n    it('valid', () => {\n      const valid = validate({\n        prop: '12345',\n        extraProp: 'foo'\n      })\n      assert.ok(valid)\n    })\n\n    it('invalid', () => {\n      const valid = validate({\n        prop: '12345'\n      })\n      assert.deepStrictEqual(validate.errors, [\n        {\n          instancePath: '',\n          keyword: 'required',\n          message: \"must have required property 'extraProp'\",\n          params: { missingProperty: 'extraProp' },\n          schemaPath: '#/then/required'\n        }\n      ])\n      assert.ok(!valid)\n    })\n  })\n\n  describe('ifThenElse', () => {\n    const ajv = new Ajv()\n\n    const VALUES = ['ONE', 'TWO']\n    const schema = S.object()\n      .prop('ifProp')\n      .ifThenElse(\n        S.object().prop('ifProp', S.string().enum([VALUES[0]])),\n        S.object().prop('thenProp', S.string()).required(),\n        S.object().prop('elseProp', S.string()).required()\n      )\n      .valueOf()\n\n    const validate = ajv.compile(schema)\n\n    it('then', () => {\n      const valid = validate({\n        ifProp: 'ONE',\n        thenProp: 'foo'\n      })\n      assert.ok(valid)\n    })\n\n    it('else', () => {\n      const valid = validate({\n        prop: '123456'\n      })\n      assert.deepStrictEqual(validate.errors, [\n        {\n          instancePath: '',\n          keyword: 'required',\n          message: \"must have required property 'thenProp'\",\n          params: { missingProperty: 'thenProp' },\n          schemaPath: '#/then/required'\n        }\n      ])\n      assert.ok(!valid)\n    })\n  })\n\n  describe('combine and definition', () => {\n    const ajv = new Ajv()\n    const schema = S.object() // FIXME LS it shouldn't be object()\n      .definition(\n        'address',\n        S.object()\n          .id('#/definitions/address')\n          .prop('street_address', S.string())\n          .required()\n          .prop('city', S.string())\n          .required()\n          .prop('state', S.string().required())\n      )\n      .allOf([\n        S.ref('#/definitions/address'),\n        S.object().prop('type', S.string()).enum(['residential', 'business'])\n      ])\n      .valueOf()\n    const validate = ajv.compile(schema)\n    it('matches', () => {\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        definitions: {\n          address: {\n            $id: '#/definitions/address',\n            type: 'object',\n            properties: {\n              street_address: { type: 'string' },\n              city: { type: 'string' },\n              state: { type: 'string' }\n            },\n            required: ['street_address', 'city', 'state']\n          }\n        },\n        allOf: [\n          { $ref: '#/definitions/address' },\n          {\n            type: 'object',\n            properties: {\n              type: { type: 'string', enum: ['residential', 'business'] }\n            }\n          }\n        ]\n      })\n    })\n\n    it('valid', () => {\n      const valid = validate({\n        street_address: 'via Paolo Rossi',\n        city: 'Topolinia',\n        state: 'Disney World',\n        type: 'business'\n      })\n      assert.strictEqual(validate.errors, null)\n      assert.ok(valid)\n    })\n  })\n\n  // https://github.com/fastify/fluent-json-schema/pull/40\n  describe('cloning objects retains boolean', () => {\n    const ajv = new Ajv()\n    const config = {\n      schema: S.object().prop('foo', S.string().enum(['foo']))\n    }\n    const _config = require('lodash.merge')({}, config)\n    const schema = _config.schema.valueOf()\n    const validate = ajv.compile(schema)\n    it('matches', () => {\n      assert.notStrictEqual(\n        config.schema[Symbol.for('fluent-schema-object')],\n        undefined\n      )\n      assert.ok(_config.schema.isFluentJSONSchema)\n      assert.strictEqual(\n        _config.schema[Symbol.for('fluent-schema-object')],\n        undefined\n      )\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        properties: {\n          foo: {\n            type: 'string',\n            enum: ['foo']\n          }\n        }\n      })\n    })\n\n    it('valid', () => {\n      const valid = validate({ foo: 'foo' })\n      assert.strictEqual(validate.errors, null)\n      assert.ok(valid)\n    })\n  })\n\n  describe('compose keywords', () => {\n    const ajv = new Ajv()\n    const schema = S.object()\n      .prop('foo', S.anyOf([S.string()]))\n      .prop('bar', S.not(S.anyOf([S.integer()])))\n      .prop('prop', S.allOf([S.string(), S.boolean()]))\n      .prop('anotherProp', S.oneOf([S.string(), S.boolean()]))\n      .required()\n      .valueOf()\n\n    const validate = ajv.compile(schema)\n\n    it('valid', () => {\n      const valid = validate({\n        foo: 'foo',\n        anotherProp: true\n      })\n      assert.ok(valid)\n    })\n\n    it('invalid', () => {\n      const valid = validate({\n        foo: 'foo',\n        bar: 1\n      })\n      assert.ok(!valid)\n    })\n  })\n\n  describe('compose ifThen', () => {\n    const ajv = new Ajv()\n    const schema = S.object()\n      .prop('foo', S.string().default(false).required())\n      .prop('bar', S.string().default(false).required())\n      .prop('thenFooA', S.string())\n      .prop('thenFooB', S.string())\n      .allOf([\n        S.ifThen(\n          S.object().prop('foo', S.string()).enum(['foo']),\n          S.required(['thenFooA', 'thenFooB'])\n        ),\n        S.ifThen(\n          S.object().prop('bar', S.string()).enum(['BAR']),\n          S.required(['thenBarA', 'thenBarB'])\n        )\n      ])\n      .valueOf()\n\n    const validate = ajv.compile(schema)\n    it('matches', () => {\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        allOf: [\n          {\n            if: {\n              properties: {\n                foo: { $id: undefined, enum: ['foo'], type: 'string' }\n              }\n            },\n            then: { required: ['thenFooA', 'thenFooB'] }\n          },\n          {\n            if: {\n              properties: {\n                bar: { $id: undefined, enum: ['BAR'], type: 'string' }\n              }\n            },\n            then: { required: ['thenBarA', 'thenBarB'] }\n          }\n        ],\n        properties: {\n          bar: { default: false, type: 'string' },\n          foo: { default: false, type: 'string' },\n          thenFooA: { type: 'string' },\n          thenFooB: { type: 'string' }\n        },\n        required: ['foo', 'bar'],\n        type: 'object'\n      })\n    })\n\n    it('valid', () => {\n      const valid = validate({\n        foo: 'foo',\n        thenFooA: 'thenFooA',\n        thenFooB: 'thenFooB',\n        bar: 'BAR',\n        thenBarA: 'thenBarA',\n        thenBarB: 'thenBarB'\n      })\n      assert.strictEqual(validate.errors, null)\n      assert.ok(valid)\n    })\n  })\n\n  describe('complex', () => {\n    const ajv = new Ajv()\n    const schema = S.object()\n      .id('http://foo.com/user')\n      .title('A User')\n      .description('A User desc')\n      .definition(\n        'address',\n        S.object()\n          .id('#address')\n          .prop('country', S.string())\n          .prop('city', S.string())\n          .prop('zipcode', S.string())\n      )\n      .prop('username', S.string())\n      .required()\n      .prop('password', S.string().required())\n      .prop('address', S.object().ref('#address'))\n\n      .required()\n      .prop(\n        'role',\n        S.object()\n          .id('http://foo.com/role')\n          .required()\n          .prop('name', S.string())\n          .prop('permissions')\n      )\n      .prop('age', S.number())\n      .valueOf()\n    const validate = ajv.compile(schema)\n    it('valid', () => {\n      const valid = validate({\n        username: 'aboutlo',\n        password: 'pwsd',\n        address: {\n          country: 'Italy',\n          city: 'Milan',\n          zipcode: '20100'\n        },\n        role: {\n          name: 'admin',\n          permissions: 'read:write'\n        },\n        age: 30\n      })\n      assert.ok(valid)\n    })\n\n    describe('invalid', () => {\n      const model = {\n        username: 'aboutlo',\n        password: 'pswd',\n        address: {\n          country: 'Italy',\n          city: 'Milan',\n          zipcode: '20100'\n        },\n        role: {\n          name: 'admin',\n          permissions: 'read:write'\n        },\n        age: 30\n      }\n      it('password', () => {\n        const { password, ...data } = model\n        const valid = validate(data)\n        assert.deepStrictEqual(validate.errors, [\n          {\n            instancePath: '',\n            keyword: 'required',\n            message: \"must have required property 'password'\",\n            params: { missingProperty: 'password' },\n            schemaPath: '#/required'\n          }\n        ])\n        assert.ok(!valid)\n      })\n      it('address', () => {\n        const { address, ...data } = model\n        const valid = validate({\n          ...data,\n          address: {\n            ...address,\n            city: 1234\n          }\n        })\n        assert.deepStrictEqual(validate.errors, [\n          {\n            instancePath: '/address/city',\n            keyword: 'type',\n            message: 'must be string',\n            params: { type: 'string' },\n            schemaPath: '#address/properties/city/type'\n          }\n        ])\n        assert.ok(!valid)\n      })\n    })\n  })\n\n  describe('basic.json', () => {\n    it('generate', () => {\n      const [step] = basic\n      assert.deepStrictEqual(\n        S.array()\n          .title('Product set')\n          .items(\n            S.object()\n              .title('Product')\n              .prop(\n                'uuid',\n                S.number()\n                  .description('The unique identifier for a product')\n                  .required()\n              )\n              .prop('name', S.string())\n              .required()\n              .prop('price', S.number().exclusiveMinimum(0).required())\n              .prop(\n                'tags',\n                S.array().items(S.string()).minItems(1).uniqueItems(true)\n              )\n              .prop(\n                'dimensions',\n                S.object()\n                  .prop('length', S.number().required())\n                  .prop('width', S.number().required())\n                  .prop('height', S.number().required())\n              )\n              .prop(\n                'warehouseLocation',\n                S.string().description(\n                  'Coordinates of the warehouse with the product'\n                )\n              )\n          )\n          .valueOf(),\n        {\n          ...step.schema,\n          items: {\n            ...step.schema.items,\n            properties: {\n              ...step.schema.items.properties,\n              dimensions: {\n                ...step.schema.items.properties.dimensions,\n                properties: {\n                  length: { $id: undefined, type: 'number' },\n                  width: { $id: undefined, type: 'number' },\n                  height: { $id: undefined, type: 'number' }\n                }\n              }\n            }\n          }\n        }\n      )\n    })\n  })\n\n  describe('raw', () => {\n    describe('swaggger', () => {\n      describe('nullable', () => {\n        it('allows nullable', () => {\n          const ajv = new Ajv()\n          const schema = S.object()\n            .prop('foo', S.raw({ nullable: true, type: 'string' }))\n            .valueOf()\n          const validate = ajv.compile(schema)\n          const valid = validate({\n            test: null\n          })\n          assert.strictEqual(validate.errors, null)\n          assert.ok(valid)\n        })\n      })\n    })\n\n    describe('ajv', () => {\n      describe('formatMaximum', () => {\n        it('checks custom keyword formatMaximum', () => {\n          const ajv = new Ajv()\n          require('ajv-formats')(ajv)\n          /*        const schema = S.string()\n            .raw({ nullable: false })\n            .valueOf() */\n          // { type: 'number', nullable: true }\n          const schema = S.object()\n            .prop(\n              'birthday',\n              S.raw({\n                format: 'date',\n                formatMaximum: '2020-01-01',\n                type: 'string'\n              })\n            )\n            .valueOf()\n\n          const validate = ajv.compile(schema)\n          const valid = validate({\n            birthday: '2030-01-01'\n          })\n          assert.deepStrictEqual(validate.errors, [\n            {\n              instancePath: '/birthday',\n              keyword: 'formatMaximum',\n              message: 'should be <= 2020-01-01',\n              params: {\n                comparison: '<=',\n                limit: '2020-01-01'\n              },\n              schemaPath: '#/properties/birthday/formatMaximum'\n            }\n          ])\n          assert.ok(!valid)\n        })\n        it('checks custom keyword larger with $data', () => {\n          const ajv = new Ajv({ $data: true })\n          require('ajv-formats')(ajv)\n          /*        const schema = S.string()\n            .raw({ nullable: false })\n            .valueOf() */\n          // { type: 'number', nullable: true }\n          const schema = S.object()\n            .prop('smaller', S.number().raw({ maximum: { $data: '1/larger' } }))\n            .prop('larger', S.number())\n            .valueOf()\n\n          const validate = ajv.compile(schema)\n          const valid = validate({\n            smaller: 10,\n            larger: 7\n          })\n          assert.deepStrictEqual(validate.errors, [\n            {\n              instancePath: '/smaller',\n              keyword: 'maximum',\n              message: 'must be <= 7',\n              params: {\n                comparison: '<=',\n                limit: 7\n              },\n              schemaPath: '#/properties/smaller/maximum'\n            }\n          ])\n          assert.ok(!valid)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/FluentSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst S = require('./FluentJSONSchema')\n\ndescribe('S', () => {\n  it('defined', () => {\n    assert.notStrictEqual(S, undefined)\n  })\n\n  describe('factory', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(S.object().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object'\n      })\n    })\n\n    describe('generatedIds', () => {\n      describe('properties', () => {\n        it('true', () => {\n          assert.deepStrictEqual(\n            S.withOptions({ generateIds: true })\n              .object()\n              .prop('prop', S.string())\n              .valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              properties: { prop: { $id: '#properties/prop', type: 'string' } },\n              type: 'object'\n            }\n          )\n        })\n\n        it('false', () => {\n          assert.deepStrictEqual(\n            S.object().prop('prop', S.string()).valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              properties: { prop: { type: 'string' } },\n              type: 'object'\n            }\n          )\n        })\n\n        describe('nested', () => {\n          it('true', () => {\n            assert.deepStrictEqual(\n              S.withOptions({ generateIds: true })\n                .object()\n                .prop('foo', S.object().prop('bar', S.string()).required())\n                .valueOf(),\n              {\n                $schema: 'http://json-schema.org/draft-07/schema#',\n                properties: {\n                  foo: {\n                    $id: '#properties/foo',\n                    properties: {\n                      bar: {\n                        $id: '#properties/foo/properties/bar',\n                        type: 'string'\n                      }\n                    },\n                    required: ['bar'],\n                    type: 'object'\n                  }\n                },\n                type: 'object'\n              }\n            )\n          })\n          it('false', () => {\n            const id = 'myId'\n            assert.deepStrictEqual(\n              S.object()\n                .prop(\n                  'foo',\n                  S.object()\n                    .prop('bar', S.string().id(id))\n\n                    .required()\n                )\n                .valueOf(),\n              {\n                $schema: 'http://json-schema.org/draft-07/schema#',\n                properties: {\n                  foo: {\n                    properties: {\n                      bar: { $id: 'myId', type: 'string' }\n                    },\n                    required: ['bar'],\n                    type: 'object'\n                  }\n                },\n                type: 'object'\n              }\n            )\n          })\n        })\n      })\n      // TODO LS not sure the test makes sense\n      describe('definitions', () => {\n        it('true', () => {\n          assert.deepStrictEqual(\n            S.withOptions({ generateIds: true })\n              .object()\n              .definition(\n                'entity',\n                S.object().prop('foo', S.string()).prop('bar', S.string())\n              )\n              .prop('prop')\n              .ref('entity')\n              .valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              definitions: {\n                entity: {\n                  $id: '#definitions/entity',\n                  properties: {\n                    bar: {\n                      type: 'string'\n                    },\n                    foo: {\n                      type: 'string'\n                    }\n                  },\n                  type: 'object'\n                }\n              },\n              properties: {\n                prop: {\n                  $ref: 'entity'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n\n        it('false', () => {\n          assert.deepStrictEqual(\n            S.withOptions({ generateIds: false })\n              .object()\n              .definition(\n                'entity',\n                S.object().id('myCustomId').prop('foo', S.string())\n              )\n              .prop('prop')\n              .ref('entity')\n              .valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              definitions: {\n                entity: {\n                  $id: 'myCustomId',\n                  properties: {\n                    foo: { type: 'string' }\n                  },\n                  type: 'object'\n                }\n              },\n              properties: {\n                prop: {\n                  $ref: 'entity'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n\n        it('nested', () => {\n          const id = 'myId'\n          assert.deepStrictEqual(\n            S.object()\n              .prop('foo', S.object().prop('bar', S.string().id(id)).required())\n              .valueOf(),\n            {\n              $schema: 'http://json-schema.org/draft-07/schema#',\n              properties: {\n                foo: {\n                  properties: {\n                    bar: { $id: 'myId', type: 'string' }\n                  },\n                  required: ['bar'],\n                  type: 'object'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n      })\n    })\n  })\n\n  describe('composition', () => {\n    it('anyOf', () => {\n      const schema = S.object()\n        .prop('foo', S.anyOf([S.string()]))\n        .valueOf()\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        properties: { foo: { anyOf: [{ type: 'string' }] } },\n        type: 'object'\n      })\n    })\n\n    it('oneOf', () => {\n      const schema = S.object()\n        .prop(\n          'multipleRestrictedTypesKey',\n          S.oneOf([S.string(), S.number().minimum(10)])\n        )\n        .prop('notTypeKey', S.not(S.oneOf([S.string().pattern('js$')])))\n        .valueOf()\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        properties: {\n          multipleRestrictedTypesKey: {\n            oneOf: [{ type: 'string' }, { minimum: 10, type: 'number' }]\n          },\n          notTypeKey: { not: { oneOf: [{ pattern: 'js$', type: 'string' }] } }\n        },\n        type: 'object'\n      })\n    })\n  })\n\n  it('valueOf', () => {\n    assert.deepStrictEqual(S.object().prop('foo', S.string()).valueOf(), {\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      properties: { foo: { type: 'string' } },\n      type: 'object'\n    })\n  })\n\n  it('works', () => {\n    const schema = S.object()\n      .id('http://foo.com/user')\n      .title('A User')\n      .description('A User desc')\n      .definition(\n        'address',\n        S.object()\n          .id('#address')\n          .prop('country', S.string())\n          .prop('city', S.string())\n          .prop('zipcode', S.string())\n      )\n      .prop('username', S.string())\n      .required()\n      .prop('password', S.string())\n      .required()\n      .prop('address', S.ref('#address'))\n\n      .required()\n      .prop(\n        'role',\n        S.object()\n          .id('http://foo.com/role')\n          .prop('name', S.string())\n          .prop('permissions', S.string())\n      )\n      .required()\n      .prop('age', S.number())\n\n      .valueOf()\n\n    assert.deepStrictEqual(schema, {\n      definitions: {\n        address: {\n          type: 'object',\n          $id: '#address',\n          properties: {\n            country: {\n              type: 'string'\n            },\n            city: {\n              type: 'string'\n            },\n            zipcode: {\n              type: 'string'\n            }\n          }\n        }\n      },\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      type: 'object',\n      required: ['username', 'password', 'address', 'role'],\n      $id: 'http://foo.com/user',\n      title: 'A User',\n      description: 'A User desc',\n      properties: {\n        username: {\n          type: 'string'\n        },\n        password: {\n          type: 'string'\n        },\n        address: {\n          $ref: '#address'\n        },\n        age: {\n          type: 'number'\n        },\n        role: {\n          type: 'object',\n          $id: 'http://foo.com/role',\n          properties: {\n            name: {\n              $id: undefined,\n              type: 'string'\n            },\n            permissions: {\n              $id: undefined,\n              type: 'string'\n            }\n          }\n        }\n      }\n    })\n  })\n\n  describe('raw', () => {\n    describe('base', () => {\n      it('parses type', () => {\n        const input = S.enum(['foo']).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('adds an attribute', () => {\n        const input = S.enum(['foo']).valueOf()\n        const schema = S.raw(input)\n        const attribute = 'title'\n        const modified = schema.title(attribute)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          ...input,\n          title: attribute\n        })\n      })\n    })\n\n    describe('string', () => {\n      it('parses type', () => {\n        const input = S.string().valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('adds an attribute', () => {\n        const input = S.string().valueOf()\n        const schema = S.raw(input)\n        const modified = schema.minLength(3)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          minLength: 3,\n          ...input\n        })\n      })\n\n      it('parses a prop', () => {\n        const input = S.string().minLength(5).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n\n    describe('number', () => {\n      it('parses type', () => {\n        const input = S.number().valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('adds an attribute', () => {\n        const input = S.number().valueOf()\n        const schema = S.raw(input)\n        const modified = schema.maximum(3)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          maximum: 3,\n          ...input\n        })\n      })\n\n      it('parses a prop', () => {\n        const input = S.number().maximum(5).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n\n    describe('integer', () => {\n      it('parses type', () => {\n        const input = S.integer().valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('adds an attribute', () => {\n        const input = S.integer().valueOf()\n        const schema = S.raw(input)\n        const modified = schema.maximum(3)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          maximum: 3,\n          ...input\n        })\n      })\n\n      it('parses a prop', () => {\n        const input = S.integer().maximum(5).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n\n    describe('boolean', () => {\n      it('parses type', () => {\n        const input = S.boolean().valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n\n    describe('object', () => {\n      it('parses type', () => {\n        const input = S.object().valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('parses properties', () => {\n        const input = S.object().prop('foo').prop('bar', S.string()).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('parses nested properties', () => {\n        const input = S.object()\n          .prop('foo', S.object().prop('bar', S.string().minLength(3)))\n          .valueOf()\n        const schema = S.raw(input)\n        const modified = schema.prop('boom')\n        assert.ok(modified.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          ...input,\n          properties: {\n            ...input.properties,\n            boom: {}\n          }\n        })\n      })\n\n      it('parses definitions', () => {\n        const input = S.object().definition('foo', S.string()).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n\n    describe('array', () => {\n      it('parses type', () => {\n        const input = S.array().items(S.string()).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n\n      it('parses properties', () => {\n        const input = S.array().items(S.string()).valueOf()\n\n        const schema = S.raw(input).maxItems(1)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input,\n          maxItems: 1\n        })\n      })\n\n      it('parses nested properties', () => {\n        const input = S.array()\n          .items(\n            S.object().prop(\n              'foo',\n              S.object().prop('bar', S.string().minLength(3))\n            )\n          )\n          .valueOf()\n        const schema = S.raw(input)\n        const modified = schema.maxItems(1)\n        assert.ok(modified.isFluentSchema)\n        assert.deepStrictEqual(modified.valueOf(), {\n          ...input,\n          maxItems: 1\n        })\n      })\n\n      it('parses definitions', () => {\n        const input = S.object().definition('foo', S.string()).valueOf()\n        const schema = S.raw(input)\n        assert.ok(schema.isFluentSchema)\n        assert.deepStrictEqual(schema.valueOf(), {\n          ...input\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/IntegerSchema.js",
    "content": "'use strict'\nconst { NumberSchema } = require('./NumberSchema')\n\nconst initialState = {\n  type: 'integer'\n}\n\n/**\n * Represents a NumberSchema.\n * @param {Object} [options] - Options\n * @param {NumberSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {NumberSchema}\n */\n// https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1\n// Factory Functions for Mixin Composition withBaseSchema\nconst IntegerSchema = (\n  { schema, ...options } = {\n    schema: initialState,\n    generateIds: false,\n    factory: IntegerSchema\n  }\n) => ({\n  ...NumberSchema({ ...options, schema })\n})\n\nmodule.exports = {\n  IntegerSchema,\n  default: IntegerSchema\n}\n"
  },
  {
    "path": "src/IntegerSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { IntegerSchema } = require('./IntegerSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('IntegerSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(IntegerSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      IntegerSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(IntegerSchema().valueOf(), {\n        type: 'integer'\n      })\n    })\n\n    it('from S', () => {\n      assert.deepStrictEqual(S.integer().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'integer'\n      })\n    })\n  })\n\n  describe('keywords:', () => {\n    describe('minimum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.integer().minimum(5)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'integer',\n                minimum: 5\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid number', () => {\n        assert.throws(\n          () => S.integer().minimum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minimum' must be a Number\"\n        )\n      })\n      it('invalid integer', () => {\n        assert.throws(\n          () => S.integer().minimum(5.1),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minimum' must be an Integer\"\n        )\n      })\n    })\n    describe('maximum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.integer().maximum(5)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'integer',\n                maximum: 5\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid number', () => {\n        assert.throws(\n          () => S.integer().maximum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maximum' must be a Number\"\n        )\n      })\n      it('invalid float', () => {\n        assert.throws(\n          () => S.integer().maximum(5.1),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maximum' must be an Integer\"\n        )\n      })\n    })\n    describe('multipleOf', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.integer().multipleOf(5)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'integer',\n                multipleOf: 5\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.integer().multipleOf('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'multipleOf' must be a Number\"\n        )\n      })\n      it('invalid integer', () => {\n        assert.throws(\n          () => S.integer().multipleOf(5.1),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'multipleOf' must be an Integer\"\n        )\n      })\n    })\n\n    describe('exclusiveMinimum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.integer().exclusiveMinimum(5)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'integer',\n                exclusiveMinimum: 5\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid number', () => {\n        assert.throws(\n          () => S.integer().exclusiveMinimum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMinimum' must be a Number\"\n        )\n      })\n      it('invalid integer', () => {\n        assert.throws(\n          () => S.integer().exclusiveMinimum(5.1),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMinimum' must be an Integer\"\n        )\n      })\n    })\n    describe('exclusiveMaximum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.integer().exclusiveMaximum(5)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'integer',\n                exclusiveMaximum: 5\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid number', () => {\n        assert.throws(\n          () => S.integer().exclusiveMaximum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMaximum' must be a Number\"\n        )\n      })\n      it('invalid integer', () => {\n        assert.throws(\n          () => S.integer().exclusiveMaximum(5.1),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMaximum' must be an Integer\"\n        )\n      })\n    })\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const schema = IntegerSchema().raw({ customKeyword: true }).valueOf()\n\n      assert.deepStrictEqual(schema, {\n        type: 'integer',\n        customKeyword: true\n      })\n    })\n  })\n\n  it('works', () => {\n    const schema = S.object()\n      .id('http://foo.com/user')\n      .title('A User')\n      .description('A User desc')\n      .prop('age', S.integer().maximum(10))\n      .valueOf()\n\n    assert.deepStrictEqual(schema, {\n      $id: 'http://foo.com/user',\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      description: 'A User desc',\n      properties: { age: { maximum: 10, type: 'integer' } },\n      title: 'A User',\n      type: 'object'\n    })\n  })\n})\n"
  },
  {
    "path": "src/MixedSchema.js",
    "content": "'use strict'\nconst { NullSchema } = require('./NullSchema')\nconst { BooleanSchema } = require('./BooleanSchema')\nconst { StringSchema } = require('./StringSchema')\nconst { NumberSchema } = require('./NumberSchema')\nconst { IntegerSchema } = require('./IntegerSchema')\nconst { ObjectSchema } = require('./ObjectSchema')\nconst { ArraySchema } = require('./ArraySchema')\n\nconst { TYPES, FLUENT_SCHEMA } = require('./utils')\n\nconst initialState = {\n  type: [],\n  definitions: [],\n  properties: [],\n  required: []\n}\n\n/**\n * Represents a MixedSchema.\n * @param {Object} [options] - Options\n * @param {MixedSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {StringSchema}\n */\n\nconst MixedSchema = ({ schema = initialState, ...options } = {}) => {\n  options = {\n    generateIds: false,\n    factory: MixedSchema,\n    ...options\n  }\n  return {\n    [FLUENT_SCHEMA]: true,\n    ...(schema.type.includes(TYPES.STRING)\n      ? StringSchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.NUMBER)\n      ? NumberSchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.BOOLEAN)\n      ? BooleanSchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.INTEGER)\n      ? IntegerSchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.OBJECT)\n      ? ObjectSchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.ARRAY)\n      ? ArraySchema({ ...options, schema, factory: MixedSchema })\n      : {}),\n    ...(schema.type.includes(TYPES.NULL)\n      ? NullSchema({ ...options, schema, factory: MixedSchema })\n      : {})\n  }\n}\n\nmodule.exports = {\n  MixedSchema,\n  default: MixedSchema\n}\n"
  },
  {
    "path": "src/MixedSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { MixedSchema } = require('./MixedSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('MixedSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(MixedSchema, undefined)\n  })\n\n  it('Expose symbol / 1', () => {\n    assert.notStrictEqual(\n      MixedSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  it('Expose symbol / 2', () => {\n    const types = [\n      S.TYPES.STRING,\n      S.TYPES.NUMBER,\n      S.TYPES.BOOLEAN,\n      S.TYPES.INTEGER,\n      S.TYPES.OBJECT,\n      S.TYPES.ARRAY,\n      S.TYPES.NULL\n    ]\n    assert.notStrictEqual(\n      MixedSchema(types)[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('factory', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(MixedSchema().valueOf(), {\n        [Symbol.for('fluent-schema-object')]: true\n      })\n    })\n  })\n\n  describe('from S', () => {\n    it('valid', () => {\n      const types = [\n        S.TYPES.STRING,\n        S.TYPES.NUMBER,\n        S.TYPES.BOOLEAN,\n        S.TYPES.INTEGER,\n        S.TYPES.OBJECT,\n        S.TYPES.ARRAY,\n        S.TYPES.NULL\n      ]\n      assert.deepStrictEqual(S.mixed(types).valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: types\n      })\n    })\n    it('invalid param', () => {\n      const types = ''\n      assert.throws(\n        () => S.mixed(types),\n        (err) =>\n          err instanceof S.FluentSchemaError &&\n          err.message ===\n            \"Invalid 'types'. It must be an array of types. Valid types are string | number | boolean | integer | object | array | null\"\n      )\n    })\n\n    it('invalid type', () => {\n      const types = ['string', 'invalid']\n      assert.throws(\n        () => S.mixed(types),\n        (err) =>\n          err instanceof S.FluentSchemaError &&\n          err.message ===\n            \"Invalid 'types'. It must be an array of types. Valid types are string | number | boolean | integer | object | array | null\"\n      )\n    })\n  })\n\n  it('sets a type object to the prop', () => {\n    assert.deepStrictEqual(\n      S.object()\n        .prop(\n          'prop',\n          S.mixed([S.TYPES.STRING, S.TYPES.NUMBER]).minimum(10).maxLength(5)\n        )\n        .valueOf(),\n      {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        properties: {\n          prop: { maxLength: 5, minimum: 10, type: ['string', 'number'] }\n        },\n        type: 'object'\n      }\n    )\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const types = [S.TYPES.STRING, S.TYPES.NUMBER]\n\n      const schema = S.mixed(types).raw({ customKeyword: true }).valueOf()\n\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: ['string', 'number'],\n        customKeyword: true\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/NullSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst { setAttribute, FLUENT_SCHEMA } = require('./utils')\n\nconst initialState = {\n  type: 'null'\n}\n\n/**\n * Represents a NullSchema.\n * @param {Object} [options] - Options\n * @param {StringSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {StringSchema}\n */\n\nconst NullSchema = ({ schema = initialState, ...options } = {}) => {\n  options = {\n    generateIds: false,\n    factory: NullSchema,\n    ...options\n  }\n  const { valueOf, raw } = BaseSchema({ ...options, schema })\n  return {\n    valueOf,\n    raw,\n    [FLUENT_SCHEMA]: true,\n    isFluentSchema: true,\n\n    /**\n     * Set a property to type null\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.1.1|reference}\n     * @returns {FluentSchema}\n     */\n    null: () => setAttribute({ schema, ...options }, ['type', 'null'])\n  }\n}\n\nmodule.exports = {\n  NullSchema,\n  default: NullSchema\n}\n"
  },
  {
    "path": "src/NullSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { NullSchema } = require('./NullSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('NullSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(NullSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      NullSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(NullSchema().valueOf(), {\n        type: 'null'\n      })\n    })\n    it('from S', () => {\n      assert.deepStrictEqual(S.null().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'null'\n      })\n    })\n  })\n\n  it('sets a null type to the prop', () => {\n    assert.strictEqual(\n      S.object().prop('prop', S.null()).valueOf().properties.prop.type,\n      'null'\n    )\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const schema = NullSchema().raw({ customKeyword: true }).valueOf()\n\n      assert.deepStrictEqual(schema, {\n        type: 'null',\n        customKeyword: true\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/NumberSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst { setAttribute, FluentSchemaError } = require('./utils')\n\nconst initialState = {\n  type: 'number'\n}\n\n/**\n * Represents a NumberSchema.\n * @param {Object} [options] - Options\n * @param {NumberSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {NumberSchema}\n */\n// https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1\n// Factory Functions for Mixin Composition withBaseSchema\nconst NumberSchema = (\n  { schema, ...options } = {\n    schema: initialState,\n    generateIds: false,\n    factory: NumberSchema\n  }\n) => ({\n  ...BaseSchema({ ...options, schema }),\n\n  /**\n   * It represents  an inclusive lower limit for a numeric instance.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.4|reference}\n   * @param {number} min\n   * @returns {FluentSchema}\n   */\n\n  minimum: min => {\n    if (typeof min !== 'number') { throw new FluentSchemaError(\"'minimum' must be a Number\") }\n    if (schema.type === 'integer' && !Number.isInteger(min)) { throw new FluentSchemaError(\"'minimum' must be an Integer\") }\n    return setAttribute({ schema, ...options }, ['minimum', min, 'number'])\n  },\n\n  /**\n   * It represents an exclusive lower limit for a numeric instance.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.5|reference}\n   * @param {number} min\n   * @returns {FluentSchema}\n   */\n\n  exclusiveMinimum: min => {\n    if (typeof min !== 'number') { throw new FluentSchemaError(\"'exclusiveMinimum' must be a Number\") }\n    if (schema.type === 'integer' && !Number.isInteger(min)) { throw new FluentSchemaError(\"'exclusiveMinimum' must be an Integer\") }\n    return setAttribute({ schema, ...options }, [\n      'exclusiveMinimum',\n      min,\n      'number'\n    ])\n  },\n\n  /**\n   * It represents  an inclusive upper limit for a numeric instance.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.2|reference}\n   * @param {number} max\n   * @returns {FluentSchema}\n   */\n\n  maximum: max => {\n    if (typeof max !== 'number') { throw new FluentSchemaError(\"'maximum' must be a Number\") }\n    if (schema.type === 'integer' && !Number.isInteger(max)) { throw new FluentSchemaError(\"'maximum' must be an Integer\") }\n    return setAttribute({ schema, ...options }, ['maximum', max, 'number'])\n  },\n\n  /**\n   * It represents an exclusive upper limit for a numeric instance.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.3|reference}\n   * @param {number} max\n   * @returns {FluentSchema}\n   */\n\n  exclusiveMaximum: max => {\n    if (typeof max !== 'number') { throw new FluentSchemaError(\"'exclusiveMaximum' must be a Number\") }\n    if (schema.type === 'integer' && !Number.isInteger(max)) { throw new FluentSchemaError(\"'exclusiveMaximum' must be an Integer\") }\n    return setAttribute({ schema, ...options }, [\n      'exclusiveMaximum',\n      max,\n      'number'\n    ])\n  },\n\n  /**\n   * It's strictly greater than 0.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.2.1|reference}\n   * @param {number} multiple\n   * @returns {FluentSchema}\n   */\n\n  multipleOf: multiple => {\n    if (typeof multiple !== 'number') { throw new FluentSchemaError(\"'multipleOf' must be a Number\") }\n    if (schema.type === 'integer' && !Number.isInteger(multiple)) { throw new FluentSchemaError(\"'multipleOf' must be an Integer\") }\n    return setAttribute({ schema, ...options }, [\n      'multipleOf',\n      multiple,\n      'number'\n    ])\n  }\n})\n\nmodule.exports = {\n  NumberSchema,\n  default: NumberSchema\n}\n"
  },
  {
    "path": "src/NumberSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { NumberSchema } = require('./NumberSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('NumberSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(NumberSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      NumberSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(NumberSchema().valueOf(), {\n        type: 'number'\n      })\n    })\n\n    it('from S', () => {\n      assert.deepStrictEqual(S.number().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'number'\n      })\n    })\n  })\n\n  describe('keywords:', () => {\n    describe('minimum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.number().minimum(5.1)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'number',\n                minimum: 5.1\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.number().minimum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minimum' must be a Number\"\n        )\n      })\n    })\n    describe('maximum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.number().maximum(5.1)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'number',\n                maximum: 5.1\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.number().maximum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maximum' must be a Number\"\n        )\n      })\n    })\n    describe('multipleOf', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.number().multipleOf(5.1)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'number',\n                multipleOf: 5.1\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.number().multipleOf('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'multipleOf' must be a Number\"\n        )\n      })\n    })\n\n    describe('exclusiveMinimum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.number().exclusiveMinimum(5.1)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'number',\n                exclusiveMinimum: 5.1\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.number().exclusiveMinimum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMinimum' must be a Number\"\n        )\n      })\n    })\n    describe('exclusiveMaximum', () => {\n      it('valid', () => {\n        const prop = 'prop'\n        assert.deepStrictEqual(\n          S.object().prop(prop, S.number().exclusiveMaximum(5.1)).valueOf(),\n          {\n            $schema: 'http://json-schema.org/draft-07/schema#',\n            properties: {\n              prop: {\n                type: 'number',\n                exclusiveMaximum: 5.1\n              }\n            },\n            type: 'object'\n          }\n        )\n      })\n      it('invalid value', () => {\n        assert.throws(\n          () => S.number().exclusiveMaximum('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'exclusiveMaximum' must be a Number\"\n        )\n      })\n    })\n\n    describe('raw', () => {\n      it('allows to add a custom attribute', () => {\n        const schema = NumberSchema().raw({ customKeyword: true }).valueOf()\n\n        assert.deepStrictEqual(schema, {\n          type: 'number',\n          customKeyword: true\n        })\n      })\n    })\n  })\n\n  it('works', () => {\n    const schema = S.object()\n      .id('http://foo.com/user')\n      .title('A User')\n      .description('A User desc')\n      .prop('age', S.number().maximum(10))\n      .valueOf()\n\n    assert.deepStrictEqual(schema, {\n      $id: 'http://foo.com/user',\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      description: 'A User desc',\n      properties: { age: { maximum: 10, type: 'number' } },\n      title: 'A User',\n      type: 'object'\n    })\n  })\n})\n"
  },
  {
    "path": "src/ObjectSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst {\n  omit,\n  setAttribute,\n  isFluentSchema,\n  hasCombiningKeywords,\n  patchIdsWithParentId,\n  appendRequired,\n  FluentSchemaError,\n  combineDeepmerge\n} = require('./utils')\n\nconst initialState = {\n  type: 'object',\n  definitions: [],\n  properties: [],\n  required: []\n}\n\n/**\n * Represents a ObjectSchema.\n * @param {Object} [options] - Options\n * @param {StringSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {StringSchema}\n */\n\nconst ObjectSchema = ({ schema = initialState, ...options } = {}) => {\n  // TODO LS think about default values and how pass all of them through the functions\n  options = {\n    generateIds: false,\n    factory: ObjectSchema,\n    ...options\n  }\n  return {\n    ...BaseSchema({ ...options, schema }),\n\n    /**\n     * It defines a URI for the schema, and the base URI that other URI references within the schema are resolved against.\n     * Calling `id`  on an ObjectSchema will alway set the id on the root of the object rather than in its \"properties\", which\n     * differs from other schema types.\n     *\n     * {@link https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8.2|reference}\n     * @param {string} id - an #id\n     **/\n    id: id => {\n      if (!id) {\n        throw new FluentSchemaError(\n          'id should not be an empty fragment <#> or an empty string <> (e.g. #myId)'\n        )\n      }\n      return options.factory({ schema: { ...schema, $id: id }, ...options })\n    },\n    /**\n     * This keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\n     * Validation with \"additionalProperties\" applies only to the child values of instance names that do not match any names in \"properties\",\n     * and do not match any regular expression in \"patternProperties\".\n     * For all such properties, validation succeeds if the child instance validates against the \"additionalProperties\" schema.\n     * Omitting this keyword has the same behavior as an empty schema.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.6|reference}\n     * @param {FluentSchema|boolean} value\n     * @returns {FluentSchema}\n     */\n\n    additionalProperties: value => {\n      if (typeof value === 'boolean') {\n        return setAttribute({ schema, ...options }, [\n          'additionalProperties',\n          value,\n          'object'\n        ])\n      }\n      if (isFluentSchema(value)) {\n        const { $schema, ...rest } = value.valueOf({ isRoot: false })\n        return setAttribute({ schema, ...options }, [\n          'additionalProperties',\n          { ...rest },\n          'array'\n        ])\n      }\n\n      throw new FluentSchemaError(\n        \"'additionalProperties' must be a boolean or a S\"\n      )\n    },\n\n    /**\n     * An object instance is valid against \"maxProperties\" if its number of properties is less than, or equal to, the value of this keyword.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.1|reference}\n     * @param {number} max\n     * @returns {FluentSchema}\n     */\n\n    maxProperties: max => {\n      if (!Number.isInteger(max)) { throw new FluentSchemaError(\"'maxProperties' must be a Integer\") }\n      return setAttribute({ schema, ...options }, [\n        'maxProperties',\n        max,\n        'object'\n      ])\n    },\n\n    /**\n     * An object instance is valid against \"minProperties\" if its number of properties is greater than, or equal to, the value of this keyword.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.2|reference}\n     * @param {number} min\n     * @returns {FluentSchema}\n     */\n\n    minProperties: min => {\n      if (!Number.isInteger(min)) { throw new FluentSchemaError(\"'minProperties' must be a Integer\") }\n      return setAttribute({ schema, ...options }, [\n        'minProperties',\n        min,\n        'object'\n      ])\n    },\n\n    /**\n     * Each property name of this object SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\n     * Each property value of this object MUST be a valid JSON Schema.\n     * This keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.\n     * Validation of the primitive instance type against this keyword always succeeds.\n     * Validation succeeds if, for each instance name that matches any regular expressions that appear as a property name in this keyword's value, the child instance for that name successfully validates against each schema that corresponds to a matching regular expression.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.5|reference}\n     * @param {object} opts\n     * @returns {FluentSchema}\n     */\n\n    patternProperties: opts => {\n      const values = Object.entries(opts).reduce((memo, [pattern, schema]) => {\n        if (!isFluentSchema(schema)) {\n          throw new FluentSchemaError(\n            \"'patternProperties' invalid options. Provide a valid map e.g. { '^fo.*$': S.string() }\"\n          )\n        }\n        memo[pattern] = omit(schema.valueOf({ isRoot: false }), ['$schema'])\n        return memo\n      }, {})\n      return setAttribute({ schema, ...options }, [\n        'patternProperties',\n        values,\n        'object'\n      ])\n    },\n\n    /**\n     * This keyword specifies rules that are evaluated if the instance is an object and contains a certain property.\n     * This keyword's value MUST be an object. Each property specifies a dependency. Each dependency value MUST be an array or a valid JSON Schema.\n     * If the dependency value is a subschema, and the dependency key is a property in the instance, the entire instance must validate against the dependency value.\n     * If the dependency value is an array, each element in the array, if any, MUST be a string, and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.7|reference}\n     * @param {object} opts\n     * @returns {FluentSchema}\n     */\n\n    dependencies: opts => {\n      const values = Object.entries(opts).reduce((memo, [prop, schema]) => {\n        if (!isFluentSchema(schema) && !Array.isArray(schema)) {\n          throw new FluentSchemaError(\n            \"'dependencies' invalid options. Provide a valid map e.g. { 'foo': ['bar'] } or { 'foo': S.string() }\"\n          )\n        }\n        memo[prop] = Array.isArray(schema)\n          ? schema\n          : omit(schema.valueOf({ isRoot: false }), ['$schema', 'type', 'definitions'])\n        return memo\n      }, {})\n      return setAttribute({ schema, ...options }, [\n        'dependencies',\n        values,\n        'object'\n      ])\n    },\n\n    /**\n     * The value of \"properties\" MUST be an object. Each dependency value MUST be an array.\n     * Each element in the array MUST be a string and MUST be unique. If the dependency key is a property in the instance, each of the items in the dependency value must be a property that exists in the instance.\n     *\n     * {@link https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.5.4|reference}\n     * @param {object} opts\n     * @returns {FluentSchema}\n     */\n\n    dependentRequired: opts => {\n      const values = Object.entries(opts).reduce((memo, [prop, schema]) => {\n        if (!Array.isArray(schema)) {\n          throw new FluentSchemaError(\n            \"'dependentRequired' invalid options. Provide a valid array e.g. { 'foo': ['bar'] }\"\n          )\n        }\n        memo[prop] = schema\n        return memo\n      }, {})\n\n      return setAttribute({ schema, ...options }, [\n        'dependentRequired',\n        values,\n        'object'\n      ])\n    },\n\n    /**\n     * The value of \"properties\" MUST be an object. The dependency value MUST be a valid JSON Schema.\n     * Each dependency key is a property in the instance and the entire instance must validate against the dependency value.\n     *\n     * {@link https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.2.4|reference}\n     * @param {object} opts\n     * @returns {FluentSchema}\n     */\n    dependentSchemas: opts => {\n      const values = Object.entries(opts).reduce((memo, [prop, schema]) => {\n        if (!isFluentSchema(schema)) {\n          throw new FluentSchemaError(\n            \"'dependentSchemas' invalid options. Provide a valid schema e.g. { 'foo': S.string() }\"\n          )\n        }\n\n        memo[prop] = omit(schema.valueOf({ isRoot: false }), ['$schema', 'type', 'definitions'])\n        return memo\n      }, {})\n\n      return setAttribute({ schema, ...options }, [\n        'dependentSchemas',\n        values,\n        'object'\n      ])\n    },\n\n    /**\n     * If the instance is an object, this keyword validates if every property name in the instance validates against the provided schema.\n     * Note the property name that the schema is testing will always be a string.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.8|reference}\n     * @param {FluentSchema} value\n     * @returns {FluentSchema}\n     */\n\n    propertyNames: value => {\n      if (!isFluentSchema(value)) { throw new FluentSchemaError(\"'propertyNames' must be a S\") }\n      return setAttribute({ schema, ...options }, [\n        'propertyNames',\n        omit(value.valueOf({ isRoot: false }), ['$schema']),\n        'object'\n      ])\n    },\n\n    /**\n     * The value of \"properties\" MUST be an object. Each value of this object MUST be a valid JSON Schema.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.5.4|reference}\n     * @param {string} name\n     * @param {FluentSchema} props\n     * @returns {FluentSchema}\n     */\n\n    prop: (name, props = {}) => {\n      if (Array.isArray(props) || typeof props !== 'object') {\n        throw new FluentSchemaError(\n          `'${name}' doesn't support value '${JSON.stringify(\n            props\n          )}'. Pass a FluentSchema object`\n        )\n      }\n      const target = props.def ? 'definitions' : 'properties'\n      let attributes = props.valueOf({ isRoot: false })\n\n      const { $ref, $id: attributeId, required, ...restAttributes } = attributes\n      const $id =\n        attributeId ||\n        (options.generateIds ? `#${target}/${name}` : undefined)\n      if (isFluentSchema(props)) {\n        attributes = patchIdsWithParentId({\n          schema: attributes,\n          parentId: $id,\n          ...options\n        })\n\n        const [schemaPatched, attributesPatched] = appendRequired({\n          schema,\n          attributes: {\n            ...attributes,\n            name\n          }\n        })\n\n        schema = schemaPatched\n        attributes = attributesPatched\n      }\n\n      const type = hasCombiningKeywords(attributes)\n        ? undefined\n        : attributes.type\n\n      // strip undefined values or empty arrays or internals\n      attributes = Object.entries({ ...attributes, $id, type }).reduce(\n        (memo, [key, value]) => {\n          if (\n            key !== '$schema' &&\n            key !== 'def' &&\n            value !== undefined &&\n            !(Array.isArray(value) && value.length === 0 && key !== 'default')\n          ) {\n            memo[key] = value\n          }\n          return memo\n        },\n        {}\n      )\n\n      return ObjectSchema({\n        schema: {\n          ...schema,\n          [target]: [\n            ...schema[target],\n            $ref ? { name, $ref, ...restAttributes } : { name, ...attributes }\n          ]\n        },\n        ...options\n      })\n    },\n\n    extend: base => {\n      if (!base) {\n        throw new FluentSchemaError(\"Schema can't be null or undefined\")\n      }\n      if (!base.isFluentSchema) {\n        throw new FluentSchemaError(\"Schema isn't FluentSchema type\")\n      }\n      const src = base._getState()\n      const extended = combineDeepmerge(src, schema)\n      const {\n        valueOf,\n        isFluentSchema,\n        FLUENT_SCHEMA,\n        _getState,\n        extend\n      } = ObjectSchema({ schema: extended, ...options })\n      return { valueOf, isFluentSchema, FLUENT_SCHEMA, _getState, extend }\n    },\n\n    /**\n     * Returns an object schema with only a subset of keys provided. If called on an ObjectSchema with an\n     * `$id`, it will be removed and the return value will be considered a new schema.\n     *\n     * @param properties a list of properties you want to keep\n     * @returns {ObjectSchema}\n     */\n    only: properties => {\n      return ObjectSchema({\n        schema: {\n          ...omit(schema, ['$id', 'properties']),\n          properties: schema.properties.filter(({ name }) => properties.includes(name)),\n          required: schema.required.filter(p => properties.includes(p))\n        },\n        ...options\n      })\n    },\n\n    /**\n     * Returns an object schema without a subset of keys provided. If called on an ObjectSchema with an\n     * `$id`, it will be removed and the return value will be considered a new schema.\n     *\n     * @param properties a list of properties you dont want to keep\n     * @returns {ObjectSchema}\n     */\n    without: properties => {\n      return ObjectSchema({\n        schema: {\n          ...omit(schema, ['$id', 'properties']),\n          properties: schema.properties.filter(({ name }) => !properties.includes(name)),\n          required: schema.required.filter(p => !properties.includes(p))\n        },\n        ...options\n      })\n    },\n\n    /**\n     * The \"definitions\" keywords provides a standardized location for schema authors to inline re-usable JSON Schemas into a more general schema.\n     * There are no restrictions placed on the values within the array.\n     *\n     * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.9|reference}\n     * @param {string} name\n     * @param {FluentSchema} props\n     * @returns {FluentSchema}\n     */\n    // FIXME LS move to BaseSchema and remove .prop\n    // TODO LS Is a definition a proper schema?\n    definition: (name, props = {}) =>\n      ObjectSchema({ schema, ...options }).prop(name, {\n        ...props.valueOf({ isRoot: false }),\n        def: true\n      })\n  }\n}\n\nmodule.exports = {\n  ObjectSchema,\n  default: ObjectSchema\n}\n"
  },
  {
    "path": "src/ObjectSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { ObjectSchema } = require('./ObjectSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('ObjectSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(ObjectSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      ObjectSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(ObjectSchema().valueOf(), {\n        // $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object'\n      })\n    })\n\n    describe('generatedIds', () => {\n      describe('properties', () => {\n        it('true', () => {\n          assert.deepStrictEqual(\n            ObjectSchema({ generateIds: true })\n              .prop('prop', S.string())\n              .valueOf(),\n            {\n              properties: { prop: { $id: '#properties/prop', type: 'string' } },\n              type: 'object'\n            }\n          )\n        })\n\n        it('false', () => {\n          assert.deepStrictEqual(ObjectSchema().prop('prop').valueOf(), {\n            properties: { prop: {} },\n            type: 'object'\n          })\n        })\n\n        describe('nested', () => {\n          it('true', () => {\n            assert.deepStrictEqual(\n              ObjectSchema({ generateIds: true })\n                .prop('foo', ObjectSchema().prop('bar').required())\n                .valueOf(),\n              {\n                properties: {\n                  foo: {\n                    $id: '#properties/foo',\n                    properties: {\n                      bar: {\n                        $id: '#properties/foo/properties/bar'\n                      }\n                    },\n                    required: ['bar'],\n                    type: 'object'\n                  }\n                },\n                type: 'object'\n              }\n            )\n          })\n          it('false', () => {\n            const id = 'myId'\n            assert.deepStrictEqual(\n              ObjectSchema()\n                .prop('foo', ObjectSchema().prop('bar', S.id(id)).required())\n                .valueOf(),\n              {\n                properties: {\n                  foo: {\n                    properties: {\n                      bar: { $id: 'myId' }\n                    },\n                    required: ['bar'],\n                    type: 'object'\n                  }\n                },\n                type: 'object'\n              }\n            )\n          })\n          it('invalid', () => {\n            assert.throws(\n              () => ObjectSchema().id(''),\n              (err) =>\n                err instanceof S.FluentSchemaError &&\n                err.message ===\n                  'id should not be an empty fragment <#> or an empty string <> (e.g. #myId)'\n            )\n          })\n        })\n      })\n\n      describe('definitions', () => {\n        it('true', () => {\n          assert.deepStrictEqual(\n            ObjectSchema({ generateIds: true })\n              .definition('entity', ObjectSchema().prop('foo').prop('bar'))\n              .prop('prop', S.ref('entity'))\n              .valueOf(),\n            {\n              definitions: {\n                entity: {\n                  $id: '#definitions/entity',\n                  properties: {\n                    bar: {},\n                    foo: {}\n                  },\n                  type: 'object'\n                }\n              },\n              properties: {\n                prop: {\n                  $ref: 'entity'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n\n        it('false', () => {\n          assert.deepStrictEqual(\n            ObjectSchema({ generateIds: false })\n              .definition('entity', ObjectSchema().id('myCustomId').prop('foo'))\n              .prop('prop', S.ref('entity'))\n              .valueOf(),\n            {\n              definitions: {\n                entity: {\n                  $id: 'myCustomId',\n                  properties: {\n                    foo: {}\n                  },\n                  type: 'object'\n                }\n              },\n              properties: {\n                prop: {\n                  $ref: 'entity'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n\n        it('nested', () => {\n          const id = 'myId'\n          assert.deepStrictEqual(\n            ObjectSchema()\n              .prop(\n                'foo',\n                ObjectSchema().prop('bar', S.string().id(id)).required()\n              )\n              .valueOf(),\n            {\n              properties: {\n                foo: {\n                  properties: {\n                    bar: { $id: 'myId', type: 'string' }\n                  },\n                  required: ['bar'],\n                  type: 'object'\n                }\n              },\n              type: 'object'\n            }\n          )\n        })\n      })\n    })\n  })\n\n  it('from S', () => {\n    assert.deepStrictEqual(S.object().valueOf(), {\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      type: 'object'\n    })\n  })\n\n  it('sets a type object to the prop', () => {\n    assert.strictEqual(\n      ObjectSchema().prop('prop', S.object()).valueOf().properties.prop.type,\n      'object'\n    )\n  })\n\n  it('valueOf', () => {\n    assert.deepStrictEqual(ObjectSchema().prop('foo', S.string()).valueOf(), {\n      properties: { foo: { type: 'string' } },\n      type: 'object'\n    })\n  })\n\n  describe('keywords:', () => {\n    describe('id', () => {\n      it('valid', () => {\n        const id = 'myId'\n        assert.deepStrictEqual(ObjectSchema().prop('prop').id(id).valueOf(), {\n          $id: id,\n          properties: { prop: {} },\n          type: 'object'\n        })\n      })\n\n      describe('nested', () => {\n        it('object', () => {\n          const id = 'myId'\n          assert.deepStrictEqual(\n            ObjectSchema().prop('foo', S.string().id(id)).valueOf().properties\n              .foo,\n            {\n              type: 'string',\n              $id: id\n            }\n          )\n        })\n\n        it('string', () => {\n          assert.deepStrictEqual(\n            ObjectSchema().prop('foo', S.string().title('Foo')).valueOf()\n              .properties,\n            {\n              foo: {\n                type: 'string',\n                title: 'Foo'\n              }\n            }\n          )\n        })\n      })\n    })\n\n    describe('properties', () => {\n      it('string', () => {\n        assert.deepStrictEqual(\n          ObjectSchema().prop('prop', S.string()).valueOf().properties,\n          {\n            prop: {\n              type: 'string'\n            }\n          }\n        )\n      })\n\n      describe('nested', () => {\n        it('object', () => {\n          assert.deepStrictEqual(\n            ObjectSchema().prop('foo', ObjectSchema().prop('bar')).valueOf()\n              .properties.foo.properties,\n            {\n              bar: { $id: undefined }\n            }\n          )\n        })\n\n        it('string', () => {\n          assert.deepStrictEqual(\n            ObjectSchema().prop('foo', S.string().title('Foo')).valueOf()\n              .properties,\n            {\n              foo: {\n                type: 'string',\n                title: 'Foo'\n              }\n            }\n          )\n        })\n      })\n      describe('invalid', () => {\n        it('throws an error passing a string as value', () => {\n          assert.throws(\n            () => ObjectSchema().prop('prop', 'invalid'),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'prop' doesn't support value '\\\"invalid\\\"'. Pass a FluentSchema object\"\n          )\n        })\n\n        it('throws an error passing a number as value', () => {\n          assert.throws(\n            () => ObjectSchema().prop('prop', 555),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'prop' doesn't support value '555'. Pass a FluentSchema object\"\n          )\n        })\n\n        it('throws an error passing an array as value', () => {\n          assert.throws(\n            () => ObjectSchema().prop('prop', []),\n            (err) =>\n              err instanceof S.FluentSchemaError &&\n              err.message ===\n                \"'prop' doesn't support value '[]'. Pass a FluentSchema object\"\n          )\n        })\n      })\n    })\n\n    describe('additionalProperties', () => {\n      it('true', () => {\n        const value = true\n        assert.deepStrictEqual(\n          ObjectSchema().additionalProperties(value).prop('prop').valueOf()\n            .additionalProperties,\n          value\n        )\n      })\n\n      it('false', () => {\n        const value = false\n        assert.deepStrictEqual(\n          ObjectSchema().additionalProperties(value).prop('prop').valueOf()\n            .additionalProperties,\n          value\n        )\n      })\n\n      it('object', () => {\n        assert.deepStrictEqual(\n          ObjectSchema().additionalProperties(S.string()).prop('prop').valueOf()\n            .additionalProperties,\n          { type: 'string' }\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').additionalProperties(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'additionalProperties' must be a boolean or a S\"\n        )\n      })\n    })\n\n    describe('maxProperties', () => {\n      it('valid', () => {\n        const value = 2\n        assert.deepStrictEqual(\n          ObjectSchema().maxProperties(value).prop('prop').valueOf()\n            .maxProperties,\n          value\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').maxProperties(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maxProperties' must be a Integer\"\n        )\n      })\n    })\n\n    describe('minProperties', () => {\n      it('valid', () => {\n        const value = 2\n        assert.deepStrictEqual(\n          ObjectSchema().minProperties(value).prop('prop').valueOf()\n            .minProperties,\n          value\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').minProperties(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minProperties' must be a Integer\"\n        )\n      })\n    })\n\n    describe('patternProperties', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .patternProperties({\n              '^fo.*$': S.string()\n            })\n            .prop('foo')\n            .valueOf(),\n          {\n            patternProperties: { '^fo.*$': { type: 'string' } },\n            properties: { foo: {} },\n            type: 'object'\n          }\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').patternProperties(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'patternProperties' invalid options. Provide a valid map e.g. { '^fo.*$': S.string() }\"\n        )\n      })\n    })\n\n    describe('dependencies', () => {\n      it('map of array', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .dependencies({\n              foo: ['bar']\n            })\n            .prop('foo')\n            .prop('bar')\n            .valueOf(),\n          {\n            dependencies: { foo: ['bar'] },\n            properties: {\n              bar: {},\n              foo: {}\n            },\n            type: 'object'\n          }\n        )\n      })\n\n      it('object', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .dependencies({\n              foo: ObjectSchema().prop('bar', S.string())\n            })\n            .prop('foo')\n            .valueOf(),\n          {\n            dependencies: {\n              foo: {\n                properties: {\n                  bar: { type: 'string' }\n                }\n              }\n            },\n            properties: { foo: {} },\n            type: 'object'\n          }\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').dependencies(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'dependencies' invalid options. Provide a valid map e.g. { 'foo': ['bar'] } or { 'foo': S.string() }\"\n        )\n      })\n    })\n\n    describe('dependentRequired', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .dependentRequired({\n              foo: ['bar']\n            })\n            .prop('foo')\n            .prop('bar')\n            .valueOf(),\n          {\n            type: 'object',\n            dependentRequired: {\n              foo: ['bar']\n            },\n            properties: {\n              foo: {},\n              bar: {}\n            }\n          }\n        )\n      })\n\n      it('invalid', () => {\n        const value = {\n          foo: ObjectSchema().prop('bar', S.string())\n        }\n\n        assert.throws(\n          () => {\n            assert.deepStrictEqual(\n              ObjectSchema().dependentRequired(value).prop('foo'),\n              value\n            )\n          },\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'dependentRequired' invalid options. Provide a valid array e.g. { 'foo': ['bar'] }\"\n        )\n      })\n    })\n\n    describe('dependentSchemas', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .dependentSchemas({\n              foo: ObjectSchema().prop('bar', S.string())\n            })\n            .prop('foo')\n            .valueOf(),\n          {\n            dependentSchemas: {\n              foo: {\n                properties: {\n                  bar: { type: 'string' }\n                }\n              }\n            },\n            properties: { foo: {} },\n            type: 'object'\n          }\n        )\n      })\n\n      it('invalid', () => {\n        const value = {\n          foo: ['bar']\n        }\n\n        assert.throws(\n          () => {\n            assert.deepStrictEqual(\n              ObjectSchema().dependentSchemas(value).prop('foo'),\n              value\n            )\n          },\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'dependentSchemas' invalid options. Provide a valid schema e.g. { 'foo': S.string() }\"\n        )\n      })\n    })\n\n    describe('propertyNames', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          ObjectSchema()\n            .propertyNames(S.string().format(S.FORMATS.EMAIL))\n            .prop('foo@bar.com')\n            .valueOf().propertyNames,\n          {\n            format: 'email',\n            type: 'string'\n          }\n        )\n      })\n\n      it('invalid', () => {\n        const value = 'invalid'\n        assert.throws(\n          () =>\n            assert.strictEqual(\n              ObjectSchema().prop('prop').propertyNames(value),\n              value\n            ),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'propertyNames' must be a S\"\n        )\n      })\n    })\n  })\n\n  describe('null', () => {\n    it('sets a type object from the root', () => {\n      assert.strictEqual(S.null().valueOf().type, 'null')\n    })\n\n    it('sets a type object from the prop', () => {\n      assert.strictEqual(\n        ObjectSchema().prop('value', S.null()).valueOf().properties.value.type,\n        'null'\n      )\n    })\n  })\n\n  describe('definition', () => {\n    it('add', () => {\n      assert.deepStrictEqual(\n        ObjectSchema()\n          .definition('foo', ObjectSchema().prop('foo').prop('bar'))\n          .valueOf().definitions,\n        {\n          foo: {\n            type: 'object',\n            properties: {\n              foo: {},\n              bar: {}\n            }\n          }\n        }\n      )\n    })\n\n    it('empty props', () => {\n      assert.deepStrictEqual(\n        ObjectSchema().definition('foo').valueOf().definitions,\n        {\n          foo: {}\n        }\n      )\n    })\n\n    it('with id', () => {\n      assert.deepStrictEqual(\n        ObjectSchema()\n          .definition(\n            'foo',\n            ObjectSchema().id('myDefId').prop('foo').prop('bar')\n          )\n          .valueOf().definitions,\n        {\n          foo: {\n            $id: 'myDefId',\n            type: 'object',\n            properties: {\n              foo: {},\n              bar: {}\n            }\n          }\n        }\n      )\n    })\n  })\n\n  describe('extend', () => {\n    it('extends a simple schema', () => {\n      const base = S.object()\n        .id('base')\n        .title('base')\n        .additionalProperties(false)\n        .prop('foo', S.string().minLength(5).required(true))\n\n      const extended = S.object()\n        .id('extended')\n        .title('extended')\n        .prop('bar', S.string().required())\n        .extend(base)\n      assert.deepStrictEqual(extended.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        $id: 'extended',\n        title: 'extended',\n        additionalProperties: false,\n        properties: {\n          foo: {\n            type: 'string',\n            minLength: 5\n          },\n          bar: {\n            type: 'string'\n          }\n        },\n        required: ['foo', 'bar'],\n        type: 'object'\n      })\n    })\n\n    it('extends a nested schema', () => {\n      const base = S.object()\n        .id('base')\n        .additionalProperties(false)\n        .prop(\n          'foo',\n          S.object().prop('id', S.string().format('uuid').required())\n        )\n        .prop('str', S.string().required())\n        .prop('bol', S.boolean().required())\n        .prop('num', S.integer().required())\n\n      const extended = S.object()\n        .id('extended')\n        .prop('bar', S.number())\n        .extend(base)\n\n      assert.deepStrictEqual(extended.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        $id: 'extended',\n        additionalProperties: false,\n        properties: {\n          foo: {\n            type: 'object',\n            properties: {\n              id: {\n                $id: undefined,\n                type: 'string',\n                format: 'uuid'\n              }\n            },\n            required: ['id']\n          },\n          bar: {\n            type: 'number'\n          },\n          str: {\n            type: 'string'\n          },\n          bol: {\n            type: 'boolean'\n          },\n          num: {\n            type: 'integer'\n          }\n        },\n        required: ['str', 'bol', 'num'],\n        type: 'object'\n      })\n    })\n    it('extends a schema with definitions', () => {\n      const base = S.object()\n        .id('base')\n        .additionalProperties(false)\n        .definition('def1', S.object().prop('some'))\n        .definition('def2', S.object().prop('somethingElse'))\n        .prop(\n          'foo',\n          S.object().prop('id', S.string().format('uuid').required())\n        )\n        .prop('str', S.string().required())\n        .prop('bol', S.boolean().required())\n        .prop('num', S.integer().required())\n\n      const extended = S.object()\n        .id('extended')\n        .definition('def1', S.object().prop('someExtended'))\n        .prop('bar', S.number())\n        .extend(base)\n\n      assert.deepStrictEqual(extended.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        definitions: {\n          def1: { type: 'object', properties: { some: {}, someExtended: {} } },\n          def2: { type: 'object', properties: { somethingElse: {} } }\n        },\n        type: 'object',\n        $id: 'extended',\n        additionalProperties: false,\n        properties: {\n          foo: {\n            type: 'object',\n            properties: {\n              id: { $id: undefined, type: 'string', format: 'uuid' }\n            },\n            required: ['id']\n          },\n          str: { type: 'string' },\n          bol: { type: 'boolean' },\n          num: { type: 'integer' },\n          bar: { type: 'number' }\n        },\n        required: ['str', 'bol', 'num']\n      })\n    })\n    it('extends a schema overriding the props', () => {\n      const base = S.object().prop('reason', S.string().title('title'))\n\n      const extended = S.object()\n        .prop('other')\n        .prop('reason', S.string().minLength(1))\n        .extend(base)\n\n      assert.deepStrictEqual(extended.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        properties: {\n          other: {},\n          reason: { title: 'title', type: 'string', minLength: 1 }\n        }\n      })\n    })\n    it('extends a chain of schemas overriding the props', () => {\n      const base = S.object().prop('reason', S.string().title('title'))\n\n      const extended = S.object()\n        .prop('other')\n        .prop('reason', S.string().minLength(1))\n        .extend(base)\n\n      const extendedAgain = S.object()\n        .prop('again')\n        .prop('reason', S.string().minLength(2))\n        .extend(extended)\n        .extend(S.object().prop('multiple'))\n\n      assert.deepStrictEqual(extendedAgain.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        properties: {\n          other: {},\n          again: {},\n          multiple: {},\n          reason: { title: 'title', type: 'string', minLength: 2 }\n        }\n      })\n    })\n\n    it('throws an error if a schema is not provided', () => {\n      assert.throws(\n        () => S.object().extend(),\n        (err) =>\n          err instanceof S.FluentSchemaError &&\n          err.message === \"Schema can't be null or undefined\"\n      )\n    })\n\n    it('throws an error if a schema is invalid', () => {\n      assert.throws(\n        () => S.object().extend('boom!'),\n        (err) =>\n          err instanceof S.FluentSchemaError &&\n          err.message === \"Schema isn't FluentSchema type\"\n      )\n    })\n\n    it('throws an error if you append a new prop after extend', () => {\n      assert.throws(\n        () => {\n          const base = S.object()\n          S.object().extend(base).prop('foo')\n        },\n        (err) =>\n          // err instanceof S.FluentSchemaError &&\n          err instanceof TypeError &&\n          err.message === 'S.object(...).extend(...).prop is not a function'\n      )\n    })\n  })\n\n  describe('only', () => {\n    it('returns a subset of the object', () => {\n      const base = S.object()\n        .id('base')\n        .title('base')\n        .prop('foo', S.string())\n        .prop('bar', S.string())\n        .prop('baz', S.string())\n        .prop(\n          'children',\n          S.object().prop('alpha', S.string()).prop('beta', S.string())\n        )\n\n      const only = base.only(['foo'])\n\n      assert.deepStrictEqual(only.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        title: 'base',\n        properties: {\n          foo: {\n            type: 'string'\n          }\n        },\n        type: 'object'\n      })\n    })\n\n    it('works correctly with required properties', () => {\n      const base = S.object()\n        .id('base')\n        .title('base')\n        .prop('foo', S.string().required())\n        .prop('bar', S.string())\n        .prop('baz', S.string().required())\n        .prop('qux', S.string())\n\n      const only = base.only(['foo', 'bar'])\n\n      assert.deepStrictEqual(only.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        title: 'base',\n        properties: {\n          foo: {\n            type: 'string'\n          },\n          bar: {\n            type: 'string'\n          }\n        },\n        required: ['foo'],\n        type: 'object'\n      })\n    })\n  })\n\n  describe('without', () => {\n    it('returns a subset of the object', () => {\n      const base = S.object()\n        .id('base')\n        .title('base')\n        .prop('foo', S.string())\n        .prop('bar', S.string())\n        .prop('baz', S.string())\n        .prop(\n          'children',\n          S.object().prop('alpha', S.string()).prop('beta', S.string())\n        )\n\n      const without = base.without(['foo', 'children'])\n\n      assert.deepStrictEqual(without.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        title: 'base',\n        properties: {\n          bar: {\n            type: 'string'\n          },\n          baz: {\n            type: 'string'\n          }\n        },\n        type: 'object'\n      })\n    })\n\n    it('works correctly with required properties', () => {\n      const base = S.object()\n        .id('base')\n        .title('base')\n        .prop('foo', S.string().required())\n        .prop('bar', S.string())\n        .prop('baz', S.string().required())\n        .prop('qux', S.string())\n\n      const without = base.without(['foo', 'bar'])\n\n      assert.deepStrictEqual(without.valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        title: 'base',\n        properties: {\n          baz: {\n            type: 'string'\n          },\n          qux: {\n            type: 'string'\n          }\n        },\n        required: ['baz'],\n        type: 'object'\n      })\n    })\n  })\n\n  describe('raw', () => {\n    it('allows to add a custom attribute', () => {\n      const schema = ObjectSchema().raw({ customKeyword: true }).valueOf()\n\n      assert.deepStrictEqual(schema, {\n        type: 'object',\n        customKeyword: true\n      })\n    })\n\n    it('Carry raw properties', () => {\n      const schema = S.object()\n        .prop('test', S.ref('foo').raw({ test: true }))\n        .valueOf()\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        properties: { test: { $ref: 'foo', test: true } }\n      })\n    })\n\n    it('Carry raw properties multiple props', () => {\n      const schema = S.object()\n        .prop('a', S.string())\n        .prop('test', S.ref('foo').raw({ test: true }))\n        .valueOf()\n      assert.deepStrictEqual(schema, {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'object',\n        properties: { a: { type: 'string' }, test: { $ref: 'foo', test: true } }\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/RawSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst { BooleanSchema } = require('./BooleanSchema')\nconst { StringSchema } = require('./StringSchema')\nconst { NumberSchema } = require('./NumberSchema')\nconst { IntegerSchema } = require('./IntegerSchema')\nconst { ObjectSchema } = require('./ObjectSchema')\nconst { ArraySchema } = require('./ArraySchema')\nconst { toArray, FluentSchemaError } = require('./utils')\n\n/**\n * Represents a raw JSON Schema that will be parsed\n * @param {Object} schema\n * @returns {FluentSchema}\n */\n\nconst RawSchema = (schema = {}) => {\n  if (typeof schema !== 'object') {\n    throw new FluentSchemaError('A fragment must be a JSON object')\n  }\n  const { type, definitions, properties, required, ...props } = schema\n  switch (schema.type) {\n    case 'string': {\n      const schema = {\n        type,\n        ...props\n      }\n      return StringSchema({ schema, factory: StringSchema })\n    }\n\n    case 'integer': {\n      const schema = {\n        type,\n        ...props\n      }\n      return IntegerSchema({ schema, factory: NumberSchema })\n    }\n    case 'number': {\n      const schema = {\n        type,\n        ...props\n      }\n      return NumberSchema({ schema, factory: NumberSchema })\n    }\n\n    case 'boolean': {\n      const schema = {\n        type,\n        ...props\n      }\n      return BooleanSchema({ schema, factory: BooleanSchema })\n    }\n\n    case 'object': {\n      const schema = {\n        type,\n        definitions: toArray(definitions) || [],\n        properties: toArray(properties) || [],\n        required: required || [],\n        ...props\n      }\n      return ObjectSchema({ schema, factory: ObjectSchema })\n    }\n\n    case 'array': {\n      const schema = {\n        type,\n        ...props\n      }\n      return ArraySchema({ schema, factory: ArraySchema })\n    }\n\n    default: {\n      const schema = {\n        ...props\n      }\n\n      return BaseSchema({\n        schema,\n        factory: BaseSchema\n      })\n    }\n  }\n}\n\nmodule.exports = {\n  RawSchema,\n  default: RawSchema\n}\n"
  },
  {
    "path": "src/RawSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { RawSchema } = require('./RawSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('RawSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(RawSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      RawSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('base', () => {\n    it('parses type', () => {\n      const input = S.enum(['foo']).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('adds an attribute', () => {\n      const input = S.enum(['foo']).valueOf()\n      const schema = RawSchema(input)\n      const attribute = 'title'\n      const modified = schema.title(attribute)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        ...input,\n        title: attribute\n      })\n    })\n\n    it(\"throws an exception if the input isn't an object\", () => {\n      assert.throws(\n        () => RawSchema('boom!'),\n        (err) =>\n          err instanceof S.FluentSchemaError &&\n          err.message === 'A fragment must be a JSON object'\n      )\n    })\n  })\n\n  describe('string', () => {\n    it('parses type', () => {\n      const input = S.string().valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('adds an attribute', () => {\n      const input = S.string().valueOf()\n      const schema = RawSchema(input)\n      const modified = schema.minLength(3)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        minLength: 3,\n        ...input\n      })\n    })\n\n    it('parses a prop', () => {\n      const input = S.string().minLength(5).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n\n  describe('number', () => {\n    it('parses type', () => {\n      const input = S.number().valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('adds an attribute', () => {\n      const input = S.number().valueOf()\n      const schema = RawSchema(input)\n      const modified = schema.maximum(3)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        maximum: 3,\n        ...input\n      })\n    })\n\n    it('parses a prop', () => {\n      const input = S.number().maximum(5).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n\n  describe('integer', () => {\n    it('parses type', () => {\n      const input = S.integer().valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('adds an attribute', () => {\n      const input = S.integer().valueOf()\n      const schema = RawSchema(input)\n      const modified = schema.maximum(3)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        maximum: 3,\n        ...input\n      })\n    })\n\n    it('parses a prop', () => {\n      const input = S.integer().maximum(5).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n\n  describe('boolean', () => {\n    it('parses type', () => {\n      const input = S.boolean().valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n\n  describe('object', () => {\n    it('parses type', () => {\n      const input = S.object().valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('parses properties', () => {\n      const input = S.object().prop('foo').prop('bar', S.string()).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('parses nested properties', () => {\n      const input = S.object()\n        .prop('foo', S.object().prop('bar', S.string().minLength(3)))\n        .valueOf()\n      const schema = RawSchema(input)\n      const modified = schema.prop('boom')\n      assert.ok(modified.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        ...input,\n        properties: {\n          ...input.properties,\n          boom: {}\n        }\n      })\n    })\n\n    it('parses definitions', () => {\n      const input = S.object().definition('foo', S.string()).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n\n  describe('array', () => {\n    it('parses type', () => {\n      const input = S.array().items(S.string()).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n\n    it('parses properties', () => {\n      const input = S.array().items(S.string()).valueOf()\n\n      const schema = RawSchema(input).maxItems(1)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input,\n        maxItems: 1\n      })\n    })\n\n    it('parses nested properties', () => {\n      const input = S.array()\n        .items(\n          S.object().prop(\n            'foo',\n            S.object().prop('bar', S.string().minLength(3))\n          )\n        )\n        .valueOf()\n      const schema = RawSchema(input)\n      const modified = schema.maxItems(1)\n      assert.ok(modified.isFluentSchema)\n      assert.deepStrictEqual(modified.valueOf(), {\n        ...input,\n        maxItems: 1\n      })\n    })\n\n    it('parses definitions', () => {\n      const input = S.object().definition('foo', S.string()).valueOf()\n      const schema = RawSchema(input)\n      assert.ok(schema.isFluentSchema)\n      assert.deepStrictEqual(schema.valueOf(), {\n        ...input\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/StringSchema.js",
    "content": "'use strict'\nconst { BaseSchema } = require('./BaseSchema')\nconst { FORMATS, setAttribute, FluentSchemaError } = require('./utils')\n\nconst initialState = {\n  type: 'string',\n  // properties: [], //FIXME it shouldn't be set for a string because it has only attributes\n  required: []\n}\n\n/**\n * Represents a StringSchema.\n * @param {Object} [options] - Options\n * @param {StringSchema} [options.schema] - Default schema\n * @param {boolean} [options.generateIds = false] - generate the id automatically e.g. #properties.foo\n * @returns {StringSchema}\n */\n// https://medium.com/javascript-scene/javascript-factory-functions-with-es6-4d224591a8b1\n// Factory Functions for Mixin Composition withBaseSchema\nconst StringSchema = (\n  { schema, ...options } = {\n    schema: initialState,\n    generateIds: false,\n    factory: StringSchema\n  }\n) => ({\n  ...BaseSchema({ ...options, schema }),\n  /* /!**\n   * Set a property to type string\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3|reference}\n   * @returns {StringSchema}\n   *!/\n\n  string: () =>\n    StringSchema({ schema: { ...schema }, ...options }).as('string'), */\n\n  /**\n   * A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword.\n   * The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.2|reference}\n   * @param {number} min\n   * @returns {StringSchema}\n   */\n\n  minLength: min => {\n    if (!Number.isInteger(min)) { throw new FluentSchemaError(\"'minLength' must be an Integer\") }\n    return setAttribute({ schema, ...options }, ['minLength', min, 'string'])\n  },\n\n  /**\n   * A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword.\n   * The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159].\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.1|reference}\n   * @param {number} max\n   * @returns {StringSchema}\n   */\n\n  maxLength: max => {\n    if (!Number.isInteger(max)) { throw new FluentSchemaError(\"'maxLength' must be an Integer\") }\n    return setAttribute({ schema, ...options }, ['maxLength', max, 'string'])\n  },\n\n  /**\n   * A string value can be RELATIVE_JSON_POINTER, JSON_POINTER, UUID, REGEX, IPV6, IPV4, HOSTNAME, EMAIL, URL, URI_TEMPLATE, URI_REFERENCE, URI, TIME, DATE,\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.7.3|reference}\n   * @param {string} format\n   * @returns {StringSchema}\n   */\n\n  format: format => {\n    if (!Object.values(FORMATS).includes(format)) {\n      throw new FluentSchemaError(\n        `'format' must be one of ${Object.values(FORMATS).join(', ')}`\n      )\n    }\n    return setAttribute({ schema, ...options }, ['format', format, 'string'])\n  },\n\n  /**\n   *  This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect.\n   *  A string instance is considered valid if the regular expression matches the instance successfully.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.6.3.3|reference}\n   * @param {string} pattern\n   * @returns {StringSchema}\n   */\n  pattern: pattern => {\n    if (!(typeof pattern === 'string') && !(pattern instanceof RegExp)) {\n      throw new FluentSchemaError(\n        '\\'pattern\\' must be a string or a RegEx (e.g. /.*/)'\n      )\n    }\n\n    if (pattern instanceof RegExp) {\n      const flags = new RegExp(pattern).flags\n      pattern = pattern\n        .toString()\n        .substr(1)\n        .replace(new RegExp(`/${flags}$`), '')\n    }\n\n    return setAttribute({ schema, ...options }, ['pattern', pattern, 'string'])\n  },\n\n  /**\n   *  If the instance value is a string, this property defines that the string SHOULD\n   *  be interpreted as binary data and decoded using the encoding named by this property.\n   *  RFC 2045, Sec 6.1 [RFC2045] lists the possible values for this property.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.3|reference}\n   * @param {string} encoding\n   * @returns {StringSchema}\n   */\n\n  contentEncoding: encoding => {\n    if (!(typeof encoding === 'string')) { throw new FluentSchemaError('\\'contentEncoding\\' must be a string') }\n    return setAttribute({ schema, ...options }, [\n      'contentEncoding',\n      encoding,\n      'string'\n    ])\n  },\n\n  /**\n   *  The value of this property must be a media type, as defined by RFC 2046 [RFC2046].\n   *  This property defines the media type of instances which this schema defines.\n   *\n   * {@link https://tools.ietf.org/id/draft-handrews-json-schema-validation-01.html#rfc.section.8.4|reference}\n   * @param {string} mediaType\n   * @returns {StringSchema}\n   */\n\n  contentMediaType: mediaType => {\n    if (!(typeof mediaType === 'string')) { throw new FluentSchemaError('\\'contentMediaType\\' must be a string') }\n    return setAttribute({ schema, ...options }, [\n      'contentMediaType',\n      mediaType,\n      'string'\n    ])\n  }\n})\n\nmodule.exports = {\n  StringSchema,\n  FORMATS,\n  default: StringSchema\n}\n"
  },
  {
    "path": "src/StringSchema.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { StringSchema, FORMATS } = require('./StringSchema')\nconst S = require('./FluentJSONSchema')\n\ndescribe('StringSchema', () => {\n  it('defined', () => {\n    assert.notStrictEqual(StringSchema, undefined)\n  })\n\n  it('Expose symbol', () => {\n    assert.notStrictEqual(\n      StringSchema()[Symbol.for('fluent-schema-object')],\n      undefined\n    )\n  })\n\n  describe('constructor', () => {\n    it('without params', () => {\n      assert.deepStrictEqual(StringSchema().valueOf(), {\n        type: 'string'\n      })\n    })\n\n    it('from S', () => {\n      assert.deepStrictEqual(S.string().valueOf(), {\n        $schema: 'http://json-schema.org/draft-07/schema#',\n        type: 'string'\n      })\n    })\n  })\n\n  describe('keywords:', () => {\n    describe('minLength', () => {\n      it('valid', () => {\n        const schema = S.object()\n          .prop('prop', StringSchema().minLength(5))\n          .valueOf()\n        assert.deepStrictEqual(schema, {\n          $schema: 'http://json-schema.org/draft-07/schema#',\n          properties: {\n            prop: {\n              type: 'string',\n              minLength: 5\n            }\n          },\n          type: 'object'\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => StringSchema().minLength('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'minLength' must be an Integer\"\n        )\n      })\n    })\n    describe('maxLength', () => {\n      it('valid', () => {\n        const schema = StringSchema().maxLength(10).valueOf()\n        assert.deepStrictEqual(schema, {\n          type: 'string',\n          maxLength: 10\n        })\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => StringSchema().maxLength('5.1'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'maxLength' must be an Integer\"\n        )\n      })\n    })\n    describe('format', () => {\n      it('valid FORMATS.DATE', () => {\n        assert.deepStrictEqual(StringSchema().format(FORMATS.DATE).valueOf(), {\n          type: 'string',\n          format: FORMATS.DATE\n        })\n      })\n      it('valid FORMATS.DATE_TIME', () => {\n        assert.deepStrictEqual(\n          StringSchema().format(FORMATS.DATE_TIME).valueOf(),\n          {\n            type: 'string',\n            format: 'date-time'\n          }\n        )\n      })\n      it('valid FORMATS.ISO_DATE_TIME', () => {\n        assert.deepStrictEqual(\n          StringSchema().format(FORMATS.ISO_DATE_TIME).valueOf(),\n          {\n            type: 'string',\n            format: 'iso-date-time'\n          }\n        )\n      })\n      it('valid FORMATS.ISO_TIME', () => {\n        assert.deepStrictEqual(\n          StringSchema().format(FORMATS.ISO_TIME).valueOf(),\n          {\n            type: 'string',\n            format: 'iso-time'\n          }\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => StringSchema().format('invalid'),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message ===\n              \"'format' must be one of relative-json-pointer, json-pointer, uuid, regex, ipv6, ipv4, hostname, email, url, uri-template, uri-reference, uri, time, date, date-time, iso-time, iso-date-time\"\n        )\n      })\n    })\n    describe('pattern', () => {\n      it('as a string', () => {\n        assert.deepStrictEqual(StringSchema().pattern('\\\\/.*\\\\/').valueOf(), {\n          type: 'string',\n          pattern: '\\\\/.*\\\\/'\n        })\n      })\n      it('as a regex without flags', () => {\n        assert.deepStrictEqual(\n          StringSchema()\n            .pattern(/\\/.*\\//)\n            .valueOf(),\n          {\n            type: 'string',\n            pattern: '\\\\/.*\\\\/'\n          }\n        )\n      })\n\n      it('as a regex with flags', () => {\n        assert.deepStrictEqual(\n          StringSchema()\n            .pattern(/\\/.*\\//gi)\n            .valueOf(),\n          {\n            type: 'string',\n            pattern: '\\\\/.*\\\\/'\n          }\n        )\n      })\n\n      it('invalid value', () => {\n        assert.throws(\n          () => StringSchema().pattern(1111),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'pattern' must be a string or a RegEx (e.g. /.*/)\"\n        )\n      })\n    })\n    describe('contentEncoding', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          StringSchema().contentEncoding('base64').valueOf(),\n          {\n            type: 'string',\n            contentEncoding: 'base64'\n          }\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => StringSchema().contentEncoding(1000),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'contentEncoding' must be a string\"\n        )\n      })\n    })\n    describe('contentMediaType', () => {\n      it('valid', () => {\n        assert.deepStrictEqual(\n          StringSchema().contentMediaType('image/png').valueOf(),\n          {\n            type: 'string',\n            contentMediaType: 'image/png'\n          }\n        )\n      })\n      it('invalid', () => {\n        assert.throws(\n          () => StringSchema().contentMediaType(1000),\n          (err) =>\n            err instanceof S.FluentSchemaError &&\n            err.message === \"'contentMediaType' must be a string\"\n        )\n      })\n    })\n\n    describe('raw', () => {\n      it('allows to add a custom attribute', () => {\n        const schema = StringSchema().raw({ customKeyword: true }).valueOf()\n\n        assert.deepStrictEqual(schema, {\n          type: 'string',\n          customKeyword: true\n        })\n      })\n      it('allows to mix custom attibutes with regular one', () => {\n        const schema = StringSchema()\n          .format('date')\n          .raw({ formatMaximum: '2020-01-01' })\n          .valueOf()\n\n        assert.deepStrictEqual(schema, {\n          type: 'string',\n          formatMaximum: '2020-01-01',\n          format: 'date'\n        })\n      })\n    })\n  })\n\n  it('works', () => {\n    const schema = S.object()\n      .id('http://bar.com/object')\n      .title('A object')\n      .description('A object desc')\n      .prop(\n        'name',\n        StringSchema()\n          .id('http://foo.com/string')\n          .title('A string')\n          .description('A string desc')\n          .pattern(/.*/g)\n          .format('date-time')\n      )\n      .valueOf()\n\n    assert.deepStrictEqual(schema, {\n      $id: 'http://bar.com/object',\n      $schema: 'http://json-schema.org/draft-07/schema#',\n      description: 'A object desc',\n      properties: {\n        name: {\n          $id: 'http://foo.com/string',\n          description: 'A string desc',\n          title: 'A string',\n          type: 'string',\n          format: 'date-time',\n          pattern: '.*'\n        }\n      },\n      title: 'A object',\n      type: 'object'\n    })\n  })\n})\n"
  },
  {
    "path": "src/example.js",
    "content": "'use strict'\nconst S = require('./FluentJSONSchema')\nconst Ajv = require('ajv')\n\nconst ROLES = {\n  ADMIN: 'ADMIN',\n  USER: 'USER'\n}\n\nconst schema = S.object()\n  .id('http://foo/user')\n  .title('My First Fluent JSON Schema')\n  .description('A simple user')\n  .prop(\n    'email',\n    S.string()\n      .format(S.FORMATS.EMAIL)\n      .required()\n  )\n  .prop(\n    'password',\n    S.string()\n      .minLength(8)\n      .required()\n  )\n  .prop(\n    'role',\n    S.string()\n      .enum(Object.values(ROLES))\n      .default(ROLES.USER)\n  )\n  .prop(\n    'birthday',\n    S.raw({ type: 'string', format: 'date', formatMaximum: '2020-01-01' }) // formatMaximum is an AJV custom keywords\n  )\n  .definition(\n    'address',\n    S.object()\n      .id('#address')\n      .prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable\n      .prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger  nullable\n      .prop('country', S.string())\n      .prop('city', S.string())\n      .prop('zipcode', S.string())\n      .required(['line1', 'country', 'city', 'zipcode'])\n  )\n  .prop('address', S.ref('#address'))\n\nconsole.log(JSON.stringify(schema.valueOf(), undefined, 2))\n\nconst ajv = new Ajv({ allErrors: true })\nconst validate = ajv.compile(schema.valueOf())\nlet user = {}\nlet valid = validate(user)\nconsole.log({ valid }) //= > {valid: false}\nconsole.log(validate.errors)\n/* [\n  {\n    keyword: 'required',\n    dataPath: '',\n    schemaPath: '#/required',\n    params: { missingProperty: 'email' },\n    message: \"should have required property 'email'\",\n  },\n  {\n    keyword: 'required',\n    dataPath: '',\n    schemaPath: '#/required',\n    params: { missingProperty: 'password' },\n    message: \"should have required property 'password'\",\n  },\n] */\n\nuser = { email: 'test', password: 'password' }\nvalid = validate(user)\nconsole.log({ valid }) //= > {valid: false}\nconsole.log(validate.errors)\n/*\n[ { keyword: 'format',\n    dataPath: '.email',\n    schemaPath: '#/properties/email/format',\n    params: { format: 'email' },\n    message: 'should match format \"email\"' } ]\n*/\n\nuser = { email: 'test@foo.com', password: 'password' }\nvalid = validate(user)\nconsole.log({ valid }) //= > {valid: true}\nconsole.log(validate.errors) // => null\n\nuser = { email: 'test@foo.com', password: 'password', address: { line1: '' } }\nvalid = validate(user)\nconsole.log({ valid }) //= > {valid: false}\nconsole.log(validate.errors)\n\n/*\n{ valid: false }\n[ { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'country' },\n    message: 'should have required property \\'country\\'' },\n  { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'city' },\n    message: 'should have required property \\'city\\'' },\n  { keyword: 'required',\n    dataPath: '.address',\n    schemaPath: '#definitions/address/required',\n    params: { missingProperty: 'zipcoce' },\n    message: 'should have required property \\'zipcode\\'' } ]\n*/\n\nconst userBaseSchema = S.object()\n  .additionalProperties(false)\n  .prop('username', S.string())\n  .prop('password', S.string())\n\nconst userSchema = S.object()\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n  .extend(userBaseSchema)\n  .valueOf()\n\nconsole.log(userSchema.valueOf())\n"
  },
  {
    "path": "src/schemas/basic.json",
    "content": "[\n  {\n    \"description\": \"basic schema from z-schema benchmark (https://github.com/zaggino/z-schema)\",\n    \"schema\": {\n      \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n      \"title\": \"Product set\",\n      \"type\": \"array\",\n      \"items\": {\n        \"title\": \"Product\",\n        \"type\": \"object\",\n        \"properties\": {\n          \"uuid\": {\n            \"description\": \"The unique identifier for a product\",\n            \"type\": \"number\"\n          },\n          \"name\": {\n            \"type\": \"string\"\n          },\n          \"price\": {\n            \"type\": \"number\",\n            \"exclusiveMinimum\": 0\n          },\n          \"tags\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"minItems\": 1,\n            \"uniqueItems\": true\n          },\n          \"dimensions\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"length\": { \"type\": \"number\" },\n              \"width\": { \"type\": \"number\" },\n              \"height\": { \"type\": \"number\" }\n            },\n            \"required\": [\"length\", \"width\", \"height\"]\n          },\n          \"warehouseLocation\": {\n            \"description\": \"Coordinates of the warehouse with the product\",\n            \"type\": \"string\"\n          }\n        },\n        \"required\": [\"uuid\", \"name\", \"price\"]\n      }\n    },\n    \"tests\": [\n      {\n        \"description\": \"valid array from z-schema benchmark\",\n        \"data\": [\n          {\n            \"id\": 2,\n            \"name\": \"An ice sculpture\",\n            \"price\": 12.5,\n            \"tags\": [\"cold\", \"ice\"],\n            \"dimensions\": {\n              \"length\": 7.0,\n              \"width\": 12.0,\n              \"height\": 9.5\n            },\n            \"warehouseLocation\": {\n              \"latitude\": -78.75,\n              \"longitude\": 20.4\n            }\n          },\n          {\n            \"id\": 3,\n            \"name\": \"A blue mouse\",\n            \"price\": 25.5,\n            \"dimensions\": {\n              \"length\": 3.1,\n              \"width\": 1.0,\n              \"height\": 1.0\n            },\n            \"warehouseLocation\": {\n              \"latitude\": 54.4,\n              \"longitude\": -32.7\n            }\n          }\n        ],\n        \"valid\": true\n      },\n      {\n        \"description\": \"not array\",\n        \"data\": 1,\n        \"valid\": false\n      },\n      {\n        \"description\": \"array of not onjects\",\n        \"data\": [1, 2, 3],\n        \"valid\": false\n      },\n      {\n        \"description\": \"missing required properties\",\n        \"data\": [{}],\n        \"valid\": false\n      },\n      {\n        \"description\": \"required property of wrong type\",\n        \"data\": [{ \"id\": 1, \"name\": \"product\", \"price\": \"not valid\" }],\n        \"valid\": false\n      },\n      {\n        \"description\": \"smallest valid product\",\n        \"data\": [{ \"id\": 1, \"name\": \"product\", \"price\": 100 }],\n        \"valid\": true\n      },\n      {\n        \"description\": \"tags should be array\",\n        \"data\": [{ \"tags\": {}, \"id\": 1, \"name\": \"product\", \"price\": 100 }],\n        \"valid\": false\n      },\n      {\n        \"description\": \"dimensions should be object\",\n        \"data\": [\n          { \"dimensions\": [], \"id\": 1, \"name\": \"product\", \"price\": 100 }\n        ],\n        \"valid\": false\n      },\n      {\n        \"description\": \"valid product with tag\",\n        \"data\": [\n          { \"tags\": [\"product\"], \"id\": 1, \"name\": \"product\", \"price\": 100 }\n        ],\n        \"valid\": true\n      },\n      {\n        \"description\": \"dimensions miss required properties\",\n        \"data\": [\n          {\n            \"dimensions\": {},\n            \"tags\": [\"product\"],\n            \"id\": 1,\n            \"name\": \"product\",\n            \"price\": 100\n          }\n        ],\n        \"valid\": false\n      },\n      {\n        \"description\": \"valid product with tag and dimensions\",\n        \"data\": [\n          {\n            \"dimensions\": { \"length\": 7, \"width\": 12, \"height\": 9.5 },\n            \"tags\": [\"product\"],\n            \"id\": 1,\n            \"name\": \"product\",\n            \"price\": 100\n          }\n        ],\n        \"valid\": true\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "src/utils.js",
    "content": "'use strict'\nconst deepmerge = require('@fastify/deepmerge')\nconst isFluentSchema = (obj) => obj?.isFluentSchema\n\nconst hasCombiningKeywords = (attributes) =>\n  attributes.allOf || attributes.anyOf || attributes.oneOf || attributes.not\n\nclass FluentSchemaError extends Error {\n  constructor (message) {\n    super(message)\n    this.name = 'FluentSchemaError'\n  }\n}\n\nconst last = (array) => {\n  if (!array) return\n  const [prop] = [...array].reverse()\n  return prop\n}\n\nconst isUniq = (array) =>\n  array.filter((v, i, a) => a.indexOf(v) === i).length === array.length\n\nconst isBoolean = (value) => typeof value === 'boolean'\n\nconst omit = (obj, props) =>\n  Object.entries(obj).reduce((memo, [key, value]) => {\n    if (!props.includes(key)) {\n      memo[key] = value\n    }\n    return memo\n  }, {})\n\nconst flat = (array) =>\n  array.reduce((memo, prop) => {\n    const { name, ...rest } = prop\n    memo[name] = rest\n    return memo\n  }, {})\n\nconst combineArray = (options) => {\n  const { clone, isMergeableObject, deepmerge } = options\n\n  return (target, source) => {\n    const result = target.slice()\n\n    source.forEach((item, index) => {\n      const prop = target.find((attr) => attr.name === item.name)\n      if (result[index] === undefined) {\n        result[index] = clone(item)\n      } else if (isMergeableObject(prop)) {\n        const propIndex = target.findIndex((prop) => prop.name === item.name)\n        result[propIndex] = deepmerge(prop, item)\n      } else if (target.indexOf(item) === -1) {\n        result.push(item)\n      }\n    })\n    return result\n  }\n}\n\nconst combineDeepmerge = deepmerge({ mergeArray: combineArray })\nconst toArray = (obj) =>\n  obj && Object.entries(obj).map(([key, value]) => ({ name: key, ...value }))\n\nconst REQUIRED = Symbol('required')\nconst FLUENT_SCHEMA = Symbol.for('fluent-schema-object')\n\nconst RELATIVE_JSON_POINTER = 'relative-json-pointer'\nconst JSON_POINTER = 'json-pointer'\nconst UUID = 'uuid'\nconst REGEX = 'regex'\nconst IPV6 = 'ipv6'\nconst IPV4 = 'ipv4'\nconst HOSTNAME = 'hostname'\nconst EMAIL = 'email'\nconst URL = 'url'\nconst URI_TEMPLATE = 'uri-template'\nconst URI_REFERENCE = 'uri-reference'\nconst URI = 'uri'\nconst TIME = 'time'\nconst DATE = 'date'\nconst DATE_TIME = 'date-time'\nconst ISO_TIME = 'iso-time'\nconst ISO_DATE_TIME = 'iso-date-time'\n\nconst FORMATS = {\n  RELATIVE_JSON_POINTER,\n  JSON_POINTER,\n  UUID,\n  REGEX,\n  IPV6,\n  IPV4,\n  HOSTNAME,\n  EMAIL,\n  URL,\n  URI_TEMPLATE,\n  URI_REFERENCE,\n  URI,\n  TIME,\n  DATE,\n  DATE_TIME,\n  ISO_TIME,\n  ISO_DATE_TIME\n}\n\nconst STRING = 'string'\nconst NUMBER = 'number'\nconst BOOLEAN = 'boolean'\nconst INTEGER = 'integer'\nconst OBJECT = 'object'\nconst ARRAY = 'array'\nconst NULL = 'null'\n\nconst TYPES = {\n  STRING,\n  NUMBER,\n  BOOLEAN,\n  INTEGER,\n  OBJECT,\n  ARRAY,\n  NULL\n}\n\nconst patchIdsWithParentId = ({ schema, generateIds, parentId }) => {\n  const properties = Object.entries(schema.properties || {})\n  if (properties.length === 0) return schema\n  return {\n    ...schema,\n    properties: properties.reduce((memo, [key, props]) => {\n      const $id = props.$id || (generateIds ? `#properties/${key}` : undefined)\n      memo[key] = {\n        ...props,\n        $id:\n          generateIds && parentId\n            ? `${parentId}/${$id.replace('#', '')}`\n            : $id // e.g. #properties/foo/properties/bar\n      }\n      return memo\n    }, {})\n  }\n}\n\nconst appendRequired = ({\n  attributes: { name, required, ...attributes },\n  schema\n}) => {\n  const { schemaRequired, attributeRequired } = (required || []).reduce(\n    (memo, item) => {\n      if (item === REQUIRED) {\n        // Append prop name to the schema.required\n        memo.schemaRequired.push(name)\n      } else {\n        // Propagate required attributes\n        memo.attributeRequired.push(item)\n      }\n      return memo\n    },\n    { schemaRequired: [], attributeRequired: [] }\n  )\n\n  const patchedRequired = [...schema.required, ...schemaRequired]\n  if (!isUniq(patchedRequired)) {\n    throw new FluentSchemaError(\n      \"'required' has repeated keys, check your calls to .required()\"\n    )\n  }\n\n  const schemaPatched = {\n    ...schema,\n    required: patchedRequired\n  }\n  const attributesPatched = {\n    ...attributes,\n    required: attributeRequired\n  }\n  return [schemaPatched, attributesPatched]\n}\n\nconst setAttribute = ({ schema, ...options }, attribute) => {\n  const [key, value] = attribute\n  const currentProp = last(schema.properties)\n  if (currentProp) {\n    const { name, ...props } = currentProp\n    return options.factory({ schema, ...options }).prop(name, {\n      [key]: value,\n      ...props\n    })\n  }\n  return options.factory({ schema: { ...schema, [key]: value }, ...options })\n}\n\nconst setRaw = ({ schema, ...options }, raw) => {\n  const currentProp = last(schema.properties)\n  if (currentProp) {\n    const { name, ...props } = currentProp\n    return options.factory({ schema, ...options }).prop(name, {\n      ...raw,\n      ...props\n    })\n  }\n  return options.factory({ schema: { ...schema, ...raw }, ...options })\n}\n// TODO LS maybe we can just use setAttribute and remove this one\nconst setComposeType = ({ prop, schemas, schema, options }) => {\n  if (!(Array.isArray(schemas) && schemas.every((v) => isFluentSchema(v)))) {\n    throw new FluentSchemaError(\n      `'${prop}' must be a an array of FluentSchema rather than a '${typeof schemas}'`\n    )\n  }\n\n  const values = schemas.map((schema) => {\n    const { $schema, ...props } = schema.valueOf({ isRoot: false })\n    return props\n  })\n\n  return options.factory({ schema: { ...schema, [prop]: values }, ...options })\n}\n\nmodule.exports = {\n  isFluentSchema,\n  hasCombiningKeywords,\n  FluentSchemaError,\n  last,\n  isUniq,\n  isBoolean,\n  flat,\n  toArray,\n  omit,\n  REQUIRED,\n  patchIdsWithParentId,\n  appendRequired,\n  setRaw,\n  setAttribute,\n  setComposeType,\n  combineDeepmerge,\n  FORMATS,\n  TYPES,\n  FLUENT_SCHEMA\n}\n"
  },
  {
    "path": "src/utils.test.js",
    "content": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\nconst { setRaw, combineDeepmerge } = require('./utils')\nconst { StringSchema } = require('./StringSchema')\nconst { ObjectSchema } = require('./ObjectSchema')\n\ndescribe('setRaw', () => {\n  it('add an attribute to a prop using ObjectSchema', () => {\n    const factory = ObjectSchema\n    const schema = setRaw(\n      { schema: { properties: [{ name: 'foo', type: 'string' }] }, factory },\n      { nullable: true }\n    )\n    assert.deepStrictEqual(schema.valueOf(), {\n      properties: {\n        foo: {\n          nullable: true,\n          type: 'string'\n        }\n      }\n    })\n  })\n\n  it('add an attribute to a prop using StringSchema', () => {\n    const factory = StringSchema\n    const schema = setRaw(\n      { schema: { type: 'string', properties: [] }, factory },\n      { nullable: true }\n    )\n    assert.deepStrictEqual(schema.valueOf(), {\n      nullable: true,\n      type: 'string'\n    })\n  })\n})\n\ndescribe('combineDeepmerge', () => {\n  it('should merge empty arrays', () => {\n    const result = combineDeepmerge([], [])\n    assert.deepStrictEqual(result, [])\n  })\n\n  it('should merge array with primitive values', () => {\n    const result = combineDeepmerge([1], [2])\n    assert.deepStrictEqual(result, [1, 2])\n  })\n\n  it('should merge arrays with primitive values', () => {\n    const result = combineDeepmerge([], [1, 2])\n    assert.deepStrictEqual(result, [1, 2])\n  })\n\n  it('should merge arrays with primitive values', () => {\n    const result = combineDeepmerge([1, 2], [1, 2, 3])\n    assert.deepStrictEqual(result, [1, 2, 3])\n  })\n\n  it('should merge array with simple Schemas', () => {\n    const result = combineDeepmerge([{ type: 'string' }], [{ type: 'string' }])\n    assert.deepStrictEqual(result, [{ type: 'string' }])\n  })\n\n  it('should merge array with named Schemas', () => {\n    const result = combineDeepmerge(\n      [{ name: 'variant 1', type: 'string' }],\n      [{ name: 'variant 2', type: 'string' }]\n    )\n    assert.deepStrictEqual(result, [\n      { name: 'variant 1', type: 'string' },\n      { name: 'variant 2', type: 'string' }\n    ])\n  })\n\n  it('should merge array with same named Schemas', () => {\n    const result = combineDeepmerge(\n      [{ name: 'variant 2', type: 'string' }],\n      [{ name: 'variant 2', type: 'number' }]\n    )\n    assert.deepStrictEqual(result, [{ name: 'variant 2', type: 'number' }])\n  })\n\n  it('should merge array with same named Schemas', () => {\n    const result = combineDeepmerge(\n      [{ name: 'variant 2', type: 'string' }],\n      [\n        { name: 'variant 2', type: 'number' },\n        { name: 'variant 1', type: 'string' }\n      ]\n    )\n    assert.deepStrictEqual(result, [\n      { name: 'variant 2', type: 'number' },\n      { name: 'variant 1', type: 'string' }\n    ])\n  })\n})\n"
  },
  {
    "path": "types/FluentJSONSchema.d.ts",
    "content": "export interface BaseSchema<T> {\n  id: (id: string) => T\n  title: (title: string) => T\n  description: (description: string) => T\n  examples: (examples: Array<any>) => T\n  ref: (ref: string) => T\n  enum: (values: Array<any>) => T\n  const: (value: any) => T\n  default: (value: any) => T\n  required: (fields?: string[]) => T\n  ifThen: (ifClause: JSONSchema, thenClause: JSONSchema) => T\n  ifThenElse: (\n    ifClause: JSONSchema,\n    thenClause: JSONSchema,\n    elseClause: JSONSchema\n  ) => T\n  not: (schema: JSONSchema) => T\n  anyOf: (schema: Array<JSONSchema>) => T\n  allOf: (schema: Array<JSONSchema>) => T\n  oneOf: (schema: Array<JSONSchema>) => T\n  readOnly: (isReadOnly?: boolean) => T\n  writeOnly: (isWriteOnly?: boolean) => T\n  deprecated: (isDeprecated?: boolean) => T\n  isFluentSchema: boolean\n  isFluentJSONSchema: boolean\n  raw: (fragment: any) => T\n}\n\nexport type TYPE =\n  | 'string'\n  | 'number'\n  | 'boolean'\n  | 'integer'\n  | 'object'\n  | 'array'\n  | 'null'\n\ntype FORMATS = {\n  RELATIVE_JSON_POINTER: 'relative-json-pointer'\n  JSON_POINTER: 'json-pointer'\n  UUID: 'uuid'\n  REGEX: 'regex'\n  IPV6: 'ipv6'\n  IPV4: 'ipv4'\n  HOSTNAME: 'hostname'\n  EMAIL: 'email'\n  URL: 'url'\n  URI_TEMPLATE: 'uri-template'\n  URI_REFERENCE: 'uri-reference'\n  URI: 'uri'\n  TIME: 'time'\n  DATE: 'date'\n  DATE_TIME: 'date-time'\n  ISO_TIME: 'iso-time'\n  ISO_DATE_TIME: 'iso-date-time'\n}\n\nexport type JSONSchema =\n  | ObjectSchema\n  | StringSchema\n  | NumberSchema\n  | ArraySchema\n  | IntegerSchema\n  | BooleanSchema\n  | NullSchema\n  | ExtendedSchema\n\nexport class FluentSchemaError extends Error {\n  name: string\n}\n\nexport interface SchemaOptions {\n  schema: object\n  generateIds: boolean\n}\n\nexport interface StringSchema extends BaseSchema<StringSchema> {\n  minLength: (min: number) => StringSchema\n  maxLength: (min: number) => StringSchema\n  format: (format: FORMATS[keyof FORMATS]) => StringSchema\n  pattern: (pattern: string | RegExp) => StringSchema\n  contentEncoding: (encoding: string) => StringSchema\n  contentMediaType: (mediaType: string) => StringSchema\n}\n\nexport interface NullSchema {\n  null: () => StringSchema\n}\n\nexport interface BooleanSchema extends BaseSchema<BooleanSchema> {\n  boolean: () => BooleanSchema\n}\n\nexport interface NumberSchema extends BaseSchema<NumberSchema> {\n  minimum: (min: number) => NumberSchema\n  exclusiveMinimum: (min: number) => NumberSchema\n  maximum: (max: number) => NumberSchema\n  exclusiveMaximum: (max: number) => NumberSchema\n  multipleOf: (multiple: number) => NumberSchema\n}\n\nexport interface IntegerSchema extends BaseSchema<IntegerSchema> {\n  minimum: (min: number) => IntegerSchema\n  exclusiveMinimum: (min: number) => IntegerSchema\n  maximum: (max: number) => IntegerSchema\n  exclusiveMaximum: (max: number) => IntegerSchema\n  multipleOf: (multiple: number) => IntegerSchema\n}\n\nexport interface ArraySchema extends BaseSchema<ArraySchema> {\n  items: (items: JSONSchema | Array<JSONSchema>) => ArraySchema\n  additionalItems: (items: Array<JSONSchema> | boolean) => ArraySchema\n  contains: (value: JSONSchema | boolean) => ArraySchema\n  uniqueItems: (boolean: boolean) => ArraySchema\n  minItems: (min: number) => ArraySchema\n  maxItems: (max: number) => ArraySchema\n}\n\nexport interface ObjectSchema<T extends Record<string, any> = Record<string, any>> extends BaseSchema<ObjectSchema<T>> {\n  definition: (name: Key<T>, props?: JSONSchema) => ObjectSchema<T>\n  prop: (name: Key<T>, props?: JSONSchema) => ObjectSchema<T>\n  additionalProperties: (value: JSONSchema | boolean) => ObjectSchema<T>\n  maxProperties: (max: number) => ObjectSchema<T>\n  minProperties: (min: number) => ObjectSchema<T>\n  patternProperties: (options: PatternPropertiesOptions) => ObjectSchema<T>\n  dependencies: (options: DependenciesOptions) => ObjectSchema<T>\n  propertyNames: (value: JSONSchema) => ObjectSchema<T>\n  extend: (schema: ObjectSchema<T> | ExtendedSchema) => ExtendedSchema\n  only: (properties: string[]) => ObjectSchema<T>\n  without: (properties: string[]) => ObjectSchema<T>\n  dependentRequired: (options: DependentRequiredOptions<T>) => ObjectSchema<T>\n  dependentSchemas: (options: DependentSchemaOptions<T>) => ObjectSchema<T>\n}\n\nexport type ExtendedSchema = Pick<ObjectSchema, 'isFluentSchema' | 'extend'>\n\ntype InferSchemaMap = {\n  string: StringSchema\n  number: NumberSchema\n  boolean: BooleanSchema\n  integer: IntegerSchema\n  object: ObjectSchema\n  array: ArraySchema\n  null: NullSchema\n}\n\nexport type MixedSchema<T extends TYPE[]> =\n  T extends readonly [infer First extends TYPE, ...infer Rest extends TYPE[]]\n    ? InferSchemaMap[First] & MixedSchema<Rest>\n    : unknown\n\ninterface PatternPropertiesOptions {\n  [key: string]: JSONSchema\n}\n\ninterface DependenciesOptions {\n  [key: string]: JSONSchema[]\n}\n\ntype Key<T> = keyof T | (string & {})\n\ntype DependentSchemaOptions<T extends Partial<Record<string, JSONSchema>>> = Partial<Record<keyof T, JSONSchema>>\n\ntype DependentRequiredOptions<T extends Partial<Record<string, string[]>>> = Partial<Record<keyof T, string[]>>\n\nexport function withOptions<T> (options: SchemaOptions): T\n\ntype ObjectPlaceholder = Record<string | number | symbol, any>\n\nexport interface S extends BaseSchema<S> {\n  string: () => StringSchema\n  number: () => NumberSchema\n  integer: () => IntegerSchema\n  boolean: () => BooleanSchema\n  array: () => ArraySchema\n  object: <T extends ObjectPlaceholder = ObjectPlaceholder>() => ObjectSchema<T>\n  null: () => NullSchema\n  mixed<T extends [TYPE, ...TYPE[]]>(types: T): MixedSchema<T>\n  raw: (fragment: any) => S\n  FORMATS: FORMATS\n}\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare, no-var\nexport declare var S: S\n\nexport default S\n"
  },
  {
    "path": "types/FluentJSONSchema.test-d.ts",
    "content": "// This file will be passed to the TypeScript CLI to verify our typings compile\n\nimport S, { FluentSchemaError } from '..'\n\nconsole.log('isFluentSchema:', S.object().isFluentJSONSchema)\nconst schema = S.object()\n  .id('http://foo.com/user')\n  .title('A User')\n  .description('A User desc')\n  .definition(\n    'address',\n    S.object()\n      .id('#address')\n      .prop('line1', S.anyOf([S.string(), S.null()])) // JSON Schema nullable\n      .prop('line2', S.string().raw({ nullable: true })) // Open API / Swagger  nullable\n      .prop('country')\n      .allOf([S.string()])\n      .prop('city')\n      .prop('zipcode')\n  )\n  .prop('username', S.string().pattern(/[a-z]*/g))\n  .prop('email', S.string().format('email'))\n  .prop('email2', S.string().format(S.FORMATS.EMAIL))\n  .prop(\n    'avatar',\n    S.string().contentEncoding('base64').contentMediaType('image/png')\n  )\n  .required()\n  .prop(\n    'password',\n    S.string().default('123456').minLength(6).maxLength(12).pattern('.*')\n  )\n  .required()\n  .prop('addresses', S.array().items([S.ref('#address')]))\n  .required()\n  .prop(\n    'role',\n    S.object()\n      .id('http://foo.com/role')\n      .prop('name')\n      .enum(['ADMIN', 'USER'])\n      .prop('permissions')\n  )\n  .required()\n  .prop('age', S.mixed(['string', 'integer']))\n  .ifThen(S.object().prop('age', S.string()), S.required(['age']))\n  .readOnly()\n  .writeOnly(true)\n  .valueOf()\n\nconsole.log('example:\\n', JSON.stringify(schema))\nconsole.log('isFluentSchema:', S.object().isFluentSchema)\n\nconst userBaseSchema = S.object()\n  .additionalProperties(false)\n  .prop('username', S.string())\n  .prop('password', S.string())\n\nconst userSchema = S.object()\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n  .extend(userBaseSchema)\n\nconsole.log('user:\\n', JSON.stringify(userSchema.valueOf()))\n\nconst largeUserSchema = S.object()\n  .prop('id', S.string().format('uuid'))\n  .prop('username', S.string())\n  .prop('password', S.string())\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n\nconst userSubsetSchema = largeUserSchema.only(['username', 'password'])\n\nconsole.log('user subset:', JSON.stringify(userSubsetSchema.valueOf()))\n\nconst personSchema = S.object()\n  .prop('name', S.string())\n  .prop('age', S.number())\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.string().format('time'))\n  .prop('updatedAt', S.string().format('time'))\n\nconst bodySchema = personSchema.without(['createdAt', 'updatedAt'])\n\nconsole.log('person subset:', JSON.stringify(bodySchema.valueOf()))\n\nconst personSchemaAllowsUnix = S.object()\n  .prop('name', S.string())\n  .prop('age', S.number())\n  .prop('id', S.string().format('uuid'))\n  .prop('createdAt', S.mixed(['string', 'integer']).format('time'))\n  .prop('updatedAt', S.mixed(['string', 'integer']).minimum(0))\n\nconsole.log('person schema allows unix:', JSON.stringify(personSchemaAllowsUnix.valueOf()))\n\ntry {\n  S.object().prop('foo', 'boom!' as any)\n} catch (e) {\n  if (e instanceof FluentSchemaError) {\n    console.log(e.message)\n  }\n}\n\nconst arrayExtendedSchema = S.array().items(userSchema).valueOf()\n\nconsole.log('array of user\\n', JSON.stringify(arrayExtendedSchema))\n\nconst extendExtendedSchema = S.object().extend(userSchema)\n\nconsole.log('extend of user\\n', JSON.stringify(extendExtendedSchema))\n\nconst rawNullableSchema = S.object()\n  .raw({ nullable: true })\n  .required(['foo', 'hello'])\n  .prop('foo', S.string())\n  .prop('hello', S.string())\n\nconsole.log('raw schema with nullable props\\n', JSON.stringify(rawNullableSchema))\n\nconst dependentRequired = S.object()\n  .dependentRequired({\n    foo: ['bar'],\n  })\n  .prop('foo')\n  .prop('bar')\n  .valueOf()\n\nconsole.log('dependentRequired:\\n', JSON.stringify(dependentRequired))\n\nconst dependentSchemas = S.object()\n  .dependentSchemas({\n    foo: S.object().prop('bar'),\n  })\n  .prop('bar', S.object().prop('bar'))\n  .valueOf()\n\nconsole.log('dependentRequired:\\n', JSON.stringify(dependentSchemas))\n\nconst deprecatedSchema = S.object()\n  .deprecated()\n  .prop('foo', S.string().deprecated())\n  .valueOf()\n\nconsole.log('deprecatedSchema:\\n', JSON.stringify(deprecatedSchema))\n\ntype Foo = {\n  foo: string\n  bar: string\n}\n\nconst dependentRequiredWithType = S.object<Foo>()\n  .dependentRequired({\n    foo: ['bar'],\n  })\n  .prop('foo')\n  .prop('bar')\n  .valueOf()\n\nconsole.log('dependentRequired:\\n', JSON.stringify(dependentRequiredWithType))\n\nconst dependentSchemasWithType = S.object<Foo>()\n  .dependentSchemas({\n    foo: S.object().prop('bar'),\n  })\n  .prop('bar', S.object().prop('bar'))\n  .valueOf()\n\nconsole.log('dependentSchemasWithType:\\n', JSON.stringify(dependentSchemasWithType))\n\nconst deprecatedSchemaWithType = S.object<Foo>()\n  .deprecated()\n  .prop('foo', S.string().deprecated())\n  .valueOf()\n\nconsole.log('deprecatedSchemaWithType:\\n', JSON.stringify(deprecatedSchemaWithType))\n\ntype ReallyLongType = {\n  foo: string\n  bar: string\n  baz: string\n  xpto: string\n  abcd: number\n  kct: {\n    a: string\n    b: number\n    d: null\n  }\n}\n\nconst deepTestOnTypes = S.object<ReallyLongType>()\n  .prop('bar', S.object().prop('bar'))\n  // you can provide any string, to avoid breaking changes\n  .prop('aaaa', S.anyOf([S.string()]))\n  .definition('abcd', S.number())\n  .valueOf()\n\nconsole.log('deepTestOnTypes:\\n', JSON.stringify(deepTestOnTypes))\n\nconst tsIsoSchema = S.object()\n  .prop('createdAt', S.string().format('iso-time'))\n  .prop('updatedAt', S.string().format('iso-date-time'))\n  .valueOf()\n\nconsole.log('ISO schema OK:', JSON.stringify(tsIsoSchema))\n"
  }
]