Skip to content

Commit e2f2246

Browse files
committed
refactor(vitest-pool-workers): reset ratelimit bindings by name instead of globalThis
Removes the globalThis[Symbol.for(...)] bridge between the ratelimit extension module and reset(). The Ratelimit class already exposes a public reset() method; reset() now calls it directly on each configured ratelimit binding, using binding names read from the parsed wrangler config (pool/index.ts) via a new __VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES module. This avoids walking `env` (which would touch RPC/entrypoint bindings like SELF) and avoids the miniflare wrapped-binding module entirely, so it needs no changes to the ratelimit plugin. Also fixes formatting on the existing changeset file (oxfmt).
1 parent b449326 commit e2f2246

8 files changed

Lines changed: 42 additions & 52 deletions

File tree

.changeset/vitest-pool-workers-reset-ratelimit.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
---
55

66
`reset()` from `cloudflare:test` now resets ratelimit binding state between tests. Previously, `RATE_LIMITERS` bindings retained their in-memory bucket counts across test boundaries, causing later tests in the same file to see stale rate-limit exhaustion state.
7-

packages/miniflare/src/plugins/ratelimit/index.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export const RATELIMIT_PLUGIN_NAME = "ratelimit";
2525
const SERVICE_RATELIMIT_PREFIX = `${RATELIMIT_PLUGIN_NAME}`;
2626
const SERVICE_RATELIMIT_MODULE = `cloudflare-internal:${SERVICE_RATELIMIT_PREFIX}:module`;
2727

28-
// Reserved binding name exposing a `resetAll()` hook for all ratelimit
29-
// bindings in this worker. Used by vitest-pool-workers' `reset()`; not part
30-
// of the public API.
31-
export const RATELIMIT_CONTROL_BINDING_NAME = "__MINIFLARE_RATELIMIT_CONTROL__";
32-
3328
function buildJsonBindings(bindings: Record<string, any>): Worker_Binding[] {
3429
return Object.entries(bindings).map(([name, value]) => ({
3530
name,
@@ -57,13 +52,6 @@ export const RATELIMIT_PLUGIN: Plugin<typeof RatelimitOptionsSchema> = {
5752
},
5853
})
5954
);
60-
bindings.push({
61-
name: RATELIMIT_CONTROL_BINDING_NAME,
62-
wrapped: {
63-
moduleName: SERVICE_RATELIMIT_MODULE,
64-
innerBindings: buildJsonBindings({ control: true }),
65-
},
66-
});
6755
return bindings;
6856
},
6957
getNodeBindings(options: z.infer<typeof RatelimitOptionsSchema>) {

packages/miniflare/src/workers/ratelimit/ratelimit.worker.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -92,33 +92,7 @@ class Ratelimit {
9292
}
9393
}
9494

