diff --git a/.config/automd.yaml b/.config/automd.yaml new file mode 100644 index 00000000..63e39fe7 --- /dev/null +++ b/.config/automd.yaml @@ -0,0 +1 @@ +input: ["README.md", "docs/**/*.md"] diff --git a/.eslintignore b/.eslintignore index f2213879..3ecff85f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,4 @@ dist lib +.nuxt +.output diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 5cb2623f..55b1a12e 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -16,11 +16,11 @@ jobs: - run: corepack enable - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: "pnpm" - run: pnpm install - - name: Fix lint issues - run: pnpm run lint:fix + - run: pnpm run lint:fix + - run: pnpm automd - uses: autofix-ci/action@ea32e3a12414e6d3183163c3424a7d7a8631ad84 with: - commit-message: "chore: apply automated fixes" + commit-message: "chore: apply automated updates" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c845e9f..3f58f046 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - run: pnpm lint - run: pnpm build - run: pnpm vitest --coverage && rm -rf coverage/tmp - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 - name: nightly release if: | github.event_name == 'push' && diff --git a/.gitignore b/.gitignore index 08767158..74b98e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage .idea .eslintcache tsconfig.vitest-temp.json +.DS_Store diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..7aa07b00 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +dist +lib +.nuxt +.output +docs/**/*.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 224620e7..a9a9724b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,94 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## v1.11.1 + +[compare changes](https://github.com/unjs/h3/compare/v1.11.0...v1.11.1) + +### 🩹 Fixes + +- **ws:** Resolve pathname for matching ([4f211f8](https://github.com/unjs/h3/commit/4f211f8)) + +### 📖 Documentation + +- Update bun ws example ([da464c3](https://github.com/unjs/h3/commit/da464c3)) + +### 🏡 Chore + +- Update crossws ([a61f98a](https://github.com/unjs/h3/commit/a61f98a)) + +### ❤️ Contributors + +- Pooya Parsa ([@pi0](http://github.com/pi0)) + +## v1.11.0 + +[compare changes](https://github.com/unjs/h3/compare/v1.10.2...v1.11.0) + +### 🚀 Enhancements + +- Add utilities for server sent events ([#586](https://github.com/unjs/h3/pull/586)) +- **response:** Add `sendIterable` util ([#655](https://github.com/unjs/h3/pull/655)) +- Handler resolver ([#669](https://github.com/unjs/h3/pull/669)) +- Websocket support ([#671](https://github.com/unjs/h3/pull/671)) + +### 🩹 Fixes + +- **serveStatic:** Ensure `etag` header is set before sending 304 response ([#653](https://github.com/unjs/h3/pull/653)) + +### 📖 Documentation + +- Add basic jsdocs for utils ([c8aa150](https://github.com/unjs/h3/commit/c8aa150)) +- Fix typo ([#668](https://github.com/unjs/h3/pull/668)) +- Fix typos ([#665](https://github.com/unjs/h3/pull/665)) +- Fix typo ([#662](https://github.com/unjs/h3/pull/662)) +- Fix typos ([#661](https://github.com/unjs/h3/pull/661)) +- Fix import name ([#658](https://github.com/unjs/h3/pull/658)) +- **examples/from-expressjs-to-h3:** Add node middleware usage ([#663](https://github.com/unjs/h3/pull/663)) +- Refine function usages ([#667](https://github.com/unjs/h3/pull/667)) +- Remove unwanted `console.log` ([#675](https://github.com/unjs/h3/pull/675)) +- Add jsdoc examples ([#672](https://github.com/unjs/h3/pull/672)) +- Update jsdocs example for route utils ([#673](https://github.com/unjs/h3/pull/673)) + +### 🏡 Chore + +- **release:** V1.10.2 ([a58d7c9](https://github.com/unjs/h3/commit/a58d7c9)) +- Apply automated fixes ([f5a89fc](https://github.com/unjs/h3/commit/f5a89fc)) +- Fix does issues ([#657](https://github.com/unjs/h3/pull/657)) +- Integrate automd ([5212f01](https://github.com/unjs/h3/commit/5212f01)) +- Lint ([ddffb0e](https://github.com/unjs/h3/commit/ddffb0e)) +- Update docs ([1d8b389](https://github.com/unjs/h3/commit/1d8b389)) +- Update docs ([5e3b5e5](https://github.com/unjs/h3/commit/5e3b5e5)) +- Update lockfiles ([272e1be](https://github.com/unjs/h3/commit/272e1be)) +- Apply automated updates ([96eda87](https://github.com/unjs/h3/commit/96eda87)) + +### ❤️ Contributors + +- Pooya Parsa ([@pi0](http://github.com/pi0)) +- Estéban ([@Barbapapazes](http://github.com/Barbapapazes)) +- Bram Kamies +- Joshua Sosso ([@joshmossas](http://github.com/joshmossas)) +- Nozomu Ikuta +- Markthree ([@markthree](http://github.com/markthree)) +- Sacha Stafyniak ([@stafyniaksacha](http://github.com/stafyniaksacha)) +- Meir Lamdan +- Joshua +- Matej Černý +- Amit Gurbani ([@AmitGurbani](http://github.com/AmitGurbani)) +- Neil Richter ([@noook](http://github.com/noook)) + +## v1.10.2 + +[compare changes](https://github.com/unjs/h3/compare/v1.10.1...v1.10.2) + +### 🩹 Fixes + +- **proxy:** Ignore incoming `accept` header ([#646](https://github.com/unjs/h3/pull/646)) + +### ❤️ Contributors + +- Daniel Roe ([@danielroe](http://github.com/danielroe)) + ## v1.10.1 [compare changes](https://github.com/unjs/h3/compare/v1.10.0...v1.10.1) diff --git a/README.md b/README.md index 1ebdefa0..9acfb81b 100644 --- a/README.md +++ b/README.md @@ -1,354 +1,48 @@ # H3 -[![npm version][npm-version-src]][npm-version-href] -[![npm downloads][npm-downloads-src]][npm-downloads-href] -[![bundle][bundle-src]][bundle-href] -[![Codecov][codecov-src]][codecov-href] -[![License][license-src]][license-href] -[![JSDocs][jsdocs-src]][jsdocs-href] + -H3 (pronounced as /eɪtʃθriː/, like h-3) is a minimal h(ttp) framework built for high performance and portability. - -👉 [Online Playground](https://stackblitz.com/github/unjs/h3/tree/main/playground) - -👉 [Online Examples Playground](https://stackblitz.com/github/unjs/h3/tree/main/examples) - -## Features - -✔️  **Portable:** Works perfectly in Serverless, Workers, and Node.js - -✔️  **Minimal:** Small and tree-shakable - -✔️  **Modern:** Native promise support - -✔️  **Extendable:** Ships with a set of composable utilities but can be extended - -✔️  **Router:** Super fast route matching using [unjs/radix3](https://github.com/unjs/radix3) +[![npm version](https://flat.badgen.net/npm/v/h3)](https://npmjs.com/package/h3) +[![npm downloads](https://flat.badgen.net/npm/dm/h3)](https://npmjs.com/package/h3) -✔️  **Compatible:** Compatibility layer with node/connect/express middleware + -## Install - -```bash -# Using npm -npm install h3 +H3 (pronounced as /eɪtʃθriː/, like h-3) is a minimal h(ttp) framework built for high performance and portability. -# Using yarn -yarn add h3 +👉 [Documentation](https://h3.unjs.io) -# Using pnpm -pnpm add h3 -``` +## Contribution
- Using Nightly Releases - -If you are directly using `h3` as a dependency: - -```json -{ - "dependencies": { - "h3": "npm:h3-nightly@latest" - } -} -``` - -If you are using a framework ([Nuxt](https://nuxt.com/) or [Nitro](https://nitro.unjs.io/)) that is using `h3`: - -pnpm and yarn: - -```json -{ - "resolutions": { - "h3": "npm:h3-nightly@latest" - } -} -``` - -npm: - -```json -{ - "overrides": { - "h3": "npm:h3-nightly@latest" - } -} -``` + Local development -**Note:** Make sure to recreate lockfile and `node_modules` after reinstall to avoid hoisting issues. +- Clone this repository +- Install the latest LTS version of [Node.js](https://nodejs.org/en/) +- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` +- Install dependencies using `pnpm install` +- Run tests using `pnpm dev` or `pnpm test`
-## Usage - -```ts -import { createServer } from "node:http"; -import { createApp, eventHandler, toNodeListener } from "h3"; - -const app = createApp(); -app.use( - "/", - eventHandler(() => "Hello world!"), -); - -createServer(toNodeListener(app)).listen(process.env.PORT || 3000); -``` - -Example using listhen for an elegant listener: - -```ts -import { createApp, eventHandler, toNodeListener } from "h3"; -import { listen } from "listhen"; - -const app = createApp(); -app.use( - "/", - eventHandler(() => "Hello world!"), -); - -listen(toNodeListener(app)); -``` - -## Router - -The `app` instance created by `h3` uses a middleware stack (see [how it works](./src/app.ts)) with the ability to match route prefix and apply matched middleware. - -To opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance. - -```ts -import { createApp, eventHandler, createRouter } from "h3"; - -const app = createApp(); - -const router = createRouter() - .get( - "/", - eventHandler(() => "Hello World!"), - ) - .get( - "/hello/:name", - eventHandler((event) => `Hello ${event.context.params.name}!`), - ); - -app.use(router); -``` - -**Tip:** We can register the same route more than once with different methods. - -Routes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3). - -For using nested routers, see [this example](./examples/nested-router.ts) - -## More app usage examples + -```js -// Handle can directly return object or Promise for JSON response -app.use( - "/api", - eventHandler((event) => ({ url: event.node.req.url })), -); - -// We can have better matching other than quick prefix match -app.use( - "/odd", - eventHandler(() => "Is odd!"), - { match: (url) => url.substr(1) % 2 }, -); - -// Handle can directly return string for HTML response -app.use(eventHandler(() => "

Hello world!

")); - -// We can chain calls to .use() -app - .use( - "/1", - eventHandler(() => "

Hello world!

"), - ) - .use( - "/2", - eventHandler(() => "

Goodbye!

"), - ); - -// We can proxy requests and rewrite cookie's domain and path -app.use( - "/api", - eventHandler((event) => - proxyRequest(event, "https://example.com", { - // f.e. keep one domain unchanged, rewrite one domain and remove other domains - cookieDomainRewrite: { - "example.com": "example.com", - "example.com": "somecompany.co.uk", - "*": "", - }, - cookiePathRewrite: { - "/": "/api", - }, - }), - ), -); - -// Legacy middleware with 3rd argument are automatically promisified -app.use( - fromNodeMiddleware((req, res, next) => { - req.setHeader("x-foo", "bar"); - next(); - }), -); - -// Lazy loaded routes using { lazy: true } -app.use("/big", () => import("./big-handler"), { lazy: true }); -``` - -## Utilities - -H3 has a concept of composable utilities that accept `event` (from `eventHandler((event) => {})`) as their first argument. This has several performance benefits over injecting them to `event` or `app` instances in global middleware commonly used in Node.js frameworks, such as Express. This concept means only required code is evaluated and bundled, and the rest of the utilities can be tree-shaken when not used. - -👉 You can check list of exported built-in utils from [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions). - -#### Body - -- `readRawBody(event, encoding?)` -- `readBody(event)` -- `readValidatedBody(event, validate)` -- `readMultipartFormData(event)` - -#### Request - -- `getQuery(event)` -- `getValidatedQuery(event, validate)` -- `getRouterParams(event, { decode? })` -- `getRouterParam(event, name, { decode? })` -- `getValidatedRouterParams(event, validate, { decode? })` -- `getMethod(event, default?)` -- `isMethod(event, expected, allowHead?)` -- `assertMethod(event, expected, allowHead?)` -- `getRequestHeaders(event)` (alias: `getHeaders`) -- `getRequestHeader(event, name)` (alias: `getHeader`) -- `getRequestURL(event)` -- `getRequestHost(event)` -- `getRequestProtocol(event)` -- `getRequestPath(event)` -- `getRequestIP(event, { xForwardedFor: boolean })` - -#### Response - -- `send(event, data, type?)` -- `sendNoContent(event, code = 204)` -- `setResponseStatus(event, status)` -- `getResponseStatus(event)` -- `getResponseStatusText(event)` -- `getResponseHeaders(event)` -- `getResponseHeader(event, name)` -- `setResponseHeaders(event, headers)` (alias: `setHeaders`) -- `setResponseHeader(event, name, value)` (alias: `setHeader`) -- `appendResponseHeaders(event, headers)` (alias: `appendHeaders`) -- `appendResponseHeader(event, name, value)` (alias: `appendHeader`) -- `defaultContentType(event, type)` -- `sendRedirect(event, location, code=302)` -- `isStream(data)` -- `sendStream(event, data)` -- `writeEarlyHints(event, links, callback)` - -#### Sanitize - -- `sanitizeStatusMessage(statusMessage)` -- `sanitizeStatusCode(statusCode, default = 200)` - -#### Error - -- `sendError(event, error, debug?)` -- `createError({ statusCode, statusMessage, data? })` - -#### Route - -- `useBase(base, handler)` - -#### Proxy - -- `sendProxy(event, { target, ...options })` -- `proxyRequest(event, { target, ...options })` -- `fetchWithEvent(event, req, init, { fetch? }?)` -- `getProxyRequestHeaders(event)` - -#### Cookie - -- `parseCookies(event)` -- `getCookie(event, name)` -- `setCookie(event, name, value, opts?)` -- `deleteCookie(event, name, opts?)` -- `splitCookiesString(cookiesString)` - -#### Session - -- `useSession(event, config = { password, maxAge?, name?, cookie?, seal?, crypto? })` -- `getSession(event, config)` -- `updateSession(event, config, update)` -- `sealSession(event, config)` -- `unsealSession(event, config, sealed)` -- `clearSession(event, config)` - -#### Cache - -- `handleCacheHeaders(event, opts)` - -#### Cors - -- `handleCors(options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) -- `isPreflightRequest(event)` -- `isCorsOriginAllowed(event)` -- `appendCorsHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) -- `appendCorsPreflightHeaders(event, options)` (see [h3-cors](https://github.com/NozomuIkuta/h3-cors) for more detail about options) - -## Community Packages - -You can use more H3 event utilities made by the community. +## License -Please check their READMEs for more details. + -PRs are welcome to add your packages. +Published under the [MIT](https://github.com/unjs/h3/blob/main/LICENSE) license. +Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/h3/graphs/contributors) 💛 +

+ + + -- [h3-typebox](https://github.com/kevinmarrec/h3-typebox) - - `validateBody(event, schema)` - - `validateQuery(event, schema)` -- [h3-zod](https://github.com/wobsoriano/h3-zod) - - `useValidatedBody(event, schema)` - - `useValidatedQuery(event, schema)` -- [h3-valibot](https://github.com/intevel/h3-valibot) - - `useValidateBody(event, schema)` - - `useValidateParams(event, schema)` -- [h3-compression](https://github.com/CodeDredd/h3-compression) - - `useGZipCompression(event, response)` - - `useDeflateCompression(event, response)` - - `useBrotliCompression(event, response)` - - `useCompression(event, response)` - - `useGZipCompressionStream(event, response)` - - `useDeflateCompressionStream(event, response)` - - `useCompressionStream(event, response)` -- [@intlify/h3](https://github.com/intlify/h3) - - `defineI18nMiddleware(options)` - - `useTranslation(event)` - - `getHeaderLocale(event, options)` - - `getHeaderLocales(event, options)` - - `getCookieLocale(event, options)` - - `setCookieLocale(event, options)` - - `getPathLocale(event, options)` - - `getQueryLocale(event, options)` + -## License + -MIT +--- - +_🤖 auto updated with [automd](https://automd.unjs.io)_ -[npm-version-src]: https://img.shields.io/npm/v/h3?style=flat&colorA=18181B&colorB=F0DB4F -[npm-version-href]: https://npmjs.com/package/h3 -[npm-downloads-src]: https://img.shields.io/npm/dm/h3?style=flat&colorA=18181B&colorB=F0DB4F -[npm-downloads-href]: https://npmjs.com/package/h3 -[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat&colorA=18181B&colorB=F0DB4F -[codecov-href]: https://codecov.io/gh/unjs/h3 -[bundle-src]: https://img.shields.io/bundlephobia/minzip/h3?style=flat&colorA=18181B&colorB=F0DB4F -[bundle-href]: https://bundlephobia.com/result?p=h3 -[license-src]: https://img.shields.io/github/license/unjs/h3.svg?style=flat&colorA=18181B&colorB=F0DB4F -[license-href]: https://github.com/unjs/h3/blob/main/LICENSE -[jsdocs-src]: https://img.shields.io/badge/jsDocs.io-reference-18181B?style=flat&colorA=18181B&colorB=F0DB4F -[jsdocs-href]: https://www.jsdocs.io/package/h3 + diff --git a/docs/.config/docs.yaml b/docs/.config/docs.yaml new file mode 100644 index 00000000..e42dd48c --- /dev/null +++ b/docs/.config/docs.yaml @@ -0,0 +1,75 @@ +# yaml-language-server: $schema=https://unpkg.com/undocs/schema/config.json +name: h3 +shortDescription: The Web Framework for Modern JavaScript Era +description: + H(TTP) server framework built for high performance and portability running + in any JavaScript runtime. +github: unjs/h3 +themeColor: amber +url: https://h3.unjs.io +automd: true +redirects: {} +landing: + heroLinks: + playOnline: + label: "Play Online" + icon: "i-simple-icons-lightning" + to: "https://stackblitz.com/github/unjs/h3/tree/main/playground" + contributors: true + featuresTitle: "The Web Framework for Modern JavaScript Era" + features: + # Runtime Agnostic + - title: "Runtime Agnostic" + description: "Your code will work on any JavaScript runtime including Node.js, Bun, Deno and Workers." + icon: "i-material-symbols-lock-open-right-outline-rounded" + to: "" + target: "_blank" + + # Tree-shakable + - title: "Small and Tree-shakable" + description: "h3 core is super lightweight and tree-shakable, only what you use will be included in the the final bundle." + icon: "i-material-symbols-potted-plant-outline" + to: "" + target: "_blank" + + # Composable + - title: "Composable" + description: "Expand your server and add capabilities. Your codebase will scale with your project." + icon: "i-fa-puzzle-piece" + to: "" + target: "_blank" + + # Router + - title: "Fast Router" + description: "Super fast route matching using unjs/radix3" + icon: "i-fa-tree" + to: "https://github.com/unjs/radix3" + target: "_blank" + + # Ecosystem + - title: "UnJS ecosystem" + description: "Built on top of powerful UnJS ecosystem powering Nitro, Nuxt and more frameworks!" + icon: "i-mdi-umbrella-outline" + to: "https://unjs.io" + target: "_blank" + + # Made for Humans + - title: "Made for Humans" + description: "Elegant minimal API to implement HTTP handlers compatible with Web-Standards." + icon: "i-material-symbols-robot-2-sharp" + to: "" + target: "_blank" + + # Compatibility + - title: "Compatible" + description: "Compatibility layer with node/connect/express middleware." + icon: "i-simple-icons-express" + to: "" + target: "_blank" + + # Type Friendly + - title: "Type Friendly" + description: "Codebase fully written in TypeScript with strongly typed utils." + icon: "i-mdi-language-typescript" + to: "" + target: "_blank" diff --git a/docs/.docs/public/icon.svg b/docs/.docs/public/icon.svg new file mode 100644 index 00000000..f650727a --- /dev/null +++ b/docs/.docs/public/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..21fd3a93 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,4 @@ +node_modules +.nuxt +.output +dist diff --git a/docs/1.guide/1.index.md b/docs/1.guide/1.index.md new file mode 100644 index 00000000..e4f8259c --- /dev/null +++ b/docs/1.guide/1.index.md @@ -0,0 +1,107 @@ +--- +icon: ph:book-open-duotone +--- + +# Getting Started + +> Getting started with h3 + +## Overview + +h3 (short for HTTP, pronounced as /eɪtʃθriː/, like h-3) is a lightweight and [composable](/utils) server framework for JavaScript that is designed to +work with various javascript runtimes through [adapters](/adapters). + +::read-more{to="https://nitro.unjs.io"} +Check out [Nitro](https://nitro.unjs.io) for a full featured server toolkit. +:: + +## Quick Start + +Create a new file `app.ts` (or `app.js`): + +```ts [app.ts] +// Import h3 as npm dependency +import { createApp, createRouter, defineEventHandler } from "h3"; + +// Create an app instance +export const app = createApp(); + +// Create a new router and register it in app +const router = createRouter(); +app.use(router); + +// Add a new route that matches GET requests to / path +router.get( + "/", + defineEventHandler((event) => { + return { message: "⚡️ Tadaa!" }; + }), +); +``` + +Now run the development server using [unjs/listhen](https://listhen.unjs.io): + +```sh +npx --yes listhen -w --open ./app.ts +``` + +> [!TIP] +> You don't need to install any additional dependency. Listhen has a preinstalled version of h3! + +And tadaa! We have a web server running locally. + +### What happened? + +Okay, let's now break down our hello world example: + +We first created an [app instance](/guide/app) using `createApp()`. `app` is a tiny server capable of matching requests, generating response and handling lifecycle hooks (such as errors): + +```ts +export const app = createApp(); +``` + +Then we create a [router instance](/guide/router) that can match route patterns and http methods using [unjs/radix3](https://radix3.unjs.io) and register it for app as main handler: + +```ts +const router = createRouter(); + +app.use(router); +``` + +Now it is time to add our first endpoint. In h3, request handlers can be defined using `defineEventHandler` or `eventHandler` helpers (they are aliases). Using wrappers, h3 can supercharge your code with better typehints and future compatibility. + +```ts +defineEventHandler((event) => {}); +``` + +What is beautiful in h3 is that all you have to do to make a reponse, is to simply return it! Responses can be simple string, JSON objects, data buffers, streams or standard [Web Response](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response). + +```ts +return { message: "⚡️ Tadaa!" }; +``` + +Finally, we use [unjs/listhen](https://listhen.unjs.io) CLI using npx to auto install it. Listhen will automatically setup and start our webserver with zero configuration and adds on-the-fly TypeScript support to your experience! + +## Installing h3 + +You can import h3 either as an npm package or from CDN. + +### Install as npm package + +You can use this method for Node.js and Bun. + +::pm-install{name="h3"} + +:: + +### Import from CDN + +You can directly import h3 from CDN. This method can be used for Bun, Deno and other runtimes such as Cloudflare Workers (you need an adapter). + +```js +import { createApp, toWebhandler } from "https://esm.sh/h3"; + +export const app = createApp(); + +export const handler = toWebHandler(app); +``` diff --git a/docs/1.guide/2.app.md b/docs/1.guide/2.app.md new file mode 100644 index 00000000..461d1d61 --- /dev/null +++ b/docs/1.guide/2.app.md @@ -0,0 +1,177 @@ +--- +icon: ic:baseline-bolt +--- + +# App Instance + +> App instance is the core of a h3 server. + +The core of a h3 server is an `app` instance. It is the core of the server that handles incoming requests. You can use app instance to register event handlers. + +## Initializing an app + +You can create a new h3 app instance using [`createApp`](/guide/app) utility: + +```js [app.mjs] +import { createApp } from "h3"; + +const app = createApp(); +``` + +## Setting global options + +You can pass global app configuration when initializing an app. + +**Example:** Create an app with verbose logging enabled. + +```js +const app = createApp({ + debug: true, +}); +``` + +## Setting global hooks + +When initializing an h3 app, you can register global hooks: + +- `onError` +- `onRequest` +- `onBeforeResponse` +- `onAfterResponse` + +These hooks are called for every request and can be used to add global logic to your app such as logging, error handling, etc. + +```js +const app = createApp({ + onError: (error) => { + console.error(error); + }, + onRequest: (event) => { + console.log("Request:", event.path); + }, +}); +``` + +## Registering event handlers + +You can register [event handlers](/guide/event-handler) to app instance using the `app.use`: + +```js +import { defineEventHandler } from "h3"; + +app.use( + "/hello", + defineEventHandler((event) => { + return "Hello world!"; + }), +); +``` + +This will register the event handler to the app instance and will be called for every request starting with the prefix `/hello`. This means that the event handler will be called for `/hello`, `/hello/world`, `/hello/123`, etc. + +You can define multiple event handlers for the same route. h3 will try to to call them one by one in order of registration until one of them returns a response. This is called `stack runner`. + +```js +app.use( + "/", + defineEventHandler((event) => { + return "First"; + }), +); +app.use( + "/", + defineEventHandler((event) => { + return "Second"; + }), +); +``` + +In this example, the first event handler will be called for every request starting with `/hello` and the second one will never be called. + +However, if you do not return a response from the first event handler, the second one will be called. This is useful to have a _middleware_ pattern. + +```js +app.use( + "/", + defineEventHandler((event) => { + console.log("First"); + // No response returned + }), +); +app.use( + "/", + defineEventHandler((event) => { + return "Second"; + }), +); +``` + +If all handlers get called and no response is returned, h3 will end the request with 404 status response. + +> [!NOTE] +> Using an empty `return` or `return undefined` make a [`404 Not Found`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) status response. +> Also using `return null` will make a [`204 No Content`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204) status reponse. + +> [!TIP] +> Use `return {}` or `return ""` or `return true` to make an explicit response. + +## Event handler options + +The method `use` accepts an optional `options` object as third argument: + +```js +app.use( + "/hello", + defineEventHandler((event) => { + return "Hello world!"; + }), + { + // Options + }, +); +``` + +### `matcher` + +You can define a custom matcher function to have more advanced logic for matching requests but simple than a router. + +For example, you can match only odd URLs, `/1`, `/3`, `/5`, etc.: + +```js +app.use( + "/", + defineEventHandler((event) => { + return "Odd URLs only"; + }), + { + match: (url) => { + return url.substr(1) % 2; + }, + }, +); +``` + +> [!WARNING] +> Do not use the custom matcher as a router. It is not designed for that purpose. Use a [router](/guide/router) instead. + +### `lazy` + +You can provide an async function that h3 will load on first time a request matching the route is received. It's useful for dynamic imports to reduce startup time. + +```js +app.use("/big", () => import("./big-handler"), { lazy: true }); +``` + +This reduce the startup time because the runtime have less code to load and parse when starting the server. + +## Internals + +> [!IMPORTANT] +> This details are mainly informational. **never** directly use internals for production applications! + +h3 app instance has some additional properties. However it is usually not recommended to directly access them unless you know what are you doing! + +- `app.stack`: An ordered array of currently registered event handlers. + - Each item has `route` and `handler` properties. +- `app.options`: Global options object provided when initializing the app. +- `app.handler`: Direct stack handler function (**unsafe to directly call**). diff --git a/docs/1.guide/2.event-handler.md b/docs/1.guide/2.event-handler.md new file mode 100644 index 00000000..597c7cc1 --- /dev/null +++ b/docs/1.guide/2.event-handler.md @@ -0,0 +1,234 @@ +--- +icon: fluent:target-24-regular +--- + +# Event Handler + +> Event handler define application logic. + +After creating an [app instance](/guide/app), you can start defining your application logic using event handlers. + +An event handler is a function that receive an `Event` instance and returns a response. You can compare it to controllers in other frameworks. + +## Defining event handlers + +You can define event handlers using `defineEventHandler` or [`eventHandler` utilities: + +> [!NOTE] +> You can use `defineEventHandler` and `eventHandler` interchangeably. They are aliases. You can use the one you prefer but **stick to it** for consistency. + +```js +import { defineEventHandler } from "h3"; + +defineEventHandler((event) => { + return "Response"; +}); +``` + +The callback function can be sync or async: + +```js +defineEventHandler(async (event) => { + return "Response"; +}); +``` + +### Object Syntax + +You can use an object syntax in `defineEventHandler` for more flexible options. + +```js +defineEventHandler({ + onRequest: [], + onBeforeResponse: [] + handler: (event) => { + return "Response"; + }, +}) +``` + +## Responses Types + +Values returned from event handlers are automatically converted to responses. It can be: + +- JSON serializable value. If returning a JSON object or serializable value, it will be stringified and sent with default `application/json` content-type. +- `string`: Sent as-is using default `application/html` content-type. +- `null`: h3 with end response with `204 - No Content` status code. +- [Web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [node `Readable`](https://nodejs.org/api/stream.html#readable-streams) +- [Web `ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) or [node `Buffer`](https://nodejs.org/api/buffer.html#buffer) +- [Web Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) +- [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) instance. It's supported but **recommended** to throw errors instead of returning them using `createError` utility. + +Any of above values could also be wrapped in a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). This means that you can return a `Promise` from your event handler and h3 will wait for it to resolve before sending the response. + +**Example:** Send HTML response: + +```js +app.use(defineEventHandler(async (event) => "

Hello world!

")); +``` + +**Example:** Send JSON response: + +```js +app.use( + "/api", + defineEventHandler(async (event) => ({ url: event.node.req.url })), +); +``` + +**Example:** Send a promise: + +```js +app.use( + defineEventHandlers(async (event) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ url: event.node.req.url }); + }, 1000); + }); + }), +); +``` + +## Error Handling + +You can easily control the error returned by using the `createError` utility. + +```js +import { createError, defineEventHandler } from "h3"; + +app.use( + "/hello", + defineEventHandler((event) => { + throw createError({ + status: 400, + message: "An error occured", + }); + }), +); +``` + +This will end the request with `400 - Bad Request` status code and the following JSON response: + +```json +{ + "status": 400, + "message": "An error occured" +} +``` + +### Internal errors + +If during calling an event handler an error with `new Error()` will be thrown (without `create Error`), h3 will automatically catch as a [`500 - Internal Server Error`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) status response considering it an unhandled error. + +```js +app.use( + "/hello", + defineEventHandler((event) => { + // Do NOT do this and use createError()! + throw new Error("Something went wrong"); + }), +); +``` + +## Lazy event handlers + +You can define lazy event handlers using `defineLazyEventHandler` or `lazyEventHandler` utilities. This allow you to define some one-time logic that will be executed only once when the first request matching the route is received. + +A lazy event handler must return an event handler: + +```js +import { defineLazyEventHandler } from "h3"; + +app.use( + defineLazyEventHandler(() => { + console.log("This will be executed only once"); + // This will be executed only once + return defineEventHandler((event) => { + // This will be executed on every request + return "Response"; + }); + }), +); +``` + +This is useful to define some one-time logic such as configuration, class initialization, heavy computation, etc. + +## Middleware + +Event handlers that don't return any value act as middleware. They can be used to add side effects to your application such as logging, caching, etc or to modify the request or response. + +> [!TIP] +> Middleware pattern is **not recommanded** for h3 in general. Side effects can affect global application performance and make tracing logic harder. +> Instead use h3 composables and object syntax hooks. + +Similar to normal event handlers, you can define middleware using `defineEventHandler` or `eventHandler` utilities: + +```js +defineEventHandler((event) => { + console.log(`Middleware. Path: ${event.path}`); +}); +``` + +> [!IMPORTANT] +> Middleware **must not** return any value or directly return a response for `event`. +> If you return a response, it will act as a normal event handler! + +### Registering middleware + +Then, you can register middleware to [app instance](/guide/app) using the `use` method: + +```js +app.use( + defineEventHandler((event) => { + console.log("Middleware 1"); + }), +); +app.use( + defineEventHandler((event) => { + console.log("Middleware 2"); + }), +); +app.use( + defineEventHandler((event) => { + return "Response"; + }), +); +``` + +You can define as much middleware as you need. They will be called in order of registration. + +## Converting to h3 handler + +There are situations that you might want to convert an event handler or utility made for Node.js or another framework to h3. +There are built-in utils to do this.! + +### Converting from Node.js handlers + +If you have a legacy request handler with `(req, res) => {}` syntax made for Node.js, you can use `fromNodeListener` to convert it to an h3 event handler. + +```js [app.mjs] +import { createApp, fromNodeMiddleware } from "h3"; + +import exampleMiddleware from "example-node-middleware"; + +export const app = createApp(); + +app.use(fromNodeListener(exampleMiddleware())); +``` + +> [!TIP] +> For example, this will help you to use [Vite Middleware mode](https://vitejs.dev/config/server-options.html#server-middlewaremode) with h3 apps. + +### Converting from Web handlers + +You can convert a fetch-like function (with `Request => Response` signuture) into an h3 event handler using `fromWebHandler` util. + +```js [app.mjs] +import { webHandler } from "web-handler"; // This package doesn't exist, it's just an example +import { createApp, fromWebHandler } from "h3"; + +export const app = createApp(); + +app.use(fromWebHandler(webHandler)); +``` diff --git a/docs/1.guide/4.event.md b/docs/1.guide/4.event.md new file mode 100644 index 00000000..8e8f61ef --- /dev/null +++ b/docs/1.guide/4.event.md @@ -0,0 +1,122 @@ +--- +icon: material-symbols-light:data-object +--- + +# Event Object + +> Event object carries an incoming request and context. + +Everytime a new HTTP requerst comes, h3 internally create an Event object and passes it though event handlers until sending response. + +An event is passed through all the lifecycle hooks and composable utils to use it as context. + +**Example:** + +```js +import { defineEventHandler, getQuery, readBody } from "h3"; + +app.use(defineEventHandler((event) => { + // Log event. `.toString()` stringifies to a simple string like `[GET] /` + console.log(`Request: ${event.toString()}`); + + // Parse query parms + const query = getQuery(event) + + // Try to read request body + const body = await readBody(event).catch(() => {}) + + // Echo back request as response + return { + path: event.path, + method: event.method, + query, + body, + } +})); +``` + +## Properties + +The main properties of an event are: + +### `event.node` + +The `event.node` allows to access the native Node.js request and response. In runtimes other than Node.js/Bun, h3 makes a compatible shim using [unjs/unenv](https://unenv.unjs.io). + +> [!IMPORTANT] +> Try to **avoid** depending on `event.node.*` context as much as you can and instead prefer h3 utils. + +```js +defineEventHandler((event) => { + event.node.req; // Node.js HTTP Request + event.node.res; // Node.js HTTP Response +}); +``` + +### `event.web?` + +Only if available, it is an object with [`request`](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) and [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) propertiers to access native web request context. + +### `event.method` + +Access to the normalized (uppercase) request [method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). + +### `event.path` + +Access to the request request path. (**Example:** `/test?test=123`) + +- `context` with some context information about the request. +- `headers` with a **normalized version** of the headers of the request. +- `handled` with a boolean that indicates if the request has terminated. + +### `event.headers` + +Access tp the normalized request [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers). + +> [!TIP] +> You can alternatively use `getHeaders(event)` or `getHeader(event, name)` for a simplified interface. + +### `event.context` + +The context is an object that contains arbitrary information abut the request. +You can store your custom properies inside `event.context` to share across composable utils. + +### `event.handled` + +Specifies if response is already handled or not. Initially for each request it is `false` and when a response is generated, it is set to `true`. + +**Advanced:** If you manually handle the response, set it to `true` to tell h3 stop sending any responses. + +## Methods + +Actually, h3 provide a function to help you to create a response before the end of the request. + +### `event.respondWith` + +The `respondWith` method is used to create a response without ending the request. + +You must craft a response using the [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response) constructor. + +> [!TIP] +> Prefer explicit `return` over `respondWith` as best practice. + +> [!IMPORTANT] +> A `responseWith` will **always** take precedence over the returned value, from current and next event handlers. If there is no returned value, the request will continue until the end of the stack runner. + +**Example:** + +```js +defineEventHandler((event) => { + await event.respondWith(new Response("Hello World")); + return "..."; // DOES NOT WORKS +}); + +app.use( + defineEventHandler((event) => { + await event.respondWith(new Response("Hello World")); + return "..."; // DOES NOT WORK + }), +); +``` + +With this example, the client will receive `Hello World`. diff --git a/docs/1.guide/5.router.md b/docs/1.guide/5.router.md new file mode 100644 index 00000000..ccd869a4 --- /dev/null +++ b/docs/1.guide/5.router.md @@ -0,0 +1,150 @@ +--- +icon: solar:route-linear +--- + +# Router + +> Split your application using routes. + +Using h3 router allows more advanced and convenient routing system such as parameters and HTTP methods while the [app instance](/guide/app) itself only allows static prefix matching. + +> [!NOTE] +> Internally h3 uses [unjs/radix3](https://radix3.unjs.io) for route matching. + +## Usage + +First, you need to create a router using `createRouter` utility and add it to app stack. + +```js +import { createApp, createRouter, defineEventHandler } from "h3"; + +const app = createApp(); +const router = createRouter(); +app.use(router;) +``` + +Then, you can register a route to the router using a method where the name is the HTTP method: + +```js +router.get( + "/hello", + defineEventHandler((event) => { + return "Hello world!"; + }), +); +``` + +In this example, we register a route for the `GET` method. This means that the event handler will be called only for `GET` requests for the `/hello` route. If you try to send a `POST` or a request to `/hello/world`, the event handler will not be called. + +> [!NOTE] +> You can still use `use` to register an event handler to the router. It will be called for every HTTP methods. + +This means that you can register multiple event handlers for the same route with different methods: + +```js +router + .get( + "/hello", + defineEventHandler((event) => { + return "GET Hello world!"; + }), + ) + .post( + "/hello", + defineEventHandler((event) => { + return "POST Hello world!"; + }), + ); +``` + +## Route params + +You can define parameters in your routes using `:` prefix: + +```js +router.get( + "/hello/:name", + defineEventHandler((event) => { + return `Hello ${event.context.params.name}!`; + }), +); +``` + +In this example, the `name` parameter will be available in the `event.context.params` object. + +If you send a request to `/hello/world`, the event handler will return `Hello world!`. + +> [!NOTE] +> You can use as many parameters as you want in your routes. + +## Wildcard matcher + +Instead of named params, you can use `*` for unnamed + +```js +router.get( + "/hello/*", + defineEventHandler((event) => { + return `Hello ${event.context.params._}!`; + }), +); +``` + +This will match both `/hello` and sub routes such as `/hello/world` or `/hello/123`. But it will only match one level of sub routes. + +You can access to the wildcard content using `event.context.params._` where `_` is a string containing the wildcard content. + +If you need to match multiple levels of sub routes, you can use `**` prefix: + +```js +router.get( + "/hello/**", + defineEventHandler((event) => { + return `Hello ${event.context.params._}!`; + }), +); +``` + +This will match `/hello`, `/hello/world`, `/hello/123`, `/hello/world/123`, etc. + +> [!NOTE] +> Param `_` will store the full wildcard content as a single string. + +## Nested Routers + +You can nest routers to create a tree of routers. This is useful to split your application into multiple parts like the API and the website. + +```js +import { createApp, createRouter, defineEventHandler, useBase } from "h3"; + +export const app = createApp(); + +const websiteRouter = createRouter().get( + "/", + defineEventHandler((event) => { + return "Hello world!"; + }), +); + +const apiRouter = createRouter().get( + "/hello", + defineEventHandler((event) => { + return "Hello API!"; + }), +); + +websiteRouter.use("/api/**", useBase("/api", apiRouter.handler)); + +app.use(websiteRouter); +``` + +We create two routers. The first one, called `websiteRouter` is the main one. The second one, we create a second router called `apiRouter`. + +Then, we connect the `apiRouter` to the `websiteRouter` using `use` and a wildcard to be sure that every routes starting and HTTP methods with `/api` will be handled by the `apiRouter`. + +> [!NOTE] +> Do not forget to use `.handler` to get the event handler from the router. + +`useBase` is used to add a prefix to each routes of the router. In this example, we add `/api` prefix to each routes of the `apiRouter`. So, the route `/hello` will be `/api/hello`. + +Finally, we register the `websiteRouter` to the `app` instance. diff --git a/docs/1.guide/6.websocket.md b/docs/1.guide/6.websocket.md new file mode 100644 index 00000000..05acbbbd --- /dev/null +++ b/docs/1.guide/6.websocket.md @@ -0,0 +1,115 @@ +--- +icon: cib:socket-io +--- + +# WebSockets + +> H3 has built-in support for cross platform WebSocket and SSE. + +H3 natively supports runtime agnostic [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) API using [CrossWS](https://crossws.unjs.io/). + +::tip +You can define WebSocket handlers in your exisiting [Event Handlers](/guide/event-handler) to define multiple websocket handlers, dynamically matched with your same route defenitions! +:: + +:read-more{title="WebSocket in MDN" to="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket"} + +:read-more{title="CrossWS" to="https://crossws.unjs.io/"} + +> [!IMPORTANT] +> WebSockets support is currently experimental and available in [nightly channel](/guide/nightly). + +## Usage + +### Example + +> [!TIP] +> Yuu can use `npx listhen --ws -w websocket.ts` to run this example + + + +```ts [websocket.ts] +import { createApp, defineEventHandler, defineWebSocketHandler } from "h3"; + +export const app = createApp(); + +app.use( + defineEventHandler(() => + fetch( + "https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html", + ).then((r) => r.text()), + ), +); + +app.use( + "/_ws", + defineWebSocketHandler({ + open(peer) { + console.log("[ws] open", peer); + }, + + message(peer, message) { + console.log("[ws] message", peer, message); + if (message.text().includes("ping")) { + peer.send("pong"); + } + }, + + close(peer, event) { + console.log("[ws] close", peer, event); + }, + + error(peer, error) { + console.log("[ws] error", peer, error); + }, + }), +); + +``` + + + +## Server Sent Events (SSE) + +As an alternative to WebSockets, you can use [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). + +H3 has a built-in API to create server-sent events using `createEventStream(event)` utility. + +> [!IMPORTANT] +> SSE support is currently experimental and available in [nightly channel](/guide/nightly). + +### Example + + + +```ts [server-sent-events.ts] +import { createApp, createRouter, eventHandler, createEventStream } from "h3"; + +export const app = createApp(); + +const router = createRouter(); +app.use(router); + +router.get( + "/", + eventHandler((event) => { + const eventStream = createEventStream(event); + + // Send a message every second + const interval = setInterval(async () => { + await eventStream.push("Hello world"); + }, 1000); + + // cleanup the interval and close the stream when the connection is terminated + eventStream.onClosed(async () => { + clearInterval(interval); + await eventStream.close(); + }); + + return eventStream.send(); + }), +); + +``` + + diff --git a/docs/1.guide/99.nightly.md b/docs/1.guide/99.nightly.md new file mode 100644 index 00000000..59fa4824 --- /dev/null +++ b/docs/1.guide/99.nightly.md @@ -0,0 +1,41 @@ +--- +icon: ph:moon +--- + +# Nightly Builds + +You can opt-in to early test latest h3 changes using automated nightly release channel. + +If you are directly using `h3` as a dependency in your project: + +```json +{ + "dependencies": { + "h3": "npm:h3-nightly@latest" + } +} +``` + +If you are using a framework (for example [Nuxt](https://nuxt.com/) or [Nitro](https://nitro.unjs.io/)) that is using `h3`: + +pnpm and yarn: + +```json +{ + "resolutions": { + "h3": "npm:h3-nightly@latest" + } +} +``` + +npm: + +```json +{ + "overrides": { + "h3": "npm:h3-nightly@latest" + } +} +``` + +**Note:** Make sure to recreate lockfile and `node_modules` after reinstall to avoid hoisting issues. diff --git a/docs/2.utils/0.index.md b/docs/2.utils/0.index.md new file mode 100644 index 00000000..988b2273 --- /dev/null +++ b/docs/2.utils/0.index.md @@ -0,0 +1,17 @@ +--- +icon: ph:function-bold +--- + +# Utils + +> Keep your severs light and fast with composables. + +H3 is a composable framework. Instead of providing a big core, you start with a lightweight [app instance](/guide/app) and for every functionality, there is either a built-in utility or you can make yours. + +Composable utilities have huge advantages comparing to traditional plugin/middleware approaches: + +✅ Your server only includes and runs the code that is needed
+✅ You can extend your server functionality easily without adding global plugins
+✅ The usage is explicit and clean with less global middleware and plugins
+ +All utilities, have access to [event object](/guide/event). This way they can access incoming request and use a shared context with `event.context`. diff --git a/docs/2.utils/1.request.md b/docs/2.utils/1.request.md new file mode 100644 index 00000000..1d411c3f --- /dev/null +++ b/docs/2.utils/1.request.md @@ -0,0 +1,206 @@ +--- +icon: material-symbols-light:input +--- + +# Request + +> Utilities to access incoming request + + + +### `assertMethod(event, expected, allowHead?)` + +Asserts that the incoming request method is of the expected type using `isMethod`. + +If the method is not allowed, it will throw a 405 error with the message "HTTP method is not allowed". + +### `getHeader(event, name)` + +Get a request header by name. + +### `getHeaders(event)` + +Get the request headers object. + +Array headers are joined with a comma. + +### `getQuery(event)` + +Get query the params object from the request URL parsed with [unjs/ufo](https://ufo.unjs.io). + +### `getRequestHeader(event, name)` + +Get a request header by name. + +### `getRequestHeaders(event)` + +Get the request headers object. + +Array headers are joined with a comma. + +### `getRequestHost(event, opts: { xForwardedHost? })` + +Get the request hostname. + +If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. + +If no host header is found, it will default to "localhost". + +### `getRequestIP(event)` + +Try to get the client IP address from the incoming request. + +If `xForwardedFor` is `true`, it will use the `x-forwarded-for` header if it exists. + +### `getRequestProtocol(event, opts: { xForwardedProto? })` + +Get the request protocol. + +If `x-forwarded-proto` header is set to "https", it will return "https". You can disable this behavior by setting `xForwardedProto` to `false`. + +If protocol cannot be determined, it will default to "http". + +### `getRequestURL(event, opts: { xForwardedHost?, xForwardedProto? })` + +Generated the full incoming request URL using `getRequestProtocol`, `getRequestHost` and `event.path`. + +If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. + +If `xForwardedProto` is `false`, it will not use the `x-forwarded-proto` header. + +### `getRouterParam(event, name, opts: { decode? })` + +Get a matched route param by name. + +### `getRouterParams(event, opts: { decode? })` + +Get matched route params. + +If `decode` option is `true`, it will decode the matched route params using `decodeURI`. + +### `getValidatedQuery(event, validate)` + +Get the query param from the request URL parsed with [unjs/ufo](https://ufo.unjs.io) and validated with validate function. + +### `getValidatedRouterParams(event, validate, opts: { decode? })` + +Get matched route params and validate with validate function. + +### `isMethod(event, expected, allowHead?)` + +Checks if the incoming request method is of the expected type. + +If `allowHead` is `true`, it will allow `HEAD` requests to pass if the expected method is `GET`. + +### `toWebRequest(event)` + +Convert the H3Event to a WebRequest object. + +**NOTE:** This function is not stable and might have edge cases that are not handled properly. + + + + + +### `getRequestFingerprint(event, opts)` + +Get a unique fingerprint for the incoming request. + + + +## Body utils + + + +### `getRequestWebStream(event)` + +Captures a stream from a request. + +### `readBody(event, options: { strict? })` + +Reads request body and tries to safely parse using [destr](https://github.com/unjs/destr). + +**Example:** + +```ts +export default defineEventHandler(async (event) => { + const body = await readBody(event); +}); +``` + +### `readFormData(event)` + +Constructs a FormData object from an event, after converting it to a a web request. + +**Example:** + +```ts +export default defineEventHandler(async (event) => { + const formData = await readFormData(event); + const email = formData.get("email"); + const password = formData.get("password"); +}); +``` + +### `readMultipartFormData(event)` + +Tries to read and parse the body of a an H3Event as multipart form. + +**Example:** + +```ts +export default defineEventHandler(async (event) => { + const formData = await readMultipartFormData(event); + // The result could look like: + // [ + // { + // "data": "other", + // "name": "baz", + // }, + // { + // "data": "something", + // "name": "some-other-data", + // }, + // ]; +}); +``` + +### `readRawBody(event, encoding)` + +Reads body of the request and returns encoded raw string (default), or `Buffer` if encoding is falsy. + +**Example:** + +```ts +export default defineEventHandler(async (event) => { + const body = await readRawBody(event, "utf-8"); +}); +``` + +### `readValidatedBody(event, validate)` + +Tries to read the request body via `readBody`, then uses the provided validation function and either throws a validation error or returns the result. + +You can use a simple function to validate the body or use a library like `zod` to define a schema. + +**Example:** + +```ts +export default defineEventHandler(async (event) => { + const body = await readValidatedBody(event, (body) => { + return typeof body === "object" && body !== null; + }); +}); +``` + +**Example:** + +```ts +import { z } from "zod"; +export default defineEventHandler(async (event) => { + const objectSchema = z.object(); + const body = await readValidatedBody(event, objectSchema.safeParse); +}); +``` + + diff --git a/docs/2.utils/2.reponse.md b/docs/2.utils/2.reponse.md new file mode 100644 index 00000000..029d6d43 --- /dev/null +++ b/docs/2.utils/2.reponse.md @@ -0,0 +1,148 @@ +--- +icon: material-symbols-light:output +--- + +# Response + +> Utilities to send response headers and data + + + +### `appendHeader(event, name, value)` + +Append a response header by name. + +### `appendHeaders(event, headers)` + +Append the response headers. + +### `appendResponseHeader(event, name, value)` + +Append a response header by name. + +### `appendResponseHeaders(event, headers)` + +Append the response headers. + +### `clearResponseHeaders(event, headerNames?)` + +Remove all response headers, or only those specified in the headerNames array. + +### `defaultContentType(event, type?)` + +Set the response status code and message. + +### `getResponseHeader(event, name)` + +Alias for `getResponseHeaders`. + +### `getResponseHeaders(event)` + +Get the response headers object. + +### `getResponseStatus(event)` + +Get the current response status code. + +### `getResponseStatusText(event)` + +Get the current response status message. + +### `isStream(data)` + +Checks if the data is a stream. (Node.js Readable Stream, React Pipeable Stream, or Web Stream) + +### `isWebResponse(data)` + +Checks if the data is a Response object. + +### `removeResponseHeader(event, name)` + +Remove a response header by name. + +### `send(event, data?, type?)` + +Directly send a response to the client. + +**Note:** This function should be used only when you want to send a response directly without using the `h3` event. Normaly you can directly `return` a value inside event handlers. + +### `sendIterable(event, iterable)` + +Iterate a source of chunks and send back each chunk in order. Supports mixing async work toghether with emitting chunks. + +Each chunk must be a string or a buffer. + +For generator (yielding) functions, the returned value is treated the same as yielded values. + +**Example:** + +```ts +sendIterable(event, work()); +async function* work() { + // Open document body + yield "\n

Executing...

    \n"; + // Do work ... + for (let i = 0; i < 1000) { + await delay(1000); + // Report progress + yield `
  1. Completed job #`; + yield i; + yield `
  2. \n`; + } + // Close out the report + return `
`; +} +async function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +``` + +### `sendNoContent(event, code?)` + +Respond with an empty payload.
+ +Note that calling this function will close the connection and no other data can be sent to the client afterwards. + +### `sendRedirect(event, location, code)` + +Send a redirect response to the client. + +It adds the `location` header to the response and sets the status code to 302 by default. + +In the body, it sends a simple HTML page with a meta refresh tag to redirect the client in case the headers are ignored. + +### `sendStream(event, stream)` + +Send a stream response to the client. + +Note: You can directly `return` a stream value inside event handlers alternatively which is recommended. + +### `sendWebResponse(event, response)` + +Send a Response object to the client. + +### `setHeader(event, name, value)` + +Set a response header by name. + +### `setHeaders(event)` + +Set the response headers. + +### `setResponseHeader(event, name, value)` + +Set a response header by name. + +### `setResponseHeaders(event)` + +Set the response headers. + +### `setResponseStatus(event, code?, text?)` + +Set the response status code and message. + +### `writeEarlyHints(event, hints, cb)` + +Write `HTTP/1.1 103 Early Hints` to the client. + + diff --git a/docs/2.utils/98.advanced.md b/docs/2.utils/98.advanced.md new file mode 100644 index 00000000..7ea32533 --- /dev/null +++ b/docs/2.utils/98.advanced.md @@ -0,0 +1,196 @@ +--- +icon: mingcute:plus-line +--- + +# Advanced + +> More utilities + +## Session utils + + + +### `clearSession(event, config)` + +Clear the session data for the current request. + +### `getSession(event, config)` + +Get the session for the current request. + +### `sealSession(event, config)` + +Encrypt and sign the session data for the current request. + +### `unsealSession(_event, config, sealed)` + +Decrypt and verify the session data for the current request. + +### `updateSession(event, config, update?)` + +Update the session data for the current request. + +### `useSession(event, config)` + +Create a session manager for the current request. + + + +## Cookie utils + + + +### `deleteCookie(event, name, serializeOptions?)` + +Remove a cookie by name. + +### `getCookie(event, name)` + +Get a cookie value by name. + +### `parseCookies(event)` + +Parse the request to get HTTP Cookie header string and returning an object of all cookie name-value pairs. + +### `setCookie(event, name, value, serializeOptions?)` + +Set a cookie value by name. + + + +## Sanitize + + + +### `sanitizeStatusCode(statusCode?, defaultStatusCode)` + +Make sure the status code is a valid HTTP status code. + +### `sanitizeStatusMessage(statusMessage)` + +Make sure the status message is safe to use in a response. + +Allowed characters: horizontal tabs, spaces or visible ascii characters: https://www.rfc-editor.org/rfc/rfc7230#section-3.1.2 + + + +## Route + + + +### `useBase(base, handler)` + +Prefixes and executes a handler with a base path. + +**Example:** + +```ts +const app = createApp(); +const router = createRouter(); +const apiRouter = createRouter().get( + "/hello", + defineEventHandler((event) => { + return "Hello API!"; + }), +); +router.use("/api/**", useBase("/api", apiRouter.handler)); +app.use(router.handler); +``` + + + +## Cache + + + +### `handleCacheHeaders(event, opts)` + +Check request caching headers (`If-Modified-Since`) and add caching headers (Last-Modified, Cache-Control) Note: `public` cache control will be added by default + + + +## Proxy + + + +### `fetchWithEvent(event, req, init?, options?: { fetch: F })` + +Make a fetch request with the event's context and headers. + +### `getProxyRequestHeaders(event)` + +Get the request headers object without headers known to cause issues when proxying. + +### `proxyRequest(event, target, opts)` + +Proxy the incoming request to a target URL. + +### `sendProxy(event, target, opts)` + +Make a proxy request to a target URL and send the response back to the client. + + + +## CORS + + + +### `appendCorsHeaders(event, options)` + +Append CORS headers to the response. + +### `appendCorsPreflightHeaders(event, options)` + +Append CORS preflight headers to the response. + +### `handleCors(event, options)` + +Handle CORS for the incoming request. + +If the incoming request is a CORS preflight request, it will append the CORS preflight headers and send a 204 response. + +If return value is `true`, the request is handled and no further action is needed. + +### `isCorsOriginAllowed(origin, options)` + +Check if the incoming request is a CORS request. + +### `isPreflightRequest(event)` + +Check if the incoming request is a CORS preflight request. + + + +## Server Sent Events (SSE) + + + +### `createEventStream(event, opts?)` + +Initialize an EventStream instance for creating [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) + +**Example:** + +```ts +import { createEventStream, sendEventStream } from "h3"; + +eventHandler((event) => { + const eventStream = createEventStream(event); + + // Send a message every second + const interval = setInterval(async () => { + await eventStream.push("Hello world"); + }, 1000); + + // cleanup the interval and close the stream when the connection is terminated + eventStream.onClosed(async () => { + console.log("closing SSE..."); + clearInterval(interval); + await eventStream.close(); + }); + + return eventStream.send(); +}); +``` + + diff --git a/docs/2.utils/99.community.md b/docs/2.utils/99.community.md new file mode 100644 index 00000000..90cf3fb2 --- /dev/null +++ b/docs/2.utils/99.community.md @@ -0,0 +1,29 @@ +--- +icon: lets-icons:external +--- + +# Community + +> Utils from community 💛 + +You can use external h3 event utilities made by the community. + +## `h3-typebox` + +:read-more{to="https://github.com/kevinmarrec/h3-typebox"} + +## `h3-zod` + +:read-more{to="https://github.com/wobsoriano/h3-zod"} + +## `h3-valibot` + +:read-more{to="https://github.com/intevel/h3-valibot"} + +## `h3-compression` + +:read-more{to="https://github.com/CodeDredd/h3-compression"} + +## `@intlify/h3` + +:read-more{to="https://github.com/intlify/h3"} diff --git a/docs/3.adapters/0.index.md b/docs/3.adapters/0.index.md new file mode 100644 index 00000000..28b96e32 --- /dev/null +++ b/docs/3.adapters/0.index.md @@ -0,0 +1,27 @@ +--- +icon: codicon:run-all +--- + +# Adapters + +> Run h3 everywhere using adapters. + +The [app instance](/guide/app) of h3 is lightweight without any logic about runtime it is going to run. +Using h3 adapters, we can easily integrate server with each runtime. + +::read-more{to="https://nitro.unjs.io"} +Check out [Nitro](https://nitro.unjs.io) for a full featured server toolkit and more deployment options. +:: + +There are 3 base adapters: + +- [Node.js](/adapters/node) +- [Web](/adapters/web) +- [Plain](/adapters/plain) + +Also see guides for specific runtimes: + +- [Bun](/adapters/bun) +- [Cloudflare](/adapters/cloudflare) +- [Deno](/adapters/deno) +- [Netlify](/adapters/netlify) diff --git a/docs/3.adapters/1.node.md b/docs/3.adapters/1.node.md new file mode 100644 index 00000000..5267d7b9 --- /dev/null +++ b/docs/3.adapters/1.node.md @@ -0,0 +1,62 @@ +--- +icon: akar-icons:node-fill +--- + +# Node.js + +> Natively run h3 servers with Node.js. + +In order to start h3 apps in [Node.js](https://nodejs.org/), use `toNodeListener` adapter to convert h3 app into a [Node.js requestListener](https://nodejs.org/docs/latest/api/http.html#httpcreateserveroptions-requestlistener). + +## Usage + +First, create an h3 app: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create Node.js server entry: + +```js [server.mjs] +import { createServer } from "node:http"; +import { toNodeListener } from "h3"; +import { app } from "./app.mjs"; + +createServer(toNodeListener(app)).listen(process.env.PORT || 3000); +``` + +Now, you can run you h3 app natively with Node.js: + +```bash [terminal] +node ./server.mjs +``` + +## Using listhen + +Alternatively, you can use [unjs/listhen](https://listhen.unjs.io). In this method, you only need to make `app.mjs` with a `default` or `app` export. + +Run this command to run your servers: + +```sh +npx --yes listhen ./app.ts +``` + +## WebSocket support + +:read-more{to="https://crossws.unjs.io/adapters/node"} + +> [!TIP] +> When using listhen method, websocket is supported out of the box! + +```ts +import wsAdapter from "crossws/adapters/node"; + +const { handleUpgrade } = wsAdapter(app.websocket); + +server.on("upgrade", handleUpgrade); +``` diff --git a/docs/3.adapters/2.web.md b/docs/3.adapters/2.web.md new file mode 100644 index 00000000..ed6ef31f --- /dev/null +++ b/docs/3.adapters/2.web.md @@ -0,0 +1,48 @@ +--- +icon: twemoji:spider-web +--- + +# Web + +> Run your h3 apps in edge runtimes with Web API compatibility. + +In order to run h3 apps in web compatible edge runtimes supporting [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) with [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), use `toWebHandler` adapter to convert h3 app into a fetch-like function. + +## Usage + +First, create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create web entry: + +```js [web.mjs] +import { toWebHandler } from "h3"; +import { app } from "./app.mjs"; + +// Create Web Adapter +export const handler = toWebHandler(app); + +// Integrate handler with your runtime. +// Input is a Request and response is Promise +``` + +### Local testing + +You can test adapter using any compatible JavaScript runtime by passing a Request object. + +```js [web.test.mjs] +import { handler } from "./web.mjs"; + +const response = await handler(new Request(new URL("/", "http://localhost"))); + +console.log(await response.text()); // Hello world! +``` + +Run with `node ./web.test.mjs`. diff --git a/docs/3.adapters/3.plain.md b/docs/3.adapters/3.plain.md new file mode 100644 index 00000000..ae0b35bd --- /dev/null +++ b/docs/3.adapters/3.plain.md @@ -0,0 +1,62 @@ +--- +icon: material-symbols-light:data-object +--- + +# Plain + +> Run h3 servers into any unknown runtime! + +There might be cases where your runtime is nither Node.js or Web compatible. Using plain adapter you can have an object input/output interface. + +> [!NOTE] +> This can be also be particulary useful for testing your server or running inside lambda-like environments. + +## Usage + +First, create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create plain entry: + +```js [plain.mjs] +import { toPlainHandler } from "h3"; +import { app } from "./app.mjs"; + +export const handler = toPlainHandler(app); +``` + +### Local testing + +You can test adapter using any JavaScript runtime. + +```js [plain.test.mjs] +import { handler } from "./plain.mjs"; + +const response = await handlerRequest({ + method: "GET", + path: "/", + headers: { + "x-test": "test", + }, + body: undefined, + context: {}, +}); +``` + +Example response: + +```js +{ + status: 200, + statusText: '', + headers: [ [ 'content-type', 'text/html' ] ], + body: 'Hello world!' +} +``` diff --git a/docs/3.adapters/bun.md b/docs/3.adapters/bun.md new file mode 100644 index 00000000..18a39d3c --- /dev/null +++ b/docs/3.adapters/bun.md @@ -0,0 +1,66 @@ +--- +icon: simple-icons:bun +--- + +# Bun + +> Run your h3 apps with Bun + +In order to run h3 apps in [Bun](https://bun.sh/), use the [Web Adapter](/adapters/web). + +> [!NOTE] +> Alternatively you can use [Node.js adapter](/adapters/node) as Bun is fully compatible with Node.js API! + +## Usage + +Create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create Bun server entry: + +```js [server.mjs] +import { toWebHandler } from "h3"; +import { app } from "./app.mjs"; + +const server = Bun.serve({ + port: 3000, + fetch: toWebHandler(app), +}); +``` + +Now, your can run Bun server: + +```bash +bun --bun ./server.mjs +``` + +## WebSocket support + +:read-more{to="https://crossws.unjs.io/adapters/bun"} + +```ts +import wsAdapter from "crossws/adapters/bun"; + +const { websocket, handleUpgrade } = wsAdapter(app.websocket); + +const handler = toWebHandler(app) + +const server = Bun.serve({ + port: 3000, + websocket, + fetch(req, server) { + if (await handleUpgrade(req, server)) { + return; + } + return handler(req) + } +}); +``` + diff --git a/docs/3.adapters/cloudflare.md b/docs/3.adapters/cloudflare.md new file mode 100644 index 00000000..14e10f7f --- /dev/null +++ b/docs/3.adapters/cloudflare.md @@ -0,0 +1,85 @@ +--- +icon: devicon-plain:cloudflareworkers +--- + +# Cloudflare + +> Run your h3 apps in Cloudflare Workers + +You can directly host your h3 applications to [Cloudflare Workers](https://workers.cloudflare.com/) using [Web Adapter](/adapters/web). + +## Usage + +Create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create entry for a Cloudflare Worker: + +```js [cloudflare.mjs] +import { toWebHandler } from "h3"; +import { app } from "./app.mjs"; + +const handler = toWebHandler(app); + +export default { + async fetch(request, env, ctx) { + return handler(request, { + cloudflare: { env, ctx }, + }); + }, +}; +``` + +Then, create a simple `wrangler.toml`: + +```ini [wrangler.toml] +name = "h3-app" +main = "cloudflare.mjs" +compatibility_date = "2023-08-01" +``` + +Finally, use `wrangler dev` to locally preview: + +```bash +npx wrangler dev +``` + +To deploy, use `wrangler deploy`: + +```bash +npx wrangler deploy +``` + +## WebSocket support + +:read-more{to="https://crossws.unjs.io/adapters/cloudflare"} + +```ts +import wsAdapter from "crossws/adapters/cloudflare"; + +const { handleUpgrade } = wsAdapter(app.websocket); + +export default { + async fetch(request, env, ctx) { + if (request.headers.get("upgrade") === "websocket") { + return handleUpgrade(request, env, context); + } + return handler(request, { + cloudflare: { env, ctx }, + }); + }, +}; +``` + +--- + +::read-more +👉 See [pi0/h3-on-edge](https://github.com/pi0/h3-on-edge) demo for a fully working example ([deployment](https://h3-on-edge.pi0.workers.dev/)). +:: diff --git a/docs/3.adapters/deno.md b/docs/3.adapters/deno.md new file mode 100644 index 00000000..d22064d1 --- /dev/null +++ b/docs/3.adapters/deno.md @@ -0,0 +1,77 @@ +--- +icon: teenyicons:deno-solid +--- + +# Deno + +> Run your h3 apps in Deno Deploy + +You can directly host your h3 applications to [Deno Deploy](https://deno.com/deploy) using [Web Adapter](/adapters/web). + +## Usage + +Create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create entry for Deno Deploy: + +```js [deno.mjs] +import { toWebHandler } from "h3"; +import { app } from "./app.mjs"; + +Deno.serve(toWebHandler(app)); +``` + +Create an `import_map.json`: + +```json [import_map.json] +{ + "imports": { + "h3": "https://esm.sh/h3@latest" + } +} +``` + +Finally, use `deno run` to locally preview: + +```bash [terminal] +deno run --allow-net ./deno.mjs +``` + +To deploy, use `deployctl deploy`: + +```bash [terminal] +deployctl deploy --prod --exclude=node_modules --import-map=./import_map.json ./deno.mjs +``` + +## WebSocket support + +:read-more{to="https://crossws.unjs.io/adapters/deno"} + +```ts +import wsAdapter from "crossws/adapters/deno"; + +const handler = toWebHandler(app) + +const { handleUpgrade } = wsAdapter(app.websocket); + +Deno.serve(request => { + if (request.headers.get("upgrade") === "websocket") { + return handleUpgrade(request); + } + return handler(request) +}) +``` + +--- + +::read-more +See [pi0/h3-on-edge](https://github.com/pi0/h3-on-edge) demo for a fully working example ([deployment](https://h3-on-edge.deno.dev/)). +:: diff --git a/docs/3.adapters/netlify.md b/docs/3.adapters/netlify.md new file mode 100644 index 00000000..3b2f30f0 --- /dev/null +++ b/docs/3.adapters/netlify.md @@ -0,0 +1,68 @@ +--- +icon: teenyicons:netlify-outline +--- + +# Netlify + +> Run your h3 apps in Netlify Edge + +You can directly host your h3 applications to [Netlify Edge](https://www.netlify.com/platform/core/edge/) using [Web Adapter](/adapters/web). + +## Usage + +Create app entry: + +```js [app.mjs] +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use(defineEventHandler(() => "Hello world!")); +``` + +Create entry for netlify-edge: + +```js [netlify/index.mjs] +import { toWebHandler } from "h3"; +import { app } from "./app.mjs"; + +export const handler = toWebHandler(app); +``` + +Then, create `import_map.json`: + +```json [import_map.json] +{ + "imports": { + "h3": "https://esm.sh/h3@latest" + } +} +``` + +Create `netlify.toml`: + +```ini [netlify.toml] +[build] + edge_functions = "netlify" + +[functions] + deno_import_map = "./import_map.json" +``` + +Finally, use `netlify dev` to locally preview: + +```bash [terminal] +npx netlify dev +``` + +To deploy, use `netlify deploy`: + +```bash [terminal] +npx netlify deploy --prod +``` + +--- + +::read-more +See [pi0/h3-on-edge](https://github.com/pi0/h3-on-edge) demo for a fully working example. +:: diff --git a/docs/4.examples/0.index.md b/docs/4.examples/0.index.md new file mode 100644 index 00000000..b4197cbe --- /dev/null +++ b/docs/4.examples/0.index.md @@ -0,0 +1,11 @@ +--- +icon: ph:code +--- + +# Examples + +> Common examples for h3. + +::read-more{to="https://github.com/unjs/h3/tree/main/examples"} +Check [`examples/` dir](https://github.com/unjs/h3/tree/main/examples) for more examples. +:: diff --git a/docs/4.examples/from-expressjs-to-h3.md b/docs/4.examples/from-expressjs-to-h3.md new file mode 100644 index 00000000..5de7abb3 --- /dev/null +++ b/docs/4.examples/from-expressjs-to-h3.md @@ -0,0 +1,434 @@ +# From Express.js to h3 + +> Through various examples, let's see how easy it is to use h3 if you are familiar with Express.js. + +During this guide, we will reproduce many examples from the [Express.js documentation](https://expressjs.com/en/starter/examples.html) to show you how to do the same thing with h3. + +> [!NOTE] +> If you are not familiar with Express.js, you can safely skip this guide. + +The idea is to show you how similar h3 is to Express.js. Once you understand the similarities, you will be able to use h3 without any problem if you are familiar with Express.js. + +> [!CAUTION] +> Even if h3 seems to be similar to Express.js, it does not mean that Express.js is still viable. Express.js is an old framework that has not evolved for a long time. It's not a good choice for new projects since it can easily lead to security issues and memory leaks. + +With h3, you also have reloading out-of-the-box without any configuration using [unjs/listhen](https://listhen.unjs.io). + +> [!NOTE] +> You can run every h3 examples using `npx --yes listhen -w ./app.ts`. + +## Hello World + +The first example from the Express.js documentation is the [Hello World](https://github.com/expressjs/express/blob/master/examples/hello-world/index.js). + +The code is pretty simple: + +```js [index.js] +/** + * Express.js example app. + */ +var express = require("express"); +var app = express(); + +app.get("/", function (req, res) { + res.send("Hello World"); +}); + +app.listen(3000); +console.log("Express started on port 3000"); +``` + +Let's see how to do the same thing with h3: + +```ts [app.ts] +/** + * h3 example app. + */ +import { createApp, defineEventHandler } from "h3"; + +export const app = createApp(); + +app.use( + "/", + defineEventHandler((event) => { + return "Hello World"; + }), +); +``` + +Then, you can use `npx --yes listhen -w ./app.ts` to start the server and go to http://localhost:3000 to see the result. + +:read-more{to="/guide/app"} + +## Multi Router + +The second example is the [Multi Router](https://github.com/expressjs/express/blob/master/examples/multi-router/index.js). In this example, we create many routers to split the logic. + +```js [index.js] +/** + * Express.js example app. + */ +var express = require("express"); + +var app = express(); + +var apiv1 = express.Router(); + +apiv1.get("/", function (req, res) { + res.send("Hello from APIv1 root route."); +}); + +apiv1.get("/users", function (req, res) { + res.send("List of APIv1 users."); +}); + +var apiv2 = express.Router(); + +apiv2.get("/", function (req, res) { + res.send("Hello from APIv2 root route."); +}); + +apiv2.get("/users", function (req, res) { + res.send("List of APIv2 users."); +}); + +app.use("/api/v1", apiv1); +app.use("/api/v2", apiv2); + +app.get("/", function (req, res) { + res.send("Hello from root route."); +}); + +app.listen(3000); +console.log("Express started on port 3000"); +``` + +> [!NOTE] +> For some facilities, we group every files in the same one. + +Using h3, we can do the same thing: + +```ts [app.ts] +/** + * h3 example app. + */ +import { createApp, createRouter, defineEventHandler, useBase } from "h3"; + +export const app = createApp(); + +const apiv1 = createRouter() + .get( + "/", + defineEventHandler(() => { + return "Hello from APIv1 root route."; + }), + ) + .get( + "/users", + defineEventHandler(() => { + return "List of APIv1 users."; + }), + ); + +const apiv2 = createRouter() + .get( + "/", + defineEventHandler(() => { + return "Hello from APIv2 root route."; + }), + ) + .get( + "/users", + defineEventHandler(() => { + return "List of APIv2 users."; + }), + ); + +app.use("/api/v1/**", useBase("/api/v1", apiv1.handler)); +app.use("/api/v2/**", useBase("/api/v2", apiv2.handler)); +``` + +It's quite similar. The main difference is that we have to use `useBase` to define a base path for a router. + +:read-more{to="/guide/router"} + +## Params + +The third example is the [Params](https://github.com/expressjs/express/tree/master/examples/params/index.js). In this example, we use parameters in the route. + +```js [index.js] +/** + * Express.js example app. + */ +var createError = require("http-errors"); +var express = require("express"); +var app = express(); + +var users = [ + { name: "tj" }, + { name: "tobi" }, + { name: "loki" }, + { name: "jane" }, + { name: "bandit" }, +]; + +app.param(["to", "from"], function (req, res, next, num, name) { + req.params[name] = parseInt(num, 10); + if (isNaN(req.params[name])) { + next(createError(400, "failed to parseInt " + num)); + } else { + next(); + } +}); + +app.param("user", function (req, res, next, id) { + if ((req.user = users[id])) { + next(); + } else { + next(createError(404, "failed to find user")); + } +}); + +app.get("/", function (req, res) { + res.send("Visit /user/0 or /users/0-2"); +}); + +app.get("/user/:user", function (req, res) { + res.send("user " + req.user.name); +}); + +app.get("/users/:from-:to", function (req, res) { + var from = req.params.from; + var to = req.params.to; + var names = users.map(function (user) { + return user.name; + }); + res.send("users " + names.slice(from, to + 1).join(", ")); +}); + +app.listen(3000); +console.log("Express started on port 3000"); +``` + +Using h3, we can do the same thing: + +```ts [app.ts] +/** + * h3 example app. + */ +import { + createApp, + createError, + createRouter, + defineEventHandler, + getRouterParam, + getValidatedRouterParams, +} from "h3"; +import { z } from "zod"; + +const users = [ + { name: "tj" }, + { name: "tobi" }, + { name: "loki" }, + { name: "jane" }, + { name: "bandit" }, +]; + +export const app = createApp(); +const router = createRouter(); + +router.get( + "/", + defineEventHandler(() => { + return "Visit /users/0 or /users/0/2"; + }), +); + +router.get( + "/user/:user", + defineEventHandler(async (event) => { + const { user } = await getValidatedRouterParams( + event, + z.object({ + user: z.number({ coerce: true }), + }).parse, + ); + + if (!users[user]) + throw createError({ + status: 404, + statusMessage: "User Not Found", + }); + + return `user ${user}`; + }), +); + +router.get( + "/users/:from/:to", + defineEventHandler(async (event) => { + const { from, to } = await getValidatedRouterParams( + event, + z.object({ + from: z.number({ coerce: true }), + to: z.number({ coerce: true }), + }).parse, + ); + + const names = users.map((user) => { + return user.name; + }); + + return `users ${names.slice(from, to).join(", ")}`; + }), +); + +app.use(router); +``` + +With h3, we do not have a `param` method. Instead, we use `getRouterParam` or `getValidatedRouterParams` to validate the params. It's more explicit and easier to use. In this example, we use `Zod` but you are free to use any other validation library. + +## Cookies + +The fourth example is the [Cookies](https://github.com/expressjs/express/blob/master/examples/cookies/index.js). In this example, we use cookies. + +```js [index.js] +/** + * Express.js example app. + */ +var express = require("express"); +var app = express(); +var cookieParser = require("cookie-parser"); + +app.use(cookieParser("my secret here")); + +app.use(express.urlencoded({ extended: false })); + +app.get("/", function (req, res) { + if (req.cookies.remember) { + res.send('Remembered :). Click to forget!.'); + } else { + res.send( + '

Check to ' + + '.

', + ); + } +}); + +app.get("/forget", function (req, res) { + res.clearCookie("remember"); + res.redirect("back"); +}); + +app.post("/", function (req, res) { + var minute = 60000; + if (req.body.remember) res.cookie("remember", 1, { maxAge: minute }); + res.redirect("back"); +}); + +app.listen(3000); +console.log("Express started on port 3000"); +``` + +Using h3, we can do the same thing: + +```ts [app.ts] +import { + createApp, + createRouter, + defineEventHandler, + getCookie, + getHeader, + readBody, + sendRedirect, + setCookie, +} from "h3"; + +export const app = createApp(); +const router = createRouter(); + +router.get( + "/", + defineEventHandler((event) => { + const remember = getCookie(event, "remember"); + + if (remember) { + return 'Remembered :). Click to forget!.'; + } else { + return `

Check to + .

`; + } + }), +); + +router.get( + "/forget", + defineEventHandler((event) => { + deleteCookie(event, "remember"); + + const back = getHeader(event, "referer") || "/"; + return sendRedirect(event, back); + }), +); + +router.post( + "/", + defineEventHandler(async (event) => { + const body = await readBody(event); + + if (body.remember) + setCookie(event, "remember", "1", { maxAge: 60 * 60 * 24 * 7 }); + + const back = getHeader(event, "referer") || "/"; + return sendRedirect(event, back); + }), +); + +app.use(router); +``` + +With h3, we do not have a `cookieParser` middleware. Instead, we use `getCookie` and `setCookie` to get and set cookies. It's more explicit and easier to use. + +## Middleware + +When using `express`, we usually handle requests with `middleware`. + +For instance, here we use `morgan` to handle request logging. + +```js [index.js] +var express = require("express"); +var morgan = require("morgan"); + +var app = express(); + +app.use(morgan("combined")); + +app.get("/", function (req, res) { + res.send("hello, world!"); +}); + +app.listen(3000); +console.log("Express started on port 3000"); +``` + +In `h3`, we can also directly use middleware from the `express` ecosystem. + +This can be easily achieved by wrapping with `fromNodeMiddleware`. + +```ts [app.ts] +import morgan from "morgan"; +import { defineEventHandler, createApp, fromNodeMiddleware } from "h3"; + +export const app = createApp(); + +app.use(fromNodeMiddleware(morgan())); + +app.use( + "/", + defineEventHandler((event) => { + return "Hello World"; + }), +); +``` diff --git a/docs/4.examples/handle-cookie.md b/docs/4.examples/handle-cookie.md new file mode 100644 index 00000000..9171b944 --- /dev/null +++ b/docs/4.examples/handle-cookie.md @@ -0,0 +1,71 @@ +# Handle Cookie + +> Use cookies to store data on the client. + +Handling cookies with h3 is straightforward. There is three utilities to handle cookies: + +- `setCookie` to attach a cookie to the response. +- `getCookie` to get a cookie from the request. +- `deleteCookie` to clear a cookie from the response. + +## Set a Cookie + +To set a cookie, you need to use `setCookie` in an event handler: + +```ts +import { defineEventHandler, setCookie } from "h3"; + +app.use( + defineEventHandler(async (event) => { + setCookie(event, "name", "value", { maxAge: 60 * 60 * 24 * 7 }); + }), +); +``` + +In the options, you can configure the [cookie flags](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie): + +- `maxAge` to set the expiration date of the cookie in seconds. +- `expires` to set the expiration date of the cookie in a `Date` object. +- `path` to set the path of the cookie. +- `domain` to set the domain of the cookie. +- `secure` to set the `Secure` flag of the cookie. +- `httpOnly` to set the `HttpOnly` flag of the cookie. +- `sameSite` to set the `SameSite` flag of the cookie. + +:read-more{to="/utils"} + +## Get a Cookie + +To get a cookie, you need to use `getCookie` in an event handler. + +```ts +import { defineEventHandler, getCookie } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const name = getCookie(event, "name"); + + // do something... + }), +); +``` + +This will return the value of the cookie if it exists, or `undefined` otherwise. + +## Delete a Cookie + +To delete a cookie, you need to use `deleteCookie` in an event handler: + +```ts +import { defineEventHandler, deleteCookie } from "h3"; + +app.use( + defineEventHandler(async (event) => { + deleteCookie(event, "name"); + }), +); +``` + +The utility `deleteCookie` is a wrapper around `setCookie` with the value set to `""` and the `maxAge` set to `0`. + +This will erase the cookie from the client. diff --git a/docs/4.examples/handle-session.md b/docs/4.examples/handle-session.md new file mode 100644 index 00000000..99aea85b --- /dev/null +++ b/docs/4.examples/handle-session.md @@ -0,0 +1,144 @@ +# Handle Session + +> Remember your users using a session. + +A session is a way to remember users using cookies. It is a very common way to authenticate users or save data about them such as their language or their preferences on the web. + +h3 provide many utilities to handle sessions: + +- `useSession` to initializes a session and returns a wrapper to control it. +- `getSession` to initializes or gets the current user session. +- `updateSession` to updates data of the current session. +- `clearSession` to clears the current session. + +Most of the time, you will use `useSession` to manipulate the session. + +## Initialize a Session + +To initialize a session, you need to use `useSession` in an [event handler](/guide/event-handler): + +```js +import { defineEventHandler, useSession } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const session = await useSession(event, { + password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", + }); + + // do something... + }), +); +``` + +> [!WARNING] +> You must provide a password to encrypt the session. + +This will initialize a session and return an header `Set-Cookie` with a cookie named `h3` and an encrypted content. + +If the request contains a cookie named `h3` or a header named `x-h3-session`, the session will be initialized with the content of the cookie or the header. + +> [!NOTE] +> The header take precedence over the cookie. + +## Get Data from a Session + +To get data from a session, we will still use `useSession`. Under the hood, it will use [`getSession` to get the session. + +```js +import { defineEventHandler, useSession } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const session = await useSession(event, { + password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", + }); + + return session.data; + }), +); +``` + +Data are stored in the `data` property of the session. If there is no data, it will be an empty object. + +## Add Data to a Session + +To add data to a session, we will still use `useSession`. Under the hood, it will use `updateSession` to update the session. + +```js +import { defineEventHandler, useSession } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const session = await useSession(event, { + password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", + }); + + const count = (session.data.count || 0) + 1; + await session.update({ + count: count, + }); + + return count === 0 + ? "Hello world!" + : `Hello world! You have visited this page ${count} times.`; + }), +); +``` + +What is happening here? + +We try to get a session from the request. If there is no session, a new one will be created. Then, we increment the `count` property of the session and we update the session with the new value. Finally, we return a message with the number of times the user visited the page. + +Try to visit the page multiple times and you will see the number of times you visited the page. + +> [!NOTE] +> If you use a CLI tool like `curl` to test this example, you will not see the number of times you visited the page because the CLI tool does not save cookies. You must get the cookie from the response and send it back to the server. + +## Clear a Session + +To clear a session, we will still use `useSession`. Under the hood, it will use `clearSession` to clear the session. + +```js +import { defineEventHandler, useSession } from "h3"; + +app.use( + "/clear", + defineEventHandler(async (event) => { + const session = await useSession(event, { + password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", + }); + + await session.clear(); + + return "Session cleared"; + }), +); +``` + +h3 will send a header `Set-Cookie` with an empty cookie named `h3` to clear the session. + +## Options + +When to use `useSession`, you can pass an object with options as the second argument to configure the session: + +```js +import { defineEventHandler, useSession } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const session = await useSession(event, { + password: "80d42cfb-1cd2-462c-8f17-e3237d9027e9", + cookie: { + name: "my-session", + httpOnly: true, + secure: true, + sameSite: "strict", + }, + maxAge: 60 * 60 * 24 * 7, // 7 days + }); + + return session.data; + }), +); +``` diff --git a/docs/4.examples/serve-static-assets.md b/docs/4.examples/serve-static-assets.md new file mode 100644 index 00000000..d18c78b8 --- /dev/null +++ b/docs/4.examples/serve-static-assets.md @@ -0,0 +1,107 @@ +# Serve Static Assets + +> Serve static assets such as HMTL, images, CSS, JavaScript, etc. + +h3 can serve static assets such as HTML, images, CSS, JavaScript, etc. + +> [!NOTE] +> If you use [`unjs/listhen`](https://listhen.unjs.io), you've just to create a `public` directory in your project root and put your static assets in it. They will be served automatically. + +## Usage + +To serve a static directory, you can use the `serveStatic` utility. + +```ts +import { createApp, defineEventHandler, serveStatic } from "h3"; + +export const app = createApp(); + +app.use( + defineEventHandler((event) => { + return serveStatic(event, { + getContents: (id) => { + return undefined; + }, + getMeta: (id) => { + return undefined; + }, + }); + }), +); +``` + +This does not serve any files yet. You need to implement the `getContents` and `getMeta` methods. + +- `getContents` is used to read the contents of a file. It should return a `Promise` that resolves to the contents of the file or `undefined` if the file does not exist. +- `getMeta` is used to get the metadata of a file. It should return a `Promise` that resolves to the metadata of the file or `undefined` if the file does not exist. + +They are separated to allow h3 to respond to `HEAD` requests without reading the contents of the file and to use the `Last-Modified` header. + +## Read files + +Now, create a `index.html` file in the `public` directory with a simple message and open your browser to http://localhost:3000. You should see the message. + +> [!NOTE] +> Usage of `public` is a convention but you can use any directory name you want. + +> [!NOTE] +> If you're are using [`unjs/listhen`](https://listhen.unjs.io) and want to try this example, create a directory with another name than `public` because it's the default directory used by `listhen`. + +Then, we can create the `getContents` and `getMeta` methods: + +```ts +import { createApp, defineEventHandler, serveStatic } from "h3"; +import { stat, readFile } from "node:fs/promises"; +import { join } from "pathe"; + +export const app = createApp(); + +const publicDir = "assets"; + +app.use( + defineEventHandler((event) => { + return serveStatic(event, { + getContents: (id) => readFile(join(publicDir, id)), + getMeta: async (id) => { + const stats = await stat(join(publicDir, id)).catch(() => {}); + + if (!stats || !stats.isFile()) { + return; + } + + return { + size: stats.size, + mtime: stats.mtimeMs, + }; + }, + }); + }), +); +``` + +The `getContents` read the file and returns its contents, pretty simple. The `getMeta` uses `fs.stat` to get the file metadata. If the file does not exist or is not a file, it returns `undefined`. Otherwise, it returns the file size and the last modification time. + +The file size and last modification time are used to create an etag to send a `304 Not Modified` response if the file has not been modified since the last request. This is useful to avoid sending the same file multiple times if it has not changed. + +## Resolving Assets + +If the path does not match a file, h3 will try to add `index.html` to the path and try again. If it still does not match, it will return a 404 error. + +You can change this behavior by passing a `indexNames` option to `serveStatic`: + +```ts +import { createApp, serveStatic } from "h3"; + +const app = createApp(); + +app.use( + serveStatic({ + indexNames: ["/app.html", "/index.html"], + }), +); +``` + +With this option, h3 will try to match `/app.html` first, then `/index.html` and finally return a 404 error. + +> [!IMPORTANT] +> Do not forget `/` at the beginning of the h3 concatenates the path with the index name. For example, `/index.html` will be concatenated with `/hello` to form `hello/index.html`. diff --git a/docs/4.examples/stream-response.md b/docs/4.examples/stream-response.md new file mode 100644 index 00000000..9bec64ef --- /dev/null +++ b/docs/4.examples/stream-response.md @@ -0,0 +1,103 @@ +# Stream Response + +> Stream response to the client. + +Streaming is a powerful feature of h3. It allows you to send data to the client as soon as you have it. This is useful for large files or long running tasks. + +> [!WARNING] +> Steaming is complicated and can become an overhead if you don't need it. + +## Create a Stream + +To stream a response, you first need to create a stream using the [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) API: + +```ts +const steam = new ReadableStream(); +``` + +For the example, we will create a start function that will send a random number every 100 milliseconds. After 1000 milliseconds, it will close the stream: + +```ts +let interval: NodeJS.Timeout; +const stream = new ReadableStream({ + start(controller) { + controller.enqueue("
    "); + + interval = setInterval(() => { + controller.enqueue("
  • " + Math.random() + "
  • "); + }, 100); + + setTimeout(() => { + clearInterval(interval); + controller.close(); + }, 1000); + }, + cancel() { + clearInterval(interval); + }, +}); +``` + +## Send a Stream + +In your event handler, you will need to set to response header to tell to the client that we are sending a stream. Otherwise, it will wait for the response to be completed before rendering it: + +```ts +import { defineEventHandler } from "h3"; + +app.use( + defineEventHandler(async (event) => { + setResponseHeader(event, "Content-Type", "text/html"); + setResponseHeader(event, "Cache-Control", "no-cache"); + setResponseHeader(event, "Transfer-Encoding", "chunked"); + + return null; + }), +); +``` + +Then, you can send the stream using the `sendStream` utility: + +```ts +import { + createApp, + defineEventHandler, + sendStream, + setResponseHeader, +} from "h3"; + +export const app = createApp(); + +app.use( + defineEventHandler((event) => { + setResponseHeader(event, "Content-Type", "text/html"); + setResponseHeader(event, "Cache-Control", "no-cache"); + setResponseHeader(event, "Transfer-Encoding", "chunked"); + + let interval: NodeJS.Timeout; + const stream = new ReadableStream({ + start(controller) { + controller.enqueue("
      "); + + interval = setInterval(() => { + controller.enqueue("
    • " + Math.random() + "
    • "); + }, 100); + + setTimeout(() => { + clearInterval(interval); + controller.close(); + }, 1000); + }, + cancel() { + clearInterval(interval); + }, + }); + + return sendStream(event, stream); + }), +); +``` + +Open your browser to http://localhost:3000 and you should see a list of random numbers appearing every 100 milliseconds. + +Magic! 🎉 diff --git a/docs/4.examples/validate-data.md b/docs/4.examples/validate-data.md new file mode 100644 index 00000000..6e677465 --- /dev/null +++ b/docs/4.examples/validate-data.md @@ -0,0 +1,117 @@ +# Validate Data + +> Ensure that your data are valid and safe before processing them. + +When you receive data from user on your server, you must validate them. By validate, we mean that the shape of the received data must match the expected shape. It's important because you can't trust user input. + +> [!WARNING] +> Do not use a generic as a validation. Providing an interface to a utility like `readBody` is not a validation. You must validate the data before using them. + +## Utilities for Validation + +h3 provide some utilities to help you to handle data validation. You will be able to validate: + +- query with `getValidatedQuery` +- params with `getValidatedRouterParams`. +- body with `readValidatedBody` + +To validate data, you can use any validation library you want. h3 doesn't provide any validation library like [Zod](https://zod.dev), [joi](https://joi.dev) or [myzod](https://github.com/davidmdm/myzod). + +> [!WARNING] +> h3 is runtime agnostic. This means that you can use it in [any runtime](/adapters). But some validation libraries are not compatible with all runtimes. + +Let's see how to validate data with [Zod](https://zod.dev). + +For the following examples, we will use the following schema: + +```js +import { z } from "zod"; + +const userSchema = z.object({ + name: z.string().min(3).max(20), + age: z.number({ coerce: true }).positive().int(), +}); +``` + +## Validate Query + +You can use `getValidatedQuery` to validate query and get the result, as a replacement of `getQuery`: + +```js +import { defineEventHandler, getValidatedQuery } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const query = await getValidatedQuery(event, userSchema.parse); + + return `Hello ${query.name}! You are ${query.age} years old.`; + }), +); +``` + +> [!NOTE] +> You could use `safeParse` instead of `parse` to get a partial query object and to not throw an error if the query is invalid. + +If you send a valid request like `/?name=John&age=42` to this event handler, you will get a response like this: + +```txt +Hello John! You are 42 years old. +``` + +If you send an invalid request and the validation fails, h3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user. + +## Validate Params + +You can use `getValidatedRouterParams` to validate params and get the result, as a replacement of `getRouterParams`: + +```js +import { defineEventHandler, getValidatedRouterParams } from "h3"; + +router.use( + // You must use a router to use params + "/hello/:name/:age", + defineEventHandler(async (event) => { + const params = await getValidatedRouterParams(event, userSchema.parse); + + return `Hello ${params.name}! You are ${params.age} years old!`; + }), +); +``` + +> [!NOTE] +> You could use `safeParse` instead of `parse` to get a partial params object and to not throw an error if the params is invalid. + +If you send a valid request like `/hello/John/42` to this event handler, you will get a response like this: + +```txt +Hello John! You are 42 years old. +``` + +If you send an invalid request and the validation fails, h3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user. + +## Validate Body + +You can use `readValidatedBody` to validate body and get the result, as a replacement of `readBody`: + +```js +import { defineEventHandler, readValidatedBody } from "h3"; + +app.use( + defineEventHandler(async (event) => { + const body = await readValidatedBody(event, userSchema.parse); + + return `Hello ${body.name}! You are ${body.age} years old.`; + }), +); +``` + +> [!NOTE] +> You could use `safeParse` instead of `parse` to get a partial body object and to not throw an error if the body is invalid. + +If you send a valid POST request to this event handler, you will get a response like this: + +```txt +Hello John! You are 42 years old. +``` + +If you send an invalid request and the validation fails, h3 will throw a `400 Validation Error` error. In the data of the error, you will find the validation errors you can use on your client to display a nice error message to your user. diff --git a/docs/bun.lockb b/docs/bun.lockb new file mode 100755 index 00000000..c3004914 Binary files /dev/null and b/docs/bun.lockb differ diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..26b20ece --- /dev/null +++ b/docs/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "build": "undocs build", + "dev": "undocs dev" + }, + "devDependencies": { + "undocs": "^0.2.17" + } +} diff --git a/examples/first-server.ts b/examples/first-server.ts index 3c108f79..4f21160d 100644 --- a/examples/first-server.ts +++ b/examples/first-server.ts @@ -1,4 +1,4 @@ -import { createApp, defineEventHandler, toNodeListener } from "h3"; +import { createApp, defineEventHandler } from "h3"; export const app = createApp(); diff --git a/examples/nested-router.ts b/examples/nested-router.ts index bd3478b0..e94ba800 100644 --- a/examples/nested-router.ts +++ b/examples/nested-router.ts @@ -1,7 +1,6 @@ import { createApp, defineEventHandler, - toNodeListener, createRouter, useBase, sendRedirect, diff --git a/examples/server-sent-events.ts b/examples/server-sent-events.ts new file mode 100644 index 00000000..5902f4ca --- /dev/null +++ b/examples/server-sent-events.ts @@ -0,0 +1,26 @@ +import { createApp, createRouter, eventHandler, createEventStream } from "h3"; + +export const app = createApp(); + +const router = createRouter(); +app.use(router); + +router.get( + "/", + eventHandler((event) => { + const eventStream = createEventStream(event); + + // Send a message every second + const interval = setInterval(async () => { + await eventStream.push("Hello world"); + }, 1000); + + // cleanup the interval and close the stream when the connection is terminated + eventStream.onClosed(async () => { + clearInterval(interval); + await eventStream.close(); + }); + + return eventStream.send(); + }), +); diff --git a/examples/websocket.ts b/examples/websocket.ts new file mode 100644 index 00000000..e50947ea --- /dev/null +++ b/examples/websocket.ts @@ -0,0 +1,35 @@ +import { createApp, defineEventHandler, defineWebSocketHandler } from "h3"; + +export const app = createApp(); + +app.use( + defineEventHandler(() => + fetch( + "https://raw.githubusercontent.com/unjs/crossws/main/examples/h3/public/index.html", + ).then((r) => r.text()), + ), +); + +app.use( + "/_ws", + defineWebSocketHandler({ + open(peer) { + console.log("[ws] open", peer); + }, + + message(peer, message) { + console.log("[ws] message", peer, message); + if (message.text().includes("ping")) { + peer.send("pong"); + } + }, + + close(peer, event) { + console.log("[ws] close", peer, event); + }, + + error(peer, error) { + console.log("[ws] error", peer, error); + }, + }), +); diff --git a/package.json b/package.json index fb21b352..49e6e8ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "h3", - "version": "1.10.1", + "version": "1.11.1", "description": "Minimal H(TTP) framework built for high performance and portability.", "repository": "unjs/h3", "license": "MIT", @@ -22,8 +22,8 @@ "scripts": { "build": "unbuild", "dev": "vitest", - "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground examples", - "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground examples -w", + "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground examples docs", + "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground examples docs -w", "play": "listhen -w ./playground/app.ts", "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs", "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags", @@ -32,39 +32,41 @@ }, "dependencies": { "cookie-es": "^1.0.0", + "crossws": "^0.2.4", "defu": "^6.1.4", - "destr": "^2.0.2", + "destr": "^2.0.3", "iron-webcrypto": "^1.0.0", "ohash": "^1.1.3", "radix3": "^1.1.0", - "ufo": "^1.3.2", + "ufo": "^1.4.0", "uncrypto": "^0.1.3", "unenv": "^1.9.0" }, "devDependencies": { "0x": "^5.7.0", "@types/express": "^4.17.21", - "@types/node": "^20.11.6", + "@types/node": "^20.11.24", "@types/supertest": "^6.0.2", - "@vitest/coverage-v8": "^1.2.1", - "autocannon": "^7.14.0", + "@vitest/coverage-v8": "^1.3.1", + "autocannon": "^7.15.0", + "automd": "^0.3.6", "changelogen": "^0.5.5", "connect": "^3.7.0", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-unjs": "^0.2.1", - "express": "^4.18.2", + "express": "^4.18.3", "get-port": "^7.0.0", "jiti": "^1.21.0", - "listhen": "^1.5.6", - "node-fetch-native": "^1.6.1", - "prettier": "^3.2.4", + "listhen": "^1.7.2", + "node-fetch-native": "^1.6.2", + "prettier": "^3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", "supertest": "^6.3.4", "typescript": "^5.3.3", "unbuild": "^2.0.0", - "vitest": "^1.2.1", + "vitest": "^1.3.1", "zod": "^3.22.4" }, - "packageManager": "pnpm@8.14.3" + "packageManager": "pnpm@8.15.4" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 908eb060..50fcd23b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,15 @@ importers: cookie-es: specifier: ^1.0.0 version: 1.0.0 + crossws: + specifier: ^0.2.4 + version: 0.2.4 defu: specifier: ^6.1.4 version: 6.1.4 destr: - specifier: ^2.0.2 - version: 2.0.2 + specifier: ^2.0.3 + version: 2.0.3 iron-webcrypto: specifier: ^1.0.0 version: 1.0.0 @@ -27,8 +30,8 @@ importers: specifier: ^1.1.0 version: 1.1.0 ufo: - specifier: ^1.3.2 - version: 1.3.2 + specifier: ^1.4.0 + version: 1.4.0 uncrypto: specifier: ^0.1.3 version: 0.1.3 @@ -43,17 +46,20 @@ importers: specifier: ^4.17.21 version: 4.17.21 '@types/node': - specifier: ^20.11.6 - version: 20.11.6 + specifier: ^20.11.24 + version: 20.11.24 '@types/supertest': specifier: ^6.0.2 version: 6.0.2 '@vitest/coverage-v8': - specifier: ^1.2.1 - version: 1.2.1(vitest@1.2.1) + specifier: ^1.3.1 + version: 1.3.1(vitest@1.3.1) autocannon: - specifier: ^7.14.0 - version: 7.14.0 + specifier: ^7.15.0 + version: 7.15.0 + automd: + specifier: ^0.3.6 + version: 0.3.6 changelogen: specifier: ^0.5.5 version: 0.5.5 @@ -61,14 +67,14 @@ importers: specifier: ^3.7.0 version: 3.7.0 eslint: - specifier: ^8.56.0 - version: 8.56.0 + specifier: ^8.57.0 + version: 8.57.0 eslint-config-unjs: specifier: ^0.2.1 - version: 0.2.1(eslint@8.56.0)(typescript@5.3.3) + version: 0.2.1(eslint@8.57.0)(typescript@5.3.3) express: - specifier: ^4.18.2 - version: 4.18.2 + specifier: ^4.18.3 + version: 4.18.3 get-port: specifier: ^7.0.0 version: 7.0.0 @@ -76,14 +82,14 @@ importers: specifier: ^1.21.0 version: 1.21.0 listhen: - specifier: ^1.5.6 - version: 1.5.6 + specifier: ^1.7.2 + version: 1.7.2 node-fetch-native: - specifier: ^1.6.1 - version: 1.6.1 + specifier: ^1.6.2 + version: 1.6.2 prettier: - specifier: ^3.2.4 - version: 3.2.4 + specifier: ^3.2.5 + version: 3.2.5 react: specifier: ^18.2.0 version: 18.2.0 @@ -100,8 +106,8 @@ importers: specifier: ^2.0.0 version: 2.0.0(typescript@5.3.3) vitest: - specifier: ^1.2.1 - version: 1.2.1(@types/node@20.11.6) + specifier: ^1.3.1 + version: 1.3.1(@types/node@20.11.24) zod: specifier: ^3.22.4 version: 3.22.4 @@ -116,7 +122,7 @@ importers: version: link:.. listhen: specifier: latest - version: 1.5.6 + version: 1.7.1 playground: dependencies: @@ -125,7 +131,7 @@ importers: version: link:.. listhen: specifier: latest - version: 1.5.6 + version: 1.7.1 packages: @@ -156,7 +162,7 @@ packages: opn: 5.5.0 pump: 3.0.0 pumpify: 2.0.1 - semver: 7.5.4 + semver: 7.6.0 single-line-log: 1.1.2 split2: 4.2.0 tachyons: 4.12.0 @@ -175,8 +181,8 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/gen-mapping': 0.3.4 + '@jridgewell/trace-mapping': 0.3.23 dev: true /@assemblyscript/loader@0.19.23: @@ -224,8 +230,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.23.9 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/gen-mapping': 0.3.4 + '@jridgewell/trace-mapping': 0.3.23 jsesc: 2.5.2 dev: true @@ -235,7 +241,7 @@ packages: dependencies: '@babel/compat-data': 7.23.5 '@babel/helper-validator-option': 7.23.5 - browserslist: 4.22.2 + browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 dev: true @@ -339,8 +345,15 @@ packages: '@babel/types': 7.23.9 dev: true - /@babel/standalone@7.23.9: - resolution: {integrity: sha512-89NGhVfgKDqDQrtNPxqfnhIReKvp2CR80ofPNEAUpbtnouFelq33hQFURLralD9I+eFS7s5zVK61JRg/D1nLWg==} + /@babel/runtime@7.23.9: + resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + dev: true + + /@babel/standalone@7.23.10: + resolution: {integrity: sha512-xqWviI/pt1Zb/d+6ilWa5IDL2mkDzsBnlHbreqnfyP3/QB/ofQ1bNVcHj8YQX154Rf/xZKR6y0s1ydVF3nAS8g==} engines: {node: '>=6.9.0'} dev: true @@ -598,13 +611,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true @@ -621,7 +634,7 @@ packages: debug: 4.3.4 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.0 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -630,8 +643,8 @@ packages: - supports-color dev: true - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -667,17 +680,17 @@ packages: '@sinclair/typebox': 0.27.8 dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + /@jridgewell/gen-mapping@0.3.4: + resolution: {integrity: sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/trace-mapping': 0.3.23 dev: true - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} dev: true @@ -690,10 +703,10 @@ packages: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true - /@jridgewell/trace-mapping@0.3.22: - resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + /@jridgewell/trace-mapping@0.3.23: + resolution: {integrity: sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==} dependencies: - '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 dev: true @@ -715,83 +728,83 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.16.0 + fastq: 1.17.1 dev: true - /@parcel/watcher-android-arm64@2.4.0: - resolution: {integrity: sha512-+fPtO/GsbYX1LJnCYCaDVT3EOBjvSFdQN9Mrzh9zWAOOfvidPWyScTrHIZHHfJBvlHzNA0Gy0U3NXFA/M7PHUA==} + /@parcel/watcher-android-arm64@2.4.1: + resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@parcel/watcher-darwin-arm64@2.4.0: - resolution: {integrity: sha512-T/At5pansFuQ8VJLRx0C6C87cgfqIYhW2N/kBfLCUvDhCah0EnLLwaD/6MW3ux+rpgkpQAnMELOCTKlbwncwiA==} + /@parcel/watcher-darwin-arm64@2.4.1: + resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@parcel/watcher-darwin-x64@2.4.0: - resolution: {integrity: sha512-vZMv9jl+szz5YLsSqEGCMSllBl1gU1snfbRL5ysJU03MEa6gkVy9OMcvXV1j4g0++jHEcvzhs3Z3LpeEbVmY6Q==} + /@parcel/watcher-darwin-x64@2.4.1: + resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@parcel/watcher-freebsd-x64@2.4.0: - resolution: {integrity: sha512-dHTRMIplPDT1M0+BkXjtMN+qLtqq24sLDUhmU+UxxLP2TEY2k8GIoqIJiVrGWGomdWsy5IO27aDV1vWyQ6gfHA==} + /@parcel/watcher-freebsd-x64@2.4.1: + resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@parcel/watcher-linux-arm-glibc@2.4.0: - resolution: {integrity: sha512-9NQXD+qk46RwATNC3/UB7HWurscY18CnAPMTFcI9Y8CTbtm63/eex1SNt+BHFinEQuLBjaZwR2Lp+n7pmEJPpQ==} + /@parcel/watcher-linux-arm-glibc@2.4.1: + resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@parcel/watcher-linux-arm64-glibc@2.4.0: - resolution: {integrity: sha512-QuJTAQdsd7PFW9jNGaV9Pw+ZMWV9wKThEzzlY3Lhnnwy7iW23qtQFPql8iEaSFMCVI5StNNmONUopk+MFKpiKg==} + /@parcel/watcher-linux-arm64-glibc@2.4.1: + resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@parcel/watcher-linux-arm64-musl@2.4.0: - resolution: {integrity: sha512-oyN+uA9xcTDo/45bwsd6TFHa7Lc7hKujyMlvwrCLvSckvWogndCEoVYFNfZ6JJ2KNL/6fFiGPcbjp8jJmEh5Ng==} + /@parcel/watcher-linux-arm64-musl@2.4.1: + resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@parcel/watcher-linux-x64-glibc@2.4.0: - resolution: {integrity: sha512-KphV8awJmxU3q52JQvJot0QMu07CIyEjV+2Tb2ZtbucEgqyRcxOBDMsqp1JNq5nuDXtcCC0uHQICeiEz38dPBQ==} + /@parcel/watcher-linux-x64-glibc@2.4.1: + resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@parcel/watcher-linux-x64-musl@2.4.0: - resolution: {integrity: sha512-7jzcOonpXNWcSijPpKD5IbC6xC7yTibjJw9jviVzZostYLGxbz8LDJLUnLzLzhASPlPGgpeKLtFUMjAAzM+gSA==} + /@parcel/watcher-linux-x64-musl@2.4.1: + resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@parcel/watcher-wasm@2.3.0: - resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} + /@parcel/watcher-wasm@2.4.1: + resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==} engines: {node: '>= 10.0.0'} dependencies: is-glob: 4.0.3 @@ -799,32 +812,32 @@ packages: bundledDependencies: - napi-wasm - /@parcel/watcher-win32-arm64@2.4.0: - resolution: {integrity: sha512-NOej2lqlq8bQNYhUMnOD0nwvNql8ToQF+1Zhi9ULZoG+XTtJ9hNnCFfyICxoZLXor4bBPTOnzs/aVVoefYnjIg==} + /@parcel/watcher-win32-arm64@2.4.1: + resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@parcel/watcher-win32-ia32@2.4.0: - resolution: {integrity: sha512-IO/nM+K2YD/iwjWAfHFMBPz4Zqn6qBDqZxY4j2n9s+4+OuTSRM/y/irksnuqcspom5DjkSeF9d0YbO+qpys+JA==} + /@parcel/watcher-win32-ia32@2.4.1: + resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@parcel/watcher-win32-x64@2.4.0: - resolution: {integrity: sha512-pAUyUVjfFjWaf/pShmJpJmNxZhbMvJASUpdes9jL6bTEJ+gDxPRSpXTIemNyNsb9AtbiGXs9XduP1reThmd+dA==} + /@parcel/watcher-win32-x64@2.4.1: + resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] requiresBuild: true optional: true - /@parcel/watcher@2.4.0: - resolution: {integrity: sha512-XJLGVL0DEclX5pcWa2N9SX1jCGTDd8l972biNooLFtjneuGqodupPQh6XseXIBBeVIMaaJ7bTcs3qGvXwsp4vg==} + /@parcel/watcher@2.4.1: + resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==} engines: {node: '>= 10.0.0'} dependencies: detect-libc: 1.0.3 @@ -832,18 +845,18 @@ packages: micromatch: 4.0.5 node-addon-api: 7.1.0 optionalDependencies: - '@parcel/watcher-android-arm64': 2.4.0 - '@parcel/watcher-darwin-arm64': 2.4.0 - '@parcel/watcher-darwin-x64': 2.4.0 - '@parcel/watcher-freebsd-x64': 2.4.0 - '@parcel/watcher-linux-arm-glibc': 2.4.0 - '@parcel/watcher-linux-arm64-glibc': 2.4.0 - '@parcel/watcher-linux-arm64-musl': 2.4.0 - '@parcel/watcher-linux-x64-glibc': 2.4.0 - '@parcel/watcher-linux-x64-musl': 2.4.0 - '@parcel/watcher-win32-arm64': 2.4.0 - '@parcel/watcher-win32-ia32': 2.4.0 - '@parcel/watcher-win32-x64': 2.4.0 + '@parcel/watcher-android-arm64': 2.4.1 + '@parcel/watcher-darwin-arm64': 2.4.1 + '@parcel/watcher-darwin-x64': 2.4.1 + '@parcel/watcher-freebsd-x64': 2.4.1 + '@parcel/watcher-linux-arm-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-musl': 2.4.1 + '@parcel/watcher-linux-x64-glibc': 2.4.1 + '@parcel/watcher-linux-x64-musl': 2.4.1 + '@parcel/watcher-win32-arm64': 2.4.1 + '@parcel/watcher-win32-ia32': 2.4.1 + '@parcel/watcher-win32-x64': 2.4.1 /@rollup/plugin-alias@5.1.0(rollup@3.29.4): resolution: {integrity: sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==} @@ -872,7 +885,7 @@ packages: estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 - magic-string: 0.30.5 + magic-string: 0.30.7 rollup: 3.29.4 dev: true @@ -917,7 +930,7 @@ packages: optional: true dependencies: '@rollup/pluginutils': 5.1.0(rollup@3.29.4) - magic-string: 0.30.5 + magic-string: 0.30.7 rollup: 3.29.4 dev: true @@ -936,104 +949,104 @@ packages: rollup: 3.29.4 dev: true - /@rollup/rollup-android-arm-eabi@4.9.6: - resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==} + /@rollup/rollup-android-arm-eabi@4.12.0: + resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.9.6: - resolution: {integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==} + /@rollup/rollup-android-arm64@4.12.0: + resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.9.6: - resolution: {integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==} + /@rollup/rollup-darwin-arm64@4.12.0: + resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.9.6: - resolution: {integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==} + /@rollup/rollup-darwin-x64@4.12.0: + resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.9.6: - resolution: {integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==} + /@rollup/rollup-linux-arm-gnueabihf@4.12.0: + resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.9.6: - resolution: {integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==} + /@rollup/rollup-linux-arm64-gnu@4.12.0: + resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.9.6: - resolution: {integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==} + /@rollup/rollup-linux-arm64-musl@4.12.0: + resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.9.6: - resolution: {integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==} + /@rollup/rollup-linux-riscv64-gnu@4.12.0: + resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==} cpu: [riscv64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.9.6: - resolution: {integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==} + /@rollup/rollup-linux-x64-gnu@4.12.0: + resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.9.6: - resolution: {integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==} + /@rollup/rollup-linux-x64-musl@4.12.0: + resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.9.6: - resolution: {integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==} + /@rollup/rollup-win32-arm64-msvc@4.12.0: + resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.9.6: - resolution: {integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==} + /@rollup/rollup-win32-ia32-msvc@4.12.0: + resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.9.6: - resolution: {integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==} + /@rollup/rollup-win32-x64-msvc@4.12.0: + resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==} cpu: [x64] os: [win32] requiresBuild: true @@ -1044,6 +1057,11 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@sindresorhus/merge-streams@2.3.0: + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + dev: true + /@trysound/sax@0.2.0: resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -1053,13 +1071,13 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.11.6 + '@types/node': 20.11.24 dev: true /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.11.6 + '@types/node': 20.11.24 dev: true /@types/cookiejar@2.1.5: @@ -1070,10 +1088,10 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@types/express-serve-static-core@4.17.41: - resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} + /@types/express-serve-static-core@4.17.43: + resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} dependencies: - '@types/node': 20.11.6 + '@types/node': 20.11.24 '@types/qs': 6.9.11 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -1083,7 +1101,7 @@ packages: resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.41 + '@types/express-serve-static-core': 4.17.43 '@types/qs': 6.9.11 '@types/serve-static': 1.15.5 dev: true @@ -1116,8 +1134,8 @@ packages: resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} dev: true - /@types/node@20.11.6: - resolution: {integrity: sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==} + /@types/node@20.11.24: + resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==} dependencies: undici-types: 5.26.5 dev: true @@ -1138,15 +1156,15 @@ packages: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true /@types/send@0.17.4: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.11.6 + '@types/node': 20.11.24 dev: true /@types/serve-static@1.15.5: @@ -1154,7 +1172,7 @@ packages: dependencies: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 - '@types/node': 20.11.6 + '@types/node': 20.11.24 dev: true /@types/superagent@8.1.3: @@ -1162,7 +1180,7 @@ packages: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.11.6 + '@types/node': 20.11.24 dev: true /@types/supertest@6.0.2: @@ -1172,7 +1190,7 @@ packages: '@types/superagent': 8.1.3 dev: true - /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.56.0)(typescript@5.3.3): + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1184,23 +1202,23 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3) debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 graphemer: 1.4.0 - ignore: 5.3.0 + ignore: 5.3.1 natural-compare-lite: 1.4.0 - semver: 7.5.4 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.62.0(eslint@8.56.0)(typescript@5.3.3): + /@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1214,7 +1232,7 @@ packages: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 typescript: 5.3.3 transitivePeerDependencies: - supports-color @@ -1228,7 +1246,7 @@ packages: '@typescript-eslint/visitor-keys': 5.62.0 dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@8.56.0)(typescript@5.3.3): + /@typescript-eslint/type-utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1239,9 +1257,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.3.3) debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: @@ -1267,28 +1285,28 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.0 tsutils: 3.21.0(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.3.3): + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.6 + '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3) - eslint: 8.56.0 + eslint: 8.57.0 eslint-scope: 5.1.1 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -1306,10 +1324,10 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@vitest/coverage-v8@1.2.1(vitest@1.2.1): - resolution: {integrity: sha512-fJEhKaDwGMZtJUX7BRcGxooGwg1Hl0qt53mVup/ZJeznhvL5EodteVnb/mcByhEcvVWbK83ZF31c7nPEDi4LOQ==} + /@vitest/coverage-v8@1.3.1(vitest@1.3.1): + resolution: {integrity: sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==} peerDependencies: - vitest: ^1.0.0 + vitest: 1.3.1 dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 @@ -1317,50 +1335,50 @@ packages: istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.6 - magic-string: 0.30.5 + istanbul-reports: 3.1.7 + magic-string: 0.30.7 magicast: 0.3.3 picocolors: 1.0.0 std-env: 3.7.0 test-exclude: 6.0.0 v8-to-istanbul: 9.2.0 - vitest: 1.2.1(@types/node@20.11.6) + vitest: 1.3.1(@types/node@20.11.24) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@1.2.1: - resolution: {integrity: sha512-/bqGXcHfyKgFWYwIgFr1QYDaR9e64pRKxgBNWNXPefPFRhgm+K3+a/dS0cUGEreWngets3dlr8w8SBRw2fCfFQ==} + /@vitest/expect@1.3.1: + resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} dependencies: - '@vitest/spy': 1.2.1 - '@vitest/utils': 1.2.1 + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 chai: 4.4.1 dev: true - /@vitest/runner@1.2.1: - resolution: {integrity: sha512-zc2dP5LQpzNzbpaBt7OeYAvmIsRS1KpZQw4G3WM/yqSV1cQKNKwLGmnm79GyZZjMhQGlRcSFMImLjZaUQvNVZQ==} + /@vitest/runner@1.3.1: + resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==} dependencies: - '@vitest/utils': 1.2.1 + '@vitest/utils': 1.3.1 p-limit: 5.0.0 pathe: 1.1.2 dev: true - /@vitest/snapshot@1.2.1: - resolution: {integrity: sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==} + /@vitest/snapshot@1.3.1: + resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==} dependencies: - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.2 pretty-format: 29.7.0 dev: true - /@vitest/spy@1.2.1: - resolution: {integrity: sha512-vG3a/b7INKH7L49Lbp0IWrG6sw9j4waWAucwnksPB1r1FTJgV7nkBByd9ufzu6VWya/QTvQW4V9FShZbZIB2UQ==} + /@vitest/spy@1.3.1: + resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} dependencies: - tinyspy: 2.2.0 + tinyspy: 2.2.1 dev: true - /@vitest/utils@1.2.1: - resolution: {integrity: sha512-bsH6WVZYe/J2v3+81M5LDU8kW76xWObKIURpPrOXm2pjBniBu2MERI/XP60GpS4PHU3jyK50LUutOwrx4CyHUg==} + /@vitest/utils@1.3.1: + resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -1486,11 +1504,12 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + /array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - is-array-buffer: 3.0.2 + call-bind: 1.0.7 + is-array-buffer: 3.0.4 dev: true /array-flatten@1.1.1: @@ -1501,10 +1520,10 @@ packages: resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 + es-abstract: 1.22.4 + get-intrinsic: 1.2.4 is-string: 1.0.7 dev: true @@ -1513,24 +1532,35 @@ packages: engines: {node: '>=8'} dev: true - /array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + /array.prototype.filter@1.0.3: + resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-array-method-boxes-properly: 1.0.0 + is-string: 1.0.7 + dev: true + + /array.prototype.findlastindex@1.2.4: + resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 + es-errors: 1.3.0 es-shim-unscopables: 1.0.2 - get-intrinsic: 1.2.2 dev: true /array.prototype.flat@1.3.2: resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 es-shim-unscopables: 1.0.2 dev: true @@ -1538,23 +1568,24 @@ packages: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 es-shim-unscopables: 1.0.2 dev: true - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + /arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.5 + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 + es-abstract: 1.22.4 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 dev: true /asap@2.0.6: @@ -1585,8 +1616,8 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true - /autocannon@7.14.0: - resolution: {integrity: sha512-lusP43BAwrTwQhihLjKwy7LceyX01eKSvFJUsBktASGqcR1g1ySYSPxCoCGDX08uLEs9oaqEKBBUFMenK3B3lQ==} + /autocannon@7.15.0: + resolution: {integrity: sha512-NaP2rQyA+tcubOJMFv2+oeW9jv2pq/t+LM6BL3cfJic0HEfscEcnWgAyU5YovE/oTHUzAgTliGdLPR+RQAWUbg==} hasBin: true dependencies: chalk: 4.1.2 @@ -1609,30 +1640,57 @@ packages: progress: 2.0.3 reinterval: 1.1.0 retimer: 3.0.0 - semver: 7.5.4 + semver: 7.6.0 subarg: 1.0.0 timestring: 6.0.0 dev: true - /autoprefixer@10.4.17(postcss@8.4.33): + /automd@0.3.6: + resolution: {integrity: sha512-6FmbZ6vTxFNohdU8ELwUUAee9u+ev8UNFk22A265P7g/IDviZthlWc4PTRgk1IlDJTXS1KHeMpl/PmuBgBOa+w==} + hasBin: true + dependencies: + '@parcel/watcher': 2.4.1 + c12: 1.9.0 + citty: 0.1.6 + consola: 3.2.3 + defu: 6.1.4 + destr: 2.0.3 + didyoumean2: 6.0.1 + globby: 14.0.1 + magic-string: 0.30.7 + mdbox: 0.1.0 + mlly: 1.6.1 + ofetch: 1.3.3 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.0.3 + scule: 1.3.0 + untyped: 1.4.2 + transitivePeerDependencies: + - supports-color + dev: true + + /autoprefixer@10.4.17(postcss@8.4.35): resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: - browserslist: 4.22.2 - caniuse-lite: 1.0.30001580 + browserslist: 4.23.0 + caniuse-lite: 1.0.30001589 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + dependencies: + possible-typed-array-names: 1.0.0 dev: true /balanced-match@1.0.2: @@ -1661,8 +1719,8 @@ packages: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} dev: true - /body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 @@ -1674,7 +1732,7 @@ packages: iconv-lite: 0.4.24 on-finished: 2.4.1 qs: 6.11.0 - raw-body: 2.5.1 + raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: @@ -1848,15 +1906,15 @@ packages: xtend: 4.0.2 dev: true - /browserslist@4.22.2: - resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001580 - electron-to-chromium: 1.4.646 + caniuse-lite: 1.0.30001589 + electron-to-chromium: 1.4.681 node-releases: 2.0.14 - update-browserslist-db: 1.0.13(browserslist@4.22.2) + update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true /buffer-from@1.1.2: @@ -1893,7 +1951,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: true /bundle-name@3.0.0: @@ -1908,15 +1966,16 @@ packages: engines: {node: '>= 0.8'} dev: true - /c12@1.6.1: - resolution: {integrity: sha512-fAZOi3INDvIbmjuwAVVggusyRTxwNdTAnwLay8IsXwhFzDwPPGzFxzrx6L55CPFGPulUSZI0eyFUvRDXveoE3g==} + /c12@1.9.0: + resolution: {integrity: sha512-7KTCZXdIbOA2hLRQ+1KzJ15Qp9Wn58one74dkihMVp2H6EzKTa3OYBy0BSfS1CCcmxYyqeX8L02m40zjQ+dstg==} dependencies: - chokidar: 3.5.3 + chokidar: 3.6.0 + confbox: 0.1.3 defu: 6.1.4 - dotenv: 16.4.1 + dotenv: 16.4.5 giget: 1.2.1 jiti: 1.21.0 - mlly: 1.5.0 + mlly: 1.6.1 ohash: 1.1.3 pathe: 1.1.2 perfect-debounce: 1.0.0 @@ -1933,12 +1992,15 @@ packages: resolution: {integrity: sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==} dev: true - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.2 - set-function-length: 1.2.0 + get-intrinsic: 1.2.4 + set-function-length: 1.2.1 dev: true /callsites@3.1.0: @@ -1956,14 +2018,14 @@ packages: /caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: - browserslist: 4.22.2 - caniuse-lite: 1.0.30001580 + browserslist: 4.23.0 + caniuse-lite: 1.0.30001589 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true - /caniuse-lite@1.0.30001580: - resolution: {integrity: sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==} + /caniuse-lite@1.0.30001589: + resolution: {integrity: sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==} dev: true /chai@4.4.1: @@ -2017,21 +2079,21 @@ packages: resolution: {integrity: sha512-IzgToIJ/R9NhVKmL+PW33ozYkv53bXvufDNUSH3GTKXq1iCHGgkbgbtqEWbo8tnWNnt7nPDpjL8PwSG2iS8RVw==} hasBin: true dependencies: - c12: 1.6.1 + c12: 1.9.0 colorette: 2.0.20 consola: 3.2.3 convert-gitmoji: 0.1.5 execa: 8.0.1 mri: 1.2.0 - node-fetch-native: 1.6.1 + node-fetch-native: 1.6.2 ofetch: 1.3.3 open: 9.1.0 pathe: 1.1.2 pkg-types: 1.0.3 - scule: 1.2.0 - semver: 7.5.4 + scule: 1.3.0 + semver: 7.6.0 std-env: 3.7.0 - yaml: 2.3.4 + yaml: 2.4.0 dev: true /char-spinner@1.0.1: @@ -2044,8 +2106,8 @@ packages: get-func-name: 2.0.2 dev: true - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 @@ -2076,8 +2138,8 @@ packages: safe-buffer: 5.2.1 dev: true - /citty@0.1.5: - resolution: {integrity: sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ==} + /citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} dependencies: consola: 3.2.3 @@ -2150,7 +2212,7 @@ packages: resolution: {integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==} dependencies: convert-source-map: 1.1.3 - inline-source-map: 0.6.2 + inline-source-map: 0.6.3 lodash.memoize: 3.0.4 source-map: 0.5.7 dev: true @@ -2199,6 +2261,10 @@ packages: typedarray: 0.0.6 dev: true + /confbox@0.1.3: + resolution: {integrity: sha512-eH3ZxAihl1PhKfpr4VfEN6/vUd87fmgb6JkldHgg/YR6aEBhW63qUDgzP2Y6WM0UumdsYp5H3kibalXAdHfbgg==} + dev: true + /connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -2311,6 +2377,23 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /crossws@0.2.0: + resolution: {integrity: sha512-WW4qfY5ylZDzTPplWcMVh6dj3IXUme2yb1hGC4wWnAKEwL0txtiRrWdIctSAsDlcfm2udmH7GcH60IT5esY2Zw==} + peerDependencies: + uWebSockets.js: '*' + peerDependenciesMeta: + uWebSockets.js: + optional: true + dev: false + + /crossws@0.2.4: + resolution: {integrity: sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==} + peerDependencies: + uWebSockets.js: '*' + peerDependenciesMeta: + uWebSockets.js: + optional: true + /crypto-browserify@3.12.0: resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} dependencies: @@ -2327,13 +2410,13 @@ packages: randomfill: 1.0.4 dev: true - /css-declaration-sorter@7.1.1(postcss@8.4.33): + /css-declaration-sorter@7.1.1(postcss@8.4.35): resolution: {integrity: sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true /css-select@5.1.0: @@ -2373,62 +2456,62 @@ packages: hasBin: true dev: true - /cssnano-preset-default@6.0.3(postcss@8.4.33): - resolution: {integrity: sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==} + /cssnano-preset-default@6.0.5(postcss@8.4.35): + resolution: {integrity: sha512-M+qRDEr5QZrfNl0B2ySdbTLGyNb8kBcSjuwR7WBamYBOEREH9t2efnB/nblekqhdGLZdkf4oZNetykG2JWRdZQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - css-declaration-sorter: 7.1.1(postcss@8.4.33) - cssnano-utils: 4.0.1(postcss@8.4.33) - postcss: 8.4.33 - postcss-calc: 9.0.1(postcss@8.4.33) - postcss-colormin: 6.0.2(postcss@8.4.33) - postcss-convert-values: 6.0.2(postcss@8.4.33) - postcss-discard-comments: 6.0.1(postcss@8.4.33) - postcss-discard-duplicates: 6.0.1(postcss@8.4.33) - postcss-discard-empty: 6.0.1(postcss@8.4.33) - postcss-discard-overridden: 6.0.1(postcss@8.4.33) - postcss-merge-longhand: 6.0.2(postcss@8.4.33) - postcss-merge-rules: 6.0.3(postcss@8.4.33) - postcss-minify-font-values: 6.0.1(postcss@8.4.33) - postcss-minify-gradients: 6.0.1(postcss@8.4.33) - postcss-minify-params: 6.0.2(postcss@8.4.33) - postcss-minify-selectors: 6.0.2(postcss@8.4.33) - postcss-normalize-charset: 6.0.1(postcss@8.4.33) - postcss-normalize-display-values: 6.0.1(postcss@8.4.33) - postcss-normalize-positions: 6.0.1(postcss@8.4.33) - postcss-normalize-repeat-style: 6.0.1(postcss@8.4.33) - postcss-normalize-string: 6.0.1(postcss@8.4.33) - postcss-normalize-timing-functions: 6.0.1(postcss@8.4.33) - postcss-normalize-unicode: 6.0.2(postcss@8.4.33) - postcss-normalize-url: 6.0.1(postcss@8.4.33) - postcss-normalize-whitespace: 6.0.1(postcss@8.4.33) - postcss-ordered-values: 6.0.1(postcss@8.4.33) - postcss-reduce-initial: 6.0.2(postcss@8.4.33) - postcss-reduce-transforms: 6.0.1(postcss@8.4.33) - postcss-svgo: 6.0.2(postcss@8.4.33) - postcss-unique-selectors: 6.0.2(postcss@8.4.33) - dev: true - - /cssnano-utils@4.0.1(postcss@8.4.33): + css-declaration-sorter: 7.1.1(postcss@8.4.35) + cssnano-utils: 4.0.1(postcss@8.4.35) + postcss: 8.4.35 + postcss-calc: 9.0.1(postcss@8.4.35) + postcss-colormin: 6.0.3(postcss@8.4.35) + postcss-convert-values: 6.0.4(postcss@8.4.35) + postcss-discard-comments: 6.0.1(postcss@8.4.35) + postcss-discard-duplicates: 6.0.2(postcss@8.4.35) + postcss-discard-empty: 6.0.2(postcss@8.4.35) + postcss-discard-overridden: 6.0.1(postcss@8.4.35) + postcss-merge-longhand: 6.0.3(postcss@8.4.35) + postcss-merge-rules: 6.0.4(postcss@8.4.35) + postcss-minify-font-values: 6.0.2(postcss@8.4.35) + postcss-minify-gradients: 6.0.2(postcss@8.4.35) + postcss-minify-params: 6.0.3(postcss@8.4.35) + postcss-minify-selectors: 6.0.2(postcss@8.4.35) + postcss-normalize-charset: 6.0.1(postcss@8.4.35) + postcss-normalize-display-values: 6.0.1(postcss@8.4.35) + postcss-normalize-positions: 6.0.1(postcss@8.4.35) + postcss-normalize-repeat-style: 6.0.1(postcss@8.4.35) + postcss-normalize-string: 6.0.1(postcss@8.4.35) + postcss-normalize-timing-functions: 6.0.1(postcss@8.4.35) + postcss-normalize-unicode: 6.0.3(postcss@8.4.35) + postcss-normalize-url: 6.0.1(postcss@8.4.35) + postcss-normalize-whitespace: 6.0.1(postcss@8.4.35) + postcss-ordered-values: 6.0.1(postcss@8.4.35) + postcss-reduce-initial: 6.0.3(postcss@8.4.35) + postcss-reduce-transforms: 6.0.1(postcss@8.4.35) + postcss-svgo: 6.0.2(postcss@8.4.35) + postcss-unique-selectors: 6.0.2(postcss@8.4.35) + dev: true + + /cssnano-utils@4.0.1(postcss@8.4.35): resolution: {integrity: sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /cssnano@6.0.3(postcss@8.4.33): - resolution: {integrity: sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==} + /cssnano@6.0.5(postcss@8.4.35): + resolution: {integrity: sha512-tpTp/ukgrElwu3ESFY4IvWnGn8eTt8cJhC2aAbtA3lvUlxp6t6UPv8YCLjNnEGiFreT1O0LiOM1U3QyTBVFl2A==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - cssnano-preset-default: 6.0.3(postcss@8.4.33) - lilconfig: 3.0.0 - postcss: 8.4.33 + cssnano-preset-default: 6.0.5(postcss@8.4.35) + lilconfig: 3.1.1 + postcss: 8.4.35 dev: true /csso@5.0.5: @@ -2628,13 +2711,13 @@ packages: titleize: 3.0.0 dev: true - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 + es-errors: 1.3.0 gopd: 1.0.1 - has-property-descriptors: 1.0.1 dev: true /define-lazy-prop@3.0.0: @@ -2646,8 +2729,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 - has-property-descriptors: 1.0.1 + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 object-keys: 1.1.1 dev: true @@ -2685,8 +2768,8 @@ packages: minimalistic-assert: 1.0.1 dev: true - /destr@2.0.2: - resolution: {integrity: sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg==} + /destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} @@ -2715,6 +2798,15 @@ packages: wrappy: 1.0.2 dev: true + /didyoumean2@6.0.1: + resolution: {integrity: sha512-PSy0zQwMg5O+LjT5Mz7vnKC8I7DfWLPF6M7oepqW7WP5mn2CY3hz46xZOa1GJY+KVfyXhdmz6+tdgXwrHlZc5g==} + engines: {node: ^16.14.0 || >=18.12.0} + dependencies: + '@babel/runtime': 7.23.9 + fastest-levenshtein: 1.0.16 + lodash.deburr: 4.1.0 + dev: true + /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2781,8 +2873,8 @@ packages: domhandler: 5.0.3 dev: true - /dotenv@16.4.1: - resolution: {integrity: sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==} + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} dev: true @@ -2805,8 +2897,8 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true - /electron-to-chromium@1.4.646: - resolution: {integrity: sha512-vThkQ0JuF45qT/20KbRgM56lV7IuGt7SjhawQ719PDHzhP84KAO1WJoaxgCoAffKHK47FmVKP1Fqizx7CwK1SA==} + /electron-to-chromium@1.4.681: + resolution: {integrity: sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==} dev: true /elliptic@6.5.4: @@ -2859,64 +2951,82 @@ packages: is-arrayish: 0.2.1 dev: true - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + /es-abstract@1.22.4: + resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==} engines: {node: '>= 0.4'} dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.2 - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 - es-set-tostringtag: 2.0.2 + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 es-to-primitive: 1.2.1 function.prototype.name: 1.1.6 - get-intrinsic: 1.2.2 - get-symbol-description: 1.0.0 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 globalthis: 1.0.3 gopd: 1.0.1 - has-property-descriptors: 1.0.1 - has-proto: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 - internal-slot: 1.0.6 - is-array-buffer: 3.0.2 + hasown: 2.0.1 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 is-callable: 1.2.7 - is-negative-zero: 2.0.2 + is-negative-zero: 2.0.3 is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 + is-shared-array-buffer: 1.0.3 is-string: 1.0.7 - is-typed-array: 1.1.12 + is-typed-array: 1.1.13 is-weakref: 1.0.2 object-inspect: 1.13.1 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.1 + regexp.prototype.flags: 1.5.2 safe-array-concat: 1.1.0 - safe-regex-test: 1.0.2 + safe-regex-test: 1.0.3 string.prototype.trim: 1.2.8 string.prototype.trimend: 1.0.7 string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.5 unbox-primitive: 1.0.2 - which-typed-array: 1.1.13 + which-typed-array: 1.1.14 dev: true - /es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + /es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + dev: true + + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + dev: true + + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + dev: true + + /es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - has-tostringtag: 1.0.0 - hasown: 2.0.0 + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.1 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /es-to-primitive@1.2.1: @@ -2959,8 +3069,8 @@ packages: '@esbuild/win32-x64': 0.19.12 dev: true - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} dev: true @@ -2978,25 +3088,25 @@ packages: engines: {node: '>=10'} dev: true - /eslint-compat-utils@0.1.2(eslint@8.56.0): + /eslint-compat-utils@0.1.2(eslint@8.57.0): resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==} engines: {node: '>=12'} peerDependencies: eslint: '>=6.0.0' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true - /eslint-config-prettier@8.10.0(eslint@8.56.0): + /eslint-config-prettier@8.10.0(eslint@8.57.0): resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true - /eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.56.0): + /eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.57.0): resolution: {integrity: sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==} engines: {node: '>=12.0.0'} peerDependencies: @@ -3005,29 +3115,29 @@ packages: eslint-plugin-n: '^15.0.0 || ^16.0.0 ' eslint-plugin-promise: ^6.0.0 dependencies: - eslint: 8.56.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-n: 16.6.2(eslint@8.56.0) - eslint-plugin-promise: 6.1.1(eslint@8.56.0) + eslint: 8.57.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-n: 16.6.2(eslint@8.57.0) + eslint-plugin-promise: 6.1.1(eslint@8.57.0) dev: true - /eslint-config-unjs@0.2.1(eslint@8.56.0)(typescript@5.3.3): + /eslint-config-unjs@0.2.1(eslint@8.57.0)(typescript@5.3.3): resolution: {integrity: sha512-h17q+WR86glq8yLFuHfEnAFfbEYqXpJAppXc0e0fQz0gsotJQ14BZVrlvIThE2a+stWyh0VT73gbBPfosl2rVA==} peerDependencies: eslint: '*' typescript: '*' dependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) - eslint: 8.56.0 - eslint-config-prettier: 8.10.0(eslint@8.56.0) - eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.56.0) - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-n: 16.6.2(eslint@8.56.0) - eslint-plugin-node: 11.1.0(eslint@8.56.0) - eslint-plugin-promise: 6.1.1(eslint@8.56.0) - eslint-plugin-unicorn: 47.0.0(eslint@8.56.0) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.3) + eslint: 8.57.0 + eslint-config-prettier: 8.10.0(eslint@8.57.0) + eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@16.6.2)(eslint-plugin-promise@6.1.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-n: 16.6.2(eslint@8.57.0) + eslint-plugin-node: 11.1.0(eslint@8.57.0) + eslint-plugin-promise: 6.1.1(eslint@8.57.0) + eslint-plugin-unicorn: 47.0.0(eslint@8.57.0) typescript: 5.3.3 transitivePeerDependencies: - eslint-import-resolver-node @@ -3045,7 +3155,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -3054,9 +3164,9 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.15.0 - eslint: 8.56.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint: 8.57.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -3068,7 +3178,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -3089,39 +3199,39 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.3) debug: 3.2.7 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-es-x@7.5.0(eslint@8.56.0): + /eslint-plugin-es-x@7.5.0(eslint@8.57.0): resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '>=8' dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 - eslint: 8.56.0 - eslint-compat-utils: 0.1.2(eslint@8.56.0) + eslint: 8.57.0 + eslint-compat-utils: 0.1.2(eslint@8.57.0) dev: true - /eslint-plugin-es@3.0.1(eslint@8.56.0): + /eslint-plugin-es@3.0.1(eslint@8.57.0): resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=4.19.1' dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-utils: 2.1.0 regexpp: 3.2.0 dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -3131,22 +3241,22 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.3.3) array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.3 + array.prototype.findlastindex: 1.2.4 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.56.0 + eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - hasown: 2.0.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.7 - object.groupby: 1.0.1 + object.groupby: 1.0.2 object.values: 1.1.7 semver: 6.3.1 tsconfig-paths: 3.15.0 @@ -3156,61 +3266,61 @@ packages: - supports-color dev: true - /eslint-plugin-n@16.6.2(eslint@8.56.0): + /eslint-plugin-n@16.6.2(eslint@8.57.0): resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==} engines: {node: '>=16.0.0'} peerDependencies: eslint: '>=7.0.0' dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) builtins: 5.0.1 - eslint: 8.56.0 - eslint-plugin-es-x: 7.5.0(eslint@8.56.0) + eslint: 8.57.0 + eslint-plugin-es-x: 7.5.0(eslint@8.57.0) get-tsconfig: 4.7.2 globals: 13.24.0 - ignore: 5.3.0 + ignore: 5.3.1 is-builtin-module: 3.2.1 is-core-module: 2.13.1 minimatch: 3.1.2 resolve: 1.22.8 - semver: 7.5.4 + semver: 7.6.0 dev: true - /eslint-plugin-node@11.1.0(eslint@8.56.0): + /eslint-plugin-node@11.1.0(eslint@8.57.0): resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} engines: {node: '>=8.10.0'} peerDependencies: eslint: '>=5.16.0' dependencies: - eslint: 8.56.0 - eslint-plugin-es: 3.0.1(eslint@8.56.0) + eslint: 8.57.0 + eslint-plugin-es: 3.0.1(eslint@8.57.0) eslint-utils: 2.1.0 - ignore: 5.3.0 + ignore: 5.3.1 minimatch: 3.1.2 resolve: 1.22.8 semver: 6.3.1 dev: true - /eslint-plugin-promise@6.1.1(eslint@8.56.0): + /eslint-plugin-promise@6.1.1(eslint@8.57.0): resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 dev: true - /eslint-plugin-unicorn@47.0.0(eslint@8.56.0): + /eslint-plugin-unicorn@47.0.0(eslint@8.57.0): resolution: {integrity: sha512-ivB3bKk7fDIeWOUmmMm9o3Ax9zbMz1Bsza/R2qm46ufw4T6VBFBaJIR1uN3pCKSmSXm8/9Nri8V+iUut1NhQGA==} engines: {node: '>=16'} peerDependencies: eslint: '>=8.38.0' dependencies: '@babel/helper-validator-identifier': 7.22.20 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) ci-info: 3.9.0 clean-regexp: 1.0.0 - eslint: 8.56.0 + eslint: 8.57.0 esquery: 1.5.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 @@ -3221,7 +3331,7 @@ packages: regexp-tree: 0.1.27 regjsparser: 0.10.0 safe-regex: 2.1.1 - semver: 7.5.4 + semver: 7.6.0 strip-indent: 3.0.0 dev: true @@ -3258,15 +3368,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 + '@eslint/js': 8.57.0 '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -3288,7 +3398,7 @@ packages: glob-parent: 6.0.2 globals: 13.24.0 graphemer: 1.4.0 - ignore: 5.3.0 + ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -3398,7 +3508,7 @@ packages: human-signals: 4.3.1 is-stream: 3.0.0 merge-stream: 2.0.0 - npm-run-path: 5.2.0 + npm-run-path: 5.3.0 onetime: 6.0.0 signal-exit: 3.0.7 strip-final-newline: 3.0.0 @@ -3413,7 +3523,7 @@ packages: human-signals: 5.0.0 is-stream: 3.0.0 merge-stream: 2.0.0 - npm-run-path: 5.2.0 + npm-run-path: 5.3.0 onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 @@ -3424,13 +3534,13 @@ packages: util-extend: 1.0.3 dev: true - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + /express@4.18.3: + resolution: {integrity: sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.1 + body-parser: 1.20.2 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.5.0 @@ -3490,8 +3600,13 @@ packages: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true - /fastq@1.16.0: - resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + /fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + dev: true + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 dev: true @@ -3559,7 +3674,7 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flatted: 3.2.9 + flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 dev: true @@ -3569,8 +3684,8 @@ packages: hasBin: true dev: true - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true /for-each@0.3.3: @@ -3656,9 +3771,9 @@ packages: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 functions-have-names: 1.2.3 dev: true @@ -3679,13 +3794,15 @@ packages: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: + es-errors: 1.3.0 function-bind: 1.1.2 - has-proto: 1.0.1 + has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.0 + hasown: 2.0.1 dev: true /get-port-please@3.1.2: @@ -3705,12 +3822,13 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + /get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 dev: true /get-tsconfig@4.7.2: @@ -3723,10 +3841,10 @@ packages: resolution: {integrity: sha512-4VG22mopWtIeHwogGSy1FViXVo0YT+m6BrqZfz0JJFwbSsePsCdOzdLIIli5BtMp7Xe8f/o2OmBpQX2NBOC24g==} hasBin: true dependencies: - citty: 0.1.5 + citty: 0.1.6 consola: 3.2.3 defu: 6.1.4 - node-fetch-native: 1.6.1 + node-fetch-native: 1.6.2 nypm: 0.3.6 ohash: 1.1.3 pathe: 1.1.2 @@ -3795,7 +3913,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -3806,15 +3924,27 @@ packages: dependencies: dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.1 merge2: 1.4.1 slash: 4.0.0 dev: true + /globby@14.0.1: + resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} + engines: {node: '>=18'} + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.2 + ignore: 5.3.1 + path-type: 5.0.0 + slash: 5.1.0 + unicorn-magic: 0.1.0 + dev: true + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - get-intrinsic: 1.2.2 + get-intrinsic: 1.2.4 dev: true /graceful-fs@4.2.11: @@ -3825,17 +3955,21 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /h3@1.10.0: - resolution: {integrity: sha512-Tw1kcIC+AeimwRmviiObaD5EB430Yt+lTgOxLJxNr96Vd/fGRu04EF7aKfOAcpwKCI+U2JlbxOLhycD86p3Ciw==} + /h3@1.11.1: + resolution: {integrity: sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==} dependencies: cookie-es: 1.0.0 + crossws: 0.2.4 defu: 6.1.4 - destr: 2.0.2 + destr: 2.0.3 iron-webcrypto: 1.0.0 + ohash: 1.1.3 radix3: 1.1.0 - ufo: 1.3.2 + ufo: 1.4.0 uncrypto: 0.1.3 unenv: 1.9.0 + transitivePeerDependencies: + - uWebSockets.js /has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} @@ -3863,14 +3997,14 @@ packages: engines: {node: '>=8'} dev: true - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: - get-intrinsic: 1.2.2 + es-define-property: 1.0.0 dev: true - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} dev: true @@ -3879,8 +4013,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 @@ -3911,8 +4045,8 @@ packages: minimalistic-assert: 1.0.1 dev: true - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + /hasown@2.0.1: + resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -4031,8 +4165,8 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true - /ignore@5.3.0: - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} dev: true @@ -4069,8 +4203,8 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /inline-source-map@0.6.2: - resolution: {integrity: sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==} + /inline-source-map@0.6.3: + resolution: {integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==} dependencies: source-map: 0.5.7 dev: true @@ -4091,13 +4225,13 @@ packages: xtend: 4.0.2 dev: true - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + /internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.2 - hasown: 2.0.0 - side-channel: 1.0.4 + es-errors: 1.3.0 + hasown: 2.0.1 + side-channel: 1.0.5 dev: true /internmap@1.0.1: @@ -4116,16 +4250,16 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + /is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 dev: true /is-arrayish@0.2.1: @@ -4153,8 +4287,8 @@ packages: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true /is-buffer@1.1.6: @@ -4181,14 +4315,14 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.0 + hasown: 2.0.1 dev: true /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-docker@2.2.1: @@ -4222,7 +4356,7 @@ packages: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-glob@4.0.3: @@ -4242,8 +4376,8 @@ packages: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} dev: true @@ -4251,7 +4385,7 @@ packages: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-number@7.0.0: @@ -4273,14 +4407,15 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - has-tostringtag: 1.0.0 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 dev: true - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + /is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /is-stream@2.0.1: @@ -4296,7 +4431,7 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /is-symbol@1.0.4: @@ -4306,17 +4441,17 @@ packages: has-symbols: 1.0.3 dev: true - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + /is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.13 + which-typed-array: 1.1.14 dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 dev: true /is-wsl@1.1.0: @@ -4379,8 +4514,8 @@ packages: - supports-color dev: true - /istanbul-reports@3.1.6: - resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 @@ -4395,6 +4530,10 @@ packages: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true + /js-tokens@8.0.3: + resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==} + dev: true + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -4499,8 +4638,8 @@ packages: type-check: 0.4.0 dev: true - /lilconfig@3.0.0: - resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + /lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} engines: {node: '>=14'} dev: true @@ -4508,33 +4647,63 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /listhen@1.5.6: - resolution: {integrity: sha512-gTpEJhT5L85L0bFgmu+Boqu5rP4DwDtEb4Exq5gdQUxWRwx4jbzdInZkmyLONo5EwIcQB0k7ZpWlpCDPdL77EQ==} + /listhen@1.7.1: + resolution: {integrity: sha512-kukvhU7W0H92lCkA9PibJ+K9GJVHfzUEerP+RllUYHoVCm0/Pveo/VeIKgaMcdLyq0ojtpbkJiybvRlvUfNGzQ==} + hasBin: true + dependencies: + '@parcel/watcher': 2.4.1 + '@parcel/watcher-wasm': 2.4.1 + citty: 0.1.6 + clipboardy: 4.0.0 + consola: 3.2.3 + crossws: 0.2.0 + defu: 6.1.4 + get-port-please: 3.1.2 + h3: 1.11.1 + http-shutdown: 1.2.2 + jiti: 1.21.0 + mlly: 1.6.1 + node-forge: 1.3.1 + pathe: 1.1.2 + std-env: 3.7.0 + ufo: 1.4.0 + untun: 0.1.3 + uqr: 0.1.2 + transitivePeerDependencies: + - uWebSockets.js + dev: false + + /listhen@1.7.2: + resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==} hasBin: true dependencies: - '@parcel/watcher': 2.4.0 - '@parcel/watcher-wasm': 2.3.0 - citty: 0.1.5 + '@parcel/watcher': 2.4.1 + '@parcel/watcher-wasm': 2.4.1 + citty: 0.1.6 clipboardy: 4.0.0 consola: 3.2.3 + crossws: 0.2.4 defu: 6.1.4 get-port-please: 3.1.2 - h3: 1.10.0 + h3: 1.11.1 http-shutdown: 1.2.2 jiti: 1.21.0 - mlly: 1.5.0 + mlly: 1.6.1 node-forge: 1.3.1 pathe: 1.1.2 std-env: 3.7.0 - ufo: 1.3.2 + ufo: 1.4.0 untun: 0.1.3 uqr: 0.1.2 + transitivePeerDependencies: + - uWebSockets.js + dev: true /local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} dependencies: - mlly: 1.5.0 + mlly: 1.6.1 pkg-types: 1.0.3 dev: true @@ -4560,6 +4729,10 @@ packages: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} dev: true + /lodash.deburr@4.1.0: + resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} + dev: true + /lodash.flatten@4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} dev: true @@ -4620,8 +4793,8 @@ packages: sourcemap-codec: 1.4.8 dev: true - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -4646,13 +4819,17 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.5.4 + semver: 7.6.0 dev: true /manage-path@2.0.0: resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==} dev: true + /md4w@0.2.2: + resolution: {integrity: sha512-dJwbVIueCp4HgI9Jg4I9pDVLiig1lAVXYqVmTrIVsobh3b50EtgQrXmFEzzRRR6HaK6xVqB/bd3Ac/Nt2kdwPA==} + dev: true + /md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} dependencies: @@ -4661,6 +4838,12 @@ packages: safe-buffer: 5.2.1 dev: true + /mdbox@0.1.0: + resolution: {integrity: sha512-eQA+6vf5XM4LqdfLsfPMxqUBSU8AMzSCSFbojWLXSDL2jZeO+xgHhxTggrG2jfGPAyyIWIukj6SuoFBd9a7XZw==} + dependencies: + md4w: 0.2.2 + dev: true + /mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} dev: true @@ -4822,29 +5005,29 @@ packages: typescript: optional: true dependencies: - autoprefixer: 10.4.17(postcss@8.4.33) - citty: 0.1.5 - cssnano: 6.0.3(postcss@8.4.33) + autoprefixer: 10.4.17(postcss@8.4.35) + citty: 0.1.6 + cssnano: 6.0.5(postcss@8.4.35) defu: 6.1.4 esbuild: 0.19.12 fs-extra: 11.2.0 globby: 13.2.2 jiti: 1.21.0 - mlly: 1.5.0 + mlly: 1.6.1 mri: 1.2.0 pathe: 1.1.2 - postcss: 8.4.33 - postcss-nested: 6.0.1(postcss@8.4.33) + postcss: 8.4.35 + postcss-nested: 6.0.1(postcss@8.4.35) typescript: 5.3.3 dev: true - /mlly@1.5.0: - resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} + /mlly@1.6.1: + resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} dependencies: acorn: 8.11.3 pathe: 1.1.2 pkg-types: 1.0.3 - ufo: 1.3.2 + ufo: 1.4.0 /module-deps@6.2.3: resolution: {integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==} @@ -4954,8 +5137,8 @@ packages: resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} engines: {node: ^16 || ^18 || >= 20} - /node-fetch-native@1.6.1: - resolution: {integrity: sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw==} + /node-fetch-native@1.6.2: + resolution: {integrity: sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==} /node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} @@ -4996,8 +5179,8 @@ packages: path-key: 3.1.1 dev: true - /npm-run-path@5.2.0: - resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 @@ -5018,10 +5201,10 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true dependencies: - citty: 0.1.5 + citty: 0.1.6 execa: 8.0.1 pathe: 1.1.2 - ufo: 1.3.2 + ufo: 1.4.0 dev: true /object-inspect@1.13.1: @@ -5037,7 +5220,7 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 @@ -5047,35 +5230,36 @@ packages: resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true - /object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + /object.groupby@1.0.2: + resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==} dependencies: - call-bind: 1.0.5 + array.prototype.filter: 1.0.3 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 - get-intrinsic: 1.2.2 + es-abstract: 1.22.4 + es-errors: 1.3.0 dev: true /object.values@1.1.7: resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /ofetch@1.3.3: resolution: {integrity: sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg==} dependencies: - destr: 2.0.2 - node-fetch-native: 1.6.1 - ufo: 1.3.2 + destr: 2.0.3 + node-fetch-native: 1.6.2 + ufo: 1.4.0 dev: true /ohash@1.1.3: @@ -5274,6 +5458,11 @@ packages: engines: {node: '>=8'} dev: true + /path-type@5.0.0: + resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} + engines: {node: '>=12'} + dev: true + /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -5308,7 +5497,7 @@ packages: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.1 - mlly: 1.5.0 + mlly: 1.6.1 pathe: 1.1.2 /pluralize@8.0.0: @@ -5316,274 +5505,279 @@ packages: engines: {node: '>=4'} dev: true - /postcss-calc@9.0.1(postcss@8.4.33): + /possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + dev: true + + /postcss-calc@9.0.1(postcss@8.4.35): resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.2.2 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 postcss-value-parser: 4.2.0 dev: true - /postcss-colormin@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==} + /postcss-colormin@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-ECpkS+UZRyAtu/kjive2/1mihP+GNtgC8kcdU8ueWZi1ZVxMNnRziCLdhrWECJhEtSWijfX2Cl9XTTCK/hjGaA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 + browserslist: 4.23.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-convert-values@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==} + /postcss-convert-values@6.0.4(postcss@8.4.35): + resolution: {integrity: sha512-YT2yrGzPXoQD3YeA2kBo/696qNwn7vI+15AOS2puXWEvSWqdCqlOyDWRy5GNnOc9ACRGOkuQ4ESQEqPJBWt/GA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 - postcss: 8.4.33 + browserslist: 4.23.0 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-discard-comments@6.0.1(postcss@8.4.33): + /postcss-discard-comments@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-discard-duplicates@6.0.1(postcss@8.4.33): - resolution: {integrity: sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==} + /postcss-discard-duplicates@6.0.2(postcss@8.4.35): + resolution: {integrity: sha512-U2rsj4w6pAGROCCcD13LP2eBIi1whUsXs4kgE6xkIuGfkbxCBSKhkCTWyowFd66WdVlLv0uM1euJKIgmdmZObg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-discard-empty@6.0.1(postcss@8.4.33): - resolution: {integrity: sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==} + /postcss-discard-empty@6.0.2(postcss@8.4.35): + resolution: {integrity: sha512-rj6pVC2dVCJrP0Y2RkYTQEbYaCf4HEm+R/2StQgJqGHxAa3+KcYslNQhcRqjLHtl/4wpzipJluaJLqBj6d5eDQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-discard-overridden@6.0.1(postcss@8.4.33): + /postcss-discard-overridden@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-merge-longhand@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==} + /postcss-merge-longhand@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-kF/y3DU8CRt+SX3tP/aG+2gkZI2Z7OXDsPU7FgxIJmuyhQQ1EHceIYcsp/alvzCm2P4c37Sfdu8nNrHc+YeyLg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 - stylehacks: 6.0.2(postcss@8.4.33) + stylehacks: 6.0.3(postcss@8.4.35) dev: true - /postcss-merge-rules@6.0.3(postcss@8.4.33): - resolution: {integrity: sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==} + /postcss-merge-rules@6.0.4(postcss@8.4.35): + resolution: {integrity: sha512-97iF3UJ5v8N1BWy38y+0l+Z8o5/9uGlEgtWic2PJPzoRrLB6Gxg8TVG93O0EK52jcLeMsywre26AUlX1YAYeHA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 + browserslist: 4.23.0 caniuse-api: 3.0.0 - cssnano-utils: 4.0.1(postcss@8.4.33) - postcss: 8.4.33 + cssnano-utils: 4.0.1(postcss@8.4.35) + postcss: 8.4.35 postcss-selector-parser: 6.0.15 dev: true - /postcss-minify-font-values@6.0.1(postcss@8.4.33): - resolution: {integrity: sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==} + /postcss-minify-font-values@6.0.2(postcss@8.4.35): + resolution: {integrity: sha512-IedzbVMoX0a7VZWjSYr5qJ6C37rws8kl8diPBeMZLJfWKkgXuMFY5R/OxPegn/q9tK9ztd0XRH3aR0u2t+A7uQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-gradients@6.0.1(postcss@8.4.33): - resolution: {integrity: sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==} + /postcss-minify-gradients@6.0.2(postcss@8.4.35): + resolution: {integrity: sha512-vP5mF7iI6/5fcpv+rSfwWQekOE+8I1i7/7RjZPGuIjj6eUaZVeG4XZYZrroFuw1WQd51u2V32wyQFZ+oYdE7CA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: colord: 2.9.3 - cssnano-utils: 4.0.1(postcss@8.4.33) - postcss: 8.4.33 + cssnano-utils: 4.0.1(postcss@8.4.35) + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-params@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==} + /postcss-minify-params@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-j4S74d3AAeCK5eGdQndXSrkxusV2ekOxbXGnlnZthMyZBBvSDiU34CihTASbJxuVB3bugudmwolS7+Dgs5OyOQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 - cssnano-utils: 4.0.1(postcss@8.4.33) - postcss: 8.4.33 + browserslist: 4.23.0 + cssnano-utils: 4.0.1(postcss@8.4.35) + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-selectors@6.0.2(postcss@8.4.33): + /postcss-minify-selectors@6.0.2(postcss@8.4.35): resolution: {integrity: sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 dev: true - /postcss-nested@6.0.1(postcss@8.4.33): + /postcss-nested@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 dev: true - /postcss-normalize-charset@6.0.1(postcss@8.4.33): + /postcss-normalize-charset@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-normalize-display-values@6.0.1(postcss@8.4.33): + /postcss-normalize-display-values@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-positions@6.0.1(postcss@8.4.33): + /postcss-normalize-positions@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-repeat-style@6.0.1(postcss@8.4.33): + /postcss-normalize-repeat-style@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-string@6.0.1(postcss@8.4.33): + /postcss-normalize-string@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-timing-functions@6.0.1(postcss@8.4.33): + /postcss-normalize-timing-functions@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-unicode@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==} + /postcss-normalize-unicode@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-T2Bb3gXz0ASgc3ori2dzjv6j/P2IantreaC6fT8tWjqYUiqMAh5jGIkdPwEV2FaucjQlCLeFJDJh2BeSugE1ig==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 - postcss: 8.4.33 + browserslist: 4.23.0 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-url@6.0.1(postcss@8.4.33): + /postcss-normalize-url@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-whitespace@6.0.1(postcss@8.4.33): + /postcss-normalize-whitespace@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-ordered-values@6.0.1(postcss@8.4.33): + /postcss-ordered-values@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - cssnano-utils: 4.0.1(postcss@8.4.33) - postcss: 8.4.33 + cssnano-utils: 4.0.1(postcss@8.4.35) + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true - /postcss-reduce-initial@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==} + /postcss-reduce-initial@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-w4QIR9pEa1N4xMx3k30T1vLZl6udVK2RmNqrDXhBXX9L0mBj2a8ADs8zkbaEH7eUy1m30Wyr5EBgHN31Yq1JvA==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 + browserslist: 4.23.0 caniuse-api: 3.0.0 - postcss: 8.4.33 + postcss: 8.4.35 dev: true - /postcss-reduce-transforms@6.0.1(postcss@8.4.33): + /postcss-reduce-transforms@6.0.1(postcss@8.4.35): resolution: {integrity: sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 dev: true @@ -5595,24 +5789,24 @@ packages: util-deprecate: 1.0.2 dev: true - /postcss-svgo@6.0.2(postcss@8.4.33): + /postcss-svgo@6.0.2(postcss@8.4.35): resolution: {integrity: sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==} engines: {node: ^14 || ^16 || >= 18} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-value-parser: 4.2.0 svgo: 3.2.0 dev: true - /postcss-unique-selectors@6.0.2(postcss@8.4.33): + /postcss-unique-selectors@6.0.2(postcss@8.4.35): resolution: {integrity: sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - postcss: 8.4.33 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 dev: true @@ -5620,8 +5814,8 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /postcss@8.4.33: - resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==} + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 @@ -5634,8 +5828,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier@3.2.4: - resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} engines: {node: '>=14'} hasBin: true dev: true @@ -5725,14 +5919,14 @@ packages: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.4 + side-channel: 1.0.5 dev: true /qs@6.11.2: resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.4 + side-channel: 1.0.5 dev: true /querystring-es3@0.2.1: @@ -5769,8 +5963,8 @@ packages: engines: {node: '>= 0.6'} dev: true - /raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 @@ -5783,7 +5977,7 @@ packages: resolution: {integrity: sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==} dependencies: defu: 6.1.4 - destr: 2.0.2 + destr: 2.0.3 flat: 5.0.2 dev: true @@ -5861,18 +6055,23 @@ packages: picomatch: 2.3.1 dev: true + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: true + /regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true dev: true - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + /regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - set-function-name: 2.0.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 dev: true /regexpp@3.2.0: @@ -5944,7 +6143,7 @@ packages: rollup: ^3.29.4 || ^4 typescript: ^4.5 || ^5.0 dependencies: - magic-string: 0.30.5 + magic-string: 0.30.7 rollup: 3.29.4 typescript: 5.3.3 optionalDependencies: @@ -5959,26 +6158,26 @@ packages: fsevents: 2.3.3 dev: true - /rollup@4.9.6: - resolution: {integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==} + /rollup@4.12.0: + resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.9.6 - '@rollup/rollup-android-arm64': 4.9.6 - '@rollup/rollup-darwin-arm64': 4.9.6 - '@rollup/rollup-darwin-x64': 4.9.6 - '@rollup/rollup-linux-arm-gnueabihf': 4.9.6 - '@rollup/rollup-linux-arm64-gnu': 4.9.6 - '@rollup/rollup-linux-arm64-musl': 4.9.6 - '@rollup/rollup-linux-riscv64-gnu': 4.9.6 - '@rollup/rollup-linux-x64-gnu': 4.9.6 - '@rollup/rollup-linux-x64-musl': 4.9.6 - '@rollup/rollup-win32-arm64-msvc': 4.9.6 - '@rollup/rollup-win32-ia32-msvc': 4.9.6 - '@rollup/rollup-win32-x64-msvc': 4.9.6 + '@rollup/rollup-android-arm-eabi': 4.12.0 + '@rollup/rollup-android-arm64': 4.12.0 + '@rollup/rollup-darwin-arm64': 4.12.0 + '@rollup/rollup-darwin-x64': 4.12.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.12.0 + '@rollup/rollup-linux-arm64-gnu': 4.12.0 + '@rollup/rollup-linux-arm64-musl': 4.12.0 + '@rollup/rollup-linux-riscv64-gnu': 4.12.0 + '@rollup/rollup-linux-x64-gnu': 4.12.0 + '@rollup/rollup-linux-x64-musl': 4.12.0 + '@rollup/rollup-win32-arm64-msvc': 4.12.0 + '@rollup/rollup-win32-ia32-msvc': 4.12.0 + '@rollup/rollup-win32-x64-msvc': 4.12.0 fsevents: 2.3.3 dev: true @@ -5999,8 +6198,8 @@ packages: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 has-symbols: 1.0.3 isarray: 2.0.5 dev: true @@ -6013,12 +6212,12 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /safe-regex-test@1.0.2: - resolution: {integrity: sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==} + /safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 is-regex: 1.1.4 dev: true @@ -6038,8 +6237,8 @@ packages: loose-envify: 1.4.0 dev: true - /scule@1.2.0: - resolution: {integrity: sha512-CRCmi5zHQnSoeCik9565PONMg0kfkvYmcSqrbOJY4txFfy1wvVULV4FDaiXhUblUgahdqz3F2NwHZ8i4eBTwUw==} + /scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} dev: true /semver@5.7.2: @@ -6052,8 +6251,8 @@ packages: hasBin: true dev: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} hasBin: true dependencies: @@ -6093,24 +6292,26 @@ packages: - supports-color dev: true - /set-function-length@1.2.0: - resolution: {integrity: sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==} + /set-function-length@1.2.1: + resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 + define-data-property: 1.1.4 + es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.2 + get-intrinsic: 1.2.4 gopd: 1.0.1 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true - /set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + /set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} dependencies: - define-data-property: 1.1.1 + define-data-property: 1.1.4 + es-errors: 1.3.0 functions-have-names: 1.2.3 - has-property-descriptors: 1.0.1 + has-property-descriptors: 1.0.2 dev: true /setprototypeof@1.2.0: @@ -6145,11 +6346,13 @@ packages: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} dev: true - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + /side-channel@1.0.5: + resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 object-inspect: 1.13.1 dev: true @@ -6185,6 +6388,11 @@ packages: engines: {node: '>=12'} dev: true + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + dev: true + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -6209,22 +6417,22 @@ packages: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.16 + spdx-license-ids: 3.0.17 dev: true - /spdx-exceptions@2.4.0: - resolution: {integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==} + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} dev: true /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: - spdx-exceptions: 2.4.0 - spdx-license-ids: 3.0.16 + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 dev: true - /spdx-license-ids@3.0.16: - resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} + /spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} dev: true /split2@4.2.0: @@ -6305,25 +6513,25 @@ packages: resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string.prototype.trimend@1.0.7: resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.22.3 + es-abstract: 1.22.4 dev: true /string_decoder@1.1.1: @@ -6378,20 +6586,20 @@ packages: engines: {node: '>=8'} dev: true - /strip-literal@1.3.0: - resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + /strip-literal@2.0.0: + resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==} dependencies: - acorn: 8.11.3 + js-tokens: 8.0.3 dev: true - /stylehacks@6.0.2(postcss@8.4.33): - resolution: {integrity: sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==} + /stylehacks@6.0.3(postcss@8.4.35): + resolution: {integrity: sha512-KzBqjnqktc8/I0ERCb+lGq06giF/JxDbw2r9kEVhen9noHeIDRtMWUp9r62sOk+/2bbX6sFG1GhsS7ToXG0PEg==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 dependencies: - browserslist: 4.22.2 - postcss: 8.4.33 + browserslist: 4.23.0 + postcss: 8.4.35 postcss-selector-parser: 6.0.15 dev: true @@ -6414,7 +6622,7 @@ packages: methods: 1.1.2 mime: 2.6.0 qs: 6.11.2 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color dev: true @@ -6557,8 +6765,8 @@ packages: engines: {node: '>=14.0.0'} dev: true - /tinyspy@2.2.0: - resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} dev: true @@ -6661,42 +6869,48 @@ packages: mime-types: 2.1.35 dev: true - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + /typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 - get-intrinsic: 1.2.2 - is-typed-array: 1.1.12 + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 dev: true - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + /typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 dev: true - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + /typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} engines: {node: '>= 0.4'} dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 for-each: 0.3.3 - has-proto: 1.0.1 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 dev: true - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + /typed-array-length@1.0.5: + resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 for-each: 0.3.3 - is-typed-array: 1.1.12 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 dev: true /typedarray@0.0.6: @@ -6709,8 +6923,8 @@ packages: hasBin: true dev: true - /ufo@1.3.2: - resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + /ufo@1.4.0: + resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} /umd@3.0.3: resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} @@ -6720,7 +6934,7 @@ packages: /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: - call-bind: 1.0.5 + call-bind: 1.0.7 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 @@ -6742,22 +6956,22 @@ packages: '@rollup/plugin-replace': 5.0.5(rollup@3.29.4) '@rollup/pluginutils': 5.1.0(rollup@3.29.4) chalk: 5.3.0 - citty: 0.1.5 + citty: 0.1.6 consola: 3.2.3 defu: 6.1.4 esbuild: 0.19.12 globby: 13.2.2 hookable: 5.5.3 jiti: 1.21.0 - magic-string: 0.30.5 + magic-string: 0.30.7 mkdist: 1.4.0(typescript@5.3.3) - mlly: 1.5.0 + mlly: 1.6.1 pathe: 1.1.2 pkg-types: 1.0.3 pretty-bytes: 6.1.1 rollup: 3.29.4 rollup-plugin-dts: 6.1.0(rollup@3.29.4)(typescript@5.3.3) - scule: 1.2.0 + scule: 1.3.0 typescript: 5.3.3 untyped: 1.4.2 transitivePeerDependencies: @@ -6789,9 +7003,14 @@ packages: consola: 3.2.3 defu: 6.1.4 mime: 3.0.0 - node-fetch-native: 1.6.1 + node-fetch-native: 1.6.2 pathe: 1.1.2 + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: true + /universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6811,7 +7030,7 @@ packages: resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} hasBin: true dependencies: - citty: 0.1.5 + citty: 0.1.6 consola: 3.2.3 pathe: 1.1.2 @@ -6820,24 +7039,24 @@ packages: hasBin: true dependencies: '@babel/core': 7.23.9 - '@babel/standalone': 7.23.9 + '@babel/standalone': 7.23.10 '@babel/types': 7.23.9 defu: 6.1.4 jiti: 1.21.0 mri: 1.2.0 - scule: 1.2.0 + scule: 1.3.0 transitivePeerDependencies: - supports-color dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.2): + /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.22.2 - escalade: 3.1.1 + browserslist: 4.23.0 + escalade: 3.1.2 picocolors: 1.0.0 dev: true @@ -6881,8 +7100,8 @@ packages: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 - is-typed-array: 1.1.12 - which-typed-array: 1.1.13 + is-typed-array: 1.1.13 + which-typed-array: 1.1.14 dev: true /utils-merge@1.0.1: @@ -6903,7 +7122,7 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.22 + '@jridgewell/trace-mapping': 0.3.23 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 dev: true @@ -6920,8 +7139,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /vite-node@1.2.1(@types/node@20.11.6): - resolution: {integrity: sha512-fNzHmQUSOY+y30naohBvSW7pPn/xn3Ib/uqm+5wAJQJiqQsU0NBR78XdRJb04l4bOFKjpTWld0XAfkKlrDbySg==} + /vite-node@1.3.1(@types/node@20.11.24): + resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -6929,7 +7148,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.0.12(@types/node@20.11.6) + vite: 5.1.4(@types/node@20.11.24) transitivePeerDependencies: - '@types/node' - less @@ -6941,8 +7160,8 @@ packages: - terser dev: true - /vite@5.0.12(@types/node@20.11.6): - resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} + /vite@5.1.4(@types/node@20.11.24): + resolution: {integrity: sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -6969,23 +7188,23 @@ packages: terser: optional: true dependencies: - '@types/node': 20.11.6 + '@types/node': 20.11.24 esbuild: 0.19.12 - postcss: 8.4.33 - rollup: 4.9.6 + postcss: 8.4.35 + rollup: 4.12.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@1.2.1(@types/node@20.11.6): - resolution: {integrity: sha512-TRph8N8rnSDa5M2wKWJCMnztCZS9cDcgVTQ6tsTFTG/odHJ4l5yNVqvbeDJYJRZ6is3uxaEpFs8LL6QM+YFSdA==} + /vitest@1.3.1(@types/node@20.11.24): + resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': ^1.0.0 - '@vitest/ui': ^1.0.0 + '@vitest/browser': 1.3.1 + '@vitest/ui': 1.3.1 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -7002,27 +7221,26 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.11.6 - '@vitest/expect': 1.2.1 - '@vitest/runner': 1.2.1 - '@vitest/snapshot': 1.2.1 - '@vitest/spy': 1.2.1 - '@vitest/utils': 1.2.1 + '@types/node': 20.11.24 + '@vitest/expect': 1.3.1 + '@vitest/runner': 1.3.1 + '@vitest/snapshot': 1.3.1 + '@vitest/spy': 1.3.1 + '@vitest/utils': 1.3.1 acorn-walk: 8.3.2 - cac: 6.7.14 chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.5 + magic-string: 0.30.7 pathe: 1.1.2 picocolors: 1.0.0 std-env: 3.7.0 - strip-literal: 1.3.0 + strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.0.12(@types/node@20.11.6) - vite-node: 1.2.1(@types/node@20.11.6) + vite: 5.1.4(@types/node@20.11.24) + vite-node: 1.3.1(@types/node@20.11.24) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -7048,15 +7266,15 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + /which-typed-array@1.1.14: + resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} engines: {node: '>= 0.4'} dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.5 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 dev: true /which@2.0.2: @@ -7092,9 +7310,10 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yaml@2.3.4: - resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + /yaml@2.4.0: + resolution: {integrity: sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==} engines: {node: '>= 14'} + hasBin: true dev: true /yocto-queue@0.1.0: diff --git a/src/app.ts b/src/app.ts index b7241c93..6bd5ad20 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,5 @@ -import { withoutTrailingSlash } from "ufo"; +import { joinURL, parseURL, withoutTrailingSlash } from "ufo"; +import type { AdapterOptions as WSOptions } from "crossws"; import { lazyEventHandler, toEventHandler, @@ -16,7 +17,11 @@ import { isWebResponse, sendNoContent, } from "./utils"; -import type { EventHandler, LazyEventHandler } from "./types"; +import type { + EventHandler, + EventHandlerResolver, + LazyEventHandler, +} from "./types"; export interface Layer { route: string; @@ -47,6 +52,8 @@ export interface AppUse { (options: InputLayer): App; } +export type WebSocketOptions = WSOptions; + export interface AppOptions { debug?: boolean; onError?: (error: H3Error, event: H3Event) => any; @@ -59,6 +66,7 @@ export interface AppOptions { event: H3Event, response?: { body?: unknown }, ) => void | Promise; + websocket?: WebSocketOptions; } export interface App { @@ -66,18 +74,35 @@ export interface App { handler: EventHandler; options: AppOptions; use: AppUse; + resolve: EventHandlerResolver; + readonly websocket: WebSocketOptions; } +/** + * Create a new H3 app instance. + */ export function createApp(options: AppOptions = {}): App { const stack: Stack = []; + const handler = createAppEventHandler(stack, options); + + const resolve = createResolver(stack); + handler.__resolve__ = resolve; + + const getWebsocket = cachedFn(() => websocketOptions(resolve, options)); + const app: App = { - // @ts-ignore + // @ts-expect-error use: (arg1, arg2, arg3) => use(app as App, arg1, arg2, arg3), + resolve, handler, stack, options, + get websocket() { + return getWebsocket(); + }, }; + return app; } @@ -100,9 +125,7 @@ export function use( normalizeLayer({ ...arg3, route: arg1, handler: arg2 as EventHandler }), ); } else if (typeof arg1 === "function") { - app.stack.push( - normalizeLayer({ ...arg2, route: "/", handler: arg1 as EventHandler }), - ); + app.stack.push(normalizeLayer({ ...arg2, handler: arg1 as EventHandler })); } else { app.stack.push(normalizeLayer({ ...arg1 })); } @@ -187,6 +210,37 @@ export function createAppEventHandler(stack: Stack, options: AppOptions) { }); } +function createResolver(stack: Stack): EventHandlerResolver { + return async (path: string) => { + let _layerPath: string; + for (const layer of stack) { + if (layer.route === "/" && !layer.handler.__resolve__) { + continue; + } + if (!path.startsWith(layer.route)) { + continue; + } + _layerPath = path.slice(layer.route.length) || "/"; + if (layer.match && !layer.match(_layerPath, undefined)) { + continue; + } + let res = { route: layer.route, handler: layer.handler }; + if (res.handler.__resolve__) { + const _res = await res.handler.__resolve__(_layerPath); + if (!_res) { + continue; + } + res = { + ...res, + ..._res, + route: joinURL(res.route || "/", _res.route || "/"), + }; + } + return res; + } + }; +} + function normalizeLayer(input: InputLayer) { let handler = input.handler; // @ts-ignore @@ -271,3 +325,27 @@ function handleHandlerResponse(event: H3Event, val: any, jsonSpace?: number) { statusMessage: `[h3] Cannot send ${valType} as response.`, }); } + +function cachedFn(fn: () => T): () => T { + let cache: T; + return () => { + if (!cache) { + cache = fn(); + } + return cache; + }; +} + +function websocketOptions( + evResolver: EventHandlerResolver, + appOptions: AppOptions, +): WSOptions { + return { + ...appOptions.websocket, + async resolve(info) { + const { pathname } = parseURL(info.url || "/"); + const resolved = await evResolver(pathname); + return resolved?.handler?.__websocket__ || {}; + }, + }; +} diff --git a/src/event/utils.ts b/src/event/utils.ts index 24ee2b1e..277be5bc 100644 --- a/src/event/utils.ts +++ b/src/event/utils.ts @@ -47,7 +47,8 @@ export function defineEventHandler< ): EventHandler { // Function Syntax if (typeof handler === "function") { - return Object.assign(handler, { __is_handler__: true }); + handler.__is_handler__ = true; + return handler; } // Object Syntax const _hooks: _EventHandlerHooks = { @@ -57,7 +58,10 @@ export function defineEventHandler< const _handler: EventHandler = (event) => { return _callHandler(event, handler.handler, _hooks); }; - return Object.assign(_handler, { __is_handler__: true }); + _handler.__is_handler__ = true; + _handler.__resolve__ = handler.handler.__resolve__; + _handler.__websocket__ = handler.websocket; + return _handler; } function _normalizeArray(input?: T | T[]): T[] | undefined { @@ -148,8 +152,9 @@ export function dynamicEventHandler( export function defineLazyEventHandler( factory: T, ): Awaited> { - let _promise: Promise; - let _resolved: EventHandler; + let _promise: Promise; + let _resolved: { handler: EventHandler }; + const resolveHandler = () => { if (_resolved) { return Promise.resolve(_resolved); @@ -163,17 +168,22 @@ export function defineLazyEventHandler( handler, ); } - _resolved = toEventHandler(r.default || r); + _resolved = { handler: toEventHandler(r.default || r) }; return _resolved; }); } return _promise; }; - return eventHandler((event) => { + + const handler = eventHandler((event) => { if (_resolved) { - return _resolved(event); + return _resolved.handler(event); } - return resolveHandler().then((handler) => handler(event)); + return resolveHandler().then((r) => r.handler(event)); }) as Awaited>; + + handler.__resolve__ = resolveHandler; + + return handler; } export const lazyEventHandler = defineLazyEventHandler; diff --git a/src/router.ts b/src/router.ts index 424becca..8a691b8e 100644 --- a/src/router.ts +++ b/src/router.ts @@ -3,6 +3,7 @@ import { toRouteMatcher, RouteMatcher, } from "radix3"; +import { withLeadingSlash } from "ufo"; import type { HTTPMethod, EventHandler } from "./types"; import { createError } from "./error"; import { eventHandler, toEventHandler } from "./event"; @@ -44,6 +45,9 @@ export interface CreateRouterOptions { preemptive?: boolean; } +/** + * Create a new h3 router instance. + */ export function createRouter(opts: CreateRouterOptions = {}): Router { const _router = _createRouter({}); const routes: Record = {}; @@ -79,10 +83,9 @@ export function createRouter(opts: CreateRouterOptions = {}): Router { router[method] = (path, handle) => router.add(path, handle, method); } - // Main handle - router.handler = eventHandler((event) => { + // Handler matcher + const matchHandler = (path = "/", method: RouterMethod = "get") => { // Remove query parameters for matching - let path = event.path || "/"; const qIndex = path.indexOf("?"); if (qIndex !== -1) { path = path.slice(0, Math.max(0, qIndex)); @@ -91,26 +94,20 @@ export function createRouter(opts: CreateRouterOptions = {}): Router { // Match route const matched = _router.lookup(path); if (!matched || !matched.handlers) { - if (opts.preemptive || opts.preemtive) { - throw createError({ + return { + error: createError({ statusCode: 404, name: "Not Found", - statusMessage: `Cannot find any route matching ${event.path || "/"}.`, - }); - } else { - return; // Let app match other handlers - } + statusMessage: `Cannot find any route matching ${path || "/"}.`, + }), + }; } // Match method - const method = ( - event.node.req.method || "get" - ).toLowerCase() as RouterMethod; - let handler: EventHandler | undefined = matched.handlers[method] || matched.handlers.all; - // Fallback to search for shadowed routes + // Fallback to search for (method) shadowed routes if (!handler) { if (!_matcher) { _matcher = toRouteMatcher(_router); @@ -131,32 +128,71 @@ export function createRouter(opts: CreateRouterOptions = {}): Router { } } - // Method not matched if (!handler) { - if (opts.preemptive || opts.preemtive) { - throw createError({ + return { + error: createError({ statusCode: 405, name: "Method Not Allowed", statusMessage: `Method ${method} is not allowed on this route.`, - }); + }), + }; + } + + return { matched, handler }; + }; + + // Main handle + const isPreemptive = opts.preemptive || opts.preemtive; + router.handler = eventHandler((event) => { + // Match handler + const match = matchHandler( + event.path, + event.method.toLowerCase() as RouterMethod, + ); + + // No match (method or route) + if ("error" in match) { + if (isPreemptive) { + throw match.error; } else { return; // Let app match other handlers } } // Add matched route and params to the context - event.context.matchedRoute = matched; - const params = matched.params || {}; + event.context.matchedRoute = match.matched; + const params = match.matched.params || {}; event.context.params = params; // Call handler - return Promise.resolve(handler(event)).then((res) => { - if (res === undefined && (opts.preemptive || opts.preemtive)) { + return Promise.resolve(match.handler(event)).then((res) => { + if (res === undefined && isPreemptive) { return null; // Send empty content } return res; }); }); + // Resolver + router.handler.__resolve__ = async (path) => { + path = withLeadingSlash(path); + const match = matchHandler(path); + if ("error" in match) { + return; + } + let res = { + route: match.matched.path, + handler: match.handler, + }; + if (match.handler.__resolve__) { + const _res = await match.handler.__resolve__(path); + if (!_res) { + return; + } + res = { ...res, ..._res }; + } + return res; + }; + return router; } diff --git a/src/types.ts b/src/types.ts index 1c664df3..7116c475 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import type { QueryObject } from "ufo"; +import type { Hooks as WSHooks } from "crossws"; import type { H3Event } from "./event"; import type { Session } from "./utils/session"; import type { RouteNode } from "./router"; @@ -62,11 +63,19 @@ export type InferEventInput< T, > = void extends T ? (Event extends H3Event ? E[Key] : never) : T; +type MaybePromise = T | Promise; + +export type EventHandlerResolver = ( + path: string, +) => MaybePromise; + export interface EventHandler< Request extends EventHandlerRequest = EventHandlerRequest, Response extends EventHandlerResponse = EventHandlerResponse, > { __is_handler__?: true; + __resolve__?: EventHandlerResolver; + __websocket__?: Partial; (event: H3Event): Response; } @@ -90,6 +99,8 @@ export type EventHandlerObject< onBeforeResponse?: | _ResponseMiddleware | _ResponseMiddleware[]; + /** @experimental */ + websocket?: Partial; handler: EventHandler; }; diff --git a/src/utils/body.ts b/src/utils/body.ts index ff7f6171..f83ee25f 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -25,6 +25,12 @@ const PayloadMethods: HTTPMethod[] = ["PATCH", "POST", "PUT", "DELETE"]; /** * Reads body of the request and returns encoded raw string (default), or `Buffer` if encoding is falsy. + * + * @example + * export default defineEventHandler(async (event) => { + * const body = await readRawBody(event, "utf-8"); + * }); + * * @param event {H3Event} H3 event or req passed by h3 handler * @param encoding {Encoding} encoding="utf-8" - The character encoding to use. * @@ -128,14 +134,16 @@ export function readRawBody( /** * Reads request body and tries to safely parse using [destr](https://github.com/unjs/destr). + * + * @example + * export default defineEventHandler(async (event) => { + * const body = await readBody(event); + * }); + * * @param event H3 event passed by h3 handler * @param encoding The character encoding to use, defaults to 'utf-8'. * * @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request JSON body - * - * ```ts - * const body = await readBody(event) - * ``` */ export async function readBody< @@ -170,23 +178,28 @@ export async function readBody< /** * Tries to read the request body via `readBody`, then uses the provided validation function and either throws a validation error or returns the result. + * + * You can use a simple function to validate the body or use a library like `zod` to define a schema. + * + * @example + * export default defineEventHandler(async (event) => { + * const body = await readValidatedBody(event, (body) => { + * return typeof body === "object" && body !== null; + * }); + * }); + * @example + * import { z } from "zod"; + * + * export default defineEventHandler(async (event) => { + * const objectSchema = z.object(); + * const body = await readValidatedBody(event, objectSchema.safeParse); + * }); + * * @param event The H3Event passed by the handler. * @param validate The function to use for body validation. It will be called passing the read request body. If the result is not false, the parsed body will be returned. * @throws If the validation function returns `false` or throws, a validation error will be thrown. * @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request JSON body. * @see {readBody} - * - * ```ts - * // With a custom validation function - * const body = await readValidatedBody(event, (body) => { - * return typeof body === "object" && body !== null - * }) - * - * // With a zod schema - * import { z } from 'zod' - * const objectSchema = z.object() - * const body = await readValidatedBody(event, objectSchema.safeParse) - * ``` */ export async function readValidatedBody< T, @@ -199,24 +212,26 @@ export async function readValidatedBody< /** * Tries to read and parse the body of a an H3Event as multipart form. + * + * @example + * export default defineEventHandler(async (event) => { + * const formData = await readMultipartFormData(event); + * // The result could look like: + * // [ + * // { + * // "data": "other", + * // "name": "baz", + * // }, + * // { + * // "data": "something", + * // "name": "some-other-data", + * // }, + * // ]; + * }); + * * @param event The H3Event object to read multipart form from. * * @return The parsed form data. If no form could be detected because the content type is not multipart/form-data or no boundary could be found. - * - * ```ts - * const formData = await readMultipartFormData(event) - * // The result could look like: - * // [ - * // { - * // "data": "other", - * // "name": "baz", - * // }, - * // { - * // "data": "something", - * // "name": "some-other-data", - * // }, - * // ] - * ``` */ export async function readMultipartFormData( event: H3Event, @@ -238,15 +253,15 @@ export async function readMultipartFormData( /** * Constructs a FormData object from an event, after converting it to a a web request. - * @param event The H3Event object to read the form data from. * - * ```ts - * const eventHandler = event => { - * const formData = await readFormData(event) - * const email = formData.get("email") - * const password = formData.get("password") - * } - * ``` + * @example + * export default defineEventHandler(async (event) => { + * const formData = await readFormData(event); + * const email = formData.get("email"); + * const password = formData.get("password"); + * }); + * + * @param event The H3Event object to read the form data from. */ export async function readFormData(event: H3Event): Promise { return await toWebRequest(event).formData(); diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts index 98ca9beb..1dc48c30 100644 --- a/src/utils/cookie.ts +++ b/src/utils/cookie.ts @@ -79,13 +79,16 @@ export function deleteCookie( } /** - * Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas - * that are within a single set-cookie field-value, such as in the Expires portion. + * Set-Cookie header field-values are sometimes comma joined in one string. + * + * This splits them without choking on commas that are within a single set-cookie field-value, such as in the Expires portion. * This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2 - * Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128 + * Node.js does this for every header _except_ set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128 * Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25 * Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation * @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144 + * + * @internal */ export function splitCookiesString(cookiesString: string | string[]): string[] { if (Array.isArray(cookiesString)) { diff --git a/src/utils/cors/handler.ts b/src/utils/cors/handler.ts index 82d18216..10ce3fa8 100644 --- a/src/utils/cors/handler.ts +++ b/src/utils/cors/handler.ts @@ -8,6 +8,13 @@ import { } from "./utils"; import type { H3CorsOptions } from "./types"; +/** + * Handle CORS for the incoming request. + * + * If the incoming request is a CORS preflight request, it will append the CORS preflight headers and send a 204 response. + * + * If return value is `true`, the request is handled and no further action is needed. + */ export function handleCors(event: H3Event, options: H3CorsOptions): boolean { const _options = resolveCorsOptions(options); if (isPreflightRequest(event)) { diff --git a/src/utils/cors/utils.ts b/src/utils/cors/utils.ts index b8316002..b4900c63 100644 --- a/src/utils/cors/utils.ts +++ b/src/utils/cors/utils.ts @@ -13,6 +13,9 @@ import type { H3AccessControlMaxAgeHeader, } from "./types"; +/** + * Resolve CORS options. + */ export function resolveCorsOptions( options: H3CorsOptions = {}, ): H3ResolvedCorsOptions { @@ -31,6 +34,9 @@ export function resolveCorsOptions( return defu(options, defaultOptions); } +/** + * Check if the incoming request is a CORS preflight request. + */ export function isPreflightRequest(event: H3Event): boolean { const origin = getRequestHeader(event, "origin"); const accessControlRequestMethod = getRequestHeader( @@ -41,6 +47,9 @@ export function isPreflightRequest(event: H3Event): boolean { return event.method === "OPTIONS" && !!origin && !!accessControlRequestMethod; } +/** + * Check if the incoming request is a CORS request. + */ export function isCorsOriginAllowed( origin: ReturnType["origin"], options: H3CorsOptions, @@ -69,6 +78,9 @@ export function isCorsOriginAllowed( return originOption(origin); } +/** + * Create the `access-control-allow-origin` header. + */ export function createOriginHeaders( event: H3Event, options: H3CorsOptions, @@ -89,6 +101,9 @@ export function createOriginHeaders( : {}; } +/** + * Create the `access-control-allow-methods` header. + */ export function createMethodsHeaders( options: H3CorsOptions, ): H3AccessControlAllowMethodsHeader { @@ -107,6 +122,9 @@ export function createMethodsHeaders( : {}; } +/** + * Create the `access-control-allow-credentials` header. + */ export function createCredentialsHeaders( options: H3CorsOptions, ): H3AccessControlAllowCredentialsHeader { @@ -119,6 +137,9 @@ export function createCredentialsHeaders( return {}; } +/** + * Create the `access-control-allow-headers` and `vary` headers. + */ export function createAllowHeaderHeaders( event: H3Event, options: H3CorsOptions, @@ -142,6 +163,9 @@ export function createAllowHeaderHeaders( }; } +/** + * Create the `access-control-expose-headers` header. + */ export function createExposeHeaders( options: H3CorsOptions, ): H3AccessControlExposeHeadersHeader { @@ -158,6 +182,9 @@ export function createExposeHeaders( return { "access-control-expose-headers": exposeHeaders.join(",") }; } +/** + * Create the `access-control-max-age` header. + */ export function createMaxAgeHeader( options: H3CorsOptions, ): H3AccessControlMaxAgeHeader { @@ -170,8 +197,9 @@ export function createMaxAgeHeader( return {}; } -// TODO: Implemente e2e tests to improve code coverage -/* c8 ignore start */ +/** + * Append CORS preflight headers to the response. + */ export function appendCorsPreflightHeaders( event: H3Event, options: H3CorsOptions, @@ -182,13 +210,12 @@ export function appendCorsPreflightHeaders( appendHeaders(event, createMethodsHeaders(options)); appendHeaders(event, createAllowHeaderHeaders(event, options)); } -/* c8 ignore end */ -// TODO: Implemente e2e tests to improve code coverage -/* c8 ignore start */ +/** + * Append CORS headers to the response. + */ export function appendCorsHeaders(event: H3Event, options: H3CorsOptions) { appendHeaders(event, createOriginHeaders(event, options)); appendHeaders(event, createCredentialsHeaders(options)); appendHeaders(event, createExposeHeaders(options)); } -/* c8 ignore end */ diff --git a/src/utils/fingerprint.ts b/src/utils/fingerprint.ts index 7cbd11df..2b729e02 100644 --- a/src/utils/fingerprint.ts +++ b/src/utils/fingerprint.ts @@ -22,7 +22,12 @@ export interface RequestFingerprintOptions { userAgent?: boolean; } -/** @experimental Behavior of this utility might change in the future versions */ +/** + * + * Get a unique fingerprint for the incoming request. + * + * @experimental Behavior of this utility might change in the future versions + */ export async function getRequestFingerprint( event: H3Event, opts: RequestFingerprintOptions = {}, diff --git a/src/utils/index.ts b/src/utils/index.ts index f24486b1..9e788463 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -10,4 +10,6 @@ export * from "./request"; export * from "./response"; export * from "./sanitize"; export * from "./session"; +export * from "./sse"; export * from "./static"; +export * from "./ws"; diff --git a/src/utils/internal/iteratable.ts b/src/utils/internal/iteratable.ts new file mode 100644 index 00000000..68f9aa2b --- /dev/null +++ b/src/utils/internal/iteratable.ts @@ -0,0 +1,64 @@ +export type IterationSource = + | Iterable + | AsyncIterable + | Iterator + | AsyncIterator + | (() => + | Iterator + | AsyncIterator); + +type SendableValue = string | Buffer | Uint8Array; +export type IteratorSerializer = ( + value: Value, +) => SendableValue | undefined; + +/** + * The default implementation for {@link sendIterable}'s `serializer` argument. + * It serializes values as follows: + * - Instances of {@link String}, {@link Uint8Array} and `undefined` are returned as-is. + * - Objects are serialized through {@link JSON.stringify}. + * - Functions are serialized as `undefined`. + * - Values of type boolean, number, bigint or symbol are serialized using their `toString` function. + * + * @param value - The value to serialize to either a string or Uint8Array. + */ +export function serializeIterableValue( + value: unknown, +): SendableValue | undefined { + switch (typeof value) { + case "string": { + return value; + } + case "boolean": + case "number": + case "bigint": + case "symbol": { + return value.toString(); + } + case "function": + case "undefined": { + return undefined; + } + case "object": { + if (value instanceof Uint8Array) { + return value; + } + return JSON.stringify(value); + } + } +} + +export function coerceIterable( + iterable: IterationSource, +): Iterator | AsyncIterator { + if (typeof iterable === "function") { + iterable = iterable(); + } + if (Symbol.iterator in iterable) { + return iterable[Symbol.iterator](); + } + if (Symbol.asyncIterator in iterable) { + return iterable[Symbol.asyncIterator](); + } + return iterable; +} diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts index 0ec623bc..9aded1e2 100644 --- a/src/utils/proxy.ts +++ b/src/utils/proxy.ts @@ -31,6 +31,9 @@ const ignoredHeaders = new Set([ "accept", ]); +/** + * Proxy the incoming request to a target URL. + */ export async function proxyRequest( event: H3Event, target: string, @@ -70,6 +73,9 @@ export async function proxyRequest( }); } +/** + * Make a proxy request to a target URL and send the response back to the client. + */ export async function sendProxy( event: H3Event, target: string, @@ -154,6 +160,9 @@ export async function sendProxy( return event.node.res.end(); } +/** + * Get the request headers object without headers known to cause issues when proxying. + */ export function getProxyRequestHeaders(event: H3Event) { const headers = Object.create(null); const reqHeaders = getRequestHeaders(event); @@ -165,6 +174,9 @@ export function getProxyRequestHeaders(event: H3Event) { return headers; } +/** + * Make a fetch request with the event's context and headers. + */ export function fetchWithEvent< T = unknown, // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/utils/request.ts b/src/utils/request.ts index 6f033a51..80219c68 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -10,6 +10,9 @@ import type { H3Event } from "../event"; import { validateData, ValidateFunction } from "./internal/validate"; import { getRequestWebStream } from "./body"; +/** + * Get query the params object from the request URL parsed with [unjs/ufo](https://ufo.unjs.io). + */ export function getQuery< T, Event extends H3Event = H3Event, @@ -18,6 +21,9 @@ export function getQuery< return _getQuery(event.path || "") as _T; } +/** + * Get the query param from the request URL parsed with [unjs/ufo](https://ufo.unjs.io) and validated with validate function. + */ export function getValidatedQuery< T, Event extends H3Event = H3Event, @@ -27,6 +33,11 @@ export function getValidatedQuery< return validateData(query, validate); } +/** + * Get matched route params. + * + * If `decode` option is `true`, it will decode the matched route params using `decodeURI`. + */ export function getRouterParams( event: H3Event, opts: { decode?: boolean } = {}, @@ -43,6 +54,9 @@ export function getRouterParams( return params; } +/** + * Get matched route params and validate with validate function. + */ export function getValidatedRouterParams< T, Event extends H3Event = H3Event, @@ -57,6 +71,9 @@ export function getValidatedRouterParams< return validateData(routerParams, validate); } +/** + * Get a matched route param by name. + */ export function getRouterParam( event: H3Event, name: string, @@ -77,6 +94,13 @@ export function getMethod( return (event.node.req.method || defaultMethod).toUpperCase() as HTTPMethod; } +/** + * + * Checks if the incoming request method is of the expected type. + * + * If `allowHead` is `true`, it will allow `HEAD` requests to pass if the expected method is `GET`. + * + */ export function isMethod( event: H3Event, expected: HTTPMethod | HTTPMethod[], @@ -97,6 +121,11 @@ export function isMethod( return false; } +/** + * Asserts that the incoming request method is of the expected type using `isMethod`. + * + * If the method is not allowed, it will throw a 405 error with the message "HTTP method is not allowed". + */ export function assertMethod( event: H3Event, expected: HTTPMethod | HTTPMethod[], @@ -110,6 +139,11 @@ export function assertMethod( } } +/** + * Get the request headers object. + * + * Array headers are joined with a comma. + */ export function getRequestHeaders(event: H3Event): RequestHeaders { const _headers: RequestHeaders = {}; for (const key in event.node.req.headers) { @@ -119,8 +153,14 @@ export function getRequestHeaders(event: H3Event): RequestHeaders { return _headers; } +/** + * Alias for `getRequestHeaders`. + */ export const getHeaders = getRequestHeaders; +/** + * Get a request header by name. + */ export function getRequestHeader( event: H3Event, name: HTTPHeaderName, @@ -130,8 +170,18 @@ export function getRequestHeader( return value; } +/** + * Alias for `getRequestHeader`. + */ export const getHeader = getRequestHeader; +/** + * Get the request hostname. + * + * If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. + * + * If no host header is found, it will default to "localhost". + */ export function getRequestHost( event: H3Event, opts: { xForwardedHost?: boolean } = {}, @@ -145,6 +195,13 @@ export function getRequestHost( return event.node.req.headers.host || "localhost"; } +/** + * Get the request protocol. + * + * If `x-forwarded-proto` header is set to "https", it will return "https". You can disable this behavior by setting `xForwardedProto` to `false`. + * + * If protocol cannot be determined, it will default to "http". + */ export function getRequestProtocol( event: H3Event, opts: { xForwardedProto?: boolean } = {}, @@ -159,12 +216,20 @@ export function getRequestProtocol( } const DOUBLE_SLASH_RE = /[/\\]{2,}/g; + /** @deprecated Use `event.path` instead */ export function getRequestPath(event: H3Event): string { const path = (event.node.req.url || "/").replace(DOUBLE_SLASH_RE, "/"); return path; } +/** + * Generated the full incoming request URL using `getRequestProtocol`, `getRequestHost` and `event.path`. + * + * If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. + * + * If `xForwardedProto` is `false`, it will not use the `x-forwarded-proto` header. + */ export function getRequestURL( event: H3Event, opts: { xForwardedHost?: boolean; xForwardedProto?: boolean } = {}, @@ -178,6 +243,11 @@ export function getRequestURL( return new URL(path, `${protocol}://${host}`); } +/** + * Convert the H3Event to a WebRequest object. + * + * **NOTE:** This function is not stable and might have edge cases that are not handled properly. + */ export function toWebRequest(event: H3Event) { return ( event.web?.request || @@ -191,6 +261,11 @@ export function toWebRequest(event: H3Event) { ); } +/** + * Try to get the client IP address from the incoming request. + * + * If `xForwardedFor` is `true`, it will use the `x-forwarded-for` header if it exists. + */ export function getRequestIP( event: H3Event, opts: { diff --git a/src/utils/response.ts b/src/utils/response.ts index 4f2019f1..8ee1fa19 100644 --- a/src/utils/response.ts +++ b/src/utils/response.ts @@ -7,10 +7,22 @@ import { MIMES } from "./consts"; import { sanitizeStatusCode, sanitizeStatusMessage } from "./sanitize"; import { splitCookiesString } from "./cookie"; import { hasProp } from "./internal/object"; +import { + serializeIterableValue, + coerceIterable, + IterationSource, + IteratorSerializer, +} from "./internal/iteratable"; const defer = typeof setImmediate === "undefined" ? (fn: () => any) => fn() : setImmediate; +/** + * Directly send a response to the client. + * + * **Note:** This function should be used only when you want to send a response directly without using the `h3` event. + * Normaly you can directly `return` a value inside event handlers. + */ export function send(event: H3Event, data?: any, type?: string): Promise { if (type) { defaultContentType(event, type); @@ -27,6 +39,7 @@ export function send(event: H3Event, data?: any, type?: string): Promise { /** * Respond with an empty payload.
      + * * Note that calling this function will close the connection and no other data can be sent to the client afterwards. * * @param event H3 event @@ -51,6 +64,9 @@ export function sendNoContent(event: H3Event, code?: number) { event.node.res.end(); } +/** + * Set the response status code and message. + */ export function setResponseStatus( event: H3Event, code?: number, @@ -67,14 +83,23 @@ export function setResponseStatus( } } +/** + * Get the current response status code. + */ export function getResponseStatus(event: H3Event): number { return event.node.res.statusCode; } +/** + * Get the current response status message. + */ export function getResponseStatusText(event: H3Event): string { return event.node.res.statusMessage; } +/** + * Set the response status code and message. + */ export function defaultContentType(event: H3Event, type?: string) { if ( type && @@ -85,6 +110,13 @@ export function defaultContentType(event: H3Event, type?: string) { } } +/** + * Send a redirect response to the client. + * + * It adds the `location` header to the response and sets the status code to 302 by default. + * + * In the body, it sends a simple HTML page with a meta refresh tag to redirect the client in case the headers are ignored. + */ export function sendRedirect(event: H3Event, location: string, code = 302) { event.node.res.statusCode = sanitizeStatusCode( code, @@ -96,12 +128,18 @@ export function sendRedirect(event: H3Event, location: string, code = 302) { return send(event, html, MIMES.html); } +/** + * Get the response headers object. + */ export function getResponseHeaders( event: H3Event, ): ReturnType { return event.node.res.getHeaders(); } +/** + * Alias for `getResponseHeaders`. + */ export function getResponseHeader( event: H3Event, name: HTTPHeaderName, @@ -109,6 +147,9 @@ export function getResponseHeader( return event.node.res.getHeader(name); } +/** + * Set the response headers. + */ export function setResponseHeaders( event: H3Event, headers: Partial< @@ -120,8 +161,14 @@ export function setResponseHeaders( } } +/** + * Alias for `setResponseHeaders`. + */ export const setHeaders = setResponseHeaders; +/** + * Set a response header by name. + */ export function setResponseHeader( event: H3Event, name: HTTPHeaderName, @@ -130,8 +177,14 @@ export function setResponseHeader( event.node.res.setHeader(name, value); } +/** + * Alias for `setResponseHeader`. + */ export const setHeader = setResponseHeader; +/** + * Append the response headers. + */ export function appendResponseHeaders( event: H3Event, headers: Record, @@ -141,8 +194,14 @@ export function appendResponseHeaders( } } +/** + * Alias for `appendResponseHeaders`. + */ export const appendHeaders = appendResponseHeaders; +/** + * Append a response header by name. + */ export function appendResponseHeader( event: H3Event, name: HTTPHeaderName, @@ -162,6 +221,9 @@ export function appendResponseHeader( event.node.res.setHeader(name, [...current, value]); } +/** + * Alias for `appendResponseHeader`. + */ export const appendHeader = appendResponseHeader; /** @@ -184,6 +246,9 @@ export function clearResponseHeaders( } } +/** + * Remove a response header by name. + */ export function removeResponseHeader( event: H3Event, name: HTTPHeaderName, @@ -191,6 +256,9 @@ export function removeResponseHeader( return event.node.res.removeHeader(name); } +/** + * Checks if the data is a stream. (Node.js Readable Stream, React Pipeable Stream, or Web Stream) + */ export function isStream(data: any): data is Readable | ReadableStream { if (!data || typeof data !== "object") { return false; @@ -212,10 +280,18 @@ export function isStream(data: any): data is Readable | ReadableStream { return false; } +/** + * Checks if the data is a Response object. + */ export function isWebResponse(data: any): data is Response { return typeof Response !== "undefined" && data instanceof Response; } +/** + * Send a stream response to the client. + * + * Note: You can directly `return` a stream value inside event handlers alternatively which is recommended. + */ export function sendStream( event: H3Event, stream: Readable | ReadableStream, @@ -288,6 +364,10 @@ export function sendStream( } const noop = () => {}; + +/** + * Write `HTTP/1.1 103 Early Hints` to the client. + */ export function writeEarlyHints( event: H3Event, hints: string | string[] | Record, @@ -344,6 +424,9 @@ export function writeEarlyHints( } } +/** + * Send a Response object to the client. + */ export function sendWebResponse( event: H3Event, response: Response, @@ -374,3 +457,67 @@ export function sendWebResponse( } return sendStream(event, response.body); } + +/** + * Iterate a source of chunks and send back each chunk in order. + * Supports mixing async work toghether with emitting chunks. + * + * Each chunk must be a string or a buffer. + * + * For generator (yielding) functions, the returned value is treated the same as yielded values. + * + * @param event - H3 event + * @param iterable - Iterator that produces chunks of the response. + * @param serializer - Function that converts values from the iterable into stream-compatible values. + * @template Value - Test + * + * @example + * sendIterable(event, work()); + * async function* work() { + * // Open document body + * yield "\n

      Executing...

        \n"; + * // Do work ... + * for (let i = 0; i < 1000) { + * await delay(1000); + * // Report progress + * yield `
      1. Completed job #`; + * yield i; + * yield `
      2. \n`; + * } + * // Close out the report + * return `
      `; + * } + * async function delay(ms) { + * return new Promise(resolve => setTimeout(resolve, ms)); + * } + */ +export function sendIterable( + event: H3Event, + iterable: IterationSource, + options?: { + serializer: IteratorSerializer; + }, +): Promise { + const serializer = options?.serializer ?? serializeIterableValue; + const iterator = coerceIterable(iterable); + return sendStream( + event, + new ReadableStream({ + async pull(controller) { + const { value, done } = await iterator.next(); + if (value !== undefined) { + const chunk = serializer(value); + if (chunk !== undefined) { + controller.enqueue(chunk); + } + } + if (done) { + controller.close(); + } + }, + cancel() { + iterator.return?.(); + }, + }), + ); +} diff --git a/src/utils/route.ts b/src/utils/route.ts index 6dd74550..cba359f9 100644 --- a/src/utils/route.ts +++ b/src/utils/route.ts @@ -4,6 +4,22 @@ import { eventHandler } from "../event"; /** * Prefixes and executes a handler with a base path. + * + * @example + * const app = createApp(); + * const router = createRouter(); + * + * const apiRouter = createRouter().get( + * "/hello", + * defineEventHandler((event) => { + * return "Hello API!"; + * }), + * ); + * + * router.use("/api/**", useBase("/api", apiRouter.handler)); + * + * app.use(router.handler); + * * @param base The base path to prefix. When set to an empty string, the handler will be run as is. * @param handler The event handler to use with the adapted path. */ diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index 278b4c18..21986519 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -2,10 +2,18 @@ // eslint-disable-next-line no-control-regex const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g; +/** + * Make sure the status message is safe to use in a response. + * + * Allowed characters: horizontal tabs, spaces or visible ascii characters: https://www.rfc-editor.org/rfc/rfc7230#section-3.1.2 + */ export function sanitizeStatusMessage(statusMessage = ""): string { return statusMessage.replace(DISALLOWED_STATUS_CHARS, ""); } +/** + * Make sure the status code is a valid HTTP status code. + */ export function sanitizeStatusCode( statusCode?: string | number, defaultStatusCode = 200, diff --git a/src/utils/session.ts b/src/utils/session.ts index e3b726ed..ddd4cc0c 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -41,6 +41,10 @@ const DEFAULT_COOKIE: SessionConfig["cookie"] = { httpOnly: true, }; +/** + * Create a session manager for the current request. + * + */ export async function useSession( event: H3Event, config: SessionConfig, @@ -67,6 +71,9 @@ export async function useSession( return sessionManager; } +/** + * Get the session for the current request. + */ export async function getSession( event: H3Event, config: SessionConfig, @@ -136,6 +143,9 @@ type SessionUpdate = | Partial> | ((oldData: SessionData) => Partial> | undefined); +/** + * Update the session data for the current request. + */ export async function updateSession( event: H3Event, config: SessionConfig, @@ -171,6 +181,9 @@ export async function updateSession( return session; } +/** + * Encrypt and sign the session data for the current request. + */ export async function sealSession( event: H3Event, config: SessionConfig, @@ -191,6 +204,9 @@ export async function sealSession( return sealed; } +/** + * Decrypt and verify the session data for the current request. + */ export async function unsealSession( _event: H3Event, config: SessionConfig, @@ -215,6 +231,9 @@ export async function unsealSession( return unsealed; } +/** + * Clear the session data for the current request. + */ export async function clearSession( event: H3Event, config: Partial, diff --git a/src/utils/sse/event-stream.ts b/src/utils/sse/event-stream.ts new file mode 100644 index 00000000..262a5083 --- /dev/null +++ b/src/utils/sse/event-stream.ts @@ -0,0 +1,172 @@ +import type { H3Event } from "../../event"; +import { sendStream, setResponseStatus } from "../response"; +import { + formatEventStreamMessage, + formatEventStreamMessages, + setEventStreamHeaders, +} from "./utils"; +import { EventStreamMessage, EventStreamOptions } from "./types"; + +/** + * A helper class for [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format) + */ +export class EventStream { + private readonly _h3Event: H3Event; + private readonly _transformStream = new TransformStream(); + private readonly _writer: WritableStreamDefaultWriter; + private readonly _encoder: TextEncoder = new TextEncoder(); + + private _writerIsClosed = false; + private _paused = false; + private _unsentData: undefined | string; + private _disposed = false; + private _handled = false; + + constructor(event: H3Event, opts: EventStreamOptions = {}) { + this._h3Event = event; + this._writer = this._transformStream.writable.getWriter(); + this._writer.closed.then(() => { + this._writerIsClosed = true; + }); + if (opts.autoclose !== false) { + this._h3Event.node.req.on("close", () => this.close()); + } + } + + /** + * Publish new event(s) for the client + */ + async push(message: string): Promise; + async push(message: string[]): Promise; + async push(message: EventStreamMessage): Promise; + async push(message: EventStreamMessage[]): Promise; + async push( + message: EventStreamMessage | EventStreamMessage[] | string | string[], + ) { + if (typeof message === "string") { + await this._sendEvent({ data: message }); + return; + } + if (Array.isArray(message)) { + if (message.length === 0) { + return; + } + if (typeof message[0] === "string") { + const msgs: EventStreamMessage[] = []; + for (const item of message as string[]) { + msgs.push({ data: item }); + } + await this._sendEvents(msgs); + return; + } + await this._sendEvents(message as EventStreamMessage[]); + return; + } + await this._sendEvent(message); + } + + private async _sendEvent(message: EventStreamMessage) { + if (this._writerIsClosed) { + return; + } + if (this._paused && !this._unsentData) { + this._unsentData = formatEventStreamMessage(message); + return; + } + if (this._paused) { + this._unsentData += formatEventStreamMessage(message); + return; + } + await this._writer + .write(this._encoder.encode(formatEventStreamMessage(message))) + .catch(); + } + + private async _sendEvents(messages: EventStreamMessage[]) { + if (this._writerIsClosed) { + return; + } + const payload = formatEventStreamMessages(messages); + if (this._paused && !this._unsentData) { + this._unsentData = payload; + return; + } + if (this._paused) { + this._unsentData += payload; + return; + } + + await this._writer.write(this._encoder.encode(payload)).catch(); + } + + pause() { + this._paused = true; + } + + get isPaused() { + return this._paused; + } + + async resume() { + this._paused = false; + await this.flush(); + } + + async flush() { + if (this._writerIsClosed) { + return; + } + if (this._unsentData?.length) { + await this._writer.write(this._encoder.encode(this._unsentData)); + this._unsentData = undefined; + } + } + + /** + * Close the stream and the connection if the stream is being sent to the client + */ + async close() { + if (this._disposed) { + return; + } + if (!this._writerIsClosed) { + try { + await this._writer.close(); + } catch {} + } + // check if the stream has been given to the client before closing the connection + if ( + this._h3Event._handled && + this._handled && + !this._h3Event.node.res.closed + ) { + this._h3Event.node.res.end(); + } + this._disposed = true; + } + + /** + * Triggers callback when the writable stream is closed. + * It is also triggered after calling the `close()` method. + * It also triggers when the request connection has been closed by either the client or the server. + */ + onClosed(cb: () => any) { + this._writer.closed.then(cb); + this._h3Event.node?.req.on("close", cb); + } + + async send() { + setEventStreamHeaders(this._h3Event); + setResponseStatus(this._h3Event, 200); + this._h3Event._handled = true; + this._handled = true; + await sendStream(this._h3Event, this._transformStream.readable); + } +} + +export function isEventStream(input: unknown): input is EventStream { + if (typeof input !== "object" || input === null) { + return false; + } + return input instanceof EventStream; +} diff --git a/src/utils/sse/index.ts b/src/utils/sse/index.ts new file mode 100644 index 00000000..9e418d1c --- /dev/null +++ b/src/utils/sse/index.ts @@ -0,0 +1,38 @@ +import type { H3Event } from "../../event"; +import { EventStream } from "./event-stream"; +import { EventStreamOptions } from "./types"; +/** + * Initialize an EventStream instance for creating [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) + * + * @experimental This function is experimental and might be unstable in some environments. + * + * @example + * + * ```ts + * import { createEventStream, sendEventStream } from "h3"; + * + * eventHandler((event) => { + * const eventStream = createEventStream(event); + * + * // Send a message every second + * const interval = setInterval(async () => { + * await eventStream.push("Hello world"); + * }, 1000); + * + * // cleanup the interval and close the stream when the connection is terminated + * eventStream.onClosed(async () => { + * console.log("closing SSE..."); + * clearInterval(interval); + * await eventStream.close(); + * }); + * + * return eventStream.send(); + * }); + * ``` + */ +export function createEventStream( + event: H3Event, + opts?: EventStreamOptions, +): EventStream { + return new EventStream(event, opts); +} diff --git a/src/utils/sse/types.ts b/src/utils/sse/types.ts new file mode 100644 index 00000000..6a3f1f15 --- /dev/null +++ b/src/utils/sse/types.ts @@ -0,0 +1,18 @@ +export interface EventStreamOptions { + /** + * Automatically close the writable stream when the request is closed + * + * Default is `true` + */ + autoclose?: boolean; +} + +/** + * See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields + */ +export interface EventStreamMessage { + id?: string; + event?: string; + retry?: number; + data: string; +} diff --git a/src/utils/sse/utils.ts b/src/utils/sse/utils.ts new file mode 100644 index 00000000..2a838eb7 --- /dev/null +++ b/src/utils/sse/utils.ts @@ -0,0 +1,54 @@ +import { HTTPHeaderName } from "../../types"; +import { H3Event } from "../../event"; +import { getHeader } from "../request"; +import { setResponseHeaders } from "../response"; +import { EventStreamMessage } from "./types"; + +export function formatEventStreamMessage(message: EventStreamMessage): string { + let result = ""; + if (message.id) { + result += `id: ${message.id}\n`; + } + if (message.event) { + result += `event: ${message.event}\n`; + } + if (typeof message.retry === "number" && Number.isInteger(message.retry)) { + result += `retry: ${message.retry}\n`; + } + result += `data: ${message.data}\n\n`; + return result; +} + +export function formatEventStreamMessages( + messages: EventStreamMessage[], +): string { + let result = ""; + for (const msg of messages) { + result += formatEventStreamMessage(msg); + } + return result; +} + +export function setEventStreamHeaders(event: H3Event) { + const headers: Partial< + Record + > = { + "Content-Type": "text/event-stream", + "Cache-Control": + "private, no-cache, no-store, no-transform, must-revalidate, max-age=0", + "X-Accel-Buffering": "no", // prevent nginx from buffering the response + }; + + if (!isHttp2Request(event)) { + headers.Connection = "keep-alive"; + } + + setResponseHeaders(event, headers); +} + +export function isHttp2Request(event: H3Event) { + return ( + getHeader(event, ":path") !== undefined && + getHeader(event, ":method") !== undefined + ); +} diff --git a/src/utils/static.ts b/src/utils/static.ts index 949e5605..aec9c7b6 100644 --- a/src/utils/static.ts +++ b/src/utils/static.ts @@ -60,6 +60,9 @@ export interface ServeStaticOptions { fallthrough?: boolean; } +/** + * Dynamically serve static assets based on the request path. + */ export async function serveStatic( event: H3Event, options: ServeStaticOptions, @@ -115,6 +118,10 @@ export async function serveStatic( return false; } + if (meta.etag && !getResponseHeader(event, "etag")) { + setResponseHeader(event, "etag", meta.etag); + } + const ifNotMatch = meta.etag && getRequestHeader(event, "if-none-match") === meta.etag; if (ifNotMatch) { @@ -140,10 +147,6 @@ export async function serveStatic( setResponseHeader(event, "content-type", meta.type); } - if (meta.etag && !getResponseHeader(event, "etag")) { - setResponseHeader(event, "etag", meta.etag); - } - if (meta.encoding && !getResponseHeader(event, "content-encoding")) { setResponseHeader(event, "content-encoding", meta.encoding); } diff --git a/src/utils/ws.ts b/src/utils/ws.ts new file mode 100644 index 00000000..74b51958 --- /dev/null +++ b/src/utils/ws.ts @@ -0,0 +1,30 @@ +import type { Hooks as WSHooks } from "crossws"; + +import { createError } from "../error"; +import { defineEventHandler } from "../event"; + +/** + * Define WebSocket hooks. + * + * @see https://h3.unjs.io/guide/websocket + */ +export function defineWebSocket(hooks: Partial): Partial { + return hooks; +} + +/** + * Define WebSocket event handler. + * + * @see https://h3.unjs.io/guide/websocket + */ +export function defineWebSocketHandler(hooks: Partial) { + return defineEventHandler({ + handler() { + throw createError({ + statusCode: 426, + statusMessage: "Upgrade Required", + }); + }, + websocket: hooks, + }); +} diff --git a/test/iteratable.test.ts b/test/iteratable.test.ts new file mode 100644 index 00000000..43bc4077 --- /dev/null +++ b/test/iteratable.test.ts @@ -0,0 +1,153 @@ +import { ReadableStream } from "node:stream/web"; +import supertest, { SuperTest, Test } from "supertest"; +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { + createApp, + App, + toNodeListener, + eventHandler, + sendIterable, +} from "../src"; +import { serializeIterableValue } from "../src/utils/internal/iteratable"; + +describe("iteratable", () => { + let app: App; + let request: SuperTest; + + beforeEach(() => { + app = createApp({ debug: false }); + request = supertest(toNodeListener(app)); + }); + + describe("serializeIterableValue", () => { + const exampleDate: Date = new Date(Date.UTC(2015, 6, 21, 3, 24, 54, 888)); + it.each([ + { value: "Hello, world!", output: "Hello, world!" }, + { value: 123, output: "123" }, + { value: 1n, output: "1" }, + { value: true, output: "true" }, + { value: false, output: "false" }, + { value: undefined, output: undefined }, + { value: null, output: "null" }, + { value: exampleDate, output: JSON.stringify(exampleDate) }, + { value: { field: 1 }, output: '{"field":1}' }, + { value: [1, 2, 3], output: "[1,2,3]" }, + { value: () => {}, output: undefined }, + { + value: Buffer.from("Hello, world!"), + output: Buffer.from("Hello, world!"), + }, + { value: Uint8Array.from([1, 2, 3]), output: Uint8Array.from([1, 2, 3]) }, + ])("$value => $output", ({ value, output }) => { + const serialized = serializeIterableValue(value); + expect(serialized).toStrictEqual(output); + }); + }); + + describe("sendIterable", () => { + it("sends empty body for an empty iterator", async () => { + app.use(eventHandler((event) => sendIterable(event, []))); + const result = await request.get("/"); + expect(result.header["content-length"]).toBe("0"); + expect(result.text).toBe(""); + }); + + it("concatenates iterated values", async () => { + app.use(eventHandler((event) => sendIterable(event, ["a", "b", "c"]))); + const result = await request.get("/"); + expect(result.text).toBe("abc"); + }); + + describe("iterable support", () => { + it.each([ + { type: "Array", iterable: ["the-value"] }, + { type: "Set", iterable: new Set(["the-value"]) }, + { + type: "Map.keys()", + iterable: new Map([["the-value", "unused"]]).keys(), + }, + { + type: "Map.values()", + iterable: new Map([["unused", "the-value"]]).values(), + }, + { + type: "Iterator object", + iterable: { next: () => ({ value: "the-value", done: true }) }, + }, + { + type: "AsyncIterator object", + iterable: { + next: () => Promise.resolve({ value: "the-value", done: true }), + }, + }, + { + type: "Generator (yield)", + iterable: (function* () { + yield "the-value"; + })(), + }, + { + type: "Generator (return)", + iterable: (function* () { + return "the-value"; + })(), + }, + { + type: "Generator (yield*)", + iterable: (function* () { + // prettier-ignore + yield * ["the-value"]; + })(), + }, + { + type: "AsyncGenerator", + iterable: (async function* () { + await Promise.resolve(); + yield "the-value"; + })(), + }, + { + type: "ReadableStream (push-mode)", + iterable: new ReadableStream({ + start(controller) { + controller.enqueue("the-value"); + controller.close(); + }, + }), + }, + { + type: "ReadableStream (pull-mode)", + iterable: new ReadableStream({ + pull(controller) { + controller.enqueue("the-value"); + controller.close(); + }, + }), + }, + ])("$type", async ({ iterable }) => { + app.use(eventHandler((event) => sendIterable(event, iterable))); + const response = await request.get("/"); + expect(response.text).toBe("the-value"); + }); + }); + + describe("serializer argument", () => { + it("is called for every value", async () => { + const iterable = [1, "2", { field: 3 }, null]; + const serializer = vi.fn(() => "x"); + + app.use( + eventHandler((event) => + sendIterable(event, iterable, { serializer }), + ), + ); + const response = await request.get("/"); + expect(response.text).toBe("x".repeat(iterable.length)); + expect(serializer).toBeCalledTimes(4); + for (const [i, obj] of iterable.entries()) { + expect.soft(serializer).toHaveBeenNthCalledWith(i + 1, obj); + } + }); + }); + }); +}); diff --git a/test/resolve.test.ts b/test/resolve.test.ts new file mode 100644 index 00000000..50e4a4d8 --- /dev/null +++ b/test/resolve.test.ts @@ -0,0 +1,108 @@ +import { describe, it, expect } from "vitest"; +import { + createApp, + createRouter, + eventHandler, + lazyEventHandler, +} from "../src"; + +describe("Event handler resolver", () => { + const testHandlers = Array.from({ length: 10 }).map((_, i) => + eventHandler(() => i), + ); + + const app = createApp(); + + const router = createRouter(); + app.use(router); + + // Middlware + app.use(testHandlers[0]); + app.use("/", testHandlers[1]); + + // Path prefix + app.use("/test", testHandlers[2]); + app.use("/lazy", () => testHandlers[3], { lazy: true }); + + // Sub app + const app2 = createApp(); + app.use("/nested", app2 as any); + app2.use("/path", testHandlers[4]); + // app2.use("/lazy", () => testHandlers[5], { lazy: true }); + + // Router + router.get("/router", testHandlers[6]); + router.get("/router/:id", testHandlers[7]); + router.get( + "/router/lazy", + lazyEventHandler(() => testHandlers[8]), + ); + + describe("middleware", () => { + it("does not resolves /", async () => { + expect(await app.resolve("/")).toBeUndefined(); + }); + }); + + describe("path prefix", () => { + it("resolves /test", async () => { + expect(await app.resolve("/test")).toMatchObject({ + route: "/test", + handler: testHandlers[2], + }); + }); + + it("resolves /test/foo", async () => { + expect((await app.resolve("/test/foo"))?.route).toEqual("/test"); + }); + }); + + it("resolves /lazy", async () => { + expect(await app.resolve("/lazy")).toMatchObject({ + route: "/lazy", + handler: testHandlers[3], + }); + }); + + describe("nested app", () => { + it("resolves /nested/path/foo", async () => { + expect(await app.resolve("/nested/path/foo")).toMatchObject({ + route: "/nested/path", + handler: testHandlers[4], + }); + }); + + it.skip("resolves /nested/lazy", async () => { + expect(await app.resolve("/nested/lazy")).toMatchObject({ + route: "/nested/lazy", + handler: testHandlers[5], + }); + }); + }); + + describe("router", () => { + it("resolves /router", async () => { + expect(await app.resolve("/router")).toMatchObject({ + route: "/router", + handler: testHandlers[6], + }); + expect(await app.resolve("/router/")).toMatchObject( + (await app.resolve("/router")) as any, + ); + }); + + it("resolves /router/:id", async () => { + expect(await app.resolve("/router/foo")).toMatchObject({ + route: "/router/:id", + handler: testHandlers[7], + }); + }); + + it("resolves /router/lazy", async () => { + expect(await app.resolve("/router/lazy")).toMatchObject({ + route: "/router/lazy", + handler: testHandlers[8], + }); + }); + }); +}); diff --git a/test/sse.test.ts b/test/sse.test.ts new file mode 100644 index 00000000..e18db2e6 --- /dev/null +++ b/test/sse.test.ts @@ -0,0 +1,125 @@ +import supertest, { SuperTest, Test } from "supertest"; +import { describe, it, beforeEach, expect } from "vitest"; +import { + App, + createApp, + createEventStream, + eventHandler, + getQuery, + toNodeListener, +} from "../src"; +import { + formatEventStreamMessage, + formatEventStreamMessages, +} from "../src/utils/sse/utils"; + +describe("Server Sent Events (SSE)", () => { + let app: App; + let request: SuperTest; + beforeEach(() => { + app = createApp({ debug: true }); + app.use( + "/sse", + eventHandler((event) => { + const includeMeta = getQuery(event).includeMeta !== undefined; + const eventStream = createEventStream(event); + const interval = setInterval(() => { + if (includeMeta) { + eventStream.push({ + id: "1", + event: "custom-event", + data: "hello world", + }); + return; + } + eventStream.push("hello world"); + }); + eventStream.onClosed(async () => { + await eventStream.close(); + clearInterval(interval); + }); + return eventStream.send(); + }), + ); + request = supertest(toNodeListener(app)) as any; + }); + it("streams events", async () => { + let messageCount = 0; + request + .get("/sse") + .expect(200) + .expect("Content-Type", "text/event-stream") + .buffer() + .parse((res, callback) => { + res.on("data", (chunk: Buffer) => { + messageCount++; + const message = chunk.toString(); + expect(message).toEqual("data: hello world\n\n"); + }); + res.on("end", () => { + callback(null, ""); + }); + }) + .then() + .catch(); + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 100); + }); + expect(messageCount > 10).toBe(true); + }); + it("streams events with metadata", async () => { + let messageCount = 0; + request + .get("/sse?includeMeta=true") + .expect(200) + .expect("Content-Type", "text/event-stream") + .buffer() + .parse((res, callback) => { + res.on("data", (chunk: Buffer) => { + messageCount++; + const message = chunk.toString(); + expect(message).toEqual( + `id: 1\nevent: custom-event\ndata: hello world\n\n`, + ); + }); + res.on("end", () => { + callback(null, ""); + }); + }) + .then() + .catch(); + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 100); + }); + expect(messageCount > 10).toBe(true); + }); +}); + +it("properly formats sse messages", () => { + const result = formatEventStreamMessage({ data: "hello world" }); + expect(result).toEqual(`data: hello world\n\n`); + const result2 = formatEventStreamMessage({ + id: "1", + event: "custom-event", + retry: 10, + data: "hello world", + }); + expect(result2).toEqual( + `id: 1\nevent: custom-event\nretry: 10\ndata: hello world\n\n`, + ); +}); + +it("properly formats multiple sse messages", () => { + const result = formatEventStreamMessages([ + { + data: "hello world", + }, + + { id: "1", data: "hello world 2" }, + ]); + expect(result).toEqual(`data: hello world\n\nid: 1\ndata: hello world 2\n\n`); +}); diff --git a/test/static.test.ts b/test/static.test.ts index 7446e20e..e47f66f3 100644 --- a/test/static.test.ts +++ b/test/static.test.ts @@ -83,6 +83,7 @@ describe("Serve Static", () => { it("Handles cache (if-none-match)", async () => { const res = await request.get("/test.png").set("if-none-match", "w/123"); + expect(res.headers.etag).toBe(expectedHeaders.etag); expect(res.status).toEqual(304); expect(res.text).toBe(""); }); diff --git a/test/status.test.ts b/test/status.test.ts index c95d50a5..ca29feba 100644 --- a/test/status.test.ts +++ b/test/status.test.ts @@ -6,7 +6,6 @@ import { PlainHandler, eventHandler, setResponseStatus, - send, } from "../src"; describe("setResponseStatus", () => { @@ -127,7 +126,7 @@ describe("setResponseStatus", () => { body: "", }); - console.log(res.headers); + // console.log(res.headers); expect(res).toMatchObject({ status: 304, diff --git a/test/utils.test.ts b/test/utils.test.ts index 5eb61402..e4511efa 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,6 @@ +import { ReadableStream } from "node:stream/web"; import supertest, { SuperTest, Test } from "supertest"; -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, beforeEach, vi } from "vitest"; import { createApp, App, @@ -13,7 +14,9 @@ import { readFormData, getRequestIP, getRequestFingerprint, + sendIterable, } from "../src"; +import { serializeIterableValue } from "../src/utils/internal/iteratable"; describe("", () => { let app: App; @@ -36,6 +39,138 @@ describe("", () => { }); }); + describe("serializeIterableValue", () => { + const exampleDate: Date = new Date(Date.UTC(2015, 6, 21, 3, 24, 54, 888)); + it.each([ + { value: "Hello, world!", output: "Hello, world!" }, + { value: 123, output: "123" }, + { value: 1n, output: "1" }, + { value: true, output: "true" }, + { value: false, output: "false" }, + { value: undefined, output: undefined }, + { value: null, output: "null" }, + { value: exampleDate, output: JSON.stringify(exampleDate) }, + { value: { field: 1 }, output: '{"field":1}' }, + { value: [1, 2, 3], output: "[1,2,3]" }, + { value: () => {}, output: undefined }, + { + value: Buffer.from("Hello, world!"), + output: Buffer.from("Hello, world!"), + }, + { value: Uint8Array.from([1, 2, 3]), output: Uint8Array.from([1, 2, 3]) }, + ])("$value => $output", ({ value, output }) => { + const serialized = serializeIterableValue(value); + expect(serialized).toStrictEqual(output); + }); + }); + + describe("sendIterable", () => { + it("sends empty body for an empty iterator", async () => { + app.use(eventHandler((event) => sendIterable(event, []))); + const result = await request.get("/"); + expect(result.header["content-length"]).toBe("0"); + expect(result.text).toBe(""); + }); + + it("concatenates iterated values", async () => { + app.use(eventHandler((event) => sendIterable(event, ["a", "b", "c"]))); + const result = await request.get("/"); + expect(result.text).toBe("abc"); + }); + + describe("iterable support", () => { + it.each([ + { type: "Array", iterable: ["the-value"] }, + { type: "Set", iterable: new Set(["the-value"]) }, + { + type: "Map.keys()", + iterable: new Map([["the-value", "unused"]]).keys(), + }, + { + type: "Map.values()", + iterable: new Map([["unused", "the-value"]]).values(), + }, + { + type: "Iterator object", + iterable: { next: () => ({ value: "the-value", done: true }) }, + }, + { + type: "AsyncIterator object", + iterable: { + next: () => Promise.resolve({ value: "the-value", done: true }), + }, + }, + { + type: "Generator (yield)", + iterable: (function* () { + yield "the-value"; + })(), + }, + { + type: "Generator (return)", + iterable: (function* () { + return "the-value"; + })(), + }, + { + type: "Generator (yield*)", + iterable: (function* () { + // prettier-ignore + yield * ["the-value"]; + })(), + }, + { + type: "AsyncGenerator", + iterable: (async function* () { + await Promise.resolve(); + yield "the-value"; + })(), + }, + { + type: "ReadableStream (push-mode)", + iterable: new ReadableStream({ + start(controller) { + controller.enqueue("the-value"); + controller.close(); + }, + }), + }, + { + type: "ReadableStream (pull-mode)", + iterable: new ReadableStream({ + pull(controller) { + controller.enqueue("the-value"); + controller.close(); + }, + }), + }, + ])("$type", async ({ iterable }) => { + app.use(eventHandler((event) => sendIterable(event, iterable))); + const response = await request.get("/"); + expect(response.text).toBe("the-value"); + }); + }); + + describe("serializer argument", () => { + it("is called for every value", async () => { + const iterable = [1, "2", { field: 3 }, null]; + const serializer = vi.fn(() => "x"); + + app.use( + eventHandler((event) => + sendIterable(event, iterable, { serializer }), + ), + ); + const response = await request.get("/"); + expect(response.text).toBe("x".repeat(iterable.length)); + expect(serializer).toBeCalledTimes(4); + for (const [i, obj] of iterable.entries()) { + expect.soft(serializer).toHaveBeenNthCalledWith(i + 1, obj); + } + }); + }); + }); + describe("useBase", () => { it("can prefix routes", async () => { app.use(