// node:zlib on the browser's CompressionStream/DecompressionStream, which speak // "gzip", "deflate" (the zlib wrapper) and "deflate-raw". The async convenience forms // and the Transform streams (createGzip, ...) run there over one Engine per codec; // brotli and zstd have no browser format and refuse, and the *Sync forms refuse until // the WASM residue (DESIGN step 5) gives them a synchronous codec. // // Where the browser and Node part ways: // - Errors. Chromium's DecompressionStream reports zlib's own message inside a // TypeError ("The compressed data was not valid: incorrect header check."), which // maps to Node's Error with code Z_DATA_ERROR and errno -3. Truncated input // ("Compressed input was truncated.") is Node's "unexpected end of file", Z_BUF_ERROR, // errno -5. A browser that words these differently keeps its own message under // Z_DATA_ERROR. // - Data after the end of a stream. Node ignores it for deflate and deflate-raw; for // gzip it starts another member unless the next byte is 0x00. The browser instead // errors ("Junk found after end of compressed data.") and drops the output of the // write that hit it, so the member's end is found again by bisection over the // member's input (a prefix decodes cleanly only at the exact end, is truncated // before it and has junk after it), and decoding resumes from there. // - Options. `level`, `memLevel` and `strategy` change only the compressed bytes and // are accepted and ignored, so output can differ from Node's byte for byte while // decoding to the same data. `dictionary` and a non-default `windowBits` refuse. import { Buffer } from "./buffer"; import { invalidArgType } from "./errors"; import type { Loop } from "./loop"; import { refused, refusedClass, unsupported } from "./refuse"; import { Transform, type TransformOptions } from "./stream"; type Format = "gzip" | "deflate" | "deflate-raw"; /** What a codec does: compress to a format, decompress one, or detect gzip vs zlib. */ type Mode = { compress: Format } | { decompress: Format | "unzip" }; const SYNC_WHY = "synchronous zlib arrives with the WASM residue (DESIGN step 5)"; const BROTLI_WHY = "brotli is not a CompressionStream format"; const ZSTD_WHY = "zstd is not a CompressionStream format"; const CODECS = { Gzip: { compress: "gzip" }, Gunzip: { decompress: "gzip" }, Deflate: { compress: "deflate" }, Inflate: { decompress: "deflate" }, DeflateRaw: { compress: "deflate-raw" }, InflateRaw: { decompress: "deflate-raw" }, Unzip: { decompress: "unzip" }, } as const satisfies Record; type CodecName = keyof typeof CODECS; const REFUSED = { BrotliCompress: BROTLI_WHY, BrotliDecompress: BROTLI_WHY, ZstdCompress: ZSTD_WHY, ZstdDecompress: ZSTD_WHY }; const lower = (name: string): string => name[0]!.toLowerCase() + name.slice(1); // ---- errors ---- function zlibError(message: string, code: "Z_DATA_ERROR" | "Z_BUF_ERROR", errno: number): Error { return Object.assign(new Error(message), { errno, code }); } const unexpectedEnd = (): Error => zlibError("unexpected end of file", "Z_BUF_ERROR", -5); type Failure = { kind: "truncated" } | { kind: "junk" } | { kind: "data"; error: Error }; function classify(e: unknown): Failure { const message = e instanceof Error ? e.message : String(e); if (/truncated/i.test(message)) return { kind: "truncated" }; if (/junk/i.test(message)) return { kind: "junk" }; const zlibMessage = /not valid: (.+?)\.?$/.exec(message)?.[1]; return { kind: "data", error: zlibError(zlibMessage ?? message, "Z_DATA_ERROR", -3) }; } const failureError = (f: Failure): Error => (f.kind === "data" ? f.error : unexpectedEnd()); // ---- options ---- interface ZlibOptions { windowBits?: number; dictionary?: unknown; } function checkOptions(mode: Mode, opts: unknown): void { if (opts === undefined || opts === null) return; if (typeof opts !== "object") throw invalidArgType("options", "of type object", opts); const { windowBits, dictionary } = opts as ZlibOptions; if (dictionary !== undefined) throw unsupported("zlib", "the dictionary option", "CompressionStream takes no preset dictionary"); const automatic = "decompress" in mode && windowBits === 0; if (windowBits !== undefined && windowBits !== 15 && !automatic) { throw unsupported("zlib", `windowBits ${String(windowBits)}`, "CompressionStream fixes the window at 15 bits"); } } // ---- engines over the web streams ---- /** Resolves after the current task's microtasks, through a MessageChannel task. */ function afterMicrotasks(): Promise { return new Promise((resolve) => { const { port1, port2 } = new MessageChannel(); port1.onmessage = () => { port1.close(); resolve(); }; port2.postMessage(null); }); } /** One CompressionStream/DecompressionStream run. Output reaches `onData` as the browser * produces it. write() settles once the browser has taken the chunk and every output * chunk it produced has reached `onData`: the reader drains in microtasks, which can * outrun the write's own settlement, so the write waits for the next task. */ function webCodec(stream: CompressionStream | DecompressionStream, onData: (bytes: Uint8Array) => void) { const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); const drained = (async () => { for (;;) { const { done, value } = await reader.read(); if (done) return; onData(value); } })(); drained.catch(() => {}); return { write: async (bytes: Uint8Array): Promise => { await writer.write(bytes as Uint8Array); await afterMicrotasks(); }, close: async (): Promise => { await writer.close(); await drained; }, }; } type WebCodec = ReturnType; /** The push-fed codec a stream or a convenience call drives. */ interface Engine { write(bytes: Uint8Array): Promise; finish(): Promise; } function deflater(format: Format, onData: (bytes: Uint8Array) => void): Engine { const codec = webCodec(new CompressionStream(format), onData); return { write: (bytes) => codec.write(bytes).catch((e: unknown) => Promise.reject(failureError(classify(e)))), finish: () => codec.close().catch((e: unknown) => Promise.reject(failureError(classify(e)))), }; } function concat(chunks: Uint8Array[]): Uint8Array { if (chunks.length === 1) return chunks[0]!; const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0)); let at = 0; for (const c of chunks) { out.set(c, at); at += c.length; } return out; } /** Decode `input` whole: its output, or how it failed. */ async function decodeWhole(format: Format, input: Uint8Array): Promise<{ out: Uint8Array } | Failure> { const chunks: Uint8Array[] = []; const codec = webCodec(new DecompressionStream(format), (b) => chunks.push(b)); try { await codec.write(input); await codec.close(); return { out: concat(chunks) }; } catch (e) { return classify(e); } } /** The end of the first stream in `input`, known to be followed by junk, and its output. */ async function bisectEnd(format: Format, input: Uint8Array): Promise<{ end: number; out: Uint8Array }> { let lo = 1; let hi = input.length - 1; while (lo <= hi) { const mid = (lo + hi) >>> 1; const r = await decodeWhole(format, input.subarray(0, mid)); if ("out" in r) return { end: mid, out: r.out }; if (r.kind === "truncated") lo = mid + 1; else hi = mid - 1; } throw zlibError("invalid stream end", "Z_DATA_ERROR", -3); } function inflater(mode: Format | "unzip", onData: (bytes: Uint8Array) => void, onEnd: () => void): Engine { let format: Format | null = mode === "unzip" ? null : mode; /** unzip's first bytes, held until they tell gzip from zlib. */ let head: Uint8Array[] = []; let codec: WebCodec | null = null; /** The current stream's input and the output already delivered from it. */ let member: Uint8Array[] = []; let emitted = 0; /** Past the end: Node ignores the rest of the input. */ let done = false; const start = (): WebCodec => { member = []; emitted = 0; return webCodec(new DecompressionStream(format!), (b) => { emitted += b.length; onData(b); }); }; const feed = async (bytes: Uint8Array): Promise => { if (done || bytes.length === 0) return; codec ??= start(); member.push(bytes); try { await codec.write(bytes); } catch (e) { const failure = classify(e); if (failure.kind !== "junk") throw failureError(failure); const input = concat(member); const { end, out } = await bisectEnd(format!, input); const rest = input.subarray(end); codec = null; if (out.length > emitted) onData(out.subarray(emitted)); // Node's gunzip reads on into another member unless the next byte is 0x00; // otherwise the output has ended while input remains. if (format === "gzip" && rest[0] !== 0x00) { await feed(rest); } else { done = true; onEnd(); } } }; return { async write(bytes) { if (format === null) { head.push(bytes); const seen = concat(head); if (seen.length === 0 || (seen.length === 1 && seen[0] === 0x1f)) return; format = seen[0] === 0x1f && seen[1] === 0x8b ? "gzip" : "deflate"; head = []; return feed(seen); } return feed(bytes); }, async finish() { if (done) return; // Nothing fed, or unzip still holding a lone 0x1f: Node's zlib wants more input. if (codec === null) throw unexpectedEnd(); try { await codec.close(); } catch (e) { throw failureError(classify(e)); } }, }; } function engineFor(mode: Mode, onData: (bytes: Uint8Array) => void, onEnd: () => void = () => {}): Engine { return "compress" in mode ? deflater(mode.compress, onData) : inflater(mode.decompress, onData, onEnd); } // ---- input ---- const CHUNK_TYPES = "of type string or an instance of Buffer, TypedArray, or DataView"; function toBytes(data: unknown, name = "chunk"): Uint8Array { if (typeof data === "string") return Buffer.from(data); if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); if (data instanceof ArrayBuffer) return new Uint8Array(data); throw invalidArgType(name, CHUNK_TYPES, data); } const asBuffer = (bytes: Uint8Array): Buffer => new Buffer(bytes.buffer as ArrayBuffer, bytes.byteOffset, bytes.byteLength); // ---- crc32 ---- const CRC_TABLE = (() => { const table = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; table[n] = c >>> 0; } return table; })(); /** zlib.crc32(data[, value]): the gzip/PNG CRC-32, continuing from `value`. */ function crc32(data: unknown, value: unknown = 0): number { if (typeof data !== "string" && !ArrayBuffer.isView(data)) throw invalidArgType("data", CHUNK_TYPES, data); if (typeof value !== "number") throw invalidArgType("value", "of type number", value); if (!Number.isInteger(value)) { throw Object.assign(new RangeError(`The value of "value" is out of range. It must be an integer. Received ${value}`), { code: "ERR_OUT_OF_RANGE" }); } if (value < 0 || value > 0xffffffff) { throw Object.assign(new RangeError(`The value of "value" is out of range. It must be >= 0 && <= 4294967295. Received ${value}`), { code: "ERR_OUT_OF_RANGE" }); } const bytes = toBytes(data, "data"); let c = ~value >>> 0; for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]!) & 0xff]! ^ (c >>> 8); return ~c >>> 0; } // ---- streams ---- /** Node's ZlibBase: a Transform whose writes and flush run on an Engine, one at a time. * Each completes in the poll phase, holding the loop while in flight, and pushes the * output it produced only if it succeeded, as Node does. Like Node, the write that is * last after end() also finishes the stream (Node's Z_FINISH on the last chunk), so a * truncated input fails that write; and _final returns at once, so 'finish' does not * wait for the output to end. */ class ZlibStream extends Transform { bytesWritten = 0; readonly #loop: Loop; readonly #engine: Engine; #finished: Promise | null = null; /** Output of the write or flush in flight, and whether the data ended in it. */ #out: Uint8Array[] = []; #endedEarly = false; constructor(loop: Loop, mode: Mode, opts: unknown) { checkOptions(mode, opts); const { highWaterMark, readableHighWaterMark, writableHighWaterMark } = (opts ?? {}) as TransformOptions; super({ highWaterMark, readableHighWaterMark, writableHighWaterMark }); this.#loop = loop; this.#engine = engineFor(mode, (bytes) => this.#out.push(bytes), () => (this.#endedEarly = true)); } override _transform(chunk: unknown, _encoding: string, callback: (error?: Error | null) => void): void { const bytes = chunk as Buffer; const last = this.writableEnded && this.writableLength === bytes.length; this.#complete(this.#engine.write(bytes).then(() => (last ? this.#finish() : undefined)), bytes.length, callback); } override _flush(callback: (error?: Error | null) => void): void { this.#complete(this.#finish(), 0, callback); } override _final(callback: (error?: Error | null) => void): void { callback(); } close(callback?: () => void): void { if (callback) this.once("close", callback); this.destroy(); } flush(): never { throw unsupported("zlib", "flush()", "CompressionStream cannot flush mid-stream"); } params(): never { throw unsupported("zlib", "params()", "CompressionStream options are fixed at creation"); } reset(): never { throw unsupported("zlib", "reset()", "a CompressionStream cannot be reset"); } #finish(): Promise { this.#finished ??= this.#engine.finish(); return this.#finished; } #complete(work: Promise, consumed: number, callback: (error?: Error | null) => void): void { this.#loop.hold(); const settle = (error: Error | null): void => this.#loop.enqueueCompletion(() => { this.#loop.release(); const out = this.#out; const ended = this.#endedEarly; this.#out = []; this.#endedEarly = false; if (!error && !this.destroyed) { this.bytesWritten += consumed; for (const bytes of out) this.push(asBuffer(bytes)); if (ended) this.push(null); } callback(error); }); work.then(() => settle(null), (e: Error) => settle(e)); } } // ---- the module ---- type Callback = (error: Error | null, result?: Buffer) => void; export function makeZlib(loop: Loop): Record { /** zlib.gunzip(buffer[, options], callback): the result arrives in the poll phase. */ function convenience(mode: Mode) { return (data: unknown, optsOrCb?: unknown, maybeCb?: unknown): void => { const callback = (typeof optsOrCb === "function" ? optsOrCb : maybeCb) as Callback; if (typeof callback !== "function") throw invalidArgType("callback", "of type function", callback); checkOptions(mode, typeof optsOrCb === "function" ? undefined : optsOrCb); // Node ends its engine with the input, and end(null) is an end with no chunk. const input = data === null || data === undefined ? new Uint8Array(0) : toBytes(data); const out: Uint8Array[] = []; const engine = engineFor(mode, (b) => out.push(b)); loop.hold(); const settle = (error: Error | null): void => loop.enqueueCompletion(() => { loop.release(); if (error) callback(error); else callback(null, asBuffer(concat(out))); }); engine.write(input).then(() => engine.finish()).then(() => settle(null), (e: Error) => settle(e)); }; } const constants = { Z_NO_FLUSH: 0, Z_PARTIAL_FLUSH: 1, Z_SYNC_FLUSH: 2, Z_FULL_FLUSH: 3, Z_FINISH: 4, Z_BLOCK: 5, Z_OK: 0, Z_STREAM_END: 1, Z_NEED_DICT: 2, Z_ERRNO: -1, Z_STREAM_ERROR: -2, Z_DATA_ERROR: -3, Z_MEM_ERROR: -4, Z_BUF_ERROR: -5, Z_VERSION_ERROR: -6, Z_NO_COMPRESSION: 0, Z_BEST_SPEED: 1, Z_BEST_COMPRESSION: 9, Z_DEFAULT_COMPRESSION: -1, Z_FILTERED: 1, Z_HUFFMAN_ONLY: 2, Z_RLE: 3, Z_FIXED: 4, Z_DEFAULT_STRATEGY: 0, ZLIB_VERNUM: 4880, DEFLATE: 1, INFLATE: 2, GZIP: 3, GUNZIP: 4, DEFLATERAW: 5, INFLATERAW: 6, UNZIP: 7, BROTLI_DECODE: 8, BROTLI_ENCODE: 9, ZSTD_DECOMPRESS: 11, ZSTD_COMPRESS: 10, Z_MIN_WINDOWBITS: 8, Z_MAX_WINDOWBITS: 15, Z_DEFAULT_WINDOWBITS: 15, Z_MIN_CHUNK: 64, Z_MAX_CHUNK: Infinity, Z_DEFAULT_CHUNK: 16384, Z_MIN_MEMLEVEL: 1, Z_MAX_MEMLEVEL: 9, Z_DEFAULT_MEMLEVEL: 8, Z_MIN_LEVEL: -1, Z_MAX_LEVEL: 9, Z_DEFAULT_LEVEL: -1, BROTLI_OPERATION_PROCESS: 0, BROTLI_OPERATION_FLUSH: 1, BROTLI_OPERATION_FINISH: 2, BROTLI_OPERATION_EMIT_METADATA: 3, BROTLI_PARAM_MODE: 0, BROTLI_MODE_GENERIC: 0, BROTLI_MODE_TEXT: 1, BROTLI_MODE_FONT: 2, BROTLI_DEFAULT_MODE: 0, BROTLI_PARAM_QUALITY: 1, BROTLI_MIN_QUALITY: 0, BROTLI_MAX_QUALITY: 11, BROTLI_DEFAULT_QUALITY: 11, BROTLI_PARAM_LGWIN: 2, BROTLI_MIN_WINDOW_BITS: 10, BROTLI_MAX_WINDOW_BITS: 24, BROTLI_LARGE_MAX_WINDOW_BITS: 30, BROTLI_DEFAULT_WINDOW: 22, BROTLI_PARAM_LGBLOCK: 3, BROTLI_MIN_INPUT_BLOCK_BITS: 16, BROTLI_MAX_INPUT_BLOCK_BITS: 24, BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4, BROTLI_PARAM_SIZE_HINT: 5, BROTLI_PARAM_LARGE_WINDOW: 6, BROTLI_PARAM_NPOSTFIX: 7, BROTLI_PARAM_NDIRECT: 8, BROTLI_DECODER_RESULT_ERROR: 0, BROTLI_DECODER_RESULT_SUCCESS: 1, BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2, BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3, BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0, BROTLI_DECODER_PARAM_LARGE_WINDOW: 1, BROTLI_DECODER_NO_ERROR: 0, BROTLI_DECODER_SUCCESS: 1, BROTLI_DECODER_NEEDS_MORE_INPUT: 2, BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3, BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1, BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2, BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3, BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4, BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5, BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6, BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7, BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8, BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9, BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10, BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11, BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12, BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13, BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14, BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15, BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16, BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19, BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21, BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22, BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26, BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27, BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30, BROTLI_DECODER_ERROR_UNREACHABLE: -31, ZSTD_e_continue: 0, ZSTD_e_flush: 1, ZSTD_e_end: 2, ZSTD_fast: 1, ZSTD_dfast: 2, ZSTD_greedy: 3, ZSTD_lazy: 4, ZSTD_lazy2: 5, ZSTD_btlazy2: 6, ZSTD_btopt: 7, ZSTD_btultra: 8, ZSTD_btultra2: 9, ZSTD_c_compressionLevel: 100, ZSTD_c_windowLog: 101, ZSTD_c_hashLog: 102, ZSTD_c_chainLog: 103, ZSTD_c_searchLog: 104, ZSTD_c_minMatch: 105, ZSTD_c_targetLength: 106, ZSTD_c_strategy: 107, ZSTD_c_enableLongDistanceMatching: 160, ZSTD_c_ldmHashLog: 161, ZSTD_c_ldmMinMatch: 162, ZSTD_c_ldmBucketSizeLog: 163, ZSTD_c_ldmHashRateLog: 164, ZSTD_c_contentSizeFlag: 200, ZSTD_c_checksumFlag: 201, ZSTD_c_dictIDFlag: 202, ZSTD_c_nbWorkers: 400, ZSTD_c_jobSize: 401, ZSTD_c_overlapLog: 402, ZSTD_d_windowLogMax: 100, ZSTD_CLEVEL_DEFAULT: 3, ZSTD_error_no_error: 0, ZSTD_error_GENERIC: 1, ZSTD_error_prefix_unknown: 10, ZSTD_error_version_unsupported: 12, ZSTD_error_frameParameter_unsupported: 14, ZSTD_error_frameParameter_windowTooLarge: 16, ZSTD_error_corruption_detected: 20, ZSTD_error_checksum_wrong: 22, ZSTD_error_literals_headerWrong: 24, ZSTD_error_dictionary_corrupted: 30, ZSTD_error_dictionary_wrong: 32, ZSTD_error_dictionaryCreation_failed: 34, ZSTD_error_parameter_unsupported: 40, ZSTD_error_parameter_combination_unsupported: 41, ZSTD_error_parameter_outOfBound: 42, ZSTD_error_tableLog_tooLarge: 44, ZSTD_error_maxSymbolValue_tooLarge: 46, ZSTD_error_maxSymbolValue_tooSmall: 48, ZSTD_error_stabilityCondition_notRespected: 50, ZSTD_error_stage_wrong: 60, ZSTD_error_init_missing: 62, ZSTD_error_memory_allocation: 64, ZSTD_error_workSpace_tooSmall: 66, ZSTD_error_dstSize_tooSmall: 70, ZSTD_error_srcSize_wrong: 72, ZSTD_error_dstBuffer_null: 74, ZSTD_error_noForwardProgress_destFull: 80, ZSTD_error_noForwardProgress_inputEmpty: 82, }; // zlib's return codes, both ways: code -> name and name -> code. const ZLIB_CODES = ["Z_OK", "Z_STREAM_END", "Z_NEED_DICT", "Z_ERRNO", "Z_STREAM_ERROR", "Z_DATA_ERROR", "Z_MEM_ERROR", "Z_BUF_ERROR", "Z_VERSION_ERROR"] as const; const codes: Record = {}; for (const name of ZLIB_CODES) codes[name] = constants[name]; for (const name of ZLIB_CODES) codes[constants[name]] = name; const zlib: Record = { constants, codes, crc32, }; // Node also exposes the zlib and zstd constants at top level, but not brotli's. for (const [name, value] of Object.entries(constants)) { if (!name.startsWith("BROTLI_")) zlib[name] = value; } for (const name of Object.keys(CODECS) as CodecName[]) { const mode: Mode = CODECS[name]; const cls = class extends ZlibStream { constructor(opts?: unknown) { super(loop, mode, opts); } }; Object.defineProperty(cls, "name", { value: name }); zlib[name] = cls; zlib[`create${name}`] = (opts?: unknown) => new cls(opts); zlib[lower(name)] = convenience(mode); zlib[`${lower(name)}Sync`] = refused("zlib", `${lower(name)}Sync`, SYNC_WHY); } for (const [name, why] of Object.entries(REFUSED)) { zlib[name] = refusedClass("zlib", name, why); zlib[`create${name}`] = refused("zlib", `create${name}`, why); zlib[lower(name)] = refused("zlib", lower(name), why); zlib[`${lower(name)}Sync`] = refused("zlib", `${lower(name)}Sync`, `${why}, and ${SYNC_WHY}`); } return zlib; }