Mirror: A Node.js fetch shim using built-in Request, Response, and Headers (but without native fetch)
1import { PassThrough, Transform, TransformCallback } from 'node:stream'; 2import * as zlib from 'node:zlib'; 3 4/** @see https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 */ 5type Encoding = 'gzip' | 'x-gzip' | 'deflate' | 'x-deflate' | 'br' | {}; 6 7/** @see https://github.com/nodejs/undici/pull/2650 */ 8class InflateStream extends Transform { 9 _opts?: zlib.ZlibOptions; 10 _inflate?: Transform; 11 12 constructor(opts?: zlib.ZlibOptions) { 13 super(); 14 this._opts = opts; 15 } 16 17 _transform( 18 chunk: Buffer, 19 encoding: BufferEncoding, 20 callback: TransformCallback 21 ) { 22 if (!this._inflate) { 23 if (chunk.length === 0) { 24 callback(); 25 return; 26 } 27 this._inflate = 28 (chunk[0] & 0x0f) === 0x08 29 ? zlib.createInflate(this._opts) 30 : zlib.createInflateRaw(this._opts); 31 this._inflate.on('data', this.push.bind(this)); 32 this._inflate.on('end', () => this.push(null)); 33 this._inflate.on('error', err => this.destroy(err)); 34 } 35 this._inflate.write(chunk, encoding, callback); 36 } 37 38 _final(callback: TransformCallback) { 39 if (this._inflate) { 40 this._inflate.end(); 41 this._inflate = undefined; 42 } 43 callback(); 44 } 45} 46 47export const createContentDecoder = (encoding: Encoding | {}) => { 48 // See: https://github.com/nodejs/undici/blob/008187b/lib/web/fetch/index.js#L2138-L2160 49 switch (encoding) { 50 case 'br': 51 return zlib.createBrotliDecompress({ 52 flush: zlib.constants.BROTLI_OPERATION_FLUSH, 53 finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH, 54 }); 55 case 'gzip': 56 case 'x-gzip': 57 return zlib.createGunzip({ 58 flush: zlib.constants.Z_SYNC_FLUSH, 59 finishFlush: zlib.constants.Z_SYNC_FLUSH, 60 }); 61 case 'deflate': 62 case 'x-deflate': 63 return new InflateStream({ 64 flush: zlib.constants.Z_SYNC_FLUSH, 65 finishFlush: zlib.constants.Z_SYNC_FLUSH, 66 }); 67 default: 68 return new PassThrough(); 69 } 70};