95-
// Module-level set tracking all live instances, so a dedicated "control"
96-
// wrapped binding (see plugins/ratelimit/index.ts) can reset all of them.
97-
const instances = new Set<Ratelimit>();
98-
99-
interface RatelimitControlConfig {
100-
control: true;
101-
}
102-
103-
function isControlConfig(
104-
config: RatelimitConfig | RatelimitControlConfig
105-
): config is RatelimitControlConfig {
106-
return "control" in config;
107-
}
108-
109-
// create a new Ratelimit, or the control binding used internally to reset all
110-
// live instances (see vitest-pool-workers' reset())
111-
export default function (env: RatelimitConfig | RatelimitControlConfig) {
112-
if (isControlConfig(env)) {
113-
return {
114-
resetAll(): void {
115-
for (const instance of instances) {
116-
instance.reset();
117-
}
118-
},
119-
};
120-
}
121-
const instance = new Ratelimit(env);
122-
instances.add(instance);
123-
return instance;
95+
// create a new Ratelimit
96+
export default function (env: RatelimitConfig) {
97+
return new Ratelimit(env);
12498
}

packages/vitest-pool-workers/src/pool/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ async function buildProjectWorkerOptions(
411411
runnerWorker.durableObjects ??= {};
412412
const durableObjectClassNames = getDurableObjectClasses(runnerWorker);
413413

414+
// Ratelimit binding names, so reset() can call `.reset()` on each of them
415+
// directly without needing to know their names ahead of time itself.
416+
const ratelimitBindingNames = Object.keys(runnerWorker.ratelimits ?? {});
417+
414418
const workflowClassNames = getWorkflowClasses(
415419
runnerWorker,
416420
relativeWranglerConfigPath
@@ -537,6 +541,14 @@ async function buildProjectWorkerOptions(
537541
path: path.join(modulesRoot, "__VITEST_POOL_WORKERS_DEFINES"),
538542
contents: defines,
539543
},
544+
{
545+
type: "ESModule",
546+
path: path.join(
547+
modulesRoot,
548+
"__VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES"
549+
),
550+
contents: `export default ${JSON.stringify(ratelimitBindingNames)};`,
551+
},
540552
// The native workerd provided nodejs modules don't always support everything Vitest needs.
541553
// As a short-term fix, inject polyfills into the worker bundle that override the native modules.
542554
{

packages/vitest-pool-workers/src/pool/module-fallback.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import * as cjsModuleLexer from "cjs-module-lexer";
99
import { ModuleRuleTypeSchema, Response } from "miniflare";
1010
import { workerdBuiltinModules } from "../shared/builtin-modules";
1111
import { isFileNotFoundError } from "./helpers";
12+
13+
// Root-registered virtual modules injected via `runnerWorker.modules` that
14+
// may be imported from files other than the root worker entrypoint (e.g.
15+
// `reset.ts`, compiled into `test-internal.mjs`, not the root `index.mjs`).
16+
// Like `node:*`/`cloudflare:*` builtins, these only exist at the root, so a
17+
// non-root referrer's bare import needs redirecting there too.
18+
const poolInternalModules = new Set([
19+
"__VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES",
20+
]);
1221
import type { ModuleRuleType, Request, Worker_Module } from "miniflare";
1322
import type { Vite } from "vitest/node";
1423

@@ -332,7 +341,10 @@ async function resolve(
332341
// *import*ing `node:*`/`cloudflare:*` modules, but not when *require()*ing
333342
// them. For the sake of consistency (and a nice return type on this function)
334343
// we return a redirect for `import`s too.
335-
if (referrerDir !== "/" && workerdBuiltinModules.has(specifier)) {
344+
if (
345+
referrerDir !== "/" &&
346+
(workerdBuiltinModules.has(specifier) || poolInternalModules.has(specifier))
347+
) {
336348
return `/${specifier}`;
337349
}
338350

packages/vitest-pool-workers/src/worker/reset.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ratelimitBindingNames from "__VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES";
12
import { env } from "cloudflare:workers";
23
import workerdUnsafe from "workerd:unsafe";
34
import type { DurableObjectEvictionOptions } from "workerd:unsafe";
@@ -6,18 +7,17 @@ const DEFAULT_EVICTION_OPTIONS: DurableObjectEvictionOptions = {
67
webSockets: "hibernate",
78
};
89

9-
// Matches RATELIMIT_CONTROL_BINDING_NAME in miniflare's ratelimit plugin.
10-
const RATELIMIT_CONTROL_BINDING_NAME = "__MINIFLARE_RATELIMIT_CONTROL__";
11-
1210
export async function reset(): Promise<void> {
1311
await workerdUnsafe.deleteAllDurableObjects();
1412

15-
// Only present when the worker has at least one ratelimit binding
16-
// configured (see miniflare's ratelimit plugin `getBindings()`).
17-
const ratelimitControl = env[RATELIMIT_CONTROL_BINDING_NAME] as unknown as
18-
| { resetAll?: () => void }
19-
| undefined;
20-
ratelimitControl?.resetAll?.();
13+
// Ratelimit bindings expose a `reset()` method directly (see
14+
// `ratelimit.worker.ts`). Binding names come from the parsed wrangler
15+
// config (see `pool/index.ts`), so we never need to guess or enumerate
16+
// unrelated bindings in `env`.
17+
for (const name of ratelimitBindingNames) {
18+
const binding = env[name] as unknown as { reset?: () => void };
19+
binding.reset?.();
20+
}
2121
}
2222

2323
export async function abortAllDurableObjects(): Promise<void> {

packages/vitest-pool-workers/src/worker/types-ambient.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ declare module "__VITEST_POOL_WORKERS_DEFINES" {
4444
const defines: Record<string, unknown>;
4545
export default defines;
4646
}
47+
declare module "__VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES" {
48+
const names: string[];
49+
export default names;
50+
}
4751

4852
declare module "node:vm" {
4953
export function _setUnsafeEval(newUnsafeEval: UnsafeEval): void;

packages/vitest-pool-workers/tsdown.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const commonOptions: UserConfig = {
5858
// Virtual/runtime modules
5959
"__VITEST_POOL_WORKERS_DEFINES",
6060
"__VITEST_POOL_WORKERS_USER_OBJECT",
61+
"__VITEST_POOL_WORKERS_RATELIMIT_BINDING_NAMES",
6162
// Runtime dependencies and peer dependencies (derived from package.json)
6263
...runtimeDepPatterns,
6364
],

0 commit comments

Comments
 (0)