Skip to content

Commit

Permalink
Implement Vitest testing environment for Miniflare 3 (#4795)
Browse files Browse the repository at this point in the history
* [miniflare] Add support for module fallback service

Adds a `unsafeUseModuleFallbackService` worker option and a
`unsafeModuleFallbackService` shared option for using `workerd`'s
module fallback service. Note `workerd` only accepts an address for
fallback service configuration. This means if we wanted per-worker
fallback service configuration, we'd need to start a new HTTP server
for each worker that enabled it. To avoid starting additional servers,
we reuse Miniflare's existing loopback server, and allow each worker
to enable/disable the fallback service instead.

* [miniflare] Return `Response` from `fetch()` if socket upgrade failed

Previously, Miniflare would throw an error if the `Upgrade: websocket`
header was set, but a non-WebSocket response was returned. This change
ensures the response is returned, just without `webSocket` set. This
allows the caller to handle the error case/response themselves.

* MVP for `@cloudflare/vitest-pool-workers`

Implements a basic Vitest pool that runs tests inside `workerd`,
using the module fallback service and a test runner Durable Object.
Provides access to bindings via `env` from the `cloudflare:test`
module.

Closes DEVX-1052 and DEVX-1053

* Implement workspace/test file worker isolation

Adds a `singleWorker` option for running all a project's test files
serially in the same worker. This can speed up execution if the
project contains lots of small test files, as Vitest only needs to
be initialised once.

* Add Durable Object test helpers and support for self-service bindings

Adds a `runInDurableObject()` test helper for running arbitrary code
inside a Durable Object's I/O context, and a `runDurableObjectAlarm()`
helper for running a Durable Object's scheduled alarms.

These require the new `main` option to be set to the worker's
entrypoint, so the Vitest pool can create instances of the user's
Durable Objects.

The `main` option also allows service bindings to be declared to the
worker running the tests. Events dispatched to these service bindings
will use the handler exported by the `main` worker.

Closes DEVX-1056, DEVX-1057 and DEVX-1058

* Add support for fetch mocking with `undici` `MockAgent`

Adds `fetchMock` to the `cloudflare:test` module. This is an instance
of `undici`'s `MockAgent`, and can be used to mock global `fetch()`
requests in the test worker. This works by bundling just the `undici`
`MockAgent` code, with a customisable `Dispatcher` that is set to the
original workers `fetch()` function in the pool.

Closes DEVX-1059

* Use custom test runner and reset fetch mock at the start of each file

A custom test runner allows us to hook into test execution.
Specifically, this allows us to run code before and after
files/suites/tests are executed. We'll need this for isolated storage.

* Add (inline) snapshot support and fix source maps

By default, Vitest uses the `node:fs` module for reading/writing
snapshots. This isn't available in workers, so this change implements
a custom snapshot client using service bindings.

Closes DEVX-1054

* Fix logs not showing on re-runs

* [miniflare] Export `formatZodError`

We'd like to use Zod for validating the pool's options. Miniflare has
a `formatZodError` function that makes pretty error messages from Zod
validation errors.

* Validate pool options with Zod and pretty error formatting

* Bump to stable `vitest@1.0.1`

* Add support for serialising any structured serialisable

* Add support for fake timers

* Verify module mocking and spies work

* [miniflare] Add support for "sticky blobs" that aren't deleted

For isolated per-test storage, we'll be pushing/popping databases
to a "stack". After pushing a database stack frame, we may delete a
record in a test. We'd like to avoid deleting the associated blob here
so when we pop the frame, the record still links up with it.

This change adds an `unsafeStickyBlobs` option that disables garbage
blob cleanup if enabled.

* [miniflare] Add support for getting persistence paths

Isolated storage will need access to SQLite database files. This
change adds a `unsafeGetPersistPaths()` function for getting the
persistence paths used by a `Miniflare` instance. This is unsafe as
the layout of persistence directories is not a public interface.

* [miniflare] Add support for colo-local ephemeral "Durable" Objects

For isolated storage, we'd like the runner object not to persist any
state to disk, so we don't have to worry about excluding it when
managing the storage stack. We currently have an
`unsafeEphemeralDurableObjects` option that makes _all_ Durable
Objects in a worker use in-memory state. This isn't suitable for our
Vitest pool, as user objects will run in the same worker as the runner
object.

This change adds support for `workerd` "colo-local" actors. These are
like Durable Objects, but don't provide any durable storage (i.e. no
`state.storage` API), and have a slightly different namespace binding
API.

