Skip to content

Commit

Permalink
node: expose waitUntil (#805)
Browse files Browse the repository at this point in the history
* node: expose waitUntil

* refactor: tweaks

* test: add waitUntil

* fix: destructure FetchEvent methods

* test: extend assertions

* Create unlucky-masks-cheat.md
  • Loading branch information
Kikobeats authored Feb 13, 2024
1 parent ea31509 commit 2403ac2
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 25 deletions.
7 changes: 7 additions & 0 deletions .changeset/unlucky-masks-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@edge-runtime/node-utils": minor
"@edge-runtime/primitives": minor
"@edge-runtime/vm": minor
---

Expose `context.waitUntil` for Node.js
10 changes: 1 addition & 9 deletions packages/node-utils/src/node-to-edge/fetch-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ import { BuildDependencies } from '../types'

export function buildToFetchEvent(dependencies: BuildDependencies) {
return function toFetchEvent(request: Request) {
const event = new dependencies.FetchEvent(request)
Object.defineProperty(event, 'waitUntil', {
configurable: false,
enumerable: true,
get: () => {
throw new Error('waitUntil is not supported yet.')
},
})
return event
return new dependencies.FetchEvent(request)
}
}
41 changes: 31 additions & 10 deletions packages/node-utils/test/edge-to-node/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { runTestServer } from '../test-utils/run-test-server'
import { serializeResponse } from '../test-utils/serialize-response'
import * as Edge from '@edge-runtime/primitives'

function createDeferred() {
let resolve, reject
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
return { promise, resolve, reject }
}

const transformToNode = buildToNodeHandler(
{
Headers: Edge.Headers,
ReadableStream: Edge.ReadableStream,
Request: Edge.Request,
Uint8Array: Uint8Array,
Uint8Array,
FetchEvent: Edge.FetchEvent,
},
{ defaultOrigin: 'http://example.com' },
Expand Down Expand Up @@ -231,18 +240,30 @@ it('consumes incoming headers', async () => {
})
})

it('fails when using waitUntil()', async () => {
it('interacts with waitUntil', async () => {
let emitted = false
const deferred = createDeferred()
server = await runTestServer({
handler: transformToNode((req, evt) => {
evt.waitUntil(Promise.resolve())
return new Edge.Response('ok')
handler: transformToNode((_, { waitUntil }) => {
waitUntil(
new Promise((resolve) =>
setTimeout(() => {
emitted = true
// @ts-expect-error
deferred.resolve(Date.now())
resolve()
}, 1000),
),
)
return new Edge.Response('Hello world')
}),
})

const start = Date.now()
const response = await server.fetch('/')
expect(await serializeResponse(response)).toMatchObject({
status: 500,
statusText: 'Internal Server Error',
text: 'Error: waitUntil is not supported yet.',
})
const end = (await deferred.promise) as number
expect(emitted).toBe(true)
expect(end - start).toBeGreaterThanOrEqual(1000)
expect(response.status).toBe(200)
expect(await response.text()).toBe('Hello world')
})
10 changes: 6 additions & 4 deletions packages/node-utils/test/node-to-edge/fetch-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ it('returns a fetch event with a request', () => {
expect(event.request).toBe(request)
})

it('throws when accessing waitUntil', () => {
it('interacts with waitUntil', async () => {
const request = new EdgeRuntime.Request('https://vercel.com')
const event = toFetchEvent(request)
expect(() => event.waitUntil(Promise.resolve())).toThrow(
'waitUntil is not supported yet.',
)
let duration = Date.now()
event.waitUntil(new Promise((resolve) => setTimeout(resolve, 1000)))
await Promise.all(event.awaiting)
duration = Date.now() - duration
expect(duration).toBeGreaterThanOrEqual(1000)
})
4 changes: 2 additions & 2 deletions packages/primitives/src/primitives/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export class FetchEvent extends Event {
this.awaiting = new Set()
}

respondWith(response) {
respondWith = (response) => {
this.response = response
}

waitUntil(promise) {
waitUntil = (promise) => {
this.awaiting.add(promise)
promise.finally(() => this.awaiting.delete(promise))
}
Expand Down
19 changes: 19 additions & 0 deletions packages/vm/tests/edge-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,25 @@ describe('Event handlers', () => {
expect(promises).toEqual([50, 500])
})

it('allows to destructure `waitUntil` and `respondWith', async () => {
const runtime = new EdgeVM({
initialCode: `
const delay = ms => new Promise(resolve => {
setTimeout(() => resolve(ms), ms)
});
addEventListener('fetch', ({ waitUntil, respondWith }) => {
waitUntil(Promise.resolve(delay(50)))
waitUntil(Promise.resolve(delay(500)))
respondWith(new Response())
});
`,
})

const res = await runtime.dispatchFetch('https://edge-ping.vercel.app')
expect(res.status).toBe(200)
})

it('allows to add a `fetch` event handler that responds without leaking server headers on a 200', async () => {
const runtime = new EdgeVM()

Expand Down

0 comments on commit 2403ac2

Please sign in to comment.