Skip to content

[miniflare] Route storage/remote bindings through shared services via props#14743

Open
penalosa wants to merge 1 commit into
mainfrom
penalosa/shared-services-props
Open

[miniflare] Route storage/remote bindings through shared services via props#14743
penalosa wants to merge 1 commit into
mainfrom
penalosa/shared-services-props

Conversation

@penalosa

@penalosa penalosa commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Move the per-binding configuration for storage and remote (mixed-mode) bindings out of per-resource workerd services and into runtime ctx.props, so a single shared service can serve any number of bindings.

  • Local KV namespaces now share one entry service — the namespace id is passed via props and resolved in object-entry.worker.ts (idFromName)
  • remoteProxyClientWorker() is now script-only — the connection string, binding name and trace id travel via props (buildRemoteProxyProps), read in remote-proxy-client.worker.ts and the dispatch-namespace proxy
  • All remote-binding plugins emit one shared remote-proxy service instead of one per resource
  • explorer.ts reads the KV namespace id from binding props rather than parsing it out of the service name

No user-facing behaviour change — this is internal plumbing that lets a single service back any number of bindings, replacing the previous one-service-per-resource model.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because: this is a behaviour-preserving refactor — the existing miniflare plugin suites (KV / R2 / D1 / email / remote bindings) exercise the affected paths and pass unchanged.
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: internal miniflare refactor with no user-facing surface.

Open in Devin Review

… props

Move the per-binding configuration for storage and remote (mixed-mode)
bindings out of per-resource workerd services and into runtime `ctx.props`,
so a single shared service can serve any number of bindings.

- Local KV namespaces now share one entry service; the namespace id is
  passed via props and resolved in object-entry.worker.ts (idFromName).
- remoteProxyClientWorker() is now script-only; the connection string,
  binding name and trace id travel via props (buildRemoteProxyProps),
  read in remote-proxy-client.worker.ts and the dispatch-namespace proxy.
- All remote-binding plugins emit one shared remote-proxy service instead
  of one per resource.
- explorer.ts reads the KV namespace id from binding props rather than
  parsing it out of the service name.
@penalosa penalosa added the ci:no-changeset-required Skip pull request checks for a valid changeset label Jul 17, 2026
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 400ba82

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Jul 17, 2026
@workers-devprod
workers-devprod requested review from a team and dario-piotrowicz and removed request for a team July 17, 2026 12:50
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • packages/miniflare/src/plugins/agent-memory/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/ai-search/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/ai/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/artifacts/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/browser-rendering/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/core/explorer.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/core/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/d1/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/dispatch-namespace/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/email/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/flagship/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/images/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/kv/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/media/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/mtls/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/pipelines/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/r2/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/shared/constants.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/stream/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/vectorize/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/vpc-networks/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/vpc-services/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/plugins/websearch/index.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/dispatch-namespace/dispatch-namespace-proxy.worker.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/shared/object-entry.worker.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/shared/remote-bindings-utils.ts: [@cloudflare/wrangler]
  • packages/miniflare/src/workers/shared/remote-proxy-client.worker.ts: [@cloudflare/wrangler]

@pkg-pr-new

pkg-pr-new Bot commented Jul 17, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14743

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@14743

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14743

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14743

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14743

miniflare

npm i https://pkg.pr.new/miniflare@14743

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14743

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14743

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14743

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14743

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14743

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14743

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14743

wrangler

npm i https://pkg.pr.new/wrangler@14743

commit: 400ba82

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm Bonk, and I've done a quick review of your PR.

Summary: Moves per-binding storage/remote config out of per-resource workerd services and into ctx.props, letting one shared service back many bindings. The local storage path is exercised by the existing KV/R2/D1 suites and the remote service-binding path by remote-bindings-access-warning.spec.ts, so the behaviour-preserving claim is reasonably supported.

