Is your feature request related to a problem? Please describe.
Decompressed response bytes from compressed payloads come back as a Node Buffer instances rather than the base Uint8Array type. That itself isn't an explicit issue, the Buffer does subclass the Uint8Array so is technically correct.
The issue we're having is that when that gets into react-server-land, react server components break when passing data through the server-client boundary because the Buffer has a toJSON method, which react (seemingly intentionally) does not honor as binary data. acceptCompression defaults to [compressionGzip, compressionBrotli], so any response the server compresses hits this.
For us, this surfaces in Any.value and $unknown[].data values since those end up inheriting the Buffer subclass. React then applies Buffer.toJSON(), spitting out { type: 'Buffer', data: [...] } which doesn't error but breaks client side decodes downstream.
Describe the solution you'd like
The fix would be to have connect-node's asUint8ArrayArrayBuffer (packages/connect-node/src/compression.ts) check for subclasses of Uint8Array and normalize on that type to ensure its compatible for structured-clone style consumers. This could be done with a zero-copy into a Uint8Array so it would be effectively free and still conforms to the expected type.
function asUint8ArrayArrayBuffer(
bytes: Promise<Uint8Array>,
): Promise<Uint8Array<ArrayBuffer>> {
return bytes.then((b) => {
if (b.buffer instanceof ArrayBuffer) {
// Node hands back Node Buffers. Re-view so consumers get a base Uint8Array
return b.constructor === Uint8Array
? (b as Uint8Array<ArrayBuffer>)
: new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
}
return new Uint8Array(b);
});
}
To be clear, I don't think anything is broken here, asUint8ArrayBuffer is doing its stated job and we found a workaround. It just has a bit of a server <-> client footgun that consumers can't see from the Uint8Array type, and may result in downstream decode failures far from the cause.
Describe alternatives you've considered
- Normalize in app code - What we're doing now and it seems to work. We've added a wrapper around all of our connect-node services that recursively check every response payload to transform node buffers into
Uint8Array values.
- Change in React - The react team seems to have considered this and chose to deliberately not preserve the binary data as a
Uint8Array, and warn to not pass node buffers instead.
- Leave the behavior, document it — a note that decompressed bytes may be Buffers and should be normalized before crossing a structured-clone boundary, such as with React server components
Is your feature request related to a problem? Please describe.
Decompressed response bytes from compressed payloads come back as a Node
Bufferinstances rather than the baseUint8Arraytype. That itself isn't an explicit issue, theBufferdoes subclass theUint8Arrayso is technically correct.The issue we're having is that when that gets into react-server-land, react server components break when passing data through the server-client boundary because the
Bufferhas atoJSONmethod, which react (seemingly intentionally) does not honor as binary data.acceptCompressiondefaults to[compressionGzip, compressionBrotli], so any response the server compresses hits this.For us, this surfaces in
Any.valueand$unknown[].datavalues since those end up inheriting theBuffersubclass. React then appliesBuffer.toJSON(), spitting out{ type: 'Buffer', data: [...] }which doesn't error but breaks client side decodes downstream.Describe the solution you'd like
The fix would be to have
connect-node'sasUint8ArrayArrayBuffer(packages/connect-node/src/compression.ts) check for subclasses ofUint8Arrayand normalize on that type to ensure its compatible for structured-clone style consumers. This could be done with a zero-copy into aUint8Arrayso it would be effectively free and still conforms to the expected type.To be clear, I don't think anything is broken here,
asUint8ArrayBufferis doing its stated job and we found a workaround. It just has a bit of a server <-> client footgun that consumers can't see from theUint8Arraytype, and may result in downstream decode failures far from the cause.Describe alternatives you've considered
Uint8Arrayvalues.Uint8Array, and warn to not pass node buffers instead.