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 4beb07381..e0b3ff284 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/*.log -node_modules -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 e986bdd0f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -sudo: false -language: node_js -node_js: - - "stable" - - "0.12" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 8bb7972aa..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,142 +0,0 @@ -# Change Log - -## [0.8.14][2016-05-15] - -* Bug fix: data wasn't written to file in `v0.8.13` and `v0.8.12` - -## [0.8.13][2016-05-12] - -* Make `_like` operator case insensitive - -## [0.8.12][2016-05-08] - -* Minor bug fix - -## [0.8.11][2016-05-08] - -* Support sort by nested field (e.g. `_sort=author.name`) -* Fix `graceful-fs` warning - -## [0.8.10][2016-04-18] - -* CLI option `-ng/--no-gzip` to disable `gzip` compression - -## [0.8.9][2016-03-17] - -* CLI can now read options from `json-server.json` if present -* CLI option `-c/--config` to point to a different configuration file - -## [0.8.8][2016-02-13] - -### Fixed - -* Fix #233 - -## [0.8.7][2016-01-22] - -### Added - -* `gzip` compression to improve performances -* CLI option `-nc/--no-cors` to disable CORS - -## [0.8.6][2016-01-07] - -### Added - -* CLI option `-ro/--read-only` to allow only GET requests - -## [0.8.5][2015-12-28] - -### Fixed - -* Fix #177 - -## [0.8.4][2015-12-13] - -### Added - -* Like operator `GET /posts?title_like=json` (accepts RegExp) - -## [0.8.3][2015-11-25] - -### Added - -* CLI option `-q/--quiet` -* Nested route `POST /posts/1/comments` -* Not equal operator `GET /posts?id_ne=1` - -## [0.8.2][2015-10-15] - -### Added - -* CLI option `-S/--snapshots` to set a custom snapshots directory. - -### Fixed - -* Fix plural resources: `DELETE` should return `404` if resource doesn't exist. - -## [0.8.1][2015-10-06] - -### Fixed - -* Fix plural resources: `PUT` should replace resource instead of updating properties. -* Fix singular resources: `POST`, `PUT`, `PATCH` should not convert resource properties. - -## [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 ce53c0583..752515b57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,44 @@ -The MIT License (MIT) - -Copyright (c) 2015 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 4db2137ca..f98d1e9b7 100644 --- a/README.md +++ b/README.md @@ -1,413 +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: [hotel](https://github.com/typicode/hotel) - Local domains for everyone and more! -* :dog: [husky](https://github.com/typicode/husky) - Git hooks made easy +## 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 +View db.json5 example -```json -{ "id": 1, "title": "json-server", "author": "typicode" } -``` - -Also when doing requests, its good to know that -- 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). -- Your request body JSON should be object enclosed, just like the GET output. (for example `{"name": "Foobar"}`) -- Id values are not mutable. Any `id` value in the body of your PUT or PATCH request wil be ignored. Only a value set in a POST request wil be respected, but only if not already taken. -- A POST, PUT or PATCH request should include a `Content-Type: application/json` header to use the JSON in the request body. Otherwise it wil result in a 200 OK but without changes being made to the data. - -## Install - -```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 - -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 +You can read more about JSON5 format [here](https://github.com/json5/json5). -``` -GET /posts -GET /posts/1 -POST /posts -PUT /posts/1 -PATCH /posts/1 -DELETE /posts/1 -``` +
-### Singular routes +Pass it to JSON Server CLI +```shell +$ npx json-server db.json ``` -GET /profile -POST /profile -PUT /profile -PATCH /profile -``` - -### Filter -Use `.` to access deep properties +Get a REST API -``` -GET /posts?title=json-server&author=typicode -GET /posts?id=1&id=2 -GET /comments?author.name=typicode +```shell +$ curl http://localhost:3000/posts/1 +{ + "id": "1", + "title": "a title", + "views": 100 +} ``` -### Slice +Run `json-server --help` for a list of options -Add `_start` and `_end` or `_limit` (an `X-Total-Count` header is included in the response) +## Sponsors ✨ -``` -GET /posts?_start=20&_end=30 -GET /posts/1/comments?_start=20&_end=30 -GET /posts/1/comments?_start=20&_limit=10 -``` +### Gold -### Sort +|| +| :---: | +| | +| | +| | -Add `_sort` and `_order` (ascending order by default) +### Silver -``` -GET /posts?_sort=views&_order=DESC -GET /posts/1/comments?_sort=votes&_order=ASC -``` +|| +| :---: | +| | -### Operators +### Bronze -Add `_gte` or `_lte` for getting a range +||| +| :---: | :---: | +| | | -``` -GET /posts?views_gte=10&views_lte=20 -``` +[Become a sponsor and have your company logo here](https://github.com/users/typicode/sponsorship) -Add `_ne` to exclude a value +## Sponsorware -``` -GET /posts?id_ne=1 -``` - -Add `_like` to filter (RegExp supported) +> [!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/). -``` -GET /posts?title_like=server -``` - -### 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 or create nested resources (by default one level, [add routes](#add-routes) for more) +## Params -``` -GET /posts/1/comments -POST /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 +- `page` +- `per_page` (default = 10) -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 world' > public/index.html -json-server db.json ``` - -```bash -json-server db.json --static ./static +GET /posts?_page=1&_per_page=25 ``` -### Alternative port +### Sort -You can start JSON Server on other ports with the `--port` flag: +- `_sort=f1,f2` -```bash -$ json-server --watch db.json --port 3004 ``` - -### 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?_sort=id,-views ``` -### Generate random data +### Nested and array fields -Using JS instead of a JSON file, you can create data programmatically. +- `x.y.z...` +- `x.y.z[i]...` -```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 /foo?a.b=bar +GET /foo?x.y_lt=100 +GET /foo?arr[0]=bar ``` -__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 - -Create a `routes.json` file. Pay attention to start every route with /. +### Embed -```json -{ - "/api/": "/", - "/blog/:resource/:id/show": "/:resource/:id" -} ``` - -Start JSON Server with `--routes` option. - -```bash -json-server db.json --routes routes.json -``` - -Now you can access resources using additional routes. - -```bash -/api/posts -/api/posts/1 -/blog/posts/1/show +GET /posts?_embed=comments +GET /comments?_embed=post ``` -### CLI usage +## Delete ``` -json-server [options] - -Options: - --config, -c Path to config file [default: "json-server.json"] - --port, -p Set port [default: 3000] - --host, -H Set host [default: "0.0.0.0"] - --watch, -w Watch file(s) [boolean] - --routes, -r Path to routes file - --static, -s Set static files directory - --read-only, --ro Allow only GET requests [boolean] - --no-cors, --nc Disable Cross-Origin Resource Sharing [boolean] - --no-gzip, --ng Disable GZIP Content-Encoding [boolean] - --snapshots, -S Set snapshots directory [default: "."] - --delay, -d Add delay to responses (ms) - --id, -i Set database id property (e.g. _id) [default: "id"] - --quiet, -q Suppress log messages from output [boolean] - --help, -h Show help [boolean] - --version, -v Show version number [boolean] - -Examples: - bin db.json - bin file.js - bin http://example.com/db.json -``` - -You can also set options in a `json-server.json` configuration file. - -```json -{ - "port": 3000 -} +DELETE /posts/1 +DELETE /posts/1?_dependent=comments ``` -### Module +## Serving static files -If you need to add authentication, validation, or __any behavior__, you can use the project as a module in combination with other Express middlewares. +If you create a `./public` directory, JSON Server will serve its content in addition to the REST API. -__Simple example__ - -```js -// server.js -var jsonServer = require('json-server') -var server = jsonServer.create() -var router = jsonServer.router('db.json') -var middlewares = jsonServer.defaults() - -server.use(middlewares) -server.use(router) -server.listen(3000, function () { - console.log('JSON Server is running') -}) -``` +You can also add custom directories using `-s/--static` option. ```sh -$ node server.js -``` - -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. - -__Custom routes example__ - -Let's say you want a route that echoes query parameters and another one that set a timestamp on every resource created. - -```js -var jsonServer = require('json-server') -var server = jsonServer.create() -var router = jsonServer.router('db.json') -var middlewares = jsonServer.defaults() - -// Set default middlewares (logger, static, cors and no-cache) -server.use(middlewares) - -// Add custom routes before JSON Server router -server.get('/echo', function (req, res) { - res.jsonp(req.query) -}) - -server.use(function (req, res, next) { - if (req.method === 'POST') { - req.body.createdAt = Date.now() - } - // Continue to JSON Server router - next() -}) - -// Use default router -server.use(router) -server.listen(3000, function () { - console.log('JSON Server is running') -}) +json-server -s ./static +json-server -s ./static -s ./node_modules ``` -__Custom output example__ - -To modify responses, overwrite `router.render` method: - -```javascript -// In this example, returned resources will be wrapped in a body property -router.render = function (req, res) { - res.jsonp({ - body: res.locals.data - }) -} -``` - -__Rewriter example__ - -To add rewrite rules, use `jsonServer.rewriter()`: - -```javascript -// Add this before server.use(router) -server.use(jsonServer.rewriter({ - '/api/': '/', - '/blog/:resource/:id/show': '/:resource/:id' -})) -``` - -__Mounting JSON Server on another endpoint example__ - -Alternatively, you can also mount the router on `/api`. - -```javascript -server.use('/api', router) -``` - -### 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. - -## Links - -### Video - -* [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/) -* [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/) -* [Create a Mock REST API in Seconds for Prototyping your Frontend](https://coligo.io/create-mock-rest-api-with-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) - -## 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 9bea5a6e0..a0cfb2993 100644 --- a/package.json +++ b/package.json @@ -1,72 +1,63 @@ { "name": "json-server", - "version": "0.8.14", - "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", - "compression": "^1.6.0", - "connect-pause": "^0.1.0", - "cors": "^2.3.0", - "errorhandler": "^1.2.0", - "express": "^4.9.5", - "got": "^3.3.0", - "lodash": "^4.11.2", - "lowdb": "^0.13.0-beta.2", - "method-override": "^2.1.2", - "morgan": "^1.3.1", - "node-uuid": "^1.4.2", - "object-assign": "^4.0.1", - "pluralize": "^1.1.2", - "server-destroy": "^1.0.1", - "underscore-db": "^0.10.0", - "update-notifier": "^0.5.0", - "yargs": "^4.2.0" - }, - "devDependencies": { - "husky": "^0.11.4", - "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": "npm run test:cli && npm run test:server && standard", - "test:cli": "NODE_ENV=test mocha -R spec test/cli/*.js", - "test:server": "NODE_ENV=test mocha -R spec test/server/*.js", - "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 773134ff6..000000000 --- a/src/cli/index.js +++ /dev/null @@ -1,87 +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 - .config('config') - .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: 'Path to routes file' - }, - static: { - alias: 's', - description: 'Set static files directory' - }, - 'read-only': { - alias: 'ro', - description: 'Allow only GET requests' - }, - 'no-cors': { - alias: 'nc', - description: 'Disable Cross-Origin Resource Sharing' - }, - 'no-gzip': { - alias: 'ng', - description: 'Disable GZIP Content-Encoding' - }, - snapshots: { - alias: 'S', - description: 'Set snapshots directory', - default: '.' - }, - delay: { - alias: 'd', - description: 'Add delay to responses (ms)' - }, - id: { - alias: 'i', - description: 'Set database id property (e.g. _id)', - default: 'id' - }, - quiet: { - alias: 'q', - description: 'Suppress log messages from output' - }, - config: { - alias: 'c', - description: 'Path to config file', - default: 'json-server.json' - } - }) - .boolean('watch') - .boolean('read-only') - .boolean('quiet') - .boolean('no-cors') - .boolean('no-gzip') - .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 d35e1dede..000000000 --- a/src/cli/run.js +++ /dev/null @@ -1,161 +0,0 @@ -var fs = require('fs') -var path = require('path') -var chalk = require('chalk') -var enableDestroy = require('server-destroy') -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 defaultsOpts = { - logger: !argv.quiet, - readOnly: argv.readOnly, - noCors: argv.noCors, - noGzip: argv.noGzip - } - - if (argv.static) { - defaultsOpts.static = path.join(process.cwd(), argv.static) - } - - var defaults = jsonServer.defaults(defaultsOpts) - 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 - - if (!fs.existsSync(argv.snapshots)) { - console.log('Error: snapshots directory ' + argv.snapshots + ' doesn\'t exist') - process.exit(1) - } - - // noop log fn - if (argv.quiet) { - console.log = function () {} - } - - 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) - - // Enhance with a destroy function - enableDestroy(server) - - // 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 filename = 'db-' + Date.now() + '.json' - var file = path.join(argv.snapshots, filename) - app - .db - .write(file) - .then(function () { - console.log(' Saved snapshot to ' + path.relative(process.cwd(), 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.destroy() - start() - }) - } - - }) - -} diff --git a/src/cli/utils/is.js b/src/cli/utils/is.js deleted file mode 100644 index a59da210a..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 !isURL(s) && /\.json$/.test(s) -} - -function isJS (s) { - return !isURL(s) && /\.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 df2fec81f..000000000 --- a/src/cli/utils/load.js +++ /dev/null @@ -1,39 +0,0 @@ -var path = require('path') -var got = require('got') -var low = require('lowdb') -var fileAsync = require('lowdb/lib/file-async') -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] - var dataFn = require(filename) - - if (typeof dataFn !== 'function') { - throw new Error('The database is a JavaScript file but the export is not a function.') - } - - data = dataFn() - cb(null, data) - - } else if (is.JSON(source)) { - - data = low(source, { storage: fileAsync }).getState() - 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 b8e99979e..000000000 --- a/src/server/defaults.js +++ /dev/null @@ -1,70 +0,0 @@ -var fs = require('fs') -var path = require('path') -var express = require('express') -var logger = require('morgan') -var cors = require('cors') -var compression = require('compression') -var errorhandler = require('errorhandler') -var objectAssign = require('object-assign') - -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 = objectAssign({ logger: true, static: staticDir }, opts) - - var arr = [] - - // Compress all requests - if (!opts.noGzip) { - arr.push(compression()) - } - - // Logger - if (opts.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 - if (!opts.noCors) { - 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() - }) - - // Read-only - if (opts.readOnly) { - arr.push(function (req, res, next) { - if (req.method === 'GET') { - next() // Continue - } else { - res.sendStatus(403) // Forbidden - } - }) - } - - 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 43dab7f18..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 = _.maxBy(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 cf7461bea..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 13b83047e..000000000 --- a/src/server/router/index.js +++ /dev/null @@ -1,91 +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 fileAsync = require('lowdb/lib/file-async') -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.setState(source) - } else { - db = low(source, { storage: fileAsync }) - } - - // Add underscore-db methods to db - db._.mixin(_db) - - // Add specific mixins - db._.mixin(mixins) - - // 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.getState() - next() - } - - router.get('/db', showDatabase) - - router.use(nested()) - - // Create routes - db.forEach(function (value, key) { - if (_.isPlainObject(value)) { - router.use('/' + key, singular(db, key)) - return - } - - if (_.isArray(value)) { - router.use('/' + key, plural(db, key)) - return - } - - var msg = - 'Type of "' + key + '" (' + typeof value + ') ' + - (_.isObject(source) ? '' : 'in ' + source) + ' is not supported. ' + - 'Use objects or arrays of objects.' - - throw new Error(msg) - }).value() - - router.use(function (req, res) { - if (!res.locals.data) { - res.status(404) - res.locals.data = {} - } - - router.render(req, res) - }) - - router.use(function (err, req, res, next) { - console.error(err.stack) - res.status(500).send(err.stack) - }) - - return router -} diff --git a/src/server/router/nested.js b/src/server/router/nested.js deleted file mode 100644 index b8413e9f7..000000000 --- a/src/server/router/nested.js +++ /dev/null @@ -1,28 +0,0 @@ -var express = require('express') -var pluralize = require('pluralize') -var utils = require('../utils') - -module.exports = function () { - - var router = express.Router() - - // Rewrite URL (/:resource/:id/:nested -> /:nested) and request query - function get (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() - } - - // Rewrite URL (/:resource/:id/:nested -> /:nested) and request body - function post (req, res, next) { - var prop = pluralize.singular(req.params.resource) - req.body[prop + 'Id'] = utils.toNative(req.params.id) - req.url = '/' + req.params.nested - next() - } - - return router - .get('/:resource/:id/:nested', get) - .post('/:resource/:id/:nested', post) -} diff --git a/src/server/router/plural.js b/src/server/router/plural.js deleted file mode 100644 index a53127815..000000000 --- a/src/server/router/plural.js +++ /dev/null @@ -1,273 +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.get(externalResource).value) { - var query = {} - var singularResource = pluralize.singular(name) - query[singularResource + 'Id'] = resource.id - resource[externalResource] = db.get(externalResource).filter(query).value() - } - }) - } - - // 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.get(plural).value()) { - var prop = innerResource + 'Id' - resource[innerResource] = db.get(plural).getById(resource[prop]).value() - } - }) - } - - // 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.get(name) - - // 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.get(name).value() - for (var i in arr) { - if ( - _.has(arr[i], query) || - query === 'callback' || - query === '_' || - query.indexOf('_lte') !== -1 || - query.indexOf('_gte') !== -1 || - query.indexOf('_ne') !== -1 || - query.indexOf('_like') !== -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 isDifferent = key.indexOf('_ne') !== -1 - var isRange = key.indexOf('_lte') !== -1 || key.indexOf('_gte') !== -1 - var isLike = key.indexOf('_like') !== -1 - var path = key.replace(/(_lte|_gte|_ne|_like)$/, '') - var elementValue = _.get(element, path) - - if (isRange) { - var isLowerThan = key.indexOf('_gte') !== -1 - - if (isLowerThan) { - return value <= elementValue - } else { - return value >= elementValue - } - } else if (isDifferent) { - return value !== elementValue - } else if (isLike) { - return new RegExp(value, 'i').test(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 _.get(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.get(name).getById(id).value() - - 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.get(name) - .insert(req.body) - .value() - - 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 id = utils.toNative(req.params.id) - var chain = db.get(name) - - chain = req.method === 'PATCH' ? - chain.updateById(id, req.body) : - chain.replaceById(id, req.body) - - var resource = chain.value() - - if (resource) { - res.locals.data = resource - } - - next() - } - - // DELETE /name/:id - function destroy (req, res, next) { - var resource = db.get(name).removeById(utils.toNative(req.params.id)).value() - - // Remove dependents documents - var removable = db._.getRemovable(db.getState()) - - _.each(removable, function (item) { - db.get(item.name).removeById(item.id).value() - }) - - if (resource) { - 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 151abb9d5..000000000 --- a/src/server/router/singular.js +++ /dev/null @@ -1,41 +0,0 @@ -var express = require('express') - -module.exports = function (db, name) { - - var router = express.Router() - - function show (req, res, next) { - res.locals.data = db.get(name).value() - next() - } - - function create (req, res, next) { - db.set(name, req.body).value() - res.locals.data = db.get(name).value() - res.status(201) - next() - } - - function update (req, res, next) { - if (req.method === 'PUT') { - db.set(name, req.body) - .value() - } else { - db.get(name) - .assign(req.body) - .value() - } - - res.locals.data = db.get(name).value() - 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 93b90f3a1..000000000 --- a/test/cli/fixtures/seed.js +++ /dev/null @@ -1,29 +0,0 @@ -// Need some fake data for the gzip test to work -module.exports = function () { - return { posts: [ - { - id: 1, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - }, - { - id: 2, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - }, - { - id: 3, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - }, - { - id: 4, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - }, - { - id: 5, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - }, - { - id: 6, - content: "Oh! The garbage chute was a really wonderful idea. What an incredible smell you've discovered! Let's get out of here! Get away from there... No! wait! Will you forget it? I already tried it. It's magnetically sealed! Put that thing away! You're going to get us all killed. Absolutely, Your Worship. Look, I had everything under control until you led us down here. You know, it's not going to take them long to figure out what happened to us. It could be worst... It's worst. There's something alive in here! That's your imagination. Something just moves past my leg! Look! Did you see that? What? Help!" - } - ]} -} diff --git a/test/cli/index.js b/test/cli/index.js deleted file mode 100644 index e00c2fcf9..000000000 --- a/test/cli/index.js +++ /dev/null @@ -1,277 +0,0 @@ -var os = require('os') -var fs = require('fs') -var path = require('path') -var cp = require('child_process') -var assert = require('assert') -var supertest = require('supertest') -var rmrf = require('rimraf') -var serverReady = require('server-ready') -var pkg = require('../../package.json') - -var PORT = 3100 -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), { - cwd: __dirname, - stdio: ['pipe', process.stdout, process.stderr] - }) -} - -/* global beforeEach, afterEach, describe, it */ - -describe('cli', function () { - - var child - var request - - beforeEach(function () { - rmrf.sync(tmpDir) - fs.mkdirSync(tmpDir) - fs.writeFileSync(dbFile, JSON.stringify({ - posts: [ - { id: 1 }, - {_id: 2 } - ] - })) - fs.writeFileSync(routesFile, JSON.stringify({ - '/blog/': '/' - })) - ++PORT - request = supertest('http://localhost/' + PORT) - }) - - afterEach(function () { - rmrf.sync(tmpDir) - child.kill() - }) - - describe('db.json', function () { - - beforeEach(function (done) { - child = cli([dbFile]) - serverReady(PORT, done) - }) - - it('should support JSON file', function (done) { - request.get('/posts').expect(200, done) - }) - - it('should send CORS headers', function (done) { - var origin = 'http://example.com/' - - request.get('/posts') - .set('Origin', origin) - .expect('access-control-allow-origin', origin) - .expect(200, done) - }) - - it('should update JSON file', function (done) { - request.post('/posts') - .send({ title: 'hello' }) - .end(function () { - setTimeout(function () { - var str = fs.readFileSync(dbFile, 'utf8') - assert(str.indexOf('hello') !== -1) - done() - }, 1000) - }) - }) - }) - - 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 --read-only', function () { - - beforeEach(function (done) { - child = cli([dbFile, '-r', routesFile, '-i', '_id', '--read-only']) - serverReady(PORT, done) - }) - - it('should use routes.json and _id as the identifier', function (done) { - request.get('/blog/posts/2').expect(200, done) - }) - - it('should allow only GET requests', function (done) { - request.post('/blog/posts').expect(403, 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 -S ../../tmp', function () { - - var snapshotsDir = path.join(tmpDir, 'snapshots') - var publicDir = 'fixtures/public' - - beforeEach(function (done) { - fs.mkdirSync(snapshotsDir) - child = cli([dbFile, '-s', publicDir, '-S', snapshotsDir]) - serverReady(PORT, function () { - child.stdin.write('s\n') - setTimeout(done, 100) - }) - }) - - it('should serve fixtures/public', function (done) { - request.get('/').expect(/Hello/, done) - }) - - it('should save a snapshot in ../../tmp', function () { - assert.equal(fs.readdirSync(snapshotsDir).length, 1) - }) - - }) - - describe('db.json --no-cors=true', function () { - - beforeEach(function (done) { - child = cli(['fixtures/seed.js', '--no-cors=true']) - serverReady(PORT, done) - }) - - it('should not send Access-Control-Allow-Origin headers', function (done) { - var origin = 'http://example.com/' - - request.get('/posts') - .set('Origin', origin) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err) - } else if ('access-control-allow-origin' in res.headers) { - done(new Error('CORS headers were not excluded from response')) - } else { - done() - } - }) - }) - - }) - - describe('db.json --no-gzip=true', function () { - - beforeEach(function (done) { - child = cli(['fixtures/seed.js', '--no-gzip=true']) - serverReady(PORT, done) - }) - - it('should not set Content-Encoding to gzip', function (done) { - var origin = 'http://example.com/' - - request.get('/posts') - .set('Origin', origin) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err) - } else if ('content-encoding' in res.headers) { - done(new Error('Content-Encoding is set to gzip')) - } else { - done() - } - }) - }) - - }) - - describe('db.json --no-gzip=false', function () { - - beforeEach(function (done) { - child = cli(['fixtures/seed.js', '--no-gzip=false']) - serverReady(PORT, done) - }) - - it('should set Content-Encoding to gzip', function (done) { - var origin = 'http://example.com/' - - request.get('/posts') - .set('Origin', origin) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err) - } else if ('content-encoding' in res.headers) { - done() - } else { - done(new Error('Content-Encoding is not set to gzip')) - } - }) - }) - - }) - - // 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 48cfffcdb..000000000 --- a/test/server/plural.js +++ /dev/null @@ -1,644 +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 } - ] - - db.nested = [ - {resource: {name: 'dewey'}}, - {resource: {name: 'cheatem'}}, - {resource: {name: 'howe'}} - ] - - 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) - }) - - it('should sort on nested field', function (done) { - request(server) - .get('/nested?_sort=resource.name') - .expect('Content-Type', /json/) - .expect([db.nested[1], db.nested[0], db.nested[2]]) - .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_gte=&attr_lte=', 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 /:resource?attr_ne=', function () { - it('should respond with a limited array', function (done) { - request(server) - .get('/comments?id_ne=1') - .expect('Content-Type', /json/) - .expect(db.comments.slice(1)) - .expect(200, done) - }) - }) - - describe('GET /:resource?attr_like=', function () { - it('should respond with an array that matches the like operator (case insensitive)', function (done) { - request(server) - .get('/tags?body_like=photo') - .expect('Content-Type', /json/) - .expect([ - db.tags[1], - db.tags[2] - ]) - .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('POST /:parent/:parentId/:resource', function () { - it('should respond with json and set parentId', function (done) { - request(server) - .post('/posts/1/comments') - .send({body: 'foo'}) - .expect('Content-Type', /json/) - .expect({id: 6, postId: 1, body: 'foo'}) - .expect(201, done) - }) - }) - - describe('PUT /:resource/:id', function () { - it('should respond with json and replace resource', function (done) { - var post = {id: 1, booleanValue: true, integerValue: 1} - request(server) - .put('/posts/1') - // body property omitted to test that the resource is replaced - .send({id: 1, booleanValue: 'true', integerValue: '1'}) - .expect('Content-Type', /json/) - .expect(post) - .expect(200) - .end(function (err, res) { - if (err) return done(err) - // assert it was created in database too - assert.deepEqual(db.posts[0], post) - 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() - }) - }) - - it('should respond with 404 if resource is not found', function (done) { - request(server) - .del('/posts/9001') - .expect('Content-Type', /json/) - .expect({}) - .expect(404, 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 state', function () { - it('should be accessible', function () { - assert(router.db.getState()) - }) - }) - - 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.setState({ - posts: [ - { _id: 1 } - ] - }) - - router.db._.id = '_id' - }) - - it('should be possible to GET using a different id property', function (done) { - request(server) - .get('/posts/1') - .expect('Content-Type', /json/) - .expect(router.db.getState().posts[0]) - .expect(200, done) - }) - - it('should be possible to POST using a different id property', function (done) { - request(server) - .post('/posts') - .send({ body: 'hello' }) - .expect('Content-Type', /json/) - .expect({_id: 2, body: 'hello'}) - .expect(201, done) - }) - - }) -}) diff --git a/test/server/singular.js b/test/server/singular.js deleted file mode 100644 index 9c3973750..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 update resource', function (done) { - var user = { name: 'bar' } - request(server) - .put('/user') - .send(user) - .expect(user) - .expect(200, done) - }) - }) - - describe('PATCH /:resource', function () { - it('should update 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