* [miniflare] Bind `this` to `Miniflare` instance in custom services

We'd like to extend our Vitest pool's custom loopback service with
endpoints for managing isolated storage stacks. To do this, we need to
know the persistence paths of the `Miniflare` instance we're currently
running in. A previous commit added an `unsafeGetPersistPaths()`
function. If we wanted to access these paths in the pool's loopback
service, we'd need to capture the paths/`Miniflare` instance in a
closure used as the service binding function. Unfortunately, we use
`util.deepStrictEqual()` on Miniflare options to determine if we
should restart the `Miniflare` instance. This would always return
`true` if we created a new bound/arrow function on each test run.

This change binds `this` to the current `Miniflare` instance when
calling custom service functions, meaning we can reuse the same
function when building options.

* Add support for isolated storage

This change adds an `isolatedStorage` pool option. If enabled, each
test gets its own isolated storage environment. Storage writes in a
test are automatically undone at the end of the test. The storage
environment is copied from the parent suite, ensuring writes from
`beforeAll()`s show up in tests.

This is implemented with an on-disk stack of `.sqlite` files. Before
a suite/test attempt begins, the current storage state is "pushed".
When the suite/test attempt completes/fails, the state is "popped",
undoing all changes. Sticky blobs are used to ensure deleting records
in a test keeps the corresponding blobs around, so they can be used
when the state is "popped".

Closes DEVX-1055

* [miniflare] Use `z.input()` for user facing options types

Previously, we were using `z.infer()` on our option schemas to derive
`WorkerOptions` and `SharedOptions` types. This gave the _output_
types for schemas, rather than the user _input_ types. Usually, these
were the same. However, if a schema was `.transform()`ed, they could
be different. Notably, `hyperdrives` values output objects, but accept
strings, leading to type errors if these were used.

* Add `defineWorkersPoolOptions()` for typed pool options in config

* [miniflare] Easy service bindings to self

* Use Miniflare's mechanism for self-service bindings

* Tidy up Node and package polyfills

Tries to remove unnecessary polyfills/functions, so users are less
likely to import APIs that would be unsupported in production in their
tests.

* Throw actual unhandled errors in pool on failed test runs

Tunnels through the error thrown when running tests fails. This is
useful for debugging, as it means the startup error thrown by Vitest
includes a useful message and stack.

* Add argument validation to `cloudflare:test` Durable Object helpers

While we expect most users to write their tests in TypeScript,
JavaScript tests are also supported. Since we can't guarantee correct
types at runtime, this change adds type validation to test helpers,
throwing errors in the same JSG-style as other `workerd` APIs.

* [miniflare] Add support for `rootPath`s

Previously, Miniflare would always resolve path-valued options
relative to the current working directory. For the Vitest pool, we'd
like to be able to resolve these relative to the project config file.

This change adds back Miniflare 2's `rootPath` option for controlling
the resolution directory. `rootPath` can be set as a shared or
per-worker option. Path-valued options resolve relative to the closest
`rootPath`.

* [miniflare] Fix linting scripts

* Resolve pool Miniflare options relative to `vitest.config.ts`

...rather than the current working directory. This ensures paths are
always resolved to the same location when `vitest` is run in different
directories.

* Support injecting provided values in pool options

Vitest global setup hooks provide a good place to start auxiliary
servers required by tests (e.g. upstream). This change allows provided
values to be injected into the Miniflare config (e.g. using port from
global setup in hyperdrive local connection string).

* Support seeding data in config and reset storage between runs

This change adds a `setupEnvironment(env)` function called with a
bindings proxy per-`Miniflare`-instance per-test-run. This should be
used for seeding data from disk (e.g. applying D1 migrations).

* [miniflare] Fix `DurableObjectStub` detection

Enabling Durable Object JS RPC changes the class name of
`DurableObjectStub` from `DurableObject` to `WorkerRpc`. This was
breaking `Fetcher#fetch()` detection and WebSocket-upgrading
`DurableObjectStub#fetch()`es when using the magic proxy.

* Bump to `vitest@1.1.3`

* Support calling `fetch`/`scheduled`/`queue` handlers directly

This change adds some new test helpers useful when importing Worker
source code and calling handlers directly. Namely...

