Skip to content

node:zlib: wire callback-form deflate/inflate/unzip codecs #2667

Description

@andrewtdiz

Summary

node:zlib exposes callback-form one-shot codecs beyond gzip/gunzip, but current Perry source only routes the async/promise-returning dispatch for gzip, gunzip, brotliCompress, and brotliDecompress. The exported deflate, inflate, deflateRaw, inflateRaw, and unzip functions should actually run the corresponding codec and invoke the callback / work through util.promisify() like Node.

No local Perry binary was available in this issue-creator workspace, so this report is based on current origin/main source inspection plus the Node oracle below.

Node oracle

Node v25.9.0 exposes all five functions and they complete callback and promisified round trips:

const zlib = require('node:zlib');
const { promisify } = require('node:util');
const input = Buffer.from('perry zlib callback coverage');

console.log(process.version);
console.log('types:', ['deflate','inflate','deflateRaw','inflateRaw','unzip']
  .map((name) => name + ':' + typeof zlib[name]).join(','));

zlib.deflate(input, (err, compressed) => {
  console.log('deflate cb:', err && err.name, Buffer.isBuffer(compressed), compressed.length > 0);
  zlib.inflate(compressed, (err2, plain) => console.log('inflate cb:', err2 && err2.name, plain.toString()));
});

zlib.deflateRaw(input, (err, compressed) => {
  console.log('deflateRaw cb:', err && err.name, Buffer.isBuffer(compressed), compressed.length > 0);
  zlib.inflateRaw(compressed, (err2, plain) => console.log('inflateRaw cb:', err2 && err2.name, plain.toString()));
});

zlib.gzip(input, (err, compressed) => {
  zlib.unzip(compressed, (err2, plain) => console.log('unzip gzip cb:', err2 && err2.name, plain.toString()));
});

Promise.all([
  promisify(zlib.deflate)(input).then((buf) => ['pdeflate', Buffer.isBuffer(buf), buf.length > 0]),
  promisify(zlib.unzip)(zlib.gzipSync(input)).then((buf) => ['punzip', buf.toString()]),
]).then((rows) => rows.forEach((row) => console.log(...row)));

Output:

v25.9.0
types: deflate:function,inflate:function,deflateRaw:function,inflateRaw:function,unzip:function
deflate cb: null true true
deflateRaw cb: null true true
pdeflate true true
punzip perry zlib callback coverage
inflate cb: null perry zlib callback coverage
inflateRaw cb: null perry zlib callback coverage
unzip gzip cb: null perry zlib callback coverage

Perry evidence

Current origin/main source shows the functions are surfaced but not wired end to end:

  • crates/perry-api-manifest/src/entries.rs registers zlib.deflate, deflateRaw, inflate, inflateRaw, and unzip, with a comment saying these callback-form variants were stubbed so typeof zlib.deflate === "function" resolves true and should piggy-back on existing native-table sync routes when used with util.promisify().
  • crates/perry-runtime/src/util_promisify.rs treats all zlib callback-form codecs, including these five names, as already Promise-returning and returns the bound function unchanged.
  • crates/perry-codegen/src/lower_call/native_table/media.rs has native call entries for gzip, gunzip, Brotli async methods, and the sync raw/unzip methods, but no direct async entries for deflate, inflate, deflateRaw, inflateRaw, or unzip.
  • crates/perry-stdlib/src/zlib.rs::js_zlib_native_dispatch() dispatches sync forms plus gzip, gunzip, brotliCompress, and brotliDecompress; the five non-gzip callback-form names fall through to undefined.

That leaves these Node functions in a feature-detection shape without the actual callback/promisify behavior Node provides.

Suggested parity coverage

Add granular node-suite/zlib cases for:

import { deflate, inflate, deflateRaw, inflateRaw, unzip, gzipSync } from 'node:zlib';
import { promisify } from 'node:util';

const input = Buffer.from('perry zlib callback coverage');

deflate(input, (err, compressed) => {
  console.log('deflate callback:', err && err.name, Buffer.isBuffer(compressed), compressed.length > 0);
  inflate(compressed, (err2, plain) => console.log('inflate callback:', err2 && err2.name, plain.toString()));
});

deflateRaw(input, (err, compressed) => {
  console.log('deflateRaw callback:', err && err.name, Buffer.isBuffer(compressed), compressed.length > 0);
  inflateRaw(compressed, (err2, plain) => console.log('inflateRaw callback:', err2 && err2.name, plain.toString()));
});

unzip(gzipSync(input), (err, plain) => console.log('unzip callback:', err && err.name, plain.toString()));
console.log('promisified deflate:', Buffer.isBuffer(await promisify(deflate)(input)));
console.log('promisified unzip:', (await promisify(unzip)(gzipSync(input))).toString());

Scope / non-goals

This is narrower than #2557 option-object parity and #2510 Zstd codec support. It is also not about the already-wired gzip/gunzip/Brotli callback forms or stream Transform factories. The requested fix is routing the five exposed one-shot callback-form codecs to their existing sync/raw/unzip implementations with Node-compatible callback and util.promisify() behavior.

Duplicate searches performed before filing:

  • zlib deflate inflate unzip callback
  • zlib native dispatch deflate inflate async
  • zlib callback-form variants
  • PR search for zlib deflate inflate unzip callback native dispatch async

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