{"id":25045908,"url":"https://github.com/cloudflare/cabidela","last_synced_at":"2025-04-14T03:20:48.974Z","repository":{"id":276007470,"uuid":"927880746","full_name":"cloudflare/cabidela","owner":"cloudflare","description":"Cabidela is a small, fast, eval-less, Cloudflare Workers compatible, dynamic JSON Schema validator.","archived":false,"fork":false,"pushed_at":"2025-03-24T12:05:43.000Z","size":532,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-27T17:21:16.720Z","etag":null,"topics":["json-schema"],"latest_commit_sha":null,"homepage":"https://cabidela.pages.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudflare.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-05T17:39:19.000Z","updated_at":"2025-03-26T10:03:40.000Z","dependencies_parsed_at":"2025-02-27T20:27:31.161Z","dependency_job_id":"c61cf9f7-b529-4168-850b-ca01580c8321","html_url":"https://github.com/cloudflare/cabidela","commit_stats":null,"previous_names":["cloudflare/cabidela"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fcabidela","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fcabidela/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fcabidela/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fcabidela/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudflare","download_url":"https://codeload.github.com/cloudflare/cabidela/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248814039,"owners_count":21165668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-schema"],"created_at":"2025-02-06T06:26:56.249Z","updated_at":"2025-04-14T03:20:48.967Z","avatar_url":"https://github.com/cloudflare.png","language":"TypeScript","funding_links":[],"categories":["Frameworks \u0026 Libraries"],"sub_categories":["Utilities \u0026 Processing"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://cabidela.pages.dev/\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/cloudflare/cabidela/refs/heads/main/assets/cabidela.png\" width=\"500\" height=\"auto\" alt=\"cabidela\"/\u003e\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cem\u003eSmall, fast, eval-less, \u003ca href=\"https://developers.cloudflare.com/workers/\"\u003eCloudflare Workers\u003c/a\u003e compatible, dynamic JSON Schema validator.\u003c/em\u003e\n\u003c/p\u003e\n\n\u003chr /\u003e\n\n## What is\n\nCabidela is a small, fast, eval-less, Cloudflare Workers compatible, dynamic JSON Schema validator. It implements a large subset of \u003chttps://json-schema.org/draft/2020-12/json-schema-validation\u003e that should cover most use-cases. But not all. See limitations below.\n\n## How to use\n\nInstall the package:\n\n```bash\nnpm install @cloudflare/cabidela --save\n```\n\nImport it:\n\n```ts\nimport { Cabidela } from \"@cloudflare/cabidela\";\n```\n\nUse it:\n\n```ts\nlet schema: any = {\n  type: \"object\",\n  properties: {\n    prompt: {\n      type: \"string\",\n      minLength: 1,\n      maxLength: 131072,\n      description: \"The input text prompt for the model to generate a response.\",\n    },\n    num_steps: {\n      type: \"number\",\n      minimum: 0,\n      maximum: 20,\n      description: \"Increases the likelihood of the model introducing new topics.\",\n    },\n  },\n  required: [\"prompt\"],\n};\n\nconst cabidela = new Cabidela(schema);\n\ncabidela.validate({\n  prompt: \"Tell me a joke\",\n  num_steps: 5,\n});\n```\n\nCabidela implements a [Exception-Driven Validation](https://json-schema.org/implementers/interfaces#exception-driven-validation) approach. If any condition in the schema is not met, we throw an error.\n\n## API\n\n### New instance\n\n`const cabidela = new Cabidela(schema: any, options?: CabidelaOptions)`\n\nCabidela takes a JSON-Schema and optional configuration flags:\n\n- `applyDefaults`: boolean - If true, the validator will apply default values to the input object. Default is false.\n- `errorMessages`: boolean - If true, the validator will use custom `errorMessage` messages from the schema. Default is false.\n- `fullErrors`: boolean - If true, the validator will be more verbose when throwing errors for complex schemas (example: anyOf, oneOf's), set to false for shorter exceptions. Default is true.\n- `useMerge`: boolean - Set to true if you want to use the `$merge` keyword. Default is false. See below for more information.\n- `subSchemas`: any[] - An optional array of sub-schemas that can be used with `$id` and `$ref`. See below for more information.\n\nReturns a validation object.\n\nYou can change the schema at any time by calling `cabidela.setSchema(schema: any)`.\n\nYou can change the options at any time by calling `cabidela.setOptions(options: CabidelaOptions)`.\n\n### Validate payload\n\nCall `cabidela.validate(payload: any)` to validate your payload.\n\nReturns truth if the payload is valid, throws an error otherwise.\n\n```js\nconst payload = {\n  messages: [\n    { role: \"system\", content: \"You're a helpful assistant\" },\n    { role: \"user\", content: \"What is Cloudflare?\" },\n  ],\n};\n\ntry {\n  cabidela.validate(payload);\n  console.log(\"Payload is valid\");\n} catch (e) {\n  console.error(e);\n}\n```\n\n## Modifying the payload\n\nSome options, like `applyDefaults`, will modify the input object.\n\n```js\nconst schema = {\n  type: \"object\",\n  properties: {\n    prompt: {\n      type: \"string\",\n    },\n    num_steps: {\n      type: \"number\",\n      default: 10,\n    },\n  },\n};\n\nconst cabidela = new Cabidela(schema, { applyDefaults: true });\n\nconst payload = {\n  prompt: \"Tell me a joke\",\n});\n\ncabidela.validate(payload);\n\nconsole.log(payload);\n\n// {\n//   prompt: 'Tell me a joke',\n//   num_steps: 10\n// }\n\n```\n\n### oneOf defaults\n\nUsing `applyDefaults` with `oneOf` will one apply the default value of the sub-schema that matches the condition. For\nexample, using this schema:\n\n```javascript\n  {\n    type: \"object\",\n    oneOf: [\n      {\n        type: \"object\",\n        properties: {\n          sun: {\n            type: \"number\",\n            default: 9000,\n          },\n          moon: {\n            type: \"number\",\n            default: 9000,\n          },\n        },\n        required: [\"sun\"],\n      },\n      {\n        type: \"object\",\n        properties: {\n          sun: {\n            type: \"number\",\n            default: 9000,\n          },\n          moon: {\n            type: \"number\",\n            default: 9000,\n          },\n        },\n        required: [\"moon\"],\n      },\n    ],\n  };\n```\n\n- The payload `{ sun: 10}` will be modified to `{ sun: 10, moon: 9000 }`.\n- The payload `{ moon: 10}` will be modified to `{ sun: 9000, moon: 10 }`.\n- The payload `{ saturn: 10}` will throw an error because no condition is met.\n\n### $id, $ref, $defs\n\nThe keywords [$id](https://json-schema.org/understanding-json-schema/structuring#id), [$ref](https://json-schema.org/understanding-json-schema/structuring#dollarref) and [$defs](https://json-schema.org/understanding-json-schema/structuring#defs) can be used to build and maintain complex schemas where the reusable parts are defined in separate schemas.\n\nThe following is the main schema and a `customer` sub-schema that defines the `contacts` and `address` properties.\n\n```js\nimport { Cabidela } from \"@cloudflare/cabidela\";\n\nconst schema = {\n  $id: \"http://example.com/schemas/main\",\n  type: \"object\",\n  properties: {\n    name: { type: \"string\" },\n    contacts: { $ref: \"customer#/contacts\" },\n    address: { $ref: \"customer#/address\" },\n    balance: { $ref: \"$defs#/balance\" },\n  },\n  required: [\"name\", \"contacts\", \"address\"],\n  \"$defs\": {\n    \"balance\": {\n      type: \"object\",\n      prope      properties: {\n        currency: { type: \"string\" },\n        amount: { type: \"number\" },\n      },\n    }\n  }\n};\n\nconst contactSchema = {\n  $id: \"http://example.com/schemas/customer\",\n  contacts: {\n    type: \"object\",\n    properties: {\n      email: { type: \"string\" },\n      phone: { type: \"string\" },\n    },\n    required: [\"email\", \"phone\"],\n  },\n  address: {\n    type: \"object\",\n    properties: {\n      street: { type: \"string\" },\n      city: { type: \"string\" },\n      zip: { type: \"string\" },\n      country: { type: \"string\" },\n    },\n    required: [\"street\", \"city\", \"zip\", \"country\"],\n  },\n};\n\nconst cabidela = new Cabidela(schema, { subSchemas: [contactSchema] });\n\ncabidela.validate({\n  name: \"John\",\n  contacts: {\n    email: \"john@example.com\",\n    phone: \"+123456789\",\n  },\n  address: {\n    street: \"123 Main St\",\n    city: \"San Francisco\",\n    zip: \"94105\",\n    country: \"USA\",\n  },\n});\n```\n\n## Combined schemas and $merge\n\nThe standard way of combining and extending schemas is by using the [`allOf`](https://json-schema.org/understanding-json-schema/reference/combining#allOf) (AND), [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) (OR), [`oneOf`](https://json-schema.org/understanding-json-schema/reference/combining#oneOf) (XOR) and [`not`](https://json-schema.org/understanding-json-schema/reference/combining#not) keywords, all supported by this library.\n\nCabidela supports an additional keyword `$merge` (inspired by [Ajv](https://ajv.js.org/guide/combining-schemas.html#merge-and-patch-keywords)) that allows you to merge two objects. This is useful when you want to extend a schema with additional properties and `allOf`` is not enough.\n\nHere's how it works:\n\n```json\n{\n  \"$merge\": {\n    \"source\": {\n      \"type\": \"object\",\n      \"properties\": { \"p\": { \"type\": \"string\" } },\n      \"additionalProperties\": false\n    },\n    \"with\": {\n      \"properties\": { \"q\": { \"type\": \"number\" } }\n    }\n  }\n}\n```\n\nResolves to:\n\n```json\n{\n  \"type\": \"object\",\n  \"properties\": {\n    \"p\": {\n      \"type\": \"string\" }\n    },\n    \"q\": {\n      \"type\": \"number\"\n    }\n  },\n  \"additionalProperties\": false\n}\n```\n\nTo use `$merge` set the `useMerge` flag to true when creating the instance.\n\n```js\nnew Cabidela(schema, { useMerge: true });\n```\n\nYou can combine `$merge` with `$id` and `$ref` keywords, which get resolved first, for even more flexibility.\n\n## Custom errors\n\nIf the new instance options has the `errorMessages` flag set to true, you can use the property `errorMessage` in the schema to define custom error messages.\n\n```js\nconst schema = {\n  type: \"object\",\n  properties: {\n    prompt: {\n      type: \"string\",\n    },\n  },\n  required: [\"prompt\"],\n  errorMessage: \"prompt required\",\n};\n\nconst cabidela = new Cabidela(schema, { errorMessages: true });\n\nconst payload = {\n  missing: \"prompt\",\n});\n\ncabidela.validate(payload);\n// throws \"Error: prompt required\"\n```\n\n## Tests\n\nThe tests can be found [here](./tests/).\n\nCabidela uses [vitest](https://vitest.dev/) to test internal methods and compliance with the [JSON Schema specification](https://json-schema.org/). To run the tests type:\n\n```bash\nnpm run test\n```\n\nYou can also run the tests with [Ajv](https://ajv.js.org/), or both. This allows us to compare the results and double-check how we interpret the specification.\n\n```bash\nnpm run test-ajv\nnpm run test-all\n```\n\n## Performance\n\nJSON Schema validators like Ajv tend to follow this pattern:\n\n1. Instantiate a validator.\n2. Compile the schema.\n3. Validate one or more payloads against the (compiled) schema.\n\nAll of these steps have a cost. Compiling the schema makes sense if you are going to validate multiple payloads in the same session. But in the case of a Workers application we typically want to validate with the HTTP request, one payload at a time, and then we discard the validator.\n\nCabidela skips the compilation step and validates the payload directly against the schema.\n\nIn our benchmarks, Cabidela is significantly faster than Ajv on all operations if you don't reuse the validator. Even when we skip the instantiation and compilation steps from Ajv, Cabidela still performs relatively well.\n\nHere are some results:\n\n```bash\n  Cabidela - benchmarks/00-basic.bench.js \u003e allOf, two properties\n    1929.61x faster than Ajv\n\n  Cabidela - benchmarks/00-basic.bench.js \u003e allOf, two objects\n    1351.41x faster than Ajv\n\n  Cabidela - benchmarks/00-basic.bench.js \u003e anyOf, two conditions\n    227.48x faster than Ajv\n\n  Cabidela - benchmarks/00-basic.bench.js \u003e oneOf, two conditions\n    224.49x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e Big array payload\n    386.44x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e Big object payload\n    6.08x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e Deep schema, deep payload\n    59.75x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e allOf, two properties\n   1701.95x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e allOf, two objects\n    1307.04x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e anyOf, two conditions\n    207.73x faster than Ajv\n\n  Cabidela - benchmarks/80-big-ops.bench.js \u003e oneOf, two conditions\n    211.72x faster than Ajv\n```\n\nWe use Vitest's [bench](https://vitest.dev/api/#bench) feature to run the benchmarks. You can find the benchmarks in the [benchmarks](./benchmarks/) folder and you can run them with:\n\n```bash\nnpm run benchmark\n```\n\n## Current limitations\n\nCabidela supports most of JSON Schema specification, and should be useful for many applications, but it's not complete. **Currently** we do not support:\n\n- Multiple (array of) types `{ \"type\": [\"number\", \"string\"] }`\n- Pattern properties\n- `dependentRequired`, `dependentSchemas`, `If-Then-Else`\n\nyet.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudflare%2Fcabidela","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudflare%2Fcabidela","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudflare%2Fcabidela/lists"}