- `createExecutionContext()` for creating an `ExecutionContext`
- `getWaitUntil()` for waiting for all `ctx.waitUntil()` callbacks
- `createScheduledController()` for creating a `scheduled` handler's
  `ScheduledController`
- `getScheduledResult()` for getting the "no-retry" state of a
  `ScheduledController`
- `createMessageBatch()` for creating a `queue` handler's
  `MessageBatch`
- `getQueueResult()` for getting ack/retry state of a `MessageBatch`

Note `createScheduledController()`/`getScheduledResult()` and
`createMessageBatch()`/`getQueueResult()` take/return the same types
as `Fetcher#scheduled()` and `Fetcher#queue()` respectively.

Closes DEVX-1060

* Block custom environments

Our Vitest pool runs tests inside `workerd` itself. This means we
don't need to use Vitest's custom environment feature to setup the
correct global scope. Modifying the global scope would result in some
franken-`workerd`-environment that didn't match production.

With Miniflare 2's `vitest-environment-miniflare`, we _did_ require
users to enable a custom environment. Considering this is a
significant difference, this change adds a very helpful error message
if we detect a custom `environment` is configured.

* Assert compatible Vitest version on startup

Our Vitest pool currently depends on internal Vitest APIs that are not
protected by semantic-versioning guarantees. Therefore, this change
adds a runtime sanity check for a peer-dependency version constraint
on `vitest`.

* Fix fallback service on Windows

* [miniflare] Update API docs for unsupported products

* Integrate `vitest-pool-workers` with monorepo tooling

* Only include `dist` directory when publishing

* [pre] Move `CloudflareTestEnv` to `cloudflare:test` `ProvidedEnv`

* [pre] Use correct project path for non-workspace projects

`WorkspaceProject#path` was actually giving the path to the directory
containing the Vitest config, not the config file itself, for
non-workspace projects.

* [pre] Manually construct `__dirname`s

`vite-node` was transforming the pool files during development, but
externalising them when the pool was depended on externally. When
Vite transforms files, it automatically injects `__dirname`, even
though these are ES modules, which shouldn't have this by default.

* [pre] Construct serialised `poolOptions` to prevent serialising symbol

* [pre] Include `cloudflare:test` types in package

These can be referenced by adding `@cloudflare/vitest-pool-workers` to
the `types` array in `tsconfig.json`.

* Fixup comments: move method comments to JSDocs & add additional docs

* fixup! [miniflare] Add support for `rootPath`s

* Allow pool restarts on config change

Vitest will dispose of the pool and recreate it when the Vitest config
changes. This was causing `Miniflare` instances to be disposed, then
reused when the pool was recreated. This change moves the project
cache from the module-level to a "pool-level" local variable.

* Get builtin module list and `ExportedHandler` keys from RTTI

* fixup! Add support for fetch mocking with `undici` `MockAgent`

Our mock agent implementation depends on `undici` internals. This
change ensures we pin our version, so we don't accidentally upgrade
to an incompatible version.

* fixup! [miniflare] Bind `this` to `Miniflare` instance in custom services

Switch to passing `Miniflare` instance as parameter instead of `this`

* Use the same `esbuild` version as `wrangler`

* Use `node:path/posix` for posix path helpers in module fallback

* fixup! Fix fallback service on Windows

* Check all parents in `"type": "module"` cache before reading files

* fixup! Support seeding data in config and reset storage between runs

Rename abort-all helper worker

* Remove unnecessary `if` statement in fallback service

* fixup! Tidy up Node and package polyfills

Update not-implemented messages

* Remove unnecessarily complex `groupBy()`

* Add back support for listing Durable Objects in a namespace

Adds a `listDurableObjectIds()` test helper that behaves similarly to
`getMiniflareDurableObjectIds()` from Miniflare 2's test environments.

* [miniflare] Allow `URL`s to be passed in `hyperdrives` option

Importantly, this change makes the Zod output type of `hyperdrives`
assignable to the input type. This means we can pass the output of
parsing options schemas back to the `new Miniflare()` constructor.
Also adds a few more `hyperdrives` tests to verify this doesn't change
behaviour.

* Remove `setupEnvironment()` in favor of services to access file-system

* Add `SELF` export to `cloudflare:test` module

