Repository: cdimascio/express-openapi-validator
Pull request #1027 (2024-12-24 14:28:01)

feat(openapi): support OpenAPI version 3.1 (extends PR #882)
This PR adds support for OpenAPI version 3.1. See https://github.com/cdimascio/express-openapi-validator/pull/882
for details.

This PR was largely driven by the incredible efforts of the community. I truly appreciate this amazing community for all its help and support in driving express-openapi-validator forward!

- **feat(openapi): support version 3.1**
- **test(openapi_3.1): ensure that an API with webhooks and no routes is supported**
- **feat(openapi_3.1): adds open api 3.1 type**
- **chore(test-scripts): run mocha with --extension instead of glob to pick up subdirectories**
- **test(openapi-3.1): adds test to ensure an API with only components is considered valid**
- **test(openapi-3.1): remove unnecessary import**
- **test(openapi-3.1): add support for summary in info object**
- **test(openapi-3.1): add support for identifier in license**
- **test(openapi_3.1): ensure API with type set to null works correctly**
- **test(open_api3.1): ensure that methods with non-explicit semantics allow request body**
- **test(open_api3.1): ensure 500 is returned when server variable has no default**
- **feat(openapi_3.1): ensure API supports an endpoint without response**
- **feat(openapi_3.1): add full type support for open api 3.1**
- **test(openapi_3.1): adds test for path item support in components**
- **fix(openapi_3.1_schema): update schema to fix bug**
- **feat(openapi_3.1): support reusable path items**
- **style(linting): fix linting issues**
- **style(openapi): improve readability of version validation**
- **docs(schema-validator): clearly state why media-range attribute is not defined**
- **version 6.0.0-alpha.1 with initial OAS-3.1 support (from PR https://github.com/cdimascio/express-openapi-validator/pull/882)**
- **v6.0.0-alpha.2**
- **feat(openapi): support version 3.1**
- **test(openapi_3.1): ensure that an API with webhooks and no routes is supported**
- **feat(openapi_3.1): adds open api 3.1 type**
- **chore(test-scripts): run mocha with --extension instead of glob to pick up subdirectories**
- **test(openapi-3.1): adds test to ensure an API with only components is considered valid**
- **test(openapi-3.1): remove unnecessary import**
- **test(openapi-3.1): add support for summary in info object**
- **test(openapi-3.1): add support for identifier in license**
- **test(openapi_3.1): ensure API with type set to null works correctly**
- **test(open_api3.1): ensure that methods with non-explicit semantics allow request body**
- **test(open_api3.1): ensure 500 is returned when server variable has no default**
- **feat(openapi_3.1): ensure API supports an endpoint without response**
- **feat(openapi_3.1): add full type support for open api 3.1**
- **test(openapi_3.1): adds test for path item support in components**
- **fix(openapi_3.1_schema): update schema to fix bug**
- **feat(openapi_3.1): support reusable path items**
- **style(linting): fix linting issues**
- **style(openapi): improve readability of version validation**
- **docs(schema-validator): clearly state why media-range attribute is not defined**
- **version 6.0.0-alpha.1 with initial OAS-3.1 support (from PR https://github.com/cdimascio/express-openapi-validator/pull/882)**
- **v6.0.0-alpha.2**
- **alpha.3**
- **feat(openapi): support version 3.1**
- **feat(openapi_3.1): adds open api 3.1 type**
- **fix(openapi_3.1_schema): update schema to fix bug**
- **style(linting): fix linting issues**
- **fix: instantiate Ajv2020 for OAS 3.1 (#1009)**
- **update alpha 3.1 version**
- **fix: examples/4-eov-operations-babel/package.json & examples/4-eov-operations-babel/package-lock.json to reduce vulnerabilities (#1021)**
- **fix: package.json & package-lock.json to reduce vulnerabilities (#1017)**
- **chore(deps): bump path-to-regexp and express in /examples/1-standard (#1016)**
- **fix: upgrade express-openapi-validator from 5.3.7 to 5.3.8 (#1011)**
- **fix: upgrade express-openapi-validator from 5.3.6 to 5.3.7 (#1008)**
- **fix: upgrade @apidevtools/json-schema-ref-parser from 11.7.0 to 11.7.2 (#1006)**
- **adds standard example for oas-3.1**
- **v6.0.0.alpha.6**
- **update oas3.1 example**
- **v5.4.0 - adds initial oas3.1 support**



Patch:
diff --git a/README.md b/README.md
index 8273f86..cd230aa 100644
--- a/README.md
+++ b/README.md
@@ -29,16 +29,12 @@
 [NestJS](https://github.com/cdimascio/express-openapi-validator/tree/master/examples/9-nestjs)
 [Koa](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/koa-openapi-validator) and [Fastify](https://github.com/cdimascio/express-openapi-validator/tree/lerna-fastify/packages/fastify-openapi-validator) now available! 🚀
 
-[OAS 3.1 (experimental)](https://github.com/cdimascio/express-openapi-validator/pull/882)
+[OAS 3.1 (beta)](https://github.com/cdimascio/express-openapi-validator/pull/882) is available in v5.4.0!
 
 ## Install
 
 ```shell
 npm install express-openapi-validator
-
-# experimental OAS 3.1 in alpha (contributions welcome - see branch `oas-3.1` and pr-882 
-# please provide feedback on (issue-573)
-npm install express-openapi-validator@6.0.0-alpha.5
 ```
 
 ## Usage
diff --git a/examples/1-standard-oas-3.1/README.md b/examples/1-standard-oas-3.1/README.md
new file mode 100644
index 0000000..27e6e48
--- /dev/null
+++ b/examples/1-standard-oas-3.1/README.md
@@ -0,0 +1,172 @@
+# example
+
+simple example using express-openapi-validator
+
+## Install
+
+```shell
+npm run deps && npm i
+```
+
+## Run
+
+From this `1-standard` directory, run:
+
+```shell
+npm start
+```
+
+## Try
+
+```shell
+## call ping
+curl http://localhost:3000/v1/ping
+
+## call pets
+## the call below should return 400 since it requires additional parameters
+curl http://localhost:3000/v1/pets
+```
+
+## Text examples
+
+### GET /pets
+
+success
+```shell
+curl -s 'localhost:3000/v1/pets?limit=5' |jq
+[
+  {
+    "id": 1,
+    "name": "sparky",
+    "type": "dog",
+    "tags": [
+      "sweet"
+    ]
+  },
+  {
+    "id": 2,
+    "name": "buzz",
+    "type": "cat",
+    "tags": [
+      "purrfect"
+    ]
+  },
+  {
+    "id": 3,
+    "name": "max",
+    "type": "dog",
+    "tags": []
+  }
+]
+```
+
+error 
+```shell
+ curl -s 'localhost:3000/v1/pets' |jq
+{
+  "message": "request/query must have required property 'limit'",
+  "errors": [
+    {
+      "path": "/query/limit",
+      "message": "must have required property 'limit'",
+      "errorCode": "required.openapi.validation"
+    }
+  ],
+  "code": 400
+}
+```
+
+### POST /pets
+
+success
+```shell
+curl -s -XPOST 'localhost:3000/v1/pets' -d '{"id": 1, "name": "jobe"}' -H 'Content-type: application/json'|jq
+{
+  "id": 1,
+  "name": "jobe"
+}
+```
+
+error
+```shell
+curl -s -XPOST 'localhost:3000/v1/pets' -d '{"id": "sdfsf", "name": "jobe"}' -H 'Content-type: application/json'|jq
+{
+  "message": "request/body/id must be integer",
+  "errors": [
+    {
+      "path": "/body/id",
+      "message": "must be integer",
+      "errorCode": "type.openapi.validation"
+    }
+  ],
+  "code": 400
+}
+```
+
+### GET /pets/:id
+
+success
+```shell
+curl -s 'localhost:3000/v1/pets/1' |jq
+{
+  "id": 1,
+  "name": "sparky",
+  "type": "dog",
+  "tags": [
+    "sweet"
+  ]
+}
+```
+
+error: bad id type
+```shell
+curl -s 'localhost:3000/v1/pets/lkl' |jq
+{
+  "message": "request/params/petId must be number",
+  "errors": [
+    {
+      "path": "/params/petId",
+      "message": "must be number",
+      "errorCode": "type.openapi.validation"
+    }
+  ],
+  "code": 400
+}
+```
+
+
+### /v1/pets/1/photos
+
+success
+```shell
+curl  -XPOST 'localhost:3000/v1/pets/1/photos' -H 'Content-type: multipart/form-data' -F 'file=@test.txt'|jq
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100   304  100    90  100   214  25996  61813 --:--:-- --:--:-- --:--:--   98k
+{
+  "files_metadata": [
+    {
+      "originalname": "test.txt",
+      "encoding": "7bit",
+      "mimetype": "text/plain"
+    }
+  ]
+}
+```
+
+error: no file
+```shell
+ curl -s  -XPOST 'localhost:3000/v1/pets/1/photos' -H 'Content-type: multipart/form-data' |jq
+{
+  "message": "request/body must have required property 'file'",
+  "errors": [
+    {
+      "path": "/body/file",
+      "message": "must have required property 'file'",
+      "errorCode": "required.openapi.validation"
+    }
+  ],
+  "code": 400
+}
+```
+
diff --git a/examples/1-standard-oas-3.1/api.yaml b/examples/1-standard-oas-3.1/api.yaml
new file mode 100644
index 0000000..b236c4c
--- /dev/null
+++ b/examples/1-standard-oas-3.1/api.yaml
@@ -0,0 +1,157 @@
+openapi: 3.1.0
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+    url: https://opensource.org/licenses/MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: true
+          schema:
+            type: integer
+            format: int32
+      responses:
+        '200':
+          description: A paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pets'
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Pet'
+      responses:
+        '201':
+          description: Null response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pet'
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+  /pets/{id}:
+    get:
+      summary: Info for a specific pet
+      operationId: showPetById
+      tags:
+        - pets
+      parameters:
+        - name: id
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          schema:
+            type: number
+      responses:
+        '200':
+          description: Expected response to a valid request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pet'
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+
+  /pets/{id}/photos:
+    post:
+      summary: upload a photo of the pet
+      operationId: uploadPetPhoto
+      parameters:
+        - name: id
+          in: path
+          description: ID of pet to fetch
+          required: true
+          schema:
+            type: integer
+            format: int64
+      requestBody:
+        content:
+          multipart/form-data:
+            schema:
+              type: object
+              required:
+                - file
+              properties:
+                file:
+                  description: The photo
+                  type: string
+                  format: binary
+        required: true
+      responses:
+        '200':
+          description: Null response
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  success:
+                    type: boolean
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: '#/components/schemas/Pet'
+    Error:
+      type: object
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
diff --git a/examples/1-standard-oas-3.1/app.js b/examples/1-standard-oas-3.1/app.js
new file mode 100644
index 0000000..8ee5d61
--- /dev/null
+++ b/examples/1-standard-oas-3.1/app.js
@@ -0,0 +1,86 @@
+const express = require('express');
+const path = require('path');
+const http = require('http');
+const cookieParser = require('cookie-parser'); // Add if using cookie auth
+const { Pets } = require('./services');
+const OpenApiValidator = require('express-openapi-validator');
+
+const port = 3000;
+const app = express();
+const apiSpec = path.join(__dirname, 'api.yaml');
+
+// 1. Install bodyParsers for the request types your API will support
+app.use(express.urlencoded({ extended: false }));
+app.use(express.text());
+app.use(express.json());
+app.use(cookieParser()); // Add if using cookie auth enables req.cookies
+
+// Optionally serve the API spec
+app.use('/spec', express.static(apiSpec));
+
+//  2. Install the OpenApiValidator on your express app
+app.use(
+  OpenApiValidator.middleware({
+    apiSpec,
+    validateResponses: true, // default false
+  }),
+);
+
+const pets = new Pets();
+// 3. Add routes
+app.get('/v1/ping', function (req, res, next) {
+  res.send('pong');
+});
+app.get('/v1/pets', function (req, res, next) {
+  res.json(pets.findAll(req.query));
+});
+
+app.post('/v1/pets', function (req, res, next) {
+  res.status(201).json(pets.create(req.body));
+});
+
+app.delete('/v1/pets/:id', function (req, res, next) {
+  res.json(pets.delete(req.params.id));
+});
+
+app.get('/v1/pets/:id', function (req, res, next) {
+  const pet = pets.findById(req.params.id);
+  return pet
+    ? res.json({ ...pet })
+    : res.status(404).json({ message: 'not found', code: 23 });
+});
+
+// 3a. Add a route upload file(s)
+app.post('/v1/pets/:id/photos', function (req, res, next) {
+  // DO something with the file
+  // files are found in req.files
+  // non file multipar params are in req.body['my-param']
+  console.log(req.files);
+
+  res.json({
+    files_metadata: req.files.map((f) => ({
+      originalname: f.originalname,
+      encoding: f.encoding,
+      mimetype: f.mimetype,
+      // Buffer of file conents
+      // buffer: f.buffer,
+    })),
+  });
+});
+
+// 4. Create a custom error handler
+app.use((err, req, res, next) => {
+  // format errors
+  // console.log(err)
+  res.status(err.status || 500).json({
+    message: err.message,
+    errors: err.errors,
+    code: err.status ?? 500,
+  });
+});
+
+
+http.createServer(app).listen(port);
+console.log(`Listening on port ${port}`);
+
+module.exports = app;
diff --git a/examples/1-standard-oas-3.1/package-lock.json b/examples/1-standard-oas-3.1/package-lock.json
new file mode 100644
index 0000000..7527836
--- /dev/null
+++ b/examples/1-standard-oas-3.1/package-lock.json
@@ -0,0 +1,663 @@
+{
+  "name": "example",
+  "version": "1.0.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "example",
+      "version": "1.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "express-openapi-validator": "file:../../dist/index.js"
+      },
+      "devDependencies": {
+        "nodemon": "^2.0.22"
+      }
+    },
+    "../../dist/index.js": {},
+    "node_modules/abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://paulmillr.com/funding/"
+        }
+      ],
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "node_modules/express-openapi-validator": {
+      "resolved": "../../dist/index.js",
+      "link": true
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+      "dev": true
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/nodemon": {
+      "version": "2.0.22",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
+      "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.5.2",
+        "debug": "^3.2.7",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.1.2",
+        "pstree.remy": "^1.1.8",
+        "semver": "^5.7.1",
+        "simple-update-notifier": "^1.0.7",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.5"
+      },
+      "bin": {
+        "nodemon": "bin/nodemon.js"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nodemon"
+      }
+    },
+    "node_modules/nodemon/node_modules/debug": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
+    "node_modules/nodemon/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+      "dev": true,
+      "dependencies": {
+        "abbrev": "1"
+      },
+      "bin": {
+        "nopt": "bin/nopt.js"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pstree.remy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+      "dev": true
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/semver": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
+    "node_modules/simple-update-notifier": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
+      "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
+      "dev": true,
+      "dependencies": {
+        "semver": "~7.0.0"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/simple-update-notifier/node_modules/semver": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+      "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/touch": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "dev": true,
+      "dependencies": {
+        "nopt": "~1.0.10"
+      },
+      "bin": {
+        "nodetouch": "bin/nodetouch.js"
+      }
+    },
+    "node_modules/undefsafe": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+      "dev": true
+    }
+  },
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "requires": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "chokidar": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+      "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+      "dev": true,
+      "requires": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "fsevents": "~2.3.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "express-openapi-validator": {
+      "version": "file:../../dist/index.js"
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "optional": true
+    },
+    "glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^2.0.0"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "nodemon": {
+      "version": "2.0.22",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
+      "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
+      "dev": true,
+      "requires": {
+        "chokidar": "^3.5.2",
+        "debug": "^3.2.7",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.1.2",
+        "pstree.remy": "^1.1.8",
+        "semver": "^5.7.1",
+        "simple-update-notifier": "^1.0.7",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.5"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+          "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+      "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true
+    },
+    "pstree.remy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+      "dev": true
+    },
+    "readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "requires": {
+        "picomatch": "^2.2.1"
+      }
+    },
+    "semver": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+      "dev": true
+    },
+    "simple-update-notifier": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
+      "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
+      "dev": true,
+      "requires": {
+        "semver": "~7.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+          "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+          "dev": true
+        }
+      }
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "touch": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+      "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+      "dev": true,
+      "requires": {
+        "nopt": "~1.0.10"
+      }
+    },
+    "undefsafe": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+      "dev": true
+    }
+  }
+}
diff --git a/examples/1-standard-oas-3.1/package.json b/examples/1-standard-oas-3.1/package.json
new file mode 100644
index 0000000..bbc8f72
--- /dev/null
+++ b/examples/1-standard-oas-3.1/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "example",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "node app.js",
+    "deps": "cd ../../ && npm i && npm run compile",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "express-openapi-validator": "file:../../dist/index.js"
+  },
+  "devDependencies": {
+    "nodemon": "^2.0.22"
+  }
+}
diff --git a/examples/1-standard-oas-3.1/services/index.js b/examples/1-standard-oas-3.1/services/index.js
new file mode 100644
index 0000000..952dc10
--- /dev/null
+++ b/examples/1-standard-oas-3.1/services/index.js
@@ -0,0 +1,44 @@
+let data = [
+  {
+    id: 1,
+    name: 'sparky',
+    type: 'dog',
+    tags: ['sweet'],
+  },
+  {
+    id: 2,
+    name: 'buzz',
+    type: 'cat',
+    tags: ['purrfect'],
+  },
+  {
+    id: 3,
+    name: 'max',
+    type: 'dog',
+    tags: [],
+  },
+];
+
+module.exports.Pets = class {
+  constructor() {
+    this.id = 4;
+  }
+  findAll({ type, limit }) {
+    return data.filter(d => !type || d.type === type).slice(0, limit);
+  }
+
+  findById(id) {
+    console.log(id, data, data.filter(p => p.id === id), typeof id);
+    return data.filter(p => p.id === id)[0];
+  }
+
+  create(pet) {
+    const npet = { id: this.id++, ...pet };
+    data.push(npet);
+    return npet;
+  }
+
+  delete(id) {
+    return data.filter(e => e.id !== id);
+  }
+};
diff --git a/examples/3-eov-operations/package-lock.json b/examples/3-eov-operations/package-lock.json
index ca55c16..b7acaf9 100644
--- a/examples/3-eov-operations/package-lock.json
+++ b/examples/3-eov-operations/package-lock.json
@@ -13,8 +13,12 @@
         "morgan": "^1.10.0"
       },
       "devDependencies": {
+        "@eslint/js": "^9.14.0",
+        "eslint": "^9.14.0",
+        "globals": "^15.12.0",
         "nodemon": "^2.0.4",
-        "prettier": "^2.1.1"
+        "prettier": "^2.1.1",
+        "typescript-eslint": "^8.13.0"
       }
     },
     "node_modules/@apidevtools/json-schema-ref-parser": {
@@ -34,11 +38,302 @@
         "url": "https://github.com/sponsors/philsturgeon"
       }
     },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+      "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
