Skip to content

Copy of #7575 - #7748

Merged
1 commit merged into
mainfrom
felix/agracia/SQC-843-hyperdrive-js-binding
Oct 14, 2026
Merged

1 commit merged into
mainfrom
felix/agracia/SQC-843-hyperdrive-js-binding

Conversation

@fhanau

@fhanau fhanau commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@fhanau
fhanau requested review from a team as code owners October 14, 2026 18:50
@ask-bonk

ask-bonk Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Since last review: 0 resolved, 1 still open, 2 new.

Introduces a wrapped TypeScript Hyperdrive binding and virtual-host routing for PostgreSQL and MySQL.

Findings outside the diff

  • [BLOCKING] src/workerd/api/http.c++:2345-2352: This stored callback captures JSG_THIS in a kj::Function, and the global scope never visits connectOverrides. The jsg::Ref<Fetcher> is consequently untraced while held by the isolate-global map, which can leave a stale GC-managed reference. Make ConnectFn a jsg::Function, make this lambda visitable, and visit each map value from ServiceWorkerGlobalScope::visitForGc().

    using ConnectFn = jsg::Function<jsg::Ref<api::Socket>()>;
    
    void visitForGc(jsg::GcVisitor& visitor) {
      visitor.visit(processValue, bufferValue, defaultFetcher);
      for (auto& entry: connectOverrides) {
        visitor.visit(entry.value);
      }
    }
    
    JSG_VISITABLE_LAMBDA((self = JSG_THIS), (self), (jsg::Lock& js) mutable {
      return self->connect(
          js,
          kj::str(KJ_ASSERT_NONNULL(self->host), ":", KJ_ASSERT_NONNULL(self->port)),
          kj::none);
    })
    

Carried forward from the last review: api-compat, jsg-gc (no author changes in their files since then; earlier findings stand)

Not re-run: tests, docs, compat-flags, design-simplicity, kj-style, memory-safety (no author changes in their files since the last review)


Reviewed commit: 65187428 · github run

this.#fetcher = env.fetcher;
this.#scheme = scheme;
this.#port = PORTS[scheme];
const authority = entrypoints.mapVirtualHost(this.#fetcher, this.#port);

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.

[BLOCKING] mapVirtualHost() registers only a connectOverrides entry. Although the generated .workers.alt hostname is recognized by node:dns, getCallerDnsOverride() has no corresponding DNS entry and lookup falls through to normal DNS. The binding also does not define ip, despite the public Hyperdrive interface requiring a synthetic IPv4 for drivers that need an IP literal. Consequently, drivers that resolve connectionString's host, or read hd.ip, cannot connect through this binding. Register a synthetic IPv4 as both a DNS and connect override and expose it as the binding's ip property; cover dns.lookup(hd.host) and connect(${hd.ip}:${hd.port}) in this test suite.

a migration toward rpc
backed binding
- uses mapVirtualHost approach instead of extendedFetcher
@fhanau
fhanau force-pushed the felix/agracia/SQC-843-hyperdrive-js-binding branch 2 times, most recently from 5fc8f95 to 6518742 Compare October 14, 2026 21:38
this.#fetcher = env.fetcher;
this.#scheme = scheme;
this.#port = PORTS[scheme];
const authority = entrypoints.mapVirtualHost(this.#fetcher, this.#port);

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.

[WARNING] The binding's constructor is reachable as Object.getPrototypeOf(env.HYPERDRIVE).constructor. A Worker with a Durable Object namespace can repeatedly construct wrappers around fresh getByName() stubs. Each constructor call creates a random host and permanently inserts a new entry into the isolate-global connectOverrides map; the map has no removal path. This permits untrusted code to accumulate native entries across requests until the isolate runs out of memory. Require a module-private construction token and create instances only through the two binding factories.

Suggested change
const authority = entrypoints.mapVirtualHost(this.#fetcher, this.#port);
const hyperdriveBindingToken = Symbol();
constructor(token: symbol, env: HyperdriveEnv, scheme: HyperdriveScheme) {
if (token !== hyperdriveBindingToken) {
throw new TypeError('Hyperdrive bindings cannot be constructed directly');
}
super(env.fetcher);
// ...
}
export function makeHyperdrive(env: HyperdriveEnv, scheme: HyperdriveScheme): Hyperdrive {
return new Hyperdrive(hyperdriveBindingToken, env, scheme);
}

@fhanau fhanau closed this pull request by merging all changes into main in e7440fd Oct 14, 2026
@fhanau
fhanau deleted the felix/agracia/SQC-843-hyperdrive-js-binding branch October 14, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants