Skip to content

Commit 70a0efb

Browse files
mack-erelclaude
andcommitted
fix(hyperdrive): namespace services by worker and seed every dev entry point
Addresses two review comments. Services, proxies and TCP bridges were keyed on the binding name alone, so in a multi-worker setup two workers binding Hyperdrive under the same name (`DB` is the obvious case) collided on one service and one bridge. They are now keyed by worker index as well, matching how the cache plugin and the core plugin's custom services namespace theirs. Edge credentials also now reach the entry points that could not fetch them themselves: `unstable_getMiniflareWorkerOptions()` accepts them as an option, and the Vite plugin and vitest-pool-workers — which already await `maybeStartOrUpdateRemoteProxySession()` — pass through what the session prepared. The warning for a binding left without credentials no longer claims those environments are unsupported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GDyTfEa62t3bSe5wWn8Mt
1 parent f3ac6bd commit 70a0efb

7 files changed

Lines changed: 72 additions & 33 deletions

File tree

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

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ import type { Plugin, RemoteProxyConnectionString } from "../shared";
1212

1313
export const HYPERDRIVE_PLUGIN_NAME = "hyperdrive";
1414

15+
/**
16+
* Service (and proxy/bridge) name for a Hyperdrive binding.
17+
*
18+
* Namespaced by worker as well as binding name: in a multi-worker setup it is
19+
* common for several workers to bind Hyperdrive under the same name (`DB`), and
20+
* an unqualified name would make them collide on one service and one bridge.
21+
*/
22+
export function getHyperdriveServiceName(
23+
workerIndex: number,
24+
bindingName: string
25+
) {
26+
return `${HYPERDRIVE_PLUGIN_NAME}:${workerIndex}:${bindingName}`;
27+
}
28+
1529
// Placeholder connection string used to synthesise the local Hyperdrive binding
1630
// when a remote binding has no local connection string. workerd still needs a
1731
// scheme/database/user/password to build the magic `connectionString` it exposes
@@ -119,7 +133,7 @@ export const HyperdriveSchema = z
119133