Explorer registers a colliding "remote" id for remote bindings (low severity). Remote KV/R2/D1 bindings now all point at a shared service name (kv:ns:remote, r2:bucket:remote, d1:db:remote), but constructExplorerBindingMap still derives the resource id by string-stripping that name. Every remote binding of a given type therefore registers the literal id "remote", so multiple remote bindings overwrite each other in IDToBindingName, and a bogus "remote" id leaks into the explorer map (selecting it would try to open storage for a namespace literally named remote). KV reads the id from props but still falls through to the name parse for remote (yielding "remote"); the R2 block (explorer.ts:218, const bucketName = binding.r2Bucket.name.replace(/^r2:bucket:/, "")) and the D1 block (explorer.ts:177, const databaseId = innerBinding.service?.name?.replace(/^d1:db:/, "")) weren't updated at all and have the same problem. Since remote resources aren't locally browsable, the cleanest fix is to skip the shared remote service name in all three blocks. Inline suggestion below covers the KV block; please apply the equivalent !== "remote" guard to the R2 and D1 blocks.

}
}
if (namespaceId === undefined) {
namespaceId = binding.kvNamespace.name.replace(/^kv:ns:/, "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remote KV namespaces now point at the shared kv:ns:remote service and carry buildRemoteProxyProps (no MINIFLARE_NAMESPACE), so the props lookup misses and this fallback strips the name to the literal "remote". Multiple remote namespaces all collapse to id "remote" and overwrite each other in IDToBindingName.kv. Consider skipping the shared remote service here:

Suggested change
namespaceId = binding.kvNamespace.name.replace(/^kv:ns:/, "");
if (namespaceId === undefined) {
const rawId = binding.kvNamespace.name.replace(/^kv:ns:/, "");
// Skip the shared remote-proxy service — remote namespaces aren't
// locally explorable and would otherwise all register id "remote".
if (rawId !== "remote") {
namespaceId = rawId;
}
}

@ask-bonk

ask-bonk Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review posted successfully to PR #14743.

I reviewed the PR and posted a single COMMENT review with one inline suggestion. The one actionable finding:

Explorer registers a colliding "remote" id for remote bindings (low severity). After collapsing remote KV/R2/D1 bindings onto shared service names (kv:ns:remote, r2:bucket:remote, d1:db:remote), constructExplorerBindingMap still derives the resource id by stripping the service-name prefix, so every remote binding registers the literal id "remote" — they overwrite each other and a bogus entry leaks into the explorer map. KV attempts a props lookup but falls through to the same buggy parse; R2/D1 weren't updated. Suggested a !== "remote" guard (inline suggestion for KV, prose note for R2/D1).

Everything else checks out: the local storage path is covered by the existing KV/R2/D1 plugin suites and the remote service-binding path by remote-bindings-access-warning.spec.ts (which exercises the new ctx.props mechanism), so the behaviour-preserving claim is reasonably supported.

github run

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +203 to +205
if (namespaceId === undefined) {
namespaceId = binding.kvNamespace.name.replace(/^kv:ns:/, "");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Explorer collapses all remote KV namespaces to a single wrong entry

Remote KV namespaces are recorded in the explorer under the literal id "remote" (binding.kvNamespace.name.replace(/^kv:ns:/, "") at packages/miniflare/src/plugins/core/explorer.ts:204) instead of their real namespace id, so multiple remote namespaces overwrite each other and none can be matched correctly.
Impact: When more than one remote (mixed-mode) KV namespace is configured, the local explorer lists/keys them incorrectly, dropping all but one.

Why the fallback breaks for remote namespaces

Before this PR, remote KV bindings used a service name of the form kv:ns:<id>[:remote-<sha>], so stripping the kv:ns: prefix yielded a per-namespace id. This PR changes remote namespaces in packages/miniflare/src/plugins/kv/index.ts:95 to all share the single service name KV_REMOTE_SERVICE_NAME = kv:ns:remote, and their props (packages/miniflare/src/plugins/kv/index.ts:96-99) carry remoteProxyConnectionString/binding — not SharedBindings.TEXT_NAMESPACE.

In constructExplorerBindingMap, JSON.parse(propsJson)[SharedBindings.TEXT_NAMESPACE] is therefore undefined for remote namespaces, so the code falls through to binding.kvNamespace.name.replace(/^kv:ns:/, ""), which is always "remote". Every remote namespace then collides on IDToBindingName.kv["remote"]. The inline comment at packages/miniflare/src/plugins/core/explorer.ts:185-186 claiming remote namespaces "still encode the id in the service name (kv:ns:ID[:remoteSuffix])" is no longer accurate.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:no-changeset-required Skip pull request checks for a valid changeset

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants