@@ -162,64 +162,81 @@ export abstract class RavenCommand<TResult> {
162162
163163 let optionsToUse : RequestInit | BunFetchRequestInit ;
164164
165- if ( RuntimeUtil . isBun ( ) ) {
166- optionsToUse = { body : bodyToUse , ...restOptions } as BunFetchRequestInit ;
167- } else if ( RuntimeUtil . isWorkerd ( ) ) {
168- // Cloudflare Workers' fetch has no undici `dispatcher` concept; mTLS is
169- // handled by the custom fetch (an mtls_certificate binding). Passing a
170- // dispatcher here is meaningless and can confuse the runtime.
171- optionsToUse = { body : bodyToUse , ...restOptions } as RequestInit ;
172- } else {
165+ if ( RuntimeUtil . supportsUndiciDispatcher ( ) ) {
173166 if ( dispatcher ) { // support for fiddler
174167 agent = dispatcher ;
175168 }
176169 optionsToUse = { body : bodyToUse , ...restOptions , dispatcher : agent } as RequestInit ;
170+ } else {
171+ // Bun and Cloudflare Workers (workerd) have no undici `dispatcher` concept, so
172+ // one must never leak into their fetch init. On workerd mTLS is handled by the
173+ // custom fetch (an mtls_certificate binding); Bun manages TLS itself.
174+ optionsToUse = { body : bodyToUse , ...restOptions } as RequestInit ;
177175 }
178176
179- let passthrough : PassThrough ;
180- try {
181- passthrough = new PassThrough ( ) ;
182- } catch ( err ) {
183- throw RavenCommand . _maybeNodeStreamUnavailableError ( err as Error ) ;
184- }
185- passthrough . pause ( ) ;
177+ // `new PassThrough()`, `Readable.fromWeb`, `new Stream()` and `.pipe` are all
178+ // `node:stream` APIs; an incomplete edge polyfill (Nitro/unenv) can throw from any
179+ // of them. `_guardNodeStream` turns those into an actionable error. PassThrough is
180+ // built BEFORE the fetch on purpose: on a broken polyfill we fail fast without
181+ // issuing the (possibly state-mutating) request.
182+ const passthrough = RavenCommand . _guardNodeStream ( ( ) => {
183+ const pt = new PassThrough ( ) ;
184+ pt . pause ( ) ;
185+ return pt ;
186+ } ) ;
186187
187188 const fetchFn = fetcher ?? fetch ; // support for custom fetcher
188189 const response = await fetchFn ( uri , optionsToUse ) ;
189190
190- // `Readable.fromWeb` / `new Stream()` / `.pipe` are all `node:stream` APIs,
191- // so an incomplete edge polyfill (Nitro/unenv) can throw here just like
192- // `new PassThrough()` above. Guard them so the actionable error is raised.
193- try {
191+ RavenCommand . _guardNodeStream ( ( ) => {
194192 const effectiveStream : Stream =
195193 response . body
196194 ? Readable . fromWeb ( response . body )
197195 : new Stream ( ) ;
198196
199197 effectiveStream
200198 . pipe ( passthrough ) ;
201- } catch ( err ) {
202- throw RavenCommand . _maybeNodeStreamUnavailableError ( err as Error ) ;
203- }
199+ } ) ;
204200
205201 return {
206202 response,
207203 bodyStream : passthrough
208204 } ;
209205 }
210206
207+ // Runs a `node:stream` operation, translating an incomplete-polyfill failure into an
208+ // actionable error. Shared by every node:stream call site in send() so none can be
209+ // added without the guard, and the translation logic lives in one place.
210+ private static _guardNodeStream < T > ( fn : ( ) => T ) : T {
211+ try {
212+ return fn ( ) ;
213+ } catch ( err ) {
214+ throw RavenCommand . _maybeNodeStreamUnavailableError ( err as Error ) ;
215+ }
216+ }
217+
211218 private static _maybeNodeStreamUnavailableError ( err : Error ) : Error {
212- // Some edge bundlers ship an incomplete `node:stream` where PassThrough throws
213- // "not implemented" (e.g. Nitro/unenv, used by TanStack Start & Nuxt). Turn that
214- // cryptic stub error into an actionable one pointing at the real fix.
215- if ( RuntimeUtil . isWorkerd ( ) ) {
219+ // The failure is signalled by the polyfill itself, not by the runtime: unenv
220+ // (used by Nitro-based presets -- TanStack Start, Nuxt -- and edge targets such as
221+ // Cloudflare Workers, Vercel Edge, Deno Deploy) ships `node:stream` stubs that
222+ // throw e.g. "[unenv] PassThrough is not implemented yet!". We match that signature
223+ // rather than the runtime so (a) every affected runtime gets the guidance, not just
224+ // workerd, and (b) an unrelated node:stream error on a correctly configured runtime
225+ // is NOT mislabeled as a polyfill problem. The original error is kept as the cause.
226+ const message = err ?. message ?? "" ;
227+ const isIncompletePolyfill = message . includes ( "[unenv]" )
228+ || / \b n o t i m p l e m e n t e d \b / i. test ( message ) ;
229+
230+ if ( isIncompletePolyfill ) {
216231 return getError (
217232 "InvalidOperationException" ,
218- "The RavenDB client requires a working node:stream on Cloudflare Workers, but this "
219- + "runtime provided an incomplete polyfill. Enable Node.js compatibility by adding "
220- + `compatibility_flags = ["nodejs_compat"] (and a recent compatibility_date) to your `
221- + "wrangler configuration. For framework bundlers such as Nitro / TanStack Start / Nuxt "
222- + "this makes node: built-ins resolve to the workerd runtime instead of unenv stubs." ,
233+ "The RavenDB client requires a working node:stream, but this runtime provided an "
234+ + "incomplete polyfill (unenv). On Cloudflare Workers, enable Node.js compatibility by "
235+ + `adding compatibility_flags = ["nodejs_compat"] (and a recent compatibility_date) to `
236+ + "your wrangler configuration; for framework bundlers such as Nitro / TanStack Start / "
237+ + "Nuxt this makes node: built-ins resolve to the runtime instead of unenv stubs. On "
238+ + "other edge runtimes (e.g. Vercel Edge, Deno Deploy) enable the equivalent Node.js "
239+ + "compatibility so node:stream is backed by a real implementation." ,
223240 err ) ;
224241 }
225242 return err ;
0 commit comments