120134
export const HYPERDRIVE_PLUGIN: Plugin = {
121135
bindingTypeDescription: "Hyperdrive",
122-
getBindings(options) {
136+
getBindings(options, workerIndex) {
123137
return getHyperdrives(options.config, options.dev).map<Worker_Binding>(
124138
([name, url]) => {
125139
const database = url.pathname.replace("/", "");
@@ -133,7 +147,7 @@ export const HYPERDRIVE_PLUGIN: Plugin = {
133147
// which relays to the edge. workerd is unmodified either way —
134148
// pointing a Hyperdrive designator at a Worker service SIGSEGVs.
135149
designator: {
136-
name: `${HYPERDRIVE_PLUGIN_NAME}:${name}`,
150+
name: getHyperdriveServiceName(workerIndex, name),
137151
},
138152
database: decodeURIComponent(database),
139153
user: decodeURIComponent(url.username),
@@ -163,7 +177,7 @@ export const HYPERDRIVE_PLUGIN: Plugin = {
163177
})
164178
);
165179
},
166-
async getServices({ options, hyperdriveProxyController }) {
180+
async getServices({ options, workerIndex, hyperdriveProxyController }) {
167181
const services = [];
168182
for (const [name, url, remoteProxyConnectionString] of getHyperdrives(
169183
options.config,
@@ -176,11 +190,11 @@ export const HYPERDRIVE_PLUGIN: Plugin = {
176190
if (remoteProxyConnectionString) {
177191
const bridgePort =
178192
await hyperdriveProxyController.createRemoteTcpBridge({
179-
name,
193+
name: getHyperdriveServiceName(workerIndex, name),
180194
remoteProxyConnectionString,
181195
});
182196
services.push({
183-
name: `${HYPERDRIVE_PLUGIN_NAME}:${name}`,
197+
name: getHyperdriveServiceName(workerIndex, name),
184198
external: {
185199
address: `127.0.0.1:${bridgePort}`,
186200
tcp: {},
@@ -203,7 +217,7 @@ export const HYPERDRIVE_PLUGIN: Plugin = {
203217
// SSL modes (require, prefer) need the proxy to handle
204218
// TLS negotiation with the target database
205219
const proxyPort = await hyperdriveProxyController.createProxyServer({
206-
name,
220+
name: getHyperdriveServiceName(workerIndex, name),
207221
targetHost: url.hostname,
208222
targetPort,
209223
scheme,
@@ -214,7 +228,7 @@ export const HYPERDRIVE_PLUGIN: Plugin = {
214228
}
215229

216230
services.push({
217-
name: `${HYPERDRIVE_PLUGIN_NAME}:${name}`,
231+
name: getHyperdriveServiceName(workerIndex, name),
218232
external: {
219233
address,
220234
tcp: {},

packages/miniflare/test/plugins/hyperdrive/index.spec.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ describe("proxy server creation", () => {
250250
),
251251
},
252252
},
253+
workerIndex: 0,
253254
hyperdriveProxyController: controller,
254255
} as unknown as Parameters<typeof HYPERDRIVE_PLUGIN.getServices>[0];
255256
}
@@ -267,7 +268,7 @@ describe("proxy server creation", () => {
267268
expect(controller.createProxyServer).not.toHaveBeenCalled();
268269
expect(services).toEqual([
269270
{
270-
name: "hyperdrive:DB",
271+
name: "hyperdrive:0:DB",
271272
external: { address: "db.example.com:5432", tcp: {} },
272273
},
273274
]);
@@ -290,7 +291,7 @@ describe("proxy server creation", () => {
290291
expect(controller.createProxyServer).not.toHaveBeenCalled();
291292
expect(services).toEqual([
292293
{
293-
name: "hyperdrive:DB",
294+
name: "hyperdrive:0:DB",
294295
external: { address: "db.example.com:5432", tcp: {} },
295296
},
296297
]);
@@ -311,7 +312,7 @@ describe("proxy server creation", () => {
311312
expect(controller.createProxyServer).not.toHaveBeenCalled();
312313
expect(services).toEqual([
313314
{
314-
name: "hyperdrive:DB",
315+
name: "hyperdrive:0:DB",
315316
external: { address: "[::1]:5432", tcp: {} },
316317
},
317318
]);
@@ -332,7 +333,7 @@ describe("proxy server creation", () => {
332333
expect(controller.createProxyServer).not.toHaveBeenCalled();
333334
expect(services).toEqual([
334335
{
335-
name: "hyperdrive:DB",
336+
name: "hyperdrive:0:DB",
336337
external: { address: "db.example.com:3306", tcp: {} },
337338
},
338339
]);
@@ -352,15 +353,15 @@ describe("proxy server creation", () => {
352353
);
353354
expect(controller.createProxyServer).toHaveBeenCalledOnce();
354355
expect(controller.createProxyServer).toHaveBeenCalledWith({
355-
name: "DB",
356+
name: "hyperdrive:0:DB",
356357
targetHost: "db.example.com",
357358
targetPort: "5432",
358359
scheme: "postgres",
359360
sslmode: "require",
360361
});
361362
expect(services).toEqual([
362363
{
363-
name: "hyperdrive:DB",
364+
name: "hyperdrive:0:DB",
364365
external: { address: "127.0.0.1:12345", tcp: {} },
365366
},
366367
]);
@@ -380,15 +381,15 @@ describe("proxy server creation", () => {
380381
);
381382
expect(controller.createProxyServer).toHaveBeenCalledOnce();
382383
expect(controller.createProxyServer).toHaveBeenCalledWith({
383-
name: "DB",
384+
name: "hyperdrive:0:DB",
384385
targetHost: "db.example.com",
385386
targetPort: "5432",
386387
scheme: "postgres",
387388
sslmode: "prefer",
388389
});
389390
expect(services).toEqual([
390391
{
391-
name: "hyperdrive:DB",
392+
name: "hyperdrive:0:DB",
392393
external: { address: "127.0.0.1:12345", tcp: {} },
393394
},
394395
]);
@@ -408,15 +409,15 @@ describe("proxy server creation", () => {
408409
);
409410
expect(controller.createProxyServer).toHaveBeenCalledOnce();
410411
expect(controller.createProxyServer).toHaveBeenCalledWith({
411-
name: "DB",
412+
name: "hyperdrive:0:DB",
412413
targetHost: "db.example.com",
413414
targetPort: "3306",
414415
scheme: "mysql",
415416
sslmode: "require",
416417
});
417418
expect(services).toEqual([
418419
{
419-
name: "hyperdrive:DB",
420+
name: "hyperdrive:0:DB",
420421
external: { address: "127.0.0.1:12345", tcp: {} },
421422
},
422423
]);

packages/vite-plugin-cloudflare/src/miniflare-options.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ export async function getDevMiniflareOptions(
414414
remoteProxyConnectionString:
415415
remoteProxySessionData?.session
416416
?.remoteProxyConnectionString,
417-
417+
hyperdriveConnectionStrings:
418+
remoteProxySessionData?.hyperdriveConnectionStrings,
418419
containerBuildId,
419420
}
420421
);
@@ -829,7 +830,8 @@ export async function getPreviewMiniflareOptions(
829830
wrangler.unstable_getMiniflareWorkerOptions(workerConfig, undefined, {
830831
remoteProxyConnectionString:
831832
remoteProxySessionData?.session?.remoteProxyConnectionString,
832-
833+
hyperdriveConnectionStrings:
834+
remoteProxySessionData?.hyperdriveConnectionStrings,
833835
containerBuildId,
834836
});
835837

packages/vitest-plugin/src/pool/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ async function parseCustomPoolOptions(
443443
},
444444
remoteProxyConnectionString:
445445
remoteProxySessionData?.session?.remoteProxyConnectionString,
446+
hyperdriveConnectionStrings:
447+
remoteProxySessionData?.hyperdriveConnectionStrings,
446448
});
447449

448450
// If `main` wasn't explicitly configured, fall back to the config's entrypoint

packages/wrangler/src/__tests__/dev/miniflare-hyperdrive.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ describe("hyperdrive bindings in local dev", () => {
7373
expect(std.warn).toBe("");
7474
});
7575

76-
it("warns when a remote binding's edge credentials could not be seeded", ({
76+
it("warns when a remote binding is given no edge credentials", ({
7777
expect,
7878
}) => {
79-
// Reached from dev entry points that cannot seed (the binding builder is
80-
// synchronous), e.g. `unstable_getMiniflareWorkerOptions`. Without the
81-
// warning the binding would silently fall back to placeholder credentials
82-
// and fail to authenticate at the edge.
79+
// Credentials come from the remote proxy session; reaching this means the
80+
// caller did not pass them on. Without the warning the binding would
81+
// silently fall back to placeholder credentials and fail to authenticate
82+
// at the edge.
8383
expect(
8484
buildHyperdriveOptions(
8585
{
@@ -96,7 +96,7 @@ describe("hyperdrive bindings in local dev", () => {
9696
},
9797
});
9898
expect(std.warn).toContain(
99-
`The Hyperdrive binding "HYPERDRIVE" is configured with "remote": true, but its edge credentials could not be seeded in this context`
99+
`The Hyperdrive binding "HYPERDRIVE" is configured with "remote": true, but no edge credentials were provided for it`
100100
);
101101
});
102102

packages/wrangler/src/api/integrations/platform/index.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,12 @@ export function unstable_getMiniflareWorkerOptions(
415415
env?: string,
416416
options?: {
417417
remoteProxyConnectionString?: RemoteProxyConnectionString;
418+
/**
419+
* Edge credentials for remote Hyperdrive bindings, prepared once when the
420+
* remote proxy session started (see `maybeStartOrUpdateRemoteProxySession`).
421+
* Without them a remote Hyperdrive binding cannot authenticate at the edge.
422+
*/
423+
hyperdriveConnectionStrings?: ReadonlyMap<string, string>;
418424
overrides?: {
419425
assets?: Partial<AssetsOptions>;
420426
enableContainers?: boolean;
@@ -427,6 +433,12 @@ export function unstable_getMiniflareWorkerOptions(
427433
env?: string,
428434
options?: {
429435
remoteProxyConnectionString?: RemoteProxyConnectionString;
436+
/**
437+
* Edge credentials for remote Hyperdrive bindings, prepared once when the
438+
* remote proxy session started (see `maybeStartOrUpdateRemoteProxySession`).
439+
* Without them a remote Hyperdrive binding cannot authenticate at the edge.
440+
*/
441+
hyperdriveConnectionStrings?: ReadonlyMap<string, string>;
430442
overrides?: {
431443
assets?: Partial<AssetsOptions>;
432444
enableContainers?: boolean;
@@ -440,6 +452,12 @@ export function unstable_getMiniflareWorkerOptions(
440452
options?: {
441453
envFiles?: string[];
442454
remoteProxyConnectionString?: RemoteProxyConnectionString;
455+
/**
456+
* Edge credentials for remote Hyperdrive bindings, prepared once when the
457+
* remote proxy session started (see `maybeStartOrUpdateRemoteProxySession`).
458+
* Without them a remote Hyperdrive binding cannot authenticate at the edge.
459+
*/
460+
hyperdriveConnectionStrings?: ReadonlyMap<string, string>;
443461
overrides?: {
444462
assets?: Partial<AssetsOptions>;
445463
enableContainers?: boolean;
@@ -492,7 +510,8 @@ export function unstable_getMiniflareWorkerOptions(
492510
containerBuildId: options?.containerBuildId,
493511
enableContainers,
494512
},
495-
options?.remoteProxyConnectionString
513+
options?.remoteProxyConnectionString,
514+
options?.hyperdriveConnectionStrings
496515
);
497516

498517
const sitesAssetPaths = getSiteAssetPaths(config);

packages/wrangler/src/dev/miniflare/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -367,14 +367,15 @@ function hyperdriveEntry(
367367
hyperdrive.binding
368368
);
369369
if (seededConnectionString === undefined) {
370-
// Seeding is what makes a database client authenticate through the edge
371-
// proxy; without it miniflare falls back to placeholder credentials (and
372-
// a `mysql` scheme even for Postgres) and the login is rejected. Warn
373-
// rather than throw, so dev paths that cannot seed — the entry points
374-
// below are synchronous — stay usable and fail visibly instead of
375-
// silently.
370+
// The edge session's credentials are what let a database client
371+
// authenticate through the proxy; without them miniflare falls back to
372+
// placeholder credentials (and a `mysql` scheme even for Postgres) and
373+
// the login is rejected. They are prepared by
374+
// `maybeStartOrUpdateRemoteProxySession`, so reaching this means the
375+
// caller did not pass them on. Warn rather than throw so the session
376+
// stays usable and fails visibly instead of silently.
376377
logger.once.warn(
377-
`The Hyperdrive binding "${hyperdrive.binding}" is configured with "remote": true, but its edge credentials could not be seeded in this context, so connections through it will likely fail to authenticate. Remote Hyperdrive bindings are currently supported in \`wrangler dev\` and \`getPlatformProxy()\`.`
378+
`The Hyperdrive binding "${hyperdrive.binding}" is configured with "remote": true, but no edge credentials were provided for it, so connections through it will likely fail to authenticate.`
378379
);
379380
}
380381
return [

0 commit comments

Comments
 (0)