+      "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/object-schema": "^2.1.4",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/config-array/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@eslint/config-array/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/@eslint/core": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
+      "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+      "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/@eslint/eslintrc/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/@eslint/js": {
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz",
+      "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
+      "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
+      "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
+      "dev": true,
+      "dependencies": {
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node": {
+      "version": "0.16.6",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+      "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+      "dev": true,
+      "dependencies": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+      "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+      "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
     "node_modules/@jsdevtools/ono": {
       "version": "7.1.3",
       "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
       "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
     },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/@types/body-parser": {
       "version": "1.19.5",
       "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -58,6 +353,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true
+    },
     "node_modules/@types/express": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
@@ -151,6 +452,305 @@
         "@types/send": "*"
       }
     },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz",
+      "integrity": "sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/type-utils": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.3.1",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^1.3.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+        "eslint": "^8.57.0 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.13.0.tgz",
+      "integrity": "sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/typescript-estree": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz",
+      "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.13.0.tgz",
+      "integrity": "sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^1.3.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz",
+      "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz",
+      "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "debug": "^4.3.4",
+        "fast-glob": "^3.3.2",
+        "is-glob": "^4.0.3",
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^1.3.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz",
+      "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/typescript-estree": "8.13.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz",
+      "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.13.0",
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -171,6 +771,27 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/acorn": {
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
     "node_modules/ajv": {
       "version": "8.17.1",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -216,6 +837,21 @@
         }
       }
     },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
     "node_modules/anymatch": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -320,12 +956,12 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -375,6 +1011,52 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chalk/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/chalk/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -402,6 +1084,24 @@
         "fsevents": "~2.3.2"
       }
     },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -485,6 +1185,20 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
     },
+    "node_modules/cross-spawn": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
+      "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -493,6 +1207,12 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
     "node_modules/define-data-property": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -540,35 +1260,251 @@
       "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
       "peer": true,
       "engines": {
-        "node": ">= 0.8"
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "peer": true,
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "peer": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "peer": true
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz",
+      "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.18.0",
+        "@eslint/core": "^0.7.0",
+        "@eslint/eslintrc": "^3.1.0",
+        "@eslint/js": "9.14.0",
+        "@eslint/plugin-kit": "^0.2.0",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.0",
+        "@types/estree": "^1.0.6",
+        "@types/json-schema": "^7.0.15",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^8.2.0",
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "esquery": "^1.5.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "text-table": "^0.2.0"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "jiti": "*"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+      "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint/node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/eslint/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint/node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/eslint/node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/eslint/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/espree": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^4.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
       }
     },
-    "node_modules/es-define-property": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
-      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
-      "peer": true,
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
       "dependencies": {
-        "get-intrinsic": "^1.2.4"
+        "estraverse": "^5.2.0"
       },
       "engines": {
-        "node": ">= 0.4"
+        "node": ">=4.0"
       }
     },
-    "node_modules/es-errors": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
-      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-      "peer": true,
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
       "engines": {
-        "node": ">= 0.4"
+        "node": ">=4.0"
       }
     },
-    "node_modules/escape-html": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "peer": true
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
     },
     "node_modules/etag": {
       "version": "1.8.1",
@@ -690,16 +1626,65 @@
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
+    "node_modules/fast-glob": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
     "node_modules/fast-uri": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
       "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
       "license": "MIT"
     },
+    "node_modules/fastq": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
@@ -738,6 +1723,41 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+      "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+      "dev": true
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -811,6 +1831,18 @@
         "node": ">= 6"
       }
     },
+    "node_modules/globals": {
+      "version": "15.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
+      "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/gopd": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -823,6 +1855,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
     "node_modules/has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -908,12 +1946,46 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
     "node_modules/ignore-by-default": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
       "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
       "dev": true
     },
+    "node_modules/import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
     "node_modules/inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -976,6 +2048,12 @@
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
       "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
     },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
     "node_modules/js-yaml": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -988,11 +2066,60 @@
         "js-yaml": "bin/js-yaml.js"
       }
     },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
     "node_modules/json-schema-traverse": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
     },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/lodash.clonedeep": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -1003,6 +2130,12 @@
       "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
       "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
     },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
     "node_modules/media-typer": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
@@ -1020,6 +2153,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -1030,6 +2172,19 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
     "node_modules/mime": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -1129,6 +2284,12 @@
         "node": ">= 6.0.0"
       }
     },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
     "node_modules/negotiator": {
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1250,6 +2411,65 @@
         "@jsdevtools/ono": "7.1.3"
       }
     },
+    "node_modules/optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1259,6 +2479,24 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/path-to-regexp": {
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
@@ -1280,6 +2518,15 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/prettier": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz",
@@ -1317,6 +2564,15 @@
       "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
       "dev": true
     },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/qs": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -1332,6 +2588,26 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
     "node_modules/range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1390,6 +2666,48 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
     "node_modules/safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -1490,14 +2808,35 @@
         "has-property-descriptors": "^1.0.2"
       },
       "engines": {
-        "node": ">= 0.4"
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "peer": true
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
       }
     },
-    "node_modules/setprototypeof": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
-      "peer": true
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
     },
     "node_modules/side-channel": {
       "version": "1.0.6",
@@ -1563,6 +2902,18 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -1575,6 +2926,12 @@
         "node": ">=4"
       }
     },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1608,6 +2965,30 @@
         "nodetouch": "bin/nodetouch.js"
       }
     },
+    "node_modules/ts-api-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz",
+      "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.2.0"
+      }
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/type-is": {
       "version": "1.6.18",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -1633,6 +3014,43 @@
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
       "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
     },
+    "node_modules/typescript": {
+      "version": "5.6.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "dev": true,
+      "peer": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/typescript-eslint": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.13.0.tgz",
+      "integrity": "sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "8.13.0",
+        "@typescript-eslint/parser": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/undefsafe": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -1654,6 +3072,15 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -1679,6 +3106,30 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -1686,6 +3137,18 @@
       "engines": {
         "node": ">=0.4"
       }
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
     }
   },
   "dependencies": {
@@ -1699,11 +3162,209 @@
         "js-yaml": "^4.1.0"
       }
     },
+    "@eslint-community/eslint-utils": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+      "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "3.4.3",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+          "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+          "dev": true
+        }
+      }
+    },
+    "@eslint-community/regexpp": {
+      "version": "4.12.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+      "dev": true
+    },
+    "@eslint/config-array": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
+      "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+      "dev": true,
+      "requires": {
+        "@eslint/object-schema": "^2.1.4",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "@eslint/core": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
+      "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
+      "dev": true
+    },
+    "@eslint/eslintrc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+      "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.6",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+          "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "globals": {
+          "version": "14.0.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+          "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+          "dev": true
+        },
+        "json-schema-traverse": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "@eslint/js": {
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz",
+      "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==",
+      "dev": true
+    },
+    "@eslint/object-schema": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
+      "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+      "dev": true
+    },
+    "@eslint/plugin-kit": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
+      "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
+      "dev": true,
+      "requires": {
+        "levn": "^0.4.1"
+      }
+    },
+    "@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true
+    },
+    "@humanfs/node": {
+      "version": "0.16.6",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+      "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+      "dev": true,
+      "requires": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.3.0"
+      },
+      "dependencies": {
+        "@humanwhocodes/retry": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+          "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+          "dev": true
+        }
+      }
+    },
+    "@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true
+    },
+    "@humanwhocodes/retry": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+      "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+      "dev": true
+    },
     "@jsdevtools/ono": {
       "version": "7.1.3",
       "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
       "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
     },