Exposing `kCurrentWorker` to users for integration testing wasn't very
nice. This is a common use case so this change ensures it's always
bound and adds it directly to the `cloudflare:test` module.

* Replace `getWaitUntil` with `waitOnExecutionContext` and `void` return

This function aims to allow users to wait on side-effecting
`waitUntil`s, then assert on their effects. `workerd` ignores the
resolved value of `Promise`s, so exposing these may make it trickier
to change the implementation of this function in the future.

* Remove redundant `isolateDurableObjectBindings` serialised option

`durableObjectBindingDesignators` includes the same and more
information, so there's no need to pass this.

* Fail if required compatibility flags aren't set

This makes it explicit which flags we're enabling, and helps avoid
behaviour differences when running `wrangler dev` or deployed code.

* Rename `WorkersProjectOptions` to `WorkersPoolOptions`

* fixup! Add support for isolated storage

Clarify `StackedStorageState` is per `Miniflare`-instance

* fixup! Add `SELF` export to `cloudflare:test` module

Update docs in `config.ts`

* Format with monorepo's `prettier` version
  • Loading branch information
mrbbot authored Feb 16, 2024
1 parent 86d94ff commit 027f971
Show file tree
Hide file tree
Showing 117 changed files with 8,280 additions and 249 deletions.
26 changes: 26 additions & 0 deletions .changeset/poor-carrots-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"miniflare": minor
---

feat: pass `Miniflare` instance as argument to custom service binding handlers

This change adds a new `Miniflare`-typed parameter to function-valued service binding handlers. This provides easy access to the correct bindings when re-using service functions across instances.

<!--prettier-ignore-start-->

```js
import assert from "node:assert";
import { Miniflare, Response } from "miniflare";

const mf = new Miniflare({
// ...
serviceBindings: {
SERVICE(request, instance) {
assert(instance === mf);
return new Response();
},
},
});
```

<!--prettier-ignore-end-->
7 changes: 7 additions & 0 deletions .changeset/proud-plums-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": minor
---

feat: allow `URL`s to be passed in `hyperdrives`

Previously, the `hyperdrives` option only accepted `string`s as connection strings. This change allows `URL` objects to be passed too.
39 changes: 39 additions & 0 deletions .changeset/rotten-suns-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
"miniflare": minor
---

feat: add support for custom root paths

Miniflare has lots of file-path-valued options (e.g. `scriptPath`, `kvPersist`, `textBlobBindings`). Previously, these were always resolved relative to the current working directory before being used. This change adds a new `rootPath` shared, and per-worker option for customising this behaviour. Instead of resolving relative to the current working directory, Miniflare will now resolve path-valued options relative to the closest `rootPath` option. Paths are still resolved relative to the current working directory if no `rootPath`s are defined. Worker-level `rootPath`s are themselves resolved relative to the shared `rootPath` if defined.

<!--prettier-ignore-start-->

```js
import { Miniflare } from "miniflare";

const mf1 = new Miniflare({
scriptPath: "index.mjs", // Resolves to "$PWD/index.mjs"
});

const mf2 = new Miniflare({
rootPath: "a/b",
scriptPath: "c/index.mjs", // Resolves to "$PWD/a/b/c/index.mjs"
});

const mf3 = new Miniflare({
rootPath: "/a/b",
workers: [
{
name: "1",
rootPath: "c",
scriptPath: "index.mjs", // Resolves to "/a/b/c/index.mjs"
},
{
name: "2",
scriptPath: "index.mjs", // Resolves to "/a/b/index.mjs"
}
],
});
```

<!--prettier-ignore-end-->
7 changes: 7 additions & 0 deletions .changeset/sharp-mayflies-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

fix: return non-WebSocket responses for failed WebSocket upgrading `fetch()`es

Previously, Miniflare's `fetch()` would throw an error if the `Upgrade: websocket` header was set, and a non-WebSocket response was returned from the origin. This change ensures the non-WebSocket response is returned from `fetch()` instead, with `webSocket` set to `null`. This allows the caller to handle the response as they see fit.
34 changes: 34 additions & 0 deletions .changeset/tidy-beans-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"miniflare": minor
---

feat: allow easy binding to current worker

Previously, if you wanted to create a service binding to the current Worker, you'd need to know the Worker's name. This is usually possible, but can get tricky when dealing with many Workers. This change adds a new `kCurrentWorker` symbol that can be used instead of a Worker name in `serviceBindings`. `kCurrentWorker` always points to the Worker with the binding.

