Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 379836d

Browse files
CraigglesOmrbbot
andauthored
Feature: getMiniflareWaitUntil (#345)
* getMiniflareWaitUntil addded * tests pass * added ExecutionContext; WaitUntil Promises; resolve immediately * getMiniflareWaitUntil addded * set ExecutionContext to global; adjust tests accordingly * getMiniflareWaitUntil addded * cleanup; fix ava * remove extra waitUntil file * Add `getMiniflareWaitUntil()` support to Vitest environment Co-authored-by: CraigglesO <[email protected]> Co-authored-by: bcoll <[email protected]>
1 parent 4c1bfdb commit 379836d

File tree

7 files changed

+183
-3
lines changed

7 files changed

+183
-3
lines changed

packages/core/src/standards/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const SUGGEST_GLOBAL_BINDING_MODULES =
3434

3535
const kResponse = Symbol("kResponse");
3636
const kPassThrough = Symbol("kPassThrough");
37-
const kWaitUntil = Symbol("kWaitUntil");
37+
export const kWaitUntil = Symbol("kWaitUntil");
3838
const kSent = Symbol("kSent");
3939

4040
export class FetchEvent extends Event {

packages/jest-environment-miniflare/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { MiniflareCore } from "@miniflare/core";
1010
import { QueueBroker } from "@miniflare/queues";
1111
import { VMScriptRunner, defineHasInstances } from "@miniflare/runner-vm";
1212
import {
13+
ExecutionContext,
1314
PLUGINS,
1415
StackedMemoryStorageFactory,
1516
createMiniflareEnvironment,
@@ -127,6 +128,7 @@ export default class MiniflareEnvironment implements JestEnvironment<Timer> {
127128
},
128129
this.config.testEnvironmentOptions,
129130
{
131+
ExecutionContext,
130132
// Make sure fancy jest console and faked timers are included
131133
console: global.console,
132134
setTimeout: global.setTimeout,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
function testResponse(body) {
2+
return new Response(body, { headers: { "Cache-Control": "max-age=3600" } });
3+
}
4+
5+
test("FetchEvent: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
6+
// build a FetchEvent:
7+
const url = "http://localhost/fetchEvent/waitUntil";
8+
const request = new Request(url);
9+
const fetchEvent = new FetchEvent("fetch", { request });
10+
11+
// run a waitUntil
12+
fetchEvent.waitUntil(caches.default.put(url, testResponse("example cache")));
13+
14+
// ensure that waitUntil has yet to be run
15+
let cachedResponse = await caches.default.match(url);
16+
expect(cachedResponse).toBeUndefined();
17+
18+
// pull in the waitUntil stack
19+
const waitUntilList = await getMiniflareWaitUntil(fetchEvent);
20+
expect(waitUntilList).not.toBeNull();
21+
22+
cachedResponse = await caches.default.match(url);
23+
expect(cachedResponse).toBeTruthy();
24+
expect(await cachedResponse.text()).toBe("example cache");
25+
});
26+
27+
test("ScheduledEvent: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
28+
const url = "http://localhost/scheduledEvent/waitUntil";
29+
30+
const scheduledEvent = new ScheduledEvent("scheduled", {
31+
scheduledTime: 1000,
32+
cron: "30 * * * *",
33+
});
34+
35+
// run a waitUntil
36+
scheduledEvent.waitUntil(
37+
caches.default.put(url, testResponse("example cache"))
38+
);
39+
40+
// ensure that waitUntil has yet to be run
41+
let cachedResponse = await caches.default.match(url);
42+
expect(cachedResponse).toBeUndefined();
43+
44+
// pull in the waitUntil stack
45+
const waitUntilList = await getMiniflareWaitUntil(scheduledEvent);
46+
expect(waitUntilList).not.toBeNull();
47+
48+
cachedResponse = await caches.default.match(url);
49+
expect(cachedResponse).toBeTruthy();
50+
expect(await cachedResponse.text()).toBe("example cache");
51+
});
52+
53+
test("ExecutionContext: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
54+
const url = "http://localhost/ctx/waitUntil";
55+
56+
const ctx = new ExecutionContext();
57+
expect(Object.getPrototypeOf(ctx).constructor.name).toBe("ExecutionContext");
58+
59+
// run a waitUntil
60+
ctx.waitUntil(caches.default.put(url, testResponse("example cache")));
61+
62+
// ensure that waitUntil has yet to be run
63+
let cachedResponse = await caches.default.match(url);
64+
expect(cachedResponse).toBeUndefined();
65+
66+
// pull in the waitUntil stack
67+
const waitUntilList = await getMiniflareWaitUntil(ctx);
68+
expect(waitUntilList).not.toBeNull();
69+
70+
cachedResponse = await caches.default.match(url);
71+
expect(cachedResponse).toBeTruthy();
72+
expect(await cachedResponse.text()).toBe("example cache");
73+
});

packages/shared-test-environment/src/globals.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { MiniflareCore } from "@miniflare/core";
1+
import {
2+
FetchEvent,
3+
MiniflareCore,
4+
ScheduledEvent,
5+
kWaitUntil,
6+
} from "@miniflare/core";
27
import {
38
DurableObjectId,
49
DurableObjectStorage,
@@ -7,12 +12,25 @@ import { Context } from "@miniflare/shared";
712
import { MockAgent } from "undici";
813
import { PLUGINS } from "./plugins";
914

15+
export class ExecutionContext {
16+
[kWaitUntil]: Promise<unknown>[] = [];
17+
18+
passThroughOnException(): void {}
19+
20+
waitUntil(promise: Promise<any>): void {
21+
this[kWaitUntil].push(promise);
22+
}
23+
}
24+
1025
declare global {
1126
function getMiniflareBindings<Bindings = Context>(): Bindings;
1227
function getMiniflareDurableObjectStorage(
1328
id: DurableObjectId
1429
): Promise<DurableObjectStorage>;
1530
function getMiniflareFetchMock(): MockAgent;
31+
function getMiniflareWaitUntil<WaitUntil extends any[] = unknown[]>(
32+
event: FetchEvent | ScheduledEvent | ExecutionContext
33+
): Promise<WaitUntil>;
1634
function flushMiniflareDurableObjectAlarms(
1735
ids: DurableObjectId[]
1836
): Promise<void>;
@@ -24,6 +42,9 @@ export interface MiniflareEnvironmentUtilities {
2442
id: DurableObjectId
2543
): Promise<DurableObjectStorage>;
2644
getMiniflareFetchMock(): MockAgent;
45+
getMiniflareWaitUntil<WaitUntil extends any[] = unknown[]>(
46+
event: FetchEvent | ScheduledEvent | ExecutionContext
47+
): Promise<WaitUntil>;
2748
flushMiniflareDurableObjectAlarms(ids: DurableObjectId[]): Promise<void>;
2849
}
2950

@@ -48,6 +69,11 @@ export async function createMiniflareEnvironmentUtilities(
4869
getMiniflareFetchMock() {
4970
return fetchMock;
5071
},
72+
getMiniflareWaitUntil<WaitUntil extends any[] = unknown[]>(
73+
event: FetchEvent | ScheduledEvent | ExecutionContext
74+
): Promise<WaitUntil> {
75+
return Promise.all(event[kWaitUntil]) as Promise<WaitUntil>;
76+
},
5177
async flushMiniflareDurableObjectAlarms(ids?: DurableObjectId[]) {
5278
const plugin = (await mf.getPlugins()).DurableObjectsPlugin;
5379
const storage = mf.getPluginStorage("DurableObjectsPlugin");

packages/shared-test-environment/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PLUGINS } from "./plugins";
1010

1111
export * from "./plugins";
1212
export * from "./storage";
13+
export { ExecutionContext } from "./globals";
1314
export type { MiniflareEnvironmentUtilities } from "./globals";
1415

1516
const log = new NoOpLog();

packages/vitest-environment-miniflare/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { QueueBroker } from "@miniflare/queues";
22
import { VMScriptRunner } from "@miniflare/runner-vm";
33
import {
4+
ExecutionContext,
45
StackedMemoryStorageFactory,
56
createMiniflareEnvironment,
67
} from "@miniflare/shared-test-environment";
@@ -66,7 +67,8 @@ export default <Environment>{
6667
const storageFactory = new StackedMemoryStorageFactory();
6768
const [mf, mfGlobalScope] = await createMiniflareEnvironment(
6869
{ storageFactory, scriptRunner, queueBroker },
69-
options
70+
options,
71+
{ ExecutionContext }
7072
);
7173

7274
// Attach isolated storage setup function
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { expect, test } from "vitest";
2+
setupMiniflareIsolatedStorage();
3+
4+
function testResponse(body) {
5+
return new Response(body, { headers: { "Cache-Control": "max-age=3600" } });
6+
}
7+
8+
test("FetchEvent: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
9+
// build a FetchEvent:
10+
const url = "http://localhost/fetchEvent/waitUntil";
11+
const request = new Request(url);
12+
const fetchEvent = new FetchEvent("fetch", { request });
13+
14+
// run a waitUntil
15+
fetchEvent.waitUntil(caches.default.put(url, testResponse("example cache")));
16+
17+
// ensure that waitUntil has yet to be run
18+
let cachedResponse = await caches.default.match(url);
19+
expect(cachedResponse).toBeUndefined();
20+
21+
// pull in the waitUntil stack
22+
const waitUntilList = await getMiniflareWaitUntil(fetchEvent);
23+
expect(waitUntilList).not.toBeNull();
24+
25+
cachedResponse = await caches.default.match(url);
26+
expect(cachedResponse).toBeTruthy();
27+
expect(await cachedResponse.text()).toBe("example cache");
28+
});
29+
30+
test("ScheduledEvent: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
31+
const url = "http://localhost/scheduledEvent/waitUntil";
32+
33+
const scheduledEvent = new ScheduledEvent("scheduled", {
34+
scheduledTime: 1000,
35+
cron: "30 * * * *",
36+
});
37+
38+
// run a waitUntil
39+
scheduledEvent.waitUntil(
40+
caches.default.put(url, testResponse("example cache"))
41+
);
42+
43+
// ensure that waitUntil has yet to be run
44+
let cachedResponse = await caches.default.match(url);
45+
expect(cachedResponse).toBeUndefined();
46+
47+
// pull in the waitUntil stack
48+
const waitUntilList = await getMiniflareWaitUntil(scheduledEvent);
49+
expect(waitUntilList).not.toBeNull();
50+
51+
cachedResponse = await caches.default.match(url);
52+
expect(cachedResponse).toBeTruthy();
53+
expect(await cachedResponse.text()).toBe("example cache");
54+
});
55+
56+
test("ExecutionContext: waitUntil: await resolving promise array using getMiniflareWaitUntil", async () => {
57+
const url = "http://localhost/ctx/waitUntil";
58+
59+
const ctx = new ExecutionContext();
60+
expect(Object.getPrototypeOf(ctx).constructor.name).toBe("ExecutionContext");
61+
62+
// run a waitUntil
63+
ctx.waitUntil(caches.default.put(url, testResponse("example cache")));
64+
65+
// ensure that waitUntil has yet to be run
66+
let cachedResponse = await caches.default.match(url);
67+
expect(cachedResponse).toBeUndefined();
68+
69+
// pull in the waitUntil stack
70+
const waitUntilList = await getMiniflareWaitUntil(ctx);
71+
expect(waitUntilList).not.toBeNull();
72+
73+
cachedResponse = await caches.default.match(url);
74+
expect(cachedResponse).toBeTruthy();
75+
expect(await cachedResponse.text()).toBe("example cache");
76+
});

0 commit comments

Comments
 (0)