+    "@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      }
+    },
+    "@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true
+    },
+    "@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      }
+    },
     "@types/body-parser": {
       "version": "1.19.5",
       "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -1721,6 +3382,12 @@
         "@types/node": "*"
       }
     },
+    "@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true
+    },
     "@types/express": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
@@ -1798,9 +3465,188 @@
       "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
       "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
       "requires": {
-        "@types/http-errors": "*",
-        "@types/node": "*",
-        "@types/send": "*"
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
+    "@typescript-eslint/eslint-plugin": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz",
+      "integrity": "sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==",
+      "dev": true,
+      "requires": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/type-utils": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.3.1",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^1.3.0"
+      }
+    },
+    "@typescript-eslint/parser": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.13.0.tgz",
+      "integrity": "sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/typescript-estree": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "debug": "^4.3.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "@typescript-eslint/scope-manager": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz",
+      "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0"
+      }
+    },
+    "@typescript-eslint/type-utils": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.13.0.tgz",
+      "integrity": "sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/typescript-estree": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^1.3.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "@typescript-eslint/types": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz",
+      "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==",
+      "dev": true
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz",
+      "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/visitor-keys": "8.13.0",
+        "debug": "^4.3.4",
+        "fast-glob": "^3.3.2",
+        "is-glob": "^4.0.3",
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^1.3.0"
+      },
+      "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "dev": true,
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "minimatch": {
+          "version": "9.0.5",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+          "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+          "dev": true,
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        },
+        "semver": {
+          "version": "7.6.3",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+          "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+          "dev": true
+        }
+      }
+    },
+    "@typescript-eslint/utils": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz",
+      "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==",
+      "dev": true,
+      "requires": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@typescript-eslint/scope-manager": "8.13.0",
+        "@typescript-eslint/types": "8.13.0",
+        "@typescript-eslint/typescript-estree": "8.13.0"
+      }
+    },
+    "@typescript-eslint/visitor-keys": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz",
+      "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "8.13.0",
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "3.4.3",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+          "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+          "dev": true
+        }
       }
     },
     "abbrev": {
@@ -1819,6 +3665,19 @@
         "negotiator": "0.6.3"
       }
     },
+    "acorn": {
+      "version": "8.14.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+      "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "requires": {}
+    },
     "ajv": {
       "version": "8.17.1",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
@@ -1844,6 +3703,15 @@
         "ajv": "^8.0.0"
       }
     },
+    "ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^2.0.1"
+      }
+    },
     "anymatch": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -1932,12 +3800,12 @@
       }
     },
     "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "requires": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       }
     },
     "buffer-from": {
@@ -1972,6 +3840,39 @@
         "set-function-length": "^1.2.1"
       }
     },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "dependencies": {
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
     "chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -1988,6 +3889,21 @@
         "readdirp": "~3.6.0"
       }
     },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2044,6 +3960,17 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
     },
+    "cross-spawn": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
+      "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      }
+    },
     "debug": {
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2052,6 +3979,12 @@
         "ms": "2.0.0"
       }
     },
+    "deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
     "define-data-property": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2106,6 +4039,156 @@
       "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
       "peer": true
     },
+    "escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true
+    },
+    "eslint": {
+      "version": "9.14.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz",
+      "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==",
+      "dev": true,
+      "requires": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.18.0",
+        "@eslint/core": "^0.7.0",
+        "@eslint/eslintrc": "^3.1.0",
+        "@eslint/js": "9.14.0",
+        "@eslint/plugin-kit": "^0.2.0",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.0",
+        "@types/estree": "^1.0.6",
+        "@types/json-schema": "^7.0.15",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^8.2.0",
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "esquery": "^1.5.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "text-table": "^0.2.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "6.12.6",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+          "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "fast-json-stable-stringify": "^2.0.0",
+            "json-schema-traverse": "^0.4.1",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "debug": {
+          "version": "4.3.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+          "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.3"
+          }
+        },
+        "glob-parent": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+          "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+          "dev": true,
+          "requires": {
+            "is-glob": "^4.0.3"
+          }
+        },
+        "json-schema-traverse": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+          "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-scope": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+      "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "dev": true
+    },
+    "espree": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "dev": true,
+      "requires": {
+        "acorn": "^8.14.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^4.2.0"
+      }
+    },
+    "esquery": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      }
+    },
+    "estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
     "etag": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -2199,15 +4282,58 @@
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
+    "fast-glob": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      }
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
     "fast-uri": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
       "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw=="
     },
+    "fastq": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+      "dev": true,
+      "requires": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^4.0.0"
+      }
+    },
     "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "requires": {
         "to-regex-range": "^5.0.1"
@@ -2239,6 +4365,32 @@
         }
       }
     },
+    "find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      }
+    },
+    "flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "requires": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      }
+    },
+    "flatted": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+      "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+      "dev": true
+    },
     "forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2286,6 +4438,12 @@
         "is-glob": "^4.0.1"
       }
     },
+    "globals": {
+      "version": "15.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
+      "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
+      "dev": true
+    },
     "gopd": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -2295,6 +4453,12 @@
         "get-intrinsic": "^1.1.3"
       }
     },
+    "graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -2353,12 +4517,34 @@
         "safer-buffer": ">= 2.1.2 < 3"
       }
     },
+    "ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true
+    },
     "ignore-by-default": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
       "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
       "dev": true
     },
+    "import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true
+    },
     "inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -2405,6 +4591,12 @@
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
       "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
     },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
     "js-yaml": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2413,11 +4605,51 @@
         "argparse": "^2.0.1"
       }
     },
+    "json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
     "json-schema-traverse": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
     },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "requires": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      }
+    },
+    "locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^5.0.0"
+      }
+    },
     "lodash.clonedeep": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
@@ -2428,6 +4660,12 @@
       "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
       "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
     },
+    "lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
     "media-typer": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
@@ -2439,12 +4677,28 @@
       "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
       "peer": true
     },
+    "merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true
+    },
     "methods": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
       "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
       "peer": true
     },
+    "micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "requires": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      }
+    },
     "mime": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -2517,6 +4771,12 @@
         "xtend": "^4.0.0"
       }
     },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
     "negotiator": {
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -2605,12 +4865,65 @@
         "@jsdevtools/ono": "7.1.3"
       }
     },
+    "optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "requires": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      }
+    },
+    "p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "requires": {
+        "yocto-queue": "^0.1.0"
+      }
+    },
+    "p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^3.0.2"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
     "parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
       "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
       "peer": true
     },
+    "path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true
+    },
+    "path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true
+    },
     "path-to-regexp": {
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
@@ -2622,6 +4935,12 @@
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
       "dev": true
     },
+    "prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true
+    },
     "prettier": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz",
@@ -2649,6 +4968,12 @@
       "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
       "dev": true
     },
+    "punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true
+    },
     "qs": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -2658,6 +4983,12 @@
         "side-channel": "^1.0.6"
       }
     },
+    "queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true
+    },
     "range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -2704,6 +5035,27 @@
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
       "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
     },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true
+    },
+    "run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "requires": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
     "safe-buffer": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -2797,6 +5149,21 @@
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
       "peer": true
     },
+    "shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^3.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true
+    },
     "side-channel": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
@@ -2845,6 +5212,12 @@
         "safe-buffer": "~5.1.0"
       }
     },
+    "strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
     "supports-color": {
       "version": "5.5.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2854,6 +5227,12 @@
         "has-flag": "^3.0.0"
       }
     },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
     "to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2878,6 +5257,22 @@
         "nopt": "~1.0.10"
       }
     },
+    "ts-api-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz",
+      "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==",
+      "dev": true,
+      "requires": {}
+    },
+    "type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1"
+      }
+    },
     "type-is": {
       "version": "1.6.18",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -2899,6 +5294,24 @@
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
       "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
     },
+    "typescript": {
+      "version": "5.6.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "dev": true,
+      "peer": true
+    },
+    "typescript-eslint": {
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.13.0.tgz",
+      "integrity": "sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/eslint-plugin": "8.13.0",
+        "@typescript-eslint/parser": "8.13.0",
+        "@typescript-eslint/utils": "8.13.0"
+      }
+    },
     "undefsafe": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -2916,6 +5329,15 @@
       "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
       "peer": true
     },
+    "uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2933,10 +5355,31 @@
       "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
       "peer": true
     },
+    "which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true
+    },
     "xtend": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
       "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    },
+    "yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true
     }
   }
 }
diff --git a/examples/3-eov-operations/package.json b/examples/3-eov-operations/package.json
index 5cb79fa..325cc19 100644
--- a/examples/3-eov-operations/package.json
+++ b/examples/3-eov-operations/package.json
@@ -16,7 +16,11 @@
     "morgan": "^1.10.0"
   },
   "devDependencies": {
+    "@eslint/js": "^9.14.0",
+    "eslint": "^9.14.0",
+    "globals": "^15.12.0",
     "nodemon": "^2.0.4",
-    "prettier": "^2.1.1"
+    "prettier": "^2.1.1",
+    "typescript-eslint": "^8.13.0"
   }
 }