<!--prettier-ignore-start-->

```js
import { Miniflare, kCurrentWorker } from "miniflare";

const mf = new Miniflare({
serviceBindings: {
SELF: kCurrentWorker,
},
modules: true,
script: `export default {
fetch(request, env, ctx) {
const { pathname } = new URL(request.url);
if (pathname === "/recurse") {
return env.SELF.fetch("http://placeholder");
}
return new Response("body");
}
}`,
});

const response = await mf.dispatchFetch("http://placeholder/recurse");
console.log(await response.text()); // body
```

<!--prettier-ignore-end-->
7 changes: 7 additions & 0 deletions .changeset/yellow-kings-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

fix: ensure `MiniflareOptions`, `WorkerOptions`, and `SharedOptions` types are correct

Miniflare uses Zod for validating options. Previously, Miniflare inferred `*Options` from the _output_ types of its Zod schemas, rather than the _input_ types. In most cases, these were the same. However, the `hyperdrives` option has different input/output types, preventing these from being type checked correctly.
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ fixtures/**/dist/**
# Miniflare shouldn't be formatted with the root `prettier` version
packages/miniflare

# Generated Cap'n Proto files shouldn't be formatted
*.capnp.js
*.capnp.d.ts

# In the C3 templates, in particular framework templates, we want to be able to
# use any format that the framework authors prefer/use in their own templates,
# so let's ignore all the c3 template files, exlcuding the c3.ts ones
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"fix": "pnpm run prettify && dotenv -- turbo check:lint -- --fix",
"prettify": "prettier . --write --ignore-unknown",
"test": "dotenv -- turbo test",
"test:ci": "vitest run && dotenv -- turbo test:ci --filter=wrangler --filter=miniflare --filter=kv-asset-handler",
"test:ci": "vitest run && dotenv -- turbo test:ci --filter=wrangler --filter=miniflare --filter=kv-asset-handler --filter=@cloudflare/vitest-pool-workers",
"test:watch": "turbo test:watch",
"type:tests": "dotenv -- turbo type:tests",
"gen:package": "turbo gen package"
Expand Down
42 changes: 34 additions & 8 deletions packages/miniflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ parameter in module format Workers.
Unique name for this worker. Only required if multiple `workers` are
specified.

- `rootPath?: string`

Path against which all other path options for this Worker are resolved
relative to. This path is itself resolved relative to the `rootPath` from
`SharedOptions` if multiple workers are specified. Defaults to the current
working directory.

- `script?: string`

JavaScript code for this worker. If this is a service worker format Worker, it
Expand Down Expand Up @@ -289,7 +296,7 @@ parameter in module format Workers.
Record mapping binding name to paths containing arbitrary binary data to
inject as `ArrayBuffer` bindings into this Worker.
- `serviceBindings?: Record<string, string | { network: Network } | { external: ExternalServer } | { disk: DiskDirectory } | (request: Request) => Awaitable<Response>>`
- `serviceBindings?: Record<string, string | typeof kCurrentWorker | { network: Network } | { external: ExternalServer } | { disk: DiskDirectory } | (request: Request, instance: Miniflare) => Awaitable<Response>>`
Record mapping binding name to service designators to inject as
`{ fetch: typeof fetch }`
Expand All @@ -298,6 +305,8 @@ parameter in module format Workers.
- If the designator is a `string`, requests will be dispatched to the Worker
with that `name`.
- If the designator is `(await import("miniflare")).kCurrentWorker`, requests
will be dispatched to the Worker defining the binding.
- If the designator is an object of the form `{ network: { ... } }`, where
`network` is a
[`workerd` `Network` struct](https://github.com/cloudflare/workerd/blob/bdbd6075c7c53948050c52d22f2dfa37bf376253/src/workerd/server/workerd.capnp#L555-L598),
Expand All @@ -313,7 +322,8 @@ parameter in module format Workers.
directory.
- If the designator is a function, requests will be dispatched to your custom
handler. This allows you to access data and functions defined in Node.js
from your Worker.
from your Worker. Note `instance` will be the `Miniflare` instance
dispatching the request.
<!--prettier-ignore-start-->
Expand Down Expand Up @@ -509,10 +519,11 @@ parameter in module format Workers.
- `d1Databases?: Record<string, string> | string[]`
Record mapping binding name to D1 database IDs to inject as `Fetcher` bindings
into this Worker. Note these bindings must be wrapped with a facade to provide
the expected `D1Database` API. Different Workers may bind to the same database
ID with different binding names. If a `string[]` of binding names is
Record mapping binding name to D1 database IDs to inject as `D1Database`
bindings into this Worker. Note binding names starting with `__D1_BETA__` are
injected as `Fetcher` bindings instead, and must be wrapped with a facade to
provide the expected `D1Database` API. Different Workers may bind to the same
database ID with different binding names. If a `string[]` of binding names is
specified, the binding name and database ID are assumed to be the same.
#### Queues
Expand All @@ -531,16 +542,31 @@ parameter in module format Workers.
have at most one consumer. If a `string[]` of queue names is specified,
default consumer options will be used.
#### Analytics Engine
#### Analytics Engine, Sending Email, Vectorize and Workers for Platforms
_Not yet supported_
If you need support for these locally, consider using the `wrappedBindings`
option to mock them out.
#### Browser Rendering and Workers AI
_Not yet supported_
If you need support for these locally, consider using the `serviceBindings`
option to mock them out.
### `interface SharedOptions`
Options shared between all Workers/"nanoservices".
#### Core
- `rootPath?: string`
Path against which all other path options for this instance are resolved
relative to. Defaults to the current working directory.
- `host?: string`
Hostname that the `workerd` server should listen on. Defaults to `127.0.0.1`.
Expand Down Expand Up @@ -656,7 +682,7 @@ Options shared between all Workers/"nanoservices".

Where to persist data stored in D1 databases. See docs for `Persistence`.

#### Analytics Engine
#### Analytics Engine, Browser Rendering, Sending Email, Vectorize, Workers AI and Workers for Platforms

_Not yet supported_

Expand Down
3 changes: 2 additions & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"dev": "concurrently -n esbuild,typechk,typewrk -c yellow,blue,blue.dim \"node scripts/build.mjs watch\" \"node scripts/types.mjs tsconfig.json watch\" \"node scripts/types.mjs src/workers/tsconfig.json watch\"",
"test": "node scripts/build.mjs && ava && rimraf ./.tmp",
"test:ci": "pnpm run test",
"check:lint": "eslint \"{src,test}/**/*.ts\" \"scripts/**/*.{js,mjs}\" \"types/**/*.ts\"",
"check:lint": "eslint --max-warnings=0 \"{src,test}/**/*.ts\" \"scripts/**/*.{js,mjs}\" \"types/**/*.ts\"",
"lint:fix": "pnpm run check:lint --fix",
"types:build": "node scripts/types.mjs tsconfig.json && node scripts/types.mjs src/workers/tsconfig.json"
},
Expand Down Expand Up @@ -76,6 +76,7 @@
"devalue": "^4.3.0",
"devtools-protocol": "^0.0.1182435",
"esbuild": "^0.16.17",
"eslint": "^8.6.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-import": "2.26.x",
Expand Down
30 changes: 19 additions & 11 deletions packages/miniflare/src/http/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,28 @@ export async function fetch(
...rejectUnauthorized,
});

// Get response headers from upgrade
const headersPromise = new DeferredPromise<Headers>();
const responsePromise = new DeferredPromise<Response>();
ws.once("upgrade", (req) => {
headersPromise.resolve(headersFromIncomingRequest(req));
const headers = headersFromIncomingRequest(req);
// Couple web socket with pair and resolve
const [worker, client] = Object.values(new WebSocketPair());
const couplePromise = coupleWebSocket(ws, client);
const response = new Response(null, {
status: 101,
webSocket: worker,
headers,
});
responsePromise.resolve(couplePromise.then(() => response));
});

// Couple web socket with pair and resolve
const [worker, client] = Object.values(new WebSocketPair());
await coupleWebSocket(ws, client);
return new Response(null, {
status: 101,
webSocket: worker,
headers: await headersPromise,
ws.once("unexpected-response", (_, req) => {
const headers = headersFromIncomingRequest(req);
const response = new Response(req, {
status: req.statusCode,
headers,
});
responsePromise.resolve(response);
});
return responsePromise;
}

const response = await baseFetch(request, {
Expand Down
Loading

0 comments on commit 027f971

Please sign in to comment.