diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 46c36d33e..1029c106f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,17 +16,17 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- - name: Setup Node.js
+ - name: Set up Node.js
uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 18
- uses: pnpm/action-setup@v2
with:
version: 7.12
- name: Install dependencies
- run: pnpm install --frozen-lockfile
+ run: pnpm install
- name: Unit tests
run: pnpm test:unit
@@ -46,41 +46,3 @@ jobs:
with:
name: playwright-report
path: test/browser/test-results
-
- # Checks the library's compatibility with different
- # TypeScript versions to discover type regressions.
- typescript:
- runs-on: macos-latest
- # Skip TypeScript compatibility check on "main".
- # A merged pull request implies passing "typescript" job.
- if: github.ref != 'refs/heads/main'
- strategy:
- fail-fast: false
- matrix:
- ts: ['4.4', '4.5', '4.6', '4.7', '4.8', '4.9', '5.0', '5.1', '5.2']
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Setup Node.js
- uses: actions/setup-node@v3
- with:
- node-version: 16
-
- - uses: pnpm/action-setup@v2
- with:
- version: 7.12
-
- - name: Install dependencies
- run: pnpm install
-
- - name: Install TypeScript ${{ matrix.ts }}
- run: pnpm add typescript@${{ matrix.ts }}
-
- - name: Build
- run: pnpm build
-
- - name: Typings tests
- run: |
- pnpm tsc --version
- pnpm test:ts
diff --git a/.github/workflows/compat.yml b/.github/workflows/compat.yml
new file mode 100644
index 000000000..2cc378ed1
--- /dev/null
+++ b/.github/workflows/compat.yml
@@ -0,0 +1,78 @@
+name: compat
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ # Validate the package.json exports and emitted CJS/ESM bundles.
+ exports:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - name: Set up pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: 7.12
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Build
+ run: pnpm build
+
+ - name: Validate package.json exports
+ run: pnpm check:exports
+
+ - name: Test modules (Node.js)
+ run: pnpm test:modules:node
+
+ - name: Test modules (browser)
+ run: pnpm test:modules:browser
+
+ # Checks the library's compatibility with different
+ # TypeScript versions to discover type regressions.
+ typescript:
+ runs-on: macos-latest
+ # Skip TypeScript compatibility check on "main".
+ # A merged pull request implies passing "typescript" job.
+ if: github.ref != 'refs/heads/main'
+ strategy:
+ fail-fast: false
+ matrix:
+ ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2']
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 7.12
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Install TypeScript ${{ matrix.ts }}
+ run: pnpm add typescript@${{ matrix.ts }}
+
+ - name: Build
+ run: pnpm build
+
+ - name: Typings tests
+ run: |
+ pnpm tsc --version
+ pnpm test:ts
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1b7796f38..540f3d59f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,7 +18,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 18
always-auth: true
registry-url: https://registry.npmjs.org
diff --git a/.nvmrc b/.nvmrc
index e2838c8b8..e8b25b544 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v16.14.0
\ No newline at end of file
+v18.14.2
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1df787cf6..1d38a6d00 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -148,19 +148,18 @@ Let's write an example integration test that asserts the interception of a GET r
```js
// test/browser/example.mocks.ts
-import { rest, setupWorker } from 'msw'
+import { http, HttpResponse } from 'msw'
+import { setupWorker } from 'msw/browser'
const worker = setupWorker(
- rest.get('/books', (req, res, ctx) => {
- return res(
- ctx.json([
- {
- id: 'ea42ffcb-e729-4dd5-bfac-7a5b645cb1da',
- title: 'The Lord of the Rings',
- publishedAt: -486867600,
- },
- ]),
- )
+ http.get('/books', () => {
+ return HttpResponse.json([
+ {
+ id: 'ea42ffcb-e729-4dd5-bfac-7a5b645cb1da',
+ title: 'The Lord of the Rings',
+ publishedAt: -486867600,
+ },
+ ])
}),
)
@@ -217,24 +216,23 @@ Let's replicate the same `GET /books` integration test in Node.js.
```ts
// test/node/example.test.ts
import fetch from 'node-fetch'
-import { rest } from 'msw'
+import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
- rest.get('/books', (req, res, ctx) => {
- return res(
- ctx.json([
- {
- id: 'ea42ffcb-e729-4dd5-bfac-7a5b645cb1da',
- title: 'The Lord of the Rings',
- publishedAt: -486867600,
- },
- ]),
- )
+ http.get('/books', () => {
+ return HttpResponse.json([
+ {
+ id: 'ea42ffcb-e729-4dd5-bfac-7a5b645cb1da',
+ title: 'The Lord of the Rings',
+ publishedAt: -486867600,
+ },
+ ])
}),
)
beforeAll(() => server.listen())
+
afterAll(() => server.close())
test('returns a mocked response', async () => {
diff --git a/MIGRATING.md b/MIGRATING.md
new file mode 100644
index 000000000..8ddc0e003
--- /dev/null
+++ b/MIGRATING.md
@@ -0,0 +1,659 @@
+# Migration guide
+
+This guide will help you migrate from the latest version of MSW to the `next` release that introduces a first-class support for Fetch API primitives to the library. **This is a breaking change**. In fact, this is the biggest change to our public API since the day the library was first published. Do not fret, however, as this is precisely why this document exists.
+
+## Getting started
+
+```sh
+npm install msw@next --save-dev
+```
+
+## Table of contents
+
+To help you navigate, we've structured this guide on the feature basis. You can read it top-to-bottom, or you can jump to a particular feature you have trouble migrating from.
+
+- [**Imports**](#imports)
+- [**Response resolver**](#response-resolver) (call signature change)
+- [Request changes](#request-changes)
+- [req.params](#reqparams)
+- [req.cookies](#request-cookies)
+- [req.passthrough](#reqpassthrough)
+- [res.once](#resonce)
+- [res.networkError](#resnetworkerror)
+- [Context utilities](#context-utilities)
+ - [ctx.status](#ctxstatus)
+ - [ctx.set](#ctxset)
+ - [ctx.cookie](#ctxcookie)
+ - [ctx.body](#ctxbody)
+ - [ctx.text](#ctxtext)
+ - [ctx.json](#ctxjson)
+ - [ctx.xml](#ctxxml)
+ - [ctx.data](#ctxdata)
+ - [ctx.errors](#ctxerrors)
+ - [ctx.delay](#ctxdelay)
+ - [ctx.fetch](#ctx-fetch)
+- [Life-cycle events](#life-cycle-events)
+- [`.printHandlers()`](#print-handlers)
+- [Advanced](#advanced)
+- [**What's new in this release?**](#whats-new)
+- [Common issues](#common-issues)
+
+---
+
+## Imports
+
+### `rest` becomes `http`
+
+The `rest` request handler namespace has been renamed to `http`.
+
+```diff
+-import { rest } from 'msw'
++import { http } from 'msw'
+```
+
+This affects the request handlers declaration as well:
+
+```js
+import { http } from 'msw'
+
+export const handlers = [
+ http.get('/resource', resolver),
+ http.post('/resource', resolver),
+ http.all('*', resolver),
+]
+```
+
+### Browser imports
+
+The `setupWorker` API, alongside any related type definitions, are no longer exported from the root of `msw`. Instead, import them from `msw/browser`:
+
+```diff
+-import { setupWorker } from 'msw'
++import { setupWorker } from 'msw/browser'
+```
+
+> Note that the request handlers like `http` and `graphql`, as well as the utility functions like `bypass` and `passthrough` must still be imported from the root-level `msw`.
+
+## Response resolver
+
+A response resolver now exposes a single object argument instead of `(req, res, ctx)`. That argument represents resolver information and consists of properties that are always present for all handler types and extra properties specific to handler types.
+
+### Resolver info
+
+#### General
+
+- `request`, a Fetch API `Request` instance representing an intercepted request.
+- `cookies`, a parsed cookies object based on the request cookies.
+
+#### REST-specific
+
+- `params`, an object of parsed path parameters.
+
+#### GraphQL-specific
+
+- `query`, a GraphQL query string extracted from either URL search parameters or a POST request body.
+- `variables`, an object of GraphQL query variables.
+
+### Using a new signature
+
+To mock responses, you should now return a Fetch API `Response` instance from the response resolver. You no longer need to compose a response via `res()`, and all the context utilities have also [been removed](#context-utilities).
+
+```js
+http.get('/greet/:name', ({ request, params }) => {
+ console.log('Intercepted %s %s', request.method, request.url)
+ return new Response(`hello, ${params.name}!`)
+})
+```
+
+Now, a more complex example for both REST and GraphQL requests.
+
+```js
+import { http, graphql } from 'msw'
+
+export const handlers = [
+ http.put('/user/:id', async ({ request, params, cookies }) => {
+ // Read request body as you'd normally do with Fetch.
+ const payload = await request.json()
+ // Access path parameters like before.
+ const { id } = params
+ // Access cookies like before.
+ const { sessionId } = cookies
+
+ return new Response(null, { status: 201 })
+ }),
+
+ graphql.mutation('CreateUser', ({ request, query, variables }) => {
+ return new Response(
+ JSON.stringify({
+ data: {
+ user: {
+ id: 'abc-123',
+ firstName: variables.firstName,
+ },
+ },
+ }),
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ },
+ )
+ }),
+]
+```
+
+### Request changes
+
+Since the returned `request` is now an instance of Fetch API `Request`, there are some changes to its properties.
+
+#### Request URL
+
+The `request.url` property is a string (previously, a `URL` instance). If you wish to operate with it like a `URL`, you need to construct it manually:
+
+```js
+http.get('/product', ({ request }) => {
+ // For example, this is how you would access
+ // request search parameters now.
+ const url = new URL(request.url)
+ const productId = url.searchParams.get('id')
+})
+```
+
+#### `req.params`
+
+Path parameters are now exposed directly on the [Resolver info](#resolver-info) object (previously, `req.params`).
+
+```js
+http.get('/resource', ({ params }) => {
+ console.log('Request path parameters:', params)
+})
+```
+
+#### `req.cookies`
+
+Request cookies are now exposed directly on the [Resolver info](#resolver-info) object (previously, `req.cookies`).
+
+```js
+http.get('/resource', ({ cookies }) => {
+ console.log('Request cookies:', cookies)
+})
+```
+
+#### Request body
+
+The library now does no assumptions when reading the intercepted request's body (previously, `req.body`). Instead, you are in charge to read the request body as you see appropriate.
+
+> Note that since the intercepted request is now represented by a Fetch API `Request` instance, its `request.body` property still exists but returns a `ReadableStream`.
+
+For example, this is how you would read request body:
+
+```js
+http.post('/resource', async ({ request }) => {
+ const data = await request.json()
+ // request.formData() / request.arrayBuffer() / etc.
+})
+```
+
+### Convenient response declarations
+
+Using the Fetch API `Response` instance may get quite verbose. To give you more convenient means of declaring mocked responses while remaining specification compliant and compatible, the library now exports an `HttpResponse` object. You can use that object to construct response instances faster.
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/user', () => {
+ // This is synonymous to "ctx.json()":
+ // HttpResponse.json() stringifies the given body
+ // and sets the correct "Content-Type" response header
+ // to describe a JSON response body.
+ return HttpResponse.json({ firstName: 'John' })
+ }),
+]
+```
+
+> Read more on how to use `HttpResponse` to mock [REST API](#rest-response-body-utilities) and [GraphQL API](#graphql-response-body-utilities) responses.
+
+## Responses in Node.js
+
+Although MSW now respects the Fetch API specification, the older versions of Node.js do not, so you can't construct a `Response` instance because there is no such global class.
+
+To account for this, the library exports a `Response` class that you should use when declaring request handlers. Behind the hood, that response class is resolved to a compatible polyfill in Node.js; in the browser, it only aliases `global.Response` without introducing additional behaviors.
+
+```js
+import { http,Response } from 'msw'
+
+setupServer(
+ http.get('/ping', () => {
+ return new Response('hello world)
+ })
+)
+```
+
+Relying on a single universal `Response` class will allow you to write request handlers that can run in both browser and Node.js environments.
+
+## `res.once`
+
+To create a one-time request handler, pass it an object as the third argument with `once: true` set:
+
+```js
+import { HttpResponse, http } from 'msw'
+
+export const handlers = [
+ http.get(
+ '/user',
+ () => {
+ return HttpResponse.text('hello')
+ },
+ { once: true },
+ ),
+]
+```
+
+## `res.networkError`
+
+To respond to a request with a network error, use the `HttpResponse.error()` static method:
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.error()
+ }),
+]
+```
+
+> Note that we are dropping support for custom network error messages to be more compliant with the standard [`Response.error()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/error_static) network errors, which don't support custom error messages.
+
+## `req.passthrough`
+
+```js
+import { http, passthrough } from 'msw'
+
+export const handlers = [
+ http.get('/user', () => {
+ // Previously, "req.passthrough()".
+ return passthrough()
+ }),
+]
+```
+
+---
+
+## Context utilities
+
+Most of the context utilities you'd normally use via `ctx.*` were removed. Instead, we encourage you to set respective properties directly on the response instance:
+
+```js
+import { HttpResponse, http } from 'msw'
+
+export const handlers = [
+ http.post('/user', () => {
+ // ctx.json()
+ return HttpResponse.json(
+ { firstName: 'John' },
+ {
+ status: 201, // ctx.status()
+ headers: {
+ 'X-Custom-Header': 'value', // ctx.set()
+ },
+ },
+ )
+ }),
+]
+```
+
+Let's go through each previously existing context utility and see how to declare its analogue using the `Response` class.
+
+### `ctx.status`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.text('hello', { status: 201 })
+ }),
+]
+```
+
+### `ctx.set`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.text('hello', {
+ headers: {
+ 'Content-Type': 'text/plain; charset=windows-1252',
+ },
+ })
+ }),
+]
+```
+
+### `ctx.cookie`
+
+```js
+import { HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.text('hello', {
+ headers: {
+ 'Set-Cookie': 'token=abc-123',
+ },
+ })
+ }),
+]
+```
+
+When you provide an object as the `ResponseInit.headers` value, you cannot specify multiple response cookies with the same name. Instead, to support multiple response cookies, provide a `Headers` instance:
+
+```js
+import { HttpResponse, http } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return new HttpResponse(null, {
+ headers: new Headers([
+ // Mock a multi-value response cookie header.
+ ['Set-Cookie', 'sessionId=123'],
+ ['Set-Cookie', 'gtm=en_US'],
+ ]),
+ })
+ }),
+]
+```
+
+> This is applicable to any multi-value headers, really.
+
+### `ctx.body`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return new HttpResponse('any-body')
+ }),
+]
+```
+
+> Do not forget to set the `Content-Type` header that represents the mocked response's body type. If using common response body types, like text or json, see the respective migration instructions for those context utilities below.
+
+### `ctx.text`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.text('hello')
+ }),
+]
+```
+
+### `ctx.json`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.json({ firstName: 'John' })
+ }),
+]
+```
+
+### `ctx.xml`
+
+```js
+import { http, HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.xml('')
+ }),
+]
+```
+
+### `ctx.data`
+
+The `ctx.data` utility has been removed in favor of constructing a mocked JSON response with the "data" property in it.
+
+```js
+import { HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.json({
+ data: {
+ user: {
+ firstName: 'John',
+ },
+ },
+ })
+ }),
+]
+```
+
+### `ctx.errors`
+
+The `ctx.errors` utility has been removed in favor of constructing a mocked JSON response with the "errors" property in it.
+
+```js
+import { HttpResponse } from 'msw'
+
+export const handlers = [
+ http.get('/resource', () => {
+ return HttpResponse.json({
+ errors: [
+ {
+ message: 'Something went wrong',
+ },
+ ],
+ })
+ }),
+]
+```
+
+### `ctx.delay`
+
+```js
+import { http, HttpResponse, delay } from 'msw'
+
+export const handlers = [
+ http.get('/resource', async () => {
+ await delay()
+ return HttpResponse.text('hello')
+ }),
+]
+```
+
+The `delay` function has the same call signature as the `ctx.delay` context function. This means it supports the delay mode as an argument:
+
+```js
+await delay(500)
+await delay('infinite')
+```
+
+### `ctx.fetch`
+
+The `ctx.fetch()` function has been removed in favor of the `bypass()` function. You should now always perform a regular `fetch()` call and wrap the request in the `bypass()` function if you wish for it to ignore any otherwise matching request handlers.
+
+```js
+import { http, HttpResponse, bypass } from 'msw'
+
+export const handlers = [
+ http.get('/resource', async ({ request }) => {
+ // Use the regular "fetch" from your environment.
+ const originalResponse = await fetch(bypass(request))
+ const json = await originalResponse.json()
+
+ // ...handle the original response, maybe return a mocked one.
+ }),
+]
+```
+
+The `bypass()` function also accepts `RequestInit` as the second argument to modify the bypassed request.
+
+```js
+// Bypass the given "request" and modify its headers.
+bypass(request, {
+ headers: {
+ 'X-Modified-Header': 'true',
+ },
+})
+```
+
+---
+
+## Life-cycle events
+
+The life-cycle events listeners now accept a single argument being an object with contextual properties.
+
+```diff
+-server.events.on('request:start', (request, requestId) = {})
++server.events.on('request:start', ({ request, requestId}) => {})
+```
+
+The request and response instances exposed in the life-cycle API have also been updated to return Fetch API `Request` and `Response` respectively.
+
+The request ID is now exposed as a standalone argument (previously, `req.id`).
+
+```js
+server.events.on('request:start', ({ request, requestId }) => {
+ console.log(request.method, request.url)
+})
+```
+
+To read a request body, make sure to clone the request first. Otherwise, it won't be performed as it would be already read.
+
+```js
+server.events.on('request:match', async ({ request }) => {
+ // Make sure to clone the request so it could be
+ // processed further down the line.
+ const clone = request.clone()
+ const json = await clone.json()
+
+ console.log('Performed request with body:', json)
+})
+```
+
+The `response:*` events now always contain the response reference, the related request, and its id in the listener arguments.
+
+```js
+worker.events.on('response:mocked', ({ response, request, requestId }) => {
+ console.log('response to %s %s is:', request.method, request.url, response)
+})
+```
+
+---
+
+## `.printHandlers()
+
+The `worker.prinHandlers()` and `server.printHandlers()` methods were removed. Use the `.listHandlers()` method instead:
+
+```diff
+-worker.printHandlers()
++console.log(worker.listHandlers())
+```
+
+---
+
+## Advanced
+
+It is still possible to create custom handlers and resolvers, just make sure to account for the new [resolver call signature](#response-resolver).
+
+### Custom response composition
+
+As this release removes the concept of response composition via `res()`, you can no longer compose context utilities or abstract their partial composed state to a helper function.
+
+Instead, you can abstract a common response logic into a plain function that creates a new `Response` or modifies a provided instance.
+
+```js
+// utils.js
+import { HttpResponse } from 'msw'
+
+export function augmentResponse(json) {
+ const response = HttpResponse.json(json, {
+ // Come up with some reusable defaults here.
+ })
+ return response
+}
+```
+
+```js
+import { http } from 'msw'
+import { augmentResponse } from './utils'
+
+export const handlers = [
+ http.get('/user', () => {
+ return augmentResponse({ id: 1 })
+ }),
+]
+```
+
+---
+
+## What's new?
+
+The main benefit of this release is the adoption of Fetch API primitives—`Request` and `Response` classes. By handling requests and responses as the platform does it, you bring your API mocking setup to the next level. Less library-specific abstractions, flatter learning curve, improved compatibility with other tools. But, most importantly, specification compliance and investment into a solution that uses standard APIs that are here to stay.
+
+### New request body methods
+
+You can now read the intercepted request body as you would a regular `Request` instance. This mainly means the addition of the following methods on the `request`:
+
+- `request.blob()`
+- `request.formData()`
+- `request.arrayBuffer()`
+
+For example, this is how you would read the request as `Blob`:
+
+```js
+import { http } from 'msw'
+
+export const handlers = [
+ http.get('/resource', async ({ request }) => {
+ const blob = await request.blob()
+ }),
+]
+```
+
+### Support `ReadableStream` mocked responses
+
+You can now send a `ReadableStream` as the mocked response body. This is great for mocking any kind of streaming in HTTP responses.
+
+```js
+import { http, HttpResponse, delay } from 'msw'
+
+http.get('/greeting', () => {
+ const encoder = new TextEncoder()
+ const stream = new ReadableStream({
+ async start(controller) {
+ controller.enqueue(encoder.encode('hello'))
+ await delay(100)
+ controller.enqueue(encoder.encode('world'))
+ await delay(100)
+ controller.close()
+ },
+ })
+
+ return new HttpResponse(stream)
+})
+```
+
+---
+
+## Common issues
+
+### `Response is not defined`
+
+This likely means that you are running an old version of Node.js. Please use Node.js v18.14.0 and higher with this version of MSW. Also, see [this](#responses-in-nodejs).
+
+### `multipart/form-data is not supported` in Node.js
+
+Earlier versions of Node.js 18, like v18.8.0, had no support for `request.formData()`. Please upgrade to the latest Node.js version where Undici have added the said support to resolve the issue.
diff --git a/README.md b/README.md
index 225664989..38edf3573 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
+> [!IMPORTANT]\
+> **MSW 2.0 is finally here! 🎉** Read the [Release notes](https://github.com/mswjs/msw/releases/tag/v2.0.0) and please follow the [**Migration guidelines**](https://mswjs.io/docs/migrations/1.x-to-2.x) to upgrade. If you're having any questions while upgrading, please reach out in our [Discord server](https://kettanaito.com/discord).
+
@@ -25,7 +28,7 @@
- **Seamless**. A dedicated layer of requests interception at your disposal. Keep your application's code and tests unaware of whether something is mocked or not.
- **Deviation-free**. Request the same production resources and test the actual behavior of your app. Augment an existing API, or design it as you go when there is none.
-- **Familiar & Powerful**. Use [Express](https://github.com/expressjs/express)-like routing syntax to capture requests. Use parameters, wildcards, and regular expressions to match requests, and respond with necessary status codes, headers, cookies, delays, or completely custom resolvers.
+- **Familiar & Powerful**. Use [Express](https://github.com/expressjs/express)-like routing syntax to intercept requests. Use parameters, wildcards, and regular expressions to match requests, and respond with necessary status codes, headers, cookies, delays, or completely custom resolvers.
---
@@ -38,8 +41,7 @@
This README will give you a brief overview on the library but there's no better place to start with Mock Service Worker than its official documentation.
- [Documentation](https://mswjs.io/docs)
-- [**Getting started**](https://mswjs.io/docs/getting-started/install)
-- [Recipes](https://mswjs.io/docs/recipes)
+- [**Getting started**](https://mswjs.io/docs/getting-started)
- [FAQ](https://mswjs.io/docs/faq)
## Examples
@@ -48,12 +50,12 @@ This README will give you a brief overview on the library but there's no better
## Browser
-- [Learn more about using MSW in a browser](https://mswjs.io/docs/getting-started/integrate/browser)
+- [Learn more about using MSW in a browser](https://mswjs.io/docs/integrations/browser)
- [`setupWorker` API](https://mswjs.io/docs/api/setup-worker)
### How does it work?
-In-browser usage is what sets Mock Service Worker apart from other tools. Utilizing the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which can intercept requests for the purpose of caching, Mock Service Worker responds to captured requests with your mock definition on the network level. This way your application knows nothing about the mocking.
+In-browser usage is what sets Mock Service Worker apart from other tools. Utilizing the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which can intercept requests for the purpose of caching, Mock Service Worker responds to intercepted requests with your mock definition on the network level. This way your application knows nothing about the mocking.
**Take a look at this quick presentation on how Mock Service Worker functions in a browser:**
@@ -71,17 +73,20 @@ In-browser usage is what sets Mock Service Worker apart from other tools. Utiliz
```js
// src/mocks.js
// 1. Import the library.
-import { setupWorker, rest } from 'msw'
+import { http, HttpResponse } from 'msw'
+import { setupWorker } from 'msw/browser'
// 2. Describe network behavior with request handlers.
const worker = setupWorker(
- rest.get('https://github.com/octocat', (req, res, ctx) => {
- return res(
- ctx.delay(1500),
- ctx.status(202, 'Mocked status'),
- ctx.json({
- message: 'Mocked response JSON body',
- }),
+ http.get('https://github.com/octocat', ({ request, params, cookies }) => {
+ return HttpResponse.json(
+ {
+ message: 'Mocked response',
+ },
+ {
+ status: 202,
+ statusText: 'Mocked status',
+ },
)
}),
)
@@ -98,7 +103,7 @@ Performing a `GET https://github.com/octocat` request in your application will r
## Node.js
-- [Learn more about using MSW in Node.js](https://mswjs.io/docs/getting-started/integrate/node)
+- [Learn more about using MSW in Node.js](https://mswjs.io/docs/integrations/node)
- [`setupServer` API](https://mswjs.io/docs/api/setup-server)
### How does it work?
@@ -118,7 +123,7 @@ Take a look at the example of an integration test in Jest that uses [React Testi
// test/Dashboard.test.js
import React from 'react'
-import { rest } from 'msw'
+import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { render, screen, waitFor } from '@testing-library/react'
import Dashboard from '../src/components/Dashboard'
@@ -127,19 +132,17 @@ const server = setupServer(
// Describe network behavior with request handlers.
// Tip: move the handlers into their own module and
// import it across your browser and Node.js setups!
- rest.get('/posts', (req, res, ctx) => {
- return res(
- ctx.json([
- {
- id: 'f8dd058f-9006-4174-8d49-e3086bc39c21',
- title: `Avoid Nesting When You're Testing`,
- },
- {
- id: '8ac96078-6434-4959-80ed-cc834e7fef61',
- title: `How I Built A Modern Website In 2021`,
- },
- ]),
- )
+ http.get('/posts', ({ request, params, cookies }) => {
+ return HttpResponse.json([
+ {
+ id: 'f8dd058f-9006-4174-8d49-e3086bc39c21',
+ title: `Avoid Nesting When You're Testing`,
+ },
+ {
+ id: '8ac96078-6434-4959-80ed-cc834e7fef61',
+ title: `How I Built A Modern Website In 2021`,
+ },
+ ])
}),
)
@@ -174,7 +177,7 @@ it('displays the list of recent posts', async () => {
})
```
-> Don't get overwhelmed! We've prepared a step-by-step [**Getting started**](https://mswjs.io/docs/getting-started/install) tutorial that you can follow to learn how to integrate Mock Service Worker into your project.
+> Don't get overwhelmed! We've prepared a step-by-step [**Getting started**](https://mswjs.io/docs/getting-started) tutorial that you can follow to learn how to integrate Mock Service Worker into your project.
Despite the API being called `setupServer`, there are no actual servers involved! The name was chosen for familiarity, and the API was designed to resemble operating with an actual server.
diff --git a/browser/package.json b/browser/package.json
new file mode 100644
index 000000000..d29733135
--- /dev/null
+++ b/browser/package.json
@@ -0,0 +1,5 @@
+{
+ "main": "../lib/browser/index.js",
+ "module": "../lib/browser/index.mjs",
+ "types": "../lib/browser/index.d.ts"
+}
diff --git a/cli/init.js b/cli/init.js
index 16a0f312f..aed4f8600 100755
--- a/cli/init.js
+++ b/cli/init.js
@@ -21,14 +21,14 @@ module.exports = async function init(args) {
if (!dirExists) {
// Try to create the directory if it doesn't exist
- const [createDirectoryError] = await until(() =>
+ const createDirectoryResult = await until(() =>
fs.promises.mkdir(absolutePublicDir, { recursive: true }),
)
invariant(
- createDirectoryError == null,
+ createDirectoryResult.error == null,
'Failed to create a Service Worker at "%s": directory does not exist and could not be created.\nMake sure to include a relative path to the root directory of your server.\n\nSee the original error below:\n%s',
absolutePublicDir,
- createDirectoryError,
+ createDirectoryResult.error,
)
}
diff --git a/config/constants.js b/config/constants.js
index e38940945..ba32bc9af 100644
--- a/config/constants.js
+++ b/config/constants.js
@@ -2,8 +2,7 @@ const path = require('path')
const SERVICE_WORKER_SOURCE_PATH = path.resolve(
__dirname,
- '../',
- 'src/mockServiceWorker.js',
+ '../src/mockServiceWorker.js',
)
const SERVICE_WORKER_BUILD_PATH = path.resolve(
diff --git a/config/copyServiceWorker.ts b/config/copyServiceWorker.ts
index 264ed4da2..7058b36cb 100644
--- a/config/copyServiceWorker.ts
+++ b/config/copyServiceWorker.ts
@@ -1,10 +1,7 @@
import * as fs from 'fs'
import * as path from 'path'
-import chalk from 'chalk'
import { until } from '@open-draft/until'
-const { cyan } = chalk
-
/**
* Copies the given Service Worker source file into the destination.
* Injects the integrity checksum into the destination file.
@@ -16,11 +13,11 @@ export default async function copyServiceWorker(
): Promise {
console.log('Compiling Service Worker...')
- const [readError, fileContent] = await until(() =>
+ const readFileResult = await until(() =>
fs.promises.readFile(sourceFilePath, 'utf8'),
)
- if (readError) {
+ if (readFileResult.error) {
throw new Error('Failed to read file.\n${readError.message}')
}
@@ -36,17 +33,17 @@ export default async function copyServiceWorker(
fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8'),
)
- const nextFileContent = fileContent
+ const nextFileContent = readFileResult.data
.replace('', checksum)
.replace('', packageJson.version)
- const [writeFileError] = await until(() =>
+ const writeFileResult = await until(() =>
fs.promises.writeFile(destFilePath, nextFileContent),
)
- if (writeFileError) {
- throw new Error(`Failed to write file.\n${writeFileError.message}`)
+ if (writeFileResult.error) {
+ throw new Error(`Failed to write file.\n${writeFileResult.error.message}`)
}
- console.log('Service Worker copied to: %s', cyan(destFilePath))
+ console.log('Service Worker copied to: %s', destFilePath)
}
diff --git a/config/plugins/esbuild/copyWorkerPlugin.ts b/config/plugins/esbuild/copyWorkerPlugin.ts
new file mode 100644
index 000000000..720671b0c
--- /dev/null
+++ b/config/plugins/esbuild/copyWorkerPlugin.ts
@@ -0,0 +1,80 @@
+import fs from 'fs'
+import path from 'path'
+import crypto from 'crypto'
+import minify from 'babel-minify'
+import { invariant } from 'outvariant'
+import type { Plugin } from 'esbuild'
+import copyServiceWorker from '../../copyServiceWorker'
+
+const SERVICE_WORKER_ENTRY_PATH = path.resolve(
+ process.cwd(),
+ './src/mockServiceWorker.js',
+)
+
+const SERVICE_WORKER_OUTPUT_PATH = path.resolve(
+ process.cwd(),
+ './lib/mockServiceWorker.js',
+)
+
+function getChecksum(contents: string): string {
+ const { code } = minify(
+ contents,
+ {},
+ {
+ // @ts-ignore "babel-minify" has no type definitions.
+ comments: false,
+ },
+ )
+
+ return crypto.createHash('md5').update(code, 'utf8').digest('hex')
+}
+
+export function getWorkerChecksum(): string {
+ const workerContents = fs.readFileSync(SERVICE_WORKER_ENTRY_PATH, 'utf8')
+ return getChecksum(workerContents)
+}
+
+export function copyWorkerPlugin(checksum: string): Plugin {
+ return {
+ name: 'copyWorkerPlugin',
+ async setup(build) {
+ invariant(
+ SERVICE_WORKER_ENTRY_PATH,
+ 'Failed to locate the worker script source file',
+ )
+
+ if (fs.existsSync(SERVICE_WORKER_OUTPUT_PATH)) {
+ console.warn(
+ 'Skipped copying the worker script to "%s": already exists',
+ SERVICE_WORKER_OUTPUT_PATH,
+ )
+ return
+ }
+
+ // Generate the checksum from the worker script's contents.
+ // const workerContents = await fs.readFile(workerSourcePath, 'utf8')
+ // const checksum = getChecksum(workerContents)
+
+ build.onLoad({ filter: /mockServiceWorker\.js$/ }, async () => {
+ return {
+ // Prevent the worker script from being transpiled.
+ // But, generally, the worker script is not in the entrypoints.
+ contents: '',
+ }
+ })
+
+ build.onEnd(() => {
+ console.log('worker script checksum:', checksum)
+
+ // Copy the worker script on the next tick.
+ process.nextTick(async () => {
+ await copyServiceWorker(
+ SERVICE_WORKER_ENTRY_PATH,
+ SERVICE_WORKER_OUTPUT_PATH,
+ checksum,
+ )
+ })
+ })
+ },
+ }
+}
diff --git a/config/plugins/esbuild/forceEsmExtensionsPlugin.ts b/config/plugins/esbuild/forceEsmExtensionsPlugin.ts
new file mode 100644
index 000000000..61e007409
--- /dev/null
+++ b/config/plugins/esbuild/forceEsmExtensionsPlugin.ts
@@ -0,0 +1,54 @@
+import { Plugin } from 'esbuild'
+
+export function forceEsmExtensionsPlugin(): Plugin {
+ return {
+ name: 'forceEsmExtensionsPlugin',
+ setup(build) {
+ const isEsm = build.initialOptions.format === 'esm'
+
+ build.onEnd(async (result) => {
+ if (result.errors.length > 0) {
+ return
+ }
+
+ for (const outputFile of result.outputFiles || []) {
+ const fileContents = outputFile.text
+ const nextFileContents = modifyRelativeImports(fileContents, isEsm)
+
+ outputFile.contents = Buffer.from(nextFileContents)
+ }
+ })
+ },
+ }
+}
+
+const CJS_RELATIVE_IMPORT_EXP = /require\(["'](\..+)["']\)(;)?/gm
+const ESM_RELATIVE_IMPORT_EXP = /from ["'](\..+)["'](;)?/gm
+
+function modifyRelativeImports(contents: string, isEsm: boolean): string {
+ const extension = isEsm ? '.mjs' : '.js'
+ const importExpression = isEsm
+ ? ESM_RELATIVE_IMPORT_EXP
+ : CJS_RELATIVE_IMPORT_EXP
+
+ return contents.replace(
+ importExpression,
+ (_, importPath, maybeSemicolon = '') => {
+ if (importPath.endsWith('.') || importPath.endsWith('/')) {
+ return isEsm
+ ? `from '${importPath}/index${extension}'${maybeSemicolon}`
+ : `require("${importPath}/index${extension}")${maybeSemicolon}`
+ }
+
+ if (importPath.endsWith(extension)) {
+ return isEsm
+ ? `from '${importPath}'${maybeSemicolon}`
+ : `require("${importPath}")${maybeSemicolon}`
+ }
+
+ return isEsm
+ ? `from '${importPath}${extension}'${maybeSemicolon}`
+ : `require("${importPath}${extension}")${maybeSemicolon}`
+ },
+ )
+}
diff --git a/config/plugins/esbuild/resolveCoreImportsPlugin.ts b/config/plugins/esbuild/resolveCoreImportsPlugin.ts
new file mode 100644
index 000000000..1aa30ee8e
--- /dev/null
+++ b/config/plugins/esbuild/resolveCoreImportsPlugin.ts
@@ -0,0 +1,24 @@
+import { Plugin } from 'esbuild'
+
+const { replaceCoreImports } = require('../../replaceCoreImports')
+
+export function resolveCoreImportsPlugin(): Plugin {
+ return {
+ name: 'resolveCoreImportsPlugin',
+ setup(build) {
+ build.onEnd(async (result) => {
+ if (result.errors.length > 0) {
+ return
+ }
+
+ for (const outputFile of result.outputFiles || []) {
+ const isEsm = outputFile.path.endsWith('.mjs')
+ const fileContents = outputFile.text
+ const nextFileContents = replaceCoreImports(fileContents, isEsm)
+
+ outputFile.contents = Buffer.from(nextFileContents)
+ }
+ })
+ },
+ }
+}
diff --git a/config/plugins/esbuild/workerScriptPlugin.ts b/config/plugins/esbuild/workerScriptPlugin.ts
deleted file mode 100644
index f2c5885b7..000000000
--- a/config/plugins/esbuild/workerScriptPlugin.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import path from 'path'
-import fs from 'fs-extra'
-import crypto from 'crypto'
-import minify from 'babel-minify'
-import { invariant } from 'outvariant'
-import type { Plugin } from 'esbuild'
-import copyServiceWorker from '../../copyServiceWorker'
-
-function getChecksum(contents: string): string {
- const { code } = minify(
- contents,
- {},
- {
- // @ts-ignore "babel-minify" has no type definitions.
- comments: false,
- },
- )
-
- return crypto.createHash('md5').update(code, 'utf8').digest('hex')
-}
-
-let hasRunAlready = false
-
-export function workerScriptPlugin(): Plugin {
- return {
- name: 'workerScriptPlugin',
- async setup(build) {
- const workerSourcePath = path.resolve(
- process.cwd(),
- './src/mockServiceWorker.js',
- )
- const workerOutputPath = path.resolve(
- process.cwd(),
- './lib/mockServiceWorker.js',
- )
-
- invariant(
- workerSourcePath,
- 'Failed to locate the worker script source file',
- )
- invariant(
- workerOutputPath,
- 'Failed to locate the worker script output file',
- )
-
- // Generate the checksum from the worker script's contents.
- const workerContents = await fs.readFile(workerSourcePath, 'utf8')
- const checksum = getChecksum(workerContents)
-
- // Inject the global "SERVICE_WORKER_CHECKSUM" variable
- // for runtime worker integrity check.
- build.initialOptions.define = {
- SERVICE_WORKER_CHECKSUM: JSON.stringify(checksum),
- }
-
- // Prevent from copying the worker script multiple times.
- // esbuild will execute this plugin for *each* format.
- if (hasRunAlready) {
- return
- }
-
- hasRunAlready = true
-
- build.onLoad({ filter: /mockServiceWorker\.js$/ }, async () => {
- return {
- // Prevent the worker script from being transpiled.
- // But, generally, the worker script is not in the entrypoints.
- contents: '',
- }
- })
-
- build.onEnd(() => {
- console.log('worker script checksum:', checksum)
-
- // Copy the worker script on the next tick.
- setTimeout(async () => {
- await copyServiceWorker(workerSourcePath, workerOutputPath, checksum)
- }, 100)
- })
- },
- }
-}
diff --git a/config/replaceCoreImports.js b/config/replaceCoreImports.js
new file mode 100644
index 000000000..3087485cf
--- /dev/null
+++ b/config/replaceCoreImports.js
@@ -0,0 +1,21 @@
+function replaceCoreImports(fileContents, isEsm) {
+ const importPattern = isEsm
+ ? /from ["'](~\/core(.*))["'](;)?$/gm
+ : /require\(["'](~\/core(.*))["']\)(;)?/gm
+
+ return fileContents.replace(
+ importPattern,
+ (_, __, maybeSubmodulePath, maybeSemicolon) => {
+ const submodulePath = maybeSubmodulePath || '/index'
+ const semicolon = maybeSemicolon || ''
+
+ return isEsm
+ ? `from "../core${submodulePath}"${semicolon}`
+ : `require("../core${submodulePath}")${semicolon}`
+ },
+ )
+}
+
+module.exports = {
+ replaceCoreImports,
+}
diff --git a/config/scripts/patch-ts.js b/config/scripts/patch-ts.js
new file mode 100644
index 000000000..fa25c7895
--- /dev/null
+++ b/config/scripts/patch-ts.js
@@ -0,0 +1,29 @@
+const fs = require('fs')
+const path = require('path')
+const { replaceCoreImports } = require('../replaceCoreImports')
+
+async function patchTypeDefs() {
+ const typeDefsPaths = [
+ path.resolve(__dirname, '../..', 'lib/browser/index.d.ts'),
+ path.resolve(__dirname, '../..', 'lib/node/index.d.ts'),
+ path.resolve(__dirname, '../..', 'lib/native/index.d.ts'),
+ ]
+
+ for (const typeDefsPath of typeDefsPaths) {
+ if (!fs.existsSync(typeDefsPath)) {
+ continue
+ }
+
+ const fileContents = fs.readFileSync(typeDefsPath, 'utf8')
+
+ // Treat ".d.ts" files as ESM to replace "import" statements.
+ // Force no extension on the ".d.ts" imports.
+ const nextFileContents = replaceCoreImports(fileContents, true)
+
+ fs.writeFileSync(typeDefsPath, nextFileContents, 'utf8')
+
+ console.log('Successfully patched at "%s"!', typeDefsPath)
+ }
+}
+
+patchTypeDefs()
diff --git a/config/scripts/validate-esm.js b/config/scripts/validate-esm.js
new file mode 100644
index 000000000..cc473820b
--- /dev/null
+++ b/config/scripts/validate-esm.js
@@ -0,0 +1,247 @@
+const fs = require('fs')
+const path = require('path')
+const { invariant } = require('outvariant')
+
+const ROOT_PATH = path.resolve(__dirname, '../..')
+
+function fromRoot(...paths) {
+ return path.resolve(ROOT_PATH, ...paths)
+}
+
+const PKG_JSON_PATH = fromRoot('package.json')
+const PKG_JSON = require(PKG_JSON_PATH)
+
+function validatePackageExports() {
+ const { exports } = PKG_JSON
+
+ // Validate the "main", "browser", and "types" root fields.
+ invariant('main' in PKG_JSON, 'Missing "main" field in package.json')
+ invariant('module' in PKG_JSON, 'Missing "module" field in package.json')
+ invariant('types' in PKG_JSON, 'Missing "types" field in package.json')
+
+ invariant(
+ fs.existsSync(fromRoot(PKG_JSON.main)),
+ 'The "main" field points at a non-existing path at "%s"',
+ PKG_JSON.main,
+ )
+
+ // The "exports" key must be present.
+ invariant(exports, 'package.json must have an "exports" field')
+
+ // The "exports" must list expected paths.
+ const expectedExportPaths = [
+ '.',
+ './browser',
+ './node',
+ './package.json',
+ './native',
+ ]
+ expectedExportPaths.forEach((exportPath) => {
+ invariant(exportPath in exports, 'Missing exports path "%s"', exportPath)
+ })
+
+ // Must describe the root export properly.
+ const rootExport = exports['.']
+
+ validateExportConditions(`exports['.']`, rootExport)
+ validateBundle(rootExport.require, false)
+ validateBundle(rootExport.import, true)
+ validateTypeDefs(rootExport.types)
+
+ // Validate "./browser" exports.
+ const browserExports = exports['./browser']
+ validateExportConditions(`exports['./browser']`, browserExports)
+ invariant(
+ browserExports.node === null,
+ 'The "browser" export must set the "node" field to null',
+ )
+ validateBundle(browserExports.require, false)
+ validateBundle(browserExports.import, true)
+ validateTypeDefs(browserExports.types)
+
+ // Validate "./node" exports.
+ const nodeExports = exports['./node']
+ validateExportConditions(`exports['./node']`, nodeExports)
+ invariant(
+ nodeExports.browser === null,
+ 'The "node" export must set the "browser" field to null',
+ )
+ validateBundle(nodeExports.require, false)
+ validateBundle(nodeExports.import, true)
+ validateTypeDefs(nodeExports.types)
+
+ // Validate "./native" exports.
+ const nativeExports = exports['./native']
+ validateExportConditions(`exports['./native']`, nativeExports)
+ invariant(
+ nativeExports.browser === null,
+ 'The "native" export must set the "browser" field to null',
+ )
+ validateBundle(nativeExports.require, false)
+ validateBundle(nativeExports.import, true)
+ validateTypeDefs(nativeExports.types)
+
+ // Validate "./package.json" exports.
+ validateExportConditions(
+ `exports['./package.json]`,
+ exports['./package.json'],
+ )
+
+ console.log('✅ Validated package.json exports')
+}
+
+function validateExportConditions(pointer, conditions) {
+ if (typeof conditions === 'string') {
+ invariant(
+ fs.existsSync(conditions),
+ 'Expected a valid path at "%s" but got %s',
+ pointer,
+ conditions,
+ )
+ return
+ }
+
+ const keys = Object.keys(conditions)
+
+ if (conditions[keys[0]] !== null) {
+ invariant(keys[0] === 'types', 'FS')
+ }
+
+ // Ensure that paths point to existing files.
+ keys.forEach((key) => {
+ const relativeExportPath = conditions[key]
+
+ if (relativeExportPath === null) {
+ return
+ }
+
+ const exportPath = fromRoot(relativeExportPath)
+ invariant(
+ fs.existsSync(exportPath),
+ 'Expected the path at "%s" ("%s") to point at existing file but got %s',
+ pointer,
+ key,
+ exportPath,
+ )
+ })
+}
+
+const ESM_CORE_IMPORT_EXP = /from ["'](..\/)+core(.*)["'];?$/gm
+const CJS_CORE_IMPORT_EXP = /require\(["'](..\/)+core(.*)["']\);?$/gm
+
+function getCodeSnippetAt(contents, index) {
+ return contents.slice(index - 100, index + 50)
+}
+
+function validateBundle(bundlePath, isEsm = false) {
+ const expectedExtension = isEsm ? '.mjs' : '.js'
+
+ invariant(
+ bundlePath.endsWith(expectedExtension),
+ 'Failed to validate bundle: provided bundle path does not point at an ".mjs" file: %s',
+ bundlePath,
+ )
+
+ const absoluteBundlePath = fromRoot(bundlePath)
+ const contents = fs.readFileSync(absoluteBundlePath, 'utf8')
+
+ // The "~/core" imports must be overwritten on the bundler level.
+ invariant(
+ !contents.includes('~/core'),
+ 'Bundle at "%s" includes unresolved "~/core" imports:\n\n%s',
+ bundlePath,
+ getCodeSnippetAt(contents, contents.indexOf('~/core')),
+ )
+
+ // The "core" imports must end with the explicit ".mjs" extension.
+ const coreImportsMatches =
+ contents.matchAll(isEsm ? ESM_CORE_IMPORT_EXP : CJS_CORE_IMPORT_EXP) || []
+
+ for (const match of coreImportsMatches) {
+ const [, backslashes, relativeImportPath] = match
+
+ invariant(
+ backslashes === '../',
+ 'Found a "core" import with incorrect nesting level',
+ )
+
+ invariant(
+ relativeImportPath !== '',
+ 'Found a "core" import without an explicit path at "%s":\n\n%s',
+ absoluteBundlePath,
+ getCodeSnippetAt(contents, match.index),
+ )
+
+ if (isEsm) {
+ // Ensure that all relative imports in the ESM bundle end with ".mjs".
+ // This way bundlers can distinguish between the referenced modules
+ // since the "core" directory contains both ".js" and ".mjs" modules on the same level.
+ invariant(
+ relativeImportPath.endsWith('.mjs'),
+ `Found a "core" import without "${expectedExtension}" extension at "%s":\n\n%s`,
+ absoluteBundlePath,
+ getCodeSnippetAt(contents, match.index),
+ )
+ }
+ }
+
+ console.log('✅ Validated bundle at "%s"', bundlePath)
+}
+
+function validateTypeDefs(typeDefsPath) {
+ const absoluteTypeDefsPath = fromRoot(typeDefsPath)
+ invariant(
+ fs.existsSync(absoluteTypeDefsPath),
+ 'Failed to validate type definitions at "%s": file does not exist',
+ absoluteTypeDefsPath,
+ )
+
+ const contents = fs.readFileSync(absoluteTypeDefsPath, 'utf8')
+
+ // The "~/core" imports must also be replaced with relative paths on build.
+ invariant(
+ !contents.includes('~/core'),
+ 'Found unresolved "~/core" imports at "%s":\n\n%s',
+ absoluteTypeDefsPath,
+ getCodeSnippetAt(contents, contents.indexOf('~/core')),
+ )
+
+ console.log('✅ Validated type definitions at "%s"', typeDefsPath)
+}
+
+function validatePackageFiles() {
+ const { files } = PKG_JSON
+
+ const expectedFiles = [
+ 'config/constants.js',
+ 'config/scripts/postinstall.js',
+ 'cli',
+ 'lib',
+ 'browser',
+ 'node',
+ 'native',
+ ]
+
+ // Must list all the expcted files.
+ expectedFiles.forEach((expectedFile) => {
+ invariant(
+ files.includes(expectedFile),
+ '"%s" is not listed in "files" in package.json',
+ expectedFile,
+ )
+ })
+
+ // All the listed files must exist.
+ expectedFiles.every((expectedFile) => {
+ invariant(
+ fs.existsSync(fromRoot(expectedFile)),
+ 'The file "%s" in "files" points at non-existing file',
+ expectedFile,
+ )
+ })
+
+ console.log('✅ Validated package.json "files" field')
+}
+
+validatePackageExports()
+validatePackageFiles()
diff --git a/global.d.ts b/global.d.ts
index 8976bba1f..e8970504b 100644
--- a/global.d.ts
+++ b/global.d.ts
@@ -1 +1,10 @@
declare const SERVICE_WORKER_CHECKSUM: string
+
+declare module '@bundled-es-modules/cookie' {
+ export * as default from 'cookie'
+}
+
+declare module '@bundled-es-modules/statuses' {
+ import * as statuses from 'statuses'
+ export default statuses
+}
diff --git a/jest.config.js b/jest.config.js
index 68450831a..6feb897fd 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -7,4 +7,7 @@ module.exports = {
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(j|t)sx?$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
setupFiles: ['./jest.setup.js'],
+ moduleNameMapper: {
+ '^~/core(/.*)?$': '/src/core/$1',
+ },
}
diff --git a/jest.setup.js b/jest.setup.js
index 50eb416a3..318a499b0 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -1,13 +1,27 @@
-const fetch = require('node-fetch')
+const { TextEncoder, TextDecoder } = require('util')
-if (typeof window !== 'undefined') {
- // Provide "Headers" to be accessible in test cases
- // since they are not, by default.
- Object.defineProperty(window, 'Headers', {
- writable: true,
- value: fetch.Headers,
- })
+/**
+ * @note Temporary global polyfills for Jest because it's
+ * ignoring Node.js defaults.
+ */
+Object.defineProperties(globalThis, {
+ TextDecoder: { value: TextDecoder },
+ TextEncoder: { value: TextEncoder },
+})
+
+const { Blob } = require('buffer')
+const { Request, Response, Headers, File, FormData } = require('undici')
+Object.defineProperties(globalThis, {
+ Headers: { value: Headers },
+ Request: { value: Request },
+ Response: { value: Response },
+ File: { value: File },
+ Blob: { value: Blob },
+ FormData: { value: FormData },
+})
+
+if (typeof window !== 'undefined') {
Object.defineProperty(navigator || {}, 'serviceWorker', {
writable: false,
value: {
diff --git a/native/package.json b/native/package.json
index bb12dd1d1..a44c5d0e4 100644
--- a/native/package.json
+++ b/native/package.json
@@ -1,5 +1,6 @@
{
+ "browser": null,
"main": "../lib/native/index.js",
"module": "../lib/native/index.mjs",
- "typings": "../lib/native/index.d.ts"
+ "types": "../lib/native/index.d.ts"
}
diff --git a/node/package.json b/node/package.json
index 3d1591115..395e07575 100644
--- a/node/package.json
+++ b/node/package.json
@@ -1,5 +1,6 @@
{
+ "browser": null,
"main": "../lib/node/index.js",
"module": "../lib/node/index.mjs",
- "typings": "../lib/node/index.d.ts"
+ "types": "../lib/node/index.d.ts"
}
diff --git a/package.json b/package.json
index 20d9c42ba..8aec4edaa 100644
--- a/package.json
+++ b/package.json
@@ -2,39 +2,60 @@
"name": "msw",
"version": "1.3.2",
"description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
- "main": "./lib/index.js",
- "types": "./lib/index.d.ts",
+ "main": "./lib/core/index.js",
+ "module": "./lib/core/index.mjs",
+ "types": "./lib/core/index.d.ts",
"packageManager": "pnpm@7.12.2",
"exports": {
".": {
- "default": "./lib/index.js"
+ "types": "./lib/core/index.d.ts",
+ "require": "./lib/core/index.js",
+ "import": "./lib/core/index.mjs",
+ "default": "./lib/core/index.js"
},
- "./native": {
- "types": "./lib/native/index.d.ts",
- "default": "./lib/native/index.js"
+ "./browser": {
+ "node": null,
+ "types": "./lib/browser/index.d.ts",
+ "require": "./lib/browser/index.js",
+ "import": "./lib/browser/index.mjs",
+ "default": "./lib/browser/index.js"
},
"./node": {
+ "browser": null,
"types": "./lib/node/index.d.ts",
"require": "./lib/node/index.js",
+ "import": "./lib/node/index.mjs",
"default": "./lib/node/index.mjs"
},
+ "./native": {
+ "browser": null,
+ "types": "./lib/native/index.d.ts",
+ "require": "./lib/native/index.js",
+ "import": "./lib/native/index.mjs",
+ "default": "./lib/native/index.js"
+ },
"./package.json": "./package.json"
},
"bin": {
"msw": "cli/index.js"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
},
"scripts": {
"start": "tsup --watch",
"clean": "rimraf ./lib",
"lint": "eslint \"{cli,config,src,test}/**/*.ts\"",
- "build": "cross-env NODE_ENV=production tsup",
+ "prebuild": "rimraf ./lib",
+ "build": "pnpm clean && cross-env NODE_ENV=production tsup && pnpm patch:dts",
+ "patch:dts": "node \"./config/scripts/patch-ts.js\"",
+ "check:exports": "node \"./config/scripts/validate-esm.js\"",
"test": "pnpm test:unit && pnpm test:node && pnpm test:browser",
"test:unit": "cross-env BABEL_ENV=test jest --maxWorkers=3",
- "test:node": "jest --config=./test/jest.config.js",
+ "test:node": "jest --config=./test/jest.config.js --forceExit",
"test:browser": "playwright test -c ./test/browser/playwright.config.ts",
+ "test:modules:node": "jest --config=./test/modules/node/jest.config.js",
+ "test:modules:browser": "playwright test -c ./test/modules/browser/playwright.config.ts",
"test:ts": "ts-node test/typings/run.ts",
"prepare": "pnpm simple-git-hooks init",
"prepack": "pnpm build",
@@ -68,8 +89,9 @@
"config/scripts/postinstall.js",
"cli",
"lib",
- "native",
+ "browser",
"node",
+ "native",
"LICENSE.md",
"README.md"
],
@@ -88,23 +110,27 @@
],
"sideEffects": false,
"dependencies": {
- "@mswjs/cookies": "^0.2.2",
- "@mswjs/interceptors": "^0.17.10",
- "@open-draft/until": "^1.0.3",
+ "@bundled-es-modules/cookie": "^2.0.0",
+ "@bundled-es-modules/js-levenshtein": "^2.0.1",
+ "@bundled-es-modules/statuses": "^1.0.1",
+ "@mswjs/cookies": "^1.0.0",
+ "@mswjs/interceptors": "^0.25.1",
+ "@open-draft/until": "^2.1.0",
"@types/cookie": "^0.4.1",
"@types/js-levenshtein": "^1.1.1",
- "chalk": "^4.1.1",
+ "@types/statuses": "^2.0.1",
+ "chalk": "^4.1.2",
"chokidar": "^3.4.2",
- "cookie": "^0.4.2",
+ "formdata-node": "4.4.1",
"graphql": "^16.8.1",
- "headers-polyfill": "3.2.5",
+ "headers-polyfill": "^4.0.1",
"inquirer": "^8.2.0",
"is-node-process": "^1.2.0",
"js-levenshtein": "^1.1.6",
"node-fetch": "^2.6.7",
"outvariant": "^1.4.0",
"path-to-regexp": "^6.2.0",
- "strict-event-emitter": "^0.4.3",
+ "strict-event-emitter": "^0.5.0",
"type-fest": "^2.19.0",
"yargs": "^17.3.1"
},
@@ -120,19 +146,22 @@
"@swc/jest": "^0.2.24",
"@types/express": "^4.17.17",
"@types/fs-extra": "^9.0.13",
+ "@types/glob": "^8.1.0",
"@types/jest": "^29.4.0",
"@types/json-bigint": "^1.0.1",
- "@types/node": "^14.14.31",
+ "@types/node": "18.x",
"@types/node-fetch": "^2.5.11",
"@types/puppeteer": "^5.4.4",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
+ "@web/dev-server": "^0.1.38",
"babel-loader": "^8.2.3",
"babel-minify": "^0.5.1",
"commitizen": "^4.2.4",
"cross-env": "^7.0.3",
"cross-fetch": "^3.1.5",
"cz-conventional-changelog": "3.3.0",
+ "esbuild": "^0.17.15",
"esbuild-loader": "^2.21.0",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
@@ -140,26 +169,27 @@
"express": "^4.18.2",
"fs-extra": "^10.0.0",
"fs-teardown": "^0.3.0",
+ "glob": "^9.3.4",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"json-bigint": "^1.0.0",
"lint-staged": "^13.0.3",
- "page-with": "^0.5.0",
+ "page-with": "^0.6.1",
"prettier": "^2.7.1",
"regenerator-runtime": "^0.13.9",
"rimraf": "^3.0.2",
"simple-git-hooks": "^2.8.0",
- "statuses": "^2.0.0",
"ts-node": "^10.9.1",
- "tsup": "^5.12.8",
+ "tsup": "^6.7.0",
"typescript": "^5.0.2",
+ "undici": "^5.20.0",
"url-loader": "^4.1.1",
"webpack": "^5.68.0",
"webpack-dev-server": "^3.11.2",
"webpack-http-server": "^0.5.0"
},
"peerDependencies": {
- "typescript": ">= 4.4.x <= 5.2.x"
+ "typescript": ">= 4.7.x <= 5.2.x"
},
"peerDependenciesMeta": {
"typescript": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 05e02d57c..84896c459 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,12 +6,15 @@ overrides:
specifiers:
'@babel/core': ^7.17.2
'@babel/preset-env': ^7.16.11
+ '@bundled-es-modules/cookie': ^2.0.0
+ '@bundled-es-modules/js-levenshtein': ^2.0.1
+ '@bundled-es-modules/statuses': ^1.0.1
'@commitlint/cli': ^16.1.0
'@commitlint/config-conventional': ^16.0.0
- '@mswjs/cookies': ^0.2.2
- '@mswjs/interceptors': ^0.17.10
+ '@mswjs/cookies': ^1.0.0
+ '@mswjs/interceptors': ^0.25.1
'@open-draft/test-server': ^0.4.2
- '@open-draft/until': ^1.0.3
+ '@open-draft/until': ^2.1.0
'@ossjs/release': ^0.8.0
'@playwright/test': ^1.30.0
'@swc/core': ^1.3.35
@@ -19,32 +22,37 @@ specifiers:
'@types/cookie': ^0.4.1
'@types/express': ^4.17.17
'@types/fs-extra': ^9.0.13
+ '@types/glob': ^8.1.0
'@types/jest': ^29.4.0
'@types/js-levenshtein': ^1.1.1
'@types/json-bigint': ^1.0.1
- '@types/node': ^14.14.31
+ '@types/node': 18.x
'@types/node-fetch': ^2.5.11
'@types/puppeteer': ^5.4.4
+ '@types/statuses': ^2.0.1
'@typescript-eslint/eslint-plugin': ^5.11.0
'@typescript-eslint/parser': ^5.11.0
+ '@web/dev-server': ^0.1.38
babel-loader: ^8.2.3
babel-minify: ^0.5.1
- chalk: ^4.1.1
+ chalk: ^4.1.2
chokidar: 3.4.1
commitizen: ^4.2.4
- cookie: ^0.4.2
cross-env: ^7.0.3
cross-fetch: ^3.1.5
cz-conventional-changelog: 3.3.0
+ esbuild: ^0.17.15
esbuild-loader: ^2.21.0
eslint: ^7.30.0
eslint-config-prettier: ^8.3.0
eslint-plugin-prettier: ^3.4.0
express: ^4.18.2
+ formdata-node: 4.4.1
fs-extra: ^10.0.0
fs-teardown: ^0.3.0
+ glob: ^9.3.4
graphql: ^16.8.1
- headers-polyfill: 3.2.5
+ headers-polyfill: ^4.0.1
inquirer: ^8.2.0
is-node-process: ^1.2.0
jest: ^29.4.3
@@ -54,18 +62,18 @@ specifiers:
lint-staged: ^13.0.3
node-fetch: ^2.6.7
outvariant: ^1.4.0
- page-with: ^0.5.0
+ page-with: ^0.6.1
path-to-regexp: ^6.2.0
prettier: ^2.7.1
regenerator-runtime: ^0.13.9
rimraf: ^3.0.2
simple-git-hooks: ^2.8.0
- statuses: ^2.0.0
- strict-event-emitter: ^0.4.3
+ strict-event-emitter: ^0.5.0
ts-node: ^10.9.1
- tsup: ^5.12.8
+ tsup: ^6.7.0
type-fest: ^2.19.0
typescript: ^5.0.2
+ undici: ^5.20.0
url-loader: ^4.1.1
webpack: ^5.68.0
webpack-dev-server: ^3.11.2
@@ -73,23 +81,27 @@ specifiers:
yargs: ^17.3.1
dependencies:
- '@mswjs/cookies': 0.2.2
- '@mswjs/interceptors': 0.17.10
- '@open-draft/until': 1.0.3
+ '@bundled-es-modules/cookie': 2.0.0
+ '@bundled-es-modules/js-levenshtein': 2.0.1
+ '@bundled-es-modules/statuses': 1.0.1
+ '@mswjs/cookies': 1.0.0
+ '@mswjs/interceptors': 0.25.1
+ '@open-draft/until': 2.1.0
'@types/cookie': 0.4.1
'@types/js-levenshtein': 1.1.1
- chalk: 4.1.1
+ '@types/statuses': 2.0.1
+ chalk: 4.1.2
chokidar: 3.4.1
- cookie: 0.4.2
+ formdata-node: 4.4.1
graphql: 16.8.1
- headers-polyfill: 3.2.5
+ headers-polyfill: 4.0.1
inquirer: 8.2.5
is-node-process: 1.2.0
js-levenshtein: 1.1.6
node-fetch: 2.6.9
outvariant: 1.4.0
path-to-regexp: 6.2.1
- strict-event-emitter: 0.4.6
+ strict-event-emitter: 0.5.0
type-fest: 2.19.0
yargs: 17.7.0
@@ -105,19 +117,22 @@ devDependencies:
'@swc/jest': 0.2.24_@swc+core@1.3.35
'@types/express': 4.17.17
'@types/fs-extra': 9.0.13
+ '@types/glob': 8.1.0
'@types/jest': 29.4.0
'@types/json-bigint': 1.0.1
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
'@types/node-fetch': 2.6.2
'@types/puppeteer': 5.4.7
'@typescript-eslint/eslint-plugin': 5.52.0_aaw67h7nkydj3qj4plp2jqjmxe
'@typescript-eslint/parser': 5.52.0_jeuwjvsopuo23ctsjsevxmvjsi
+ '@web/dev-server': 0.1.38
babel-loader: 8.3.0_la66t7xldg4uecmyawueag5wkm
babel-minify: 0.5.2
commitizen: 4.3.0_@swc+core@1.3.35
cross-env: 7.0.3
cross-fetch: 3.1.5
cz-conventional-changelog: 3.3.0_@swc+core@1.3.35
+ esbuild: 0.17.19
esbuild-loader: 2.21.0_webpack@5.75.0
eslint: 7.32.0
eslint-config-prettier: 8.6.0_eslint@7.32.0
@@ -125,26 +140,35 @@ devDependencies:
express: 4.18.2
fs-extra: 10.1.0
fs-teardown: 0.3.2
- jest: 29.4.3_nw6xvwuzmqp7vps7knduexkcvm
+ glob: 9.3.5
+ jest: 29.4.3_v5qag4bu7yd4vl7sd6rt2doplm
jest-environment-jsdom: 29.4.3
json-bigint: 1.0.0
lint-staged: 13.1.2
- page-with: 0.5.1_@swc+core@1.3.35
+ page-with: 0.6.1_mtsvlg4x4u5udzh2pohivgt4x4
prettier: 2.8.4
regenerator-runtime: 0.13.11
rimraf: 3.0.2
simple-git-hooks: 2.8.1
- statuses: 2.0.1
- ts-node: 10.9.1_oe3jy5ze54sjippw2sqzxdlwem
- tsup: 5.12.9_4s7jzcjqpdttwnwh3e3glkuq6y
+ ts-node: 10.9.1_x2vjra2lmmhd46xm3mchw7ztui
+ tsup: 6.7.0_4s7jzcjqpdttwnwh3e3glkuq6y
typescript: 5.0.2
+ undici: 5.23.0
url-loader: 4.1.1_webpack@5.75.0
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
webpack-dev-server: 3.11.3_webpack@5.75.0
- webpack-http-server: 0.5.0_@swc+core@1.3.35
+ webpack-http-server: 0.5.0_mtsvlg4x4u5udzh2pohivgt4x4
packages:
+ /@75lb/deep-merge/1.1.1:
+ resolution: {integrity: sha512-xvgv6pkMGBA6GwdyJbNAnDmfAIR/DfWhrj9jgWh3TY7gRm3KO46x/GPjRg6wJ0nOepwqrNxFfojebh0Df4h4Tw==}
+ engines: {node: '>=12.17'}
+ dependencies:
+ lodash.assignwith: 4.2.0
+ typical: 7.1.1
+ dev: true
+
/@ampproject/remapping/2.2.0:
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
engines: {node: '>=6.0.0'}
@@ -1346,6 +1370,24 @@ packages:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
+ /@bundled-es-modules/cookie/2.0.0:
+ resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
+ dependencies:
+ cookie: 0.5.0
+ dev: false
+
+ /@bundled-es-modules/js-levenshtein/2.0.1:
+ resolution: {integrity: sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==}
+ dependencies:
+ js-levenshtein: 1.1.6
+ dev: false
+
+ /@bundled-es-modules/statuses/1.0.1:
+ resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
+ dependencies:
+ statuses: 2.0.1
+ dev: false
+
/@commitlint/cli/16.3.0_@swc+core@1.3.35:
resolution: {integrity: sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA==}
engines: {node: '>=v12'}
@@ -1413,7 +1455,7 @@ packages:
engines: {node: '>=v12'}
dependencies:
'@commitlint/types': 16.2.1
- chalk: 4.1.1
+ chalk: 4.1.2
dev: true
/@commitlint/is-ignored/16.2.4:
@@ -1442,10 +1484,10 @@ packages:
'@commitlint/execute-rule': 16.2.1
'@commitlint/resolve-extends': 16.2.1
'@commitlint/types': 16.2.1
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
cosmiconfig: 7.1.0
- cosmiconfig-typescript-loader: 2.0.2_plaptv2cv5vvro2su5yxvauvda
+ cosmiconfig-typescript-loader: 2.0.2_f6calhiv3qbku3gmsoec3zvctu
lodash: 4.17.21
resolve-from: 5.0.0
typescript: 4.9.5
@@ -1463,15 +1505,15 @@ packages:
'@commitlint/execute-rule': 17.4.0
'@commitlint/resolve-extends': 17.4.4
'@commitlint/types': 17.4.4
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
cosmiconfig: 8.0.0
- cosmiconfig-typescript-loader: 4.3.0_gg653o4cxizhrslchmhiad54ma
+ cosmiconfig-typescript-loader: 4.3.0_bmci5xihqld5vqu3v4tyk3r5ra
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
- ts-node: 10.9.1_plaptv2cv5vvro2su5yxvauvda
+ ts-node: 10.9.1_f6calhiv3qbku3gmsoec3zvctu
typescript: 4.9.5
transitivePeerDependencies:
- '@swc/core'
@@ -1555,14 +1597,14 @@ packages:
resolution: {integrity: sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA==}
engines: {node: '>=v12'}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
dev: true
/@commitlint/types/17.4.4:
resolution: {integrity: sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==}
engines: {node: '>=v14'}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
dev: true
optional: true
@@ -1582,6 +1624,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-arm/0.17.19:
+ resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/android-arm64/0.16.17:
resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==}
engines: {node: '>=12'}
@@ -1591,6 +1642,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-arm64/0.17.19:
+ resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/android-x64/0.16.17:
resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==}
engines: {node: '>=12'}
@@ -1600,6 +1660,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-x64/0.17.19:
+ resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/darwin-arm64/0.16.17:
resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==}
engines: {node: '>=12'}
@@ -1609,6 +1678,15 @@ packages:
dev: true
optional: true
+ /@esbuild/darwin-arm64/0.17.19:
+ resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/darwin-x64/0.16.17:
resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==}
engines: {node: '>=12'}
@@ -1618,6 +1696,15 @@ packages:
dev: true
optional: true
+ /@esbuild/darwin-x64/0.17.19:
+ resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/freebsd-arm64/0.16.17:
resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==}
engines: {node: '>=12'}
@@ -1627,6 +1714,15 @@ packages:
dev: true
optional: true
+ /@esbuild/freebsd-arm64/0.17.19:
+ resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/freebsd-x64/0.16.17:
resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==}
engines: {node: '>=12'}
@@ -1636,6 +1732,15 @@ packages:
dev: true
optional: true
+ /@esbuild/freebsd-x64/0.17.19:
+ resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-arm/0.16.17:
resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==}
engines: {node: '>=12'}
@@ -1645,6 +1750,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-arm/0.17.19:
+ resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-arm64/0.16.17:
resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==}
engines: {node: '>=12'}
@@ -1654,6 +1768,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-arm64/0.17.19:
+ resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-ia32/0.16.17:
resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==}
engines: {node: '>=12'}
@@ -1663,10 +1786,10 @@ packages:
dev: true
optional: true
- /@esbuild/linux-loong64/0.14.54:
- resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+ /@esbuild/linux-ia32/0.17.19:
+ resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
engines: {node: '>=12'}
- cpu: [loong64]
+ cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
@@ -1681,6 +1804,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-loong64/0.17.19:
+ resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-mips64el/0.16.17:
resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==}
engines: {node: '>=12'}
@@ -1690,6 +1822,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-mips64el/0.17.19:
+ resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-ppc64/0.16.17:
resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==}
engines: {node: '>=12'}
@@ -1699,6 +1840,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-ppc64/0.17.19:
+ resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-riscv64/0.16.17:
resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==}
engines: {node: '>=12'}
@@ -1708,6 +1858,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-riscv64/0.17.19:
+ resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-s390x/0.16.17:
resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==}
engines: {node: '>=12'}
@@ -1717,6 +1876,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-s390x/0.17.19:
+ resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-x64/0.16.17:
resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==}
engines: {node: '>=12'}
@@ -1726,6 +1894,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-x64/0.17.19:
+ resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/netbsd-x64/0.16.17:
resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==}
engines: {node: '>=12'}
@@ -1735,6 +1912,15 @@ packages:
dev: true
optional: true
+ /@esbuild/netbsd-x64/0.17.19:
+ resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/openbsd-x64/0.16.17:
resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==}
engines: {node: '>=12'}
@@ -1744,6 +1930,15 @@ packages:
dev: true
optional: true
+ /@esbuild/openbsd-x64/0.17.19:
+ resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/sunos-x64/0.16.17:
resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==}
engines: {node: '>=12'}
@@ -1753,6 +1948,15 @@ packages:
dev: true
optional: true
+ /@esbuild/sunos-x64/0.17.19:
+ resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-arm64/0.16.17:
resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==}
engines: {node: '>=12'}
@@ -1762,6 +1966,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-arm64/0.17.19:
+ resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-ia32/0.16.17:
resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==}
engines: {node: '>=12'}
@@ -1771,6 +1984,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-ia32/0.17.19:
+ resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-x64/0.16.17:
resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==}
engines: {node: '>=12'}
@@ -1780,6 +2002,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-x64/0.17.19:
+ resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@eslint/eslintrc/0.4.3:
resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -1833,8 +2064,8 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.4.3
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
jest-message-util: 29.4.3
jest-util: 29.4.3
slash: 3.0.0
@@ -1854,14 +2085,14 @@ packages:
'@jest/test-result': 29.4.3
'@jest/transform': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
ansi-escapes: 4.3.2
- chalk: 4.1.1
+ chalk: 4.1.2
ci-info: 3.8.0
exit: 0.1.2
graceful-fs: 4.2.10
jest-changed-files: 29.4.3
- jest-config: 29.4.3_ghv2zugsw3zjg5rog5rhyka5ja
+ jest-config: 29.4.3_v5qag4bu7yd4vl7sd6rt2doplm
jest-haste-map: 29.4.3
jest-message-util: 29.4.3
jest-regex-util: 29.4.3
@@ -1895,7 +2126,7 @@ packages:
dependencies:
'@jest/fake-timers': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-mock: 29.4.3
dev: true
@@ -1922,7 +2153,7 @@ packages:
dependencies:
'@jest/types': 29.4.3
'@sinonjs/fake-timers': 10.0.2
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-message-util: 29.4.3
jest-mock: 29.4.3
jest-util: 29.4.3
@@ -1955,8 +2186,8 @@ packages:
'@jest/transform': 29.4.3
'@jest/types': 29.4.3
'@jridgewell/trace-mapping': 0.3.17
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
collect-v8-coverage: 1.0.1
exit: 0.1.2
glob: 7.2.3
@@ -2021,7 +2252,7 @@ packages:
'@jest/types': 29.4.3
'@jridgewell/trace-mapping': 0.3.17
babel-plugin-istanbul: 6.1.1
- chalk: 4.1.1
+ chalk: 4.1.2
convert-source-map: 2.0.0
fast-json-stable-stringify: 2.1.0
graceful-fs: 4.2.10
@@ -2042,9 +2273,9 @@ packages:
dependencies:
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
'@types/yargs': 16.0.5
- chalk: 4.1.1
+ chalk: 4.1.2
dev: true
/@jest/types/29.4.3:
@@ -2054,9 +2285,9 @@ packages:
'@jest/schemas': 29.4.3
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
'@types/yargs': 17.0.22
- chalk: 4.1.1
+ chalk: 4.1.2
dev: true
/@jridgewell/gen-mapping/0.1.1:
@@ -2111,28 +2342,21 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
- /@mswjs/cookies/0.2.2:
- resolution: {integrity: sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==}
+ /@mswjs/cookies/1.0.0:
+ resolution: {integrity: sha512-TdXoBdI+h/EDTsVLCX/34s4+9U0sWi92qFnIGUEikpMCSKLhBeujovyYVSoORNbYgsBH5ga7/tfxyWcEZAxiYA==}
engines: {node: '>=14'}
- dependencies:
- '@types/set-cookie-parser': 2.4.2
- set-cookie-parser: 2.5.1
dev: false
- /@mswjs/interceptors/0.17.10:
- resolution: {integrity: sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==}
- engines: {node: '>=14'}
+ /@mswjs/interceptors/0.25.1:
+ resolution: {integrity: sha512-iM/2Qp+y7zKrX1sf45sPvvE7CGly8AKSR8Ua7cXAszXCK/To5i/L8AwiheEaBSVcZ6R7Em7kTcyZWN5H2ivcEQ==}
+ engines: {node: '>=18'}
dependencies:
- '@open-draft/until': 1.0.3
- '@types/debug': 4.1.7
- '@xmldom/xmldom': 0.8.6
- debug: 4.3.4
- headers-polyfill: 3.2.5
+ '@open-draft/deferred-promise': 2.2.0
+ '@open-draft/logger': 0.3.0
+ '@open-draft/until': 2.1.0
+ is-node-process: 1.2.0
outvariant: 1.4.0
- strict-event-emitter: 0.2.8
- web-encoding: 1.1.5
- transitivePeerDependencies:
- - supports-color
+ strict-event-emitter: 0.5.0
dev: false
/@nodelib/fs.scandir/2.1.5:
@@ -2156,9 +2380,15 @@ packages:
fastq: 1.15.0
dev: true
- /@open-draft/deferred-promise/2.1.0:
- resolution: {integrity: sha512-Rzd5JrXZX8zErHzgcGyngh4fmEbSHqTETdGj9rXtejlqMIgXFlyKBA7Jn1Xp0Ls0M0Y22+xHcWiEzbmdWl0BOA==}
- dev: true
+ /@open-draft/deferred-promise/2.2.0:
+ resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
+
+ /@open-draft/logger/0.3.0:
+ resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
+ dependencies:
+ is-node-process: 1.2.0
+ outvariant: 1.4.0
+ dev: false
/@open-draft/test-server/0.4.2:
resolution: {integrity: sha512-J9wbdQkPx5WKcDNtgfnXsx5ew4UJd6BZyGr89YlHeaUkOShkO2iO5QIyCCsG4qpjIvr2ZTkEYJA9ujOXXyO6Pg==}
@@ -2176,18 +2406,14 @@ packages:
- utf-8-validate
dev: true
- /@open-draft/until/1.0.3:
- resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==}
-
/@open-draft/until/2.1.0:
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
- dev: true
/@ossjs/release/0.8.0:
resolution: {integrity: sha512-vzxhYvad/Ub3j8bWWCRfdwTvFzK3HtKjm8IM5J+7njnQcZZie5iouUXX+G65OI3F1YgQSWvsozrWqHyN1x7fjQ==}
hasBin: true
dependencies:
- '@open-draft/deferred-promise': 2.1.0
+ '@open-draft/deferred-promise': 2.2.0
'@open-draft/until': 2.1.0
'@types/conventional-commits-parser': 3.0.3
'@types/issue-parser': 3.0.1
@@ -2218,10 +2444,37 @@ packages:
engines: {node: '>=14'}
hasBin: true
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
playwright-core: 1.30.0
dev: true
+ /@rollup/plugin-node-resolve/13.3.0_rollup@2.79.1:
+ resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==}
+ engines: {node: '>= 10.0.0'}
+ peerDependencies:
+ rollup: ^2.42.0
+ dependencies:
+ '@rollup/pluginutils': 3.1.0_rollup@2.79.1
+ '@types/resolve': 1.17.1
+ deepmerge: 4.3.0
+ is-builtin-module: 3.2.1
+ is-module: 1.0.0
+ resolve: 1.22.1
+ rollup: 2.79.1
+ dev: true
+
+ /@rollup/pluginutils/3.1.0_rollup@2.79.1:
+ resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
+ engines: {node: '>= 8.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0
+ dependencies:
+ '@types/estree': 0.0.39
+ estree-walker: 1.0.1
+ picomatch: 2.3.1
+ rollup: 2.79.1
+ dev: true
+
/@sinclair/typebox/0.25.23:
resolution: {integrity: sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ==}
dev: true
@@ -2381,6 +2634,12 @@ packages:
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
dev: true
+ /@types/accepts/1.3.5:
+ resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
+ dependencies:
+ '@types/node': 18.17.14
+ dev: true
+
/@types/babel__core/7.20.0:
resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
dependencies:
@@ -2414,34 +2673,52 @@ packages:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies:
'@types/connect': 3.4.35
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
+ dev: true
+
+ /@types/command-line-args/5.2.1:
+ resolution: {integrity: sha512-U2OcmS2tj36Yceu+mRuPyUV0ILfau/h5onStcSCzqTENsq0sBiAp2TmaXu1k8fY4McLcPKSYl9FRzn2hx5bI+w==}
dev: true
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
+ dev: true
+
+ /@types/content-disposition/0.5.6:
+ resolution: {integrity: sha512-GmShTb4qA9+HMPPaV2+Up8tJafgi38geFi7vL4qAM7k8BwjoelgHZqEUKJZLvughUw22h6vD/wvwN4IUCaWpDA==}
dev: true
/@types/conventional-commits-parser/3.0.3:
resolution: {integrity: sha512-aoUKfRQYvGMH+spFpOTX9jO4nZoz9/BKp4hlHPxL3Cj2r2Xj+jEcwlXtFIyZr5uL8bh1nbWynDEYaAota+XqPg==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
dev: true
/@types/cookie/0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+ /@types/cookies/0.7.8:
+ resolution: {integrity: sha512-y6KhF1GtsLERUpqOV+qZJrjUGzc0GE6UTa0b5Z/LZ7Nm2mKSdCXmS6Kdnl7fctPNnMSouHjxqEWI12/YqQfk5w==}
+ dependencies:
+ '@types/connect': 3.4.35
+ '@types/express': 4.17.17
+ '@types/keygrip': 1.0.2
+ '@types/node': 18.17.14
+ dev: true
+
/@types/cors/2.8.13:
resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
dev: true
/@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:
'@types/ms': 0.7.31
+ dev: true
/@types/eslint-scope/3.7.4:
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
@@ -2457,6 +2734,10 @@ packages:
'@types/json-schema': 7.0.11
dev: true
+ /@types/estree/0.0.39:
+ resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
+ dev: true
+
/@types/estree/0.0.51:
resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
dev: true
@@ -2464,7 +2745,7 @@ packages:
/@types/express-serve-static-core/4.17.33:
resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
dev: true
@@ -2481,20 +2762,35 @@ packages:
/@types/fs-extra/9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies:
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
dev: true
/@types/glob/7.2.0:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
dependencies:
'@types/minimatch': 5.1.2
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
+ dev: true
+
+ /@types/glob/8.1.0:
+ resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
+ dependencies:
+ '@types/minimatch': 5.1.2
+ '@types/node': 18.17.14
dev: true
/@types/graceful-fs/4.1.6:
resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
+ dev: true
+
+ /@types/http-assert/1.5.3:
+ resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==}
+ dev: true
+
+ /@types/http-errors/2.0.1:
+ resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==}
dev: true
/@types/issue-parser/3.0.1:
@@ -2531,7 +2827,7 @@ packages:
/@types/jsdom/20.0.1:
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
'@types/tough-cookie': 4.0.2
parse5: 7.1.2
dev: true
@@ -2544,6 +2840,29 @@ packages:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true
+ /@types/keygrip/1.0.2:
+ resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==}
+ dev: true
+
+ /@types/koa-compose/3.2.5:
+ resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==}
+ dependencies:
+ '@types/koa': 2.13.8
+ dev: true
+
+ /@types/koa/2.13.8:
+ resolution: {integrity: sha512-Ugmxmgk/yPRW3ptBTh9VjOLwsKWJuGbymo1uGX0qdaqqL18uJiiG1ZoV0rxCOYSaDGhvEp5Ece02Amx0iwaxQQ==}
+ dependencies:
+ '@types/accepts': 1.3.5
+ '@types/content-disposition': 0.5.6
+ '@types/cookies': 0.7.8
+ '@types/http-assert': 1.5.3
+ '@types/http-errors': 2.0.1
+ '@types/keygrip': 1.0.2
+ '@types/koa-compose': 3.2.5
+ '@types/node': 18.17.14
+ dev: true
+
/@types/mime/3.0.1:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: true
@@ -2558,6 +2877,7 @@ packages:
/@types/ms/0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
+ dev: true
/@types/mustache/4.2.2:
resolution: {integrity: sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA==}
@@ -2566,16 +2886,17 @@ packages:
/@types/node-fetch/2.6.2:
resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
dependencies:
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
form-data: 3.0.1
dev: true
- /@types/node/14.18.36:
- resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
- dev: true
-
/@types/node/16.18.12:
resolution: {integrity: sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw==}
+ dev: true
+
+ /@types/node/18.17.14:
+ resolution: {integrity: sha512-ZE/5aB73CyGqgQULkLG87N9GnyGe5TcQjv34pwS8tfBs1IkCh0ASM69mydb2znqd6v0eX+9Ytvk6oQRqu8T1Vw==}
+ dev: true
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -2585,6 +2906,10 @@ packages:
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
dev: true
+ /@types/parse5/6.0.3:
+ resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
+ dev: true
+
/@types/prettier/2.7.2:
resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==}
dev: true
@@ -2592,7 +2917,7 @@ packages:
/@types/puppeteer/5.4.7:
resolution: {integrity: sha512-JdGWZZYL0vKapXF4oQTC5hLVNfOgdPrqeZ1BiQnGk5cB7HeE91EWUiTdVSdQPobRN8rIcdffjiOgCYJ/S8QrnQ==}
dependencies:
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
dev: true
/@types/qs/6.9.7:
@@ -2613,6 +2938,12 @@ packages:
resolution: {integrity: sha512-VtTUcUaJGiJtlBKYwwFIOSvrcnuKmpPGO+x56XijNZnaDpnzKh2VwoTw5hewrOMW2BgjoU+uFbVAvSCW2FpWmA==}
dev: true
+ /@types/resolve/1.17.1:
+ resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
+ dependencies:
+ '@types/node': 18.17.14
+ dev: true
+
/@types/semver/7.3.13:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
dev: true
@@ -2625,19 +2956,17 @@ packages:
resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
dependencies:
'@types/mime': 3.0.1
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
dev: true
- /@types/set-cookie-parser/2.4.2:
- resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
- dependencies:
- '@types/node': 16.18.12
- dev: false
-
/@types/stack-utils/2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
dev: true
+ /@types/statuses/2.0.1:
+ resolution: {integrity: sha512-vVRgv7WXbhIZzILgr58b4Ki2uqpN/dlVCUBWCMkPbMDlV1CrQrgCl5hnIoUlMrgymGcTmdfVqbs1yWj/IRIRtQ==}
+ dev: false
+
/@types/tough-cookie/4.0.2:
resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==}
dev: true
@@ -2646,6 +2975,12 @@ packages:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true
+ /@types/ws/7.4.7:
+ resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==}
+ dependencies:
+ '@types/node': 18.17.14
+ dev: true
+
/@types/yargs-parser/21.0.0:
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
dev: true
@@ -2781,15 +3116,99 @@ packages:
semver: 7.3.8
transitivePeerDependencies:
- supports-color
- - typescript
+ - typescript
+ dev: true
+
+ /@typescript-eslint/visitor-keys/5.52.0:
+ resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ '@typescript-eslint/types': 5.52.0
+ eslint-visitor-keys: 3.3.0
+ dev: true
+
+ /@web/config-loader/0.1.3:
+ resolution: {integrity: sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ semver: 7.5.4
+ dev: true
+
+ /@web/dev-server-core/0.4.1:
+ resolution: {integrity: sha512-KdYwejXZwIZvb6tYMCqU7yBiEOPfKLQ3V9ezqqEz8DA9V9R3oQWaowckvCpFB9IxxPfS/P8/59OkdzGKQjcIUw==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@types/koa': 2.13.8
+ '@types/ws': 7.4.7
+ '@web/parse5-utils': 1.3.1
+ chokidar: 3.4.1
+ clone: 2.1.2
+ es-module-lexer: 1.3.0
+ get-stream: 6.0.1
+ is-stream: 2.0.1
+ isbinaryfile: 5.0.0
+ koa: 2.14.2
+ koa-etag: 4.0.0
+ koa-send: 5.0.1
+ koa-static: 5.0.0
+ lru-cache: 6.0.0
+ mime-types: 2.1.35
+ parse5: 6.0.1
+ picomatch: 2.3.1
+ ws: 7.5.9
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
+ /@web/dev-server-rollup/0.4.1:
+ resolution: {integrity: sha512-Ebsv7Ovd9MufeH3exvikBJ7GmrZA5OmHnOgaiHcwMJ2eQBJA5/I+/CbRjsLX97ICj/ZwZG//p2ITRz8W3UfSqg==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@rollup/plugin-node-resolve': 13.3.0_rollup@2.79.1
+ '@web/dev-server-core': 0.4.1
+ nanocolors: 0.2.13
+ parse5: 6.0.1
+ rollup: 2.79.1
+ whatwg-url: 11.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
+ /@web/dev-server/0.1.38:
+ resolution: {integrity: sha512-WUq7Zi8KeJ5/UZmmpZ+kzUpUlFlMP/rcreJKYg9Lxiz998KYl4G5Rv24akX0piTuqXG7r6h+zszg8V/hdzjCoA==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/code-frame': 7.18.6
+ '@types/command-line-args': 5.2.1
+ '@web/config-loader': 0.1.3
+ '@web/dev-server-core': 0.4.1
+ '@web/dev-server-rollup': 0.4.1
+ camelcase: 6.3.0
+ command-line-args: 5.2.1
+ command-line-usage: 7.0.1
+ debounce: 1.2.1
+ deepmerge: 4.3.0
+ ip: 1.1.8
+ nanocolors: 0.2.13
+ open: 8.4.2
+ portfinder: 1.0.32
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
dev: true
- /@typescript-eslint/visitor-keys/5.52.0:
- resolution: {integrity: sha512-qMwpw6SU5VHCPr99y274xhbm+PRViK/NATY6qzt+Et7+mThGuFSl/ompj2/hrBlRP/kq+BFdgagnOSgw9TB0eA==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ /@web/parse5-utils/1.3.1:
+ resolution: {integrity: sha512-haCgDchZrAOB9EhBJ5XqiIjBMsS/exsM5Ru7sCSyNkXVEJWskyyKuKMFk66BonnIGMPpDtqDrTUfYEis5Zi3XA==}
+ engines: {node: '>=10.0.0'}
dependencies:
- '@typescript-eslint/types': 5.52.0
- eslint-visitor-keys: 3.3.0
+ '@types/parse5': 6.0.3
+ parse5: 6.0.1
dev: true
/@webassemblyjs/ast/1.11.1:
@@ -2898,11 +3317,6 @@ packages:
'@xtuc/long': 4.2.2
dev: true
- /@xmldom/xmldom/0.8.6:
- resolution: {integrity: sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==}
- engines: {node: '>=10.0.0'}
- dev: false
-
/@xtuc/ieee754/1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
dev: true
@@ -2911,12 +3325,6 @@ packages:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
dev: true
- /@zxing/text-encoding/0.9.0:
- resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
- requiresBuild: true
- dev: false
- optional: true
-
/JSONStream/1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true
@@ -3147,6 +3555,16 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /array-back/3.1.0:
+ resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /array-back/6.2.2:
+ resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==}
+ engines: {node: '>=12.17'}
+ dev: true
+
/array-flatten/1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: true
@@ -3240,6 +3658,7 @@ packages:
/available-typed-arrays/1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
+ dev: true
/babel-helper-evaluate-path/0.5.0:
resolution: {integrity: sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==}
@@ -3280,7 +3699,7 @@ packages:
'@types/babel__core': 7.20.0
babel-plugin-istanbul: 6.1.1
babel-preset-jest: 29.4.3_@babel+core@7.20.12
- chalk: 4.1.1
+ chalk: 4.1.2
graceful-fs: 4.2.10
slash: 3.0.0
transitivePeerDependencies:
@@ -3299,7 +3718,7 @@ packages:
loader-utils: 2.0.4
make-dir: 3.1.0
schema-utils: 2.7.1
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
dev: true
/babel-minify/0.5.2:
@@ -3654,6 +4073,12 @@ packages:
concat-map: 0.0.1
dev: true
+ /brace-expansion/2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
/braces/2.3.2_supports-color@6.1.0:
resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
engines: {node: '>=0.10.0'}
@@ -3709,16 +4134,28 @@ packages:
base64-js: 1.5.1
ieee754: 1.2.1
- /bundle-require/3.1.2_esbuild@0.14.54:
- resolution: {integrity: sha512-Of6l6JBAxiyQ5axFxUM6dYeP/W7X2Sozeo/4EYB9sJhL+dqL7TKjg+shwxp6jlu/6ZSERfsYtIpSJ1/x3XkAEA==}
+ /builtin-modules/3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /bundle-require/4.0.1_esbuild@0.17.19:
+ resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
- esbuild: '>=0.13'
+ esbuild: '>=0.17'
dependencies:
- esbuild: 0.14.54
+ esbuild: 0.17.19
load-tsconfig: 0.2.3
dev: true
+ /busboy/1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+ dependencies:
+ streamsearch: 1.1.0
+ dev: true
+
/bytes/3.0.0:
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
engines: {node: '>= 0.8'}
@@ -3749,6 +4186,14 @@ packages:
unset-value: 1.0.0
dev: true
+ /cache-content-type/1.0.1:
+ resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==}
+ engines: {node: '>= 6.0.0'}
+ dependencies:
+ mime-types: 2.1.35
+ ylru: 1.3.2
+ dev: true
+
/cachedir/2.3.0:
resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==}
engines: {node: '>=6'}
@@ -3759,6 +4204,7 @@ packages:
dependencies:
function-bind: 1.1.1
get-intrinsic: 1.2.0
+ dev: true
/callsites/3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
@@ -3798,6 +4244,13 @@ packages:
resolution: {integrity: sha512-XFHJY5dUgmpMV25UqaD4kVq2LsiaU5rS8fb0f17pCoXQiQslzmFgnfOxfvo1bTpTqf7dwG/N/05CnLCnOEKmzA==}
dev: true
+ /chalk-template/0.4.0:
+ resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
+ engines: {node: '>=12'}
+ dependencies:
+ chalk: 4.1.2
+ dev: true
+
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -3807,8 +4260,8 @@ packages:
supports-color: 5.5.0
dev: true
- /chalk/4.1.1:
- resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==}
+ /chalk/4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
@@ -3924,6 +4377,11 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
+ /clone/2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+ dev: true
+
/co/4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -3971,6 +4429,26 @@ packages:
delayed-stream: 1.0.0
dev: true
+ /command-line-args/5.2.1:
+ resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ array-back: 3.1.0
+ find-replace: 3.0.0
+ lodash.camelcase: 4.3.0
+ typical: 4.0.0
+ dev: true
+
+ /command-line-usage/7.0.1:
+ resolution: {integrity: sha512-NCyznE//MuTjwi3y84QVUGEOT+P5oto1e1Pk/jFPVdPPfsG03qpTIl3yw6etR+v73d0lXsoojRpvbru2sqePxQ==}
+ engines: {node: '>=12.20.0'}
+ dependencies:
+ array-back: 6.2.2
+ chalk-template: 0.4.0
+ table-layout: 3.0.2
+ typical: 7.1.1
+ dev: true
+
/commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
@@ -4148,10 +4626,18 @@ packages:
/cookie/0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
+ dev: true
/cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
+
+ /cookies/0.8.0:
+ resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ keygrip: 1.1.0
dev: true
/copy-descriptor/0.1.1:
@@ -4177,23 +4663,23 @@ packages:
vary: 1.1.2
dev: true
- /cosmiconfig-typescript-loader/2.0.2_plaptv2cv5vvro2su5yxvauvda:
+ /cosmiconfig-typescript-loader/2.0.2_f6calhiv3qbku3gmsoec3zvctu:
resolution: {integrity: sha512-KmE+bMjWMXJbkWCeY4FJX/npHuZPNr9XF9q9CIQ/bpFwi1qHfCmSiKarrCcRa0LO4fWjk93pVoeRtJAkTGcYNw==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
'@types/node': '*'
typescript: '>=3'
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
cosmiconfig: 7.1.0
- ts-node: 10.9.1_plaptv2cv5vvro2su5yxvauvda
+ ts-node: 10.9.1_f6calhiv3qbku3gmsoec3zvctu
typescript: 4.9.5
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
dev: true
- /cosmiconfig-typescript-loader/4.3.0_gg653o4cxizhrslchmhiad54ma:
+ /cosmiconfig-typescript-loader/4.3.0_bmci5xihqld5vqu3v4tyk3r5ra:
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
@@ -4202,9 +4688,9 @@ packages:
ts-node: '>=10'
typescript: '>=3'
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
cosmiconfig: 8.0.0
- ts-node: 10.9.1_plaptv2cv5vvro2su5yxvauvda
+ ts-node: 10.9.1_f6calhiv3qbku3gmsoec3zvctu
typescript: 4.9.5
dev: true
optional: true
@@ -4321,6 +4807,10 @@ packages:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dev: true
+ /debounce/1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+ dev: true
+
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -4344,6 +4834,17 @@ packages:
supports-color: 6.1.0
dev: true
+ /debug/3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.3
+ dev: true
+
/debug/3.2.7_supports-color@6.1.0:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -4366,6 +4867,7 @@ packages:
optional: true
dependencies:
ms: 2.1.2
+ dev: true
/debug/4.3.4_supports-color@6.1.0:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -4406,6 +4908,10 @@ packages:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
dev: true
+ /deep-equal/1.0.1:
+ resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
+ dev: true
+
/deep-equal/1.1.1:
resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==}
dependencies:
@@ -4444,6 +4950,11 @@ packages:
dependencies:
clone: 1.0.4
+ /define-lazy-prop/2.0.0:
+ resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
+ engines: {node: '>=8'}
+ dev: true
+
/define-properties/1.2.0:
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
engines: {node: '>= 0.4'}
@@ -4492,6 +5003,10 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
+ /delegates/1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+ dev: true
+
/depd/1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
engines: {node: '>= 0.6'}
@@ -4651,7 +5166,7 @@ packages:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.13
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.4.2
@@ -4745,6 +5260,10 @@ packages:
resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==}
dev: true
+ /es-module-lexer/1.3.0:
+ resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==}
+ dev: true
+
/es-set-tostringtag/2.0.1:
resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
engines: {node: '>= 0.4'}
@@ -4763,132 +5282,6 @@ packages:
is-symbol: 1.0.4
dev: true
- /esbuild-android-64/0.14.54:
- resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-android-arm64/0.14.54:
- resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-darwin-64/0.14.54:
- resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-darwin-arm64/0.14.54:
- resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-freebsd-64/0.14.54:
- resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-freebsd-arm64/0.14.54:
- resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-32/0.14.54:
- resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-64/0.14.54:
- resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-arm/0.14.54:
- resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-arm64/0.14.54:
- resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-mips64le/0.14.54:
- resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-ppc64le/0.14.54:
- resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-riscv64/0.14.54:
- resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-linux-s390x/0.14.54:
- resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
/esbuild-loader/2.21.0_webpack@5.75.0:
resolution: {integrity: sha512-k7ijTkCT43YBSZ6+fBCW1Gin7s46RrJ0VQaM8qA7lq7W+OLsGgtLyFV8470FzYi/4TeDexniTBTPTwZUnXXR5g==}
peerDependencies:
@@ -4899,93 +5292,10 @@ packages:
json5: 2.2.3
loader-utils: 2.0.4
tapable: 2.2.1
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
webpack-sources: 1.4.3
dev: true
- /esbuild-netbsd-64/0.14.54:
- resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-openbsd-64/0.14.54:
- resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-sunos-64/0.14.54:
- resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-32/0.14.54:
- resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-64/0.14.54:
- resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild-windows-arm64/0.14.54:
- resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /esbuild/0.14.54:
- resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- '@esbuild/linux-loong64': 0.14.54
- esbuild-android-64: 0.14.54
- esbuild-android-arm64: 0.14.54
- esbuild-darwin-64: 0.14.54
- esbuild-darwin-arm64: 0.14.54
- esbuild-freebsd-64: 0.14.54
- esbuild-freebsd-arm64: 0.14.54
- esbuild-linux-32: 0.14.54
- esbuild-linux-64: 0.14.54
- esbuild-linux-arm: 0.14.54
- esbuild-linux-arm64: 0.14.54
- esbuild-linux-mips64le: 0.14.54
- esbuild-linux-ppc64le: 0.14.54
- esbuild-linux-riscv64: 0.14.54
- esbuild-linux-s390x: 0.14.54
- esbuild-netbsd-64: 0.14.54
- esbuild-openbsd-64: 0.14.54
- esbuild-sunos-64: 0.14.54
- esbuild-windows-32: 0.14.54
- esbuild-windows-64: 0.14.54
- esbuild-windows-arm64: 0.14.54
- dev: true
-
/esbuild/0.16.17:
resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
engines: {node: '>=12'}
@@ -5016,6 +5326,36 @@ packages:
'@esbuild/win32-x64': 0.16.17
dev: true
+ /esbuild/0.17.19:
+ resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/android-arm': 0.17.19
+ '@esbuild/android-arm64': 0.17.19
+ '@esbuild/android-x64': 0.17.19
+ '@esbuild/darwin-arm64': 0.17.19
+ '@esbuild/darwin-x64': 0.17.19
+ '@esbuild/freebsd-arm64': 0.17.19
+ '@esbuild/freebsd-x64': 0.17.19
+ '@esbuild/linux-arm': 0.17.19
+ '@esbuild/linux-arm64': 0.17.19
+ '@esbuild/linux-ia32': 0.17.19
+ '@esbuild/linux-loong64': 0.17.19
+ '@esbuild/linux-mips64el': 0.17.19
+ '@esbuild/linux-ppc64': 0.17.19
+ '@esbuild/linux-riscv64': 0.17.19
+ '@esbuild/linux-s390x': 0.17.19
+ '@esbuild/linux-x64': 0.17.19
+ '@esbuild/netbsd-x64': 0.17.19
+ '@esbuild/openbsd-x64': 0.17.19
+ '@esbuild/sunos-x64': 0.17.19
+ '@esbuild/win32-arm64': 0.17.19
+ '@esbuild/win32-ia32': 0.17.19
+ '@esbuild/win32-x64': 0.17.19
+ dev: true
+
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
@@ -5126,7 +5466,7 @@ packages:
'@eslint/eslintrc': 0.4.3
'@humanwhocodes/config-array': 0.5.0
ajv: 6.12.6
- chalk: 4.1.1
+ chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.4
doctrine: 3.0.0
@@ -5205,6 +5545,10 @@ packages:
engines: {node: '>=4.0'}
dev: true
+ /estree-walker/1.0.1:
+ resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
+ dev: true
+
/esutils/2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -5222,6 +5566,7 @@ packages:
/events/3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
+ dev: true
/eventsource/2.0.2:
resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==}
@@ -5556,6 +5901,13 @@ packages:
merge: 2.1.1
dev: true
+ /find-replace/3.0.0:
+ resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ array-back: 3.1.0
+ dev: true
+
/find-root/1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
dev: true
@@ -5621,6 +5973,7 @@ packages:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
is-callable: 1.2.7
+ dev: true
/for-in/1.0.2:
resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
@@ -5645,6 +5998,14 @@ packages:
mime-types: 2.1.35
dev: true
+ /formdata-node/4.4.1:
+ resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
+ engines: {node: '>= 12.20'}
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 4.0.0-beta.3
+ dev: false
+
/forwarded/0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -5718,6 +6079,7 @@ packages:
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ dev: true
/function.prototype.name/1.1.5:
resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
@@ -5752,6 +6114,7 @@ packages:
function-bind: 1.1.1
has: 1.0.3
has-symbols: 1.0.3
+ dev: true
/get-package-type/0.1.0:
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
@@ -5838,6 +6201,16 @@ packages:
path-is-absolute: 1.0.1
dev: true
+ /glob/9.3.5:
+ resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ fs.realpath: 1.0.0
+ minimatch: 8.0.4
+ minipass: 4.2.8
+ path-scurry: 1.10.1
+ dev: true
+
/global-dirs/0.1.1:
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
engines: {node: '>=4'}
@@ -5911,6 +6284,7 @@ packages:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies:
get-intrinsic: 1.2.0
+ dev: true
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@@ -5961,12 +6335,14 @@ packages:
/has-symbols/1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
+ dev: true
/has-tostringtag/1.0.0:
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.3
+ dev: true
/has-value/0.3.1:
resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
@@ -6004,9 +6380,15 @@ packages:
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
+ dev: true
- /headers-polyfill/3.2.5:
- resolution: {integrity: sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==}
+ /headers-polyfill/3.2.3:
+ resolution: {integrity: sha512-oj6MO8sdFQ9gQQedSVdMGh96suxTNp91vPQu7C4qx/57FqYsA5TiNr92nhIZwVQq8zygn4nu3xS1aEqpakGqdw==}
+ dev: true
+
+ /headers-polyfill/4.0.1:
+ resolution: {integrity: sha512-CD3yq1U/nwyKZHRFIjESyveXz6Buk0ImoIwlEOEyNVNAqJLjNX3YkJkaH9Mg5rqU5JiVgTBq/6Z0jR1L6KS0Gg==}
+ dev: false
/homedir-polyfill/1.0.3:
resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
@@ -6050,6 +6432,14 @@ packages:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
dev: true
+ /http-assert/1.5.0:
+ resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ deep-equal: 1.0.1
+ http-errors: 1.8.1
+ dev: true
+
/http-deceiver/1.2.7:
resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==}
dev: true
@@ -6064,6 +6454,17 @@ packages:
statuses: 1.5.0
dev: true
+ /http-errors/1.8.1:
+ resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ depd: 1.1.2
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 1.5.0
+ toidentifier: 1.0.1
+ dev: true
+
/http-errors/2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
@@ -6219,7 +6620,7 @@ packages:
engines: {node: '>=12.0.0'}
dependencies:
ansi-escapes: 4.3.2
- chalk: 4.1.1
+ chalk: 4.1.2
cli-cursor: 3.1.0
cli-width: 3.0.0
external-editor: 3.1.0
@@ -6290,6 +6691,7 @@ packages:
dependencies:
call-bind: 1.0.2
has-tostringtag: 1.0.0
+ dev: true
/is-array-buffer/3.0.1:
resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==}
@@ -6327,9 +6729,17 @@ packages:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: true
+ /is-builtin-module/3.2.1:
+ resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
+ engines: {node: '>=6'}
+ dependencies:
+ builtin-modules: 3.3.0
+ dev: true
+
/is-callable/1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
+ dev: true
/is-core-module/2.11.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
@@ -6376,6 +6786,12 @@ packages:
kind-of: 6.0.3
dev: true
+ /is-docker/2.2.1:
+ resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dev: true
+
/is-extendable/0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@@ -6416,7 +6832,7 @@ packages:
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.0
- dev: false
+ dev: true
/is-glob/4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
@@ -6428,6 +6844,10 @@ packages:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
+ /is-module/1.0.0:
+ resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
+ dev: true
+
/is-negative-zero/2.0.2:
resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
engines: {node: '>= 0.4'}
@@ -6561,6 +6981,7 @@ packages:
for-each: 0.3.3
gopd: 1.0.1
has-tostringtag: 1.0.0
+ dev: true
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
@@ -6586,10 +7007,22 @@ packages:
engines: {node: '>=4'}
dev: true
+ /is-wsl/2.2.0:
+ resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+ engines: {node: '>=8'}
+ dependencies:
+ is-docker: 2.2.1
+ dev: true
+
/isarray/1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: true
+ /isbinaryfile/5.0.0:
+ resolution: {integrity: sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg==}
+ engines: {node: '>= 14.0.0'}
+ dev: true
+
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
@@ -6679,8 +7112,8 @@ packages:
'@jest/expect': 29.4.3
'@jest/test-result': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
co: 4.6.0
dedent: 0.7.0
is-generator-fn: 2.1.0
@@ -6698,7 +7131,7 @@ packages:
- supports-color
dev: true
- /jest-cli/29.4.3_nw6xvwuzmqp7vps7knduexkcvm:
+ /jest-cli/29.4.3_v5qag4bu7yd4vl7sd6rt2doplm:
resolution: {integrity: sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
@@ -6711,11 +7144,11 @@ packages:
'@jest/core': 29.4.3_ts-node@10.9.1
'@jest/test-result': 29.4.3
'@jest/types': 29.4.3
- chalk: 4.1.1
+ chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.10
import-local: 3.1.0
- jest-config: 29.4.3_nw6xvwuzmqp7vps7knduexkcvm
+ jest-config: 29.4.3_v5qag4bu7yd4vl7sd6rt2doplm
jest-util: 29.4.3
jest-validate: 29.4.3
prompts: 2.4.2
@@ -6726,47 +7159,7 @@ packages:
- ts-node
dev: true
- /jest-config/29.4.3_ghv2zugsw3zjg5rog5rhyka5ja:
- resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==}
- engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- peerDependencies:
- '@types/node': '*'
- ts-node: '>=9.0.0'
- peerDependenciesMeta:
- '@types/node':
- optional: true
- ts-node:
- optional: true
- dependencies:
- '@babel/core': 7.20.12
- '@jest/test-sequencer': 29.4.3
- '@jest/types': 29.4.3
- '@types/node': 16.18.12
- babel-jest: 29.4.3_@babel+core@7.20.12
- chalk: 4.1.1
- ci-info: 3.8.0
- deepmerge: 4.3.0
- glob: 7.2.3
- graceful-fs: 4.2.10
- jest-circus: 29.4.3
- jest-environment-node: 29.4.3
- jest-get-type: 29.4.3
- jest-regex-util: 29.4.3
- jest-resolve: 29.4.3
- jest-runner: 29.4.3
- jest-util: 29.4.3
- jest-validate: 29.4.3
- micromatch: 4.0.5
- parse-json: 5.2.0
- pretty-format: 29.4.3
- slash: 3.0.0
- strip-json-comments: 3.1.1
- ts-node: 10.9.1_oe3jy5ze54sjippw2sqzxdlwem
- transitivePeerDependencies:
- - supports-color
- dev: true
-
- /jest-config/29.4.3_nw6xvwuzmqp7vps7knduexkcvm:
+ /jest-config/29.4.3_v5qag4bu7yd4vl7sd6rt2doplm:
resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -6781,9 +7174,9 @@ packages:
'@babel/core': 7.20.12
'@jest/test-sequencer': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
babel-jest: 29.4.3_@babel+core@7.20.12
- chalk: 4.1.1
+ chalk: 4.1.2
ci-info: 3.8.0
deepmerge: 4.3.0
glob: 7.2.3
@@ -6801,7 +7194,7 @@ packages:
pretty-format: 29.4.3
slash: 3.0.0
strip-json-comments: 3.1.1
- ts-node: 10.9.1_oe3jy5ze54sjippw2sqzxdlwem
+ ts-node: 10.9.1_x2vjra2lmmhd46xm3mchw7ztui
transitivePeerDependencies:
- supports-color
dev: true
@@ -6810,7 +7203,7 @@ packages:
resolution: {integrity: sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
diff-sequences: 29.4.3
jest-get-type: 29.4.3
pretty-format: 29.4.3
@@ -6828,7 +7221,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.4.3
- chalk: 4.1.1
+ chalk: 4.1.2
jest-get-type: 29.4.3
jest-util: 29.4.3
pretty-format: 29.4.3
@@ -6847,7 +7240,7 @@ packages:
'@jest/fake-timers': 29.4.3
'@jest/types': 29.4.3
'@types/jsdom': 20.0.1
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-mock: 29.4.3
jest-util: 29.4.3
jsdom: 20.0.3
@@ -6864,7 +7257,7 @@ packages:
'@jest/environment': 29.4.3
'@jest/fake-timers': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-mock: 29.4.3
jest-util: 29.4.3
dev: true
@@ -6880,7 +7273,7 @@ packages:
dependencies:
'@jest/types': 29.4.3
'@types/graceful-fs': 4.1.6
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.10
@@ -6905,7 +7298,7 @@ packages:
resolution: {integrity: sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
jest-diff: 29.4.3
jest-get-type: 29.4.3
pretty-format: 29.4.3
@@ -6918,7 +7311,7 @@ packages:
'@babel/code-frame': 7.18.6
'@jest/types': 29.4.3
'@types/stack-utils': 2.0.1
- chalk: 4.1.1
+ chalk: 4.1.2
graceful-fs: 4.2.10
micromatch: 4.0.5
pretty-format: 29.4.3
@@ -6931,7 +7324,7 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.4.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-util: 29.4.3
dev: true
@@ -6966,7 +7359,7 @@ packages:
resolution: {integrity: sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
graceful-fs: 4.2.10
jest-haste-map: 29.4.3
jest-pnp-resolver: 1.2.3_jest-resolve@29.4.3
@@ -6986,8 +7379,8 @@ packages:
'@jest/test-result': 29.4.3
'@jest/transform': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.10
jest-docblock: 29.4.3
@@ -7017,8 +7410,8 @@ packages:
'@jest/test-result': 29.4.3
'@jest/transform': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
cjs-module-lexer: 1.2.2
collect-v8-coverage: 1.0.1
glob: 7.2.3
@@ -7052,7 +7445,7 @@ packages:
'@types/babel__traverse': 7.18.3
'@types/prettier': 2.7.2
babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.12
- chalk: 4.1.1
+ chalk: 4.1.2
expect: 29.4.3
graceful-fs: 4.2.10
jest-diff: 29.4.3
@@ -7073,8 +7466,8 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
'@jest/types': 29.4.3
- '@types/node': 16.18.12
- chalk: 4.1.1
+ '@types/node': 18.17.14
+ chalk: 4.1.2
ci-info: 3.8.0
graceful-fs: 4.2.10
picomatch: 2.3.1
@@ -7086,7 +7479,7 @@ packages:
dependencies:
'@jest/types': 29.4.3
camelcase: 6.3.0
- chalk: 4.1.1
+ chalk: 4.1.2
jest-get-type: 29.4.3
leven: 3.1.0
pretty-format: 29.4.3
@@ -7098,9 +7491,9 @@ packages:
dependencies:
'@jest/test-result': 29.4.3
'@jest/types': 29.4.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
ansi-escapes: 4.3.2
- chalk: 4.1.1
+ chalk: 4.1.2
emittery: 0.13.1
jest-util: 29.4.3
string-length: 4.0.2
@@ -7110,7 +7503,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
@@ -7119,13 +7512,13 @@ packages:
resolution: {integrity: sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
dependencies:
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
jest-util: 29.4.3
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
- /jest/29.4.3_nw6xvwuzmqp7vps7knduexkcvm:
+ /jest/29.4.3_v5qag4bu7yd4vl7sd6rt2doplm:
resolution: {integrity: sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
@@ -7138,7 +7531,7 @@ packages:
'@jest/core': 29.4.3_ts-node@10.9.1
'@jest/types': 29.4.3
import-local: 3.1.0
- jest-cli: 29.4.3_nw6xvwuzmqp7vps7knduexkcvm
+ jest-cli: 29.4.3_v5qag4bu7yd4vl7sd6rt2doplm
transitivePeerDependencies:
- '@types/node'
- supports-color
@@ -7272,6 +7665,13 @@ packages:
engines: {'0': node >= 0.2.0}
dev: true
+ /keygrip/1.1.0:
+ resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ tsscmp: 1.0.6
+ dev: true
+
/killable/1.0.1:
resolution: {integrity: sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==}
dev: true
@@ -7305,6 +7705,76 @@ packages:
engines: {node: '>=6'}
dev: true
+ /koa-compose/4.1.0:
+ resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==}
+ dev: true
+
+ /koa-convert/2.0.0:
+ resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==}
+ engines: {node: '>= 10'}
+ dependencies:
+ co: 4.6.0
+ koa-compose: 4.1.0
+ dev: true
+
+ /koa-etag/4.0.0:
+ resolution: {integrity: sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==}
+ dependencies:
+ etag: 1.8.1
+ dev: true
+
+ /koa-send/5.0.1:
+ resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
+ engines: {node: '>= 8'}
+ dependencies:
+ debug: 4.3.4
+ http-errors: 1.8.1
+ resolve-path: 1.4.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /koa-static/5.0.0:
+ resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
+ engines: {node: '>= 7.6.0'}
+ dependencies:
+ debug: 3.2.7
+ koa-send: 5.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /koa/2.14.2:
+ resolution: {integrity: sha512-VFI2bpJaodz6P7x2uyLiX6RLYpZmOJqNmoCst/Yyd7hQlszyPwG/I9CQJ63nOtKSxpt5M7NH67V6nJL2BwCl7g==}
+ engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4}
+ dependencies:
+ accepts: 1.3.8
+ cache-content-type: 1.0.1
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookies: 0.8.0
+ debug: 4.3.4
+ delegates: 1.0.0
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ fresh: 0.5.2
+ http-assert: 1.5.0
+ http-errors: 1.6.3
+ is-generator-function: 1.0.10
+ koa-compose: 4.1.0
+ koa-convert: 2.0.0
+ on-finished: 2.4.1
+ only: 0.0.2
+ parseurl: 1.3.3
+ statuses: 1.5.0
+ type-is: 1.6.18
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/leven/2.1.0:
resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==}
engines: {node: '>=0.10.0'}
@@ -7423,6 +7893,14 @@ packages:
p-locate: 5.0.0
dev: true
+ /lodash.assignwith/4.2.0:
+ resolution: {integrity: sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==}
+ dev: true
+
+ /lodash.camelcase/4.3.0:
+ resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
+ dev: true
+
/lodash.capitalize/4.2.1:
resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==}
dev: true
@@ -7480,7 +7958,7 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
dependencies:
- chalk: 4.1.1
+ chalk: 4.1.2
is-unicode-supported: 0.1.0
/log-update/4.0.0:
@@ -7503,6 +7981,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /lru-cache/10.0.1:
+ resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
+ engines: {node: 14 || >=16.14}
+ dev: true
+
/lru-cache/5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
@@ -7695,6 +8178,13 @@ packages:
brace-expansion: 1.1.11
dev: true
+ /minimatch/8.0.4:
+ resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
@@ -7712,6 +8202,16 @@ packages:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
+ /minipass/4.2.8:
+ resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /minipass/7.0.3:
+ resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dev: true
+
/mixin-deep/1.3.2:
resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
engines: {node: '>=0.10.0'}
@@ -7738,6 +8238,7 @@ packages:
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+ dev: true
/ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -7771,6 +8272,10 @@ packages:
thenify-all: 1.6.0
dev: true
+ /nanocolors/0.2.13:
+ resolution: {integrity: sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==}
+ dev: true
+
/nanomatch/1.2.13_supports-color@6.1.0:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'}
@@ -7811,6 +8316,11 @@ packages:
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
dev: true
+ /node-domexception/1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ dev: false
+
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
@@ -7863,7 +8373,7 @@ packages:
dependencies:
hosted-git-info: 4.1.0
is-core-module: 2.11.0
- semver: 7.3.8
+ semver: 7.5.4
validate-npm-package-license: 3.0.4
dev: true
@@ -8000,6 +8510,19 @@ packages:
mimic-fn: 4.0.0
dev: true
+ /only/0.0.2:
+ resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
+ dev: true
+
+ /open/8.4.2:
+ resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ define-lazy-prop: 2.0.0
+ is-docker: 2.2.1
+ is-wsl: 2.2.0
+ dev: true
+
/opn/5.5.0:
resolution: {integrity: sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==}
engines: {node: '>=4'}
@@ -8036,7 +8559,7 @@ packages:
engines: {node: '>=10'}
dependencies:
bl: 4.1.0
- chalk: 4.1.1
+ chalk: 4.1.2
cli-cursor: 3.1.0
cli-spinners: 2.7.0
is-interactive: 1.0.0
@@ -8116,22 +8639,22 @@ packages:
engines: {node: '>=6'}
dev: true
- /page-with/0.5.1_@swc+core@1.3.35:
- resolution: {integrity: sha512-830oKHY2kfhPuc3vsaaeqrwCH1FkOhopFQ0JmSPObGI4yVZQLYYpRW4frNP4hYyuxVgC0zbwMqr+Z4ESgDX5Sw==}
+ /page-with/0.6.1_mtsvlg4x4u5udzh2pohivgt4x4:
+ resolution: {integrity: sha512-5J58fSpc8CKonUWCPsh8b2LctFrNSOpXQ8O3tB+/iJvixOQf1qHp4+cDLiIVsl/WiuheXdZTzMcuR0KLQMaWcg==}
dependencies:
- '@open-draft/until': 1.0.3
+ '@open-draft/until': 2.1.0
'@types/debug': 4.1.7
'@types/express': 4.17.17
'@types/mustache': 4.2.2
'@types/uuid': 8.3.4
debug: 4.3.4
express: 4.18.2
- headers-polyfill: 3.2.5
+ headers-polyfill: 3.2.3
memfs: 3.4.13
mustache: 4.2.0
playwright: 1.30.0
uuid: 8.3.2
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
webpack-merge: 5.8.0
transitivePeerDependencies:
- '@swc/core'
@@ -8163,6 +8686,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /parse5/6.0.1:
+ resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
+ dev: true
+
/parse5/7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
@@ -8217,6 +8744,14 @@ packages:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
+ /path-scurry/1.10.1:
+ resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ lru-cache: 10.0.1
+ minipass: 7.0.3
+ dev: true
+
/path-to-regexp/0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
dev: true
@@ -8347,6 +8882,17 @@ packages:
playwright-core: 1.30.0
dev: true
+ /portfinder/1.0.32:
+ resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
+ engines: {node: '>= 0.12.0'}
+ dependencies:
+ async: 2.6.4
+ debug: 3.2.7
+ mkdirp: 0.5.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/portfinder/1.0.32_supports-color@6.1.0:
resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
engines: {node: '>= 0.12.0'}
@@ -8376,7 +8922,7 @@ packages:
optional: true
dependencies:
lilconfig: 2.0.6
- ts-node: 10.9.1_oe3jy5ze54sjippw2sqzxdlwem
+ ts-node: 10.9.1_x2vjra2lmmhd46xm3mchw7ztui
yaml: 1.10.2
dev: true
@@ -8733,6 +9279,14 @@ packages:
global-dirs: 0.1.1
dev: true
+ /resolve-path/1.4.0:
+ resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ http-errors: 1.6.3
+ path-is-absolute: 1.0.1
+ dev: true
+
/resolve-url/0.2.1:
resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
deprecated: https://github.com/lydell/resolve-url#deprecated
@@ -8800,6 +9354,14 @@ packages:
fsevents: 2.3.2
dev: true
+ /rollup/3.29.0:
+ resolution: {integrity: sha512-nszM8DINnx1vSS+TpbWKMkxem0CDWk3cSit/WWCBVs9/JZ1I/XLwOsiUglYuYReaeWWSsW9kge5zE5NZtf/a4w==}
+ engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
/run-async/2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -9017,10 +9579,6 @@ packages:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
- /set-cookie-parser/2.5.1:
- resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==}
- dev: false
-
/set-value/2.0.1:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
engines: {node: '>=0.10.0'}
@@ -9370,7 +9928,6 @@ packages:
/statuses/2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
- dev: true
/stream-combiner2/1.1.1:
resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==}
@@ -9379,18 +9936,22 @@ packages:
readable-stream: 2.3.7
dev: true
+ /stream-read-all/3.0.1:
+ resolution: {integrity: sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A==}
+ engines: {node: '>=10'}
+ dev: true
+
/stream-shift/1.0.1:
resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==}
dev: true
- /strict-event-emitter/0.2.8:
- resolution: {integrity: sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==}
- dependencies:
- events: 3.3.0
- dev: false
+ /streamsearch/1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+ dev: true
- /strict-event-emitter/0.4.6:
- resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==}
+ /strict-event-emitter/0.5.0:
+ resolution: {integrity: sha512-sqnMpVJLSB3daNO6FcvsEk4Mq5IJeAwDeH80DP1S8+pgxrF6yZnE1+VeapesGled7nEcIkz1Ax87HzaIy+02kA==}
dev: false
/string-argv/0.3.1:
@@ -9572,6 +10133,20 @@ packages:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
+ /table-layout/3.0.2:
+ resolution: {integrity: sha512-rpyNZYRw+/C+dYkcQ3Pr+rLxW4CfHpXjPDnG7lYhdRoUcZTUt+KEsX+94RGp/aVp/MQU35JCITv2T/beY4m+hw==}
+ engines: {node: '>=12.17'}
+ hasBin: true
+ dependencies:
+ '@75lb/deep-merge': 1.1.1
+ array-back: 6.2.2
+ command-line-args: 5.2.1
+ command-line-usage: 7.0.1
+ stream-read-all: 3.0.1
+ typical: 7.1.1
+ wordwrapjs: 5.1.0
+ dev: true
+
/table/6.8.1:
resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==}
engines: {node: '>=10.0.0'}
@@ -9588,7 +10163,7 @@ packages:
engines: {node: '>=6'}
dev: true
- /terser-webpack-plugin/5.3.6_gwpkmym7uf5m6snr3dgsgj5rrq:
+ /terser-webpack-plugin/5.3.6_46rrhsymls7zkxn67al7zvwy5y:
resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==}
engines: {node: '>= 10.13.0'}
peerDependencies:
@@ -9606,11 +10181,12 @@ packages:
dependencies:
'@jridgewell/trace-mapping': 0.3.17
'@swc/core': 1.3.35
+ esbuild: 0.17.19
jest-worker: 27.5.1
schema-utils: 3.1.1
serialize-javascript: 6.0.1
terser: 5.16.4
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
dev: true
/terser/5.16.4:
@@ -9781,7 +10357,7 @@ packages:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
- /ts-node/10.9.1_oe3jy5ze54sjippw2sqzxdlwem:
+ /ts-node/10.9.1_f6calhiv3qbku3gmsoec3zvctu:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@@ -9801,19 +10377,19 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
- '@types/node': 14.18.36
+ '@types/node': 18.17.14
acorn: 8.8.2
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
- typescript: 5.0.2
+ typescript: 4.9.5
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
- /ts-node/10.9.1_plaptv2cv5vvro2su5yxvauvda:
+ /ts-node/10.9.1_x2vjra2lmmhd46xm3mchw7ztui:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@@ -9833,14 +10409,14 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
- '@types/node': 16.18.12
+ '@types/node': 18.17.14
acorn: 8.8.2
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
- typescript: 4.9.5
+ typescript: 5.0.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
@@ -9852,13 +10428,19 @@ packages:
/tslib/2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
- /tsup/5.12.9_4s7jzcjqpdttwnwh3e3glkuq6y:
- resolution: {integrity: sha512-dUpuouWZYe40lLufo64qEhDpIDsWhRbr2expv5dHEMjwqeKJS2aXA/FPqs1dxO4T6mBojo7rvo3jP9NNzaKyDg==}
+ /tsscmp/1.0.6:
+ resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
+ engines: {node: '>=0.6.x'}
+ dev: true
+
+ /tsup/6.7.0_4s7jzcjqpdttwnwh3e3glkuq6y:
+ resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==}
+ engines: {node: '>=14.18'}
hasBin: true
peerDependencies:
'@swc/core': ^1
postcss: ^8.4.12
- typescript: ^4.1.0
+ typescript: '>=4.1.0'
peerDependenciesMeta:
'@swc/core':
optional: true
@@ -9868,17 +10450,17 @@ packages:
optional: true
dependencies:
'@swc/core': 1.3.35
- bundle-require: 3.1.2_esbuild@0.14.54
+ bundle-require: 4.0.1_esbuild@0.17.19
cac: 6.7.14
chokidar: 3.4.1
debug: 4.3.4
- esbuild: 0.14.54
+ esbuild: 0.17.19
execa: 5.1.1
globby: 11.1.0
joycon: 3.1.1
postcss-load-config: 3.1.4_ts-node@10.9.1
resolve-from: 5.0.0
- rollup: 2.79.1
+ rollup: 3.29.0
source-map: 0.8.0-beta.0
sucrase: 3.29.0
tree-kill: 1.2.2
@@ -9974,6 +10556,16 @@ packages:
hasBin: true
dev: true
+ /typical/4.0.0:
+ resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /typical/7.1.1:
+ resolution: {integrity: sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==}
+ engines: {node: '>=12.17'}
+ dev: true
+
/unbox-primitive/1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@@ -9983,6 +10575,13 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
+ /undici/5.23.0:
+ resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==}
+ engines: {node: '>=14.0'}
+ dependencies:
+ busboy: 1.6.0
+ dev: true
+
/unicode-canonical-property-names-ecmascript/2.0.0:
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
engines: {node: '>=4'}
@@ -10074,7 +10673,7 @@ packages:
loader-utils: 2.0.4
mime-types: 2.1.35
schema-utils: 3.1.1
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
dev: true
/url-parse/1.5.10:
@@ -10109,16 +10708,6 @@ packages:
object.getownpropertydescriptors: 2.1.5
dev: true
- /util/0.12.5:
- resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
- dependencies:
- inherits: 2.0.4
- is-arguments: 1.1.1
- is-generator-function: 1.0.10
- is-typed-array: 1.1.10
- which-typed-array: 1.1.9
- dev: false
-
/utils-merge/1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
@@ -10196,12 +10785,9 @@ packages:
dependencies:
defaults: 1.0.4
- /web-encoding/1.1.5:
- resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==}
- dependencies:
- util: 0.12.5
- optionalDependencies:
- '@zxing/text-encoding': 0.9.0
+ /web-streams-polyfill/4.0.0-beta.3:
+ resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
+ engines: {node: '>= 14'}
dev: false
/webidl-conversions/3.0.1:
@@ -10226,7 +10812,7 @@ packages:
mime: 2.6.0
mkdirp: 0.5.6
range-parser: 1.2.1
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
webpack-log: 2.0.0
dev: true
@@ -10270,7 +10856,7 @@ packages:
strip-ansi: 3.0.1
supports-color: 6.1.0
url: 0.11.0
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
webpack-dev-middleware: 3.7.3_webpack@5.75.0
webpack-log: 2.0.0
ws: 6.2.2
@@ -10280,7 +10866,7 @@ packages:
- utf-8-validate
dev: true
- /webpack-http-server/0.5.0_@swc+core@1.3.35:
+ /webpack-http-server/0.5.0_mtsvlg4x4u5udzh2pohivgt4x4:
resolution: {integrity: sha512-kyewxAnzmDuZxe09fn/Bb0PeEnaDxHChYKFVsMy4oeBUs9Cyv2j1uEgzQJ7ljPFexLU8ongUS4i4O+e22CeBZQ==}
dependencies:
'@types/express': 4.17.17
@@ -10289,7 +10875,7 @@ packages:
memfs: 3.4.13
mustache: 4.2.0
outvariant: 1.4.0
- webpack: 5.75.0_@swc+core@1.3.35
+ webpack: 5.75.0_mtsvlg4x4u5udzh2pohivgt4x4
transitivePeerDependencies:
- '@swc/core'
- esbuild
@@ -10326,7 +10912,7 @@ packages:
engines: {node: '>=10.13.0'}
dev: true
- /webpack/5.75.0_@swc+core@1.3.35:
+ /webpack/5.75.0_mtsvlg4x4u5udzh2pohivgt4x4:
resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==}
engines: {node: '>=10.13.0'}
hasBin: true
@@ -10357,7 +10943,7 @@ packages:
neo-async: 2.6.2
schema-utils: 3.1.1
tapable: 2.2.1
- terser-webpack-plugin: 5.3.6_gwpkmym7uf5m6snr3dgsgj5rrq
+ terser-webpack-plugin: 5.3.6_46rrhsymls7zkxn67al7zvwy5y
watchpack: 2.4.0
webpack-sources: 3.2.3
transitivePeerDependencies:
@@ -10438,6 +11024,7 @@ packages:
gopd: 1.0.1
has-tostringtag: 1.0.0
is-typed-array: 1.1.10
+ dev: true
/which/1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
@@ -10463,6 +11050,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /wordwrapjs/5.1.0:
+ resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==}
+ engines: {node: '>=12.17'}
+ dev: true
+
/wrap-ansi/5.1.0:
resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
engines: {node: '>=6'}
@@ -10515,6 +11107,19 @@ packages:
async-limiter: 1.0.1
dev: true
+ /ws/7.5.9:
+ resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
+ engines: {node: '>=8.3.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: true
+
/ws/8.11.0:
resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
engines: {node: '>=10.0.0'}
@@ -10630,6 +11235,11 @@ packages:
yargs-parser: 21.1.1
dev: true
+ /ylru/1.3.2:
+ resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
diff --git a/src/browser/index.ts b/src/browser/index.ts
new file mode 100644
index 000000000..0eafbe76f
--- /dev/null
+++ b/src/browser/index.ts
@@ -0,0 +1,3 @@
+export { setupWorker } from './setupWorker/setupWorker'
+export type { SetupWorker, StartOptions } from './setupWorker/glossary'
+export { SetupWorkerApi } from './setupWorker/setupWorker'
diff --git a/src/setupWorker/glossary.ts b/src/browser/setupWorker/glossary.ts
similarity index 77%
rename from src/setupWorker/glossary.ts
rename to src/browser/setupWorker/glossary.ts
index d0370cff9..23017c6ec 100644
--- a/src/setupWorker/glossary.ts
+++ b/src/browser/setupWorker/glossary.ts
@@ -1,20 +1,17 @@
-import { FlatHeadersObject } from 'headers-polyfill'
import { Emitter } from 'strict-event-emitter'
import {
LifeCycleEventEmitter,
LifeCycleEventsMap,
SharedOptions,
-} from '../sharedOptions'
+} from '~/core/sharedOptions'
import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
import {
- DefaultBodyType,
RequestHandler,
RequestHandlerDefaultInfo,
-} from '../handlers/RequestHandler'
+} from '~/core/handlers/RequestHandler'
import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors'
-import { Path } from '../utils/matching/matchRequestUrl'
-import { RequiredDeep } from '../typeUtils'
-import { MockedRequest } from '../utils/request/MockedRequest'
+import { Path } from '~/core/utils/matching/matchRequestUrl'
+import { RequiredDeep } from '~/core/typeUtils'
export type ResolvedPath = Path | URL
@@ -37,15 +34,11 @@ type RequestWithoutMethods = Omit<
*/
export interface ServiceWorkerIncomingRequest extends RequestWithoutMethods {
/**
- * Unique UUID of the request generated once the request is
- * captured by the "fetch" event in the Service Worker.
+ * Unique ID of the request generated once the request is
+ * intercepted by the "fetch" event in the Service Worker.
*/
id: string
-
- /**
- * Text response body.
- */
- body?: string
+ body?: ArrayBuffer | null
}
export type ServiceWorkerIncomingResponse = Pick<
@@ -53,6 +46,7 @@ export type ServiceWorkerIncomingResponse = Pick<
'type' | 'ok' | 'status' | 'statusText' | 'body' | 'headers' | 'redirected'
> & {
requestId: string
+ isMockedResponse: boolean
}
/**
@@ -77,13 +71,17 @@ export type ServiceWorkerOutgoingEventTypes =
| 'KEEPALIVE_REQUEST'
| 'CLIENT_CLOSED'
+export interface StringifiedResponse extends ResponseInit {
+ body: string | ArrayBuffer | ReadableStream | null
+}
+
/**
* Map of the events that can be sent to the Service Worker
* only as a part of a single `fetch` event handler.
*/
export interface ServiceWorkerFetchEventMap {
- MOCK_RESPONSE(payload: SerializedResponse): void
- MOCK_RESPONSE_START(payload: SerializedResponse): void
+ MOCK_RESPONSE(payload: StringifiedResponse): void
+ MOCK_RESPONSE_START(payload: StringifiedResponse): void
MOCK_NOT_FOUND(): void
NETWORK_ERROR(payload: { name: string; message: string }): void
@@ -95,15 +93,17 @@ export interface ServiceWorkerBroadcastChannelMessageMap {
MOCK_RESPONSE_END(): void
}
-export type WorkerLifecycleEventsMap = LifeCycleEventsMap
+export interface StrictEventListener {
+ (event: EventType): void
+}
export interface SetupWorkerInternalContext {
isMockingEnabled: boolean
startOptions: RequiredDeep
worker: ServiceWorker | null
registration: ServiceWorkerRegistration | null
- requestHandlers: RequestHandler[]
- emitter: Emitter
+ requestHandlers: Array
+ emitter: Emitter
keepAliveInterval?: number
workerChannel: {
/**
@@ -128,10 +128,10 @@ export interface SetupWorkerInternalContext {
* Adds an event listener on the given target.
* Returns a clean-up function that removes that listener.
*/
- addListener(
+ addListener(
target: EventTarget,
eventType: string,
- listener: (event: EventType) => void,
+ callback: StrictEventListener,
): () => void
/**
* Removes all currently attached listeners.
@@ -146,7 +146,10 @@ export interface SetupWorkerInternalContext {
ServiceWorkerMessage
>
}
- useFallbackMode: boolean
+ supports: {
+ serviceWorkerApi: boolean
+ readableStreamTransfer: boolean
+ }
fallbackInterceptor?: Interceptor
}
@@ -174,7 +177,7 @@ export interface StartOptions extends SharedOptions {
}
/**
- * Disables the logging of captured requests
+ * Disables the logging of the intercepted requests
* into browser's console.
* @default false
*/
@@ -194,14 +197,6 @@ export interface StartOptions extends SharedOptions {
findWorker?: FindWorker
}
-export interface SerializedResponse {
- status: number
- statusText: string
- headers: FlatHeadersObject
- body: BodyType
- delay?: number
-}
-
export type StartReturnType = Promise
export type StartHandler = (
options: RequiredDeep,
@@ -212,54 +207,53 @@ export type StopHandler = () => void
export interface SetupWorker {
/**
* Registers and activates the mock Service Worker.
- * @see {@link https://mswjs.io/docs/api/setup-worker/start `worker.start()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/start `worker.start()` API reference}
*/
start: (options?: StartOptions) => StartReturnType
/**
* Stops requests interception for the current client.
- * @see {@link https://mswjs.io/docs/api/setup-worker/stop `worker.stop()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/stop `worker.stop()` API reference}
*/
stop: StopHandler
/**
* Prepends given request handlers to the list of existing handlers.
* @param {RequestHandler[]} handlers List of runtime request handlers.
- * @see {@link https://mswjs.io/docs/api/setup-worker/use `worker.use()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/use `worker.use()` API reference}
*/
use: (...handlers: RequestHandler[]) => void
/**
* Marks all request handlers that respond using `res.once()` as unused.
- * @see {@link https://mswjs.io/docs/api/setup-worker/restore-handlers `worker.restoreHandlers()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/restore-handlers `worker.restoreHandlers()` API reference}
*/
restoreHandlers: () => void
/**
* Resets request handlers to the initial list given to the `setupWorker` call, or to the explicit next request handlers list, if given.
* @param {RequestHandler[]} nextHandlers List of the new initial request handlers.
- * @see {@link https://mswjs.io/docs/api/setup-worker/reset-handlers `worker.resetHandlers()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/reset-handlers `worker.resetHandlers()` API reference}
*/
resetHandlers: (...nextHandlers: RequestHandler[]) => void
/**
* Returns a readonly list of currently active request handlers.
- * @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()` API reference}
*/
- listHandlers(): ReadonlyArray<
- RequestHandler<
- RequestHandlerDefaultInfo,
- MockedRequest,
- any,
- MockedRequest
- >
- >
+ listHandlers(): ReadonlyArray>
/**
- * Lists all active request handlers.
- * @see {@link https://mswjs.io/docs/api/setup-worker/print-handlers `worker.printHandlers()`}
+ * Life-cycle events.
+ * Life-cycle events allow you to subscribe to the internal library events occurring during the request/response handling.
+ *
+ * @see {@link https://mswjs.io/docs/api/life-cycle-events Life-cycle Events API reference}
*/
- printHandlers: () => void
-
- events: LifeCycleEventEmitter
+ events: LifeCycleEventEmitter
}
diff --git a/src/setupWorker/setupWorker.node.test.ts b/src/browser/setupWorker/setupWorker.node.test.ts
similarity index 100%
rename from src/setupWorker/setupWorker.node.test.ts
rename to src/browser/setupWorker/setupWorker.node.test.ts
diff --git a/src/setupWorker/setupWorker.ts b/src/browser/setupWorker/setupWorker.ts
similarity index 64%
rename from src/setupWorker/setupWorker.ts
rename to src/browser/setupWorker/setupWorker.ts
index 3246ccbb0..b89376641 100644
--- a/src/setupWorker/setupWorker.ts
+++ b/src/browser/setupWorker/setupWorker.ts
@@ -3,7 +3,6 @@ import { isNodeProcess } from 'is-node-process'
import {
SetupWorkerInternalContext,
ServiceWorkerIncomingEventsMap,
- WorkerLifecycleEventsMap,
StartReturnType,
StopHandler,
StartHandler,
@@ -12,23 +11,25 @@ import {
import { createStartHandler } from './start/createStartHandler'
import { createStop } from './stop/createStop'
import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
-import { RequestHandler } from '../handlers/RequestHandler'
+import { RequestHandler } from '~/core/handlers/RequestHandler'
import { DEFAULT_START_OPTIONS } from './start/utils/prepareStartHandler'
import { createFallbackStart } from './start/createFallbackStart'
import { createFallbackStop } from './stop/createFallbackStop'
-import { devUtils } from '../utils/internal/devUtils'
-import { SetupApi } from '../SetupApi'
-import { mergeRight } from '../utils/internal/mergeRight'
+import { devUtils } from '~/core/utils/internal/devUtils'
+import { SetupApi } from '~/core/SetupApi'
+import { mergeRight } from '~/core/utils/internal/mergeRight'
+import { LifeCycleEventsMap } from '~/core/sharedOptions'
import { SetupWorker } from './glossary'
+import { supportsReadableStreamTransfer } from '../utils/supportsReadableStreamTransfer'
interface Listener {
target: EventTarget
eventType: string
- callback: EventListener
+ callback: EventListenerOrEventListenerObject
}
export class SetupWorkerApi
- extends SetupApi
+ extends SetupApi
implements SetupWorker
{
private context: SetupWorkerInternalContext
@@ -51,7 +52,7 @@ export class SetupWorkerApi
}
private createWorkerContext(): SetupWorkerInternalContext {
- const context = {
+ const context: SetupWorkerInternalContext = {
// Mocking is not considered enabled until the worker
// signals back the successful activation event.
isMockingEnabled: false,
@@ -61,55 +62,41 @@ export class SetupWorkerApi
requestHandlers: this.currentHandlers,
emitter: this.emitter,
workerChannel: {
- on: (
- eventType: EventType,
- callback: (
- event: MessageEvent,
- message: ServiceWorkerMessage<
- EventType,
- ServiceWorkerIncomingEventsMap[EventType]
- >,
- ) => void,
- ) => {
- this.context.events.addListener(
- navigator.serviceWorker,
- 'message',
- (event: MessageEvent) => {
- // Avoid messages broadcasted from unrelated workers.
- if (event.source !== this.context.worker) {
- return
- }
+ on: (eventType, callback) => {
+ this.context.events.addListener<
+ MessageEvent>
+ >(navigator.serviceWorker, 'message', (event) => {
+ // Avoid messages broadcasted from unrelated workers.
+ if (event.source !== this.context.worker) {
+ return
+ }
- const message = event.data as ServiceWorkerMessage<
- typeof eventType,
- any
- >
+ const message = event.data
- if (!message) {
- return
- }
+ if (!message) {
+ return
+ }
- if (message.type === eventType) {
- callback(event, message)
- }
- },
- )
+ if (message.type === eventType) {
+ callback(event, message)
+ }
+ })
},
- send: (type: any) => {
+ send: (type) => {
this.context.worker?.postMessage(type)
},
},
events: {
- addListener: (
- target: EventTarget,
- eventType: string,
- callback: EventListener,
- ) => {
- target.addEventListener(eventType, callback)
- this.listeners.push({ eventType, target, callback })
+ addListener: (target, eventType, callback) => {
+ target.addEventListener(eventType, callback as EventListener)
+ this.listeners.push({
+ eventType,
+ target,
+ callback: callback as EventListener,
+ })
return () => {
- target.removeEventListener(eventType, callback)
+ target.removeEventListener(eventType, callback as EventListener)
}
},
removeAllListeners: () => {
@@ -118,9 +105,7 @@ export class SetupWorkerApi
}
this.listeners = []
},
- once: (
- eventType: EventType,
- ) => {
+ once: (eventType) => {
const bindings: Array<() => void> = []
return new Promise<
@@ -158,8 +143,11 @@ export class SetupWorkerApi
})
},
},
- useFallbackMode:
- !('serviceWorker' in navigator) || location.protocol === 'file:',
+ supports: {
+ serviceWorkerApi:
+ !('serviceWorker' in navigator) || location.protocol === 'file:',
+ readableStreamTransfer: supportsReadableStreamTransfer(),
+ },
}
/**
@@ -172,11 +160,11 @@ export class SetupWorkerApi
},
})
- this.startHandler = context.useFallbackMode
+ this.startHandler = context.supports.serviceWorkerApi
? createFallbackStart(context)
: createStartHandler(context)
- this.stopHandler = context.useFallbackMode
+ this.stopHandler = context.supports.serviceWorkerApi
? createFallbackStop(context)
: createStop(context)
@@ -192,26 +180,6 @@ export class SetupWorkerApi
return await this.startHandler(this.context.startOptions, options)
}
- public printHandlers(): void {
- const handlers = this.listHandlers()
-
- handlers.forEach((handler) => {
- const { header, callFrame } = handler.info
- const pragma = handler.info.hasOwnProperty('operationType')
- ? '[graphql]'
- : '[rest]'
-
- console.groupCollapsed(`${pragma} ${header}`)
-
- if (callFrame) {
- console.log(`Declaration: ${callFrame}`)
- }
-
- console.log('Handler:', handler)
- console.groupEnd()
- })
- }
-
public stop(): void {
super.dispose()
this.context.events.removeAllListeners()
@@ -223,7 +191,8 @@ export class SetupWorkerApi
/**
* Sets up a requests interception in the browser with the given request handlers.
* @param {RequestHandler[]} handlers List of request handlers.
- * @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker`}
+ *
+ * @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker()` API reference}
*/
export function setupWorker(...handlers: Array): SetupWorker {
return new SetupWorkerApi(...handlers)
diff --git a/src/browser/setupWorker/start/createFallbackRequestListener.ts b/src/browser/setupWorker/start/createFallbackRequestListener.ts
new file mode 100644
index 000000000..c722b2772
--- /dev/null
+++ b/src/browser/setupWorker/start/createFallbackRequestListener.ts
@@ -0,0 +1,67 @@
+import {
+ Interceptor,
+ BatchInterceptor,
+ HttpRequestEventMap,
+} from '@mswjs/interceptors'
+import { FetchInterceptor } from '@mswjs/interceptors/fetch'
+import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
+import { SetupWorkerInternalContext, StartOptions } from '../glossary'
+import type { RequiredDeep } from '~/core/typeUtils'
+import { handleRequest } from '~/core/utils/handleRequest'
+
+export function createFallbackRequestListener(
+ context: SetupWorkerInternalContext,
+ options: RequiredDeep,
+): Interceptor {
+ const interceptor = new BatchInterceptor({
+ name: 'fallback',
+ interceptors: [new FetchInterceptor(), new XMLHttpRequestInterceptor()],
+ })
+
+ interceptor.on('request', async ({ request, requestId }) => {
+ const requestCloneForLogs = request.clone()
+
+ const response = await handleRequest(
+ request,
+ requestId,
+ context.requestHandlers,
+ options,
+ context.emitter,
+ {
+ onMockedResponse(_, { handler, parsedResult }) {
+ if (!options.quiet) {
+ context.emitter.once('response:mocked', ({ response }) => {
+ handler.log({
+ request: requestCloneForLogs,
+ response,
+ parsedResult,
+ })
+ })
+ }
+ },
+ },
+ )
+
+ if (response) {
+ request.respondWith(response)
+ }
+ })
+
+ interceptor.on(
+ 'response',
+ ({ response, isMockedResponse, request, requestId }) => {
+ context.emitter.emit(
+ isMockedResponse ? 'response:mocked' : 'response:bypass',
+ {
+ response,
+ request,
+ requestId,
+ },
+ )
+ },
+ )
+
+ interceptor.apply()
+
+ return interceptor
+}
diff --git a/src/setupWorker/start/createFallbackStart.ts b/src/browser/setupWorker/start/createFallbackStart.ts
similarity index 100%
rename from src/setupWorker/start/createFallbackStart.ts
rename to src/browser/setupWorker/start/createFallbackStart.ts
diff --git a/src/browser/setupWorker/start/createRequestListener.ts b/src/browser/setupWorker/start/createRequestListener.ts
new file mode 100644
index 000000000..b6ee1e56a
--- /dev/null
+++ b/src/browser/setupWorker/start/createRequestListener.ts
@@ -0,0 +1,116 @@
+import {
+ StartOptions,
+ SetupWorkerInternalContext,
+ ServiceWorkerIncomingEventsMap,
+} from '../glossary'
+import {
+ ServiceWorkerMessage,
+ WorkerChannel,
+} from './utils/createMessageChannel'
+import { parseWorkerRequest } from '../../utils/parseWorkerRequest'
+import { handleRequest } from '~/core/utils/handleRequest'
+import { RequiredDeep } from '~/core/typeUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
+import { toResponseInit } from '~/core/utils/toResponseInit'
+
+export const createRequestListener = (
+ context: SetupWorkerInternalContext,
+ options: RequiredDeep,
+) => {
+ return async (
+ event: MessageEvent,
+ message: ServiceWorkerMessage<
+ 'REQUEST',
+ ServiceWorkerIncomingEventsMap['REQUEST']
+ >,
+ ) => {
+ const messageChannel = new WorkerChannel(event.ports[0])
+
+ const requestId = message.payload.id
+ const request = parseWorkerRequest(message.payload)
+ const requestCloneForLogs = request.clone()
+
+ try {
+ await handleRequest(
+ request,
+ requestId,
+ context.requestHandlers,
+ options,
+ context.emitter,
+ {
+ onPassthroughResponse() {
+ messageChannel.postMessage('NOT_FOUND')
+ },
+ async onMockedResponse(response, { handler, parsedResult }) {
+ // Clone the mocked response so its body could be read
+ // to buffer to be sent to the worker and also in the
+ // ".log()" method of the request handler.
+ const responseClone = response.clone()
+ const responseInit = toResponseInit(response)
+
+ /**
+ * @note Safari doesn't support transferring a "ReadableStream".
+ * Check that the browser supports that before sending it to the worker.
+ */
+ if (context.supports.readableStreamTransfer) {
+ const responseStream = response.body
+ messageChannel.postMessage(
+ 'MOCK_RESPONSE',
+ {
+ ...responseInit,
+ body: responseStream,
+ },
+ responseStream ? [responseStream] : undefined,
+ )
+ } else {
+ // As a fallback, send the response body buffer to the worker.
+ const responseBuffer = await responseClone.arrayBuffer()
+ messageChannel.postMessage('MOCK_RESPONSE', {
+ ...responseInit,
+ body: responseBuffer,
+ })
+ }
+
+ if (!options.quiet) {
+ context.emitter.once('response:mocked', ({ response }) => {
+ handler.log({
+ request: requestCloneForLogs,
+ response,
+ parsedResult,
+ })
+ })
+ }
+ },
+ },
+ )
+ } catch (error) {
+ if (error instanceof Error) {
+ devUtils.error(
+ `Uncaught exception in the request handler for "%s %s":
+
+%s
+
+This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses`,
+ request.method,
+ request.url,
+ error.stack ?? error,
+ )
+
+ // Treat all other exceptions in a request handler as unintended,
+ // alerting that there is a problem that needs fixing.
+ messageChannel.postMessage('MOCK_RESPONSE', {
+ status: 500,
+ statusText: 'Request Handler Error',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ name: error.name,
+ message: error.message,
+ stack: error.stack,
+ }),
+ })
+ }
+ }
+ }
+}
diff --git a/src/setupWorker/start/createResponseListener.ts b/src/browser/setupWorker/start/createResponseListener.ts
similarity index 62%
rename from src/setupWorker/start/createResponseListener.ts
rename to src/browser/setupWorker/start/createResponseListener.ts
index 6fa37ad89..7719dfe19 100644
--- a/src/setupWorker/start/createResponseListener.ts
+++ b/src/browser/setupWorker/start/createResponseListener.ts
@@ -1,7 +1,7 @@
import {
ServiceWorkerIncomingEventsMap,
SetupWorkerInternalContext,
-} from '../../setupWorker/glossary'
+} from '../glossary'
import { ServiceWorkerMessage } from './utils/createMessageChannel'
export function createResponseListener(context: SetupWorkerInternalContext) {
@@ -25,13 +25,22 @@ export function createResponseListener(context: SetupWorkerInternalContext) {
return
}
- const response = new Response(responseJson.body || null, responseJson)
- const isMockedResponse = response.headers.get('x-powered-by') === 'msw'
+ const response =
+ responseJson.status === 0
+ ? Response.error()
+ : new Response(responseJson.body, responseJson)
- if (isMockedResponse) {
- context.emitter.emit('response:mocked', response, responseJson.requestId)
- } else {
- context.emitter.emit('response:bypass', response, responseJson.requestId)
- }
+ context.emitter.emit(
+ responseJson.isMockedResponse ? 'response:mocked' : 'response:bypass',
+ {
+ response,
+ /**
+ * @todo @fixme In this context, we don't know anything about
+ * the request.
+ */
+ request: null as any,
+ requestId: responseJson.requestId,
+ },
+ )
}
}
diff --git a/src/setupWorker/start/createStartHandler.ts b/src/browser/setupWorker/start/createStartHandler.ts
similarity index 94%
rename from src/setupWorker/start/createStartHandler.ts
rename to src/browser/setupWorker/start/createStartHandler.ts
index c7a127593..2ad650604 100644
--- a/src/setupWorker/start/createStartHandler.ts
+++ b/src/browser/setupWorker/start/createStartHandler.ts
@@ -1,13 +1,13 @@
import { until } from '@open-draft/until'
+import { devUtils } from '~/core/utils/internal/devUtils'
import { getWorkerInstance } from './utils/getWorkerInstance'
import { enableMocking } from './utils/enableMocking'
import { SetupWorkerInternalContext, StartHandler } from '../glossary'
import { createRequestListener } from './createRequestListener'
-import { requestIntegrityCheck } from '../../utils/internal/requestIntegrityCheck'
+import { requestIntegrityCheck } from '../../utils/requestIntegrityCheck'
import { deferNetworkRequestsUntil } from '../../utils/deferNetworkRequestsUntil'
import { createResponseListener } from './createResponseListener'
import { validateWorkerScope } from './utils/validateWorkerScope'
-import { devUtils } from '../../utils/internal/devUtils'
export const createStartHandler = (
context: SetupWorkerInternalContext,
@@ -76,13 +76,13 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
})
// Check if the active Service Worker is the latest published one
- const [integrityError] = await until(() =>
+ const integrityCheckResult = await until(() =>
requestIntegrityCheck(context, worker),
)
- if (integrityError) {
+ if (integrityCheckResult.error) {
devUtils.error(`\
-Detected outdated Service Worker: ${integrityError.message}
+Detected outdated Service Worker: ${integrityCheckResult.error.message}
The mocking is still enabled, but it's highly recommended that you update your Service Worker by running:
diff --git a/src/setupWorker/start/utils/createMessageChannel.ts b/src/browser/setupWorker/start/utils/createMessageChannel.ts
similarity index 62%
rename from src/setupWorker/start/utils/createMessageChannel.ts
rename to src/browser/setupWorker/start/utils/createMessageChannel.ts
index 207ee5638..210a7c3d1 100644
--- a/src/setupWorker/start/utils/createMessageChannel.ts
+++ b/src/browser/setupWorker/start/utils/createMessageChannel.ts
@@ -1,5 +1,5 @@
import {
- SerializedResponse,
+ StringifiedResponse,
ServiceWorkerIncomingEventsMap,
} from '../../glossary'
@@ -12,9 +12,11 @@ export interface ServiceWorkerMessage<
}
interface WorkerChannelEventsMap {
- MOCK_RESPONSE: [data: SerializedResponse, body?: [ArrayBuffer]]
+ MOCK_RESPONSE: [
+ data: StringifiedResponse,
+ transfer?: [ReadableStream],
+ ]
NOT_FOUND: []
- NETWORK_ERROR: [data: { name: string; message: string }]
}
export class WorkerChannel {
@@ -25,6 +27,13 @@ export class WorkerChannel {
...rest: WorkerChannelEventsMap[Event]
): void {
const [data, transfer] = rest
- this.port.postMessage({ type: event, data }, { transfer })
+ this.port.postMessage(
+ { type: event, data },
+ {
+ // @ts-ignore ReadableStream can be transferred
+ // but TypeScript doesn't acknowledge that.
+ transfer,
+ },
+ )
}
}
diff --git a/src/setupWorker/start/utils/enableMocking.ts b/src/browser/setupWorker/start/utils/enableMocking.ts
similarity index 94%
rename from src/setupWorker/start/utils/enableMocking.ts
rename to src/browser/setupWorker/start/utils/enableMocking.ts
index 890211b3d..c0f19f314 100644
--- a/src/setupWorker/start/utils/enableMocking.ts
+++ b/src/browser/setupWorker/start/utils/enableMocking.ts
@@ -1,4 +1,4 @@
-import { devUtils } from '../../../utils/internal/devUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
import { StartOptions, SetupWorkerInternalContext } from '../../glossary'
import { printStartMessage } from './printStartMessage'
diff --git a/src/setupWorker/start/utils/getWorkerByRegistration.ts b/src/browser/setupWorker/start/utils/getWorkerByRegistration.ts
similarity index 100%
rename from src/setupWorker/start/utils/getWorkerByRegistration.ts
rename to src/browser/setupWorker/start/utils/getWorkerByRegistration.ts
diff --git a/src/setupWorker/start/utils/getWorkerInstance.ts b/src/browser/setupWorker/start/utils/getWorkerInstance.ts
similarity index 88%
rename from src/setupWorker/start/utils/getWorkerInstance.ts
rename to src/browser/setupWorker/start/utils/getWorkerInstance.ts
index 56949d806..05594426b 100644
--- a/src/setupWorker/start/utils/getWorkerInstance.ts
+++ b/src/browser/setupWorker/start/utils/getWorkerInstance.ts
@@ -1,8 +1,8 @@
import { until } from '@open-draft/until'
+import { devUtils } from '~/core/utils/internal/devUtils'
+import { getAbsoluteWorkerUrl } from '../../../utils/getAbsoluteWorkerUrl'
import { getWorkerByRegistration } from './getWorkerByRegistration'
import { ServiceWorkerInstanceTuple, FindWorker } from '../../glossary'
-import { getAbsoluteWorkerUrl } from '../../../utils/url/getAbsoluteWorkerUrl'
-import { devUtils } from '../../../utils/internal/devUtils'
/**
* Returns an active Service Worker instance.
@@ -50,7 +50,7 @@ export const getWorkerInstance = async (
}
// When the Service Worker wasn't found, register it anew and return the reference.
- const [error, instance] = await until(
+ const registrationResult = await until(
async () => {
const registration = await navigator.serviceWorker.register(url, options)
return [
@@ -63,8 +63,8 @@ export const getWorkerInstance = async (
)
// Handle Service Worker registration errors.
- if (error) {
- const isWorkerMissing = error.message.includes('(404)')
+ if (registrationResult.error) {
+ const isWorkerMissing = registrationResult.error.message.includes('(404)')
// Produce a custom error message when given a non-existing Service Worker url.
// Suggest developers to check their setup.
@@ -85,10 +85,10 @@ Learn more about creating the Service Worker script: https://mswjs.io/docs/cli/i
throw new Error(
devUtils.formatMessage(
'Failed to register the Service Worker:\n\n%s',
- error.message,
+ registrationResult.error.message,
),
)
}
- return instance
+ return registrationResult.data
}
diff --git a/src/setupWorker/start/utils/prepareStartHandler.test.ts b/src/browser/setupWorker/start/utils/prepareStartHandler.test.ts
similarity index 100%
rename from src/setupWorker/start/utils/prepareStartHandler.test.ts
rename to src/browser/setupWorker/start/utils/prepareStartHandler.test.ts
diff --git a/src/setupWorker/start/utils/prepareStartHandler.ts b/src/browser/setupWorker/start/utils/prepareStartHandler.ts
similarity index 90%
rename from src/setupWorker/start/utils/prepareStartHandler.ts
rename to src/browser/setupWorker/start/utils/prepareStartHandler.ts
index 3828a13c1..e98fe832c 100644
--- a/src/setupWorker/start/utils/prepareStartHandler.ts
+++ b/src/browser/setupWorker/start/utils/prepareStartHandler.ts
@@ -1,5 +1,5 @@
-import { RequiredDeep } from '../../../typeUtils'
-import { mergeRight } from '../../../utils/internal/mergeRight'
+import { RequiredDeep } from '~/core/typeUtils'
+import { mergeRight } from '~/core/utils/internal/mergeRight'
import {
SetupWorker,
SetupWorkerInternalContext,
diff --git a/src/setupWorker/start/utils/printStartMessage.test.ts b/src/browser/setupWorker/start/utils/printStartMessage.test.ts
similarity index 100%
rename from src/setupWorker/start/utils/printStartMessage.test.ts
rename to src/browser/setupWorker/start/utils/printStartMessage.test.ts
diff --git a/src/setupWorker/start/utils/printStartMessage.ts b/src/browser/setupWorker/start/utils/printStartMessage.ts
similarity index 93%
rename from src/setupWorker/start/utils/printStartMessage.ts
rename to src/browser/setupWorker/start/utils/printStartMessage.ts
index 9e588afaa..44ffcd353 100644
--- a/src/setupWorker/start/utils/printStartMessage.ts
+++ b/src/browser/setupWorker/start/utils/printStartMessage.ts
@@ -1,4 +1,4 @@
-import { devUtils } from '../../../utils/internal/devUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
export interface PrintStartMessageArgs {
quiet?: boolean
diff --git a/src/setupWorker/start/utils/validateWorkerScope.ts b/src/browser/setupWorker/start/utils/validateWorkerScope.ts
similarity index 91%
rename from src/setupWorker/start/utils/validateWorkerScope.ts
rename to src/browser/setupWorker/start/utils/validateWorkerScope.ts
index b288e0d39..0e93412c2 100644
--- a/src/setupWorker/start/utils/validateWorkerScope.ts
+++ b/src/browser/setupWorker/start/utils/validateWorkerScope.ts
@@ -1,4 +1,4 @@
-import { devUtils } from '../../../utils/internal/devUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
import { StartOptions } from '../../glossary'
export function validateWorkerScope(
diff --git a/src/setupWorker/stop/createFallbackStop.ts b/src/browser/setupWorker/stop/createFallbackStop.ts
similarity index 100%
rename from src/setupWorker/stop/createFallbackStop.ts
rename to src/browser/setupWorker/stop/createFallbackStop.ts
diff --git a/src/setupWorker/stop/createStop.ts b/src/browser/setupWorker/stop/createStop.ts
similarity index 94%
rename from src/setupWorker/stop/createStop.ts
rename to src/browser/setupWorker/stop/createStop.ts
index df4a2e5d1..48c37996d 100644
--- a/src/setupWorker/stop/createStop.ts
+++ b/src/browser/setupWorker/stop/createStop.ts
@@ -1,4 +1,4 @@
-import { devUtils } from '../../utils/internal/devUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
import { SetupWorkerInternalContext, StopHandler } from '../glossary'
import { printStopMessage } from './utils/printStopMessage'
diff --git a/src/setupWorker/stop/utils/printStopMessage.test.ts b/src/browser/setupWorker/stop/utils/printStopMessage.test.ts
similarity index 100%
rename from src/setupWorker/stop/utils/printStopMessage.test.ts
rename to src/browser/setupWorker/stop/utils/printStopMessage.test.ts
diff --git a/src/setupWorker/stop/utils/printStopMessage.ts b/src/browser/setupWorker/stop/utils/printStopMessage.ts
similarity index 79%
rename from src/setupWorker/stop/utils/printStopMessage.ts
rename to src/browser/setupWorker/stop/utils/printStopMessage.ts
index d12246fea..43a08a7a3 100644
--- a/src/setupWorker/stop/utils/printStopMessage.ts
+++ b/src/browser/setupWorker/stop/utils/printStopMessage.ts
@@ -1,4 +1,4 @@
-import { devUtils } from '../../../utils/internal/devUtils'
+import { devUtils } from '~/core/utils/internal/devUtils'
export function printStopMessage(args: { quiet?: boolean } = {}): void {
if (args.quiet) {
diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json
new file mode 100644
index 000000000..30d12be0c
--- /dev/null
+++ b/src/browser/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "lib": ["dom", "WebWorker"]
+ },
+ "include": ["./**/*.ts"]
+}
diff --git a/src/utils/deferNetworkRequestsUntil.test.ts b/src/browser/utils/deferNetworkRequestsUntil.test.ts
similarity index 94%
rename from src/utils/deferNetworkRequestsUntil.test.ts
rename to src/browser/utils/deferNetworkRequestsUntil.test.ts
index 8ba23be8a..c5f0e963c 100644
--- a/src/utils/deferNetworkRequestsUntil.test.ts
+++ b/src/browser/utils/deferNetworkRequestsUntil.test.ts
@@ -31,7 +31,7 @@ test('defers any requests that happen while a given promise is pending', async (
events.push('promise resolved')
})
- // Calling this functions captures all requests that happen while
+ // Calling this functions intercepts all requests that happen while
// the given promise is pending, and defers their execution
// until the promise is resolved.
deferNetworkRequestsUntil(workerPromise)
diff --git a/src/utils/deferNetworkRequestsUntil.ts b/src/browser/utils/deferNetworkRequestsUntil.ts
similarity index 100%
rename from src/utils/deferNetworkRequestsUntil.ts
rename to src/browser/utils/deferNetworkRequestsUntil.ts
diff --git a/src/utils/url/getAbsoluteWorkerUrl.test.ts b/src/browser/utils/getAbsoluteWorkerUrl.test.ts
similarity index 100%
rename from src/utils/url/getAbsoluteWorkerUrl.test.ts
rename to src/browser/utils/getAbsoluteWorkerUrl.test.ts
diff --git a/src/utils/url/getAbsoluteWorkerUrl.ts b/src/browser/utils/getAbsoluteWorkerUrl.ts
similarity index 100%
rename from src/utils/url/getAbsoluteWorkerUrl.ts
rename to src/browser/utils/getAbsoluteWorkerUrl.ts
diff --git a/src/browser/utils/parseWorkerRequest.ts b/src/browser/utils/parseWorkerRequest.ts
new file mode 100644
index 000000000..4160efcb8
--- /dev/null
+++ b/src/browser/utils/parseWorkerRequest.ts
@@ -0,0 +1,15 @@
+import { pruneGetRequestBody } from './pruneGetRequestBody'
+import type { ServiceWorkerIncomingRequest } from '../setupWorker/glossary'
+
+/**
+ * Converts a given request received from the Service Worker
+ * into a Fetch `Request` instance.
+ */
+export function parseWorkerRequest(
+ incomingRequest: ServiceWorkerIncomingRequest,
+): Request {
+ return new Request(incomingRequest.url, {
+ ...incomingRequest,
+ body: pruneGetRequestBody(incomingRequest),
+ })
+}
diff --git a/src/browser/utils/pruneGetRequestBody.test.ts b/src/browser/utils/pruneGetRequestBody.test.ts
new file mode 100644
index 000000000..eee2932a9
--- /dev/null
+++ b/src/browser/utils/pruneGetRequestBody.test.ts
@@ -0,0 +1,53 @@
+/**
+ * @jest-environment jsdom
+ */
+import { TextEncoder } from 'util'
+import { pruneGetRequestBody } from './pruneGetRequestBody'
+
+test('sets empty GET request body to undefined', () => {
+ expect(
+ pruneGetRequestBody({
+ method: 'GET',
+ }),
+ ).toBeUndefined()
+
+ expect(
+ pruneGetRequestBody({
+ method: 'GET',
+ // There's no such thing as a GET request with a body.
+ body: new ArrayBuffer(5),
+ }),
+ ).toBeUndefined()
+})
+
+test('sets HEAD request body to undefined', () => {
+ expect(
+ pruneGetRequestBody({
+ method: 'HEAD',
+ }),
+ ).toBeUndefined()
+
+ expect(
+ pruneGetRequestBody({
+ method: 'HEAD',
+ body: new ArrayBuffer(5),
+ }),
+ ).toBeUndefined()
+})
+
+test('ignores requests of the other methods than GET', () => {
+ const body = new TextEncoder().encode('hello world')
+ expect(
+ pruneGetRequestBody({
+ method: 'POST',
+ body,
+ }),
+ ).toEqual(body)
+
+ expect(
+ pruneGetRequestBody({
+ method: 'PUT',
+ body,
+ }),
+ ).toEqual(body)
+})
diff --git a/src/browser/utils/pruneGetRequestBody.ts b/src/browser/utils/pruneGetRequestBody.ts
new file mode 100644
index 000000000..b17602217
--- /dev/null
+++ b/src/browser/utils/pruneGetRequestBody.ts
@@ -0,0 +1,21 @@
+import type { ServiceWorkerIncomingRequest } from '../setupWorker/glossary'
+
+type Input = Pick
+
+/**
+ * Ensures that an empty GET request body is always represented as `undefined`.
+ */
+export function pruneGetRequestBody(
+ request: Input,
+): ServiceWorkerIncomingRequest['body'] {
+ // Force HEAD/GET request body to always be empty.
+ // The worker reads any request's body as ArrayBuffer,
+ // and you cannot re-construct a GET/HEAD Request
+ // with an ArrayBuffer, even if empty. Also note that
+ // "request.body" is always undefined in the worker.
+ if (['HEAD', 'GET'].includes(request.method)) {
+ return undefined
+ }
+
+ return request.body
+}
diff --git a/src/utils/internal/requestIntegrityCheck.ts b/src/browser/utils/requestIntegrityCheck.ts
similarity index 90%
rename from src/utils/internal/requestIntegrityCheck.ts
rename to src/browser/utils/requestIntegrityCheck.ts
index 10f1fb112..67e1b4144 100644
--- a/src/utils/internal/requestIntegrityCheck.ts
+++ b/src/browser/utils/requestIntegrityCheck.ts
@@ -1,4 +1,4 @@
-import { SetupWorkerInternalContext } from '../../setupWorker/glossary'
+import type { SetupWorkerInternalContext } from '../setupWorker/glossary'
export async function requestIntegrityCheck(
context: SetupWorkerInternalContext,
diff --git a/src/browser/utils/supportsReadableStreamTransfer.ts b/src/browser/utils/supportsReadableStreamTransfer.ts
new file mode 100644
index 000000000..b1c5dc295
--- /dev/null
+++ b/src/browser/utils/supportsReadableStreamTransfer.ts
@@ -0,0 +1,17 @@
+/**
+ * Returns a boolean indicating whether the current browser
+ * supports `ReadableStream` as a `Transferable` when posting
+ * messages.
+ */
+export function supportsReadableStreamTransfer() {
+ try {
+ const stream = new ReadableStream({
+ start: (controller) => controller.close(),
+ })
+ const message = new MessageChannel()
+ message.port1.postMessage(stream, [stream])
+ return true
+ } catch (error) {
+ return false
+ }
+}
diff --git a/src/context/body.test.ts b/src/context/body.test.ts
deleted file mode 100644
index c6e7056a9..000000000
--- a/src/context/body.test.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { body } from './body'
-import { set } from './set'
-import { response } from '../response'
-
-test('sets a given body value without implicit "Content-Type" header', async () => {
- const result = await response(body('Lorem ipsum'))
-
- expect(result).toHaveProperty('body', 'Lorem ipsum')
- expect(result.headers.get('content-type')).toBeNull()
-})
-
-test('does not stringify raw body twice if content is string and "Content-Type" header is "json"', async () => {
- const result = await response(
- set('Content-Type', 'application/json'),
- body(JSON.stringify('some text')),
- )
-
- expect(result).toHaveProperty('body', `"some text"`)
-})
diff --git a/src/context/body.ts b/src/context/body.ts
deleted file mode 100644
index 4a07a62ec..000000000
--- a/src/context/body.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { ResponseTransformer } from '../response'
-
-/**
- * Sets a raw response body. Does not append any `Content-Type` headers.
- * @example
- * res(ctx.body('Successful response'))
- * res(ctx.body(JSON.stringify({ key: 'value' })))
- * @see {@link https://mswjs.io/docs/api/context/body `ctx.body()`}
- */
-export const body = <
- BodyType extends string | Blob | BufferSource | ReadableStream | FormData,
->(
- value: BodyType,
-): ResponseTransformer => {
- return (res) => {
- res.body = value
- return res
- }
-}
diff --git a/src/context/cookie.node.test.ts b/src/context/cookie.node.test.ts
deleted file mode 100644
index 8551b706a..000000000
--- a/src/context/cookie.node.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @jest-environment node
- */
-import { cookie } from './cookie'
-import { response } from '../response'
-
-test('sets a cookie on the response headers, node environment', async () => {
- const result = await response(cookie('my-cookie', 'arbitrary-value'))
- expect(result.headers.get('set-cookie')).toEqual('my-cookie=arbitrary-value')
-})
diff --git a/src/context/cookie.test.ts b/src/context/cookie.test.ts
deleted file mode 100644
index c17cca610..000000000
--- a/src/context/cookie.test.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import * as cookieUtils from 'cookie'
-import { cookie } from './cookie'
-import { response } from '../response'
-import { clearCookies } from '../../test/support/utils'
-
-beforeAll(() => {
- clearCookies()
-})
-
-afterEach(() => {
- clearCookies()
-})
-
-test('sets a given response cookie', async () => {
- const result = await response(cookie('myCookie', 'value'))
-
- expect(result.headers.get('set-cookie')).toBe('myCookie=value')
-
- // Propagates the response cookies on the document.
- const allCookies = cookieUtils.parse(document.cookie)
- expect(allCookies).toEqual({ myCookie: 'value' })
-})
-
-test('supports setting multiple response cookies', async () => {
- const result = await response(
- cookie('firstCookie', 'yes'),
- cookie('secondCookie', 'no'),
- )
-
- expect(result.headers.get('set-cookie')).toBe(
- 'secondCookie=no, firstCookie=yes',
- )
-
- const allCookies = cookieUtils.parse(document.cookie)
- expect(allCookies).toEqual({ firstCookie: 'yes', secondCookie: 'no' })
-})
diff --git a/src/context/cookie.ts b/src/context/cookie.ts
deleted file mode 100644
index 8d38b270a..000000000
--- a/src/context/cookie.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as cookieUtils from 'cookie'
-import { ResponseTransformer } from '../response'
-
-/**
- * Sets a given cookie on the mocked response.
- * @example res(ctx.cookie('name', 'value'))
- */
-export const cookie = (
- name: string,
- value: string,
- options?: cookieUtils.CookieSerializeOptions,
-): ResponseTransformer => {
- return (res) => {
- const serializedCookie = cookieUtils.serialize(name, value, options)
- res.headers.append('Set-Cookie', serializedCookie)
-
- if (typeof document !== 'undefined') {
- document.cookie = serializedCookie
- }
-
- return res
- }
-}
diff --git a/src/context/data.test.ts b/src/context/data.test.ts
deleted file mode 100644
index f73e0693c..000000000
--- a/src/context/data.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { data } from './data'
-import { errors } from './errors'
-import { response } from '../response'
-
-test('sets a single data on the response JSON body', async () => {
- const result = await response(data({ name: 'msw' }))
-
- expect(result.headers.get('content-type')).toBe('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- data: {
- name: 'msw',
- },
- }),
- )
-})
-
-test('sets multiple data on the response JSON body', async () => {
- const result = await response(
- data({ name: 'msw' }),
- data({ description: 'API mocking library' }),
- )
-
- expect(result.headers.get('content-type')).toBe('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- data: {
- description: 'API mocking library',
- name: 'msw',
- },
- }),
- )
-})
-
-test('combines with error in the response JSON body', async () => {
- const result = await response(
- data({ name: 'msw' }),
- errors([
- {
- message: 'exceeds the limit of awesomeness',
- },
- ]),
- )
-
- expect(result.headers.get('content-type')).toBe('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- errors: [
- {
- message: 'exceeds the limit of awesomeness',
- },
- ],
- data: {
- name: 'msw',
- },
- }),
- )
-})
diff --git a/src/context/data.ts b/src/context/data.ts
deleted file mode 100644
index 9180cb7d9..000000000
--- a/src/context/data.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { jsonParse } from '../utils/internal/jsonParse'
-import { mergeRight } from '../utils/internal/mergeRight'
-import { json } from './json'
-import { GraphQLPayloadContext } from '../typeUtils'
-
-/**
- * Sets a given payload as a GraphQL response body.
- * @example
- * res(ctx.data({ user: { firstName: 'John' }}))
- * @see {@link https://mswjs.io/docs/api/context/data `ctx.data()`}
- */
-export const data: GraphQLPayloadContext> = (
- payload,
-) => {
- return (res) => {
- const prevBody = jsonParse(res.body) || {}
- const nextBody = mergeRight(prevBody, { data: payload })
-
- return json(nextBody)(res)
- }
-}
diff --git a/src/context/delay.node.test.ts b/src/context/delay.node.test.ts
deleted file mode 100644
index 09d78a465..000000000
--- a/src/context/delay.node.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @jest-environment node
- */
-import { delay, NODE_SERVER_RESPONSE_TIME } from './delay'
-import { response } from '../response'
-
-test('sets a Node.js-specific response delay when not provided', async () => {
- const resolvedResponse = await response(delay())
- expect(resolvedResponse).toHaveProperty('delay', NODE_SERVER_RESPONSE_TIME)
-})
-
-test('allows response delay duration overrides', async () => {
- const resolvedResponse = await response(delay(1234))
- expect(resolvedResponse).toHaveProperty('delay', 1234)
-})
-
-test('throws an exception given a too large duration', async () => {
- const createErrorMessage = (value: any) => {
- return `Failed to delay a response: provided delay duration (${value}) exceeds the maximum allowed duration for "setTimeout" (2147483647). This will cause the response to be returned immediately. Please use a number within the allowed range to delay the response by exact duration, or consider the "infinite" delay mode to delay the response indefinitely.`
- }
-
- const exceedingValues = [
- Infinity,
- Number.MAX_VALUE,
- Number.MAX_SAFE_INTEGER,
- 2147483648,
- ]
-
- for (const value of exceedingValues) {
- await expect(() => response(delay(value))).rejects.toThrow(
- createErrorMessage(value),
- )
- }
-})
-
-test('throws an exception given an unknown delay mode', async () => {
- await expect(() => response(delay('foo' as any))).rejects.toThrow(
- 'Failed to delay a response: unknown delay mode "foo". Please make sure you provide one of the supported modes ("real", "infinite") or a number to "ctx.delay".',
- )
-})
diff --git a/src/context/delay.test.ts b/src/context/delay.test.ts
deleted file mode 100644
index e3ea29852..000000000
--- a/src/context/delay.test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * @jest-environment jsdom
- *
- * Since jsdom also runs in Node.js, expect a Node.js-specific implicit delay.
- */
-import { delay, NODE_SERVER_RESPONSE_TIME } from './delay'
-import { response } from '../response'
-
-test('sets a Node.js-specific response delay when not provided', async () => {
- const resolvedResponse = await response(delay())
- expect(resolvedResponse).toHaveProperty('delay', NODE_SERVER_RESPONSE_TIME)
-})
-
-test('allows response delay duration overrides', async () => {
- const resolvedResponse = await response(delay(1234))
- expect(resolvedResponse).toHaveProperty('delay', 1234)
-})
-
-test('throws an exception given a too large duration', async () => {
- const createErrorMessage = (value: any) => {
- return `Failed to delay a response: provided delay duration (${value}) exceeds the maximum allowed duration for "setTimeout" (2147483647). This will cause the response to be returned immediately. Please use a number within the allowed range to delay the response by exact duration, or consider the "infinite" delay mode to delay the response indefinitely.`
- }
-
- const exceedingValues = [
- Infinity,
- Number.MAX_VALUE,
- Number.MAX_SAFE_INTEGER,
- 2147483648,
- ]
-
- for (const value of exceedingValues) {
- await expect(() => response(delay(value))).rejects.toThrow(
- createErrorMessage(value),
- )
- }
-})
-
-test('throws an exception given an unknown delay mode', async () => {
- await expect(() => response(delay('foo' as any))).rejects.toThrow(
- 'Failed to delay a response: unknown delay mode "foo". Please make sure you provide one of the supported modes ("real", "infinite") or a number to "ctx.delay".',
- )
-})
diff --git a/src/context/delay.ts b/src/context/delay.ts
deleted file mode 100644
index 50ff9b01a..000000000
--- a/src/context/delay.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { isNodeProcess } from 'is-node-process'
-import { ResponseTransformer } from '../response'
-
-export const SET_TIMEOUT_MAX_ALLOWED_INT = 2147483647
-export const MIN_SERVER_RESPONSE_TIME = 100
-export const MAX_SERVER_RESPONSE_TIME = 400
-export const NODE_SERVER_RESPONSE_TIME = 5
-
-const getRandomServerResponseTime = () => {
- if (isNodeProcess()) {
- return NODE_SERVER_RESPONSE_TIME
- }
-
- return Math.floor(
- Math.random() * (MAX_SERVER_RESPONSE_TIME - MIN_SERVER_RESPONSE_TIME) +
- MIN_SERVER_RESPONSE_TIME,
- )
-}
-
-export type DelayMode = 'real' | 'infinite'
-
-/**
- * Delays the response by the given duration (ms).
- * @example
- * res(ctx.delay(1200)) // delay response by 1200ms
- * res(ctx.delay()) // emulate realistic server response time
- * res(ctx.delay('infinite')) // delay response infinitely
- * @see {@link https://mswjs.io/docs/api/context/delay `ctx.delay()`}
- */
-export const delay = (
- durationOrMode?: DelayMode | number,
-): ResponseTransformer => {
- return (res) => {
- let delayTime: number
-
- if (typeof durationOrMode === 'string') {
- switch (durationOrMode) {
- case 'infinite': {
- // Using `Infinity` as a delay value executes the response timeout immediately.
- // Instead, use the maximum allowed integer for `setTimeout`.
- delayTime = SET_TIMEOUT_MAX_ALLOWED_INT
- break
- }
- case 'real': {
- delayTime = getRandomServerResponseTime()
- break
- }
- default: {
- throw new Error(
- `Failed to delay a response: unknown delay mode "${durationOrMode}". Please make sure you provide one of the supported modes ("real", "infinite") or a number to "ctx.delay".`,
- )
- }
- }
- } else if (typeof durationOrMode === 'undefined') {
- // Use random realistic server response time when no explicit delay duration was provided.
- delayTime = getRandomServerResponseTime()
- } else {
- // Guard against passing values like `Infinity` or `Number.MAX_VALUE`
- // as the response delay duration. They don't produce the result you may expect.
- if (durationOrMode > SET_TIMEOUT_MAX_ALLOWED_INT) {
- throw new Error(
- `Failed to delay a response: provided delay duration (${durationOrMode}) exceeds the maximum allowed duration for "setTimeout" (${SET_TIMEOUT_MAX_ALLOWED_INT}). This will cause the response to be returned immediately. Please use a number within the allowed range to delay the response by exact duration, or consider the "infinite" delay mode to delay the response indefinitely.`,
- )
- }
-
- delayTime = durationOrMode
- }
-
- res.delay = delayTime
- return res
- }
-}
diff --git a/src/context/errors.test.ts b/src/context/errors.test.ts
deleted file mode 100644
index ed1cd1f1f..000000000
--- a/src/context/errors.test.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { errors } from './errors'
-import { data } from './data'
-import { response } from '../response'
-
-test('sets a given error on the response JSON body', async () => {
- const result = await response(errors([{ message: 'Error message' }]))
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- errors: [
- {
- message: 'Error message',
- },
- ],
- }),
- )
-})
-
-test('sets given errors on the response JSON body', async () => {
- const result = await response(
- errors([{ message: 'Error message' }, { message: 'Second error' }]),
- )
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- errors: [
- {
- message: 'Error message',
- },
- {
- message: 'Second error',
- },
- ],
- }),
- )
-})
-
-test('combines with data in the response JSON body', async () => {
- const result = await response(
- data({ name: 'msw' }),
- errors([{ message: 'exceeds the limit of awesomeness' }]),
- )
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- errors: [
- {
- message: 'exceeds the limit of awesomeness',
- },
- ],
- data: {
- name: 'msw',
- },
- }),
- )
-})
-
-test('bypasses undefined errors', async () => {
- const result = await response(errors(undefined), errors(null))
-
- expect(result.headers.get('content-type')).not.toEqual('application/json')
- expect(result).toHaveProperty('body', null)
-})
diff --git a/src/context/errors.ts b/src/context/errors.ts
deleted file mode 100644
index c64d97331..000000000
--- a/src/context/errors.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { GraphQLError } from 'graphql'
-import { ResponseTransformer } from '../response'
-import { jsonParse } from '../utils/internal/jsonParse'
-import { mergeRight } from '../utils/internal/mergeRight'
-import { json } from './json'
-
-/**
- * Sets a given list of GraphQL errors on the mocked response.
- * @example res(ctx.errors([{ message: 'Unauthorized' }]))
- * @see {@link https://mswjs.io/docs/api/context/errors}
- */
-export const errors = <
- ErrorsType extends readonly Partial[] | null | undefined,
->(
- errorsList: ErrorsType,
-): ResponseTransformer => {
- return (res) => {
- if (errorsList == null) {
- return res
- }
-
- const prevBody = jsonParse(res.body) || {}
- const nextBody = mergeRight(prevBody, { errors: errorsList })
-
- return json(nextBody)(res as any) as any
- }
-}
diff --git a/src/context/extensions.test.ts b/src/context/extensions.test.ts
deleted file mode 100644
index b341e1598..000000000
--- a/src/context/extensions.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { errors } from './errors'
-import { data } from './data'
-import { extensions } from './extensions'
-import { response } from '../response'
-
-test('sets standalone extensions on the response JSON body', async () => {
- const result = await response(extensions({ tracking: { version: 1 } }))
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result.body).toEqual(
- JSON.stringify({
- extensions: {
- tracking: {
- version: 1,
- },
- },
- }),
- )
-})
-
-test('sets given extensions on the response JSON body with data', async () => {
- const result = await response(
- data({ hello: 'world' }),
- extensions({ tracking: { version: 1 } }),
- )
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result.body).toEqual(
- JSON.stringify({
- extensions: {
- tracking: {
- version: 1,
- },
- },
- data: {
- hello: 'world',
- },
- }),
- )
-})
-
-test('sets given extensions on the response JSON body in the presence with data and errors', async () => {
- const result = await response(
- data({ hello: 'world' }),
- extensions({ tracking: { version: 1 } }),
- errors([{ message: 'Error message' }]),
- )
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result.body).toEqual(
- JSON.stringify({
- errors: [
- {
- message: 'Error message',
- },
- ],
- extensions: {
- tracking: {
- version: 1,
- },
- },
- data: {
- hello: 'world',
- },
- }),
- )
-})
diff --git a/src/context/extensions.ts b/src/context/extensions.ts
deleted file mode 100644
index 4c2925394..000000000
--- a/src/context/extensions.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { jsonParse } from '../utils/internal/jsonParse'
-import { mergeRight } from '../utils/internal/mergeRight'
-import { json } from './json'
-import { GraphQLPayloadContext } from '../typeUtils'
-
-/**
- * Sets the GraphQL extensions on a given response.
- * @example
- * res(ctx.extensions({ tracing: { version: 1 }}))
- * @see {@link https://mswjs.io/docs/api/context/extensions `ctx.extensions()`}
- */
-export const extensions: GraphQLPayloadContext> = (
- payload,
-) => {
- return (res) => {
- const prevBody = jsonParse(res.body) || {}
- const nextBody = mergeRight(prevBody, { extensions: payload })
- return json(nextBody)(res)
- }
-}
diff --git a/src/context/fetch.test.ts b/src/context/fetch.test.ts
deleted file mode 100644
index 01c1ae13a..000000000
--- a/src/context/fetch.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { augmentRequestInit } from './fetch'
-
-test('augments RequestInit with the Headers instance', () => {
- const result = augmentRequestInit({
- headers: new Headers({ Authorization: 'token' }),
- })
- const headers = new Headers(result.headers)
-
- expect(headers.get('Authorization')).toEqual('token')
- expect(headers.get('x-msw-bypass')).toEqual('true')
-})
-
-test('augments RequestInit with the string[][] headers object', () => {
- const result = augmentRequestInit({
- headers: [['Authorization', 'token']],
- })
- const headers = new Headers(result.headers)
-
- expect(headers.get('x-msw-bypass')).toEqual('true')
- expect(headers.get('authorization')).toEqual('token')
-})
-
-test('aguments RequestInit with the Record headers', () => {
- const result = augmentRequestInit({
- headers: {
- Authorization: 'token',
- },
- })
- const headers = new Headers(result.headers)
-
- expect(headers.get('x-msw-bypass')).toEqual('true')
- expect(headers.get('authorization')).toEqual('token')
-})
diff --git a/src/context/fetch.ts b/src/context/fetch.ts
deleted file mode 100644
index 6d91a756d..000000000
--- a/src/context/fetch.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { isNodeProcess } from 'is-node-process'
-import { Headers } from 'headers-polyfill'
-import { MockedRequest } from '../utils/request/MockedRequest'
-
-const useFetch: (input: RequestInfo, init?: RequestInit) => Promise =
- isNodeProcess()
- ? (input, init) =>
- import('node-fetch').then(({ default: nodeFetch }) =>
- (nodeFetch as unknown as typeof window.fetch)(input, init),
- )
- : globalThis.fetch
-
-export const augmentRequestInit = (requestInit: RequestInit): RequestInit => {
- const headers = new Headers(requestInit.headers)
- headers.set('x-msw-bypass', 'true')
-
- return {
- ...requestInit,
- headers: headers.all(),
- }
-}
-
-const createFetchRequestParameters = (input: MockedRequest): RequestInit => {
- const { body, method } = input
- const requestParameters: RequestInit = {
- ...input,
- body: undefined,
- }
-
- if (['GET', 'HEAD'].includes(method)) {
- return requestParameters
- }
-
- if (
- typeof body === 'object' ||
- typeof body === 'number' ||
- typeof body === 'boolean'
- ) {
- requestParameters.body = JSON.stringify(body)
- } else {
- requestParameters.body = body
- }
-
- return requestParameters
-}
-
-/**
- * Performs a bypassed request inside a request handler.
- * @example
- * const originalResponse = await ctx.fetch(req)
- * @see {@link https://mswjs.io/docs/api/context/fetch `ctx.fetch()`}
- */
-export const fetch = (
- input: string | MockedRequest,
- requestInit: RequestInit = {},
-): Promise => {
- if (typeof input === 'string') {
- return useFetch(input, augmentRequestInit(requestInit))
- }
-
- const requestParameters = createFetchRequestParameters(input)
- const derivedRequestInit = augmentRequestInit(requestParameters)
-
- return useFetch(input.url.href, derivedRequestInit)
-}
diff --git a/src/context/field.test.ts b/src/context/field.test.ts
deleted file mode 100644
index 48952e9d0..000000000
--- a/src/context/field.test.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { field } from './field'
-import { response } from '../response'
-import { data } from './data'
-import { errors } from './errors'
-
-test('sets a given field value string on the response JSON body', async () => {
- const result = await response(field('field', 'value'))
-
- expect(result.headers.get('content-type')).toBe('application/json')
- expect(result).toHaveProperty('body', JSON.stringify({ field: 'value' }))
-})
-
-test('sets a given field value object on the response JSON body', async () => {
- const result = await response(
- field('metadata', {
- date: new Date('2022-05-27'),
- comment: 'nice metadata',
- }),
- )
-
- expect(result.headers.get('content-type')).toBe('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- metadata: { date: new Date('2022-05-27'), comment: 'nice metadata' },
- }),
- )
-})
-
-test('combines with data, errors and other field in the response JSON body', async () => {
- const result = await response(
- data({ name: 'msw' }),
- errors([{ message: 'exceeds the limit of awesomeness' }]),
- field('field', { errorCode: 'value' }),
- field('field2', 123),
- )
-
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result).toHaveProperty(
- 'body',
- JSON.stringify({
- field2: 123,
- field: { errorCode: 'value' },
- errors: [
- {
- message: 'exceeds the limit of awesomeness',
- },
- ],
- data: {
- name: 'msw',
- },
- }),
- )
-})
-
-test('throws when trying to set non-serializable values', async () => {
- await expect(response(field('metadata', BigInt(1)))).rejects.toThrow(
- 'Do not know how to serialize a BigInt',
- )
-})
-
-test('throws when passing an empty string as field name', async () => {
- await expect(response(field('' as string, 'value'))).rejects.toThrow(
- `[MSW] Failed to set a custom field on a GraphQL response: field name cannot be empty.`,
- )
-})
-
-test('throws when passing an empty string (when trimmed) as field name', async () => {
- await expect(response(field(' ' as string, 'value'))).rejects.toThrow(
- `[MSW] Failed to set a custom field on a GraphQL response: field name cannot be empty.`,
- )
-})
-
-test('throws when using "data" as the field name', async () => {
- await expect(
- response(
- field(
- // @ts-expect-error Test runtime value.
- 'data',
- 'value',
- ),
- ),
- ).rejects.toThrow(
- '[MSW] Failed to set a custom "data" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.data()" instead?',
- )
-})
-
-test('throws when using "errors" as the field name', async () => {
- await expect(
- response(
- field(
- // @ts-expect-error Test runtime value.
- 'errors',
- 'value',
- ),
- ),
- ).rejects.toThrow(
- '[MSW] Failed to set a custom "errors" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.errors()" instead?',
- )
-})
-
-test('throws when using "extensions" as the field name', async () => {
- await expect(
- response(
- field(
- // @ts-expect-error Test runtime value.
- 'extensions',
- 'value',
- ),
- ),
- ).rejects.toThrow(
- '[MSW] Failed to set a custom "extensions" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.extensions()" instead?',
- )
-})
diff --git a/src/context/field.ts b/src/context/field.ts
deleted file mode 100644
index a97764607..000000000
--- a/src/context/field.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { invariant } from 'outvariant'
-import { ResponseTransformer } from '../response'
-import { devUtils } from '../utils/internal/devUtils'
-import { jsonParse } from '../utils/internal/jsonParse'
-import { mergeRight } from '../utils/internal/mergeRight'
-import { json } from './json'
-
-type ForbiddenFieldNames = '' | 'data' | 'errors' | 'extensions'
-
-/**
- * Set a custom field on the GraphQL mocked response.
- * @example res(ctx.fields('customField', value))
- * @see {@link https://mswjs.io/docs/api/context/field}
- */
-export const field = (
- fieldName: FieldNameType extends ForbiddenFieldNames ? never : FieldNameType,
- fieldValue: FieldValueType,
-): ResponseTransformer => {
- return (res) => {
- validateFieldName(fieldName)
-
- const prevBody = jsonParse(res.body) || {}
- const nextBody = mergeRight(prevBody, { [fieldName]: fieldValue })
-
- return json(nextBody)(res as any) as any
- }
-}
-
-function validateFieldName(fieldName: string) {
- invariant(
- fieldName.trim() !== '',
- devUtils.formatMessage(
- 'Failed to set a custom field on a GraphQL response: field name cannot be empty.',
- ),
- )
-
- invariant(
- fieldName !== 'data',
- devUtils.formatMessage(
- 'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.data()" instead?',
- fieldName,
- ),
- )
-
- invariant(
- fieldName !== 'errors',
- devUtils.formatMessage(
- 'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.errors()" instead?',
- fieldName,
- ),
- )
-
- invariant(
- fieldName !== 'extensions',
- devUtils.formatMessage(
- 'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.extensions()" instead?',
- fieldName,
- ),
- )
-}
diff --git a/src/context/index.ts b/src/context/index.ts
deleted file mode 100644
index 6cbed148d..000000000
--- a/src/context/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export { status } from './status'
-export { set } from './set'
-export { cookie } from './cookie'
-export { body } from './body'
-export { data } from './data'
-export { extensions } from './extensions'
-export { delay } from './delay'
-export { errors } from './errors'
-export { fetch } from './fetch'
-export { json } from './json'
-export { text } from './text'
-export { xml } from './xml'
diff --git a/src/context/json.test.ts b/src/context/json.test.ts
deleted file mode 100644
index 4c0f2b3cf..000000000
--- a/src/context/json.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { json } from './json'
-import { response } from '../response'
-
-test('sets response content type and body to the given JSON', async () => {
- const result = await response(json({ firstName: 'John' }))
- expect(result.headers.get('content-type')).toEqual('application/json')
- expect(result).toHaveProperty('body', `{"firstName":"John"}`)
-})
-
-test('sets given Array as the response JSOn body', async () => {
- const result = await response(json([1, '2', true, { ok: true }, '']))
- expect(result).toHaveProperty('body', `[1,"2",true,{"ok":true},""]`)
-})
-
-test('sets given string as the response JSON body', async () => {
- const result = await response(json('some string'))
- expect(result).toHaveProperty('body', `"some string"`)
-})
-
-test('sets given boolean as the response JSON body', async () => {
- const result = await response(json(true))
- expect(result).toHaveProperty('body', `true`)
-})
-
-test('sets given date as the response JSON body', async () => {
- const result = await response(json(new Date(Date.UTC(2020, 0, 1))))
- expect(result).toHaveProperty('body', `"2020-01-01T00:00:00.000Z"`)
-})
diff --git a/src/context/json.ts b/src/context/json.ts
deleted file mode 100644
index ae67fd45d..000000000
--- a/src/context/json.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ResponseTransformer } from '../response'
-
-/**
- * Sets the given value as the JSON body of the response.
- * Appends a `Content-Type: application/json` header on the
- * mocked response.
- * @example
- * res(ctx.json('Some string'))
- * res(ctx.json({ key: 'value' }))
- * res(ctx.json([1, '2', false, { ok: true }]))
- * @see {@link https://mswjs.io/docs/api/context/json `ctx.json()`}
- */
-export const json = (
- body: BodyTypeJSON,
-): ResponseTransformer => {
- return (res) => {
- res.headers.set('Content-Type', 'application/json')
- res.body = JSON.stringify(body) as any
-
- return res
- }
-}
diff --git a/src/context/set.test.ts b/src/context/set.test.ts
deleted file mode 100644
index fa061f4f1..000000000
--- a/src/context/set.test.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { set } from './set'
-import { response } from '../response'
-
-test('sets a single header', async () => {
- const { headers } = await response(set('Content-Type', 'image/*'))
- expect(headers.get('content-type')).toEqual('image/*')
-})
-
-test('sets a single header with multiple values', async () => {
- const { headers } = await response(
- set({
- Accept: ['application/json', 'image/png'],
- }),
- )
-
- expect(headers.get('accept')).toEqual('application/json, image/png')
-})
-
-test('sets multiple headers', async () => {
- const { headers } = await response(
- set({
- Accept: '*/*',
- 'Accept-Language': 'en',
- 'Content-Type': 'application/json',
- }),
- )
-
- expect(headers.get('accept')).toEqual('*/*')
- expect(headers.get('accept-language')).toEqual('en')
- expect(headers.get('content-type')).toEqual('application/json')
-})
diff --git a/src/context/set.ts b/src/context/set.ts
deleted file mode 100644
index 71ca89c52..000000000
--- a/src/context/set.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { objectToHeaders } from 'headers-polyfill'
-import { ResponseTransformer } from '../response'
-
-export type HeadersObject = Record<
- KeyType,
- string | string[]
->
-
-/**
- * @see https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
- */
-export type ForbiddenHeaderNames =
- | 'cookie'
- | 'cookie2'
- | 'set-cookie'
- | 'set-cookie2'
-
-export type ForbiddenHeaderError =
- `SafeResponseHeader: the '${HeaderName}' header cannot be set on the response. Please use the 'ctx.cookie()' function instead.`
-
-/**
- * Sets one or multiple response headers.
- * @example
- * ctx.set('Content-Type', 'text/plain')
- * ctx.set({
- * 'Accept': 'application/javascript',
- * 'Content-Type': "text/plain"
- * })
- * @see {@link https://mswjs.io/docs/api/context/set `ctx.set()`}
- */
-export function set(
- ...args: N extends string
- ? Lowercase extends ForbiddenHeaderNames
- ? [ForbiddenHeaderError]
- : [N, string]
- : N extends HeadersObject
- ? Lowercase extends ForbiddenHeaderNames
- ? [ForbiddenHeaderError]
- : [N]
- : [N]
-): ResponseTransformer {
- return (res) => {
- const [name, value] = args
-
- if (typeof name === 'string') {
- res.headers.append(name, value as string)
- } else {
- const headers = objectToHeaders(name)
- headers.forEach((value, name) => {
- res.headers.append(name, value)
- })
- }
-
- return res
- }
-}
diff --git a/src/context/status.test.ts b/src/context/status.test.ts
deleted file mode 100644
index 2bd795536..000000000
--- a/src/context/status.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { status } from './status'
-import { response } from '../response'
-
-test('sets given status code on the response', async () => {
- const result = await response(status(403))
- expect(result).toHaveProperty('status', 403)
- expect(result).toHaveProperty('statusText', 'Forbidden')
-})
-
-test('supports custom status text', async () => {
- const result = await response(status(301, 'Custom text'))
- expect(result).toHaveProperty('status', 301)
- expect(result).toHaveProperty('statusText', 'Custom text')
-})
diff --git a/src/context/status.ts b/src/context/status.ts
deleted file mode 100644
index e8113519c..000000000
--- a/src/context/status.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import statuses from 'statuses/codes.json'
-import { ResponseTransformer } from '../response'
-
-/**
- * Sets a response status code and text.
- * @example
- * res(ctx.status(301))
- * res(ctx.status(400, 'Custom status text'))
- * @see {@link https://mswjs.io/docs/api/context/status `ctx.status()`}
- */
-export const status = (
- statusCode: number,
- statusText?: string,
-): ResponseTransformer => {
- return (res) => {
- res.status = statusCode
- res.statusText =
- statusText || statuses[String(statusCode) as keyof typeof statuses]
-
- return res
- }
-}
diff --git a/src/context/text.test.ts b/src/context/text.test.ts
deleted file mode 100644
index aafc263b1..000000000
--- a/src/context/text.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { text } from './text'
-import { response } from '../response'
-
-test('sets a given text as the response body', async () => {
- const result = await response(text('Lorem ipsum'))
-
- expect(result.headers.get('content-type')).toEqual('text/plain')
- expect(result).toHaveProperty('body', 'Lorem ipsum')
-})
diff --git a/src/context/text.ts b/src/context/text.ts
deleted file mode 100644
index 6cdbb2d1d..000000000
--- a/src/context/text.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { ResponseTransformer } from '../response'
-
-/**
- * Sets a textual response body. Appends a `Content-Type: text/plain`
- * header on the mocked response.
- * @example res(ctx.text('Successful response'))
- * @see {@link https://mswjs.io/docs/api/context/text `ctx.text()`}
- */
-export const text = (
- body: BodyType,
-): ResponseTransformer => {
- return (res) => {
- res.headers.set('Content-Type', 'text/plain')
- res.body = body
- return res
- }
-}
diff --git a/src/context/xml.test.ts b/src/context/xml.test.ts
deleted file mode 100644
index 479e24817..000000000
--- a/src/context/xml.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import { xml } from './xml'
-import { response } from '../response'
-
-test('sets a given XML as the response body', async () => {
- const result = await response(xml('JohnJohnContent'))
- * @see {@link https://mswjs.io/docs/api/context/xml `ctx.xml()`}
- */
-export const xml = (
- body: BodyType,
-): ResponseTransformer => {
- return (res) => {
- res.headers.set('Content-Type', 'text/xml')
- res.body = body
- return res
- }
-}
diff --git a/src/core/HttpResponse.test.ts b/src/core/HttpResponse.test.ts
new file mode 100644
index 000000000..ef9ec2df7
--- /dev/null
+++ b/src/core/HttpResponse.test.ts
@@ -0,0 +1,65 @@
+/**
+ * @jest-environment node
+ */
+import { TextEncoder } from 'util'
+import { HttpResponse } from './HttpResponse'
+
+it('creates a plain response', async () => {
+ const response = new HttpResponse(null, { status: 301 })
+ expect(response.status).toBe(301)
+ expect(response.statusText).toBe('Moved Permanently')
+ expect(response.body).toBe(null)
+ expect(await response.text()).toBe('')
+})
+
+it('creates a text response', async () => {
+ const response = HttpResponse.text('hello world', { status: 201 })
+
+ expect(response.status).toBe(201)
+ expect(response.statusText).toBe('Created')
+ expect(response.body).toBeInstanceOf(ReadableStream)
+ expect(await response.text()).toBe('hello world')
+})
+
+it('creates a json response', async () => {
+ const response = HttpResponse.json({ firstName: 'John' })
+
+ expect(response.status).toBe(200)
+ expect(response.statusText).toBe('OK')
+ expect(response.body).toBeInstanceOf(ReadableStream)
+ expect(await response.json()).toEqual({ firstName: 'John' })
+})
+
+it('creates an xml response', async () => {
+ const response = HttpResponse.xml('')
+
+ expect(response.status).toBe(200)
+ expect(response.statusText).toBe('OK')
+ expect(response.body).toBeInstanceOf(ReadableStream)
+ expect(await response.text()).toBe('')
+})
+
+it('creates an array buffer response', async () => {
+ const buffer = new TextEncoder().encode('hello world')
+ const response = HttpResponse.arrayBuffer(buffer)
+
+ expect(response.status).toBe(200)
+ expect(response.statusText).toBe('OK')
+ expect(response.body).toBeInstanceOf(ReadableStream)
+
+ const responseData = await response.arrayBuffer()
+ expect(responseData).toEqual(buffer.buffer)
+})
+
+it('creates a form data response', async () => {
+ const formData = new FormData()
+ formData.append('firstName', 'John')
+ const response = HttpResponse.formData(formData)
+
+ expect(response.status).toBe(200)
+ expect(response.statusText).toBe('OK')
+ expect(response.body).toBeInstanceOf(ReadableStream)
+
+ const responseData = await response.formData()
+ expect(responseData.get('firstName')).toBe('John')
+})
diff --git a/src/core/HttpResponse.ts b/src/core/HttpResponse.ts
new file mode 100644
index 000000000..e5e51c482
--- /dev/null
+++ b/src/core/HttpResponse.ts
@@ -0,0 +1,122 @@
+import type { DefaultBodyType } from './handlers/RequestHandler'
+import {
+ decorateResponse,
+ normalizeResponseInit,
+} from './utils/HttpResponse/decorators'
+
+export interface HttpResponseInit extends ResponseInit {
+ type?: ResponseType
+}
+
+declare const bodyType: unique symbol
+
+export interface StrictRequest
+ extends Request {
+ json(): Promise
+}
+
+/**
+ * Opaque `Response` type that supports strict body type.
+ */
+export interface StrictResponse
+ extends Response {
+ readonly [bodyType]: BodyType
+}
+
+/**
+ * A drop-in replacement for the standard `Response` class
+ * to allow additional features, like mocking the response `Set-Cookie` header.
+ *
+ * @example
+ * new HttpResponse('Hello world', { status: 201 })
+ * HttpResponse.json({ name: 'John' })
+ * HttpResponse.formData(form)
+ *
+ * @see {@link https://mswjs.io/docs/api/http-response `HttpResponse` API reference}
+ */
+export class HttpResponse extends Response {
+ constructor(body?: BodyInit | null, init?: HttpResponseInit) {
+ const responseInit = normalizeResponseInit(init)
+ super(body, responseInit)
+ decorateResponse(this, responseInit)
+ }
+
+ /**
+ * Create a `Response` with a `Content-Type: "text/plain"` body.
+ * @example
+ * HttpResponse.text('hello world')
+ * HttpResponse.text('Error', { status: 500 })
+ */
+ static text(
+ body?: BodyType | null,
+ init?: HttpResponseInit,
+ ): StrictResponse {
+ const responseInit = normalizeResponseInit(init)
+ responseInit.headers.set('Content-Type', 'text/plain')
+ return new HttpResponse(body, responseInit) as StrictResponse
+ }
+
+ /**
+ * Create a `Response` with a `Content-Type: "application/json"` body.
+ * @example
+ * HttpResponse.json({ firstName: 'John' })
+ * HttpResponse.json({ error: 'Not Authorized' }, { status: 401 })
+ */
+ static json(
+ body?: BodyType | null,
+ init?: HttpResponseInit,
+ ): StrictResponse {
+ const responseInit = normalizeResponseInit(init)
+ responseInit.headers.set('Content-Type', 'application/json')
+ return new HttpResponse(
+ JSON.stringify(body),
+ responseInit,
+ ) as StrictResponse
+ }
+
+ /**
+ * Create a `Response` with a `Content-Type: "application/xml"` body.
+ * @example
+ * HttpResponse.xml(``)
+ * HttpResponse.xml(``, { status: 201 })
+ */
+ static xml(
+ body?: BodyType | null,
+ init?: HttpResponseInit,
+ ): Response {
+ const responseInit = normalizeResponseInit(init)
+ responseInit.headers.set('Content-Type', 'text/xml')
+ return new HttpResponse(body, responseInit)
+ }
+
+ /**
+ * Create a `Response` with an `ArrayBuffer` body.
+ * @example
+ * const buffer = new ArrayBuffer(3)
+ * const view = new Uint8Array(buffer)
+ * view.set([1, 2, 3])
+ *
+ * HttpResponse.arrayBuffer(buffer)
+ */
+ static arrayBuffer(body?: ArrayBuffer, init?: HttpResponseInit): Response {
+ const responseInit = normalizeResponseInit(init)
+
+ if (body) {
+ responseInit.headers.set('Content-Length', body.byteLength.toString())
+ }
+
+ return new HttpResponse(body, responseInit)
+ }
+
+ /**
+ * Create a `Response` with a `FormData` body.
+ * @example
+ * const data = new FormData()
+ * data.set('name', 'Alice')
+ *
+ * HttpResponse.formData(data)
+ */
+ static formData(body?: FormData, init?: HttpResponseInit): Response {
+ return new HttpResponse(body, normalizeResponseInit(init))
+ }
+}
diff --git a/src/SetupApi.ts b/src/core/SetupApi.ts
similarity index 84%
rename from src/SetupApi.ts
rename to src/core/SetupApi.ts
index d345c82fa..d7cae2f48 100644
--- a/src/SetupApi.ts
+++ b/src/core/SetupApi.ts
@@ -1,7 +1,6 @@
import { invariant } from 'outvariant'
import { EventMap, Emitter } from 'strict-event-emitter'
import {
- DefaultBodyType,
RequestHandler,
RequestHandlerDefaultInfo,
} from './handlers/RequestHandler'
@@ -9,12 +8,12 @@ import { LifeCycleEventEmitter } from './sharedOptions'
import { devUtils } from './utils/internal/devUtils'
import { pipeEvents } from './utils/internal/pipeEvents'
import { toReadonlyArray } from './utils/internal/toReadonlyArray'
-import { MockedRequest } from './utils/request/MockedRequest'
+import { Disposable } from './utils/internal/Disposable'
/**
* Generic class for the mock API setup.
*/
-export abstract class SetupApi {
+export abstract class SetupApi extends Disposable {
protected initialHandlers: ReadonlyArray
protected currentHandlers: Array
protected readonly emitter: Emitter
@@ -23,6 +22,8 @@ export abstract class SetupApi {
public readonly events: LifeCycleEventEmitter
constructor(...initialHandlers: Array) {
+ super()
+
this.validateHandlers(...initialHandlers)
this.initialHandlers = toReadonlyArray(initialHandlers)
@@ -33,6 +34,11 @@ export abstract class SetupApi {
pipeEvents(this.emitter, this.publicEmitter)
this.events = this.createLifeCycleEvents()
+
+ this.subscriptions.push(() => {
+ this.emitter.removeAllListeners()
+ this.publicEmitter.removeAllListeners()
+ })
}
private validateHandlers(...handlers: ReadonlyArray): void {
@@ -48,18 +54,13 @@ export abstract class SetupApi {
}
}
- protected dispose(): void {
- this.emitter.removeAllListeners()
- this.publicEmitter.removeAllListeners()
- }
-
public use(...runtimeHandlers: Array): void {
this.currentHandlers.unshift(...runtimeHandlers)
}
public restoreHandlers(): void {
this.currentHandlers.forEach((handler) => {
- handler.markAsSkipped(false)
+ handler.isUsed = false
})
}
@@ -69,12 +70,7 @@ export abstract class SetupApi {
}
public listHandlers(): ReadonlyArray<
- RequestHandler<
- RequestHandlerDefaultInfo,
- MockedRequest,
- any,
- MockedRequest
- >
+ RequestHandler
> {
return toReadonlyArray(this.currentHandlers)
}
@@ -92,6 +88,4 @@ export abstract class SetupApi {
},
}
}
-
- abstract printHandlers(): void
}
diff --git a/src/core/bypass.test.ts b/src/core/bypass.test.ts
new file mode 100644
index 000000000..aca432070
--- /dev/null
+++ b/src/core/bypass.test.ts
@@ -0,0 +1,47 @@
+/**
+ * @jest-environment jsdom
+ */
+import { bypass } from './bypass'
+
+it('returns bypassed request given a request url string', async () => {
+ const request = bypass('https://api.example.com/resource')
+
+ // Relative URLs are rebased against the current location.
+ expect(request.method).toBe('GET')
+ expect(request.url).toBe('https://api.example.com/resource')
+ expect(Object.fromEntries(request.headers.entries())).toEqual({
+ 'x-msw-intention': 'bypass',
+ })
+})
+
+it('returns bypassed request given a request url', async () => {
+ const request = bypass(new URL('/resource', 'https://api.example.com'))
+
+ expect(request.url).toBe('https://api.example.com/resource')
+ expect(Object.fromEntries(request.headers)).toEqual({
+ 'x-msw-intention': 'bypass',
+ })
+})
+
+it('returns bypassed request given request instance', async () => {
+ const original = new Request('http://localhost/resource', {
+ method: 'POST',
+ headers: {
+ 'X-My-Header': 'value',
+ },
+ body: 'hello world',
+ })
+ const request = bypass(original)
+
+ expect(request.method).toBe('POST')
+ expect(request.url).toBe('http://localhost/resource')
+
+ const bypassedRequestBody = await request.text()
+ expect(original.bodyUsed).toBe(false)
+
+ expect(bypassedRequestBody).toEqual(await original.text())
+ expect(Object.fromEntries(request.headers.entries())).toEqual({
+ ...Object.fromEntries(original.headers.entries()),
+ 'x-msw-intention': 'bypass',
+ })
+})
diff --git a/src/core/bypass.ts b/src/core/bypass.ts
new file mode 100644
index 000000000..e467cf30c
--- /dev/null
+++ b/src/core/bypass.ts
@@ -0,0 +1,36 @@
+import { invariant } from 'outvariant'
+
+export type BypassRequestInput = string | URL | Request
+
+/**
+ * Creates a `Request` instance that will always be ignored by MSW.
+ *
+ * @example
+ * import { bypass } from 'msw'
+ *
+ * fetch(bypass('/resource'))
+ * fetch(bypass(new URL('/resource', 'https://example.com)))
+ * fetch(bypass(new Request('https://example.com/resource')))
+ *
+ * @see {@link https://mswjs.io/docs/api/bypass `bypass()` API reference}
+ */
+export function bypass(input: BypassRequestInput, init?: RequestInit): Request {
+ const request = input instanceof Request ? input : new Request(input, init)
+
+ invariant(
+ !request.bodyUsed,
+ 'Failed to create a bypassed request to "%s %s": given request instance already has its body read. Make sure to clone the intercepted request if you wish to read its body before bypassing it.',
+ request.method,
+ request.url,
+ )
+
+ const requestClone = request.clone()
+
+ // Set the internal header that would instruct MSW
+ // to bypass this request from any further request matching.
+ // Unlike "passthrough()", bypass is meant for performing
+ // additional requests within pending request resolution.
+ requestClone.headers.set('x-msw-intention', 'bypass')
+
+ return requestClone
+}
diff --git a/src/core/delay.ts b/src/core/delay.ts
new file mode 100644
index 000000000..0244567ed
--- /dev/null
+++ b/src/core/delay.ts
@@ -0,0 +1,70 @@
+import { isNodeProcess } from 'is-node-process'
+
+export const SET_TIMEOUT_MAX_ALLOWED_INT = 2147483647
+export const MIN_SERVER_RESPONSE_TIME = 100
+export const MAX_SERVER_RESPONSE_TIME = 400
+export const NODE_SERVER_RESPONSE_TIME = 5
+
+function getRealisticResponseTime(): number {
+ if (isNodeProcess()) {
+ return NODE_SERVER_RESPONSE_TIME
+ }
+
+ return Math.floor(
+ Math.random() * (MAX_SERVER_RESPONSE_TIME - MIN_SERVER_RESPONSE_TIME) +
+ MIN_SERVER_RESPONSE_TIME,
+ )
+}
+
+export type DelayMode = 'real' | 'infinite'
+
+/**
+ * Delays the response by the given duration (ms).
+ *
+ * @example
+ * await delay() // emulate realistic server response time
+ * await delay(1200) // delay response by 1200ms
+ * await delay('infinite') // delay response infinitely
+ *
+ * @see {@link https://mswjs.io/docs/api/delay `delay()` API reference}
+ */
+export async function delay(
+ durationOrMode?: DelayMode | number,
+): Promise {
+ let delayTime: number
+
+ if (typeof durationOrMode === 'string') {
+ switch (durationOrMode) {
+ case 'infinite': {
+ // Using `Infinity` as a delay value executes the response timeout immediately.
+ // Instead, use the maximum allowed integer for `setTimeout`.
+ delayTime = SET_TIMEOUT_MAX_ALLOWED_INT
+ break
+ }
+ case 'real': {
+ delayTime = getRealisticResponseTime()
+ break
+ }
+ default: {
+ throw new Error(
+ `Failed to delay a response: unknown delay mode "${durationOrMode}". Please make sure you provide one of the supported modes ("real", "infinite") or a number.`,
+ )
+ }
+ }
+ } else if (typeof durationOrMode === 'undefined') {
+ // Use random realistic server response time when no explicit delay duration was provided.
+ delayTime = getRealisticResponseTime()
+ } else {
+ // Guard against passing values like `Infinity` or `Number.MAX_VALUE`
+ // as the response delay duration. They don't produce the result you may expect.
+ if (durationOrMode > SET_TIMEOUT_MAX_ALLOWED_INT) {
+ throw new Error(
+ `Failed to delay a response: provided delay duration (${durationOrMode}) exceeds the maximum allowed duration for "setTimeout" (${SET_TIMEOUT_MAX_ALLOWED_INT}). This will cause the response to be returned immediately. Please use a number within the allowed range to delay the response by exact duration, or consider the "infinite" delay mode to delay the response indefinitely.`,
+ )
+ }
+
+ delayTime = durationOrMode
+ }
+
+ return new Promise((resolve) => setTimeout(resolve, delayTime))
+}
diff --git a/src/graphql.test.ts b/src/core/graphql.test.ts
similarity index 100%
rename from src/graphql.test.ts
rename to src/core/graphql.test.ts
index 0658b09a5..e783d0fbd 100644
--- a/src/graphql.test.ts
+++ b/src/core/graphql.test.ts
@@ -3,9 +3,9 @@ import { graphql } from './graphql'
test('exports supported GraphQL operation types', () => {
expect(graphql).toBeDefined()
expect(Object.keys(graphql)).toEqual([
- 'operation',
'query',
'mutation',
+ 'operation',
'link',
])
})
diff --git a/src/core/graphql.ts b/src/core/graphql.ts
new file mode 100644
index 000000000..b3409eb7f
--- /dev/null
+++ b/src/core/graphql.ts
@@ -0,0 +1,138 @@
+import type { DocumentNode, OperationTypeNode } from 'graphql'
+import {
+ ResponseResolver,
+ RequestHandlerOptions,
+} from './handlers/RequestHandler'
+import {
+ GraphQLHandler,
+ GraphQLVariables,
+ ExpectedOperationTypeNode,
+ GraphQLHandlerNameSelector,
+ GraphQLResolverExtras,
+ GraphQLResponseBody,
+} from './handlers/GraphQLHandler'
+import type { Path } from './utils/matching/matchRequestUrl'
+
+export interface TypedDocumentNode<
+ Result = { [key: string]: any },
+ Variables = { [key: string]: any },
+> extends DocumentNode {
+ __apiType?: (variables: Variables) => Result
+ __resultType?: Result
+ __variablesType?: Variables
+}
+
+function createScopedGraphQLHandler(
+ operationType: ExpectedOperationTypeNode,
+ url: Path,
+) {
+ return <
+ Query extends Record,
+ Variables extends GraphQLVariables = GraphQLVariables,
+ >(
+ operationName:
+ | GraphQLHandlerNameSelector
+ | DocumentNode
+ | TypedDocumentNode,
+ resolver: ResponseResolver<
+ GraphQLResolverExtras,
+ null,
+ GraphQLResponseBody
+ >,
+ options: RequestHandlerOptions = {},
+ ) => {
+ return new GraphQLHandler(
+ operationType,
+ operationName,
+ url,
+ resolver,
+ options,
+ )
+ }
+}
+
+function createGraphQLOperationHandler(url: Path) {
+ return <
+ Query extends Record,
+ Variables extends GraphQLVariables = GraphQLVariables,
+ >(
+ resolver: ResponseResolver<
+ GraphQLResolverExtras,
+ null,
+ GraphQLResponseBody
+ >,
+ ) => {
+ return new GraphQLHandler('all', new RegExp('.*'), url, resolver)
+ }
+}
+
+const standardGraphQLHandlers = {
+ /**
+ * Intercepts a GraphQL query by a given name.
+ *
+ * @example
+ * graphql.query('GetUser', () => {
+ * return HttpResponse.json({ data: { user: { name: 'John' } } })
+ * })
+ *
+ * @see {@link https://mswjs.io/docs/api/graphql#graphqlqueryqueryname-resolver `graphql.query()` API reference}
+ */
+ query: createScopedGraphQLHandler('query' as OperationTypeNode, '*'),
+
+ /**
+ * Intercepts a GraphQL mutation by its name.
+ *
+ * @example
+ * graphql.mutation('SavePost', () => {
+ * return HttpResponse.json({ data: { post: { id: 'abc-123 } } })
+ * })
+ *
+ * @see {@link https://mswjs.io/docs/api/graphql#graphqlmutationmutationname-resolver `graphql.query()` API reference}
+ *
+ */
+ mutation: createScopedGraphQLHandler('mutation' as OperationTypeNode, '*'),
+
+ /**
+ * Intercepts any GraphQL operation, regardless of its type or name.
+ *
+ * @example
+ * graphql.operation(() => {
+ * return HttpResponse.json({ data: { name: 'John' } })
+ * })
+ *
+ * @see {@link https://mswjs.io/docs/api/graphql#graphloperationresolver `graphql.operation()` API reference}
+ */
+ operation: createGraphQLOperationHandler('*'),
+}
+
+function createGraphQLLink(url: Path): typeof standardGraphQLHandlers {
+ return {
+ operation: createGraphQLOperationHandler(url),
+ query: createScopedGraphQLHandler('query' as OperationTypeNode, url),
+ mutation: createScopedGraphQLHandler('mutation' as OperationTypeNode, url),
+ }
+}
+
+/**
+ * A namespace to intercept and mock GraphQL operations
+ *
+ * @example
+ * graphql.query('GetUser', resolver)
+ * graphql.mutation('DeletePost', resolver)
+ *
+ * @see {@link https://mswjs.io/docs/api/graphql `graphql` API reference}
+ */
+export const graphql = {
+ ...standardGraphQLHandlers,
+
+ /**
+ * Intercepts GraphQL operations scoped by the given URL.
+ *
+ * @example
+ * const github = graphql.link('https://api.github.com/graphql')
+ * github.query('GetRepo', resolver)
+ *
+ * @see {@link https://mswjs.io/docs/api/graphql#graphqllinkurl `graphql.link()` API reference}
+ */
+ link: createGraphQLLink,
+}
diff --git a/src/handlers/GraphQLHandler.test.ts b/src/core/handlers/GraphQLHandler.test.ts
similarity index 72%
rename from src/handlers/GraphQLHandler.test.ts
rename to src/core/handlers/GraphQLHandler.test.ts
index 4fefa5d86..cebac6440 100644
--- a/src/handlers/GraphQLHandler.test.ts
+++ b/src/core/handlers/GraphQLHandler.test.ts
@@ -3,27 +3,25 @@
*/
import { encodeBuffer } from '@mswjs/interceptors'
import { OperationTypeNode, parse } from 'graphql'
-import { Headers } from 'headers-polyfill'
-import { context, MockedRequest, MockedRequestInit } from '..'
-import { response } from '../response'
import {
- GraphQLContext,
GraphQLHandler,
- GraphQLRequest,
GraphQLRequestBody,
+ GraphQLResolverExtras,
isDocumentNode,
} from './GraphQLHandler'
+import { HttpResponse } from '../HttpResponse'
import { ResponseResolver } from './RequestHandler'
-const resolver: ResponseResolver<
- GraphQLRequest<{ userId: string }>,
- GraphQLContext
-> = (req, res, ctx) => {
- return res(
- ctx.data({
- user: { id: req.variables.userId },
- }),
- )
+const resolver: ResponseResolver> = ({
+ variables,
+}) => {
+ return HttpResponse.json({
+ data: {
+ user: {
+ id: variables.userId,
+ },
+ },
+ })
}
function createGetGraphQLRequest(
@@ -33,17 +31,15 @@ function createGetGraphQLRequest(
const requestUrl = new URL(hostname)
requestUrl.searchParams.set('query', body?.query)
requestUrl.searchParams.set('variables', JSON.stringify(body?.variables))
- return new MockedRequest(requestUrl)
+ return new Request(requestUrl)
}
function createPostGraphQLRequest(
body: GraphQLRequestBody,
hostname = 'https://example.com',
- requestInit: MockedRequestInit = {},
) {
- return new MockedRequest(new URL(hostname), {
+ return new Request(new URL(hostname), {
method: 'POST',
- ...requestInit,
headers: new Headers({ 'Content-Type': 'application/json' }),
body: encodeBuffer(JSON.stringify(body)),
})
@@ -152,7 +148,7 @@ describe('info', () => {
describe('parse', () => {
describe('query', () => {
- test('parses a query without variables (GET)', () => {
+ test('parses a query without variables (GET)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -163,14 +159,15 @@ describe('parse', () => {
query: GET_USER,
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'query',
operationName: 'GetUser',
+ query: GET_USER,
variables: undefined,
})
})
- test('parses a query with variables (GET)', () => {
+ test('parses a query with variables (GET)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -184,16 +181,17 @@ describe('parse', () => {
},
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'query',
operationName: 'GetUser',
+ query: GET_USER,
variables: {
userId: 'abc-123',
},
})
})
- test('parses a query without variables (POST)', () => {
+ test('parses a query without variables (POST)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -204,14 +202,15 @@ describe('parse', () => {
query: GET_USER,
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'query',
operationName: 'GetUser',
+ query: GET_USER,
variables: undefined,
})
})
- test('parses a query with variables (POST)', () => {
+ test('parses a query with variables (POST)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -225,9 +224,10 @@ describe('parse', () => {
},
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'query',
operationName: 'GetUser',
+ query: GET_USER,
variables: {
userId: 'abc-123',
},
@@ -236,7 +236,7 @@ describe('parse', () => {
})
describe('mutation', () => {
- test('parses a mutation without variables (GET)', () => {
+ test('parses a mutation without variables (GET)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.MUTATION,
'GetUser',
@@ -247,14 +247,15 @@ describe('parse', () => {
query: LOGIN,
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'mutation',
operationName: 'Login',
+ query: LOGIN,
variables: undefined,
})
})
- test('parses a mutation with variables (GET)', () => {
+ test('parses a mutation with variables (GET)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.MUTATION,
'GetUser',
@@ -268,16 +269,17 @@ describe('parse', () => {
},
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'mutation',
operationName: 'Login',
+ query: LOGIN,
variables: {
userId: 'abc-123',
},
})
})
- test('parses a mutation without variables (POST)', () => {
+ test('parses a mutation without variables (POST)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.MUTATION,
'GetUser',
@@ -288,14 +290,15 @@ describe('parse', () => {
query: LOGIN,
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'mutation',
operationName: 'Login',
+ query: LOGIN,
variables: undefined,
})
})
- test('parses a mutation with variables (POST)', () => {
+ test('parses a mutation with variables (POST)', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.MUTATION,
'GetUser',
@@ -309,9 +312,10 @@ describe('parse', () => {
},
})
- expect(handler.parse(request)).toEqual({
+ expect(await handler.parse({ request })).toEqual({
operationType: 'mutation',
operationName: 'Login',
+ query: LOGIN,
variables: {
userId: 'abc-123',
},
@@ -321,7 +325,7 @@ describe('parse', () => {
})
describe('predicate', () => {
- test('respects operation type', () => {
+ test('respects operation type', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -335,13 +339,21 @@ describe('predicate', () => {
query: LOGIN,
})
- expect(handler.predicate(request, handler.parse(request))).toBe(true)
- expect(handler.predicate(alienRequest, handler.parse(alienRequest))).toBe(
- false,
- )
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
+ expect(
+ handler.predicate({
+ request: alienRequest,
+ parsedResult: await handler.parse({ request: alienRequest }),
+ }),
+ ).toBe(false)
})
- test('respects operation name', () => {
+ test('respects operation name', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -361,13 +373,21 @@ describe('predicate', () => {
`,
})
- expect(handler.predicate(request, handler.parse(request))).toBe(true)
- expect(handler.predicate(alienRequest, handler.parse(alienRequest))).toBe(
- false,
- )
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
+ expect(
+ handler.predicate({
+ request: alienRequest,
+ parsedResult: await handler.parse({ request: alienRequest }),
+ }),
+ ).toBe(false)
})
- test('allows anonymous GraphQL opertaions when using "all" expected operation type', () => {
+ test('allows anonymous GraphQL opertaions when using "all" expected operation type', async () => {
const handler = new GraphQLHandler('all', new RegExp('.*'), '*', resolver)
const request = createPostGraphQLRequest({
query: `
@@ -380,10 +400,15 @@ describe('predicate', () => {
`,
})
- expect(handler.predicate(request, handler.parse(request))).toBe(true)
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
})
- test('respects custom endpoint', () => {
+ test('respects custom endpoint', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -400,15 +425,23 @@ describe('predicate', () => {
query: GET_USER,
})
- expect(handler.predicate(request, handler.parse(request))).toBe(true)
- expect(handler.predicate(alienRequest, handler.parse(alienRequest))).toBe(
- false,
- )
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
+ expect(
+ handler.predicate({
+ request: alienRequest,
+ parsedResult: await handler.parse({ request: alienRequest }),
+ }),
+ ).toBe(false)
})
})
describe('test', () => {
- test('respects operation type', () => {
+ test('respects operation type', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -422,11 +455,11 @@ describe('test', () => {
query: LOGIN,
})
- expect(handler.test(request)).toBe(true)
- expect(handler.test(alienRequest)).toBe(false)
+ expect(await handler.test({ request })).toBe(true)
+ expect(await handler.test({ request: alienRequest })).toBe(false)
})
- test('respects operation name', () => {
+ test('respects operation name', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -446,11 +479,11 @@ describe('test', () => {
`,
})
- expect(handler.test(request)).toBe(true)
- expect(handler.test(alienRequest)).toBe(false)
+ expect(await handler.test({ request })).toBe(true)
+ expect(await handler.test({ request: alienRequest })).toBe(false)
})
- test('respects custom endpoint', () => {
+ test('respects custom endpoint', async () => {
const handler = new GraphQLHandler(
OperationTypeNode.QUERY,
'GetUser',
@@ -467,8 +500,8 @@ describe('test', () => {
query: GET_USER,
})
- expect(handler.test(request)).toBe(true)
- expect(handler.test(alienRequest)).toBe(false)
+ expect(await handler.test({ request })).toBe(true)
+ expect(await handler.test({ request: alienRequest })).toBe(false)
})
})
@@ -486,28 +519,27 @@ describe('run', () => {
userId: 'abc-123',
},
})
- const result = await handler.run(request)
+ const result = await handler.run({ request })
- expect(result).toMatchObject({
- handler,
- request: {
- ...request,
- variables: {
- userId: 'abc-123',
- },
- },
- parsedResult: {
- operationType: 'query',
- operationName: 'GetUser',
- variables: {
- userId: 'abc-123',
- },
+ expect(result!.handler).toEqual(handler)
+ expect(result!.parsedResult).toEqual({
+ operationType: 'query',
+ operationName: 'GetUser',
+ query: GET_USER,
+ variables: {
+ userId: 'abc-123',
},
- response: await response(
- context.data({
- user: { id: 'abc-123' },
- }),
- ),
+ })
+ expect(result!.request.method).toBe('POST')
+ expect(result!.request.url).toBe('https://example.com/')
+ expect(await result!.request.json()).toEqual({
+ query: GET_USER,
+ variables: { userId: 'abc-123' },
+ })
+ expect(result!.response?.status).toBe(200)
+ expect(result!.response?.statusText).toBe('OK')
+ expect(await result!.response?.json()).toEqual({
+ data: { user: { id: 'abc-123' } },
})
})
@@ -521,7 +553,7 @@ describe('run', () => {
const request = createPostGraphQLRequest({
query: LOGIN,
})
- const result = await handler.run(request)
+ const result = await handler.run({ request })
expect(result).toBeNull()
})
@@ -568,7 +600,7 @@ describe('request', () => {
`,
})
- await handler.run(request)
+ await handler.run({ request })
expect(matchAllResolver).toHaveBeenCalledTimes(1)
expect(matchAllResolver.mock.calls[0][0]).toHaveProperty(
diff --git a/src/handlers/GraphQLHandler.ts b/src/core/handlers/GraphQLHandler.ts
similarity index 51%
rename from src/handlers/GraphQLHandler.ts
rename to src/core/handlers/GraphQLHandler.ts
index c44e6dae1..0c3e56e3b 100644
--- a/src/handlers/GraphQLHandler.ts
+++ b/src/core/handlers/GraphQLHandler.ts
@@ -1,22 +1,15 @@
-import type { DocumentNode, OperationTypeNode } from 'graphql'
-import { SerializedResponse } from '../setupWorker/glossary'
-import { data } from '../context/data'
-import { extensions } from '../context/extensions'
-import { errors } from '../context/errors'
-import { field } from '../context/field'
-import { GraphQLPayloadContext } from '../typeUtils'
-import { cookie } from '../context/cookie'
+import type { DocumentNode, GraphQLError, OperationTypeNode } from 'graphql'
import {
- defaultContext,
- DefaultContext,
+ DefaultBodyType,
RequestHandler,
RequestHandlerDefaultInfo,
+ RequestHandlerOptions,
ResponseResolver,
} from './RequestHandler'
import { getTimestamp } from '../utils/logging/getTimestamp'
import { getStatusCodeColor } from '../utils/logging/getStatusCodeColor'
-import { prepareRequest } from '../utils/logging/prepareRequest'
-import { prepareResponse } from '../utils/logging/prepareResponse'
+import { serializeRequest } from '../utils/logging/serializeRequest'
+import { serializeResponse } from '../utils/logging/serializeResponse'
import { matchRequestUrl, Path } from '../utils/matching/matchRequestUrl'
import {
ParsedGraphQLRequest,
@@ -25,34 +18,11 @@ import {
parseDocumentNode,
} from '../utils/internal/parseGraphQLRequest'
import { getPublicUrlFromRequest } from '../utils/request/getPublicUrlFromRequest'
-import { tryCatch } from '../utils/internal/tryCatch'
import { devUtils } from '../utils/internal/devUtils'
-import { MockedRequest } from '../utils/request/MockedRequest'
export type ExpectedOperationTypeNode = OperationTypeNode | 'all'
export type GraphQLHandlerNameSelector = DocumentNode | RegExp | string
-// GraphQL related context should contain utility functions
-// useful for GraphQL. Functions like `xml()` bear no value
-// in the GraphQL universe.
-export type GraphQLContext> =
- DefaultContext & {
- data: GraphQLPayloadContext
- extensions: GraphQLPayloadContext
- errors: typeof errors
- cookie: typeof cookie
- field: typeof field
- }
-
-export const graphqlContext: GraphQLContext = {
- ...defaultContext,
- data,
- extensions,
- errors,
- cookie,
- field,
-}
-
export type GraphQLVariables = Record
export interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
@@ -60,6 +30,12 @@ export interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
operationName: GraphQLHandlerNameSelector
}
+export type GraphQLResolverExtras = {
+ query: string
+ operationName: string
+ variables: Variables
+}
+
export type GraphQLRequestBody =
| GraphQLJsonRequestBody
| GraphQLMultipartRequestBody
@@ -71,6 +47,11 @@ export interface GraphQLJsonRequestBody {
variables?: Variables
}
+export interface GraphQLResponseBody {
+ data?: BodyType
+ errors?: readonly Partial[]
+}
+
export function isDocumentNode(
value: DocumentNode | any,
): value is DocumentNode {
@@ -81,31 +62,10 @@ export function isDocumentNode(
return typeof value === 'object' && 'kind' in value && 'definitions' in value
}
-export class GraphQLRequest<
- Variables extends GraphQLVariables,
-> extends MockedRequest> {
- constructor(
- request: MockedRequest,
- public readonly variables: Variables,
- public readonly operationName: string,
- ) {
- super(request.url, {
- ...request,
- /**
- * TODO(https://github.com/mswjs/msw/issues/1318): Cleanup
- */
- body: request['_body'],
- })
- }
-}
-
-export class GraphQLHandler<
- Request extends GraphQLRequest = GraphQLRequest,
-> extends RequestHandler<
+export class GraphQLHandler extends RequestHandler<
GraphQLHandlerInfo,
- Request,
- ParsedGraphQLRequest | null,
- GraphQLRequest
+ ParsedGraphQLRequest,
+ GraphQLResolverExtras
> {
private endpoint: Path
@@ -113,7 +73,8 @@ export class GraphQLHandler<
operationType: ExpectedOperationTypeNode,
operationName: GraphQLHandlerNameSelector,
endpoint: Path,
- resolver: ResponseResolver,
+ resolver: ResponseResolver, any, any>,
+ options?: RequestHandlerOptions,
) {
let resolvedOperationName = operationName
@@ -146,55 +107,47 @@ export class GraphQLHandler<
operationType,
operationName: resolvedOperationName,
},
- ctx: graphqlContext,
resolver,
+ options,
})
this.endpoint = endpoint
}
- parse(request: MockedRequest) {
- return tryCatch(
- () => parseGraphQLRequest(request),
- (error) => console.error(error.message),
- )
- }
-
- protected getPublicRequest(
- request: Request,
- parsedResult: ParsedGraphQLRequest,
- ): GraphQLRequest {
- return new GraphQLRequest(
- request,
- parsedResult?.variables ?? {},
- parsedResult?.operationName ?? '',
- )
+ async parse(args: { request: Request }) {
+ return parseGraphQLRequest(args.request).catch((error) => {
+ console.error(error)
+ return undefined
+ })
}
- predicate(request: MockedRequest, parsedResult: ParsedGraphQLRequest) {
- if (!parsedResult) {
+ predicate(args: { request: Request; parsedResult: ParsedGraphQLRequest }) {
+ if (!args.parsedResult) {
return false
}
- if (!parsedResult.operationName && this.info.operationType !== 'all') {
- const publicUrl = getPublicUrlFromRequest(request)
+ if (!args.parsedResult.operationName && this.info.operationType !== 'all') {
+ const publicUrl = getPublicUrlFromRequest(args.request)
+
devUtils.warn(`\
-Failed to intercept a GraphQL request at "${request.method} ${publicUrl}": anonymous GraphQL operations are not supported.
+Failed to intercept a GraphQL request at "${args.request.method} ${publicUrl}": anonymous GraphQL operations are not supported.
-Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation\
- `)
+Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`)
return false
}
- const hasMatchingUrl = matchRequestUrl(request.url, this.endpoint)
+ const hasMatchingUrl = matchRequestUrl(
+ new URL(args.request.url),
+ this.endpoint,
+ )
const hasMatchingOperationType =
this.info.operationType === 'all' ||
- parsedResult.operationType === this.info.operationType
+ args.parsedResult.operationType === this.info.operationType
const hasMatchingOperationName =
this.info.operationName instanceof RegExp
- ? this.info.operationName.test(parsedResult.operationName || '')
- : parsedResult.operationName === this.info.operationName
+ ? this.info.operationName.test(args.parsedResult.operationName || '')
+ : args.parsedResult.operationName === this.info.operationName
return (
hasMatchingUrl.matches &&
@@ -203,24 +156,35 @@ Consider naming this operation or using "graphql.operation()" request handler to
)
}
- log(
- request: Request,
- response: SerializedResponse,
- parsedRequest: ParsedGraphQLRequest,
- ) {
- const loggedRequest = prepareRequest(request)
- const loggedResponse = prepareResponse(response)
- const statusColor = getStatusCodeColor(response.status)
- const requestInfo = parsedRequest?.operationName
- ? `${parsedRequest?.operationType} ${parsedRequest?.operationName}`
- : `anonymous ${parsedRequest?.operationType}`
+ protected extendResolverArgs(args: {
+ request: Request
+ parsedResult: ParsedGraphQLRequest
+ }) {
+ return {
+ query: args.parsedResult?.query || '',
+ operationName: args.parsedResult?.operationName || '',
+ variables: args.parsedResult?.variables || {},
+ }
+ }
+
+ async log(args: {
+ request: Request
+ response: Response
+ parsedResult: ParsedGraphQLRequest
+ }) {
+ const loggedRequest = await serializeRequest(args.request)
+ const loggedResponse = await serializeResponse(args.response)
+ const statusColor = getStatusCodeColor(loggedResponse.status)
+ const requestInfo = args.parsedResult?.operationName
+ ? `${args.parsedResult?.operationType} ${args.parsedResult?.operationName}`
+ : `anonymous ${args.parsedResult?.operationType}`
console.groupCollapsed(
devUtils.formatMessage('%s %s (%c%s%c)'),
getTimestamp(),
`${requestInfo}`,
`color:${statusColor}`,
- `${response.status} ${response.statusText}`,
+ `${loggedResponse.status} ${loggedResponse.statusText}`,
'color:inherit',
)
console.log('Request:', loggedRequest)
diff --git a/src/core/handlers/HttpHandler.test.ts b/src/core/handlers/HttpHandler.test.ts
new file mode 100644
index 000000000..0d0ac2d49
--- /dev/null
+++ b/src/core/handlers/HttpHandler.test.ts
@@ -0,0 +1,218 @@
+/**
+ * @jest-environment jsdom
+ */
+import { HttpHandler, HttpRequestResolverExtras } from './HttpHandler'
+import { HttpResponse } from '..'
+import { ResponseResolver } from './RequestHandler'
+
+const resolver: ResponseResolver<
+ HttpRequestResolverExtras<{ userId: string }>
+> = ({ params }) => {
+ return HttpResponse.json({ userId: params.userId })
+}
+
+describe('info', () => {
+ test('exposes request handler information', () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+
+ expect(handler.info.header).toEqual('GET /user/:userId')
+ expect(handler.info.method).toEqual('GET')
+ expect(handler.info.path).toEqual('/user/:userId')
+ expect(handler.isUsed).toBe(false)
+ })
+})
+
+describe('parse', () => {
+ test('parses a URL given a matching request', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const request = new Request(new URL('/user/abc-123', location.href))
+
+ expect(await handler.parse({ request })).toEqual({
+ match: {
+ matches: true,
+ params: {
+ userId: 'abc-123',
+ },
+ },
+ cookies: {},
+ })
+ })
+
+ test('parses a URL and ignores the request method', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const request = new Request(new URL('/user/def-456', location.href), {
+ method: 'POST',
+ })
+
+ expect(await handler.parse({ request })).toEqual({
+ match: {
+ matches: true,
+ params: {
+ userId: 'def-456',
+ },
+ },
+ cookies: {},
+ })
+ })
+
+ test('returns negative match result given a non-matching request', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const request = new Request(new URL('/login', location.href))
+
+ expect(await handler.parse({ request })).toEqual({
+ match: {
+ matches: false,
+ params: {},
+ },
+ cookies: {},
+ })
+ })
+})
+
+describe('predicate', () => {
+ test('returns true given a matching request', async () => {
+ const handler = new HttpHandler('POST', '/login', resolver)
+ const request = new Request(new URL('/login', location.href), {
+ method: 'POST',
+ })
+
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
+ })
+
+ test('respects RegExp as the request method', async () => {
+ const handler = new HttpHandler(/.+/, '/login', resolver)
+ const requests = [
+ new Request(new URL('/login', location.href)),
+ new Request(new URL('/login', location.href), { method: 'POST' }),
+ new Request(new URL('/login', location.href), { method: 'DELETE' }),
+ ]
+
+ for (const request of requests) {
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(true)
+ }
+ })
+
+ test('returns false given a non-matching request', async () => {
+ const handler = new HttpHandler('POST', '/login', resolver)
+ const request = new Request(new URL('/user/abc-123', location.href))
+
+ expect(
+ handler.predicate({
+ request,
+ parsedResult: await handler.parse({ request }),
+ }),
+ ).toBe(false)
+ })
+})
+
+describe('test', () => {
+ test('returns true given a matching request', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const firstTest = await handler.test({
+ request: new Request(new URL('/user/abc-123', location.href)),
+ })
+ const secondTest = await handler.test({
+ request: new Request(new URL('/user/def-456', location.href)),
+ })
+
+ expect(firstTest).toBe(true)
+ expect(secondTest).toBe(true)
+ })
+
+ test('returns false given a non-matching request', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const firstTest = await handler.test({
+ request: new Request(new URL('/login', location.href)),
+ })
+ const secondTest = await handler.test({
+ request: new Request(new URL('/user/', location.href)),
+ })
+ const thirdTest = await handler.test({
+ request: new Request(new URL('/user/abc-123/extra', location.href)),
+ })
+
+ expect(firstTest).toBe(false)
+ expect(secondTest).toBe(false)
+ expect(thirdTest).toBe(false)
+ })
+})
+
+describe('run', () => {
+ test('returns a mocked response given a matching request', async () => {
+ const handler = new HttpHandler('GET', '/user/:userId', resolver)
+ const request = new Request(new URL('/user/abc-123', location.href))
+ const result = await handler.run({ request })
+
+ expect(result!.handler).toEqual(handler)
+ expect(result!.parsedResult).toEqual({
+ match: {
+ matches: true,
+ params: {
+ userId: 'abc-123',
+ },
+ },
+ cookies: {},
+ })
+ expect(result!.request.method).toBe('GET')
+ expect(result!.request.url).toBe('http://localhost/user/abc-123')
+ expect(result!.response?.status).toBe(200)
+ expect(result!.response?.statusText).toBe('OK')
+ expect(await result?.response?.json()).toEqual({ userId: 'abc-123' })
+ })
+
+ test('returns null given a non-matching request', async () => {
+ const handler = new HttpHandler('POST', '/login', resolver)
+ const result = await handler.run({
+ request: new Request(new URL('/users', location.href)),
+ })
+
+ expect(result).toBeNull()
+ })
+
+ test('returns an empty "params" object given request with no URL parameters', async () => {
+ const handler = new HttpHandler('GET', '/users', resolver)
+ const result = await handler.run({
+ request: new Request(new URL('/users', location.href)),
+ })
+
+ expect(result?.parsedResult?.match?.params).toEqual({})
+ })
+
+ test('exhauses resolver until its generator completes', async () => {
+ const handler = new HttpHandler('GET', '/users', function* () {
+ let count = 0
+
+ while (count < 5) {
+ count += 1
+ yield HttpResponse.text('pending')
+ }
+
+ return HttpResponse.text('complete')
+ })
+
+ const run = async () => {
+ const result = await handler.run({
+ request: new Request(new URL('/users', location.href)),
+ })
+ return result?.response?.text()
+ }
+
+ expect(await run()).toBe('pending')
+ expect(await run()).toBe('pending')
+ expect(await run()).toBe('pending')
+ expect(await run()).toBe('pending')
+ expect(await run()).toBe('pending')
+ expect(await run()).toBe('complete')
+ expect(await run()).toBe('complete')
+ })
+})
diff --git a/src/core/handlers/HttpHandler.ts b/src/core/handlers/HttpHandler.ts
new file mode 100644
index 000000000..963814785
--- /dev/null
+++ b/src/core/handlers/HttpHandler.ts
@@ -0,0 +1,169 @@
+import { ResponseResolutionContext } from '../utils/getResponse'
+import { devUtils } from '../utils/internal/devUtils'
+import { isStringEqual } from '../utils/internal/isStringEqual'
+import { getStatusCodeColor } from '../utils/logging/getStatusCodeColor'
+import { getTimestamp } from '../utils/logging/getTimestamp'
+import { serializeRequest } from '../utils/logging/serializeRequest'
+import { serializeResponse } from '../utils/logging/serializeResponse'
+import {
+ matchRequestUrl,
+ Match,
+ Path,
+ PathParams,
+} from '../utils/matching/matchRequestUrl'
+import { getPublicUrlFromRequest } from '../utils/request/getPublicUrlFromRequest'
+import { getAllRequestCookies } from '../utils/request/getRequestCookies'
+import { cleanUrl, getSearchParams } from '../utils/url/cleanUrl'
+import {
+ RequestHandler,
+ RequestHandlerDefaultInfo,
+ RequestHandlerOptions,
+ ResponseResolver,
+} from './RequestHandler'
+
+type HttpHandlerMethod = string | RegExp
+
+export interface HttpHandlerInfo extends RequestHandlerDefaultInfo {
+ method: HttpHandlerMethod
+ path: Path
+}
+
+export enum HttpMethods {
+ HEAD = 'HEAD',
+ GET = 'GET',
+ POST = 'POST',
+ PUT = 'PUT',
+ PATCH = 'PATCH',
+ OPTIONS = 'OPTIONS',
+ DELETE = 'DELETE',
+}
+
+export type RequestQuery = {
+ [queryName: string]: string
+}
+
+export type HttpRequestParsedResult = {
+ match: Match
+ cookies: Record
+}
+
+export type HttpRequestResolverExtras = {
+ params: Params
+ cookies: Record>
+}
+
+/**
+ * Request handler for HTTP requests.
+ * Provides request matching based on method and URL.
+ */
+export class HttpHandler extends RequestHandler<
+ HttpHandlerInfo,
+ HttpRequestParsedResult,
+ HttpRequestResolverExtras
+> {
+ constructor(
+ method: HttpHandlerMethod,
+ path: Path,
+ resolver: ResponseResolver, any, any>,
+ options?: RequestHandlerOptions,
+ ) {
+ super({
+ info: {
+ header: `${method} ${path}`,
+ path,
+ method,
+ },
+ resolver,
+ options,
+ })
+
+ this.checkRedundantQueryParameters()
+ }
+
+ private checkRedundantQueryParameters() {
+ const { method, path } = this.info
+
+ if (path instanceof RegExp) {
+ return
+ }
+
+ const url = cleanUrl(path)
+
+ // Bypass request handler URLs that have no redundant characters.
+ if (url === path) {
+ return
+ }
+
+ const searchParams = getSearchParams(path)
+ const queryParams: string[] = []
+
+ searchParams.forEach((_, paramName) => {
+ queryParams.push(paramName)
+ })
+
+ devUtils.warn(
+ `Found a redundant usage of query parameters in the request handler URL for "${method} ${path}". Please match against a path instead and access query parameters in the response resolver function using "req.url.searchParams".`,
+ )
+ }
+
+ async parse(args: {
+ request: Request
+ resolutionContext?: ResponseResolutionContext
+ }) {
+ const url = new URL(args.request.url)
+ const match = matchRequestUrl(
+ url,
+ this.info.path,
+ args.resolutionContext?.baseUrl,
+ )
+ const cookies = getAllRequestCookies(args.request)
+
+ return {
+ match,
+ cookies,
+ }
+ }
+
+ predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) {
+ const hasMatchingMethod = this.matchMethod(args.request.method)
+ const hasMatchingUrl = args.parsedResult.match.matches
+ return hasMatchingMethod && hasMatchingUrl
+ }
+
+ private matchMethod(actualMethod: string): boolean {
+ return this.info.method instanceof RegExp
+ ? this.info.method.test(actualMethod)
+ : isStringEqual(this.info.method, actualMethod)
+ }
+
+ protected extendResolverArgs(args: {
+ request: Request
+ parsedResult: HttpRequestParsedResult
+ }) {
+ return {
+ params: args.parsedResult.match?.params || {},
+ cookies: args.parsedResult.cookies,
+ }
+ }
+
+ async log(args: { request: Request; response: Response }) {
+ const publicUrl = getPublicUrlFromRequest(args.request)
+ const loggedRequest = await serializeRequest(args.request)
+ const loggedResponse = await serializeResponse(args.response)
+ const statusColor = getStatusCodeColor(loggedResponse.status)
+
+ console.groupCollapsed(
+ devUtils.formatMessage('%s %s %s (%c%s%c)'),
+ getTimestamp(),
+ args.request.method,
+ publicUrl,
+ `color:${statusColor}`,
+ `${loggedResponse.status} ${loggedResponse.statusText}`,
+ 'color:inherit',
+ )
+ console.log('Request', loggedRequest)
+ console.log('Handler:', this)
+ console.log('Response', loggedResponse)
+ console.groupEnd()
+ }
+}
diff --git a/src/core/handlers/RequestHandler.ts b/src/core/handlers/RequestHandler.ts
new file mode 100644
index 000000000..917d6edc4
--- /dev/null
+++ b/src/core/handlers/RequestHandler.ts
@@ -0,0 +1,297 @@
+import { invariant } from 'outvariant'
+import { getCallFrame } from '../utils/internal/getCallFrame'
+import { isIterable } from '../utils/internal/isIterable'
+import type { ResponseResolutionContext } from '../utils/getResponse'
+import type { MaybePromise } from '../typeUtils'
+import { StrictRequest, StrictResponse } from '..//HttpResponse'
+
+export type DefaultRequestMultipartBody = Record<
+ string,
+ string | File | Array
+>
+
+export type DefaultBodyType =
+ | Record
+ | DefaultRequestMultipartBody
+ | string
+ | number
+ | boolean
+ | null
+ | undefined
+
+export interface RequestHandlerDefaultInfo {
+ header: string
+}
+
+export interface RequestHandlerInternalInfo {
+ callFrame?: string
+}
+
+export type ResponseResolverReturnType<
+ BodyType extends DefaultBodyType = undefined,
+> =
+ | (BodyType extends undefined ? Response : StrictResponse)
+ | undefined
+ | void
+
+export type MaybeAsyncResponseResolverReturnType<
+ BodyType extends DefaultBodyType,
+> = MaybePromise>
+
+export type AsyncResponseResolverReturnType =
+ | MaybeAsyncResponseResolverReturnType
+ | Generator<
+ MaybeAsyncResponseResolverReturnType,
+ MaybeAsyncResponseResolverReturnType,
+ MaybeAsyncResponseResolverReturnType
+ >
+
+export type ResponseResolverInfo<
+ ResolverExtraInfo extends Record,
+ RequestBodyType extends DefaultBodyType = DefaultBodyType,
+> = {
+ request: StrictRequest
+} & ResolverExtraInfo
+
+export type ResponseResolver<
+ ResolverExtraInfo extends Record = Record,
+ RequestBodyType extends DefaultBodyType = DefaultBodyType,
+ ResponseBodyType extends DefaultBodyType = undefined,
+> = (
+ info: ResponseResolverInfo,
+) => AsyncResponseResolverReturnType
+
+export interface RequestHandlerArgs<
+ HandlerInfo,
+ HandlerOptions extends RequestHandlerOptions,
+> {
+ info: HandlerInfo
+ resolver: ResponseResolver
+ options?: HandlerOptions
+}
+
+export interface RequestHandlerOptions {
+ once?: boolean
+}
+
+export interface RequestHandlerExecutionResult<
+ ParsedResult extends Record | undefined,
+> {
+ handler: RequestHandler
+ parsedResult?: ParsedResult
+ request: Request
+ response?: Response
+}
+
+export abstract class RequestHandler<
+ HandlerInfo extends RequestHandlerDefaultInfo = RequestHandlerDefaultInfo,
+ ParsedResult extends Record | undefined = any,
+ ResolverExtras extends Record = any,
+ HandlerOptions extends RequestHandlerOptions = RequestHandlerOptions,
+> {
+ public info: HandlerInfo & RequestHandlerInternalInfo
+ /**
+ * Indicates whether this request handler has been used
+ * (its resolver has successfully executed).
+ */
+ public isUsed: boolean
+
+ protected resolver: ResponseResolver
+ private resolverGenerator?: Generator<
+ MaybeAsyncResponseResolverReturnType,
+ MaybeAsyncResponseResolverReturnType,
+ MaybeAsyncResponseResolverReturnType
+ >
+ private resolverGeneratorResult?: Response | StrictResponse
+ private options?: HandlerOptions
+
+ constructor(args: RequestHandlerArgs) {
+ this.resolver = args.resolver
+ this.options = args.options
+
+ const callFrame = getCallFrame(new Error())
+
+ this.info = {
+ ...args.info,
+ callFrame,
+ }
+
+ this.isUsed = false
+ }
+
+ /**
+ * Determine if the intercepted request should be mocked.
+ */
+ abstract predicate(args: {
+ request: Request
+ parsedResult: ParsedResult
+ resolutionContext?: ResponseResolutionContext
+ }): boolean
+
+ /**
+ * Print out the successfully handled request.
+ */
+ abstract log(args: {
+ request: Request
+ response: Response
+ parsedResult: ParsedResult
+ }): void
+
+ /**
+ * Parse the intercepted request to extract additional information from it.
+ * Parsed result is then exposed to other methods of this request handler.
+ */
+ async parse(_args: {
+ request: Request
+ resolutionContext?: ResponseResolutionContext
+ }): Promise {
+ return {} as ParsedResult
+ }
+
+ /**
+ * Test if this handler matches the given request.
+ */
+ public async test(args: {
+ request: Request
+ resolutionContext?: ResponseResolutionContext
+ }): Promise {
+ const parsedResult = await this.parse({
+ request: args.request,
+ resolutionContext: args.resolutionContext,
+ })
+
+ return this.predicate({
+ request: args.request,
+ parsedResult,
+ resolutionContext: args.resolutionContext,
+ })
+ }
+
+ protected extendResolverArgs(_args: {
+ request: Request
+ parsedResult: ParsedResult
+ }): ResolverExtras {
+ return {} as ResolverExtras
+ }
+
+ /**
+ * Execute this request handler and produce a mocked response
+ * using the given resolver function.
+ */
+ public async run(args: {
+ request: StrictRequest
+ resolutionContext?: ResponseResolutionContext
+ }): Promise | null> {
+ if (this.isUsed && this.options?.once) {
+ return null
+ }
+
+ // Clone the request instance before it's passed to the handler phases
+ // and the response resolver so we can always read it for logging.
+ const mainRequestRef = args.request.clone()
+
+ // Immediately mark the handler as used.
+ // Can't await the resolver to be resolved because it's potentially
+ // asynchronous, and there may be multiple requests hitting this handler.
+ this.isUsed = true
+
+ const parsedResult = await this.parse({
+ request: args.request,
+ resolutionContext: args.resolutionContext,
+ })
+ const shouldInterceptRequest = this.predicate({
+ request: args.request,
+ parsedResult,
+ resolutionContext: args.resolutionContext,
+ })
+
+ if (!shouldInterceptRequest) {
+ return null
+ }
+
+ // Create a response extraction wrapper around the resolver
+ // since it can be both an async function and a generator.
+ const executeResolver = this.wrapResolver(this.resolver)
+
+ const resolverExtras = this.extendResolverArgs({
+ request: args.request,
+ parsedResult,
+ })
+ const mockedResponse = (await executeResolver({
+ ...resolverExtras,
+ request: args.request,
+ })) as Response
+
+ const executionResult = this.createExecutionResult({
+ // Pass the cloned request to the result so that logging
+ // and other consumers could read its body once more.
+ request: mainRequestRef,
+ response: mockedResponse,
+ parsedResult,
+ })
+
+ return executionResult
+ }
+
+ private wrapResolver(
+ resolver: ResponseResolver,
+ ): ResponseResolver {
+ return async (info): Promise> => {
+ const result = this.resolverGenerator || (await resolver(info))
+
+ if (isIterable>(result)) {
+ // Immediately mark this handler as unused.
+ // Only when the generator is done, the handler will be
+ // considered used.
+ this.isUsed = false
+
+ const { value, done } = result[Symbol.iterator]().next()
+ const nextResponse = await value
+
+ if (done) {
+ this.isUsed = true
+ }
+
+ // If the generator is done and there is no next value,
+ // return the previous generator's value.
+ if (!nextResponse && done) {
+ invariant(
+ this.resolverGeneratorResult,
+ 'Failed to returned a previously stored generator response: the value is not a valid Response.',
+ )
+
+ // Clone the previously stored response from the generator
+ // so that it could be read again.
+ return this.resolverGeneratorResult.clone()
+ }
+
+ if (!this.resolverGenerator) {
+ this.resolverGenerator = result
+ }
+
+ if (nextResponse) {
+ // Also clone the response before storing it
+ // so it could be read again.
+ this.resolverGeneratorResult = nextResponse?.clone()
+ }
+
+ return nextResponse
+ }
+
+ return result
+ }
+ }
+
+ private createExecutionResult(args: {
+ request: Request
+ parsedResult: ParsedResult
+ response?: Response
+ }): RequestHandlerExecutionResult {
+ return {
+ handler: this,
+ request: args.request,
+ response: args.response,
+ parsedResult: args.parsedResult,
+ }
+ }
+}
diff --git a/src/rest.spec.ts b/src/core/http.spec.ts
similarity index 61%
rename from src/rest.spec.ts
rename to src/core/http.spec.ts
index c44f0a85e..8bc2bdc2d 100644
--- a/src/rest.spec.ts
+++ b/src/core/http.spec.ts
@@ -1,8 +1,8 @@
-import { rest } from './rest'
+import { http } from './http'
test('exports all REST API methods', () => {
- expect(rest).toBeDefined()
- expect(Object.keys(rest)).toEqual([
+ expect(http).toBeDefined()
+ expect(Object.keys(http)).toEqual([
'all',
'head',
'get',
diff --git a/src/core/http.ts b/src/core/http.ts
new file mode 100644
index 000000000..d4f7c58f8
--- /dev/null
+++ b/src/core/http.ts
@@ -0,0 +1,51 @@
+import {
+ DefaultBodyType,
+ RequestHandlerOptions,
+ ResponseResolver,
+} from './handlers/RequestHandler'
+import {
+ HttpMethods,
+ HttpHandler,
+ HttpRequestResolverExtras,
+} from './handlers/HttpHandler'
+import type { Path, PathParams } from './utils/matching/matchRequestUrl'
+
+function createHttpHandler(
+ method: Method,
+) {
+ return <
+ Params extends PathParams = PathParams,
+ RequestBodyType extends DefaultBodyType = DefaultBodyType,
+ ResponseBodyType extends DefaultBodyType = undefined,
+ >(
+ path: Path,
+ resolver: ResponseResolver<
+ HttpRequestResolverExtras,
+ RequestBodyType,
+ ResponseBodyType
+ >,
+ options: RequestHandlerOptions = {},
+ ) => {
+ return new HttpHandler(method, path, resolver, options)
+ }
+}
+
+/**
+ * A namespace to intercept and mock HTTP requests.
+ *
+ * @example
+ * http.get('/user', resolver)
+ * http.post('/post/:id', resolver)
+ *
+ * @see {@link https://mswjs.io/docs/api/http `http` API reference}
+ */
+export const http = {
+ all: createHttpHandler(/.+/),
+ head: createHttpHandler(HttpMethods.HEAD),
+ get: createHttpHandler(HttpMethods.GET),
+ post: createHttpHandler(HttpMethods.POST),
+ put: createHttpHandler(HttpMethods.PUT),
+ delete: createHttpHandler(HttpMethods.DELETE),
+ patch: createHttpHandler(HttpMethods.PATCH),
+ options: createHttpHandler(HttpMethods.OPTIONS),
+}
diff --git a/src/core/index.ts b/src/core/index.ts
new file mode 100644
index 000000000..d7bf7f968
--- /dev/null
+++ b/src/core/index.ts
@@ -0,0 +1,55 @@
+import { checkGlobals } from './utils/internal/checkGlobals'
+
+export { SetupApi } from './SetupApi'
+
+/* Request handlers */
+export { RequestHandler } from './handlers/RequestHandler'
+export { http } from './http'
+export { HttpHandler, HttpMethods } from './handlers/HttpHandler'
+export { graphql } from './graphql'
+export { GraphQLHandler } from './handlers/GraphQLHandler'
+
+/* Utils */
+export { matchRequestUrl } from './utils/matching/matchRequestUrl'
+export * from './utils/handleRequest'
+export { cleanUrl } from './utils/url/cleanUrl'
+
+/**
+ * Type definitions.
+ */
+
+export type { SharedOptions, LifeCycleEventsMap } from './sharedOptions'
+
+export type {
+ ResponseResolver,
+ ResponseResolverReturnType,
+ AsyncResponseResolverReturnType,
+ RequestHandlerOptions,
+ DefaultBodyType,
+ DefaultRequestMultipartBody,
+} from './handlers/RequestHandler'
+
+export type {
+ RequestQuery,
+ HttpRequestParsedResult,
+} from './handlers/HttpHandler'
+
+export type {
+ GraphQLVariables,
+ GraphQLRequestBody,
+ GraphQLJsonRequestBody,
+} from './handlers/GraphQLHandler'
+
+export type { Path, PathParams, Match } from './utils/matching/matchRequestUrl'
+export type { ParsedGraphQLRequest } from './utils/internal/parseGraphQLRequest'
+
+export * from './HttpResponse'
+export * from './delay'
+export { bypass } from './bypass'
+export { passthrough } from './passthrough'
+
+// Validate environmental globals before executing any code.
+// This ensures that the library gives user-friendly errors
+// when ran in the environments that require additional polyfills
+// from the end user.
+checkGlobals()
diff --git a/src/core/passthrough.test.ts b/src/core/passthrough.test.ts
new file mode 100644
index 000000000..631fcdaf3
--- /dev/null
+++ b/src/core/passthrough.test.ts
@@ -0,0 +1,13 @@
+/**
+ * @jest-environment node
+ */
+import { passthrough } from './passthrough'
+
+it('creates a 302 response with the intention header', () => {
+ const response = passthrough()
+
+ expect(response).toBeInstanceOf(Response)
+ expect(response.status).toBe(302)
+ expect(response.statusText).toBe('Passthrough')
+ expect(response.headers.get('x-msw-intention')).toBe('passthrough')
+})
diff --git a/src/core/passthrough.ts b/src/core/passthrough.ts
new file mode 100644
index 000000000..d67afdde1
--- /dev/null
+++ b/src/core/passthrough.ts
@@ -0,0 +1,23 @@
+/**
+ * Performs the intercepted request as-is.
+ *
+ * This stops request handler lookup so no other handlers
+ * can affect this request past this point.
+ * Unlike `bypass()`, this will not trigger an additional request.
+ *
+ * @example
+ * http.get('/resource', () => {
+ * return passthrough()
+ * })
+ *
+ * @see {@link https://mswjs.io/docs/api/passthrough `passthrough()` API reference}
+ */
+export function passthrough(): Response {
+ return new Response(null, {
+ status: 302,
+ statusText: 'Passthrough',
+ headers: {
+ 'x-msw-intention': 'passthrough',
+ },
+ })
+}
diff --git a/src/core/sharedOptions.ts b/src/core/sharedOptions.ts
new file mode 100644
index 000000000..ad7f151a2
--- /dev/null
+++ b/src/core/sharedOptions.ts
@@ -0,0 +1,66 @@
+import type { Emitter } from 'strict-event-emitter'
+import type { UnhandledRequestStrategy } from './utils/request/onUnhandledRequest'
+
+export interface SharedOptions {
+ /**
+ * Specifies how to react to a request that has no corresponding
+ * request handler. Warns on unhandled requests by default.
+ *
+ * @example worker.start({ onUnhandledRequest: 'bypass' })
+ * @example worker.start({ onUnhandledRequest: 'warn' })
+ * @example server.listen({ onUnhandledRequest: 'error' })
+ */
+ onUnhandledRequest?: UnhandledRequestStrategy
+}
+
+export type LifeCycleEventsMap = {
+ 'request:start': [
+ args: {
+ request: Request
+ requestId: string
+ },
+ ]
+ 'request:match': [
+ args: {
+ request: Request
+ requestId: string
+ },
+ ]
+ 'request:unhandled': [
+ args: {
+ request: Request
+ requestId: string
+ },
+ ]
+ 'request:end': [
+ args: {
+ request: Request
+ requestId: string
+ },
+ ]
+ 'response:mocked': [
+ args: {
+ response: Response
+ request: Request
+ requestId: string
+ },
+ ]
+ 'response:bypass': [
+ args: {
+ response: Response
+ request: Request
+ requestId: string
+ },
+ ]
+ unhandledException: [
+ args: {
+ error: Error
+ request: Request
+ requestId: string
+ },
+ ]
+}
+
+export type LifeCycleEventEmitter<
+ EventsMap extends Record,
+> = Pick, 'on' | 'removeListener' | 'removeAllListeners'>
diff --git a/src/typeUtils.ts b/src/core/typeUtils.ts
similarity index 73%
rename from src/typeUtils.ts
rename to src/core/typeUtils.ts
index d9310465d..8e878be22 100644
--- a/src/typeUtils.ts
+++ b/src/core/typeUtils.ts
@@ -1,7 +1,7 @@
-import { ResponseTransformer } from './response'
-
type Fn = (...arg: any[]) => any
+export type MaybePromise = T | Promise
+
export type RequiredDeep<
Type,
U extends Record | Fn | undefined = undefined,
@@ -18,7 +18,3 @@ export type RequiredDeep<
: RequiredDeep, U>
}
: Type
-
-export type GraphQLPayloadContext> = (
- payload: QueryType,
-) => ResponseTransformer
diff --git a/src/core/utils/HttpResponse/decorators.ts b/src/core/utils/HttpResponse/decorators.ts
new file mode 100644
index 000000000..7b1bb97cc
--- /dev/null
+++ b/src/core/utils/HttpResponse/decorators.ts
@@ -0,0 +1,56 @@
+import statuses from '@bundled-es-modules/statuses'
+import type { HttpResponseInit } from '../../HttpResponse'
+
+const { message } = statuses
+
+export interface HttpResponseDecoratedInit extends HttpResponseInit {
+ status: number
+ statusText: string
+ headers: Headers
+}
+
+export function normalizeResponseInit(
+ init: HttpResponseInit = {},
+): HttpResponseDecoratedInit {
+ const status = init?.status || 200
+ const statusText = init?.statusText || message[status] || ''
+ const headers = new Headers(init?.headers)
+
+ return {
+ ...init,
+ headers,
+ status,
+ statusText,
+ }
+}
+
+export function decorateResponse(
+ response: Response,
+ init: HttpResponseDecoratedInit,
+): Response {
+ // Allow to mock the response type.
+ if (init.type) {
+ Object.defineProperty(response, 'type', {
+ value: init.type,
+ enumerable: true,
+ writable: false,
+ })
+ }
+
+ // Cookie forwarding is only relevant in the browser.
+ if (typeof document !== 'undefined') {
+ // Write the mocked response cookies to the document.
+ // Note that Fetch API Headers will concatenate multiple "Set-Cookie"
+ // headers into a single comma-separated string, just as it does
+ // with any other multi-value headers.
+ const responseCookies = init.headers.get('Set-Cookie')?.split(',') || []
+
+ for (const cookieString of responseCookies) {
+ // No need to parse the cookie headers because it's defined
+ // as the valid cookie string to begin with.
+ document.cookie = cookieString
+ }
+ }
+
+ return response
+}
diff --git a/src/core/utils/getResponse.ts b/src/core/utils/getResponse.ts
new file mode 100644
index 000000000..131804d8b
--- /dev/null
+++ b/src/core/utils/getResponse.ts
@@ -0,0 +1,55 @@
+import {
+ RequestHandler,
+ RequestHandlerExecutionResult,
+} from '../handlers/RequestHandler'
+
+export interface ResponseLookupResult {
+ handler: RequestHandler
+ parsedResult?: any
+ response?: Response
+}
+
+export interface ResponseResolutionContext {
+ baseUrl?: string
+}
+
+/**
+ * Returns a mocked response for a given request using following request handlers.
+ */
+export const getResponse = async >(
+ request: Request,
+ handlers: Handler,
+ resolutionContext?: ResponseResolutionContext,
+): Promise => {
+ let matchingHandler: RequestHandler | null = null
+ let result: RequestHandlerExecutionResult | null = null
+
+ for (const handler of handlers) {
+ result = await handler.run({ request, resolutionContext })
+
+ // If the handler produces some result for this request,
+ // it automatically becomes matching.
+ if (result !== null) {
+ matchingHandler = handler
+ }
+
+ // Stop the lookup if this handler returns a mocked response.
+ // If it doesn't, it will still be considered the last matching
+ // handler until any of them returns a response. This way we can
+ // distinguish between fallthrough handlers without responses
+ // and the lack of a matching handler.
+ if (result?.response) {
+ break
+ }
+ }
+
+ if (matchingHandler) {
+ return {
+ handler: matchingHandler,
+ parsedResult: result?.parsedResult,
+ response: result?.response,
+ }
+ }
+
+ return null
+}
diff --git a/src/core/utils/handleRequest.test.ts b/src/core/utils/handleRequest.test.ts
new file mode 100644
index 000000000..a89bd00f3
--- /dev/null
+++ b/src/core/utils/handleRequest.test.ts
@@ -0,0 +1,344 @@
+/**
+ * @jest-environment jsdom
+ */
+import { Emitter } from 'strict-event-emitter'
+import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
+import { RequestHandler } from '../handlers/RequestHandler'
+import { http } from '../http'
+import { handleRequest, HandleRequestOptions } from './handleRequest'
+import { RequiredDeep } from '../typeUtils'
+import { uuidv4 } from './internal/uuidv4'
+import { HttpResponse } from '../HttpResponse'
+import { passthrough } from '../passthrough'
+
+const options: RequiredDeep = {
+ onUnhandledRequest: jest.fn(),
+}
+const callbacks: Partial> = {
+ onPassthroughResponse: jest.fn(),
+ onMockedResponse: jest.fn(),
+}
+
+function setup() {
+ const emitter = new Emitter()
+ const listener = jest.fn()
+
+ const createMockListener = (name: string) => {
+ return (...args: any) => {
+ listener(name, ...args)
+ }
+ }
+
+ emitter.on('request:start', createMockListener('request:start'))
+ emitter.on('request:match', createMockListener('request:match'))
+ emitter.on('request:unhandled', createMockListener('request:unhandled'))
+ emitter.on('request:end', createMockListener('request:end'))
+ emitter.on('response:mocked', createMockListener('response:mocked'))
+ emitter.on('response:bypass', createMockListener('response:bypass'))
+
+ const events = listener.mock.calls
+ return { emitter, events }
+}
+
+beforeEach(() => {
+ jest.spyOn(global.console, 'warn').mockImplementation()
+})
+
+afterEach(() => {
+ jest.resetAllMocks()
+})
+
+test('returns undefined for a request with the "x-msw-intention" header equal to "bypass"', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'), {
+ headers: new Headers({
+ 'x-msw-intention': 'bypass',
+ }),
+ })
+ const handlers: Array = []
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).toBeUndefined()
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(options.onUnhandledRequest).not.toHaveBeenCalled()
+ expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
+ expect(callbacks.onMockedResponse).not.toHaveBeenCalled()
+})
+
+test('does not bypass a request with "x-msw-intention" header set to arbitrary value', async () => {
+ const { emitter } = setup()
+
+ const request = new Request(new URL('http://localhost/user'), {
+ headers: new Headers({
+ 'x-msw-intention': 'invalid',
+ }),
+ })
+ const handlers: Array = [
+ http.get('/user', () => {
+ return HttpResponse.text('hello world')
+ }),
+ ]
+
+ const result = await handleRequest(
+ request,
+ uuidv4(),
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).not.toBeUndefined()
+ expect(options.onUnhandledRequest).not.toHaveBeenCalled()
+ expect(callbacks.onMockedResponse).toHaveBeenCalledTimes(1)
+})
+
+test('reports request as unhandled when it has no matching request handlers', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'))
+ const handlers: Array = []
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).toBeUndefined()
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:unhandled', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(options.onUnhandledRequest).toHaveBeenNthCalledWith(1, request, {
+ warning: expect.any(Function),
+ error: expect.any(Function),
+ })
+ expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
+ expect(callbacks.onMockedResponse).not.toHaveBeenCalled()
+})
+
+test('returns undefined on a request handler that returns no response', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'))
+ const handlers: Array = [
+ http.get('/user', () => {
+ // Intentionally blank response resolver.
+ return
+ }),
+ ]
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).toBeUndefined()
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(options.onUnhandledRequest).not.toHaveBeenCalled()
+ expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
+ expect(callbacks.onMockedResponse).not.toHaveBeenCalled()
+
+ /**
+ * @note Returning undefined from a resolver no longer prints a warning.
+ */
+ expect(console.warn).toHaveBeenCalledTimes(0)
+})
+
+test('returns the mocked response for a request with a matching request handler', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'))
+ const mockedResponse = HttpResponse.json({ firstName: 'John' })
+ const handlers: Array = [
+ http.get('/user', () => {
+ return mockedResponse
+ }),
+ ]
+ const lookupResult = {
+ handler: handlers[0],
+ response: mockedResponse,
+ request,
+ parsedResult: {
+ match: { matches: true, params: {} },
+ cookies: {},
+ },
+ }
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).toEqual(mockedResponse)
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:match', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled()
+
+ expect(callbacks.onMockedResponse).toHaveBeenCalledTimes(1)
+ const [mockedResponseParam, lookupResultParam] =
+ callbacks.onMockedResponse.mock.calls[0]
+
+ expect(mockedResponseParam.status).toBe(mockedResponse.status)
+ expect(mockedResponseParam.statusText).toBe(mockedResponse.statusText)
+ expect(Object.fromEntries(mockedResponseParam.headers.entries())).toEqual(
+ Object.fromEntries(mockedResponse.headers.entries()),
+ )
+
+ expect(lookupResultParam).toEqual({
+ handler: lookupResult.handler,
+ parsedResult: lookupResult.parsedResult,
+ response: expect.objectContaining({
+ status: lookupResult.response.status,
+ statusText: lookupResult.response.statusText,
+ }),
+ })
+})
+
+test('returns a transformed response if the "transformResponse" option is provided', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'))
+ const mockedResponse = HttpResponse.json({ firstName: 'John' })
+ const handlers: Array = [
+ http.get('/user', () => {
+ return mockedResponse
+ }),
+ ]
+ const transformResponseImpelemntation = (response: Response): Response => {
+ return new Response('transformed', response)
+ }
+ const transformResponse = jest
+ .fn()
+ .mockImplementation(transformResponseImpelemntation)
+ const finalResponse = transformResponseImpelemntation(mockedResponse)
+ const lookupResult = {
+ handler: handlers[0],
+ response: mockedResponse,
+ request,
+ parsedResult: {
+ match: { matches: true, params: {} },
+ cookies: {},
+ },
+ }
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ {
+ ...callbacks,
+ transformResponse,
+ },
+ )
+
+ expect(result?.status).toEqual(finalResponse.status)
+ expect(result?.statusText).toEqual(finalResponse.statusText)
+ expect(Object.fromEntries(result!.headers.entries())).toEqual(
+ Object.fromEntries(mockedResponse.headers.entries()),
+ )
+
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:match', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled()
+
+ expect(transformResponse).toHaveBeenCalledTimes(1)
+ const [responseParam] = transformResponse.mock.calls[0]
+
+ expect(responseParam.status).toBe(mockedResponse.status)
+ expect(responseParam.statusText).toBe(mockedResponse.statusText)
+ expect(Object.fromEntries(responseParam.headers.entries())).toEqual(
+ Object.fromEntries(mockedResponse.headers.entries()),
+ )
+
+ expect(callbacks.onMockedResponse).toHaveBeenCalledTimes(1)
+ const [mockedResponseParam, lookupResultParam] =
+ callbacks.onMockedResponse.mock.calls[0]
+
+ expect(mockedResponseParam.status).toBe(finalResponse.status)
+ expect(mockedResponseParam.statusText).toBe(finalResponse.statusText)
+ expect(Object.fromEntries(mockedResponseParam.headers.entries())).toEqual(
+ Object.fromEntries(mockedResponse.headers.entries()),
+ )
+ expect(await mockedResponseParam.text()).toBe('transformed')
+
+ expect(lookupResultParam).toEqual({
+ handler: lookupResult.handler,
+ parsedResult: lookupResult.parsedResult,
+ response: expect.objectContaining({
+ status: lookupResult.response.status,
+ statusText: lookupResult.response.statusText,
+ }),
+ })
+})
+
+it('returns undefined without warning on a passthrough request', async () => {
+ const { emitter, events } = setup()
+
+ const requestId = uuidv4()
+ const request = new Request(new URL('http://localhost/user'))
+ const handlers: Array = [
+ http.get('/user', () => {
+ return passthrough()
+ }),
+ ]
+
+ const result = await handleRequest(
+ request,
+ requestId,
+ handlers,
+ options,
+ emitter,
+ callbacks,
+ )
+
+ expect(result).toBeUndefined()
+ expect(events).toEqual([
+ ['request:start', { request, requestId }],
+ ['request:end', { request, requestId }],
+ ])
+ expect(options.onUnhandledRequest).not.toHaveBeenCalled()
+ expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request)
+ expect(callbacks.onMockedResponse).not.toHaveBeenCalled()
+})
diff --git a/src/utils/handleRequest.ts b/src/core/utils/handleRequest.ts
similarity index 50%
rename from src/utils/handleRequest.ts
rename to src/core/utils/handleRequest.ts
index 71e4f2ae1..f70002179 100644
--- a/src/utils/handleRequest.ts
+++ b/src/core/utils/handleRequest.ts
@@ -1,17 +1,13 @@
import { until } from '@open-draft/until'
import { Emitter } from 'strict-event-emitter'
import { RequestHandler } from '../handlers/RequestHandler'
-import { ServerLifecycleEventsMap } from '../node/glossary'
-import { MockedResponse } from '../response'
-import { SharedOptions } from '../sharedOptions'
+import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
import { RequiredDeep } from '../typeUtils'
import { ResponseLookupResult, getResponse } from './getResponse'
-import { devUtils } from './internal/devUtils'
-import { MockedRequest } from './request/MockedRequest'
import { onUnhandledRequest } from './request/onUnhandledRequest'
import { readResponseCookies } from './request/readResponseCookies'
-export interface HandleRequestOptions {
+export interface HandleRequestOptions {
/**
* Options for the response resolution process.
*/
@@ -23,42 +19,41 @@ export interface HandleRequestOptions {
* Transforms a `MockedResponse` instance returned from a handler
* to a response instance supported by the lower tooling (i.e. interceptors).
*/
- transformResponse?(response: MockedResponse): ResponseType
+ transformResponse?(response: Response): Response
/**
* Invoked whenever a request is performed as-is.
*/
- onPassthroughResponse?(request: MockedRequest): void
+ onPassthroughResponse?(request: Request): void
/**
* Invoked when the mocked response is ready to be sent.
*/
onMockedResponse?(
- response: ResponseType,
+ response: Response,
handler: RequiredDeep,
): void
}
-export async function handleRequest<
- ResponseType extends Record = MockedResponse,
->(
- request: MockedRequest,
- handlers: RequestHandler[],
+export async function handleRequest(
+ request: Request,
+ requestId: string,
+ handlers: Array,
options: RequiredDeep,
- emitter: Emitter,
- handleRequestOptions?: HandleRequestOptions,
-): Promise {
- emitter.emit('request:start', request)
+ emitter: Emitter,
+ handleRequestOptions?: HandleRequestOptions,
+): Promise {
+ emitter.emit('request:start', { request, requestId })
// Perform bypassed requests (i.e. issued via "ctx.fetch") as-is.
- if (request.headers.get('x-msw-bypass') === 'true') {
- emitter.emit('request:end', request)
+ if (request.headers.get('x-msw-intention') === 'bypass') {
+ emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
// Resolve a mocked response from the list of request handlers.
- const [lookupError, lookupResult] = await until(() => {
+ const lookupResult = await until(() => {
return getResponse(
request,
handlers,
@@ -66,48 +61,43 @@ export async function handleRequest<
)
})
- if (lookupError) {
+ if (lookupResult.error) {
// Allow developers to react to unhandled exceptions in request handlers.
- emitter.emit('unhandledException', lookupError, request)
- throw lookupError
+ emitter.emit('unhandledException', {
+ error: lookupResult.error,
+ request,
+ requestId,
+ })
+ throw lookupResult.error
}
- const { handler, response } = lookupResult
-
- // When there's no handler for the request, consider it unhandled.
- // Allow the developer to react to such cases.
- if (!handler) {
- onUnhandledRequest(request, handlers, options.onUnhandledRequest)
- emitter.emit('request:unhandled', request)
- emitter.emit('request:end', request)
+ // If the handler lookup returned nothing, no request handler was found
+ // matching this request. Report the request as unhandled.
+ if (!lookupResult.data) {
+ await onUnhandledRequest(request, handlers, options.onUnhandledRequest)
+ emitter.emit('request:unhandled', { request, requestId })
+ emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
+ const { response } = lookupResult.data
+
// When the handled request returned no mocked response, warn the developer,
// as it may be an oversight on their part. Perform the request as-is.
if (!response) {
- devUtils.warn(
- `\
-Expected response resolver to return a mocked response Object, but got %s. The original response is going to be used instead.\
-\n
- \u2022 %s
- %s\
-`,
- response,
- handler.info.header,
- handler.info.callFrame,
- )
-
- emitter.emit('request:end', request)
+ emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
- // When the developer explicitly returned "req.passthrough()" do not warn them.
- // Perform the request as-is.
- if (response.passthrough) {
- emitter.emit('request:end', request)
+ // Perform the request as-is when the developer explicitly returned "req.passthrough()".
+ // This produces no warning as the request was handled.
+ if (
+ response.status === 302 &&
+ response.headers.get('x-msw-intention') === 'passthrough'
+ ) {
+ emitter.emit('request:end', { request, requestId })
handleRequestOptions?.onPassthroughResponse?.(request)
return
}
@@ -115,21 +105,21 @@ Expected response resolver to return a mocked response Object, but got %s. The o
// Store all the received response cookies in the virtual cookie store.
readResponseCookies(request, response)
- emitter.emit('request:match', request)
+ emitter.emit('request:match', { request, requestId })
const requiredLookupResult =
- lookupResult as RequiredDeep
+ lookupResult.data as RequiredDeep
const transformedResponse =
handleRequestOptions?.transformResponse?.(response) ||
- (response as any as ResponseType)
+ (response as any as Response)
handleRequestOptions?.onMockedResponse?.(
transformedResponse,
requiredLookupResult,
)
- emitter.emit('request:end', request)
+ emitter.emit('request:end', { request, requestId })
return transformedResponse
}
diff --git a/src/core/utils/internal/Disposable.ts b/src/core/utils/internal/Disposable.ts
new file mode 100644
index 000000000..ca61652ab
--- /dev/null
+++ b/src/core/utils/internal/Disposable.ts
@@ -0,0 +1,9 @@
+export type DisposableSubscription = () => Promise | void
+
+export class Disposable {
+ protected subscriptions: Array = []
+
+ public async dispose() {
+ await Promise.all(this.subscriptions.map((subscription) => subscription()))
+ }
+}
diff --git a/src/utils/internal/checkGlobals.ts b/src/core/utils/internal/checkGlobals.ts
similarity index 100%
rename from src/utils/internal/checkGlobals.ts
rename to src/core/utils/internal/checkGlobals.ts
diff --git a/src/utils/internal/devUtils.ts b/src/core/utils/internal/devUtils.ts
similarity index 100%
rename from src/utils/internal/devUtils.ts
rename to src/core/utils/internal/devUtils.ts
diff --git a/src/utils/internal/getCallFrame.test.ts b/src/core/utils/internal/getCallFrame.test.ts
similarity index 68%
rename from src/utils/internal/getCallFrame.test.ts
rename to src/core/utils/internal/getCallFrame.test.ts
index e13c875ad..a61e30f7c 100644
--- a/src/utils/internal/getCallFrame.test.ts
+++ b/src/core/utils/internal/getCallFrame.test.ts
@@ -13,9 +13,9 @@ class ErrorWithStack extends Error {
test('supports Node.js (Linux, MacOS) error stack', () => {
const linuxError = new ErrorWithStack([
'Error: ',
- ' at getCallFrame (/Users/mock/github/msw/lib/umd/index.js:3735:22)',
- ' at Object.get (/Users/mock/github/msw/lib/umd/index.js:3776:29)',
- ' at Object. (/Users/mock/github/msw/test/msw-api/setup-server/printHandlers.test.ts:13:8)', // <-- this one
+ ' at getCallFrame (/Users/mock/github/msw/lib/node/index.js:3735:22)',
+ ' at Object.get (/Users/mock/github/msw/lib/node/index.js:3776:29)',
+ ' at Object. (/Users/mock/github/msw/test/msw-api/setup-server/listHandlers.test.ts:13:8)', // <-- this one
' at Runtime._execModule (/Users/mock/github/msw/node_modules/jest-runtime/build/index.js:1299:24)',
' at Runtime._loadModule (/Users/mock/github/msw/node_modules/jest-runtime/build/index.js:898:12)',
' at Runtime.requireModule (/Users/mock/github/msw/node_modules/jest-runtime/build/index.js:746:10)',
@@ -24,15 +24,15 @@ test('supports Node.js (Linux, MacOS) error stack', () => {
' at runTest (/Users/mock/github/msw/node_modules/jest-runner/build/runTest.js:472:34)',
])
expect(getCallFrame(linuxError)).toEqual(
- '/Users/mock/github/msw/test/msw-api/setup-server/printHandlers.test.ts:13:8',
+ '/Users/mock/github/msw/test/msw-api/setup-server/listHandlers.test.ts:13:8',
)
const macOsError = new ErrorWithStack([
'Error: ',
- ' at getCallFrame (/Users/mock/git/msw/lib/umd/index.js:3735:22)',
- ' at graphQLRequestHandler (/Users/mock/git/msw/lib/umd/index.js:7071:25)',
- ' at Object.query (/Users/mock/git/msw/lib/umd/index.js:7182:18)',
- ' at Object. (/Users/mock/git/msw/test/msw-api/setup-server/printHandlers.test.ts:14:11)', // <-- this one
+ ' at getCallFrame (/Users/mock/git/msw/lib/node/index.js:3735:22)',
+ ' at graphQLRequestHandler (/Users/mock/git/msw/lib/node/index.js:7071:25)',
+ ' at Object.query (/Users/mock/git/msw/lib/node/index.js:7182:18)',
+ ' at Object. (/Users/mock/git/msw/test/msw-api/setup-server/listHandlers.test.ts:14:11)', // <-- this one
' at Runtime._execModule (/Users/mock/git/msw/node_modules/jest-runtime/build/index.js:1299:24)',
' at Runtime._loadModule (/Users/mock/git/msw/node_modules/jest-runtime/build/index.js:898:12)',
' at Runtime.requireModule (/Users/mock/git/msw/node_modules/jest-runtime/build/index.js:746:10)',
@@ -42,17 +42,17 @@ test('supports Node.js (Linux, MacOS) error stack', () => {
])
expect(getCallFrame(macOsError)).toEqual(
- '/Users/mock/git/msw/test/msw-api/setup-server/printHandlers.test.ts:14:11',
+ '/Users/mock/git/msw/test/msw-api/setup-server/listHandlers.test.ts:14:11',
)
})
test('supports Node.js (Windows) error stack', () => {
const error = new ErrorWithStack([
'Error: ',
- ' at getCallFrame (C:\\Users\\mock\\git\\msw\\lib\\umd\\index.js:3735:22)',
- ' at graphQLRequestHandler (C:\\Users\\mock\\git\\msw\\lib\\umd\\index.js:7071:25)',
- ' at Object.query (C:\\Users\\mock\\git\\msw\\lib\\umd\\index.js:7182:18)',
- ' at Object. (C:\\Users\\mock\\git\\msw\\test\\msw-api\\setup-server\\printHandlers.test.ts:75:13)', // <-- this one
+ ' at getCallFrame (C:\\Users\\mock\\git\\msw\\lib\\node\\index.js:3735:22)',
+ ' at graphQLRequestHandler (C:\\Users\\mock\\git\\msw\\lib\\node\\index.js:7071:25)',
+ ' at Object.query (C:\\Users\\mock\\git\\msw\\lib\\node\\index.js:7182:18)',
+ ' at Object. (C:\\Users\\mock\\git\\msw\\test\\msw-api\\setup-server\\listHandlers.test.ts:75:13)', // <-- this one
' at Object.asyncJestTest (C:\\Users\\mock\\git\\msw\\node_modules\\jest-jasmine2\\build\\jasmineAsyncInstall.js:106:37)',
' at C:\\Users\\mock\\git\\msw\\node_modules\\jest-jasmine2\\build\\queueRunner.js:45:12',
' at new Promise ()',
@@ -61,17 +61,17 @@ test('supports Node.js (Windows) error stack', () => {
])
expect(getCallFrame(error)).toBe(
- 'C:\\Users\\mock\\git\\msw\\test\\msw-api\\setup-server\\printHandlers.test.ts:75:13',
+ 'C:\\Users\\mock\\git\\msw\\test\\msw-api\\setup-server\\listHandlers.test.ts:75:13',
)
})
test('supports Chrome and Edge error stack', () => {
const error = new ErrorWithStack([
'Error',
- ' at getCallFrame (webpack:///./lib/esm/getCallFrame-deps.js?:272:20)',
- ' at Object.eval [as get] (webpack:///./lib/esm/rest-deps.js?:1402:90)',
- ' at eval (webpack:///./test/msw-api/setup-worker/printHandlers.mocks.ts?:6:113)', // <-- this one
- ' at Module../test/msw-api/setup-worker/printHandlers.mocks.ts (http://localhost:59464/main.js:1376:1)',
+ ' at getCallFrame (webpack:///./lib/browser/getCallFrame-deps.js?:272:20)',
+ ' at Object.eval [as get] (webpack:///./lib/browser/rest-deps.js?:1402:90)',
+ ' at eval (webpack:///./test/msw-api/setup-worker/listHandlers.mocks.ts?:6:113)', // <-- this one
+ ' at Module../test/msw-api/setup-worker/listHandlers.mocks.ts (http://localhost:59464/main.js:1376:1)',
' at __webpack_require__ (http://localhost:59464/main.js:790:30)',
' at fn (http://localhost:59464/main.js:101:20)',
' at eval (webpack:///multi_(webpack)-dev-server/client?:4:18)',
@@ -81,16 +81,16 @@ test('supports Chrome and Edge error stack', () => {
])
expect(getCallFrame(error)).toBe(
- 'webpack:///./test/msw-api/setup-worker/printHandlers.mocks.ts?:6:113',
+ 'webpack:///./test/msw-api/setup-worker/listHandlers.mocks.ts?:6:113',
)
})
test('supports Firefox (MacOS, Windows) error stack', () => {
const error = new ErrorWithStack([
- 'getCallFrame@webpack:///./lib/esm/getCallFrame-deps.js?:272:20',
- 'createRestHandler/<@webpack:///./lib/esm/rest-deps.js?:1402:90',
- '@webpack:///./test/msw-api/setup-worker/printHandlers.mocks.ts?:6:113', // <-- this one
- './test/msw-api/setup-worker/printHandlers.mocks.ts@http://localhost:59464/main.js:1376:1',
+ 'getCallFrame@webpack:///./lib/browser/getCallFrame-deps.js?:272:20',
+ 'createRestHandler/<@webpack:///./lib/browser/rest-deps.js?:1402:90',
+ '@webpack:///./test/msw-api/setup-worker/listHandlers.mocks.ts?:6:113', // <-- this one
+ './test/msw-api/setup-worker/listHandlers.mocks.ts@http://localhost:59464/main.js:1376:1',
'__webpack_require__@http://localhost:59464/main.js:790:30',
'fn@http://localhost:59464/main.js:101:20',
'@webpack:///multi_(webpack)-dev-server/client?:4:18',
@@ -100,7 +100,7 @@ test('supports Firefox (MacOS, Windows) error stack', () => {
])
expect(getCallFrame(error)).toBe(
- 'webpack:///./test/msw-api/setup-worker/printHandlers.mocks.ts?:6:113',
+ 'webpack:///./test/msw-api/setup-worker/listHandlers.mocks.ts?:6:113',
)
})
@@ -110,7 +110,7 @@ test('supports Safari (MacOS) error stack', () => {
'',
'eval code',
'eval@[native code]',
- './test/msw-api/setup-worker/printHandlers.mocks.ts@http://localhost:59464/main.js:1376:5', // <-- this one
+ './test/msw-api/setup-worker/listHandlers.mocks.ts@http://localhost:59464/main.js:1376:5', // <-- this one
'__webpack_require__@http://localhost:59464/main.js:790:34',
'fn@http://localhost:59464/main.js:101:39',
'eval code',
@@ -122,7 +122,7 @@ test('supports Safari (MacOS) error stack', () => {
])
expect(getCallFrame(errorOne)).toBe(
- './test/msw-api/setup-worker/printHandlers.mocks.ts@http://localhost:59464/main.js:1376:5',
+ './test/msw-api/setup-worker/listHandlers.mocks.ts@http://localhost:59464/main.js:1376:5',
)
const errorTwo = new ErrorWithStack([
@@ -130,7 +130,7 @@ test('supports Safari (MacOS) error stack', () => {
'graphQLRequestHandler',
'eval code',
'eval@[native code]',
- './test/msw-api/setup-worker/printHandlers.mocks.ts@http://localhost:56460/main.js:1376:5', // <-- this one
+ './test/msw-api/setup-worker/listHandlers.mocks.ts@http://localhost:56460/main.js:1376:5', // <-- this one
'__webpack_require__@http://localhost:56460/main.js:790:34',
'fn@http://localhost:56460/main.js:101:39',
'eval code',
@@ -142,7 +142,7 @@ test('supports Safari (MacOS) error stack', () => {
])
expect(getCallFrame(errorTwo)).toBe(
- './test/msw-api/setup-worker/printHandlers.mocks.ts@http://localhost:56460/main.js:1376:5',
+ './test/msw-api/setup-worker/listHandlers.mocks.ts@http://localhost:56460/main.js:1376:5',
)
})
diff --git a/src/utils/internal/getCallFrame.ts b/src/core/utils/internal/getCallFrame.ts
similarity index 91%
rename from src/utils/internal/getCallFrame.ts
rename to src/core/utils/internal/getCallFrame.ts
index 4e297d2ee..bee9e70f4 100644
--- a/src/utils/internal/getCallFrame.ts
+++ b/src/core/utils/internal/getCallFrame.ts
@@ -2,7 +2,7 @@
const SOURCE_FRAME = /[\/\\]msw[\/\\]src[\/\\](.+)/
const BUILD_FRAME =
- /(node_modules)?[\/\\]lib[\/\\](umd|esm|iief|cjs)[\/\\]|^[^\/\\]*$/
+ /(node_modules)?[\/\\]lib[\/\\](core|browser|node|native|iife)[\/\\]|^[^\/\\]*$/
/**
* Return the stack trace frame of a function's invocation.
diff --git a/src/utils/internal/isIterable.test.ts b/src/core/utils/internal/isIterable.test.ts
similarity index 100%
rename from src/utils/internal/isIterable.test.ts
rename to src/core/utils/internal/isIterable.test.ts
diff --git a/src/utils/internal/isIterable.ts b/src/core/utils/internal/isIterable.ts
similarity index 100%
rename from src/utils/internal/isIterable.ts
rename to src/core/utils/internal/isIterable.ts
diff --git a/src/utils/internal/isObject.test.ts b/src/core/utils/internal/isObject.test.ts
similarity index 100%
rename from src/utils/internal/isObject.test.ts
rename to src/core/utils/internal/isObject.test.ts
diff --git a/src/utils/internal/isObject.ts b/src/core/utils/internal/isObject.ts
similarity index 100%
rename from src/utils/internal/isObject.ts
rename to src/core/utils/internal/isObject.ts
diff --git a/src/utils/internal/isStringEqual.test.ts b/src/core/utils/internal/isStringEqual.test.ts
similarity index 100%
rename from src/utils/internal/isStringEqual.test.ts
rename to src/core/utils/internal/isStringEqual.test.ts
diff --git a/src/utils/internal/isStringEqual.ts b/src/core/utils/internal/isStringEqual.ts
similarity index 100%
rename from src/utils/internal/isStringEqual.ts
rename to src/core/utils/internal/isStringEqual.ts
diff --git a/src/utils/internal/jsonParse.test.ts b/src/core/utils/internal/jsonParse.test.ts
similarity index 100%
rename from src/utils/internal/jsonParse.test.ts
rename to src/core/utils/internal/jsonParse.test.ts
diff --git a/src/utils/internal/jsonParse.ts b/src/core/utils/internal/jsonParse.ts
similarity index 100%
rename from src/utils/internal/jsonParse.ts
rename to src/core/utils/internal/jsonParse.ts
diff --git a/src/utils/internal/mergeRight.test.ts b/src/core/utils/internal/mergeRight.test.ts
similarity index 100%
rename from src/utils/internal/mergeRight.test.ts
rename to src/core/utils/internal/mergeRight.test.ts
diff --git a/src/utils/internal/mergeRight.ts b/src/core/utils/internal/mergeRight.ts
similarity index 100%
rename from src/utils/internal/mergeRight.ts
rename to src/core/utils/internal/mergeRight.ts
diff --git a/src/core/utils/internal/parseGraphQLRequest.test.ts b/src/core/utils/internal/parseGraphQLRequest.test.ts
new file mode 100644
index 000000000..7bb624d96
--- /dev/null
+++ b/src/core/utils/internal/parseGraphQLRequest.test.ts
@@ -0,0 +1,99 @@
+/**
+ * @jest-environment jsdom
+ */
+import { encodeBuffer } from '@mswjs/interceptors'
+import { OperationTypeNode } from 'graphql'
+import {
+ ParsedGraphQLRequest,
+ parseGraphQLRequest,
+} from './parseGraphQLRequest'
+
+test('returns true given a GraphQL-compatible request', async () => {
+ const getRequest = new Request(
+ new URL(
+ 'http://localhost:8080/graphql?query=mutation Login { user { id } }',
+ ),
+ )
+ expect(await parseGraphQLRequest(getRequest)).toEqual<
+ ParsedGraphQLRequest
+ >({
+ operationType: OperationTypeNode.MUTATION,
+ operationName: 'Login',
+ query: `mutation Login { user { id } }`,
+ variables: undefined,
+ })
+
+ const postRequest = new Request(new URL('http://localhost:8080/graphql'), {
+ method: 'POST',
+ headers: new Headers({ 'Content-Type': 'application/json' }),
+ body: encodeBuffer(
+ JSON.stringify({
+ query: `query GetUser { user { firstName } }`,
+ }),
+ ),
+ })
+
+ expect(await parseGraphQLRequest(postRequest)).toEqual<
+ ParsedGraphQLRequest
+ >({
+ operationType: OperationTypeNode.QUERY,
+ operationName: 'GetUser',
+ query: `query GetUser { user { firstName } }`,
+ variables: undefined,
+ })
+})
+
+test('throws an exception given an invalid GraphQL request', async () => {
+ const getRequest = new Request(
+ new URL('http://localhost:8080/graphql?query=mutation Login() { user { {}'),
+ )
+
+ await expect(parseGraphQLRequest(getRequest)).rejects.toThrowError(
+ '[MSW] Failed to intercept a GraphQL request to "GET http://localhost:8080/graphql": cannot parse query. See the error message from the parser below.',
+ )
+
+ const postRequest = new Request(new URL('http://localhost:8080/graphql'), {
+ method: 'POST',
+ headers: new Headers({ 'Content-Type': 'application/json' }),
+ body: encodeBuffer(
+ JSON.stringify({
+ query: `query GetUser() { user {{}`,
+ }),
+ ),
+ })
+
+ await expect(parseGraphQLRequest(postRequest)).rejects.toThrowError(
+ '[MSW] Failed to intercept a GraphQL request to "POST http://localhost:8080/graphql": cannot parse query. See the error message from the parser below.\n\nSyntax Error: Expected "$", found ")".',
+ )
+})
+
+test('returns false given a GraphQL-incompatible request', async () => {
+ const getRequest = new Request(new URL('http://localhost:8080/graphql'), {
+ headers: new Headers({ 'Content-Type': 'application/json' }),
+ })
+ expect(await parseGraphQLRequest(getRequest)).toBeUndefined()
+
+ const postRequest = new Request(new URL('http://localhost:8080/graphql'), {
+ method: 'POST',
+ headers: new Headers({ 'Content-Type': 'application/json' }),
+ body: encodeBuffer(
+ JSON.stringify({
+ queryUser: true,
+ }),
+ ),
+ })
+ expect(await parseGraphQLRequest(postRequest)).toBeUndefined()
+})
+
+test('does not read the original request body', async () => {
+ const request = new Request(new URL('http://localhost/api'), {
+ method: 'POST',
+ body: JSON.stringify({ payload: 'value' }),
+ })
+
+ await parseGraphQLRequest(request)
+
+ // Must not read the original request body because GraphQL parsing
+ // is an internal operation that must not lock the body stream.
+ expect(request.bodyUsed).toBe(false)
+})
diff --git a/src/utils/internal/parseGraphQLRequest.ts b/src/core/utils/internal/parseGraphQLRequest.ts
similarity index 70%
rename from src/utils/internal/parseGraphQLRequest.ts
rename to src/core/utils/internal/parseGraphQLRequest.ts
index b3d757040..230d145f0 100644
--- a/src/utils/internal/parseGraphQLRequest.ts
+++ b/src/core/utils/internal/parseGraphQLRequest.ts
@@ -4,11 +4,11 @@ import type {
OperationTypeNode,
} from 'graphql'
import { parse } from 'graphql'
-import { GraphQLVariables } from '../../handlers/GraphQLHandler'
+import type { GraphQLVariables } from '../../handlers/GraphQLHandler'
import { getPublicUrlFromRequest } from '../request/getPublicUrlFromRequest'
-import { MockedRequest } from '../request/MockedRequest'
import { devUtils } from './devUtils'
import { jsonParse } from './jsonParse'
+import { parseMultipartData } from './parseMultipartData'
interface GraphQLInput {
query: string | null
@@ -24,13 +24,14 @@ export type ParsedGraphQLRequest<
VariablesType extends GraphQLVariables = GraphQLVariables,
> =
| (ParsedGraphQLQuery & {
+ query: string
variables?: VariablesType
})
| undefined
export function parseDocumentNode(node: DocumentNode): ParsedGraphQLQuery {
- const operationDef = node.definitions.find((def) => {
- return def.kind === 'OperationDefinition'
+ const operationDef = node.definitions.find((definition) => {
+ return definition.kind === 'OperationDefinition'
}) as OperationDefinitionNode
return {
@@ -62,6 +63,7 @@ function extractMultipartVariables(
files: Record,
) {
const operations = { variables }
+
for (const [key, pathArray] of Object.entries(map)) {
if (!(key in files)) {
throw new Error(`Given files do not have a key '${key}' .`)
@@ -83,14 +85,16 @@ function extractMultipartVariables(
target[lastPath] = files[key]
}
}
+
return operations.variables
}
-function getGraphQLInput(request: MockedRequest): GraphQLInput | null {
+async function getGraphQLInput(request: Request): Promise {
switch (request.method) {
case 'GET': {
- const query = request.url.searchParams.get('query')
- const variables = request.url.searchParams.get('variables') || ''
+ const url = new URL(request.url)
+ const query = url.searchParams.get('query')
+ const variables = url.searchParams.get('variables') || ''
return {
query,
@@ -99,19 +103,24 @@ function getGraphQLInput(request: MockedRequest): GraphQLInput | null {
}
case 'POST': {
- if (request.body?.query) {
- const { query, variables } = request.body
-
- return {
- query,
- variables,
+ // Clone the request so we could read its body without locking
+ // the body stream to the downward consumers.
+ const requestClone = request.clone()
+
+ // Handle multipart body GraphQL operations.
+ if (
+ request.headers.get('content-type')?.includes('multipart/form-data')
+ ) {
+ const responseJson = parseMultipartData(
+ await requestClone.text(),
+ request.headers,
+ )
+
+ if (!responseJson) {
+ return null
}
- }
- // Handle multipart body operations.
- if (request.body?.operations) {
- const { operations, map, ...files } =
- request.body as GraphQLMultipartRequestBody
+ const { operations, map, ...files } = responseJson
const parsedOperations =
jsonParse<{ query?: string; variables?: GraphQLVariables }>(
operations,
@@ -135,6 +144,22 @@ function getGraphQLInput(request: MockedRequest): GraphQLInput | null {
variables,
}
}
+
+ // Handle plain POST GraphQL operations.
+ const requestJson: {
+ query: string
+ variables?: GraphQLVariables
+ operations?: any /** @todo Annotate this */
+ } = await requestClone.json().catch(() => null)
+
+ if (requestJson?.query) {
+ const { query, variables } = requestJson
+
+ return {
+ query,
+ variables,
+ }
+ }
}
default:
@@ -146,13 +171,13 @@ function getGraphQLInput(request: MockedRequest): GraphQLInput | null {
* Determines if a given request can be considered a GraphQL request.
* Does not parse the query and does not guarantee its validity.
*/
-export function parseGraphQLRequest(
- request: MockedRequest,
-): ParsedGraphQLRequest {
- const input = getGraphQLInput(request)
+export async function parseGraphQLRequest(
+ request: Request,
+): Promise {
+ const input = await getGraphQLInput(request)
if (!input || !input.query) {
- return undefined
+ return
}
const { query, variables } = input
@@ -172,6 +197,7 @@ export function parseGraphQLRequest(
}
return {
+ query: input.query,
operationType: parsedResult.operationType,
operationName: parsedResult.operationName,
variables,
diff --git a/src/utils/internal/parseMultipartData.test.ts b/src/core/utils/internal/parseMultipartData.test.ts
similarity index 100%
rename from src/utils/internal/parseMultipartData.test.ts
rename to src/core/utils/internal/parseMultipartData.test.ts
diff --git a/src/utils/internal/parseMultipartData.ts b/src/core/utils/internal/parseMultipartData.ts
similarity index 100%
rename from src/utils/internal/parseMultipartData.ts
rename to src/core/utils/internal/parseMultipartData.ts
diff --git a/src/utils/internal/pipeEvents.test.ts b/src/core/utils/internal/pipeEvents.test.ts
similarity index 74%
rename from src/utils/internal/pipeEvents.test.ts
rename to src/core/utils/internal/pipeEvents.test.ts
index 727a0cdc8..d5e9e51b3 100644
--- a/src/utils/internal/pipeEvents.test.ts
+++ b/src/core/utils/internal/pipeEvents.test.ts
@@ -1,9 +1,9 @@
-import { EventEmitter } from 'stream'
+import { Emitter } from 'strict-event-emitter'
import { pipeEvents } from './pipeEvents'
it('pipes events from the source emitter to the destination emitter', () => {
- const source = new EventEmitter()
- const destination = new EventEmitter()
+ const source = new Emitter()
+ const destination = new Emitter()
pipeEvents(source, destination)
const callback = jest.fn()
diff --git a/src/utils/internal/pipeEvents.ts b/src/core/utils/internal/pipeEvents.ts
similarity index 100%
rename from src/utils/internal/pipeEvents.ts
rename to src/core/utils/internal/pipeEvents.ts
diff --git a/src/utils/internal/requestHandlerUtils.ts b/src/core/utils/internal/requestHandlerUtils.ts
similarity index 52%
rename from src/utils/internal/requestHandlerUtils.ts
rename to src/core/utils/internal/requestHandlerUtils.ts
index 10d18952d..2b50fa29f 100644
--- a/src/utils/internal/requestHandlerUtils.ts
+++ b/src/core/utils/internal/requestHandlerUtils.ts
@@ -1,21 +1,21 @@
import { RequestHandler } from '../../handlers/RequestHandler'
export function use(
- currentHandlers: RequestHandler[],
- ...handlers: RequestHandler[]
+ currentHandlers: Array,
+ ...handlers: Array
): void {
currentHandlers.unshift(...handlers)
}
-export function restoreHandlers(handlers: RequestHandler[]): void {
+export function restoreHandlers(handlers: Array): void {
handlers.forEach((handler) => {
- handler.markAsSkipped(false)
+ handler.isUsed = false
})
}
export function resetHandlers(
- initialHandlers: RequestHandler[],
- ...nextHandlers: RequestHandler[]
+ initialHandlers: Array,
+ ...nextHandlers: Array
) {
return nextHandlers.length > 0 ? [...nextHandlers] : [...initialHandlers]
}
diff --git a/src/utils/internal/toReadonlyArray.test.ts b/src/core/utils/internal/toReadonlyArray.test.ts
similarity index 100%
rename from src/utils/internal/toReadonlyArray.test.ts
rename to src/core/utils/internal/toReadonlyArray.test.ts
diff --git a/src/utils/internal/toReadonlyArray.ts b/src/core/utils/internal/toReadonlyArray.ts
similarity index 100%
rename from src/utils/internal/toReadonlyArray.ts
rename to src/core/utils/internal/toReadonlyArray.ts
diff --git a/src/utils/internal/tryCatch.test.ts b/src/core/utils/internal/tryCatch.test.ts
similarity index 100%
rename from src/utils/internal/tryCatch.test.ts
rename to src/core/utils/internal/tryCatch.test.ts
diff --git a/src/utils/internal/tryCatch.ts b/src/core/utils/internal/tryCatch.ts
similarity index 100%
rename from src/utils/internal/tryCatch.ts
rename to src/core/utils/internal/tryCatch.ts
diff --git a/src/core/utils/internal/uuidv4.ts b/src/core/utils/internal/uuidv4.ts
new file mode 100644
index 000000000..5daf9d0cc
--- /dev/null
+++ b/src/core/utils/internal/uuidv4.ts
@@ -0,0 +1,3 @@
+export function uuidv4(): string {
+ return Math.random().toString(16).slice(2)
+}
diff --git a/src/utils/logging/getStatusCodeColor.test.ts b/src/core/utils/logging/getStatusCodeColor.test.ts
similarity index 100%
rename from src/utils/logging/getStatusCodeColor.test.ts
rename to src/core/utils/logging/getStatusCodeColor.test.ts
diff --git a/src/utils/logging/getStatusCodeColor.ts b/src/core/utils/logging/getStatusCodeColor.ts
similarity index 100%
rename from src/utils/logging/getStatusCodeColor.ts
rename to src/core/utils/logging/getStatusCodeColor.ts
diff --git a/src/utils/logging/getTimestamp.test.ts b/src/core/utils/logging/getTimestamp.test.ts
similarity index 100%
rename from src/utils/logging/getTimestamp.test.ts
rename to src/core/utils/logging/getTimestamp.test.ts
diff --git a/src/utils/logging/getTimestamp.ts b/src/core/utils/logging/getTimestamp.ts
similarity index 100%
rename from src/utils/logging/getTimestamp.ts
rename to src/core/utils/logging/getTimestamp.ts
diff --git a/src/core/utils/logging/serializeRequest.test.ts b/src/core/utils/logging/serializeRequest.test.ts
new file mode 100644
index 000000000..aac41d891
--- /dev/null
+++ b/src/core/utils/logging/serializeRequest.test.ts
@@ -0,0 +1,23 @@
+import { encodeBuffer } from '@mswjs/interceptors'
+import { serializeRequest } from './serializeRequest'
+
+test('serializes given Request instance into a plain object', async () => {
+ const request = await serializeRequest(
+ new Request(new URL('http://test.mswjs.io/user'), {
+ method: 'POST',
+ headers: new Headers({
+ 'Content-Type': 'text/plain',
+ 'X-Header': 'secret',
+ }),
+ body: encodeBuffer('text-body'),
+ }),
+ )
+
+ expect(request.method).toBe('POST')
+ expect(request.url.href).toBe('http://test.mswjs.io/user')
+ expect(request.headers).toEqual({
+ 'content-type': 'text/plain',
+ 'x-header': 'secret',
+ })
+ expect(request.body).toBe('text-body')
+})
diff --git a/src/core/utils/logging/serializeRequest.ts b/src/core/utils/logging/serializeRequest.ts
new file mode 100644
index 000000000..a2c2afd01
--- /dev/null
+++ b/src/core/utils/logging/serializeRequest.ts
@@ -0,0 +1,23 @@
+export interface LoggedRequest {
+ url: URL
+ method: string
+ headers: Record
+ body: string
+}
+
+/**
+ * Formats a mocked request for introspection in browser's console.
+ */
+export async function serializeRequest(
+ request: Request,
+): Promise {
+ const requestClone = request.clone()
+ const requestText = await requestClone.text()
+
+ return {
+ url: new URL(request.url),
+ method: request.method,
+ headers: Object.fromEntries(request.headers.entries()),
+ body: requestText,
+ }
+}
diff --git a/src/core/utils/logging/serializeResponse.test.ts b/src/core/utils/logging/serializeResponse.test.ts
new file mode 100644
index 000000000..61a903286
--- /dev/null
+++ b/src/core/utils/logging/serializeResponse.test.ts
@@ -0,0 +1,77 @@
+/**
+ * @jest-environment node
+ */
+import { encodeBuffer } from '@mswjs/interceptors'
+import { serializeResponse } from './serializeResponse'
+
+it('serializes response without body', async () => {
+ const result = await serializeResponse(new Response(null))
+
+ expect(result.status).toBe(200)
+ expect(result.statusText).toBe('OK')
+ expect(result.headers).toEqual({})
+ expect(result.body).toBe('')
+})
+
+it('serializes a plain text response', async () => {
+ const result = await serializeResponse(
+ new Response('hello world', {
+ status: 201,
+ statusText: 'Created',
+ headers: {
+ 'Content-Type': 'text/plain',
+ },
+ }),
+ )
+
+ expect(result.status).toBe(201)
+ expect(result.statusText).toBe('Created')
+ expect(result.headers).toEqual({
+ 'content-type': 'text/plain',
+ })
+ expect(result.body).toBe('hello world')
+})
+
+it('serializes a JSON response', async () => {
+ const response = new Response(JSON.stringify({ users: ['John'] }), {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ const result = await serializeResponse(response)
+
+ expect(result.headers).toEqual({
+ 'content-type': 'application/json',
+ })
+ expect(result.body).toBe(JSON.stringify({ users: ['John'] }))
+})
+
+it('serializes a ArrayBuffer response', async () => {
+ const data = encodeBuffer('hello world')
+ const response = new Response(data)
+ const result = await serializeResponse(response)
+
+ expect(result.body).toBe('hello world')
+})
+
+it('serializes a Blob response', async () => {
+ const response = new Response(new Blob(['hello world']))
+ const result = await serializeResponse(response)
+
+ expect(result.body).toBe('hello world')
+})
+
+it('serializes a FormData response', async () => {
+ const data = new FormData()
+ data.set('firstName', 'Alice')
+ data.set('age', '32')
+ const response = new Response(data)
+ const result = await serializeResponse(response)
+
+ expect(result.body).toContain(
+ `Content-Disposition: form-data; name="firstName"\r\n\r\nAlice`,
+ )
+ expect(result.body).toContain(
+ `Content-Disposition: form-data; name="age"\r\n\r\n32`,
+ )
+})
diff --git a/src/core/utils/logging/serializeResponse.ts b/src/core/utils/logging/serializeResponse.ts
new file mode 100644
index 000000000..754dbd32e
--- /dev/null
+++ b/src/core/utils/logging/serializeResponse.ts
@@ -0,0 +1,31 @@
+import statuses from '@bundled-es-modules/statuses'
+
+const { message } = statuses
+
+export interface SerializedResponse {
+ status: number
+ statusText: string
+ headers: Record
+ body: string
+}
+
+export async function serializeResponse(
+ response: Response,
+): Promise {
+ const responseClone = response.clone()
+ const responseText = await responseClone.text()
+
+ // Normalize the response status and status text when logging
+ // since the default Response instance doesn't infer status texts
+ // from status codes. This has no effect on the actual response instance.
+ const responseStatus = responseClone.status || 200
+ const responseStatusText =
+ responseClone.statusText || message[responseStatus] || 'OK'
+
+ return {
+ status: responseStatus,
+ statusText: responseStatusText,
+ headers: Object.fromEntries(responseClone.headers.entries()),
+ body: responseText,
+ }
+}
diff --git a/src/utils/matching/matchRequestUrl.test.ts b/src/core/utils/matching/matchRequestUrl.test.ts
similarity index 100%
rename from src/utils/matching/matchRequestUrl.test.ts
rename to src/core/utils/matching/matchRequestUrl.test.ts
diff --git a/src/utils/matching/matchRequestUrl.ts b/src/core/utils/matching/matchRequestUrl.ts
similarity index 96%
rename from src/utils/matching/matchRequestUrl.ts
rename to src/core/utils/matching/matchRequestUrl.ts
index 8d5d25cfc..3b9ce6ebf 100644
--- a/src/utils/matching/matchRequestUrl.ts
+++ b/src/core/utils/matching/matchRequestUrl.ts
@@ -1,5 +1,5 @@
import { match } from 'path-to-regexp'
-import { getCleanUrl } from '@mswjs/interceptors/lib/utils/getCleanUrl.js'
+import { getCleanUrl } from '@mswjs/interceptors'
import { normalizePath } from './normalizePath'
export type Path = string | RegExp
diff --git a/src/utils/matching/normalizePath.node.test.ts b/src/core/utils/matching/normalizePath.node.test.ts
similarity index 100%
rename from src/utils/matching/normalizePath.node.test.ts
rename to src/core/utils/matching/normalizePath.node.test.ts
diff --git a/src/utils/matching/normalizePath.test.ts b/src/core/utils/matching/normalizePath.test.ts
similarity index 100%
rename from src/utils/matching/normalizePath.test.ts
rename to src/core/utils/matching/normalizePath.test.ts
diff --git a/src/utils/matching/normalizePath.ts b/src/core/utils/matching/normalizePath.ts
similarity index 100%
rename from src/utils/matching/normalizePath.ts
rename to src/core/utils/matching/normalizePath.ts
diff --git a/src/core/utils/request/getPublicUrlFromRequest.test.ts b/src/core/utils/request/getPublicUrlFromRequest.test.ts
new file mode 100644
index 000000000..3ac30af83
--- /dev/null
+++ b/src/core/utils/request/getPublicUrlFromRequest.test.ts
@@ -0,0 +1,26 @@
+/**
+ * @jest-environment jsdom
+ */
+import { getPublicUrlFromRequest } from './getPublicUrlFromRequest'
+
+test('returns an absolute request URL withouth search params', () => {
+ expect(
+ getPublicUrlFromRequest(new Request(new URL('https://test.mswjs.io/path'))),
+ ).toBe('https://test.mswjs.io/path')
+
+ expect(
+ getPublicUrlFromRequest(new Request(new URL('http://localhost/path'))),
+ ).toBe('/path')
+
+ expect(
+ getPublicUrlFromRequest(
+ new Request(new URL('http://localhost/path?foo=bar')),
+ ),
+ ).toBe('/path')
+})
+
+it('returns a relative URL given the request to the same origin', () => {
+ expect(getPublicUrlFromRequest(new Request('http://localhost/user'))).toBe(
+ '/user',
+ )
+})
diff --git a/src/core/utils/request/getPublicUrlFromRequest.ts b/src/core/utils/request/getPublicUrlFromRequest.ts
new file mode 100644
index 000000000..63bae1014
--- /dev/null
+++ b/src/core/utils/request/getPublicUrlFromRequest.ts
@@ -0,0 +1,15 @@
+/**
+ * Returns a relative URL if the given request URL is relative to the current origin.
+ * Otherwise returns an absolute URL.
+ */
+export function getPublicUrlFromRequest(request: Request): string {
+ if (typeof location === 'undefined') {
+ return request.url
+ }
+
+ const url = new URL(request.url)
+
+ return url.origin === location.origin
+ ? url.pathname
+ : url.origin + url.pathname
+}
diff --git a/src/utils/request/getRequestCookies.node.test.ts b/src/core/utils/request/getRequestCookies.node.test.ts
similarity index 86%
rename from src/utils/request/getRequestCookies.node.test.ts
rename to src/core/utils/request/getRequestCookies.node.test.ts
index 185af3264..2061c4fe0 100644
--- a/src/utils/request/getRequestCookies.node.test.ts
+++ b/src/core/utils/request/getRequestCookies.node.test.ts
@@ -2,7 +2,6 @@
* @jest-environment node
*/
import { getRequestCookies } from './getRequestCookies'
-import { MockedRequest } from './MockedRequest'
const prevLocation = global.location
@@ -21,7 +20,7 @@ afterAll(() => {
test('returns empty object when in a node environment with polyfilled location object', () => {
const cookies = getRequestCookies(
- new MockedRequest(new URL('/user', location.origin), {
+ new Request(new URL('/user', location.href), {
credentials: 'include',
}),
)
diff --git a/src/utils/request/getRequestCookies.test.ts b/src/core/utils/request/getRequestCookies.test.ts
similarity index 77%
rename from src/utils/request/getRequestCookies.test.ts
rename to src/core/utils/request/getRequestCookies.test.ts
index 42ee3676f..59a5c5b7b 100644
--- a/src/utils/request/getRequestCookies.test.ts
+++ b/src/core/utils/request/getRequestCookies.test.ts
@@ -2,8 +2,7 @@
* @jest-environment jsdom
*/
import { getRequestCookies } from './getRequestCookies'
-import { clearCookies } from '../../../test/support/utils'
-import { MockedRequest } from './MockedRequest'
+import { clearCookies } from '../../../../test/support/utils'
beforeAll(() => {
// Emulate some `document.cookie` value.
@@ -17,7 +16,7 @@ afterAll(() => {
test('returns all document cookies given "include" credentials', () => {
const cookies = getRequestCookies(
- new MockedRequest(new URL('/user', location.origin), {
+ new Request(new URL('/user', location.origin), {
credentials: 'include',
}),
)
@@ -30,7 +29,7 @@ test('returns all document cookies given "include" credentials', () => {
test('returns all document cookies given "same-origin" credentials and the same request origin', () => {
const cookies = getRequestCookies(
- new MockedRequest(new URL('/user', location.origin), {
+ new Request(new URL('/user', location.origin), {
credentials: 'same-origin',
}),
)
@@ -43,7 +42,7 @@ test('returns all document cookies given "same-origin" credentials and the same
test('returns an empty object given "same-origin" credentials and a different request origin', () => {
const cookies = getRequestCookies(
- new MockedRequest(new URL('https://test.mswjs.io/user'), {
+ new Request(new URL('https://test.mswjs.io/user'), {
credentials: 'same-origin',
}),
)
@@ -53,7 +52,7 @@ test('returns an empty object given "same-origin" credentials and a different re
test('returns an empty object given "omit" credentials', () => {
const cookies = getRequestCookies(
- new MockedRequest(new URL('/user', location.origin), {
+ new Request(new URL('/user', location.origin), {
credentials: 'omit',
}),
)
diff --git a/src/core/utils/request/getRequestCookies.ts b/src/core/utils/request/getRequestCookies.ts
new file mode 100644
index 000000000..749390ee2
--- /dev/null
+++ b/src/core/utils/request/getRequestCookies.ts
@@ -0,0 +1,76 @@
+import cookieUtils from '@bundled-es-modules/cookie'
+import { store } from '@mswjs/cookies'
+
+function getAllDocumentCookies() {
+ return cookieUtils.parse(document.cookie)
+}
+
+/** @todo Rename this to "getDocumentCookies" */
+/**
+ * Returns relevant document cookies based on the request `credentials` option.
+ */
+export function getRequestCookies(request: Request): Record {
+ /**
+ * @note No cookies persist on the document in Node.js: no document.
+ */
+ if (typeof document === 'undefined' || typeof location === 'undefined') {
+ return {}
+ }
+
+ switch (request.credentials) {
+ case 'same-origin': {
+ const url = new URL(request.url)
+
+ // Return document cookies only when requested a resource
+ // from the same origin as the current document.
+ return location.origin === url.origin ? getAllDocumentCookies() : {}
+ }
+
+ case 'include': {
+ // Return all document cookies.
+ return getAllDocumentCookies()
+ }
+
+ default: {
+ return {}
+ }
+ }
+}
+
+export function getAllRequestCookies(request: Request): Record {
+ const requestCookiesString = request.headers.get('cookie')
+ const cookiesFromHeaders = requestCookiesString
+ ? cookieUtils.parse(requestCookiesString)
+ : {}
+
+ store.hydrate()
+
+ const cookiesFromStore = Array.from(store.get(request)?.entries()).reduce(
+ (cookies, [name, { value }]) => {
+ return Object.assign(cookies, { [name.trim()]: value })
+ },
+ {},
+ )
+
+ const cookiesFromDocument = getRequestCookies(request)
+
+ const forwardedCookies = {
+ ...cookiesFromDocument,
+ ...cookiesFromStore,
+ }
+
+ // Set the inferred cookies from the cookie store and the document
+ // on the request's headers.
+ /**
+ * @todo Consider making this a separate step so this function
+ * is pure-er.
+ */
+ for (const [name, value] of Object.entries(forwardedCookies)) {
+ request.headers.append('cookie', `${name}=${value}`)
+ }
+
+ return {
+ ...forwardedCookies,
+ ...cookiesFromHeaders,
+ }
+}
diff --git a/src/utils/request/onUnhandledRequest.test.ts b/src/core/utils/request/onUnhandledRequest.test.ts
similarity index 51%
rename from src/utils/request/onUnhandledRequest.test.ts
rename to src/core/utils/request/onUnhandledRequest.test.ts
index 3d6de6e75..5649ea866 100644
--- a/src/utils/request/onUnhandledRequest.test.ts
+++ b/src/core/utils/request/onUnhandledRequest.test.ts
@@ -1,34 +1,36 @@
+/**
+ * @jest-environment jsdom
+ */
import {
onUnhandledRequest,
UnhandledRequestCallback,
} from './onUnhandledRequest'
-import { RestHandler, RESTMethods } from '../../handlers/RestHandler'
+import { HttpHandler, HttpMethods } from '../../handlers/HttpHandler'
import { ResponseResolver } from '../../handlers/RequestHandler'
-import { MockedRequest } from './MockedRequest'
const resolver: ResponseResolver = () => void 0
const fixtures = {
warningWithoutSuggestions: `\
-[MSW] Warning: captured a request without a matching request handler:
+[MSW] Warning: intercepted a request without a matching request handler:
- • GET http://localhost/api
+ • GET /api
If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks`,
errorWithoutSuggestions: `\
-[MSW] Error: captured a request without a matching request handler:
+[MSW] Error: intercepted a request without a matching request handler:
- • GET http://localhost/api
+ • GET /api
If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks`,
warningWithSuggestions: (suggestions: string) => `\
-[MSW] Warning: captured a request without a matching request handler:
+[MSW] Warning: intercepted a request without a matching request handler:
- • GET http://localhost/api
+ • GET /api
Did you mean to request one of the following resources instead?
@@ -47,9 +49,9 @@ afterEach(() => {
jest.restoreAllMocks()
})
-test('supports the "bypass" request strategy', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('supports the "bypass" request strategy', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[],
'bypass',
)
@@ -58,9 +60,9 @@ test('supports the "bypass" request strategy', () => {
expect(console.error).not.toHaveBeenCalled()
})
-test('supports the "warn" request strategy', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('supports the "warn" request strategy', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[],
'warn',
)
@@ -68,28 +70,28 @@ test('supports the "warn" request strategy', () => {
expect(console.warn).toHaveBeenCalledWith(fixtures.warningWithoutSuggestions)
})
-test('supports the "error" request strategy', () => {
- expect(() =>
+test('supports the "error" request strategy', async () => {
+ await expect(
onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+ new Request(new URL('http://localhost/api')),
[],
'error',
),
- ).toThrow(
+ ).rejects.toThrow(
'[MSW] Cannot bypass a request when using the "error" strategy for the "onUnhandledRequest" option.',
)
expect(console.error).toHaveBeenCalledWith(fixtures.errorWithoutSuggestions)
})
-test('supports a custom callback function', () => {
+test('supports a custom callback function', async () => {
const callback = jest.fn>(
(request) => {
- console.warn(`callback: ${request.method} ${request.url.href}`)
+ console.warn(`callback: ${request.method} ${request.url}`)
},
)
- const request = new MockedRequest(new URL('/user', 'http://localhost:3000'))
- onUnhandledRequest(request, [], callback)
+ const request = new Request(new URL('/user', 'http://localhost:3000'))
+ await onUnhandledRequest(request, [], callback)
expect(callback).toHaveBeenCalledTimes(1)
expect(callback).toHaveBeenCalledWith(request, {
@@ -103,17 +105,15 @@ test('supports a custom callback function', () => {
)
})
-test('supports calling default strategies from the custom callback function', () => {
+test('supports calling default strategies from the custom callback function', async () => {
const callback = jest.fn>(
(request, print) => {
- console.warn(`custom callback: ${request.id}`)
-
// Call the default "error" strategy.
print.error()
},
)
- const request = new MockedRequest(new URL('http://localhost/api'))
- expect(() => onUnhandledRequest(request, [], callback)).toThrow(
+ const request = new Request(new URL('http://localhost/api'))
+ await expect(onUnhandledRequest(request, [], callback)).rejects.toThrow(
`[MSW] Cannot bypass a request when using the "error" strategy for the "onUnhandledRequest" option.`,
)
@@ -123,16 +123,13 @@ test('supports calling default strategies from the custom callback function', ()
error: expect.any(Function),
})
- // Check that the custom logic in the callback was called.
- expect(console.warn).toHaveBeenCalledWith(`custom callback: ${request.id}`)
-
// Check that the default strategy was called.
expect(console.error).toHaveBeenCalledWith(fixtures.errorWithoutSuggestions)
})
-test('does not print any suggestions given no handlers to suggest', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('does not print any suggestions given no handlers to suggest', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[],
'warn',
)
@@ -140,14 +137,14 @@ test('does not print any suggestions given no handlers to suggest', () => {
expect(console.warn).toHaveBeenCalledWith(fixtures.warningWithoutSuggestions)
})
-test('does not print any suggestions given no handlers are similar', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('does not print any suggestions given no handlers are similar', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[
// None of the defined request handlers match the actual request URL
// to be used as suggestions.
- new RestHandler(RESTMethods.GET, 'https://api.github.com', resolver),
- new RestHandler(RESTMethods.GET, 'https://api.stripe.com', resolver),
+ new HttpHandler(HttpMethods.GET, 'https://api.github.com', resolver),
+ new HttpHandler(HttpMethods.GET, 'https://api.stripe.com', resolver),
],
'warn',
)
@@ -155,66 +152,66 @@ test('does not print any suggestions given no handlers are similar', () => {
expect(console.warn).toHaveBeenCalledWith(fixtures.warningWithoutSuggestions)
})
-test('respects RegExp as a request handler method', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
- [new RestHandler(/^GE/, 'http://localhost/api', resolver)],
+test('respects RegExp as a request handler method', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
+ [new HttpHandler(/^GE/, 'http://localhost/api', resolver)],
'warn',
)
expect(console.warn).toHaveBeenCalledWith(fixtures.warningWithoutSuggestions)
})
-test('sorts the suggestions by relevance', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('sorts the suggestions by relevance', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[
- new RestHandler(RESTMethods.GET, 'http://localhost/', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost:9090/api', resolver),
- new RestHandler(RESTMethods.POST, 'http://localhost/api', resolver),
+ new HttpHandler(HttpMethods.GET, '/', resolver),
+ new HttpHandler(HttpMethods.GET, 'https://api.example.com/api', resolver),
+ new HttpHandler(HttpMethods.POST, '/api', resolver),
],
'warn',
)
expect(console.warn).toHaveBeenCalledWith(
fixtures.warningWithSuggestions(`\
- • POST http://localhost/api
- • GET http://localhost/`),
+ • POST /api
+ • GET /`),
)
})
-test('does not print more than 4 suggestions', () => {
- onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+test('does not print more than 4 suggestions', async () => {
+ await onUnhandledRequest(
+ new Request(new URL('http://localhost/api')),
[
- new RestHandler(RESTMethods.GET, 'http://localhost/ap', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost/api', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost/api-1', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost/api-2', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost/api-3', resolver),
- new RestHandler(RESTMethods.GET, 'http://localhost/api-4', resolver),
+ new HttpHandler(HttpMethods.GET, '/ap', resolver),
+ new HttpHandler(HttpMethods.GET, '/api', resolver),
+ new HttpHandler(HttpMethods.GET, '/api-1', resolver),
+ new HttpHandler(HttpMethods.GET, '/api-2', resolver),
+ new HttpHandler(HttpMethods.GET, '/api-3', resolver),
+ new HttpHandler(HttpMethods.GET, '/api-4', resolver),
],
'warn',
)
expect(console.warn).toHaveBeenCalledWith(
fixtures.warningWithSuggestions(`\
- • GET http://localhost/api
- • GET http://localhost/ap
- • GET http://localhost/api-1
- • GET http://localhost/api-2`),
+ • GET /api
+ • GET /ap
+ • GET /api-1
+ • GET /api-2`),
)
})
-test('throws an exception given unknown request strategy', () => {
- expect(() =>
+test('throws an exception given unknown request strategy', async () => {
+ await expect(
onUnhandledRequest(
- new MockedRequest(new URL('http://localhost/api')),
+ new Request(new URL('http://localhost/api')),
[],
// @ts-expect-error Intentional unknown strategy.
- 'arbitrary-strategy',
+ 'invalid-strategy',
),
- ).toThrow(
- '[MSW] Failed to react to an unhandled request: unknown strategy "arbitrary-strategy". Please provide one of the supported strategies ("bypass", "warn", "error") or a custom callback function as the value of the "onUnhandledRequest" option.',
+ ).rejects.toThrow(
+ '[MSW] Failed to react to an unhandled request: unknown strategy "invalid-strategy". Please provide one of the supported strategies ("bypass", "warn", "error") or a custom callback function as the value of the "onUnhandledRequest" option.',
)
})
diff --git a/src/utils/request/onUnhandledRequest.ts b/src/core/utils/request/onUnhandledRequest.ts
similarity index 77%
rename from src/utils/request/onUnhandledRequest.ts
rename to src/core/utils/request/onUnhandledRequest.ts
index 96432e150..89a571360 100644
--- a/src/utils/request/onUnhandledRequest.ts
+++ b/src/core/utils/request/onUnhandledRequest.ts
@@ -1,16 +1,16 @@
-import getStringMatchScore from 'js-levenshtein'
+// @ts-ignore
+import jsLevenshtein from '@bundled-es-modules/js-levenshtein'
+import { RequestHandler, HttpHandler, GraphQLHandler } from '../..'
import {
ParsedGraphQLQuery,
+ ParsedGraphQLRequest,
parseGraphQLRequest,
} from '../internal/parseGraphQLRequest'
import { getPublicUrlFromRequest } from './getPublicUrlFromRequest'
import { isStringEqual } from '../internal/isStringEqual'
-import { RestHandler } from '../../handlers/RestHandler'
-import { GraphQLHandler } from '../../handlers/GraphQLHandler'
-import { RequestHandler } from '../../handlers/RequestHandler'
-import { tryCatch } from '../internal/tryCatch'
import { devUtils } from '../internal/devUtils'
-import { MockedRequest } from './MockedRequest'
+
+const getStringMatchScore = jsLevenshtein
const MAX_MATCH_SCORE = 3
const MAX_SUGGESTION_COUNT = 4
@@ -22,7 +22,7 @@ export interface UnhandledRequestPrint {
}
export type UnhandledRequestCallback = (
- request: MockedRequest,
+ request: Request,
print: UnhandledRequestPrint,
) => void
@@ -33,15 +33,17 @@ export type UnhandledRequestStrategy =
| UnhandledRequestCallback
interface RequestHandlerGroups {
- rest: RestHandler[]
- graphql: GraphQLHandler[]
+ http: Array
+ graphql: Array
}
-function groupHandlersByType(handlers: RequestHandler[]): RequestHandlerGroups {
+function groupHandlersByType(
+ handlers: Array,
+): RequestHandlerGroups {
return handlers.reduce(
(groups, handler) => {
- if (handler instanceof RestHandler) {
- groups.rest.push(handler)
+ if (handler instanceof HttpHandler) {
+ groups.http.push(handler)
}
if (handler instanceof GraphQLHandler) {
@@ -51,7 +53,7 @@ function groupHandlersByType(handlers: RequestHandler[]): RequestHandlerGroups {
return groups
},
{
- rest: [],
+ http: [],
graphql: [],
},
)
@@ -60,11 +62,11 @@ function groupHandlersByType(handlers: RequestHandler[]): RequestHandlerGroups {
type RequestHandlerSuggestion = [number, RequestHandler]
type ScoreGetterFn = (
- request: MockedRequest,
+ request: Request,
handler: RequestHandlerType,
) => number
-function getRestHandlerScore(): ScoreGetterFn {
+function getHttpHandlerScore(): ScoreGetterFn {
return (request, handler) => {
const { path, method } = handler.info
@@ -107,12 +109,12 @@ function getGraphQLHandlerScore(
}
function getSuggestedHandler(
- request: MockedRequest,
- handlers: RestHandler[] | GraphQLHandler[],
- getScore: ScoreGetterFn | ScoreGetterFn,
-): RequestHandler[] {
- const suggestedHandlers = (handlers as RequestHandler[])
- .reduce((suggestions, handler) => {
+ request: Request,
+ handlers: Array | Array,
+ getScore: ScoreGetterFn | ScoreGetterFn,
+): Array {
+ const suggestedHandlers = (handlers as Array)
+ .reduce>((suggestions, handler) => {
const score = getScore(request, handler as any)
return suggestions.concat([[score, handler]])
}, [])
@@ -135,12 +137,15 @@ ${handlers.map((handler) => ` • ${handler.info.header}`).join('\n')}`
return `Did you mean to request "${handlers[0].info.header}" instead?`
}
-export function onUnhandledRequest(
- request: MockedRequest,
- handlers: RequestHandler[],
+export async function onUnhandledRequest(
+ request: Request,
+ handlers: Array,
strategy: UnhandledRequestStrategy = 'warn',
-): void {
- const parsedGraphQLQuery = tryCatch(() => parseGraphQLRequest(request))
+): Promise