diff --git a/examples/9-nestjs/package.json b/examples/9-nestjs/package.json
index fa776b3..993e8ad 100644
--- a/examples/9-nestjs/package.json
+++ b/examples/9-nestjs/package.json
@@ -26,6 +26,9 @@
     "shx": "^0.3.3"
   },
   "devDependencies": {
+    "@eslint/js": "^9.14.0",
+    "eslint": "^9.14.0",
+    "globals": "^15.12.0",
     "@nestjs/cli": "^10.4.5",
     "@nestjs/testing": "^10.3.8",
     "@types/jest": "^27.0.2",
diff --git a/package-lock.json b/package-lock.json
index 51cf9a2..c40c07b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "express-openapi-validator",
-  "version": "5.3.9",
+  "version": "5.4.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "express-openapi-validator",
-      "version": "5.3.9",
+      "version": "5.4.0",
       "license": "MIT",
       "dependencies": {
         "@apidevtools/json-schema-ref-parser": "^11.7.2",
diff --git a/package.json b/package.json
index 653eff6..0fc6c22 100644
--- a/package.json
+++ b/package.json
@@ -1,14 +1,14 @@
 {
   "name": "express-openapi-validator",
-  "version": "5.3.9",
+  "version": "5.4.0",
   "description": "Automatically validate API requests and responses with OpenAPI 3 and Express.",
   "main": "dist/index.js",
   "scripts": {
     "compile": "rimraf dist && tsc",
     "compile:release": "rimraf dist && tsc --sourceMap false",
-    "test": "mocha -r source-map-support/register -r ts-node/register --files --recursive -R spec test/**/*.spec.ts",
-    "test:debug": "mocha -r source-map-support/register -r ts-node/register --inspect-brk --files --recursive test/**/*.spec.ts",
-    "test:coverage": "nyc mocha -r source-map-support/register -r ts-node/register --recursive test/**/*.spec.ts",
+    "test": "mocha -r source-map-support/register -r ts-node/register --files --recursive -R spec 'test/**/*.spec.ts' 'test/*.spec.ts'",
+    "test:debug": "mocha -r source-map-support/register -r ts-node/register --inspect-brk --files --recursive 'test/**/*.spec.ts' 'test/*.spec.ts'",
+    "test:coverage": "nyc mocha -r source-map-support/register -r ts-node/register --recursive 'test/**/*.spec.ts' 'test/*.spec.ts'",
     "test:reset": "rimraf node_modules && npm i && npm run compile && npm t",
     "coveralls": "cat coverage/lcov.info | coveralls -v",
     "codacy": "bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage/lcov.info",
diff --git a/src/framework/ajv/factory.ts b/src/framework/ajv/factory.ts
new file mode 100644
index 0000000..9571bc5
--- /dev/null
+++ b/src/framework/ajv/factory.ts
@@ -0,0 +1,25 @@
+import { Options } from "ajv";
+import AjvDraft4 from 'ajv-draft-04';
+import Ajv2020 from 'ajv/dist/2020';
+import { assertVersion } from "../openapi/assert.version";
+import { AjvInstance } from "../types";
+
+export const factoryAjv = (version: string, options: Options): AjvInstance => {
+  const { minor } = assertVersion(version)
+
+  let ajvInstance: AjvInstance
+
+  if (minor === '0') {
+    ajvInstance = new AjvDraft4(options);
+  } else if (minor == '1') {
+    ajvInstance = new Ajv2020(options);
+  
+    // Open API 3.1 has a custom "media-range" attribute defined in its schema, but the spec does not define it. "It's not really intended to be validated"
+    // https://github.com/OAI/OpenAPI-Specification/issues/2714#issuecomment-923185689
+    // Since the schema is non-normative (https://github.com/OAI/OpenAPI-Specification/pull/3355#issuecomment-1915695294) we will only validate that it's a string
+    // as the spec states
+    ajvInstance.addFormat('media-range', true);
+  }
+
+  return ajvInstance
+}
\ No newline at end of file
diff --git a/src/framework/ajv/index.ts b/src/framework/ajv/index.ts
index dd43943..3e204b7 100644
--- a/src/framework/ajv/index.ts
+++ b/src/framework/ajv/index.ts
@@ -1,39 +1,42 @@
 import AjvDraft4 from 'ajv-draft-04';
+import Ajv2020 from 'ajv/dist/2020'
 import { DataValidateFunction } from 'ajv/dist/types';
 import ajvType from 'ajv/dist/vocabularies/jtd/type';
 import addFormats from 'ajv-formats';
 import { formats } from './formats';
-import { OpenAPIV3, Options, SerDes } from '../types';
+import { AjvInstance, OpenAPIV3, Options, SerDes } from '../types';
 import * as traverse from 'json-schema-traverse';
+import { factoryAjv } from './factory';
 
 interface SerDesSchema extends Partial<SerDes> {
   kind?: 'req' | 'res';
 }
 
 export function createRequestAjv(
-  openApiSpec: OpenAPIV3.Document,
+  openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   options: Options = {},
-): AjvDraft4 {
+): AjvInstance {
   return createAjv(openApiSpec, options);
 }
 
 export function createResponseAjv(
-  openApiSpec: OpenAPIV3.Document,
+  openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   options: Options = {},
-): AjvDraft4 {
+): AjvInstance {
   return createAjv(openApiSpec, options, false);
 }
 
 function createAjv(
-  openApiSpec: OpenAPIV3.Document,
+  openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   options: Options = {},
   request = true,
-): AjvDraft4 {
+): AjvInstance {
   const { ajvFormats, ...ajvOptions } = options;
-  const ajv = new AjvDraft4({
+
+  const ajv = factoryAjv(openApiSpec.openapi, {
     ...ajvOptions,
-    formats: formats,
-  });
+    formats
+  })
 
   // Clean openApiSpec
   traverse(openApiSpec, { allKeys: true }, <traverse.Callback>(schema => {
diff --git a/src/framework/index.ts b/src/framework/index.ts
index 471d618..8fe2ceb 100644
--- a/src/framework/index.ts
+++ b/src/framework/index.ts
@@ -75,7 +75,7 @@ export class OpenAPIFramework {
   private loadSpec(
     filePath: string | object,
     $refParser: { mode: 'bundle' | 'dereference' } = { mode: 'bundle' },
-  ): Promise<OpenAPIV3.Document> {
+  ): Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1> {
     // Because of this issue ( https://github.com/APIDevTools/json-schema-ref-parser/issues/101#issuecomment-421755168 )
     // We need this workaround ( use '$RefParser.dereference' instead of '$RefParser.bundle' ) if asked by user
     if (typeof filePath === 'string') {
@@ -87,7 +87,7 @@ export class OpenAPIFramework {
           $refParser.mode === 'dereference'
             ? $RefParser.dereference(absolutePath)
             : $RefParser.bundle(absolutePath);
-        return doc as Promise<OpenAPIV3.Document>;
+        return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
       } else {
         throw new Error(
           `${this.loggingPrefix}spec could not be read at ${filePath}`,
@@ -98,10 +98,10 @@ export class OpenAPIFramework {
       $refParser.mode === 'dereference'
         ? $RefParser.dereference(filePath)
         : $RefParser.bundle(filePath);
-    return doc as Promise<OpenAPIV3.Document>;
+    return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
   }
 
-  private sortApiDocTags(apiDoc: OpenAPIV3.Document): void {
+  private sortApiDocTags(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): void {
     if (apiDoc && Array.isArray(apiDoc.tags)) {
       apiDoc.tags.sort((a, b): number => {
         return a.name < b.name ? -1 : 1;
diff --git a/src/framework/openapi.context.ts b/src/framework/openapi.context.ts
index 8d6fc23..eab583d 100644
--- a/src/framework/openapi.context.ts
+++ b/src/framework/openapi.context.ts
@@ -6,7 +6,7 @@ export interface RoutePair {
   openApiRoute: string;
 }
 export class OpenApiContext {
-  public readonly apiDoc: OpenAPIV3.Document;
+  public readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   public readonly expressRouteMap = {};
   public readonly openApiRouteMap = {};
   public readonly routes: RouteMetadata[] = [];
diff --git a/src/framework/openapi.schema.validator.ts b/src/framework/openapi.schema.validator.ts
index 03da012..3da4b03 100644
--- a/src/framework/openapi.schema.validator.ts
+++ b/src/framework/openapi.schema.validator.ts
@@ -1,12 +1,12 @@
-import AjvDraft4, {
+import {
   ErrorObject,
   Options,
   ValidateFunction,
 } from 'ajv-draft-04';
 import addFormats from 'ajv-formats';
-// https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.0/schema.json
-import * as openapi3Schema from './openapi.v3.schema.json';
 import { OpenAPIV3 } from './types.js';
+import { factoryAjv } from './ajv/factory';
+import { factorySchema } from './openapi/factory.schema';
 
 export interface OpenAPISchemaValidatorOpts {
   version: string;
@@ -17,7 +17,6 @@ export class OpenAPISchemaValidator {
   private validator: ValidateFunction;
   constructor(opts: OpenAPISchemaValidatorOpts) {
     const options: Options = {
-      schemaId: 'id',
       allErrors: true,
       validateFormats: true,
       coerceTypes: false,
@@ -29,18 +28,16 @@ export class OpenAPISchemaValidator {
       options.validateSchema = false;
     }
 
-    const v = new AjvDraft4(options);
-    addFormats(v, ['email', 'regex', 'uri', 'uri-reference']);
+    const ajvInstance = factoryAjv(opts.version, options)
+    const schema = factorySchema(opts.version)
 
-    const ver = opts.version && parseInt(String(opts.version), 10);
-    if (!ver) throw Error('version missing from OpenAPI specification');
-    if (ver != 3) throw Error('OpenAPI v3 specification version is required');
+    addFormats(ajvInstance, ['email', 'regex', 'uri', 'uri-reference']);
 
-    v.addSchema(openapi3Schema);
-    this.validator = v.compile(openapi3Schema);
+    ajvInstance.addSchema(schema);
+    this.validator = ajvInstance.compile(schema);
   }
 
-  public validate(openapiDoc: OpenAPIV3.Document): {
+  public validate(openapiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): {
     errors: Array<ErrorObject> | null;
   } {
     const valid = this.validator(openapiDoc);
diff --git a/src/framework/openapi.spec.loader.ts b/src/framework/openapi.spec.loader.ts
index f080f9a..f96374a 100644
--- a/src/framework/openapi.spec.loader.ts
+++ b/src/framework/openapi.spec.loader.ts
@@ -7,7 +7,7 @@ import {
 } from './types';
 
 export interface Spec {
-  apiDoc: OpenAPIV3.Document;
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   basePaths: string[];
   routes: RouteMetadata[];
   serial: number;
@@ -22,7 +22,7 @@ export interface RouteMetadata {
 }
 
 interface DiscoveredRoutes {
-  apiDoc: OpenAPIV3.Document;
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   basePaths: string[];
   routes: RouteMetadata[];
   serial: number;
@@ -53,7 +53,7 @@ export class OpenApiSpecLoader {
     const routes: RouteMetadata[] = [];
     const toExpressParams = this.toExpressParams;
     // const basePaths = this.framework.basePaths;
-    // let apiDoc: OpenAPIV3.Document = null;
+    // let apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 = null;
     // let basePaths: string[] = null;
     const { apiDoc, basePaths } = await this.framework.initialize({
       visitApi(ctx: OpenAPIFrameworkAPIContext): void {
@@ -61,36 +61,38 @@ export class OpenApiSpecLoader {
         const basePaths = ctx.basePaths;
         for (const bpa of basePaths) {
           const bp = bpa.replace(/\/$/, '');
-          for (const [path, methods] of Object.entries(apiDoc.paths)) {
-            for (const [method, schema] of Object.entries(methods)) {
-              if (
-                method.startsWith('x-') ||
-                ['parameters', 'summary', 'description'].includes(method)
-              ) {
-                continue;
+          if (apiDoc.paths) {
+            for (const [path, methods] of Object.entries(apiDoc.paths)) {
+              for (const [method, schema] of Object.entries(methods)) {
+                if (
+                  method.startsWith('x-') ||
+                  ['parameters', 'summary', 'description'].includes(method)
+                ) {
+                  continue;
+                }
+
+                const pathParams = [
+                  ...(schema.parameters ?? []),
+                  ...(methods.parameters ?? []),
+                ]
+                  .map(param => dereferenceParameter(apiDoc, param))
+                  .filter(param => param.in === 'path')
+                  .map(param => param.name);
+
+                const openApiRoute = `${bp}${path}`;
+                const expressRoute = `${openApiRoute}`
+                  .split(':')
+                  .map(toExpressParams)
+                  .join('\\:');
+  
+                routes.push({
+                  basePath: bp,
+                  expressRoute,
+                  openApiRoute,
+                  method: method.toUpperCase(),
+                  pathParams: Array.from(new Set(pathParams)),
+                });
               }
-
-              const pathParams = [
-                ...(schema.parameters ?? []),
-                ...(methods.parameters ?? []),
-              ]
-                .map(param => dereferenceParameter(apiDoc, param))
-                .filter(param => param.in === 'path')
-                .map(param => param.name);
-
-              const openApiRoute = `${bp}${path}`;
-              const expressRoute = `${openApiRoute}`
-                .split(':')
-                .map(toExpressParams)
-                .join('\\:');
-
-              routes.push({
-                basePath: bp,
-                expressRoute,
-                openApiRoute,
-                method: method.toUpperCase(),
-                pathParams: Array.from(new Set(pathParams)),
-              });
             }
           }
         }
diff --git a/src/framework/openapi.v3_1.modified.schema.json b/src/framework/openapi.v3_1.modified.schema.json
new file mode 100644
index 0000000..f34616f
--- /dev/null
+++ b/src/framework/openapi.v3_1.modified.schema.json
@@ -0,0 +1,1268 @@
+{
+  "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0",
+  "type": "object",
+  "properties": {
+    "openapi": {
+      "type": "string",
+      "pattern": "^3\\.1\\.\\d+(-.+)?$"
+    },
+    "info": {
+      "$ref": "#/$defs/info"
+    },
+    "jsonSchemaDialect": {
+      "type": "string",
+      "format": "uri",
+      "default": "https://spec.openapis.org/oas/3.1/dialect/base"
+    },
+    "servers": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/server"
+      },
+      "default": [
+        {
+          "url": "/"
+        }
+      ]
+    },
+    "paths": {
+      "$ref": "#/$defs/paths"
+    },
+    "webhooks": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/$defs/path-item"
+      }
+    },
+    "components": {
+      "$ref": "#/$defs/components"
+    },
+    "security": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/security-requirement"
+      }
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#/$defs/tag"
+      }
+    },
+    "externalDocs": {
+      "$ref": "#/$defs/external-documentation"
+    }
+  },
+  "required": ["openapi", "info"],
+  "anyOf": [
+    {
+      "required": ["paths"]
+    },
+    {
+      "required": ["components"]
+    },
+    {
+      "required": ["webhooks"]
+    }
+  ],
+  "$ref": "#/$defs/specification-extensions",
+  "unevaluatedProperties": false,
+  "$defs": {
+    "info": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object",
+      "type": "object",
+      "properties": {
+        "title": {
+          "type": "string"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "termsOfService": {
+          "type": "string",
+          "format": "uri"
+        },
+        "contact": {
+          "$ref": "#/$defs/contact"
+        },
+        "license": {
+          "$ref": "#/$defs/license"
+        },
+        "version": {
+          "type": "string"
+        }
+      },
+      "required": ["title", "version"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "contact": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        },
+        "email": {
+          "type": "string",
+          "format": "email"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "license": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "identifier": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "required": ["name"],
+      "dependentSchemas": {
+        "identifier": {
+          "not": {
+            "required": ["url"]
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "server": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object",
+      "type": "object",
+      "properties": {
+        "url": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "variables": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/server-variable"
+          }
+        }
+      },
+      "required": ["url"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "server-variable": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object",
+      "type": "object",
+      "properties": {
+        "enum": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "minItems": 1
+        },
+        "default": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "required": ["default"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "components": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object",
+      "type": "object",
+      "properties": {
+        "schemas": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/schema"
+          }
+        },
+        "responses": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/response-or-reference"
+          }
+        },
+        "parameters": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/example-or-reference"
+          }
+        },
+        "requestBodies": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/request-body-or-reference"
+          }
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "securitySchemes": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/security-scheme-or-reference"
+          }
+        },
+        "links": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/link-or-reference"
+          }
+        },
+        "callbacks": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/callbacks-or-reference"
+          }
+        },
+        "pathItems": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/path-item"
+          }
+        }
+      },
+      "patternProperties": {
+        "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
+          "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected",
+          "propertyNames": {
+            "pattern": "^[a-zA-Z0-9._-]+$"
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "paths": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object",
+      "type": "object",
+      "patternProperties": {
+        "^/": {
+          "$ref": "#/$defs/path-item"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "path-item": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object",
+      "type": "object",
+      "properties": {
+        "$ref": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/server"
+          }
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "get": {
+          "$ref": "#/$defs/operation"
+        },
+        "put": {
+          "$ref": "#/$defs/operation"
+        },
+        "post": {
+          "$ref": "#/$defs/operation"
+        },
+        "delete": {
+          "$ref": "#/$defs/operation"
+        },
+        "options": {
+          "$ref": "#/$defs/operation"
+        },
+        "head": {
+          "$ref": "#/$defs/operation"
+        },
+        "patch": {
+          "$ref": "#/$defs/operation"
+        },
+        "trace": {
+          "$ref": "#/$defs/operation"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "operation": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object",
+      "type": "object",
+      "properties": {
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/$defs/external-documentation"
+        },
+        "operationId": {
+          "type": "string"
+        },
+        "parameters": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/parameter-or-reference"
+          }
+        },
+        "requestBody": {
+          "$ref": "#/$defs/request-body-or-reference"
+        },
+        "responses": {
+          "$ref": "#/$defs/responses"
+        },
+        "callbacks": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/callbacks-or-reference"
+          }
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "security": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/security-requirement"
+          }
+        },
+        "servers": {
+          "type": "array",
+          "items": {
+            "$ref": "#/$defs/server"
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "external-documentation": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "required": ["url"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "parameter": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "in": {
+          "enum": ["query", "header", "path", "cookie"]
+        },
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "schema": {
+          "$ref": "#/$defs/schema"
+        },
+        "content": {
+          "$ref": "#/$defs/content",
+          "minProperties": 1,
+          "maxProperties": 1
+        }
+      },
+      "required": ["name", "in"],
+      "oneOf": [
+        {
+          "required": ["schema"]
+        },
+        {
+          "required": ["content"]
+        }
+      ],
+      "if": {
+        "properties": {
+          "in": {
+            "const": "query"
+          }
+        },
+        "required": ["in"]
+      },
+      "then": {
+        "properties": {
+          "allowEmptyValue": {
+            "default": false,
+            "type": "boolean"
+          }
+        }
+      },
+      "dependentSchemas": {
+        "schema": {
+          "properties": {
+            "style": {
+              "type": "string"
+            },
+            "explode": {
+              "type": "boolean"
+            }
+          },
+          "allOf": [
+            {
+              "$ref": "#/$defs/examples"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query"
+            },
+            {
+              "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie"
+            },
+            {
+              "$ref": "#/$defs/styles-for-form"
+            }
+          ],
+          "$defs": {
+            "styles-for-path": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "path"
+                  }
+                },
+                "required": ["in"]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "simple",
+                    "enum": ["matrix", "label", "simple"]
+                  },
+                  "required": {
+                    "const": true
+                  }
+                },
+                "required": ["required"]
+              }
+            },
+            "styles-for-header": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "header"
+                  }
+                },
+                "required": ["in"]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "simple",
+                    "const": "simple"
+                  }
+                }
+              }
+            },
+            "styles-for-query": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "query"
+                  }
+                },
+                "required": ["in"]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "form",
+                    "enum": [
+                      "form",
+                      "spaceDelimited",
+                      "pipeDelimited",
+                      "deepObject"
+                    ]
+                  },
+                  "allowReserved": {
+                    "default": false,
+                    "type": "boolean"
+                  }
+                }
+              }
+            },
+            "styles-for-cookie": {
+              "if": {
+                "properties": {
+                  "in": {
+                    "const": "cookie"
+                  }
+                },
+                "required": ["in"]
+              },
+              "then": {
+                "properties": {
+                  "style": {
+                    "default": "form",
+                    "const": "form"
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "parameter-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/parameter"
+      }
+    },
+    "request-body": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "content": {
+          "$ref": "#/$defs/content"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "required": ["content"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "request-body-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/request-body"
+      }
+    },
+    "content": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10",
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/$defs/media-type"
+      },
+      "propertyNames": {
+        "format": "media-range"
+      }
+    },
+    "media-type": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object",
+      "type": "object",
+      "properties": {
+        "schema": {
+          "$ref": "#/$defs/schema"
+        },
+        "encoding": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/encoding"
+          }
+        }
+      },
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/examples"
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+    "encoding": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object",
+      "type": "object",
+      "properties": {
+        "contentType": {
+          "type": "string",
+          "format": "media-range"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "style": {
+          "default": "form",
+          "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"]
+        },
+        "explode": {
+          "type": "boolean"
+        },
+        "allowReserved": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/styles-for-form"
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+    "responses": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object",
+      "type": "object",
+      "properties": {
+        "default": {
+          "$ref": "#/$defs/response-or-reference"
+        }
+      },
+      "patternProperties": {
+        "^[1-5](?:[0-9]{2}|XX)$": {
+          "$ref": "#/$defs/response-or-reference"
+        }
+      },
+      "minProperties": 1,
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false,
+      "if": {
+        "$comment": "either default, or at least one response code property must exist",
+        "patternProperties": {
+          "^[1-5](?:[0-9]{2}|XX)$": false
+        }
+      },
+      "then": {
+        "required": ["default"]
+      }
+    },
+    "response": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "headers": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/header-or-reference"
+          }
+        },
+        "content": {
+          "$ref": "#/$defs/content"
+        },
+        "links": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/link-or-reference"
+          }
+        }
+      },
+      "required": ["description"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "response-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/response"
+      }
+    },
+    "callbacks": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object",
+      "type": "object",
+      "$ref": "#/$defs/specification-extensions",
+      "additionalProperties": {
+        "$ref": "#/$defs/path-item"
+      }
+    },
+    "callbacks-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/callbacks"
+      }
+    },
+    "example": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object",
+      "type": "object",
+      "properties": {
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "value": true,
+        "externalValue": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "not": {
+        "required": ["value", "externalValue"]
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "example-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/example"
+      }
+    },
+    "link": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object",
+      "type": "object",
+      "properties": {
+        "operationRef": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "operationId": {
+          "type": "string"
+        },
+        "parameters": {
+          "$ref": "#/$defs/map-of-strings"
+        },
+        "requestBody": true,
+        "description": {
+          "type": "string"
+        },
+        "body": {
+          "$ref": "#/$defs/server"
+        }
+      },
+      "oneOf": [
+        {
+          "required": ["operationRef"]
+        },
+        {
+          "required": ["operationId"]
+        }
+      ],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "link-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/link"
+      }
+    },
+    "header": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object",
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "required": {
+          "default": false,
+          "type": "boolean"
+        },
+        "deprecated": {
+          "default": false,
+          "type": "boolean"
+        },
+        "schema": {
+          "$ref": "#/$defs/schema"
+        },
+        "content": {
+          "$ref": "#/$defs/content",
+          "minProperties": 1,
+          "maxProperties": 1
+        }
+      },
+      "oneOf": [
+        {
+          "required": ["schema"]
+        },
+        {
+          "required": ["content"]
+        }
+      ],
+      "dependentSchemas": {
+        "schema": {
+          "properties": {
+            "style": {
+              "default": "simple",
+              "const": "simple"
+            },
+            "explode": {
+              "default": false,
+              "type": "boolean"
+            }
+          },
+          "$ref": "#/$defs/examples"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "header-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/header"
+      }
+    },
+    "tag": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object",
+      "type": "object",
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/$defs/external-documentation"
+        }
+      },
+      "required": ["name"],
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false
+    },
+    "reference": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object",
+      "type": "object",
+      "properties": {
+        "$ref": {
+          "type": "string",
+          "format": "uri-reference"
+        },
+        "summary": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        }
+      }
+    },
+    "schema": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
+      "$dynamicAnchor": "meta",
+      "type": ["object", "boolean"]
+    },
+    "security-scheme": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object",
+      "type": "object",
+      "properties": {
+        "type": {
+          "enum": ["apiKey", "http", "mutualTLS", "oauth2", "openIdConnect"]
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "required": ["type"],
+      "allOf": [
+        {
+          "$ref": "#/$defs/specification-extensions"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-apikey"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-http"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-http-bearer"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-oauth2"
+        },
+        {
+          "$ref": "#/$defs/security-scheme/$defs/type-oidc"
+        }
+      ],
+      "unevaluatedProperties": false,
+      "$defs": {
+        "type-apikey": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "apiKey"
+              }
+            },
+            "required": ["type"]
+          },
+          "then": {
+            "properties": {
+              "name": {
+                "type": "string"
+              },
+              "in": {
+                "enum": ["query", "header", "cookie"]
+              }
+            },
+            "required": ["name", "in"]
+          }
+        },
+        "type-http": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "http"
+              }
+            },
+            "required": ["type"]
+          },
+          "then": {
+            "properties": {
+              "scheme": {
+                "type": "string"
+              }
+            },
+            "required": ["scheme"]
+          }
+        },
+        "type-http-bearer": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "http"
+              },
+              "scheme": {
+                "type": "string",
+                "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$"
+              }
+            },
+            "required": ["type", "scheme"]
+          },
+          "then": {
+            "properties": {
+              "bearerFormat": {
+                "type": "string"
+              }
+            }
+          }
+        },
+        "type-oauth2": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "oauth2"
+              }
+            },
+            "required": ["type"]
+          },
+          "then": {
+            "properties": {
+              "flows": {
+                "$ref": "#/$defs/oauth-flows"
+              }
+            },
+            "required": ["flows"]
+          }
+        },
+        "type-oidc": {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "openIdConnect"
+              }
+            },
+            "required": ["type"]
+          },
+          "then": {
+            "properties": {
+              "openIdConnectUrl": {
+                "type": "string",
+                "format": "uri"
+              }
+            },
+            "required": ["openIdConnectUrl"]
+          }
+        }
+      }
+    },
+    "security-scheme-or-reference": {
+      "if": {
+        "type": "object",
+        "required": ["$ref"]
+      },
+      "then": {
+        "$ref": "#/$defs/reference"
+      },
+      "else": {
+        "$ref": "#/$defs/security-scheme"
+      }
+    },
+    "oauth-flows": {
+      "type": "object",
+      "properties": {
+        "implicit": {
+          "$ref": "#/$defs/oauth-flows/$defs/implicit"
+        },
+        "password": {
+          "$ref": "#/$defs/oauth-flows/$defs/password"
+        },
+        "clientCredentials": {
+          "$ref": "#/$defs/oauth-flows/$defs/client-credentials"
+        },
+        "authorizationCode": {
+          "$ref": "#/$defs/oauth-flows/$defs/authorization-code"
+        }
+      },
+      "$ref": "#/$defs/specification-extensions",
+      "unevaluatedProperties": false,
+      "$defs": {
+        "implicit": {
+          "type": "object",
+          "properties": {
+            "authorizationUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": ["authorizationUrl", "scopes"],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "password": {
+          "type": "object",
+          "properties": {
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": ["tokenUrl", "scopes"],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "client-credentials": {
+          "type": "object",
+          "properties": {
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": ["tokenUrl", "scopes"],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        },
+        "authorization-code": {
+          "type": "object",
+          "properties": {
+            "authorizationUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "tokenUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "refreshUrl": {
+              "type": "string",
+              "format": "uri"
+            },
+            "scopes": {
+              "$ref": "#/$defs/map-of-strings"
+            }
+          },
+          "required": ["authorizationUrl", "tokenUrl", "scopes"],
+          "$ref": "#/$defs/specification-extensions",
+          "unevaluatedProperties": false
+        }
+      }
+    },
+    "security-requirement": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object",
+      "type": "object",
+      "additionalProperties": {
+        "type": "array",
+        "items": {
+          "type": "string"
+        }
+      }
+    },
+    "specification-extensions": {
+      "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions",
+      "patternProperties": {
+        "^x-": true
+      }
+    },
+    "examples": {
+      "properties": {
+        "example": true,
+        "examples": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/$defs/example-or-reference"
+          }
+        }
+      }
+    },
+    "map-of-strings": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "string"
+      }
+    },
+    "styles-for-form": {
+      "if": {
+        "properties": {
+          "style": {
+            "const": "form"
+          }
+        },
+        "required": ["style"]
+      },
+      "then": {
+        "properties": {
+          "explode": {
+            "default": true
+          }
+        }
+      },
+      "else": {
+        "properties": {
+          "explode": {
+            "default": false
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/framework/openapi/assert.version.ts b/src/framework/openapi/assert.version.ts
new file mode 100644
index 0000000..f5c6e05
--- /dev/null
+++ b/src/framework/openapi/assert.version.ts
@@ -0,0 +1,19 @@
+/**
+ * Asserts open api version
+ * 
+ * @param openApiVersion  SemVer version
+ * @returns destructured major and minor
+ */
+export const assertVersion = (openApiVersion: string) => {
+  const [ok, major, minor] = /^(\d+)\.(\d+).(\d+)?$/.exec(openApiVersion);
+  
+  if (!ok) { 
+    throw Error('Version missing from OpenAPI specification')
+  };
+
+  if (major !== '3' || minor !== '0' && minor !== '1') {
+    throw new Error('OpenAPI v3.0 or v3.1 specification version is required');
+  }
+
+  return { major, minor }
+}
\ No newline at end of file
diff --git a/src/framework/openapi/factory.schema.ts b/src/framework/openapi/factory.schema.ts
new file mode 100644
index 0000000..2c78e1a
--- /dev/null
+++ b/src/framework/openapi/factory.schema.ts
@@ -0,0 +1,16 @@
+import { assertVersion } from "./assert.version";
+
+// https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.0/schema.json
+import * as openapi3Schema from '../openapi.v3.schema.json';
+// https://github.com/OAI/OpenAPI-Specification/blob/master/schemas/v3.1/schema.json with dynamic refs replaced due to AJV bug - https://github.com/ajv-validator/ajv/issues/1745
+import * as openapi31Schema from '../openapi.v3_1.modified.schema.json';
+
+export const factorySchema = (version: string): Object => {
+  const { minor } = assertVersion(version);
+
+  if (minor === '0') {
+    return openapi3Schema;
+  }
+  
+  return openapi31Schema
+}
\ No newline at end of file
diff --git a/src/framework/types.ts b/src/framework/types.ts
index 2df2ea0..08b3c35 100644
--- a/src/framework/types.ts
+++ b/src/framework/types.ts
@@ -3,8 +3,12 @@ import * as multer from 'multer';
 import { FormatsPluginOptions } from 'ajv-formats';
 import { Request, Response, NextFunction, RequestHandler } from 'express';
 import { RouteMetadata } from './openapi.spec.loader';
+import AjvDraft4 from 'ajv-draft-04';
+import Ajv2020 from 'ajv/dist/2020';
 export { OpenAPIFrameworkArgs };
 
+export type AjvInstance = AjvDraft4 | Ajv2020 
+
 export type BodySchema =
   | OpenAPIV3.ReferenceObject
   | OpenAPIV3.SchemaObject
@@ -22,7 +26,7 @@ export interface ValidationSchema extends ParametersSchema {
 }
 
 export interface OpenAPIFrameworkInit {
-  apiDoc: OpenAPIV3.Document;
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   basePaths: string[];
 }
 export type SecurityHandlers = {
@@ -81,7 +85,7 @@ export type OperationHandlerOptions = {
   resolver: (
     handlersPath: string,
     route: RouteMetadata,
-    apiDoc: OpenAPIV3.Document,
+    apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   ) => unknown;
 };
 
@@ -146,7 +150,7 @@ type DeepImmutableObject<T> = {
 }
 
 export interface OpenApiValidatorOpts {
-  apiSpec: DeepImmutable<OpenAPIV3.Document> | string;
+  apiSpec: DeepImmutable<OpenAPIV3.DocumentV3> | DeepImmutable<OpenAPIV3.DocumentV3_1> | string;
   validateApiSpec?: boolean;
   validateResponses?: boolean | ValidateResponseOpts;
   validateRequests?: boolean | ValidateRequestOpts;
@@ -189,7 +193,7 @@ export interface NormalizedOpenApiValidatorOpts extends OpenApiValidatorOpts {
 }
 
 export namespace OpenAPIV3 {
-  export interface Document {
+  export interface DocumentV3 {
     openapi: string;
     info: InfoObject;
     servers?: ServerObject[];
@@ -200,6 +204,19 @@ export namespace OpenAPIV3 {
     externalDocs?: ExternalDocumentationObject;
   }
 
+  interface ComponentsV3_1 extends ComponentsObject {
+    pathItems?: { [path: string]: PathItemObject | ReferenceObject }
+  }
+
+  export interface DocumentV3_1 extends Omit<DocumentV3, 'paths' | 'info' | 'components'> {
+    paths?: DocumentV3['paths'];
+    info: InfoObjectV3_1;
+    components: ComponentsV3_1;
+    webhooks: {
+      [name: string]: PathItemObject | ReferenceObject
+    }
+  }
+
   export interface InfoObject {
     title: string;
     description?: string;
@@ -209,6 +226,10 @@ export namespace OpenAPIV3 {
     version: string;
   }
 
+  interface InfoObjectV3_1 extends InfoObject {
+    summary: string;
+  }
+
   export interface ContactObject {
     name?: string;
     url?: string;
@@ -512,7 +533,7 @@ export interface OpenAPIFrameworkPathObject {
 }
 
 interface OpenAPIFrameworkArgs {
-  apiDoc: OpenAPIV3.Document | string;
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 | string;
   validateApiSpec?: boolean;
   $refParser?: {
     mode: 'bundle' | 'dereference';
@@ -522,7 +543,7 @@ interface OpenAPIFrameworkArgs {
 export interface OpenAPIFrameworkAPIContext {
   // basePaths: BasePath[];
   basePaths: string[];
-  getApiDoc(): OpenAPIV3.Document;
+  getApiDoc(): OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
 }
 
 export interface OpenAPIFrameworkVisitor {
diff --git a/src/middlewares/openapi.metadata.ts b/src/middlewares/openapi.metadata.ts
index b2675c0..ee576b0 100644
--- a/src/middlewares/openapi.metadata.ts
+++ b/src/middlewares/openapi.metadata.ts
@@ -15,7 +15,7 @@ import { httpMethods } from './parsers/schema.preprocessor';
 
 export function applyOpenApiMetadata(
   openApiContext: OpenApiContext,
-  responseApiDoc: OpenAPIV3.Document,
+  responseApiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
 ): OpenApiRequestHandler {
   return (req: OpenApiRequest, res: Response, next: NextFunction): void => {
     // note base path is empty when path is fully qualified i.e. req.path.startsWith('')
diff --git a/src/middlewares/openapi.multipart.ts b/src/middlewares/openapi.multipart.ts
index e868b30..97a5fc4 100644
--- a/src/middlewares/openapi.multipart.ts
+++ b/src/middlewares/openapi.multipart.ts
@@ -15,7 +15,7 @@ import { MulterError } from 'multer';
 const multer = require('multer');
 
 export function multipart(
-  apiDoc: OpenAPIV3.Document,
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   options: MultipartOpts,
 ): OpenApiRequestHandler {
   const mult = multer(options.multerOpts);
diff --git a/src/middlewares/openapi.request.validator.ts b/src/middlewares/openapi.request.validator.ts
index 96f8d9b..3b79572 100644
--- a/src/middlewares/openapi.request.validator.ts
+++ b/src/middlewares/openapi.request.validator.ts
@@ -31,13 +31,13 @@ type ApiKeySecurityScheme = OpenAPIV3.ApiKeySecurityScheme;
 
 export class RequestValidator {
   private middlewareCache: { [key: string]: RequestHandler } = {};
-  private apiDoc: OpenAPIV3.Document;
+  private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   private ajv: Ajv;
   private ajvBody: Ajv;
   private requestOpts: ValidateRequestOpts = {};
 
   constructor(
-    apiDoc: OpenAPIV3.Document,
+    apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     options: RequestValidatorOptions = {},
   ) {
     this.middlewareCache = {};
@@ -278,7 +278,7 @@ export class RequestValidator {
 }
 
 class Validator {
-  private readonly apiDoc: OpenAPIV3.Document;
+  private readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   readonly schemaGeneral: object;
   readonly schemaBody: object;
   readonly validatorGeneral: ValidateFunction;
@@ -286,7 +286,7 @@ class Validator {
   readonly allSchemaProperties: ValidationSchema;
 
   constructor(
-    apiDoc: OpenAPIV3.Document,
+    apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     parametersSchema: ParametersSchema,
     bodySchema: BodySchema,
     ajv: {
@@ -340,7 +340,7 @@ class Validator {
 
 class Security {
   public static queryParam(
-    apiDocs: OpenAPIV3.Document,
+    apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     schema: OperationObject,
   ): string[] {
     const hasPathSecurity = schema.security?.length > 0 ?? false;
diff --git a/src/middlewares/openapi.response.validator.ts b/src/middlewares/openapi.response.validator.ts
index a187ad0..75b467e 100644
--- a/src/middlewares/openapi.response.validator.ts
+++ b/src/middlewares/openapi.response.validator.ts
@@ -27,7 +27,7 @@ interface ValidateResult {
 }
 export class ResponseValidator {
   private ajvBody: Ajv;
-  private spec: OpenAPIV3.Document;
+  private spec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   private validatorsCache: {
     [key: string]: { [key: string]: ValidateFunction };
   } = {};
@@ -35,7 +35,7 @@ export class ResponseValidator {
   private serial: number;
 
   constructor(
-    openApiSpec: OpenAPIV3.Document,
+    openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     options: Options = {},
     eovOptions: ValidateResponseOpts = {},
     serial: number = -1,
diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts
index d9fc4f8..6445618 100644
--- a/src/middlewares/openapi.security.ts
+++ b/src/middlewares/openapi.security.ts
@@ -42,7 +42,7 @@ function didOneSchemaPassValidation(results: (SecurityHandlerResult | SecurityHa
 }
 
 export function security(
-  apiDoc: OpenAPIV3.Document,
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   securityHandlers: SecurityHandlers,
 ): OpenApiRequestHandler {
   return async (req, res, next) => {
diff --git a/src/middlewares/parsers/req.parameter.mutator.ts b/src/middlewares/parsers/req.parameter.mutator.ts
index 5b92654..43da1f9 100644
--- a/src/middlewares/parsers/req.parameter.mutator.ts
+++ b/src/middlewares/parsers/req.parameter.mutator.ts
@@ -39,14 +39,14 @@ type Schema = ReferenceObject | SchemaObject;
  * the request is mutated to accomodate various styles and types e.g. form, explode, deepObject, etc
  */
 export class RequestParameterMutator {
-  private _apiDocs: OpenAPIV3.Document;
+  private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   private path: string;
   private ajv: Ajv;
   private parsedSchema: ValidationSchema;
 
   constructor(
     ajv: Ajv,
-    apiDocs: OpenAPIV3.Document,
+    apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     path: string,
     parsedSchema: ValidationSchema,
   ) {
diff --git a/src/middlewares/parsers/schema.parse.ts b/src/middlewares/parsers/schema.parse.ts
index cbd58e4..fb5d380 100644
--- a/src/middlewares/parsers/schema.parse.ts
+++ b/src/middlewares/parsers/schema.parse.ts
@@ -17,9 +17,9 @@ type Parameter = OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject;
  */
 export class ParametersSchemaParser {
   private _ajv: Ajv;
-  private _apiDocs: OpenAPIV3.Document;
+  private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
 
-  constructor(ajv: Ajv, apiDocs: OpenAPIV3.Document) {
+  constructor(ajv: Ajv, apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
     this._ajv = ajv;
     this._apiDocs = apiDocs;
   }
diff --git a/src/middlewares/parsers/schema.preprocessor.ts b/src/middlewares/parsers/schema.preprocessor.ts
index 8e14f4c..b6f756e 100644
--- a/src/middlewares/parsers/schema.preprocessor.ts
+++ b/src/middlewares/parsers/schema.preprocessor.ts
@@ -91,12 +91,12 @@ export const httpMethods = new Set([
 ]);
 export class SchemaPreprocessor {
   private ajv: Ajv;
-  private apiDoc: OpenAPIV3.Document;
-  private apiDocRes: OpenAPIV3.Document;
+  private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
+  private apiDocRes: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
   private serDesMap: SerDesMap;
   private responseOpts: ValidateResponseOpts;
   constructor(
-    apiDoc: OpenAPIV3.Document,
+    apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
     ajvOptions: Options,
     validateResponsesOpts: ValidateResponseOpts,
   ) {
@@ -108,22 +108,28 @@ export class SchemaPreprocessor {
 
   public preProcess() {
     const componentSchemas = this.gatherComponentSchemaNodes();
-    const r = this.gatherSchemaNodesFromPaths();
+    let r;
+
+    if (this.apiDoc.paths) {
+      r = this.gatherSchemaNodesFromPaths();
+    }
 
     // Now that we've processed paths, clone a response spec if we are validating responses
     this.apiDocRes = !!this.responseOpts ? cloneDeep(this.apiDoc) : null;
 
     const schemaNodes = {
       schemas: componentSchemas,
-      requestBodies: r.requestBodies,
-      responses: r.responses,
-      requestParameters: r.requestParameters,
+      requestBodies: r?.requestBodies,
+      responses: r?.responses,
+      requestParameters: r?.requestParameters,
     };
 
     // Traverse the schemas
-    this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
-      this.schemaVisitor(parent, schema, opts),
-    );
+    if (r) {
+      this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
+        this.schemaVisitor(parent, schema, opts),
+      );
+    }
 
     return {
       apiDoc: this.apiDoc,
@@ -151,6 +157,11 @@ export class SchemaPreprocessor {
 
     for (const [p, pi] of Object.entries(this.apiDoc.paths)) {
       const pathItem = this.resolveSchema<OpenAPIV3.PathItemObject>(pi);
+
+      // Since OpenAPI 3.1, paths can be a #ref to reusable path items
+      // The following line mutates the paths item to dereference the reference, so that we can process as a POJO, as we would if it wasn't a reference
+      this.apiDoc.paths[p] = pathItem;
+      
       for (const method of Object.keys(pathItem)) {
         if (httpMethods.has(method)) {
           const operation = <OpenAPIV3.OperationObject>pathItem[method];
@@ -535,7 +546,7 @@ export class SchemaPreprocessor {
     const op = node.schema;
     const responses = op.responses;
 
-    if (!responses) return;
+    if (!responses) return [];
 
     const schemas: Root<SchemaObject>[] = [];
     for (const [statusCode, response] of Object.entries(responses)) {
diff --git a/src/middlewares/parsers/util.ts b/src/middlewares/parsers/util.ts
index aa0642e..0c2119e 100644
--- a/src/middlewares/parsers/util.ts
+++ b/src/middlewares/parsers/util.ts
@@ -4,7 +4,7 @@ import ajv = require('ajv');
 import { OpenAPIFramework } from '../../framework';
 
 export function dereferenceParameter(
-  apiDocs: OpenAPIV3.Document,
+  apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   parameter: OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject,
 ): OpenAPIV3.ParameterObject {
   // TODO this should recurse or use ajv.getSchema - if implemented as such, may want to cache the result
diff --git a/src/openapi.validator.ts b/src/openapi.validator.ts
index 3265c18..047c9a9 100644
--- a/src/openapi.validator.ts
+++ b/src/openapi.validator.ts
@@ -37,7 +37,7 @@ export {
 
 interface MiddlewareContext {
   context: OpenApiContext,
-  responseApiDoc: OpenAPIV3.Document,
+  responseApiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   error: any,
 }
 
@@ -264,26 +264,26 @@ export class OpenApiValidator {
 
   private metadataMiddleware(
     context: OpenApiContext,
-    responseApiDoc: OpenAPIV3.Document,
+    responseApiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
   ) {
     return middlewares.applyOpenApiMetadata(context, responseApiDoc);
   }
 
-  private multipartMiddleware(apiDoc: OpenAPIV3.Document) {
+  private multipartMiddleware(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
     return middlewares.multipart(apiDoc, {
       multerOpts: this.options.fileUploader,
       ajvOpts: this.ajvOpts.multipart,
     });
   }
 
-  private securityMiddleware(apiDoc: OpenAPIV3.Document) {
+  private securityMiddleware(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
     const securityHandlers = (<ValidateSecurityOpts>(
       this.options.validateSecurity
     ))?.handlers;
     return middlewares.security(apiDoc, securityHandlers);
   }
 
-  private requestValidationMiddleware(apiDoc: OpenAPIV3.Document) {
+  private requestValidationMiddleware(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
     const requestValidator = new middlewares.RequestValidator(
       apiDoc,
       this.ajvOpts.request,
@@ -291,7 +291,7 @@ export class OpenApiValidator {
     return (req, res, next) => requestValidator.validate(req, res, next);
   }
 
-  private responseValidationMiddleware(apiDoc: OpenAPIV3.Document, serial: number) {
+  private responseValidationMiddleware(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1, serial: number) {
     return new middlewares.ResponseValidator(
       apiDoc,
       this.ajvOpts.response,
diff --git a/src/resolvers.ts b/src/resolvers.ts
index 3092e3a..18aba62 100644
--- a/src/resolvers.ts
+++ b/src/resolvers.ts
@@ -7,7 +7,7 @@ const cache = {};
 export function defaultResolver(
   handlersPath: string,
   route: RouteMetadata,
-  apiDoc: OpenAPIV3.Document,
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
 ): RequestHandler {
   const tmpModules = {};
   const { basePath, expressRoute, openApiRoute, method } = route;
@@ -51,7 +51,7 @@ export function defaultResolver(
 export function modulePathResolver(
   handlersPath: string,
   route: RouteMetadata,
-  apiDoc: OpenAPIV3.Document,
+  apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
 ): RequestHandler {
   const pathKey = route.openApiRoute.substring(route.basePath.length);
   const schema = apiDoc.paths[pathKey][route.method.toLowerCase()];


Test patch:
diff --git a/test.txt b/test.txt
new file mode 100644
index 0000000..9c9a655
--- /dev/null
+++ b/test.txt
@@ -0,0 +1,1 @@
+My file conents
diff --git a/test/440.spec.ts b/test/440.spec.ts
index e105f07..5096024 100644
--- a/test/440.spec.ts
+++ b/test/440.spec.ts
@@ -9,7 +9,7 @@ describe(packageJson.name, () => {
 
   before(async () => {
     // Set up the express app
-    const apiSpec: OpenAPIV3.Document = {
+    const apiSpec: OpenAPIV3.DocumentV3 = {
       openapi: '3.0.0',
       info: { title: 'Api test', version: '1.0.0' },
       servers: [{ url: '/api' }],
diff --git a/test/478.spec.ts b/test/478.spec.ts
index 5825bc2..232ae65 100644
--- a/test/478.spec.ts
+++ b/test/478.spec.ts
@@ -53,7 +53,7 @@ describe('issue #478', () => {
       .expect(200));
 });
 
-function apiSpec(): OpenAPIV3.Document {
+function apiSpec(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.3',
     info: {
diff --git a/test/535.spec.ts b/test/535.spec.ts
index c07beee..a7dd79b 100644
--- a/test/535.spec.ts
+++ b/test/535.spec.ts
@@ -19,7 +19,7 @@ describe('#535 - calling `middleware()` multiple times', () => {
 });
 
 async function createApp(
-  apiSpec: OpenAPIV3.Document,
+  apiSpec: OpenAPIV3.DocumentV3,
 ): Promise<express.Express & { server?: Server }> {
   const app = express();
 
@@ -39,7 +39,7 @@ async function createApp(
   return app;
 }
 
-function createApiSpec(): OpenAPIV3.Document {
+function createApiSpec(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.3',
     info: {
diff --git a/test/577.spec.ts b/test/577.spec.ts
index 4c9b26f..a142d45 100644
--- a/test/577.spec.ts
+++ b/test/577.spec.ts
@@ -21,7 +21,7 @@ describe('#577 - Exclude response validation that is not in api spec', () => {
 });
 
 async function createApp(
-  apiSpec: OpenAPIV3.Document,
+  apiSpec: OpenAPIV3.DocumentV3,
 ): Promise<express.Express & { server?: Server }> {
   const app = express();
 
@@ -51,7 +51,7 @@ async function createApp(
   return app;
 }
 
-function createApiSpec(): OpenAPIV3.Document {
+function createApiSpec(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.3',
     info: {
diff --git a/test/821.spec.ts b/test/821.spec.ts
index 42eda86..5b9bc75 100644
--- a/test/821.spec.ts
+++ b/test/821.spec.ts
@@ -30,7 +30,7 @@ describe('issue #821 - serialization inside addiotionalProperties', () => {
 });
 
 async function createApp(
-  apiSpec: OpenAPIV3.Document | string,
+  apiSpec: OpenAPIV3.DocumentV3 | string,
 ): Promise<express.Express & { server?: Server }> {
   const app = express();
 
diff --git a/test/allow.header.spec.ts b/test/allow.header.spec.ts
index 25620c8..0441591 100644
--- a/test/allow.header.spec.ts
+++ b/test/allow.header.spec.ts
@@ -50,7 +50,7 @@ async function createApp(): Promise<express.Express & { server?: Server }> {
   return app;
 }
 
-function createApiSpec(): OpenAPIV3.Document {
+function createApiSpec(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.3',
     info: {
diff --git a/test/default.export.fn.spec.ts b/test/default.export.fn.spec.ts
index d0cdaeb..e01a356 100644
--- a/test/default.export.fn.spec.ts
+++ b/test/default.export.fn.spec.ts
@@ -3,6 +3,7 @@ import * as OpenApiValidator from '../src';
 import { expect } from 'chai';
 import * as request from 'supertest';
 import * as path from 'path';
+import {OpenAPIV3} from "../src/framework/types";
 
 describe('default export resolver', () => {
   let server = null;
@@ -11,7 +12,7 @@ describe('default export resolver', () => {
   before(async () => {
     app.use(
       OpenApiValidator.middleware({
-        apiSpec: {
+        apiSpec: <OpenAPIV3.DocumentV3>{
           openapi: '3.0.0',
           info: { version: '1.0.0', title: 'test bug OpenApiValidator' },
           paths: {
diff --git a/test/invalid.apispec.spec.ts b/test/invalid.apispec.spec.ts
index 2b7b02e..0ded5b1 100644
--- a/test/invalid.apispec.spec.ts
+++ b/test/invalid.apispec.spec.ts
@@ -41,7 +41,7 @@ async function createApp(
   return app;
 }
 
-function createApiSpec(): OpenAPIV3.Document {
+function createApiSpec(): OpenAPIV3.DocumentV3 {
   return <any>{
     openapi: '3.0.3',
     info: {
diff --git a/test/no.components.spec.ts b/test/no.components.spec.ts
index 0373bd3..866f371 100644
--- a/test/no.components.spec.ts
+++ b/test/no.components.spec.ts
@@ -33,7 +33,7 @@ describe('no components', () => {
       }));
 });
 
-function apiDoc(): OpenAPIV3.Document {
+function apiDoc(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.1',
     info: {
diff --git a/test/openapi_3.1/README.md b/test/openapi_3.1/README.md
new file mode 100644
index 0000000..defce39
--- /dev/null
+++ b/test/openapi_3.1/README.md
@@ -0,0 +1,3 @@
+# Open API 3.1 tests
+
+This folder, and its subfolders, contain tests for OpenAPI specification 3.1
diff --git a/test/openapi_3.1/components.spec.ts b/test/openapi_3.1/components.spec.ts
new file mode 100644
index 0000000..958c81b
--- /dev/null
+++ b/test/openapi_3.1/components.spec.ts
@@ -0,0 +1,30 @@
+import * as request from 'supertest';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('components support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'components.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true },
+      3005,
+      undefined,
+      false,
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  it('should support an API that only has components defined, but provides no routes', () => {
+    // The component is not made available by the provider API, so the request will return 404
+    // This test ensures that the request flow happens normally without any interruptions due to being a component
+    return request(app)
+      .get(`${app.basePath}/components`)
+      .expect(404);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/components_path_items.spec.ts b/test/openapi_3.1/components_path_items.spec.ts
new file mode 100644
index 0000000..cbd282d
--- /dev/null
+++ b/test/openapi_3.1/components_path_items.spec.ts
@@ -0,0 +1,34 @@
+import * as request from 'supertest';
+import * as express from 'express';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('component path item support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'components_path_items.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true, validateResponses: true },
+      3005,
+      (app) => app.use(
+        express
+          .Router()
+          .get(`/v1/entity`, (req, res) =>
+            res.status(200).json({}),
+          ),
+      )
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  it('should support path item on components', async () => {
+    return request(app)
+      .get(`${app.basePath}/entity`)
+      .expect(200);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/info_summary.spec.ts b/test/openapi_3.1/info_summary.spec.ts
new file mode 100644
index 0000000..20e8c5d
--- /dev/null
+++ b/test/openapi_3.1/info_summary.spec.ts
@@ -0,0 +1,30 @@
+import * as request from 'supertest';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('summary support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'info_summary.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true },
+      3005,
+      undefined,
+      false,
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  it('should support an API that has an info with a summary defined', () => {
+    // The endpoint is not made available by the provider API, so the request will return 404
+    // This test ensures that the request flow happens normally without any interruptions
+    return request(app)
+      .get(`${app.basePath}/webhook`)
+      .expect(404);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/license_identifier.spec.ts b/test/openapi_3.1/license_identifier.spec.ts
new file mode 100644
index 0000000..7f615a9
--- /dev/null
+++ b/test/openapi_3.1/license_identifier.spec.ts
@@ -0,0 +1,30 @@
+import * as request from 'supertest';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('identifier support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'license_identifier.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true },
+      3005,
+      undefined,
+      false,
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  it('should support an API that has an info with a summary defined', () => {
+    // The endpoint is not made available by the provider API, so the request will return 404
+    // This test ensures that the request flow happens normally without any interruptions
+    return request(app)
+      .get(`${app.basePath}/webhook`)
+      .expect(404);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/non_defined_semantics_request_body.spec.ts b/test/openapi_3.1/non_defined_semantics_request_body.spec.ts
new file mode 100644
index 0000000..e843a58
--- /dev/null
+++ b/test/openapi_3.1/non_defined_semantics_request_body.spec.ts
@@ -0,0 +1,48 @@
+import * as request from 'supertest';
+import * as express from 'express';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('Request body in operations without well defined semantics - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'non_defined_semantics_request_body.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true, validateResponses: true },
+      3005,
+      (app) => app.use(
+        express
+          .Router()
+          .get(`/v1/entity`, (req, res) =>
+            res.status(200).json({
+              property: null
+            }),
+          ),
+      )
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  // In OpenAPI 3.0, methods that RFC7231 does not have explicitly defined semantics for request body (GET, HEAD, DELETE) do not allow request body
+  // In OpenAPI 3.1, request body is allowed for these methods. This test ensures that GET it is correctly handled
+  it('should validate a request body on GET', async () => {
+    return request(app)
+      .get(`${app.basePath}/entity`)
+      .set('Content-Type', 'application/json')
+      .send({request: 123})
+      .expect(400);
+  });
+
+  // Ensures that DELETE it is correctly handled
+  it('should validate a request body on DELETE', async () => {
+    return request(app)
+      .delete(`${app.basePath}/entity`)
+      .set('Content-Type', 'application/json')
+      .send({request: 123})
+      .expect(400);
+  });
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/path_no_response.spec.ts b/test/openapi_3.1/path_no_response.spec.ts
new file mode 100644
index 0000000..b40619e
--- /dev/null
+++ b/test/openapi_3.1/path_no_response.spec.ts
@@ -0,0 +1,36 @@
+import * as request from 'supertest';
+import * as express from 'express';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('operation object without response - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'path_no_response.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true, validateResponses: true },
+      3005,
+      (app) => app.use(
+        express
+          .Router()
+          .get(`/v1`, (req, res) =>
+            res.status(200).end(),
+          ),
+      )
+    );
+    app
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  // In OpenAPI 3.1 it's possible to have a path without a response defined
+  it('should support endpoint with defined operation object without response', () => {
+    return request(app)
+      .get(`${app.basePath}`)
+      .expect(200);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/components.yaml b/test/openapi_3.1/resources/components.yaml
new file mode 100644
index 0000000..b968ade
--- /dev/null
+++ b/test/openapi_3.1/resources/components.yaml
@@ -0,0 +1,6 @@
+# From https://github.com/OAI/OpenAPI-Specification/blob/6f386968654fd483720aba0177e618e87a5d612d/tests/v3.1/pass/minimal_comp.yaml
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+components: {}
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/components_path_items.yaml b/test/openapi_3.1/resources/components_path_items.yaml
new file mode 100644
index 0000000..030e675
--- /dev/null
+++ b/test/openapi_3.1/resources/components_path_items.yaml
@@ -0,0 +1,21 @@
+openapi: 3.1.0
+info:
+  title: Example specification
+  version: "1.0"
+servers:
+  - url: /v1
+components:
+  pathItems: 
+    entity:
+      get: 
+        description: 'test'
+        responses: 
+          200:
+            description: GETS my entity
+            content:
+              application/json:
+                schema:
+                    type: object
+paths:
+  /entity:
+    $ref: '#/components/pathItems/entity'
diff --git a/test/openapi_3.1/resources/info_summary.yaml b/test/openapi_3.1/resources/info_summary.yaml
new file mode 100644
index 0000000..27e120c
--- /dev/null
+++ b/test/openapi_3.1/resources/info_summary.yaml
@@ -0,0 +1,7 @@
+# From https://github.com/OAI/OpenAPI-Specification/blob/6f386968654fd483720aba0177e618e87a5d612d/tests/v3.1/pass/info_summary.yaml
+openapi: 3.1.0
+info:
+  title: API
+  summary: My lovely API
+  version: 1.0.0
+components: {}
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/license_identifier.yaml b/test/openapi_3.1/resources/license_identifier.yaml
new file mode 100644
index 0000000..85dd47a
--- /dev/null
+++ b/test/openapi_3.1/resources/license_identifier.yaml
@@ -0,0 +1,10 @@
+# From https://github.com/OAI/OpenAPI-Specification/blob/6f386968654fd483720aba0177e618e87a5d612d/tests/v3.1/pass/license_identifier.yaml
+openapi: 3.1.0
+info:
+  title: API
+  summary: My lovely API
+  version: 1.0.0
+  license:
+    name: Apache
+    identifier: Apache-2.0
+components: {}
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/non_defined_semantics_request_body.yaml b/test/openapi_3.1/resources/non_defined_semantics_request_body.yaml
new file mode 100644
index 0000000..5114a27
--- /dev/null
+++ b/test/openapi_3.1/resources/non_defined_semantics_request_body.yaml
@@ -0,0 +1,55 @@
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+servers:
+  - url: /v1
+components:
+  schemas:
+    EntityRequest:
+      type: object
+      properties:
+        request:
+          type: string
+paths:
+  /entity:
+    get:
+      description: GETS my entity
+      requestBody:
+        description: Request body for entity
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/EntityRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json: 
+              schema:
+                title: Entity
+                type: object
+                properties:
+                  property:
+                    type: ['string', 'null']
+    delete:
+      description: DELETE my entity
+      requestBody:
+        description: Request body for entity
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/EntityRequest'
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json: 
+              schema:
+                title: Entity
+                type: object
+                properties:
+                  property:
+                    type: ['string', 'null']
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/path_no_response.yaml b/test/openapi_3.1/resources/path_no_response.yaml
new file mode 100644
index 0000000..5426037
--- /dev/null
+++ b/test/openapi_3.1/resources/path_no_response.yaml
@@ -0,0 +1,11 @@
+# Adapted from https://github.com/OAI/OpenAPI-Specification/blob/77c7b9a522ab6fb83a49e8088fa600e93da4f44e/tests/v3.1/pass/path_no_response.yaml
+
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+servers:
+  - url: /v1
+paths:
+  /:
+    get: {}
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/server_variable_no_default.yaml b/test/openapi_3.1/resources/server_variable_no_default.yaml
new file mode 100644
index 0000000..8408bf4
--- /dev/null
+++ b/test/openapi_3.1/resources/server_variable_no_default.yaml
@@ -0,0 +1,13 @@
+# Adapted from https://github.com/OAI/OpenAPI-Specification/blob/77c7b9a522ab6fb83a49e8088fa600e93da4f44e/tests/v3.1/fail/server_enum_empty.yaml
+
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+servers:
+  - url: https://example.com/test
+    variables:
+      var:
+        enum: ['a', 'b']
+components:
+  {}
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/type_null.yaml b/test/openapi_3.1/resources/type_null.yaml
new file mode 100644
index 0000000..54d885f
--- /dev/null
+++ b/test/openapi_3.1/resources/type_null.yaml
@@ -0,0 +1,23 @@
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+servers:
+  - url: /v1
+paths:
+  /entity:
+    get:
+        summary: test
+        description: GETS my entity
+        responses:
+          '200':
+            description: OK
+            content:
+              application/json: 
+                schema:
+                  title: Entity
+                  type: object
+                  properties:
+                    property:
+                      type: ['string', 'null']
+              
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/unevaluated_properties.yaml b/test/openapi_3.1/resources/unevaluated_properties.yaml
new file mode 100644
index 0000000..cc40845
--- /dev/null
+++ b/test/openapi_3.1/resources/unevaluated_properties.yaml
@@ -0,0 +1,28 @@
+openapi: 3.1.0
+info:
+  title: API
+  version: 1.0.0
+servers:
+  - url: /v1
+components:
+  schemas:
+    EntityRequest:
+      type: object
+      properties:
+        request:
+          type: string
+      unevaluatedProperties: false
+paths:
+  /entity:
+    post:
+      description: POSTs my entity
+      requestBody:
+        description: Request body for entity
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/EntityRequest'
+      responses:
+        '204':
+          description: No Content
\ No newline at end of file
diff --git a/test/openapi_3.1/resources/webhook.yaml b/test/openapi_3.1/resources/webhook.yaml
new file mode 100644
index 0000000..27bbd8e
--- /dev/null
+++ b/test/openapi_3.1/resources/webhook.yaml
@@ -0,0 +1,35 @@
+# From https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.1/webhook-example.yaml
+openapi: 3.1.0
+info:
+  title: Webhook Example
+  version: 1.0.0
+# Since OAS 3.1.0 the paths element isn't necessary. Now a valid OpenAPI Document can describe only paths, webhooks, or even only reusable components
+webhooks:
+  # Each webhook needs a name
+  newPet:
+    # This is a Path Item Object, the only difference is that the request is initiated by the API provider
+    post:
+      requestBody:
+        description: Information about a new pet in the system
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/Pet"
+      responses:
+        "200":
+          description: Return a 200 status to indicate that the data was received successfully
+
+components:
+  schemas:
+    Pet:
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
\ No newline at end of file
diff --git a/test/openapi_3.1/server_variable.spec.ts b/test/openapi_3.1/server_variable.spec.ts
new file mode 100644
index 0000000..d790b9c
--- /dev/null
+++ b/test/openapi_3.1/server_variable.spec.ts
@@ -0,0 +1,21 @@
+import * as request from 'supertest';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('server variable - OpenAPI 3.1', () => {
+  it('returns 500 when server variable has no default property', async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'server_variable_no_default.yaml');
+    const app = await createApp(
+      { apiSpec, validateRequests: true, validateResponses: true },
+      3005,
+      undefined,
+      false,
+    ) as any;
+
+    await request(app)
+      .get(`${app.basePath}`)
+      .expect(500);
+
+    app.server.close();
+  });
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/type_null.spec.ts b/test/openapi_3.1/type_null.spec.ts
new file mode 100644
index 0000000..452626c
--- /dev/null
+++ b/test/openapi_3.1/type_null.spec.ts
@@ -0,0 +1,37 @@
+import * as request from 'supertest';
+import * as express from 'express';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('type null support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'type_null.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true, validateResponses: true },
+      3005,
+      (app) => app.use(
+        express
+          .Router()
+          .get(`/v1/entity`, (req, res) =>
+            res.status(200).json({
+              property: null
+            }),
+          ),
+      )
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  // In OpenAPI 3.1, nullable = true was replaced by types = [..., null]. This test ensure that it works with Express OpenAPI Validator
+  it('should support an API with types set to null', async () => {
+    return request(app)
+      .get(`${app.basePath}/entity`)
+      .expect(200);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/unevaluated_properties.spec.ts b/test/openapi_3.1/unevaluated_properties.spec.ts
new file mode 100644
index 0000000..edb8453
--- /dev/null
+++ b/test/openapi_3.1/unevaluated_properties.spec.ts
@@ -0,0 +1,45 @@
+import * as request from 'supertest';
+import * as express from 'express';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('Unevaluated Properties in requests', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'unevaluated_properties.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true },
+      3005,
+      (app) => app.use(
+        express
+          .Router()
+          .post(`/v1/entity`, (_req, res) =>
+            res.status(204).json(),
+          ),
+      )
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+
+  it('should reject request body with unevaluated properties', async () => {
+    return request(app)
+      .post(`${app.basePath}/entity`)
+      .set('Content-Type', 'application/json')
+      .send({request: '123', additionalProperty: '321'})
+      .expect(400);
+  });
+
+  it('should accept request body without unevaluated properties', async () => {
+    return request(app)
+      .post(`${app.basePath}/entity`)
+      .set('Content-Type', 'application/json')
+      .send({request: '123' })
+      .expect(204);
+  });
+
+})
\ No newline at end of file
diff --git a/test/openapi_3.1/webhook.spec.ts b/test/openapi_3.1/webhook.spec.ts
new file mode 100644
index 0000000..58e81b1
--- /dev/null
+++ b/test/openapi_3.1/webhook.spec.ts
@@ -0,0 +1,30 @@
+import * as request from 'supertest';
+import { createApp } from "../common/app";
+import { join } from "path";
+
+describe('webhook support - OpenAPI 3.1', () => {
+  let app;
+
+  before(async () => {
+    const apiSpec = join('test', 'openapi_3.1', 'resources', 'webhook.yaml');
+    app = await createApp(
+      { apiSpec, validateRequests: true },
+      3005,
+      undefined,
+      false,
+    );
+  });
+
+  after(() => {
+    app.server.close();
+  });
+
+  it('should support an API that only has webhooks defined, but provides no routes', () => {
+    // The webhook is not made available by the provider API, so the request will return 404
+    // This test ensures that the request flow happens normally without any interruptions due to being a webhook
+    return request(app)
+      .get(`${app.basePath}/webhook`)
+      .expect(404);
+  });
+
+})
\ No newline at end of file
diff --git a/test/petstore.spec.ts b/test/petstore.spec.ts
index 1edf43e..8588f5b 100644
--- a/test/petstore.spec.ts
+++ b/test/petstore.spec.ts
@@ -36,7 +36,7 @@ describe('petstore', () => {
     request(app).get(`${app.basePath}/pets`).expect(200));
 });
 
-function petstoreSpec(): OpenAPIV3.Document {
+function petstoreSpec(): OpenAPIV3.DocumentV3 {
   return {
     openapi: '3.0.0',
     info: {
diff --git a/test/user-request-url.router.spec.ts b/test/user-request-url.router.spec.ts
index 4bba5de..d4470ba 100644
--- a/test/user-request-url.router.spec.ts
+++ b/test/user-request-url.router.spec.ts
@@ -112,7 +112,7 @@ function defaultResponse(): OpenAPIV3.ResponseObject {
    type of id in path and id in the response here defined as simple string
    with minLength
  */
-const gatewaySpec: OpenAPIV3.Document = {
+const gatewaySpec: OpenAPIV3.DocumentV3 = {
   openapi: '3.0.0',
   info: { version: '1.0.0', title: 'test bug OpenApiValidator' },
   servers: [{ url: 'http://localhost:3000/api' }],
@@ -168,7 +168,7 @@ const gatewaySpec: OpenAPIV3.Document = {
  represents spec of the child router. We route request from main app (gateway) to this router.
  This router has its own schema, routes and validation formats. In particular, we force id in the path and id in the response to be uuid.
  */
-const childRouterSpec: OpenAPIV3.Document = {
+const childRouterSpec: OpenAPIV3.DocumentV3 = {
   openapi: '3.0.0',
   info: { version: '1.0.0', title: 'test bug OpenApiValidator' },
   servers: [{ url: 'http://localhost:3000/' }],