Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/quiet-workers-register.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": major
---

Add per-worker control over dev registry registration

Miniflare workers must now opt in to the dev registry with `unsafeRegisterWorker`. Wrangler and the Cloudflare Vite plugin use this option to advertise user workers without exposing internal or external workers.
1 change: 1 addition & 0 deletions fixtures/entrypoints-rpc-tests/tests/entrypoints.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,7 @@ describe("entrypoints", () => {
const boundWorker = new Miniflare({
name: "bound",
unsafeDevRegistryPath: isolatedDevRegistryPath,
unsafeRegisterWorker: true,
compatibilityFlags: ["experimental"],
modules: true,
https: true,
Expand Down
9 changes: 8 additions & 1 deletion packages/miniflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ parameter in module format Workers.
Unique name for this worker. Only required if multiple `workers` are
specified.

- `unsafeRegisterWorker?: boolean`

If `true`, advertises this Worker in the dev registry configured by
`unsafeDevRegistryPath`. Defaults to `false`.

- `rootPath?: string`

Path against which all other path options for this Worker are resolved
Expand Down Expand Up @@ -597,7 +602,9 @@ Options shared between all Workers/"nanoservices".
- `unsafeDevRegistryPath?: string`

Path to the dev registry directory. This allows Miniflare to automatically
discover external services and Durable Objects running on another miniflare instance and connect them.
discover external services and Durable Objects running on another Miniflare
instance and connect them. Workers must opt in to being advertised by setting
`unsafeRegisterWorker` to `true`.

- `unsafeDevRegistryDurableObjectProxy?: boolean`

Expand Down
2 changes: 1 addition & 1 deletion packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2695,7 +2695,7 @@ export class Miniflare {

const entries: [string, WorkerDefinition][] = [];
for (const workerOpts of this.#workerOpts) {
if (!workerOpts.core.name) {
if (!workerOpts.core.name || !workerOpts.core.unsafeRegisterWorker) {
continue;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/miniflare/src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ const CoreOptionsSchemaInput = z.intersection(

unsafeEvalBinding: z.string().optional(),
unsafeUseModuleFallbackService: z.boolean().optional(),
/** Whether this Worker should be advertised in the dev registry. Defaults to `false`. */
unsafeRegisterWorker: z.boolean().optional(),
Comment thread
edmundhung marked this conversation as resolved.

/** Used to set the vitest pool worker SELF binding to point to the Router Worker if there are assets.
(If there are assets but we're not using vitest, the miniflare entry worker can point directly to
Expand Down
3 changes: 2 additions & 1 deletion packages/miniflare/src/shared/DEV_REGISTRY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The dev registry enables cross-process communication between multiple `wrangler

## Overview

Each `wrangler dev` process writes its worker's connection info to a shared filesystem directory. When Worker A needs to talk to Worker B, a proxy worker inside A's workerd process reads B's debug port address from the registry and connects via Cap'n Proto RPC.
Each `wrangler dev` process writes its user worker's connection info to a shared filesystem directory. Internal and external workers are not advertised. When Worker A needs to talk to Worker B, a proxy worker inside A's workerd process reads B's debug port address from the registry and connects via Cap'n Proto RPC.

```mermaid
graph TB
Expand Down Expand Up @@ -55,6 +55,7 @@ type WorkerDefinition = {
```

- **Heartbeat**: Every 30s, the file's mtime is touched to signal that the Worker is still running.
- **Registration**: Only workers with `unsafeRegisterWorker: true` are advertised.
- **Stale cleanup**: On every read, files older than 5 minutes are deleted (5 minutes is much longer than 30s just to provide a safe buffer)
- **Change detection**: Chokidar watches the registry directory. When a file changes, `refresh()` compares the new state against the previous JSON snapshot and fires `onUpdate` only if a watched external service actually changed.

Expand Down
45 changes: 45 additions & 0 deletions packages/miniflare/test/dev-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@ import { useDispose, useTmp } from "./test-shared";
import type { MiniflareOptions, WorkerRegistry } from "miniflare";

describe.sequential("DevRegistry", () => {
test("only registers workers that opt in", async ({ expect }) => {
const unsafeDevRegistryPath = await useTmp();
const workerOptions = {
name: "worker",
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
script: `export default { fetch() { return new Response("ok"); } };`,
} satisfies MiniflareOptions;
const mf = new Miniflare(workerOptions);
useDispose(mf);
await mf.ready;

expect(getWorkerRegistry(unsafeDevRegistryPath)).toEqual({});

await mf.setOptions({ ...workerOptions, unsafeRegisterWorker: true });
expect(getWorkerRegistry(unsafeDevRegistryPath)["worker"]).toBeDefined();
});

test("fetch to service worker", async ({ expect }) => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
script: `addEventListener("fetch", (event) => {
Expand Down Expand Up @@ -92,6 +112,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -169,6 +190,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -245,6 +267,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -316,6 +339,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -356,6 +380,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -444,6 +469,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -527,6 +553,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -568,6 +595,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

compatibilityFlags: ["experimental"],
Expand Down Expand Up @@ -635,6 +663,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

compatibilityFlags: ["experimental"],
Expand Down Expand Up @@ -735,6 +764,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

compatibilityFlags: ["experimental"],
Expand Down Expand Up @@ -813,6 +843,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

compatibilityFlags: ["experimental"],
Expand Down Expand Up @@ -873,6 +904,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

workflows: {
Expand Down Expand Up @@ -916,6 +948,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
durableObjects: {
Expand Down Expand Up @@ -979,6 +1012,7 @@ describe.sequential("DevRegistry", () => {
// Restart remote — gets a new debug port, registry file updates
await remote.setOptions({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
durableObjects: {
Expand Down Expand Up @@ -1017,6 +1051,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1066,6 +1101,7 @@ describe.sequential("DevRegistry", () => {
// Restart remote — gets a new debug port, registry file updates
await remote.setOptions({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1093,6 +1129,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
unsafeTriggerHandlers: true,
compatibilityFlags: ["experimental"],
Expand Down Expand Up @@ -1155,6 +1192,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1264,6 +1302,7 @@ describe.sequential("DevRegistry", () => {
const unsafeDevRegistryPath = await useTmp();
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1366,6 +1405,7 @@ describe.sequential("DevRegistry", () => {
};
const remoteOptions: MiniflareOptions = {
name: "remote-worker",
unsafeRegisterWorker: true,
compatibilityFlags: ["experimental"],
modules: true,
script: `
Expand Down Expand Up @@ -1471,6 +1511,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1538,6 +1579,7 @@ describe.sequential("DevRegistry", () => {

const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,

https: true,
Expand Down Expand Up @@ -1622,6 +1664,7 @@ describe.sequential("DevRegistry", () => {
const unrelated = new Miniflare({
name: "unrelated-worker",
unsafeDevRegistryPath,
unsafeRegisterWorker: true,
compatibilityFlags: ["experimental"],
modules: true,
script: `
Expand All @@ -1643,6 +1686,7 @@ describe.sequential("DevRegistry", () => {
// Create remote worker (one we're actually bound to) - this should trigger the callback
const remote = new Miniflare({
name: "remote-worker",
unsafeRegisterWorker: true,
unsafeDevRegistryPath,
compatibilityFlags: ["experimental"],
modules: true,
Expand Down Expand Up @@ -1746,6 +1790,7 @@ describe.sequential("DevRegistry", () => {
const sharedOptions = {
name: "consumer-worker",
unsafeDevRegistryPath,
unsafeRegisterWorker: true,
compatibilityFlags: ["experimental"],
modules: true,
} satisfies Partial<MiniflareOptions>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe("Cross-process aggregation", () => {

instanceA = new Miniflare({
name: "worker-a",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand Down Expand Up @@ -68,6 +69,7 @@ describe("Cross-process aggregation", () => {

instanceB = new Miniflare({
name: "worker-b",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand Down Expand Up @@ -406,6 +408,7 @@ describe("Multi-worker peer deduplication", () => {

instanceA = new Miniflare({
name: "worker-a",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand All @@ -428,6 +431,7 @@ describe("Multi-worker peer deduplication", () => {
workers: [
{
name: "worker-b1",
unsafeRegisterWorker: true,
modules: true,
script: `export default { fetch() { return new Response("Worker B1"); } }`,
kvNamespaces: {
Expand All @@ -436,6 +440,7 @@ describe("Multi-worker peer deduplication", () => {
},
{
name: "worker-b2",
unsafeRegisterWorker: true,
modules: true,
script: `export default { fetch() { return new Response("Worker B2"); } }`,
kvNamespaces: {
Expand Down Expand Up @@ -510,6 +515,7 @@ describe("Same ID across multiple instances with different persistence directori
// ties it to a specific instance.
instanceA = new Miniflare({
name: "worker-a",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand All @@ -531,6 +537,7 @@ describe("Same ID across multiple instances with different persistence directori

instanceB = new Miniflare({
name: "worker-b",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand Down Expand Up @@ -627,6 +634,7 @@ describe("Same ID across multiple instances with same persistence directories",
// ties it to a specific instance.
instanceA = new Miniflare({
name: "worker-a",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand All @@ -646,6 +654,7 @@ describe("Same ID across multiple instances with same persistence directories",

instanceB = new Miniflare({
name: "worker-b",
unsafeRegisterWorker: true,
inspectorPort: 0,
compatibilityDate: "2025-01-01",
modules: true,
Expand Down
Loading
Loading