Skip to content

node:zlib: make stream.params retune compression level and strategy #3285

Description

@andrewtdiz

Summary

zlib deflate-family streams expose stream.params(level, strategy, callback), but Perry currently treats it as a no-op callback shim. Node validates level and strategy, then retunes subsequent compression output for the stream.

Node Behavior

On Node v25.9.0, changing params before writing data produces observably different valid deflate output, and invalid arguments throw synchronously:

const zlib = require("node:zlib");
const input = Buffer.from("a".repeat(32768));

async function encode(level) {
  const chunks = [];
  const stream = zlib.createDeflate({ level: 6 });
  stream.on("data", (chunk) => chunks.push(chunk));
  await new Promise((resolve, reject) => {
    stream.params(level, zlib.constants.Z_DEFAULT_STRATEGY, (err) => err ? reject(err) : resolve());
  });
  stream.end(input);
  await require("node:events").once(stream, "end");
  return Buffer.concat(chunks);
}

const level0 = await encode(0);
const level9 = await encode(9);
console.log(level0.length, level9.length); // 32789 58 on local Node
console.log(zlib.inflateSync(level0).length, zlib.inflateSync(level9).length); // 32768 32768

zlib.createDeflate().params(99, zlib.constants.Z_DEFAULT_STRATEGY, () => {});
// RangeError ERR_OUT_OF_RANGE
zlib.createDeflate().params(1, 999, () => {});
// RangeError ERR_OUT_OF_RANGE
zlib.createDeflate().params("1", zlib.constants.Z_DEFAULT_STRATEGY, () => {});
// TypeError ERR_INVALID_ARG_TYPE

Exact compressed bytes can vary by backend, but option-sensitive output, callback completion, valid round-trip, and validation are observable Node behavior.

Perry Behavior

crates/perry-stdlib/src/zlib.rs documents and implements zlib_stream_params() as a no-op callback dispatch:

/// `stream.params(level, strategy, cb?)` — Perry does not currently retune
/// compression levels, but Node exposes this as a function and invokes the
/// callback asynchronously when parameters are unchanged.
pub fn zlib_stream_params(_handle: i64, cb: i64) {
    if cb != 0 {
        ZLIB_PENDING_EVENTS.lock().unwrap().push(ZlibEvent::Callback(cb));
        perry_runtime::event_pump::js_notify_main_thread();
    }
}

The function ignores the stream handle, level, and strategy, so it cannot validate inputs or alter compression behavior.

Suggested PR Cut

Batch this with related issues in:
node:zlib: compression options and stream retuning parity cut

Good batch candidates:

Do not batch with:

Acceptance

  • parity/regression test proves stream.params(0, strategy, cb) and stream.params(9, strategy, cb) produce different valid deflate-family output for a repeated input
  • invalid level and strategy values throw Node-compatible errors for covered cases
  • callback completion remains Node-compatible for successful retuning
  • implementation matches Node observable behavior without requiring byte-for-byte compression parity
  • related known-failure/docs/manifest entries updated if touched

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions