diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..60404ce83 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +*.ts linguist-language=JavaScript \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..f433b7e28 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: typicode diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 000000000..6204e5d05 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,31 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node.js CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: npm test diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 000000000..10707c7e3 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,38 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm ci + - run: npm test + + publish-npm: + needs: build + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.gitignore b/.gitignore index 08af561d5..e0b3ff284 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ **/*.log -node_modules -db.json -tmp .DS_Store .idea -generate.js +lib +node_modules +public/output.css +tmp diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..72c4429bc --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm test diff --git a/.prettier.config.js b/.prettier.config.js new file mode 100644 index 000000000..45713a76e --- /dev/null +++ b/.prettier.config.js @@ -0,0 +1,5 @@ +export default { + semi: false, + singleQuote: true, + trailingComma: 'all', +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 796d98f9e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: node_js -node_js: - - "0.12" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1a3959db2..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,60 +0,0 @@ -# Change Log - -## [0.8.0][2015-09-21] - -### Changed - -* `jsonServer.defaults` is now a function and can take an object. -If you're using the project as a module, you need to update your code: - -```js -// Before -jsonServer.defaults -// After -jsonServer.defaults() -jsonServer.defaults({ static: '/some/path'}) -``` - -* Automatically ignore unknown query parameters. - -```bash -# Before -GET /posts?author=typicode&foo=bar # [] -# After -GET /posts?author=typicode&foo=bar # [{...}, {...}] -``` - -### Added - -* CLI option for setting a custom static files directory. - -```bash -json-server --static some/path -``` - -## [0.7.28][2015-09-09] - -```bash -# Support range -GET /products?price_gte=50&price_lte=100 -``` - -## [0.7.27][2015-09-02] - -### Added - -```bash -# Support OR -GET /posts?id=1&id2 -GET /posts?category=javascript&category=html -``` - -## [0.7.26][2015-09-01] - -### Added - -```bash -# Support embed and expand in lists -GET /posts?embed=comments -GET /posts?expand=user -``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..bbb830ad5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,21 @@ +## Agreement + +Thanks for your interest in contributing! + +By contributing to this project, you agree to the following: + +1. **Relicensing:** to support the project's sustainability and ensure it's longevity, your contributions can be relicensed to any license. + +2. **Ownership Rights:** You confirm you own the rights to your contributed code. + +3. **Disagreement:** If you disagree with these terms, please create an issue instead. I'll handle the bug or feature request. + +4. **Benefits for Contributors:** If your contribution is merged, you'll enjoy the same benefits as a sponsor for one year. This includes using the project in a company context, free of charge. + +## Fair Source License + +This project uses the Fair Source License, which is neither purely open-source nor closed-source. It allows visibility of the source code and free usage for a limited number of two users within an organization. Beyond this limit (three or more users), a small licensing fee via [GitHub Sponsors](https://github.com/sponsors/typicode) applies. + +This doesn't apply to individuals, students, teachers, small teams, ... + +Got questions or need support? Feel free to reach out to typicode@gmail.com. diff --git a/LICENSE b/LICENSE index 3a2b74060..752515b57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,44 @@ -The MIT License (MIT) - -Copyright (c) 2014 typicode - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Fair Source License, version 0.9 + +Copyright (C) 2023-present typicode + +Licensor: typicode + +Software: json-server + +Use Limitation: 2 users + +License Grant. Licensor hereby grants to each recipient of the +Software ("you") a non-exclusive, non-transferable, royalty-free and +fully-paid-up license, under all of the Licensor's copyright and +patent rights, to use, copy, distribute, prepare derivative works of, +publicly perform and display the Software, subject to the Use +Limitation and the conditions set forth below. + +Use Limitation. The license granted above allows use by up to the +number of users per entity set forth above (the "Use Limitation"). For +determining the number of users, "you" includes all affiliates, +meaning legal entities controlling, controlled by, or under common +control with you. If you exceed the Use Limitation, your use is +subject to payment of Licensor's then-current list price for licenses. + +Conditions. Redistribution in source code or other forms must include +a copy of this license document to be provided in a reasonable +manner. Any redistribution of the Software is only allowed subject to +this license. + +Trademarks. This license does not grant you any right in the +trademarks, service marks, brand names or logos of Licensor. + +DISCLAIMER. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OR +CONDITION, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. LICENSORS HEREBY DISCLAIM ALL LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE. + +Termination. If you violate the terms of this license, your rights +will terminate automatically and will not be reinstated without the +prior written consent of Licensor. Any such termination will not +affect the right of others who may have received copies of the +Software from you. diff --git a/README.md b/README.md index c3a2d0639..f98d1e9b7 100644 --- a/README.md +++ b/README.md @@ -1,307 +1,215 @@ -# JSON Server [![](https://travis-ci.org/typicode/json-server.svg)](https://travis-ci.org/typicode/json-server) [![](https://badge.fury.io/js/json-server.svg)](http://badge.fury.io/js/json-server) [![](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/typicode/json-server?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# JSON-Server -Get a full fake REST API with __zero coding__ in __less than 30 seconds__ (seriously) +[![Node.js CI](https://github.com/typicode/json-server/actions/workflows/node.js.yml/badge.svg)](https://github.com/typicode/json-server/actions/workflows/node.js.yml) -Created with <3 for front-end developers who need a quick back-end for prototyping and mocking. +> [!IMPORTANT] +> Viewing beta v1 documentation – usable but expect breaking changes. For stable version, see [here](https://github.com/typicode/json-server/tree/v0) - * [Egghead.io free video tutorial - Creating demo APIs with json-server](https://egghead.io/lessons/nodejs-creating-demo-apis-with-json-server) - * [JSONPlaceholder - Live running version](http://jsonplaceholder.typicode.com) +> [!NOTE] +> Using React ⚛️ ? Check my new project [MistCSS](https://github.com/typicode/mistcss) to write type-safe styles (works with TailwindCSS) -_See also [hotel](https://github.com/typicode/hotel) :hotel:, a process manager for web developers._ +## Install + +```shell +npm install json-server +``` -## Example +## Usage -Create a `db.json` file +Create a `db.json` or `db.json5` file ```json { "posts": [ - { "id": 1, "title": "json-server", "author": "typicode" } + { "id": "1", "title": "a title", "views": 100 }, + { "id": "2", "title": "another title", "views": 200 } ], "comments": [ - { "id": 1, "body": "some comment", "postId": 1 } + { "id": "1", "text": "a comment about post 1", "postId": "1" }, + { "id": "2", "text": "another comment about post 1", "postId": "1" } ], - "profile": { "name": "typicode" } + "profile": { + "name": "typicode" + } } ``` -Start JSON Server - -```bash -$ json-server --watch db.json -``` - -Now if you go to [http://localhost:3000/posts/1](), you'll get - -```json -{ "id": 1, "title": "json-server", "author": "typicode" } -``` - -Also, if you make POST, PUT, PATCH or DELETE requests, changes will be automatically and safely saved to `db.json` using [lowdb](https://github.com/typicode/lowdb). +
-## Install +View db.json5 example -```bash -$ npm install -g json-server +```json5 +{ + posts: [ + { id: '1', title: 'a title', views: 100 }, + { id: '2', title: 'another title', views: 200 }, + ], + comments: [ + { id: '1', text: 'a comment about post 1', postId: '1' }, + { id: '2', text: 'another comment about post 1', postId: '1' }, + ], + profile: { + name: 'typicode', + }, +} ``` -## Routes +You can read more about JSON5 format [here](https://github.com/json5/json5). -Based on the previous `db.json` file, here are all the default routes. You can also add [other routes](#add-routes) using `--routes`. +
-### Plural routes +Pass it to JSON Server CLI -``` -GET /posts -GET /posts/1 -POST /posts -PUT /posts/1 -PATCH /posts/1 -DELETE /posts/1 +```shell +$ npx json-server db.json ``` -### Singular routes +Get a REST API -``` -GET /profile -POST /profile -PUT /profile -PATCH /profile +```shell +$ curl http://localhost:3000/posts/1 +{ + "id": "1", + "title": "a title", + "views": 100 +} ``` -### Filter +Run `json-server --help` for a list of options -Use `.` to access deep properties +## Sponsors ✨ -``` -GET /posts?title=json-server&author=typicode -GET /posts?id=1&id=2 -GET /comments?author.name=typicode -``` +### Gold -### Slice +|| +| :---: | +| | +| | +| | -Add `_start` and `_end` or `_limit` (an `X-Total-Count` header is included in the response) +### Silver -``` -GET /posts?_start=20&_end=30 -GET /posts/1/comments?_start=20&_end=30 -GET /posts/1/comments?_start=20&_limit=10 -``` +|| +| :---: | +| | -### Sort +### Bronze -Add `_sort` and `_order` (ascending order by default) +||| +| :---: | :---: | +| | | -``` -GET /posts?_sort=views&_order=DESC -GET /posts/1/comments?_sort=votes&_order=ASC -``` - -### Range +[Become a sponsor and have your company logo here](https://github.com/users/typicode/sponsorship) -Add `_gte` or `_lte` +## Sponsorware -``` -GET /posts?views_gte=10&views_lte=20 -``` +> [!NOTE] +> This project uses the [Fair Source License](https://fair.io/). Only organizations with 3+ users are kindly asked to contribute a small amount through sponsorship [sponsor](https://github.com/sponsors/typicode) for usage. __This license helps keep the project sustainable and healthy, benefiting everyone.__ +> +> For more information, FAQs, and the rationale behind this, visit [https://fair.io/](https://fair.io/). -### Full-text search +## Routes -Add `q` +Based on the example `db.json`, you'll get the following routes: ``` -GET /posts?q=internet -``` - -### Relationships - -To include children resources, add `_embed` +GET /posts +GET /posts/:id +POST /posts +PUT /posts/:id +PATCH /posts/:id +DELETE /posts/:id +# Same for comments ``` -GET /posts?_embed=comments -GET /posts/1?_embed=comments -``` - -To include parent resource, add `_expand` ``` -GET /comments?_expand=post -GET /comments/1?_expand=post +GET /profile +PUT /profile +PATCH /profile ``` -To get nested resources (by default one level, [add routes](#add-routes) for more) +## Params -``` -GET /posts/1/comments -``` +### Conditions -### Database +- ` ` → `==` +- `lt` → `<` +- `lte` → `<=` +- `gt` → `>` +- `gte` → `>=` +- `ne` → `!=` ``` -GET /db +GET /posts?views_gt=9000 ``` -### Homepage +### Range -Returns default index file or serves `./public` directory +- `start` +- `end` +- `limit` ``` -GET / +GET /posts?_start=10&_end=20 +GET /posts?_start=10&_limit=10 ``` -## Extras +### Paginate -### Static file server - -You can use JSON Server to serve your HTML, JS and CSS, simply create a `./public` directory -or use `--static`. - -```bash -mkdir public -echo 'hello word' > public/index.html -json-server db.json -``` +- `page` +- `per_page` (default = 10) -```bash -json-server db.json --static ./static ``` - -### Access from anywhere - -You can access your fake API from anywhere using CORS and JSONP. - -### Remote schema - -You can load remote schemas. - -```bash -$ json-server http://example.com/file.json -$ json-server http://jsonplaceholder.typicode.com/db +GET /posts?_page=1&_per_page=25 ``` -### Generate random data +### Sort -Using JS instead of a JSON file, you can create data programmatically. +- `_sort=f1,f2` -```javascript -// index.js -module.exports = function() { - var data = { users: [] } - // Create 1000 users - for (var i = 0; i < 1000; i++) { - data.users.push({ id: i, name: 'user' + i }) - } - return data -} ``` - -```bash -$ json-server index.js +GET /posts?_sort=id,-views ``` -__Tip__ use modules like [faker](https://github.com/Marak/faker.js), [casual](https://github.com/boo1ean/casual) or [chance](https://github.com/victorquinn/chancejs). - -### Add routes +### Nested and array fields -Create a `routes.json` file. +- `x.y.z...` +- `x.y.z[i]...` -```json -{ - "/api/": "/", - "/blog/:resource/:id/show": "/:resource/:id" -} ``` - -Start JSON Server with `--routes` option. - -```bash -json-server db.json --routes routes.json +GET /foo?a.b=bar +GET /foo?x.y_lt=100 +GET /foo?arr[0]=bar ``` -Now you can access resources using additional routes. +### Embed -```bash -/api/posts -/api/posts/1 -/blog/posts/1/show ``` - -### Module - -If you need to add authentication, validation, you can use the project as a module in combination with other Express middlewares. - -```javascript -var jsonServer = require('json-server') - -// Returns an Express server -var server = jsonServer.create() - -// Set default middlewares (logger, static, cors and no-cache) -server.use(jsonServer.defaults()) - -// Returns an Express router -var router = jsonServer.router('db.json') -server.use(router) - -server.listen(3000) -``` - -For an in-memory database, you can pass an object to `jsonServer.router()`. -Please note also that `jsonServer.router()` can be used in existing Express projects. - -To modify responses, use `router.render()`: - -```javascript -// In this example, returned resources will be wrapped in a body property -router.render = function (req, res) { - res.jsonp({ - body: res.locals.data - }) -} +GET /posts?_embed=comments +GET /comments?_embed=post ``` -To add rewrite rules, use `jsonServer.rewriter()`: +## Delete -```javascript -// Add this before server.use(router) -server.use(jsonServer.rewriter({ - '/api/': '/', - '/blog/:resource/:id/show': '/:resource/:id' -})) ``` - -Alternatively, you can also mount the router on another path. - -```javascript -server.use('/api', router) +DELETE /posts/1 +DELETE /posts/1?_dependent=comments ``` -### Deployment - -You can deploy JSON Server. For example, [JSONPlaceholder](http://jsonplaceholder.typicode.com) is an online fake API powered by JSON Server and running on Heroku. +## Serving static files -## Links +If you create a `./public` directory, JSON Server will serve its content in addition to the REST API. -### Video +You can also add custom directories using `-s/--static` option. -* [Creating Demo APIs with json-server on egghead.io](https://egghead.io/lessons/nodejs-creating-demo-apis-with-json-server) - -### Articles - -* [Node Module Of The Week - json-server](http://nmotw.in/json-server/) -* [Mock up your REST API with JSON Server](http://www.betterpixels.co.uk/projects/2015/05/09/mock-up-your-rest-api-with-json-server/) -* [how to build quick json REST APIs for development](http://outloudthinking.me/how-to-build-quick-json-rest-apis/) -* [ng-admin: Add an AngularJS admin GUI to any RESTful API](http://marmelab.com/blog/2014/09/15/easy-backend-for-your-restful-api.html) -* [Fast prototyping using Restangular and Json-server](http://glebbahmutov.com/blog/fast-prototyping-using-restangular-and-json-server/) - -### Third-party tools - -* [Grunt JSON Server](https://github.com/tfiwm/grunt-json-server) -* [Docker JSON Server](https://github.com/clue/docker-json-server) -* [JSON Server GUI](https://github.com/naholyr/json-server-gui) -* [JSON file generator](https://github.com/dfsq/json-server-init) +```sh +json-server -s ./static +json-server -s ./static -s ./node_modules +``` -## License +## Notable differences with v0.17 -MIT - [Typicode](https://github.com/typicode) +- `id` is always a string and will be generated for you if missing +- use `_per_page` with `_page` instead of `_limit`for pagination +- use Chrome's `Network tab > throtling` to delay requests instead of `--delay` CLI option diff --git a/bin/index.js b/bin/index.js deleted file mode 100755 index 633765557..000000000 --- a/bin/index.js +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -require('../src/cli')() diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..e8b7f785c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,11 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; + + +export default [ + {files: ["**/*.{js,mjs,cjs,ts}"]}, + {languageOptions: { globals: globals.node }}, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, +]; \ No newline at end of file diff --git a/fixtures/db.json b/fixtures/db.json new file mode 100644 index 000000000..4e736d112 --- /dev/null +++ b/fixtures/db.json @@ -0,0 +1,13 @@ +{ + "posts": [ + { "id": "1", "title": "a title" }, + { "id": "2", "title": "another title" } + ], + "comments": [ + { "id": "1", "text": "a comment about post 1", "postId": "1" }, + { "id": "2", "text": "another comment about post 1", "postId": "1" } + ], + "profile": { + "name": "typicode" + } +} \ No newline at end of file diff --git a/fixtures/db.json5 b/fixtures/db.json5 new file mode 100644 index 000000000..3b06138e7 --- /dev/null +++ b/fixtures/db.json5 @@ -0,0 +1,27 @@ +{ + posts: [ + { + id: '1', + title: 'a title', + }, + { + id: '2', + title: 'another title', + }, + ], + comments: [ + { + id: '1', + text: 'a comment about post 1', + postId: '1', + }, + { + id: '2', + text: 'another comment about post 1', + postId: '1', + }, + ], + profile: { + name: 'typicode', + }, +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..60d0c463e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3908 @@ +{ + "name": "json-server", + "version": "1.0.0-beta.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "json-server", + "version": "1.0.0-beta.3", + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "@tinyhttp/app": "^2.4.0", + "@tinyhttp/cors": "^2.0.1", + "@tinyhttp/logger": "^2.0.0", + "chalk": "^5.3.0", + "chokidar": "^4.0.1", + "dot-prop": "^9.0.0", + "eta": "^3.5.0", + "inflection": "^3.0.0", + "json5": "^2.2.3", + "lowdb": "^7.0.1", + "milliparsec": "^4.0.0", + "sirv": "^2.0.4", + "sort-on": "^6.1.0" + }, + "bin": { + "json-server": "lib/bin.js" + }, + "devDependencies": { + "@eslint/js": "^9.11.0", + "@sindresorhus/tsconfig": "^6.0.0", + "@tailwindcss/typography": "^0.5.15", + "@types/node": "^22.5.5", + "concurrently": "^9.0.1", + "eslint": "^9.11.0", + "get-port": "^7.1.0", + "globals": "^15.9.0", + "husky": "^9.1.6", + "tempy": "^3.1.0", + "tsx": "^4.19.1", + "type-fest": "^4.26.1", + "typescript": "^5.6.2", + "typescript-eslint": "^8.6.0" + }, + "engines": { + "node": ">=18.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "license": "MIT", + "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, + "license": "Apache-2.0", + "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/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, + "license": "MIT", + "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/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.0.tgz", + "integrity": "sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==", + "dev": true, + "license": "MIT", + "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, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "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/@polka/url": { + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + }, + "node_modules/@sindresorhus/tsconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/tsconfig/-/tsconfig-6.0.0.tgz", + "integrity": "sha512-+fUdfuDd/7O2OZ9/UvJy76IEWn2Tpvm2l+rwUoS2Yz4jCUTSNOQQv2PLWrwekt8cPLwHmpHaBpay34bkBmVl2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz", + "integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tinyhttp/accepts": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/accepts/-/accepts-2.2.3.tgz", + "integrity": "sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==", + "license": "MIT", + "dependencies": { + "mime": "4.0.4", + "negotiator": "^0.6.3" + }, + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/app": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tinyhttp/app/-/app-2.4.0.tgz", + "integrity": "sha512-vOPiCemQRJq5twnl06dde6XnWiNbVMdVRFJWW/yC/9G0qgvV2TvzNNTxrdlz6YmyB7vIC7Fg3qS6m6gx8RbBNQ==", + "license": "MIT", + "dependencies": { + "@tinyhttp/cookie": "2.1.1", + "@tinyhttp/proxy-addr": "2.2.0", + "@tinyhttp/req": "2.2.4", + "@tinyhttp/res": "2.2.4", + "@tinyhttp/router": "2.2.3", + "header-range-parser": "1.1.3", + "regexparam": "^2.0.2" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/content-disposition": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.2.tgz", + "integrity": "sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/content-type": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/content-type/-/content-type-0.1.4.tgz", + "integrity": "sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==", + "license": "MIT", + "engines": { + "node": ">=12.4" + } + }, + "node_modules/@tinyhttp/cookie": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cookie/-/cookie-2.1.1.tgz", + "integrity": "sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/tinyhttp/tinyhttp?sponsor=1" + } + }, + "node_modules/@tinyhttp/cookie-signature": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cookie-signature/-/cookie-signature-2.1.1.tgz", + "integrity": "sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/cors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/cors/-/cors-2.0.1.tgz", + "integrity": "sha512-qrmo6WJuaiCzKWagv2yA/kw6hIISfF/hOqPWwmI6w0o8apeTMmRN3DoCFvQ/wNVuWVdU5J4KU7OX8aaSOEq51A==", + "license": "MIT", + "dependencies": { + "@tinyhttp/vary": "^0.1.3" + }, + "engines": { + "node": ">=12.20 || 14.x || >=16" + } + }, + "node_modules/@tinyhttp/encode-url": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/encode-url/-/encode-url-2.1.1.tgz", + "integrity": "sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/etag": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@tinyhttp/etag/-/etag-2.1.2.tgz", + "integrity": "sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/forwarded": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/forwarded/-/forwarded-2.1.1.tgz", + "integrity": "sha512-nO3kq0R1LRl2+CAMlnggm22zE6sT8gfvGbNvSitV6F9eaUSurHP0A8YZFMihSkugHxK+uIegh1TKrqgD8+lyGQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tinyhttp/logger/-/logger-2.0.0.tgz", + "integrity": "sha512-8DfLQjGDIaIJeivYamVrrpmwmsGwS8wt2DGvzlcY5HEBagdiI4QJy/veAFcUHuaJqufn4wLwmn4q5VUkW8BCpQ==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.20", + "dayjs": "^1.11.10", + "http-status-emojis": "^2.2.0" + }, + "engines": { + "node": ">=14.18 || >=16.20" + } + }, + "node_modules/@tinyhttp/proxy-addr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@tinyhttp/proxy-addr/-/proxy-addr-2.2.0.tgz", + "integrity": "sha512-WM/PPL9xNvrs7/8Om5nhKbke5FHrP3EfjOOR+wBnjgESfibqn0K7wdUTnzSLp1lBmemr88os1XvzwymSgaibyA==", + "license": "MIT", + "dependencies": { + "@tinyhttp/forwarded": "2.1.1", + "ipaddr.js": "^2.2.0" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/req": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/req/-/req-2.2.4.tgz", + "integrity": "sha512-lQAZIAo0NOeghxFOZS57tQzxpHSPPLs9T68Krq2BncEBImKwqaDKUt7M9Y5Kb+rvC/GwIL3LeErhkg7f5iG4IQ==", + "license": "MIT", + "dependencies": { + "@tinyhttp/accepts": "2.2.3", + "@tinyhttp/type-is": "2.2.4", + "@tinyhttp/url": "2.1.1", + "header-range-parser": "^1.1.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/res": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/res/-/res-2.2.4.tgz", + "integrity": "sha512-ETBRShnO19oJyIg2XQHQoofXPWeTXPAuwnIVYkU8WaftvXd/Vz4y5+WFQDHUzKlmdGOw5fAFnrEU7pIVMeFeVA==", + "license": "MIT", + "dependencies": { + "@tinyhttp/content-disposition": "2.2.2", + "@tinyhttp/cookie": "2.1.1", + "@tinyhttp/cookie-signature": "2.1.1", + "@tinyhttp/encode-url": "2.1.1", + "@tinyhttp/req": "2.2.4", + "@tinyhttp/send": "2.2.3", + "@tinyhttp/vary": "^0.1.3", + "es-escape-html": "^0.1.1", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/router": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/router/-/router-2.2.3.tgz", + "integrity": "sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/send": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/send/-/send-2.2.3.tgz", + "integrity": "sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==", + "license": "MIT", + "dependencies": { + "@tinyhttp/content-type": "^0.1.4", + "@tinyhttp/etag": "2.1.2", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/type-is": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tinyhttp/type-is/-/type-is-2.2.4.tgz", + "integrity": "sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==", + "license": "MIT", + "dependencies": { + "@tinyhttp/content-type": "^0.1.4", + "mime": "4.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/url": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tinyhttp/url/-/url-2.1.1.tgz", + "integrity": "sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@tinyhttp/vary": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tinyhttp/vary/-/vary-0.1.3.tgz", + "integrity": "sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.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.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.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/scope-manager": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.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.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.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/types": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", + "dev": true, + "license": "MIT", + "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.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.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.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.6.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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "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, + "license": "MIT", + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "peer": 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, + "license": "ISC", + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concurrently": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/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/concurrently/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/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "peer": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "peer": true + }, + "node_modules/dot-prop": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", + "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "dependencies": { + "type-fest": "^4.18.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/es-escape-html": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/es-escape-html/-/es-escape-html-0.1.1.tgz", + "integrity": "sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==", + "license": "MIT", + "engines": { + "node": ">=12.x" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.0.tgz", + "integrity": "sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.11.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "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.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.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", + "is-path-inside": "^3.0.3", + "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", + "strip-ansi": "^6.0.1", + "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.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "license": "BSD-2-Clause", + "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.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "license": "MIT", + "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/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.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, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz", + "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "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-glob/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/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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "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, + "license": "MIT", + "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==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": true + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "peer": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "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/header-range-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.3.tgz", + "integrity": "sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==", + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/http-status-emojis": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http-status-emojis/-/http-status-emojis-2.2.0.tgz", + "integrity": "sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==", + "license": "MIT" + }, + "node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.0.tgz", + "integrity": "sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "peer": true + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "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, + "license": "MIT", + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "peer": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "peer": true + }, + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "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/lowdb": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", + "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", + "dependencies": { + "steno": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "optional": true, + "peer": true + }, + "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/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/milliparsec": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/milliparsec/-/milliparsec-4.0.0.tgz", + "integrity": "sha512-/wk9d4Z6/9ZvoEH/6BI4TrTCgmkpZPuSRN/6fI9aUHOfXdNTuj/VhLS7d+NqG26bi6L9YmGXutVYvWC8zQ0qtA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "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/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "peer": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "peer": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "peer": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "peer": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "peer": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "peer": true + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "peer": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", + "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regexparam": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz", + "integrity": "sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sort-on": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sort-on/-/sort-on-6.1.0.tgz", + "integrity": "sha512-WTECP0nYNWO1n2g5bpsV0yZN9cBmZsF8ThHFbOqVN0HBFRoaQZLLEMvMmJlKHNPYQeVngeI5+jJzIfFqOIo1OA==", + "license": "MIT", + "dependencies": { + "dot-prop": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/steno": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "peer": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "peer": true + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/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, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "peer": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "peer": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "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/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "peer": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tsx": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.6.0.tgz", + "integrity": "sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.6.0", + "@typescript-eslint/parser": "8.6.0", + "@typescript-eslint/utils": "8.6.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/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "BSD-2-Clause", + "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", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "license": "ISC", + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "peer": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 5922b0fb2..a0cfb2993 100644 --- a/package.json +++ b/package.json @@ -1,67 +1,63 @@ { "name": "json-server", - "version": "0.8.0", - "description": "Serves JSON files through REST routes.", - "main": "./src/server/index.js", - "bin": "./bin/index.js", - "directories": { - "test": "test" + "version": "1.0.0-beta.3", + "description": "", + "type": "module", + "bin": { + "json-server": "lib/bin.js" }, - "dependencies": { - "body-parser": "^1.8.1", - "chalk": "^0.4.0", - "connect-pause": "^0.1.0", - "cors": "^2.3.0", - "errorhandler": "^1.2.0", - "express": "^4.9.5", - "got": "^3.3.0", - "lodash": "^3.9.2", - "lowdb": "^0.10.0", - "method-override": "^2.1.2", - "morgan": "^1.3.1", - "node-uuid": "^1.4.2", - "pluralize": "^1.1.2", - "underscore-db": "^0.9.0", - "update-notifier": "^0.5.0", - "yargs": "^3.10.0" - }, - "devDependencies": { - "husky": "^0.6.1", - "mocha": "^2.2.4", - "rimraf": "^2.4.1", - "server-ready": "^0.2.0", - "standard": "^3.8.0", - "supertest": "~0.8.1" + "types": "lib", + "files": [ + "lib", + "views" + ], + "engines": { + "node": ">=18.3" }, "scripts": { - "test": "NODE_ENV=test mocha -R spec test/**/*.js && standard", - "start": "node bin", - "prepush": "npm t" + "dev": "tsx watch src/bin.ts fixtures/db.json", + "build": "rm -rf lib && tsc", + "test": "node --import tsx/esm --test src/*.test.ts", + "lint": "eslint src", + "prepare": "husky", + "prepublishOnly": "npm run build" }, + "keywords": [], + "author": "typicode ", + "license": "SEE LICENSE IN ./LICENSE", "repository": { "type": "git", - "url": "git://github.com/typicode/json-server.git" + "url": "git+https://github.com/typicode/json-server.git" }, - "keywords": [ - "JSON", - "server", - "fake", - "REST", - "API", - "prototyping", - "mock", - "mocking", - "test", - "testing", - "rest", - "data", - "dummy", - "sandbox" - ], - "author": "Typicode ", - "license": "MIT", - "bugs": { - "url": "https://github.com/typicode/json-server/issues" + "devDependencies": { + "@eslint/js": "^9.11.0", + "@sindresorhus/tsconfig": "^6.0.0", + "@tailwindcss/typography": "^0.5.15", + "@types/node": "^22.5.5", + "concurrently": "^9.0.1", + "eslint": "^9.11.0", + "get-port": "^7.1.0", + "globals": "^15.9.0", + "husky": "^9.1.6", + "tempy": "^3.1.0", + "tsx": "^4.19.1", + "type-fest": "^4.26.1", + "typescript": "^5.6.2", + "typescript-eslint": "^8.6.0" }, - "homepage": "https://github.com/typicode/json-server" + "dependencies": { + "@tinyhttp/app": "^2.4.0", + "@tinyhttp/cors": "^2.0.1", + "@tinyhttp/logger": "^2.0.0", + "chalk": "^5.3.0", + "chokidar": "^4.0.1", + "dot-prop": "^9.0.0", + "eta": "^3.5.0", + "inflection": "^3.0.0", + "json5": "^2.2.3", + "lowdb": "^7.0.1", + "milliparsec": "^4.0.0", + "sirv": "^2.0.4", + "sort-on": "^6.1.0" + } } diff --git a/public/test.html b/public/test.html new file mode 100644 index 000000000..a2b4bbc78 --- /dev/null +++ b/public/test.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app.test.ts b/src/app.test.ts new file mode 100644 index 000000000..7faf530a6 --- /dev/null +++ b/src/app.test.ts @@ -0,0 +1,129 @@ +import assert from 'node:assert/strict' +import { writeFileSync } from 'node:fs' +import { join } from 'node:path' +import test from 'node:test' + +import getPort from 'get-port' +import { Low, Memory } from 'lowdb' +import { temporaryDirectory } from 'tempy' + +import { createApp } from './app.js' +import { Data } from './service.js' + +type Test = { + + method: HTTPMethods + url: string + statusCode: number +} + +type HTTPMethods = + | 'DELETE' + | 'GET' + | 'HEAD' + | 'PATCH' + | 'POST' + | 'PUT' + | 'OPTIONS' + +const port = await getPort() + +// Create custom static dir with an html file +const tmpDir = temporaryDirectory() +const file = 'file.html' +writeFileSync(join(tmpDir, file), 'utf-8') + +// Create app +const db = new Low(new Memory(), {}) +db.data = { + posts: [{ id: '1', title: 'foo' }], + comments: [{ id: '1', postId: '1' }], + object: { f1: 'foo' }, +} +const app = createApp(db, { static: [tmpDir] }) + +await new Promise((resolve, reject) => { + try { + const server = app.listen(port, () => resolve()) + test.after(() => server.close()) + } catch (err) { + reject(err) + } +}) + +await test('createApp', async (t) => { + // URLs + const POSTS = '/posts' + const POSTS_WITH_COMMENTS = '/posts?_embed=comments' + const POST_1 = '/posts/1' + const POST_NOT_FOUND = '/posts/-1' + const POST_WITH_COMMENTS = '/posts/1?_embed=comments' + const COMMENTS = '/comments' + const POST_COMMENTS = '/comments?postId=1' + const NOT_FOUND = '/not-found' + const OBJECT = '/object' + const OBJECT_1 = '/object/1' + + const arr: Test[] = [ + // Static + { method: 'GET', url: '/', statusCode: 200 }, + { method: 'GET', url: '/test.html', statusCode: 200 }, + { method: 'GET', url: `/${file}`, statusCode: 200 }, + + // CORS + { method: 'OPTIONS', url: POSTS, statusCode: 204 }, + + // API + { method: 'GET', url: POSTS, statusCode: 200 }, + { method: 'GET', url: POSTS_WITH_COMMENTS, statusCode: 200 }, + { method: 'GET', url: POST_1, statusCode: 200 }, + { method: 'GET', url: POST_NOT_FOUND, statusCode: 404 }, + { method: 'GET', url: POST_WITH_COMMENTS, statusCode: 200 }, + { method: 'GET', url: COMMENTS, statusCode: 200 }, + { method: 'GET', url: POST_COMMENTS, statusCode: 200 }, + { method: 'GET', url: OBJECT, statusCode: 200 }, + { method: 'GET', url: OBJECT_1, statusCode: 404 }, + { method: 'GET', url: NOT_FOUND, statusCode: 404 }, + + { method: 'POST', url: POSTS, statusCode: 201 }, + { method: 'POST', url: POST_1, statusCode: 404 }, + { method: 'POST', url: POST_NOT_FOUND, statusCode: 404 }, + { method: 'POST', url: OBJECT, statusCode: 404 }, + { method: 'POST', url: OBJECT_1, statusCode: 404 }, + { method: 'POST', url: NOT_FOUND, statusCode: 404 }, + + { method: 'PUT', url: POSTS, statusCode: 404 }, + { method: 'PUT', url: POST_1, statusCode: 200 }, + { method: 'PUT', url: OBJECT, statusCode: 200 }, + { method: 'PUT', url: OBJECT_1, statusCode: 404 }, + { method: 'PUT', url: POST_NOT_FOUND, statusCode: 404 }, + { method: 'PUT', url: NOT_FOUND, statusCode: 404 }, + + { method: 'PATCH', url: POSTS, statusCode: 404 }, + { method: 'PATCH', url: POST_1, statusCode: 200 }, + { method: 'PATCH', url: OBJECT, statusCode: 200 }, + { method: 'PATCH', url: OBJECT_1, statusCode: 404 }, + { method: 'PATCH', url: POST_NOT_FOUND, statusCode: 404 }, + { method: 'PATCH', url: NOT_FOUND, statusCode: 404 }, + + { method: 'DELETE', url: POSTS, statusCode: 404 }, + { method: 'DELETE', url: POST_1, statusCode: 200 }, + { method: 'DELETE', url: OBJECT, statusCode: 404 }, + { method: 'DELETE', url: OBJECT_1, statusCode: 404 }, + { method: 'DELETE', url: POST_NOT_FOUND, statusCode: 404 }, + { method: 'DELETE', url: NOT_FOUND, statusCode: 404 }, + ] + + for (const tc of arr) { + await t.test(`${tc.method} ${tc.url}`, async () => { + const response = await fetch(`http://localhost:${port}${tc.url}`, { + method: tc.method, + }) + assert.equal( + response.status, + tc.statusCode, + `${response.status} !== ${tc.statusCode} ${tc.method} ${tc.url} failed`, + ) + }) + } +}) diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 000000000..c8f6ff849 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,150 @@ +import { dirname, isAbsolute, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { App, type Request } from '@tinyhttp/app' +import { cors } from '@tinyhttp/cors' +import { Eta } from 'eta' +import { Low } from 'lowdb' +import { json } from 'milliparsec' +import sirv from 'sirv' + +import { Data, isItem, Service } from './service.js' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const isProduction = process.env['NODE_ENV'] === 'production' + +type QueryValue = Request['query'][string] | number +type Query = Record + +export type AppOptions = { + logger?: boolean + static?: string[] +} + +const eta = new Eta({ + views: join(__dirname, '../views'), + cache: isProduction, +}) + +export function createApp(db: Low, options: AppOptions = {}) { + // Create service + const service = new Service(db) + + // Create app + const app = new App() + + // Static files + app.use(sirv('public', { dev: !isProduction })) + options.static + ?.map((path) => (isAbsolute(path) ? path : join(process.cwd(), path))) + .forEach((dir) => app.use(sirv(dir, { dev: !isProduction }))) + + // CORS + app + .use((req, res, next) => { + return cors({ + allowedHeaders: req.headers['access-control-request-headers'] + ?.split(',') + .map((h) => h.trim()), + })(req, res, next) + }) + .options('*', cors()) + + // Body parser + // @ts-expect-error expected + app.use(json()) + + app.get('/', (_req, res) => + res.send(eta.render('index.html', { data: db.data })), + ) + + app.get('/:name', (req, res, next) => { + const { name = '' } = req.params + const query: Query = {} + + Object.keys(req.query).forEach((key) => { + let value: QueryValue = req.query[key] + + if ( + ['_start', '_end', '_limit', '_page', '_per_page'].includes(key) && + typeof value === 'string' + ) { + value = parseInt(value); + } + + if (!Number.isNaN(value)) { + query[key] = value; + } + }) + res.locals['data'] = service.find(name, query) + next?.() + }) + + app.get('/:name/:id', (req, res, next) => { + const { name = '', id = '' } = req.params + res.locals['data'] = service.findById(name, id, req.query) + next?.() + }) + + app.post('/:name', async (req, res, next) => { + const { name = '' } = req.params + if (isItem(req.body)) { + res.locals['data'] = await service.create(name, req.body) + } + next?.() + }) + + app.put('/:name', async (req, res, next) => { + const { name = '' } = req.params + if (isItem(req.body)) { + res.locals['data'] = await service.update(name, req.body) + } + next?.() + }) + + app.put('/:name/:id', async (req, res, next) => { + const { name = '', id = '' } = req.params + if (isItem(req.body)) { + res.locals['data'] = await service.updateById(name, id, req.body) + } + next?.() + }) + + app.patch('/:name', async (req, res, next) => { + const { name = '' } = req.params + if (isItem(req.body)) { + res.locals['data'] = await service.patch(name, req.body) + } + next?.() + }) + + app.patch('/:name/:id', async (req, res, next) => { + const { name = '', id = '' } = req.params + if (isItem(req.body)) { + res.locals['data'] = await service.patchById(name, id, req.body) + } + next?.() + }) + + app.delete('/:name/:id', async (req, res, next) => { + const { name = '', id = '' } = req.params + res.locals['data'] = await service.destroyById( + name, + id, + req.query['_dependent'], + ) + next?.() + }) + + app.use('/:name', (req, res) => { + const { data } = res.locals + if (data === undefined) { + res.sendStatus(404) + } else { + if (req.method === 'POST') res.status(201) + res.json(data) + } + }) + + return app +} diff --git a/src/bin.ts b/src/bin.ts new file mode 100644 index 000000000..4633e5e43 --- /dev/null +++ b/src/bin.ts @@ -0,0 +1,227 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { extname } from 'node:path' +import { parseArgs } from 'node:util' + +import chalk from 'chalk' +import { watch } from 'chokidar' +import JSON5 from 'json5' +import { Adapter, Low } from 'lowdb' +import { DataFile, JSONFile } from 'lowdb/node' +import { PackageJson } from 'type-fest' + +import { fileURLToPath } from 'node:url' +import { createApp } from './app.js' +import { Observer } from './observer.js' +import { Data } from './service.js' + +function help() { + console.log(`Usage: json-server [options] + +Options: + -p, --port Port (default: 3000) + -h, --host Host (default: localhost) + -s, --static Static files directory (multiple allowed) + --help Show this message + --version Show version number +`) +} + +// Parse args +function args(): { + file: string + port: number + host: string + static: string[] +} { + try { + const { values, positionals } = parseArgs({ + options: { + port: { + type: 'string', + short: 'p', + default: process.env['PORT'] ?? '3000', + }, + host: { + type: 'string', + short: 'h', + default: process.env['HOST'] ?? 'localhost', + }, + static: { + type: 'string', + short: 's', + multiple: true, + default: [], + }, + help: { + type: 'boolean', + }, + version: { + type: 'boolean', + }, + // Deprecated + watch: { + type: 'boolean', + short: 'w', + }, + }, + allowPositionals: true, + }) + + // --version + if (values.version) { + const pkg = JSON.parse( + readFileSync( + fileURLToPath(new URL('../package.json', import.meta.url)), + 'utf-8', + ), + ) as PackageJson + console.log(pkg.version) + process.exit() + } + + // Handle --watch + if (values.watch) { + console.log( + chalk.yellow( + '--watch/-w can be omitted, JSON Server 1+ watches for file changes by default', + ), + ) + } + + if (values.help || positionals.length === 0) { + help() + process.exit() + } + + // App args and options + return { + file: positionals[0] ?? '', + port: parseInt(values.port as string), + host: values.host as string, + static: values.static as string[], + } + } catch (e) { + if ((e as NodeJS.ErrnoException).code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') { + console.log(chalk.red((e as NodeJS.ErrnoException).message.split('.')[0])) + help() + process.exit(1) + } else { + throw e + } + } +} + +const { file, port, host, static: staticArr } = args() + +if (!existsSync(file)) { + console.log(chalk.red(`File ${file} not found`)) + process.exit(1) +} + +// Handle empty string JSON file +if (readFileSync(file, 'utf-8').trim() === '') { + writeFileSync(file, '{}') +} + +// Set up database +let adapter: Adapter +if (extname(file) === '.json5') { + adapter = new DataFile(file, { + parse: JSON5.parse, + stringify: JSON5.stringify, + }) +} else { + adapter = new JSONFile(file) +} +const observer = new Observer(adapter) + +const db = new Low(observer, {}) +await db.read() + +// Create app +const app = createApp(db, { logger: false, static: staticArr }) + +function logRoutes(data: Data) { + console.log(chalk.bold('Endpoints:')) + if (Object.keys(data).length === 0) { + console.log( + chalk.gray(`No endpoints found, try adding some data to ${file}`), + ) + return + } + console.log( + Object.keys(data) + .map( + (key) => `${chalk.gray(`http://${host}:${port}/`)}${chalk.blue(key)}`, + ) + .join('\n'), + ) +} + +const kaomojis = ['♡⸜(˶˃ ᵕ ˂˶)⸝♡', '♡( ◡‿◡ )', '( ˶ˆ ᗜ ˆ˵ )', '(˶ᵔ ᵕ ᵔ˶)'] + +function randomItem(items: string[]): string { + const index = Math.floor(Math.random() * items.length) + return items.at(index) ?? '' +} + +app.listen(port, () => { + console.log( + [ + chalk.bold(`JSON Server started on PORT :${port}`), + chalk.gray('Press CTRL-C to stop'), + chalk.gray(`Watching ${file}...`), + '', + chalk.magenta(randomItem(kaomojis)), + '', + chalk.bold('Index:'), + chalk.gray(`http://localhost:${port}/`), + '', + chalk.bold('Static files:'), + chalk.gray('Serving ./public directory if it exists'), + '', + ].join('\n'), + ) + logRoutes(db.data) +}) + +// Watch file for changes +if (process.env['NODE_ENV'] !== 'production') { + let writing = false // true if the file is being written to by the app + let prevEndpoints = '' + + observer.onWriteStart = () => { + writing = true + } + observer.onWriteEnd = () => { + writing = false + } + observer.onReadStart = () => { + prevEndpoints = JSON.stringify(Object.keys(db.data).sort()) + } + observer.onReadEnd = (data) => { + if (data === null) { + return + } + + const nextEndpoints = JSON.stringify(Object.keys(data).sort()) + if (prevEndpoints !== nextEndpoints) { + console.log() + logRoutes(data) + } + } + watch(file).on('change', () => { + // Do no reload if the file is being written to by the app + if (!writing) { + db.read().catch((e) => { + if (e instanceof SyntaxError) { + return console.log( + chalk.red(['', `Error parsing ${file}`, e.message].join('\n')), + ) + } + console.log(e) + }) + } + }) +} diff --git a/src/cli/index.js b/src/cli/index.js deleted file mode 100644 index a7b44b4ca..000000000 --- a/src/cli/index.js +++ /dev/null @@ -1,57 +0,0 @@ -var updateNotifier = require('update-notifier') -var yargs = require('yargs') -var run = require('./run') -var pkg = require('../../package.json') - -module.exports = function () { - - updateNotifier({ pkg: pkg }).notify() - - var argv = yargs - .usage('$0 [options] ') - .options({ - port: { - alias: 'p', - description: 'Set port', - default: 3000 - }, - host: { - alias: 'H', - description: 'Set host', - default: '0.0.0.0' - }, - watch: { - alias: 'w', - description: 'Watch file(s)' - }, - routes: { - alias: 'r', - description: 'Load routes file' - }, - static: { - alias: 's', - description: 'Set static files directory' - }, - delay: { - alias: 'd', - description: 'Add delay to responses (ms)' - }, - id: { - alias: 'i', - description: 'Set database id property (e.g. _id)', - default: 'id' - } - }) - .boolean('watch') - .help('help').alias('help', 'h') - .version(pkg.version).alias('version', 'v') - .example('$0 db.json', '') - .example('$0 file.js', '') - .example('$0 http://example.com/db.json', '') - .epilog('https://github.com/typicode/json-server') - .require(1, 'Missing argument') - .argv - - run(argv) - -} diff --git a/src/cli/run.js b/src/cli/run.js deleted file mode 100644 index 2af560aa5..000000000 --- a/src/cli/run.js +++ /dev/null @@ -1,139 +0,0 @@ -var fs = require('fs') -var path = require('path') -var chalk = require('chalk') -var is = require('./utils/is') -var load = require('./utils/load') -var watch = require('./watch') -var pause = require('connect-pause') -var jsonServer = require('../server') - -function prettyPrint (argv, object, rules) { - var host = argv.host === '0.0.0.0' ? 'localhost' : argv.host - var port = argv.port - var root = 'http://' + host + ':' + port - - console.log() - console.log(chalk.bold(' Resources')) - for (var prop in object) { - console.log(' ' + root + '/' + prop) - } - - if (rules) { - console.log() - console.log(chalk.bold(' Other routes')) - for (var rule in rules) { - console.log(' ' + rule + ' -> ' + rules[rule]) - } - } - - console.log() - console.log(chalk.bold(' Home')) - console.log(' ' + root) - console.log() -} - -function createApp (source, object, routes, argv) { - var app = jsonServer.create() - - var router = jsonServer.router( - is.JSON(source) ? - source : - object - ) - - var defaults - if (argv.static) { - defaults = jsonServer.defaults({ - static: path.join(process.cwd(), argv.static) - }) - } else { - defaults = jsonServer.defaults() - } - - app.use(defaults) - - if (routes) { - var rewriter = jsonServer.rewriter(routes) - app.use(rewriter) - } - - if (argv.delay) { - app.use(pause(argv.delay)) - } - - router.db._.id = argv.id - app.db = router.db - app.use(router) - - return app -} - -module.exports = function (argv) { - - var source = argv._[0] - var app - var server - - console.log() - console.log(chalk.cyan(' \\{^_^}/ hi!')) - - function start (cb) { - console.log() - console.log(chalk.gray(' Loading', source)) - - // Load JSON, JS or HTTP database - load(source, function (err, data) { - - if (err) throw err - - // Load additional routes - if (argv.routes) { - console.log(chalk.gray(' Loading', argv.routes)) - var routes = JSON.parse(fs.readFileSync(argv.routes)) - } - - console.log(chalk.gray(' Done')) - - // Create app and server - app = createApp(source, data, routes, argv) - server = app.listen(argv.port, argv.host) - - // Display server informations - prettyPrint(argv, data, routes) - - cb && cb() - }) - } - - // Start server - start(function () { - - // Snapshot - console.log( - chalk.gray(' Type s + enter at any time to create a snapshot of the database') - ) - - process.stdin.resume() - process.stdin.setEncoding('utf8') - process.stdin.on('data', function (chunk) { - if (chunk.trim().toLowerCase() === 's') { - var file = 'db-' + Date.now() + '.json' - app.db.saveSync(file) - console.log(' Saved snapshot to ' + file + '\n') - } - }) - - // Watch files - if (argv.watch) { - console.log(chalk.gray(' Watching...')) - console.log() - watch(argv, function (file) { - console.log(chalk.gray(' ' + file + ' has changed, reloading...')) - server && server.close() - start() - }) - } - - }) - -} diff --git a/src/cli/utils/is.js b/src/cli/utils/is.js deleted file mode 100644 index 9556ae5c3..000000000 --- a/src/cli/utils/is.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - JSON: isJSON, - JS: isJS, - URL: isURL -} - -function isJSON (s) { - return /\.json$/.test(s) -} - -function isJS (s) { - return /\.js$/.test(s) -} - -function isURL (s) { - return /^(http|https):/.test(s) -} diff --git a/src/cli/utils/load.js b/src/cli/utils/load.js deleted file mode 100644 index ab83a4d16..000000000 --- a/src/cli/utils/load.js +++ /dev/null @@ -1,32 +0,0 @@ -var path = require('path') -var got = require('got') -var low = require('lowdb') -var is = require('./is') - -module.exports = function (source, cb) { - var data - - if (is.URL(source)) { - - got(source, { json: true }, function (err, data) { - cb(err, data) - }) - - } else if (is.JS(source)) { - - var filename = path.resolve(source) - delete require.cache[filename] - data = require(filename)() - cb(null, data) - - } else if (is.JSON(source)) { - - data = low(source).object - cb(null, data) - - } else { - - throw new Error('Unsupported source ' + source) - - } -} diff --git a/src/cli/watch.js b/src/cli/watch.js deleted file mode 100644 index 5b452cb3e..000000000 --- a/src/cli/watch.js +++ /dev/null @@ -1,44 +0,0 @@ -var fs = require('fs') -var path = require('path') -var is = require('./utils/is') - -module.exports = watch - -// Because JSON file can be modified by the server, we need to be able to -// distinguish between user modification vs server modification. -// When the server modifies the JSON file, it generates a rename event. -// When the user modifies the JSON file, it generate a change event. -function watchDB (file, cb) { - var watchedDir = path.dirname(file) - var watchedFile = path.basename(file) - - fs.watch(watchedDir, function (event, changedFile) { - if (event === 'change' && changedFile === watchedFile) cb() - }) -} - -function watchJS (file, cb) { - fs.watchFile(file, cb) -} - -function watchSource (source, cb) { - if (is.JSON(source)) { - return watchDB(source, cb) - } - if (is.JS(source)) return watchJS(source, cb) - if (is.URL(source)) throw new Error('Can\'t watch URL') -} - -function watch (argv, cb) { - var source = argv._[0] - - watchSource(source, function () { - cb(source) - }) - - if (argv.routes) { - fs.watchFile(argv.routes, function () { - cb(argv.routes) - }) - } -} diff --git a/src/observer.ts b/src/observer.ts new file mode 100644 index 000000000..f2c890f92 --- /dev/null +++ b/src/observer.ts @@ -0,0 +1,36 @@ +import { Adapter } from 'lowdb' + +// Lowdb adapter to observe read/write events +export class Observer { + #adapter + + onReadStart = function () { + return + } + onReadEnd: (data: T | null) => void = function () { + return + } + onWriteStart = function () { + return + } + onWriteEnd = function () { + return + } + + constructor(adapter: Adapter) { + this.#adapter = adapter + } + + async read() { + this.onReadStart() + const data = await this.#adapter.read() + this.onReadEnd(data) + return data + } + + async write(arg: T) { + this.onWriteStart() + await this.#adapter.write(arg) + this.onWriteEnd() + } +} diff --git a/src/server/defaults.js b/src/server/defaults.js deleted file mode 100644 index 3b1646e07..000000000 --- a/src/server/defaults.js +++ /dev/null @@ -1,48 +0,0 @@ -var fs = require('fs') -var path = require('path') -var express = require('express') -var logger = require('morgan') -var cors = require('cors') -var errorhandler = require('errorhandler') - -module.exports = function (opts) { - var userDir = path.join(process.cwd(), 'public') - var defaultDir = path.join(__dirname, 'public') - var staticDir = fs.existsSync(userDir) ? - userDir : - defaultDir - - opts = opts || { static: staticDir } - - var arr = [] - - // Logger - arr.push(logger('dev', { - skip: function (req, res) { - return process.env.NODE_ENV === 'test' || - req.path === '/favicon.ico' - } - })) - - // Enable CORS for all the requests, including static files - arr.push(cors({ origin: true, credentials: true })) - - if (process.env.NODE_ENV === 'development') { - // only use in development - arr.push(errorhandler()) - } - - // Serve static files - arr.push(express.static(opts.static)) - - // No cache for IE - // https://support.microsoft.com/en-us/kb/234067 - arr.push(function (req, res, next) { - res.header('Cache-Control', 'no-cache') - res.header('Pragma', 'no-cache') - res.header('Expires', '-1') - next() - }) - - return arr -} diff --git a/src/server/index.js b/src/server/index.js deleted file mode 100644 index 9a74caf58..000000000 --- a/src/server/index.js +++ /dev/null @@ -1,12 +0,0 @@ -var express = require('express') - -module.exports = { - create: function () { - var server = express() - server.set('json spaces', 2) - return server - }, - defaults: require('./defaults'), - router: require('./router'), - rewriter: require('./rewriter') -} diff --git a/src/server/mixins.js b/src/server/mixins.js deleted file mode 100644 index b2dca69c5..000000000 --- a/src/server/mixins.js +++ /dev/null @@ -1,77 +0,0 @@ -var uuid = require('node-uuid') -var pluralize = require('pluralize') - -module.exports = { - getRemovable: getRemovable, - createId: createId, - deepQuery: deepQuery -} - -// Returns document ids that have unsatisfied relations -// Example: a comment that references a post that doesn't exist -function getRemovable (db) { - var _ = this - var removable = [] - _.each(db, function (coll, collName) { - _.each(coll, function (doc) { - _.each(doc, function (value, key) { - if (/Id$/.test(key)) { - var refName = pluralize.plural(key.slice(0, -2)) - // Test if table exists - if (db[refName]) { - // Test if references is defined in table - var ref = _.getById(db[refName], value) - if (_.isUndefined(ref)) { - removable.push({name: collName, id: doc.id}) - } - } - } - }) - }) - }) - - return removable -} - -// Return incremented id or uuid -// Used to override underscore-db's createId with utils.createId -function createId (coll) { - var _ = this - var idProperty = _.__id() - if (_.isEmpty(coll)) { - return 1 - } else { - var id = _.max(coll, function (doc) { - return doc[idProperty] - })[idProperty] - - if (_.isFinite(id)) { - // Increment integer id - return ++id - } else { - // Generate string id - return uuid() - } - } -} - -function deepQuery (value, q) { - var _ = this - if (value && q) { - if (_.isArray(value)) { - for (var i = 0; i < value.length; i++) { - if (_.deepQuery(value[i], q)) { - return true - } - } - } else if (_.isObject(value) && !_.isArray(value)) { - for (var k in value) { - if (_.deepQuery(value[k], q)) { - return true - } - } - } else if (value.toString().toLowerCase().indexOf(q) !== -1) { - return true - } - } -} diff --git a/src/server/public/favicon.ico b/src/server/public/favicon.ico deleted file mode 100644 index bfdd40dc7..000000000 Binary files a/src/server/public/favicon.ico and /dev/null differ diff --git a/src/server/public/images/json.png b/src/server/public/images/json.png deleted file mode 100644 index 57176594d..000000000 Binary files a/src/server/public/images/json.png and /dev/null differ diff --git a/src/server/public/index.html b/src/server/public/index.html deleted file mode 100644 index 7d1604a2c..000000000 --- a/src/server/public/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - JSON Server - - - - -
-

- -

- -
- -

- - Congrats! You're successfully running JSON Server. - -

- -
- -

Routes

-

- Here are the resources that JSON Server has loaded: -

-

-

    loading, please wait...
-

- -

- You can view database current state at any time: -

    -
  • - db -
  • -
-

- -

- You can use any HTTP verbs (GET, POST, PUT, PATCH and DELETE) and access your resources from anywhere - using CORS and JSONP. -

- -

Documentation

-

- View - README - on GitHub. -

- -

Issues

-

Please go - here. -

- -
- -

- To replace this page, create an index.html file in ./public, JSON Server will load it. -

-
- - - - - - - diff --git a/src/server/public/stylesheets/style.css b/src/server/public/stylesheets/style.css deleted file mode 100644 index 098c6a4c2..000000000 --- a/src/server/public/stylesheets/style.css +++ /dev/null @@ -1,16 +0,0 @@ -a { - color: #1882BC !important; -} - -img { - padding-top: 50px; - padding-bottom: 20px; -} - -li { - list-style-type: square; -} - -h4 { - padding-top: 20px; -} \ No newline at end of file diff --git a/src/server/rewriter.js b/src/server/rewriter.js deleted file mode 100644 index da80b513e..000000000 --- a/src/server/rewriter.js +++ /dev/null @@ -1,29 +0,0 @@ -var express = require('express') - -module.exports = function (routes) { - var router = express.Router() - - Object.keys(routes).forEach(function (route) { - - if (route.indexOf(':') !== -1) { - router.all(route, function (req, res, next) { - // Rewrite target url using params - var target = routes[route] - for (var param in req.params) { - target = target.replace(':' + param, req.params[param]) - } - req.url = target - next() - }) - } else { - router.all(route + '*', function (req, res, next) { - // Rewrite url by replacing prefix - req.url = req.url.replace(route, routes[route]) - next() - }) - } - - }) - - return router -} diff --git a/src/server/router/index.js b/src/server/router/index.js deleted file mode 100644 index 31031ac4a..000000000 --- a/src/server/router/index.js +++ /dev/null @@ -1,90 +0,0 @@ -var express = require('express') -var methodOverride = require('method-override') -var bodyParser = require('body-parser') -var _ = require('lodash') -var _db = require('underscore-db') -var low = require('lowdb') -var plural = require('./plural') -var nested = require('./nested') -var singular = require('./singular') -var mixins = require('../mixins') - -module.exports = function (source) { - - // Create router - var router = express.Router() - - // Add middlewares - router.use(bodyParser.json({limit: '10mb'})) - router.use(bodyParser.urlencoded({extended: false})) - router.use(methodOverride()) - - // Create database - var db - if (_.isObject(source)) { - db = low() - db.object = source - } else { - db = low(source) - } - - // Add underscore-db methods to db - db._.mixin(_db) - - // Add specific mixins - db._.mixin(mixins) - - // Expose database - router.db = db - - // Expose database - router.db = db - - // Expose render - router.render = function (req, res) { - res.jsonp(res.locals.data) - } - - // GET /db - function showDatabase (req, res, next) { - res.locals.data = db.object - next() - } - - router.get('/db', showDatabase) - - router.use(nested()) - - // Create routes - for (var prop in db.object) { - var val = db.object[prop] - - if (_.isPlainObject(val)) { - router.use('/' + prop, singular(db, prop)) - continue - } - - if (_.isArray(val)) { - router.use('/' + prop, plural(db, prop)) - continue - } - - var msg = - 'Type of "' + prop + '" (' + typeof val + ') ' + - (_.isObject(source) ? '' : 'in ' + source) + ' is not supported. ' + - 'Use objects or arrays of objects.' - - throw new Error(msg) - } - - router.use(function (req, res) { - if (!res.locals.data) { - res.status(404) - res.locals.data = {} - } - - router.render(req, res) - }) - - return router -} diff --git a/src/server/router/nested.js b/src/server/router/nested.js deleted file mode 100644 index b7760584b..000000000 --- a/src/server/router/nested.js +++ /dev/null @@ -1,18 +0,0 @@ -var express = require('express') -var pluralize = require('pluralize') -var utils = require('../utils') - -module.exports = function () { - - var router = express.Router() - - // Rewrite url to /:nested?:resourceId=:id - router.get('/:resource/:id/:nested', function (req, res, next) { - var prop = pluralize.singular(req.params.resource) - req.query[prop + 'Id'] = utils.toNative(req.params.id) - req.url = '/' + req.params.nested - next() - }) - - return router -} diff --git a/src/server/router/plural.js b/src/server/router/plural.js deleted file mode 100644 index 80a29d17e..000000000 --- a/src/server/router/plural.js +++ /dev/null @@ -1,254 +0,0 @@ -var express = require('express') -var _ = require('lodash') -var pluralize = require('pluralize') -var utils = require('../utils') - -module.exports = function (db, name) { - - // Create router - var router = express.Router() - - // Embed function used in GET /name and GET /name/id - function embed (resource, e) { - e && [].concat(e) - .forEach(function (externalResource) { - if (db.object[externalResource]) { - var query = {} - var singularResource = pluralize.singular(name) - query[singularResource + 'Id'] = resource.id - resource[externalResource] = db(externalResource).where(query) - } - }) - } - - // Expand function used in GET /name and GET /name/id - function expand (resource, e) { - e && [].concat(e) - .forEach(function (innerResource) { - var plural = pluralize(innerResource) - if (db.object[plural]) { - var prop = innerResource + 'Id' - resource[innerResource] = db(plural).getById(resource[prop]) - } - }) - } - - // GET /name - // GET /name?q= - // GET /name?attr=&attr= - // GET /name?_end=& - // GET /name?_start=&_end=& - // GET /name?_embed=&_expand= - function list (req, res, next) { - - // Resource chain - var chain = db(name).chain() - - // Remove q, _start, _end, ... from req.query to avoid filtering using those - // parameters - var q = req.query.q - var _start = req.query._start - var _end = req.query._end - var _sort = req.query._sort - var _order = req.query._order - var _limit = req.query._limit - var _embed = req.query._embed - var _expand = req.query._expand - delete req.query.q - delete req.query._start - delete req.query._end - delete req.query._sort - delete req.query._order - delete req.query._limit - delete req.query._embed - delete req.query._expand - - // Automatically delete query parameters that can't be found - // in the database - Object.keys(req.query).forEach(function (query) { - var arr = db(name).value() - for (var i in arr) { - if ( - _.has(arr[i], query) || - query === 'callback' || - query === '_' || - query.indexOf('_lte') !== -1 || - query.indexOf('_gte') !== -1 - ) return - } - delete req.query[query] - }) - - if (q) { - - // Full-text search - q = q.toLowerCase() - - chain = chain.filter(function (obj) { - for (var key in obj) { - var value = obj[key] - if (db._.deepQuery(value, q)) { - return true - } - } - }) - - } - - Object.keys(req.query).forEach(function (key) { - // Don't take into account JSONP query parameters - // jQuery adds a '_' query parameter too - if (key !== 'callback' && key !== '_') { - // Always use an array, in case req.query is an array - var arr = [].concat(req.query[key]) - - chain = chain.filter(function (element) { - return arr - .map(utils.toNative) - .map(function (value) { - var isRange = key.indexOf('_lte') !== -1 || key.indexOf('_gte') !== -1 - if (isRange) { - var path = key.replace(/(_lte|_gte)$/, '') - var isLowerThan = key.indexOf('_gte') !== -1 - var elementValue = _.get(element, path) - - if (isLowerThan) { - return value <= elementValue - } else { - return value >= elementValue - } - } else { - return _.matchesProperty(key, value)(element) - } - }).reduce(function (a, b) { - return a || b - }) - }) - } - }) - - // Sort - if (_sort) { - _order = _order || 'ASC' - - chain = chain.sortBy(function (element) { - return element[_sort] - }) - - if (_order === 'DESC') { - chain = chain.reverse() - } - } - - // Slice result - if (_end || _limit) { - res.setHeader('X-Total-Count', chain.size()) - res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count') - } - - _start = parseInt(_start, 10) || 0 - - if (_end) { - _end = parseInt(_end, 10) - chain = chain.slice(_start, _end) - } else if (_limit) { - _limit = parseInt(_limit, 10) - chain = chain.slice(_start, _start + _limit) - } - - // embed and expand - chain = chain - .cloneDeep() - .forEach(function (element) { - embed(element, _embed) - expand(element, _expand) - }) - - res.locals.data = chain.value() - next() - } - - // GET /name/:id - // GET /name/:id?_embed=&_expand - function show (req, res, next) { - var _embed = req.query._embed - var _expand = req.query._expand - var id = utils.toNative(req.params.id) - var resource = db(name).getById(id) - - if (resource) { - // Clone resource to avoid making changes to the underlying object - resource = _.cloneDeep(resource) - - // Embed other resources based on resource id - // /posts/1?_embed=comments - embed(resource, _embed) - - // Expand inner resources based on id - // /posts/1?_expand=user - expand(resource, _expand) - - res.locals.data = resource - } - - next() - } - - // POST /name - function create (req, res, next) { - for (var key in req.body) { - req.body[key] = utils.toNative(req.body[key]) - } - - var resource = db(name) - .insert(req.body) - - res.status(201) - res.locals.data = resource - next() - } - - // PUT /name/:id - // PATCH /name/:id - function update (req, res, next) { - for (var key in req.body) { - req.body[key] = utils.toNative(req.body[key]) - } - - var resource = db(name) - .updateById(utils.toNative(req.params.id), req.body) - - if (resource) { - res.locals.data = resource - } - - next() - } - - // DELETE /name/:id - function destroy (req, res, next) { - db(name).removeById(utils.toNative(req.params.id)) - - // Remove dependents documents - var removable = db._.getRemovable(db.object) - - _.each(removable, function (item) { - db(item.name).removeById(item.id) - }) - - res.locals.data = {} - next() - } - - router.route('/') - .get(list) - .post(create) - - router.route('/:id') - .get(show) - .put(update) - .patch(update) - .delete(destroy) - - return router -} diff --git a/src/server/router/singular.js b/src/server/router/singular.js deleted file mode 100644 index 8c38ce9ca..000000000 --- a/src/server/router/singular.js +++ /dev/null @@ -1,40 +0,0 @@ -var express = require('express') -var utils = require('../utils') - -module.exports = function (db, name) { - - var router = express.Router() - - function show (req, res, next) { - res.locals.data = db.object[name] - next() - } - - function create (req, res, next) { - for (var prop in req.body) { - req.body[prop] = utils.toNative(req.body[prop]) - } - - res.locals.data = db.object[name] = req.body - res.status(201) - next() - } - - function update (req, res, next) { - for (var prop in req.body) { - db.object[name][prop] = utils.toNative(req.body[prop]) - } - - res.locals.data = db.object[name] - next() - } - - router.route('/') - .get(show) - .post(create) - .put(update) - .patch(update) - - return router - -} diff --git a/src/server/utils.js b/src/server/utils.js deleted file mode 100644 index 397e2ffc4..000000000 --- a/src/server/utils.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - toNative: toNative -} - -// Turns string to native. -// Example: -// 'true' -> true -// '1' -> 1 -function toNative (value) { - if (typeof value === 'string') { - if (value === '' - || value.trim() !== value - || (value.length > 1 && value[0] === '0')) { - return value - } else if (value === 'true' || value === 'false') { - return value === 'true' - } else if (!isNaN(+value)) { - return +value - } - } - return value -} diff --git a/src/service.test.ts b/src/service.test.ts new file mode 100644 index 000000000..818a2457b --- /dev/null +++ b/src/service.test.ts @@ -0,0 +1,392 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { Low, Memory } from 'lowdb' + +import { Data, Item, PaginatedItems, Service } from './service.js' + +const defaultData = { posts: [], comments: [], object: {} } +const adapter = new Memory() +const db = new Low(adapter, defaultData) +const service = new Service(db) + +const POSTS = 'posts' +const COMMENTS = 'comments' +const OBJECT = 'object' + +const UNKNOWN_RESOURCE = 'xxx' +const UNKNOWN_ID = 'xxx' + +const post1 = { + id: '1', + title: 'a', + views: 100, + published: true, + author: { name: 'foo' }, + tags: ['foo', 'bar'], +} +const post2 = { + id: '2', + title: 'b', + views: 200, + published: false, + author: { name: 'bar' }, + tags: ['bar'], +} +const post3 = { + id: '3', + title: 'c', + views: 300, + published: false, + author: { name: 'baz' }, + tags: ['foo'], +} +const comment1 = { id: '1', title: 'a', postId: '1' } +const items = 3 + +const obj = { + f1: 'foo', +} + +function reset() { + db.data = structuredClone({ + posts: [post1, post2, post3], + comments: [comment1], + object: obj, + }) +} + +await test('constructor', () => { + const defaultData = { posts: [{ id: '1' }, {}], object: {} } satisfies Data + const db = new Low(adapter, defaultData) + new Service(db) + if (Array.isArray(db.data['posts'])) { + const id0 = db.data['posts']?.at(0)?.['id'] + const id1 = db.data['posts']?.at(1)?.['id'] + assert.ok( + typeof id1 === 'string' && id1.length > 0, + `id should be a non empty string but was: ${String(id1)}`, + ) + assert.ok( + typeof id0 === 'string' && id0 === '1', + `id should not change if already set but was: ${String(id0)}`, + ) + } +}) + +await test('findById', () => { + reset() + if (!Array.isArray(db.data?.[POSTS])) + throw new Error('posts should be an array') + assert.deepEqual(service.findById(POSTS, '1', {}), db.data?.[POSTS]?.[0]) + assert.equal(service.findById(POSTS, UNKNOWN_ID, {}), undefined) + assert.deepEqual(service.findById(POSTS, '1', { _embed: ['comments'] }), { + ...post1, + comments: [comment1], + }) + assert.deepEqual(service.findById(COMMENTS, '1', { _embed: ['post'] }), { + ...comment1, + post: post1, + }) + assert.equal(service.findById(UNKNOWN_RESOURCE, '1', {}), undefined) +}) + +await test('find', async (t) => { + const arr: { + data?: Data + name: string + params?: Parameters[1] + res: Item | Item[] | PaginatedItems | undefined + error?: Error + }[] = [ + { + name: POSTS, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { id: post1.id }, + res: [post1], + }, + { + name: POSTS, + params: { id: UNKNOWN_ID }, + res: [], + }, + { + name: POSTS, + params: { views: post1.views.toString() }, + res: [post1], + }, + { + name: POSTS, + params: { 'author.name': post1.author.name }, + res: [post1], + }, + { + name: POSTS, + params: { 'tags[0]': 'foo' }, + res: [post1, post3], + }, + { + name: POSTS, + params: { id: UNKNOWN_ID, views: post1.views.toString() }, + res: [], + }, + { + name: POSTS, + params: { views_ne: post1.views.toString() }, + res: [post2, post3], + }, + { + name: POSTS, + params: { views_lt: (post1.views + 1).toString() }, + res: [post1], + }, + { + name: POSTS, + params: { views_lt: post1.views.toString() }, + res: [], + }, + { + name: POSTS, + params: { views_lte: post1.views.toString() }, + res: [post1], + }, + { + name: POSTS, + params: { views_gt: post1.views.toString() }, + res: [post2, post3], + }, + { + name: POSTS, + params: { views_gt: (post1.views - 1).toString() }, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { views_gte: post1.views.toString() }, + res: [post1, post2, post3], + }, + { + name: POSTS, + params: { + views_gt: post1.views.toString(), + views_lt: post3.views.toString(), + }, + res: [post2], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: 'views' }, + res: [post1, post2, post3], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: '-views' }, + res: [post3, post2, post1], + }, + { + data: { posts: [post3, post1, post2] }, + name: POSTS, + params: { _sort: '-views,id' }, + res: [post3, post2, post1], + }, + { + name: POSTS, + params: { published: 'true' }, + res: [post1], + }, + { + name: POSTS, + params: { published: 'false' }, + res: [post2, post3], + }, + { + name: POSTS, + params: { views_lt: post3.views.toString(), published: 'false' }, + res: [post2], + }, + { + name: POSTS, + params: { _start: 0, _end: 2 }, + res: [post1, post2], + }, + { + name: POSTS, + params: { _start: 1, _end: 3 }, + res: [post2, post3], + }, + { + name: POSTS, + params: { _start: 0, _limit: 2 }, + res: [post1, post2], + }, + { + name: POSTS, + params: { _start: 1, _limit: 2 }, + res: [post2, post3], + }, + { + name: POSTS, + params: { _page: 1, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: null, + next: 2, + pages: 2, + items, + data: [post1, post2], + }, + }, + { + name: POSTS, + params: { _page: 2, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: 1, + next: null, + pages: 2, + items, + data: [post3], + }, + }, + { + name: POSTS, + params: { _page: 3, _per_page: 2 }, + res: { + first: 1, + last: 2, + prev: 1, + next: null, + pages: 2, + items, + data: [post3], + }, + }, + { + name: POSTS, + params: { _page: 2, _per_page: 1 }, + res: { + first: 1, + last: 3, + prev: 1, + next: 3, + pages: 3, + items, + data: [post2], + }, + }, + { + name: POSTS, + params: { _embed: ['comments'] }, + res: [ + { ...post1, comments: [comment1] }, + { ...post2, comments: [] }, + { ...post3, comments: [] }, + ], + }, + { + name: COMMENTS, + params: { _embed: ['post'] }, + res: [{ ...comment1, post: post1 }], + }, + { + name: UNKNOWN_RESOURCE, + res: undefined, + }, + { + name: OBJECT, + res: obj, + }, + ] + for (const tc of arr) { + await t.test(`${tc.name} ${JSON.stringify(tc.params)}`, () => { + if (tc.data) { + db.data = tc.data + } else { + reset() + } + + assert.deepEqual(service.find(tc.name, tc.params), tc.res) + }) + } +}) + +await test('create', async () => { + reset() + const post = { title: 'new post' } + const res = await service.create(POSTS, post) + assert.equal(res?.['title'], post.title) + assert.equal(typeof res?.['id'], 'string', 'id should be a string') + + assert.equal(await service.create(UNKNOWN_RESOURCE, post), undefined) +}) + +await test('update', async () => { + reset() + const obj = { f1: 'bar' } + const res = await service.update(OBJECT, obj) + assert.equal(res, obj) + + assert.equal( + await service.update(UNKNOWN_RESOURCE, obj), + undefined, + 'should ignore unknown resources', + ) + assert.equal( + await service.update(POSTS, {}), + undefined, + 'should ignore arrays', + ) +}) + +await test('updateById', async () => { + reset() + const post = { id: 'xxx', title: 'updated post' } + const res = await service.updateById(POSTS, post1.id, post) + assert.equal(res?.['id'], post1.id, 'id should not change') + assert.equal(res?.['title'], post.title) + + assert.equal( + await service.updateById(UNKNOWN_RESOURCE, post1.id, post), + undefined, + ) + assert.equal(await service.updateById(POSTS, UNKNOWN_ID, post), undefined) +}) + +await test('patchById', async () => { + reset() + const post = { id: 'xxx', title: 'updated post' } + const res = await service.patchById(POSTS, post1.id, post) + assert.notEqual(res, undefined) + assert.equal(res?.['id'], post1.id) + assert.equal(res?.['title'], post.title) + + assert.equal( + await service.patchById(UNKNOWN_RESOURCE, post1.id, post), + undefined, + ) + assert.equal(await service.patchById(POSTS, UNKNOWN_ID, post), undefined) +}) + +await test('destroy', async () => { + reset() + let prevLength = Number(db.data?.[POSTS]?.length) || 0 + await service.destroyById(POSTS, post1.id) + assert.equal(db.data?.[POSTS]?.length, prevLength - 1) + assert.deepEqual(db.data?.[COMMENTS], [{ ...comment1, postId: null }]) + + reset() + prevLength = db.data?.[POSTS]?.length || 0 + await service.destroyById(POSTS, post1.id, [COMMENTS]) + assert.equal(db.data[POSTS].length, prevLength - 1) + assert.equal(db.data[COMMENTS].length, 0) + + assert.equal(await service.destroyById(UNKNOWN_RESOURCE, post1.id), undefined) + assert.equal(await service.destroyById(POSTS, UNKNOWN_ID), undefined) +}) diff --git a/src/service.ts b/src/service.ts new file mode 100644 index 000000000..bb7a63f3b --- /dev/null +++ b/src/service.ts @@ -0,0 +1,461 @@ +import { randomBytes } from 'node:crypto' + +import { getProperty } from 'dot-prop' +import inflection from 'inflection' +import { Low } from 'lowdb' +import sortOn from 'sort-on' + +export type Item = Record + +export type Data = Record + +export function isItem(obj: unknown): obj is Item { + return typeof obj === 'object' && obj !== null +} + +export function isData(obj: unknown): obj is Record { + if (typeof obj !== 'object' || obj === null) { + return false + } + + const data = obj as Record + return Object.values(data).every( + (value) => Array.isArray(value) && value.every(isItem), + ) +} + +enum Condition { + lt = 'lt', + lte = 'lte', + gt = 'gt', + gte = 'gte', + ne = 'ne', + default = '', +} + +function isCondition(value: string): value is Condition { + return Object.values(Condition).includes(value) +} + +export type PaginatedItems = { + first: number + prev: number | null + next: number | null + last: number + pages: number + items: number + data: Item[] +} + +function ensureArray(arg: string | string[] = []): string[] { + return Array.isArray(arg) ? arg : [arg] +} + +function embed(db: Low, name: string, item: Item, related: string): Item { + if (inflection.singularize(related) === related) { + const relatedData = db.data[inflection.pluralize(related)] as Item[] + if (!relatedData) { + return item + } + const foreignKey = `${related}Id` + const relatedItem = relatedData.find((relatedItem: Item) => { + return relatedItem['id'] === item[foreignKey] + }) + return { ...item, [related]: relatedItem } + } + const relatedData: Item[] = db.data[related] as Item[] + + if (!relatedData) { + return item + } + + const foreignKey = `${inflection.singularize(name)}Id` + const relatedItems = relatedData.filter( + (relatedItem: Item) => relatedItem[foreignKey] === item['id'], + ) + + return { ...item, [related]: relatedItems } +} + +function nullifyForeignKey(db: Low, name: string, id: string) { + const foreignKey = `${inflection.singularize(name)}Id` + + Object.entries(db.data).forEach(([key, items]) => { + // Skip + if (key === name) return + + // Nullify + if (Array.isArray(items)) { + items.forEach((item) => { + if (item[foreignKey] === id) { + item[foreignKey] = null + } + }) + } + }) +} + +function deleteDependents(db: Low, name: string, dependents: string[]) { + const foreignKey = `${inflection.singularize(name)}Id` + + Object.entries(db.data).forEach(([key, items]) => { + // Skip + if (key === name || !dependents.includes(key)) return + + // Delete if foreign key is null + if (Array.isArray(items)) { + db.data[key] = items.filter((item) => item[foreignKey] !== null) + } + }) +} + +function randomId(): string { + return randomBytes(2).toString('hex') +} + +function fixItemsIds(items: Item[]) { + items.forEach((item) => { + if (typeof item['id'] === 'number') { + item['id'] = item['id'].toString() + } + if (item['id'] === undefined) { + item['id'] = randomId() + } + }) +} + +// Ensure all items have an id +function fixAllItemsIds(data: Data) { + Object.values(data).forEach((value) => { + if (Array.isArray(value)) { + fixItemsIds(value) + } + }) +} + +export class Service { + #db: Low + + constructor(db: Low) { + fixAllItemsIds(db.data) + this.#db = db + } + + #get(name: string): Item[] | Item | undefined { + return this.#db.data[name] + } + + has(name: string): boolean { + return Object.prototype.hasOwnProperty.call(this.#db?.data, name) + } + + findById( + name: string, + id: string, + query: { _embed?: string[] | string }, + ): Item | undefined { + const value = this.#get(name) + + if (Array.isArray(value)) { + let item = value.find((item) => item['id'] === id) + ensureArray(query._embed).forEach((related) => { + if (item !== undefined) item = embed(this.#db, name, item, related) + }) + return item + } + + return + } + + find( + name: string, + query: { + [key: string]: unknown + _embed?: string | string[] + _sort?: string + _start?: number + _end?: number + _limit?: number + _page?: number + _per_page?: number + } = {}, + ): Item[] | PaginatedItems | Item | undefined { + let items = this.#get(name) + + if (!Array.isArray(items)) { + return items + } + + // Include + ensureArray(query._embed).forEach((related) => { + if (items !== undefined && Array.isArray(items)) { + items = items.map((item) => embed(this.#db, name, item, related)) + } + }) + + // Return list if no query params + if (Object.keys(query).length === 0) { + return items + } + + // Convert query params to conditions + const conds: [string, Condition, string | string[]][] = [] + for (const [key, value] of Object.entries(query)) { + if (value === undefined || typeof value !== 'string') { + continue + } + const re = /_(lt|lte|gt|gte|ne)$/ + const reArr = re.exec(key) + const op = reArr?.at(1) + if (op && isCondition(op)) { + const field = key.replace(re, '') + conds.push([field, op, value]) + continue + } + if ( + [ + '_embed', + '_sort', + '_start', + '_end', + '_limit', + '_page', + '_per_page', + ].includes(key) + ) { + continue + } + conds.push([key, Condition.default, value]) + } + + // Loop through conditions and filter items + let filtered = items + for (const [key, op, paramValue] of conds) { + filtered = filtered.filter((item: Item) => { + if (paramValue && !Array.isArray(paramValue)) { + // https://github.com/sindresorhus/dot-prop/issues/95 + const itemValue: unknown = getProperty(item, key) + switch (op) { + // item_gt=value + case Condition.gt: { + if ( + !( + typeof itemValue === 'number' && + itemValue > parseInt(paramValue) + ) + ) { + return false + } + break + } + // item_gte=value + case Condition.gte: { + if ( + !( + typeof itemValue === 'number' && + itemValue >= parseInt(paramValue) + ) + ) { + return false + } + break + } + // item_lt=value + case Condition.lt: { + if ( + !( + typeof itemValue === 'number' && + itemValue < parseInt(paramValue) + ) + ) { + return false + } + break + } + // item_lte=value + case Condition.lte: { + if ( + !( + typeof itemValue === 'number' && + itemValue <= parseInt(paramValue) + ) + ) { + return false + } + break + } + // item_ne=value + case Condition.ne: { + switch (typeof itemValue) { + case 'number': + return itemValue !== parseInt(paramValue) + case 'string': + return itemValue !== paramValue + case 'boolean': + return itemValue !== (paramValue === 'true') + } + break + } + // item=value + case Condition.default: { + switch (typeof itemValue) { + case 'number': + return itemValue === parseInt(paramValue) + case 'string': + return itemValue === paramValue + case 'boolean': + return itemValue === (paramValue === 'true') + case 'undefined': + return false + } + } + } + } + return true + }) + } + + // Sort + const sort = query._sort || '' + const sorted = sortOn(filtered, sort.split(',')) + + // Slice + const start = query._start + const end = query._end + const limit = query._limit + if (start !== undefined) { + if (end !== undefined) { + return sorted.slice(start, end) + } + return sorted.slice(start, start + (limit || 0)) + } + if (limit !== undefined) { + return sorted.slice(0, limit) + } + + // Paginate + let page = query._page + const perPage = query._per_page || 10 + if (page) { + const items = sorted.length + const pages = Math.ceil(items / perPage) + + // Ensure page is within the valid range + page = Math.max(1, Math.min(page, pages)) + + const first = 1 + const prev = page > 1 ? page - 1 : null + const next = page < pages ? page + 1 : null + const last = pages + + const start = (page - 1) * perPage + const end = start + perPage + const data = sorted.slice(start, end) + + return { + first, + prev, + next, + last, + pages, + items, + data, + } + } + + return sorted.slice(start, end) + } + + async create( + name: string, + data: Omit = {}, + ): Promise { + const items = this.#get(name) + if (items === undefined || !Array.isArray(items)) return + + const item = { id: randomId(), ...data } + items.push(item) + + await this.#db.write() + return item + } + + async #updateOrPatch( + name: string, + body: Item = {}, + isPatch: boolean, + ): Promise { + const item = this.#get(name) + if (item === undefined || Array.isArray(item)) return + + const nextItem = (this.#db.data[name] = isPatch ? { item, ...body } : body) + + await this.#db.write() + return nextItem + } + + async #updateOrPatchById( + name: string, + id: string, + body: Item = {}, + isPatch: boolean, + ): Promise { + const items = this.#get(name) + if (items === undefined || !Array.isArray(items)) return + + const item = items.find((item) => item['id'] === id) + if (!item) return + + const nextItem = isPatch ? { ...item, ...body, id } : { ...body, id } + const index = items.indexOf(item) + items.splice(index, 1, nextItem) + + await this.#db.write() + return nextItem + } + + async update(name: string, body: Item = {}): Promise { + return this.#updateOrPatch(name, body, false) + } + + async patch(name: string, body: Item = {}): Promise { + return this.#updateOrPatch(name, body, true) + } + + async updateById( + name: string, + id: string, + body: Item = {}, + ): Promise { + return this.#updateOrPatchById(name, id, body, false) + } + + async patchById( + name: string, + id: string, + body: Item = {}, + ): Promise { + return this.#updateOrPatchById(name, id, body, true) + } + + async destroyById( + name: string, + id: string, + dependent?: string | string[], + ): Promise { + const items = this.#get(name) + if (items === undefined || !Array.isArray(items)) return + + const item = items.find((item) => item['id'] === id) + if (item === undefined) return + const index = items.indexOf(item) + items.splice(index, 1) + + nullifyForeignKey(this.#db, name, id) + const dependents = ensureArray(dependent) + deleteDependents(this.#db, name, dependents) + + await this.#db.write() + return item + } +} diff --git a/test/cli/fixtures/public/index.html b/test/cli/fixtures/public/index.html deleted file mode 100644 index e965047ad..000000000 --- a/test/cli/fixtures/public/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello diff --git a/test/cli/fixtures/seed.js b/test/cli/fixtures/seed.js deleted file mode 100644 index 22f00eb54..000000000 --- a/test/cli/fixtures/seed.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function () { - return { posts: [] } -} diff --git a/test/cli/index.js b/test/cli/index.js deleted file mode 100644 index c8c968369..000000000 --- a/test/cli/index.js +++ /dev/null @@ -1,156 +0,0 @@ -var os = require('os') -var fs = require('fs') -var path = require('path') -var cp = require('child_process') -var request = require('supertest') -var rmrf = require('rimraf') -var serverReady = require('server-ready') -var pkg = require('../../package.json') - -var PORT = 3100 - -request = request('http://localhost/' + PORT) - -var tmpDir = path.join(__dirname, '../../tmp') -var dbFile = path.join(tmpDir, 'db.json') -var routesFile = path.join(tmpDir, 'routes.json') - -function cli (args) { - var bin = path.join(__dirname, '../..', pkg.bin) - return cp.spawn('node', [bin, '-p', PORT].concat(args), { - stdio: 'inherit', - cwd: __dirname - }) -} - -/* global beforeEach, afterEach, describe, it */ - -describe('cli', function () { - - var child - - beforeEach(function () { - fs.mkdirSync(tmpDir) - fs.writeFileSync(dbFile, JSON.stringify({ posts: [{ 'id': 1, '_id': 2 }] })) - fs.writeFileSync(routesFile, JSON.stringify({ '/blog/': '/' })) - }) - - afterEach(function (done) { - rmrf.sync(tmpDir) - child.kill() - setTimeout(done, 1000) - }) - - describe('db.json', function () { - - beforeEach(function (done) { - child = cli([dbFile]) - serverReady(PORT, done) - }) - - it('should support JSON dbFile', function (done) { - request.get('/posts').expect(200, done) - }) - - }) - - describe('seed.js', function () { - - beforeEach(function (done) { - child = cli(['fixtures/seed.js']) - serverReady(PORT, done) - }) - - it('should support JS file', function (done) { - request.get('/posts').expect(200, done) - }) - - }) - - describe('http://jsonplaceholder.typicode.com/db', function () { - - beforeEach(function (done) { - child = cli(['http://jsonplaceholder.typicode.com/db']) - this.timeout(10000) - serverReady(PORT, done) - }) - - it('should support URL file', function (done) { - request.get('/posts').expect(200, done) - }) - - }) - - describe('db.json -r routes.json -i _id', function () { - - beforeEach(function (done) { - child = cli([dbFile, '-r', routesFile, '-i', '_id']) - serverReady(PORT, done) - }) - - it('should use routes.json and _id as the identifier', function (done) { - request.get('/blog/posts/2').expect(200, done) - }) - - }) - - describe('db.json -d 1000', function () { - - beforeEach(function (done) { - child = cli([dbFile, '-d', 1000]) - serverReady(PORT, done) - }) - - it('should delay response', function (done) { - var start = new Date() - request.get('/posts').expect(200, function (err) { - var end = new Date() - done(end - start > 1000 ? err : new Error('Request wasn\'t delayed')) - }) - }) - - }) - - describe('db.json -s fixtures/public', function () { - - beforeEach(function (done) { - child = cli([dbFile, '-s', 'fixtures/public']) - serverReady(PORT, done) - }) - - it('should serve fixtures/public', function (done) { - request.get('/').expect(/Hello/, done) - }) - - }) - - // FIXME test fails on OS X and maybe on Windows - // But manually updating db.json works... - if (os.platform() === 'linux') { - describe('--watch db.json -r routes.json', function () { - - beforeEach(function (done) { - child = cli(['--watch', dbFile, '-r', routesFile]) - serverReady(PORT, done) - }) - - it('should watch db file', function (done) { - fs.writeFileSync(dbFile, JSON.stringify({ foo: [] })) - setTimeout(function () { - request.get('/foo').expect(200, done) - }, 1000) - }) - - it('should watch routes file', function (done) { - // Can be very slow - this.timeout(10000) - fs.writeFileSync(routesFile, JSON.stringify({ '/api/': '/' })) - setTimeout(function () { - request.get('/api/posts').expect(200, done) - }, 9000) - }) - - }) - } - -}) diff --git a/test/server/mixins.js b/test/server/mixins.js deleted file mode 100644 index e632736a6..000000000 --- a/test/server/mixins.js +++ /dev/null @@ -1,38 +0,0 @@ -var assert = require('assert') -var _ = require('lodash') -var _db = require('underscore-db') -var mixins = require('../../src/server/mixins') - -/* global describe, it */ - -describe('mixins', function () { - - describe('getRemovable', function () { - - it('should return removable documents', function () { - - var db = { - posts: [ - {id: 1, comment: 1} - ], - comments: [ - {id: 1, postId: 1}, - // Comments below references a post that doesn't exist - {id: 2, postId: 2}, - {id: 3, postId: 2} - ] - } - - var expected = [ - {name: 'comments', id: 2}, - {name: 'comments', id: 3} - ] - - _.mixin(_db) - _.mixin(mixins) - - assert.deepEqual(_.getRemovable(db), expected) - - }) - }) -}) diff --git a/test/server/plural.js b/test/server/plural.js deleted file mode 100644 index 0585d025b..000000000 --- a/test/server/plural.js +++ /dev/null @@ -1,577 +0,0 @@ -var assert = require('assert') -var _ = require('lodash') -var request = require('supertest') -var jsonServer = require('../../src/server') - -/* global beforeEach, describe, it */ - -describe('Server', function () { - - var server - var router - var db - - beforeEach(function () { - db = {} - - db.posts = [ - {id: 1, body: 'foo'}, - {id: 2, body: 'bar'} - ] - - db.tags = [ - {id: 1, body: 'Technology'}, - {id: 2, body: 'Photography'}, - {id: 3, body: 'photo'} - ] - - db.users = [ - {id: 1, username: 'Jim'}, - {id: 2, username: 'George'} - ] - - db.comments = [ - {id: 1, body: 'foo', published: true, postId: 1, userId: 1}, - {id: 2, body: 'bar', published: false, postId: 1, userId: 2}, - {id: 3, body: 'baz', published: false, postId: 2, userId: 1}, - {id: 4, body: 'qux', published: true, postId: 2, userId: 2}, - {id: 5, body: 'quux', published: false, postId: 2, userId: 1} - ] - - db.refs = [ - {id: 'abcd-1234', url: 'http://example.com/', postId: 1, userId: 1} - ] - - db.deep = [ - { a: { b: 1 } }, - { a: 1 } - ] - - server = jsonServer.create() - router = jsonServer.router(db) - server.use(jsonServer.defaults()) - server.use(jsonServer.rewriter({ - '/api/': '/', - '/blog/posts/:id/show': '/posts/:id' - })) - server.use(router) - }) - - describe('GET /db', function () { - it('should respond with json and full database', function (done) { - request(server) - .get('/db') - .expect('Content-Type', /json/) - .expect(db) - .expect(200, done) - }) - }) - - describe('GET /:resource', function () { - it('should respond with json and corresponding resources', function (done) { - request(server) - .get('/posts') - .set('Origin', 'http://example.com/') - .expect('Content-Type', /json/) - .expect('Access-Control-Allow-Credentials', 'true') - .expect('Access-Control-Allow-Origin', 'http://example.com/') - .expect(db.posts) - .expect(200, done) - }) - - it('should respond with 404 if resource is not found', function (done) { - request(server) - .get('/undefined') - .expect(404, done) - }) - }) - - describe('GET /:resource?attr=&attr=', function () { - it('should respond with json and filter resources', function (done) { - request(server) - .get('/comments?postId=1&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[0]]) - .expect(200, done) - }) - - it('should support multiple filters', function (done) { - request(server) - .get('/comments?id=1&id=2') - .expect('Content-Type', /json/) - .expect([db.comments[0], db.comments[1]]) - .expect(200, done) - }) - - it('should support deep filter', function (done) { - request(server) - .get('/deep?a.b=1') - .expect('Content-Type', /json/) - .expect([db.deep[0]]) - .expect(200, done) - }) - - it('should ignore JSONP query parameters callback and _ ', function (done) { - request(server) - .get('/comments?callback=1&_=1') - .expect('Content-Type', /text/) - .expect(new RegExp(db.comments[0].body)) // JSONP returns text - .expect(200, done) - }) - - it('should ignore unknown query parameters', function (done) { - request(server) - .get('/comments?foo=1&bar=2') - .expect('Content-Type', /json/) - .expect(db.comments) - .expect(200, done) - }) - }) - - describe('GET /:resource?q=', function () { - it('should respond with json and make a full-text search', function (done) { - request(server) - .get('/tags?q=pho') - .expect('Content-Type', /json/) - .expect([db.tags[1], db.tags[2]]) - .expect(200, done) - }) - - it('should respond with json and make a deep full-text search', function (done) { - request(server) - .get('/deep?q=1') - .expect('Content-Type', /json/) - .expect(db.deep) - .expect(200, done) - }) - - it('should return an empty array when nothing is matched', function (done) { - request(server) - .get('/tags?q=nope') - .expect('Content-Type', /json/) - .expect([]) - .expect(200, done) - }) - - it('should support other query parameters', function (done) { - request(server) - .get('/comments?q=qu&published=true') - .expect('Content-Type', /json/) - .expect([db.comments[3]]) - .expect(200, done) - }) - }) - - describe('GET /:resource?_end=', function () { - it('should respond with a sliced array', function (done) { - request(server) - .get('/comments?_end=2') - .expect('Content-Type', /json/) - .expect('x-total-count', db.comments.length.toString()) - .expect('Access-Control-Expose-Headers', 'X-Total-Count') - .expect(db.comments.slice(0, 2)) - .expect(200, done) - }) - }) - - describe('GET /:resource?_sort=', function () { - it('should respond with json and sort on a field', function (done) { - request(server) - .get('/tags?_sort=body') - .expect('Content-Type', /json/) - .expect([db.tags[1], db.tags[0], db.tags[2]]) - .expect(200, done) - }) - - it('should reverse sorting with _order=DESC', function (done) { - request(server) - .get('/tags?_sort=body&_order=DESC') - .expect('Content-Type', /json/) - .expect([db.tags[2], db.tags[0], db.tags[1]]) - .expect(200, done) - }) - - it('should sort on numerical field', function (done) { - request(server) - .get('/posts?_sort=id&_order=DESC') - .expect('Content-Type', /json/) - .expect(db.posts.reverse()) - .expect(200, done) - }) - }) - - describe('GET /:resource?_start=&_end=', function () { - it('should respond with a sliced array', function (done) { - request(server) - .get('/comments?_start=1&_end=2') - .expect('Content-Type', /json/) - .expect('x-total-count', db.comments.length.toString()) - .expect('Access-Control-Expose-Headers', 'X-Total-Count') - .expect(db.comments.slice(1, 2)) - .expect(200, done) - }) - }) - - describe('GET /:resource?_start=&_limit=', function () { - it('should respond with a limited array', function (done) { - request(server) - .get('/comments?_start=1&_limit=1') - .expect('Content-Type', /json/) - .expect('x-total-count', db.comments.length.toString()) - .expect('Access-Control-Expose-Headers', 'X-Total-Count') - .expect(db.comments.slice(1, 2)) - .expect(200, done) - }) - }) - - describe('GET /:resource?attr>=&attr<=', function () { - it('should respond with a limited array', function (done) { - request(server) - .get('/comments?id_gte=2&id_lte=3') - .expect('Content-Type', /json/) - .expect(db.comments.slice(1, 3)) - .expect(200, done) - }) - }) - - describe('GET /:parent/:parentId/:resource', function () { - it('should respond with json and corresponding nested resources', function (done) { - request(server) - .get('/posts/1/comments') - .expect('Content-Type', /json/) - .expect([ - db.comments[0], - db.comments[1] - ]) - .expect(200, done) - }) - }) - - describe('GET /:resource/:id', function () { - it('should respond with json and corresponding resource', function (done) { - request(server) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(db.posts[0]) - .expect(200, done) - }) - - it('should support string id, respond with json and corresponding resource', function (done) { - request(server) - .get('/refs/abcd-1234') - .expect('Content-Type', /json/) - .expect(db.refs[0]) - .expect(200, done) - }) - - it('should respond with 404 if resource is not found', function (done) { - request(server) - .get('/posts/9001') - .expect('Content-Type', /json/) - .expect({}) - .expect(404, done) - }) - }) - - describe('GET /:resource?_embed=', function () { - it('should respond with corresponding resources and embedded resources', function (done) { - var posts = _.cloneDeep(db.posts) - posts[0].comments = [db.comments[0], db.comments[1]] - posts[1].comments = [db.comments[2], db.comments[3], db.comments[4]] - request(server) - .get('/posts?_embed=comments') - .expect('Content-Type', /json/) - .expect(posts) - .expect(200, done) - }) - }) - - describe('GET /:resource?_embed&_embed=', function () { - it('should respond with corresponding resources and embedded resources', function (done) { - var posts = _.cloneDeep(db.posts) - posts[0].comments = [db.comments[0], db.comments[1]] - posts[0].refs = [db.refs[0]] - posts[1].comments = [db.comments[2], db.comments[3], db.comments[4]] - posts[1].refs = [] - request(server) - .get('/posts?_embed=comments&_embed=refs') - .expect('Content-Type', /json/) - .expect(posts) - .expect(200, done) - }) - }) - - describe('GET /:resource/:id?_embed=', function () { - it('should respond with corresponding resources and embedded resources', function (done) { - var posts = db.posts[0] - posts.comments = [db.comments[0], db.comments[1]] - request(server) - .get('/posts/1?_embed=comments') - .expect('Content-Type', /json/) - .expect(posts) - .expect(200, done) - }) - }) - - describe('GET /:resource/:id?_embed=&_embed=', function () { - it('should respond with corresponding resource and embedded resources', function (done) { - var posts = db.posts[0] - posts.comments = [db.comments[0], db.comments[1]] - posts.refs = [db.refs[0]] - request(server) - .get('/posts/1?_embed=comments&_embed=refs') - .expect('Content-Type', /json/) - .expect(posts) - .expect(200, done) - }) - }) - - describe('GET /:resource?_expand=', function () { - it('should respond with corresponding resource and expanded inner resources', function (done) { - var refs = _.cloneDeep(db.refs) - refs[0].post = db.posts[0] - request(server) - .get('/refs?_expand=post') - .expect('Content-Type', /json/) - .expect(refs) - .expect(200, done) - }) - }) - - describe('GET /:resource/:id?_expand=', function () { - it('should respond with corresponding resource and expanded inner resources', function (done) { - var comments = db.comments[0] - comments.post = db.posts[0] - request(server) - .get('/comments/1?_expand=post') - .expect('Content-Type', /json/) - .expect(comments) - .expect(200, done) - }) - }) - - describe('GET /:resource?_expand=&_expand', function () { - it('should respond with corresponding resource and expanded inner resources', function (done) { - var refs = _.cloneDeep(db.refs) - refs[0].post = db.posts[0] - refs[0].user = db.users[0] - request(server) - .get('/refs?_expand=post&_expand=user') - .expect('Content-Type', /json/) - .expect(refs) - .expect(200, done) - }) - }) - - describe('GET /:resource/:id?_expand=&_expand=', function () { - it('should respond with corresponding resource and expanded inner resources', function (done) { - var comments = db.comments[0] - comments.post = db.posts[0] - comments.user = db.users[0] - request(server) - .get('/comments/1?_expand=post&_expand=user') - .expect('Content-Type', /json/) - .expect(comments) - .expect(200, done) - }) - }) - - describe('POST /:resource', function () { - it('should respond with json, create a resource and increment id', - function (done) { - request(server) - .post('/posts') - .send({body: 'foo', booleanValue: 'true', integerValue: '1'}) - .expect('Content-Type', /json/) - .expect({id: 3, body: 'foo', booleanValue: true, integerValue: 1}) - .expect(201) - .end(function (err, res) { - if (err) return done(err) - assert.equal(db.posts.length, 3) - done() - }) - }) - - it('should respond with json, create a resource and generate string id', - function (done) { - request(server) - .post('/refs') - .send({url: 'http://foo.com/', postId: '1'}) - .expect('Content-Type', /json/) - .expect(201) - .end(function (err, res) { - if (err) return done(err) - assert.equal(db.refs.length, 2) - done() - }) - }) - }) - - describe('PUT /:resource/:id', function () { - it('should respond with json and update resource', function (done) { - request(server) - .put('/posts/1') - .send({id: 1, body: 'bar', booleanValue: 'true', integerValue: '1'}) - .expect('Content-Type', /json/) - .expect({id: 1, body: 'bar', booleanValue: true, integerValue: 1}) - .expect(200) - .end(function (err, res) { - if (err) return done(err) - // assert it was created in database too - assert.deepEqual(db.posts[0], {id: 1, body: 'bar', booleanValue: true, integerValue: 1}) - done() - }) - }) - - it('should respond with 404 if resource is not found', function (done) { - request(server) - .put('/posts/9001') - .send({id: 1, body: 'bar', booleanValue: 'true', integerValue: '1'}) - .expect('Content-Type', /json/) - .expect({}) - .expect(404, done) - }) - }) - - describe('PATCH /:resource/:id', function () { - it('should respond with json and update resource', function (done) { - request(server) - .patch('/posts/1') - .send({body: 'bar'}) - .expect('Content-Type', /json/) - .expect({id: 1, body: 'bar'}) - .expect(200) - .end(function (err, res) { - if (err) return done(err) - // assert it was created in database too - assert.deepEqual(db.posts[0], {id: 1, body: 'bar'}) - done() - }) - }) - - it('should respond with 404 if resource is not found', function (done) { - request(server) - .patch('/posts/9001') - .send({body: 'bar'}) - .expect('Content-Type', /json/) - .expect({}) - .expect(404, done) - }) - }) - - describe('DELETE /:resource/:id', function () { - it('should respond with empty data, destroy resource and dependent resources', function (done) { - request(server) - .del('/posts/1') - .expect({}) - .expect(200) - .end(function (err, res) { - if (err) return done(err) - assert.equal(db.posts.length, 1) - assert.equal(db.comments.length, 3) - done() - }) - }) - }) - - describe('Static routes', function () { - - describe('GET /', function () { - it('should respond with html', function (done) { - request(server) - .get('/') - .expect(/You're successfully running JSON Server/) - .expect(200, done) - }) - }) - - describe('GET /stylesheets/style.css', function () { - it('should respond with css', function (done) { - request(server) - .get('/stylesheets/style.css') - .expect('Content-Type', /css/) - .expect(200, done) - }) - }) - - }) - - describe('Database #object', function () { - it('should be accessible', function () { - assert(router.db.object) - }) - }) - - describe('Responses', function () { - - it('should have no cache headers (for IE)', function (done) { - request(server) - .get('/db') - .expect('Cache-Control', 'no-cache') - .expect('Pragma', 'no-cache') - .expect('Expires', '-1') - .end(done) - }) - - }) - - describe('Rewriter', function () { - - it('should rewrite using prefix', function (done) { - request(server) - .get('/api/posts/1') - .expect(db.posts[0]) - .end(done) - }) - - it('should rewrite using params', function (done) { - request(server) - .get('/blog/posts/1/show') - .expect(db.posts[0]) - .end(done) - }) - - }) - - describe('router.render', function (done) { - - beforeEach(function () { - router.render = function (req, res) { - res.jsonp({ - data: res.locals.data - }) - } - }) - - it('should be possible to wrap response', function (done) { - request(server) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect({ data: db.posts[0] }) - .expect(200, done) - }) - - }) - - describe('router.db._.id', function (done) { - - beforeEach(function () { - router.db.object = { - posts: [ - { _id: 1 } - ] - } - - router.db._.id = '_id' - }) - - it('should be possible to override id property', function (done) { - request(server) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(router.db.object.posts[0]) - .expect(200, done) - }) - - }) -}) diff --git a/test/server/singular.js b/test/server/singular.js deleted file mode 100644 index 058f75ce5..000000000 --- a/test/server/singular.js +++ /dev/null @@ -1,67 +0,0 @@ -var request = require('supertest') -var jsonServer = require('../../src/server') - -/* global beforeEach, describe, it */ - -describe('Server', function () { - - var server - var router - var db - - beforeEach(function () { - db = {} - - db.user = { - name: 'foo', - email: 'foo@example.com' - } - - server = jsonServer.create() - router = jsonServer.router(db) - server.use(jsonServer.defaults()) - server.use(router) - }) - - describe('GET /:resource', function () { - it('should respond with corresponding resource', function (done) { - request(server) - .get('/user') - .expect(db.user) - .expect(200, done) - }) - }) - - describe('POST /:resource', function () { - it('should create resource', function (done) { - var user = { name: 'bar' } - request(server) - .post('/user') - .send(user) - .expect(user) - .expect(201, done) - }) - }) - - describe('PUT /:resource', function () { - it('should uptade resource', function (done) { - var user = { name: 'bar' } - request(server) - .put('/user') - .send(user) - .expect(db.user) - .expect(200, done) - }) - }) - - describe('PATCH /:resource', function () { - it('should uptade resource', function (done) { - request(server) - .patch('/user') - .send({ name: 'bar' }) - .expect({ name: 'bar', email: 'foo@example.com' }) - .expect(200, done) - }) - }) - -}) diff --git a/test/server/utils.js b/test/server/utils.js deleted file mode 100644 index e353804ee..000000000 --- a/test/server/utils.js +++ /dev/null @@ -1,27 +0,0 @@ -var assert = require('assert') -var utils = require('../../src/server/utils') - -/* global describe, it */ - -describe('utils', function () { - - describe('toNative', function () { - - it('should convert string to native type', function () { - // should convert - assert.strictEqual(utils.toNative('1'), 1) - assert.strictEqual(utils.toNative('0'), 0) - assert.strictEqual(utils.toNative('true'), true) - // should not convert - assert.strictEqual(utils.toNative(''), '') - assert.strictEqual(utils.toNative('\t\n'), '\t\n') - assert.strictEqual(utils.toNative('1 '), '1 ') - assert.strictEqual(utils.toNative('01'), '01') - assert.strictEqual(utils.toNative(' 1'), ' 1') - assert.strictEqual(utils.toNative('string'), 'string') - assert.strictEqual(utils.toNative(1), 1) - assert.strictEqual(utils.toNative(true), true) - }) - - }) -}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..0e8a67f34 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@sindresorhus/tsconfig", + "exclude": ["src/**/*.test.ts"], + "compilerOptions": { + "outDir": "./lib" + } +} diff --git a/views/index.html b/views/index.html new file mode 100644 index 000000000..96f63c8eb --- /dev/null +++ b/views/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + +
+ +
+
+

✧*。٩(ˊᗜˋ*)و✧*。

+ <% if (Object.keys(it.data).length===0) { %> +

No resources found in JSON file

+ <% } %> + <% Object.entries(it.data).forEach(function([name]) { %> +
    +
  • + /<%= name %> + + <% if (Array.isArray(it.data[name])) { %> + - <%= it.data[name].length %> + <%= it.data[name].length> 1 ? 'items' : 'item' %> + + <% } %> +
  • +
+ <% }) %> +
+ + + \ No newline at end of file