diff --git a/src/node/internal/internal_zlib.ts b/src/node/internal/internal_zlib.ts index 7c2836792ee..3e37dab02bc 100644 --- a/src/node/internal/internal_zlib.ts +++ b/src/node/internal/internal_zlib.ts @@ -3,12 +3,13 @@ // https://opensource.org/licenses/Apache-2.0 // Copyright Joyent and Node contributors. All rights reserved. MIT license. -import { default as zlibUtil, type ZlibOptions } from 'node-internal:zlib'; +import { default as zlibUtil, ZlibOptions } from 'node-internal:zlib'; import { Buffer } from 'node-internal:internal_buffer'; import { validateUint32 } from 'node-internal:validators'; -import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors'; -import { isArrayBufferView } from 'node-internal:internal_types'; import { Zlib } from 'node-internal:internal_zlib_base'; +export const { ZLIB_OS_CODE } = zlibUtil; + +type CompressCallback = (error: Error | null, result: Buffer | null) => void; const { CONST_DEFLATE, @@ -20,6 +21,149 @@ const { CONST_UNZIP, } = zlibUtil; +export function crc32( + data: ArrayBufferView | string, + value: number = 0 +): number { + validateUint32(value, 'value'); + return zlibUtil.crc32(data, value); +} + +export function inflateSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_INFLATE)); +} + +export function deflateSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_DEFLATE)); +} + +export function gunzipSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_GUNZIP)); +} + +export function gzipSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_GZIP)); +} + +export function inflateRawSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from( + zlibUtil.zlibSync(data, options, zlibUtil.CONST_INFLATERAW) + ); +} + +export function deflateRawSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + return Buffer.from( + zlibUtil.zlibSync(data, options, zlibUtil.CONST_DEFLATERAW) + ); +} + +export function unzipSync( + data: ArrayBufferView | string, + options: ZlibOptions = {} +): Buffer { + data; + options; + return Buffer.alloc(0); + // FIXME: not actually implemented +} + +export function inflate( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb(data, options, zlibUtil.CONST_INFLATE, (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + +export function unzip( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + data; + options; + callback; + // FIXME: not actually implemented +} + +export function inflateRaw( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb( + data, + options, + zlibUtil.CONST_INFLATERAW, + (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + +export function gunzip( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb(data, options, zlibUtil.CONST_GUNZIP, (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + +export function deflate( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb(data, options, zlibUtil.CONST_DEFLATE, (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + +export function deflateRaw( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb( + data, + options, + zlibUtil.CONST_DEFLATERAW, + (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + +export function gzip( + data: ArrayBufferView | string, + options: ZlibOptions = {}, + callback: CompressCallback +): void { + zlibUtil.zlibWithCb(data, options, zlibUtil.CONST_GZIP, (error, result) => + callback(error ? Error(error) : null, result ? Buffer.from(result) : null) + ); +} + const constPrefix = 'CONST_'; export const constants: Record = {}; @@ -41,19 +185,6 @@ Object.defineProperties( ) ); -export function crc32( - data: ArrayBufferView | string, - value: number = 0 -): number { - if (typeof data === 'string') { - data = Buffer.from(data); - } else if (!isArrayBufferView(data)) { - throw new ERR_INVALID_ARG_TYPE('data', 'ArrayBufferView', typeof data); - } - validateUint32(value, 'value'); - return zlibUtil.crc32(data, value); -} - export class Gzip extends Zlib { public constructor(options: ZlibOptions) { super(options, CONST_GZIP); diff --git a/src/node/internal/zlib.d.ts b/src/node/internal/zlib.d.ts index ce050f1110b..6a90748df1f 100644 --- a/src/node/internal/zlib.d.ts +++ b/src/node/internal/zlib.d.ts @@ -2,6 +2,24 @@ import { owner_symbol, type Zlib } from 'node-internal:internal_zlib_base'; export function crc32(data: ArrayBufferView, value: number): number; +type CompressCallback = ( + error: string | null, + result: ArrayBuffer | null +) => void; + +export function crc32(data: ArrayBufferView | string, value: number): number; +export function zlibSync( + data: ArrayBufferView | string, + options: ZlibOptions, + mode: number +): ArrayBuffer; +export function zlibWithCb( + data: ArrayBufferView | string, + options: ZlibOptions, + mode: number, + cb: CompressCallback +): ArrayBuffer; + // zlib.constants (part of the API contract for node:zlib) export const CONST_Z_NO_FLUSH: number; export const CONST_Z_PARTIAL_FLUSH: number; @@ -54,6 +72,8 @@ export const CONST_Z_MIN_LEVEL: number; export const CONST_Z_MAX_LEVEL: number; export const CONST_Z_DEFAULT_LEVEL: number; +export const ZLIB_OS_CODE: number; + export const CONST_BROTLI_OPERATION_PROCESS: number; export const CONST_BROTLI_OPERATION_FLUSH: number; export const CONST_BROTLI_OPERATION_FINISH: number; diff --git a/src/node/zlib.ts b/src/node/zlib.ts index efecda82e4c..fa840ed51c7 100644 --- a/src/node/zlib.ts +++ b/src/node/zlib.ts @@ -1,5 +1,5 @@ import * as zlib from 'node-internal:internal_zlib'; -import { crc32, constants } from 'node-internal:internal_zlib'; +import { crc32, constants, ZLIB_OS_CODE } from 'node-internal:internal_zlib'; import { default as compatFlags } from 'workerd:compatibility-flags'; const { nodeJsZlib } = compatFlags; @@ -29,6 +29,24 @@ const createInflate = protectMethod(zlib.createInflate); const createInflateRaw = protectMethod(zlib.createInflateRaw); const createUnzip = protectMethod(zlib.createUnzip); +const inflate = protectMethod(zlib.inflate); +const inflateSync = protectMethod(zlib.inflateSync); +const deflate = protectMethod(zlib.deflate); +const deflateSync = protectMethod(zlib.deflateSync); + +const inflateRaw = protectMethod(zlib.inflateRaw); +const inflateRawSync = protectMethod(zlib.inflateRawSync); +const deflateRaw = protectMethod(zlib.deflateRaw); +const deflateRawSync = protectMethod(zlib.deflateRawSync); + +const gzip = protectMethod(zlib.gzip); +const gzipSync = protectMethod(zlib.gzipSync); +const gunzip = protectMethod(zlib.gunzip); +const gunzipSync = protectMethod(zlib.gunzipSync); + +const unzip = protectMethod(zlib.unzip); +const unzipSync = protectMethod(zlib.unzipSync); + export { crc32, constants, @@ -50,6 +68,22 @@ export { createInflate, createInflateRaw, createUnzip, + + // One-shot methods + inflate, + inflateSync, + deflate, + deflateSync, + inflateRaw, + inflateRawSync, + deflateRaw, + deflateRawSync, + gzip, + gzipSync, + gunzip, + gunzipSync, + unzip, + unzipSync, }; export default { @@ -73,4 +107,23 @@ export default { createInflate, createInflateRaw, createUnzip, + + // One-shot methods + inflate, + inflateSync, + deflate, + deflateSync, + inflateRaw, + inflateRawSync, + deflateRaw, + deflateRawSync, + gzip, + gzipSync, + gunzip, + gunzipSync, + unzip, + unzipSync, + + // For unit tests only + ZLIB_OS_CODE, }; diff --git a/src/workerd/api/node/tests/zlib-nodejs-test.js b/src/workerd/api/node/tests/zlib-nodejs-test.js index 0cc072a3505..dda348d4a30 100644 --- a/src/workerd/api/node/tests/zlib-nodejs-test.js +++ b/src/workerd/api/node/tests/zlib-nodejs-test.js @@ -239,12 +239,9 @@ export const crc32Test = { } [undefined, null, true, 1, () => {}, {}].forEach((invalid) => { - throws( - () => { - zlib.crc32(invalid); - }, - { code: 'ERR_INVALID_ARG_TYPE' } - ); + throws(() => { + zlib.crc32(invalid); + }, TypeError("Failed to execute 'crc32' on 'ZlibUtil': parameter 1 is not of type 'string or ArrayBuffer or ArrayBufferView'.")); }); [null, true, () => {}, {}].forEach((invalid) => { @@ -667,3 +664,176 @@ export const testFailedInit = { // - [x] test-zlib-failed-init.js // - [ ] test-zlib-invalid-input.js // - [ ] test-zlib-reset-before-write.js + +const BIG_DATA = 'horse'.repeat(50_000) + 'cow'.repeat(49_000); + +export const inflateSyncTest = { + test() { + strictEqual( + zlib.inflateSync(zlib.deflateSync(BIG_DATA)).toString(), + BIG_DATA + ); + + throws( + () => zlib.inflateSync('garbage data'), + Error('incorrect header check') + ); + + strictEqual( + zlib + .inflateSync(Buffer.from('OE9LyixKUUiCEQAmfgUk', 'base64'), { + windowBits: 11, + level: 4, + }) + .toString(), + 'bird bird bird' + ); + }, +}; + +export const deflateSyncTest = { + test() { + throws( + () => zlib.deflateSync('hello world', { windowBits: 9000 }), + Error('Invalid windowBits') + ); + throws( + () => zlib.deflateSync('hello world', { strategy: 400 }), + Error('invalid strategy') + ); + throws( + () => + zlib.deflateSync(BIG_DATA, { maxOutputLength: 64 }).toString('base64'), + RangeError('Memory limit exceeded') + ); + + strictEqual( + zlib + .deflateSync('bird bird bird', { windowBits: 11, level: 4 }) + .toString('base64'), + 'OE9LyixKUUiCEQAmfgUk' + ); + + strictEqual( + zlib + .deflateSync('what happens if you do not flush?', { + finishFlush: zlib.constants.Z_NO_FLUSH, + }) + .toString('base64'), + 'eJw=' + ); + + strictEqual( + zlib + .deflateSync(Buffer.from('bird bird bird'), { + windowBits: 11, + level: 4, + }) + .toString('base64'), + 'OE9LyixKUUiCEQAmfgUk' + ); + + deepStrictEqual( + zlib.gzipSync('water, water, everywhere, nor any drop to drink'), + Buffer.from([ + 0x1f, + 0x8b, + 0x8, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + zlib.ZLIB_OS_CODE, + 0x2b, + 0x4f, + 0x2c, + 0x49, + 0x2d, + 0xd2, + 0x51, + 0x28, + 0x87, + 0x50, + 0xa9, + 0x65, + 0xa9, + 0x45, + 0x95, + 0xe5, + 0x19, + 0xa9, + 0x45, + 0xa9, + 0x3a, + 0xa, + 0x79, + 0xf9, + 0x45, + 0xa, + 0x89, + 0x79, + 0x95, + 0xa, + 0x29, + 0x45, + 0xf9, + 0x5, + 0xa, + 0x25, + 0xf9, + 0x40, + 0x3a, + 0x33, + 0x2f, + 0x1b, + 0x0, + 0xaa, + 0x92, + 0xf2, + 0x3c, + 0x2f, + 0x0, + 0x0, + 0x0, + ]) + ); + strictEqual( + zlib + .deflateRawSync('as idle as a painted ship upon a painted ocean') + .toString('base64'), + 'SyxWyEzJSVVILFZIVChIzMwrSU1RKM7ILFAoLcjPQxLLT05NzAMA' + ); + }, +}; +export const inflateTest = { + test() { + zlib.inflate( + Buffer.from('OE9LyixKUUiCEQAmfgUk', 'base64'), + { + windowBits: 11, + level: 4, + }, + (_, result) => { + strictEqual(result.toString(), 'bird bird bird'); + } + ); + }, +}; + +export const deflateTest = { + test() { + zlib.deflate( + 'bird bird bird', + { windowBits: 11, level: 4 }, + (_, result) => { + strictEqual(result.toString('base64'), 'OE9LyixKUUiCEQAmfgUk'); + } + ); + + zlib.deflate('garbage data', { level: -9000 }, (error, _) => { + strictEqual(error.message, 'Error: Invalid compression level'); + }); + }, +}; diff --git a/src/workerd/api/node/zlib-util.c++ b/src/workerd/api/node/zlib-util.c++ index 77632c6c9b5..92cdd7024d2 100644 --- a/src/workerd/api/node/zlib-util.c++ +++ b/src/workerd/api/node/zlib-util.c++ @@ -4,14 +4,105 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. #include "zlib-util.h" +#include "workerd/jsg/exception.h" + +#include + +#include namespace workerd::api::node { +uint32_t ZlibUtil::crc32Sync(InputSource data, uint32_t value) { + KJ_SWITCH_ONEOF(data) { + KJ_CASE_ONEOF(dataBuf, kj::Array) { + return crc32(value, dataBuf.begin(), dataBuf.size()); + } + + KJ_CASE_ONEOF(dataStr, jsg::NonCoercible) { + return crc32(value, dataStr.value.asBytes().begin(), dataStr.value.asBytes().size()); + } + } -uint32_t ZlibUtil::crc32Sync(kj::Array data, uint32_t value) { - // Note: Bytef is defined in zlib.h - return crc32(value, reinterpret_cast(data.begin()), data.size()); + KJ_UNREACHABLE; } +class GrowableBuffer { + // A copy of kj::Vector with some additional methods for use as a growable buffer with a maximum + // size +public: + inline explicit GrowableBuffer(size_t _chunkSize, size_t _maxCapacity) { + auto maxChunkSize = kj::min(_chunkSize, _maxCapacity); + builder = kj::heapArrayBuilder(maxChunkSize); + chunkSize = maxChunkSize; + maxCapacity = _maxCapacity; + } + + inline size_t size() const { + return builder.size(); + } + inline bool empty() const { + return size() == 0; + } + inline size_t capacity() const { + return builder.capacity(); + } + inline size_t available() const { + return capacity() - size(); + } + + inline kj::byte* begin() KJ_LIFETIMEBOUND { + return builder.begin(); + } + inline kj::byte* end() KJ_LIFETIMEBOUND { + return builder.end(); + } + + inline kj::Array releaseAsArray() { + // TODO(perf): Avoid a copy/move by allowing Array to point to incomplete space? + if (!builder.isFull()) { + setCapacity(size()); + } + return builder.finish(); + } + + inline void returnUnused(size_t unused) { + resize(capacity() - unused); + } + + inline void resize(size_t size) { + if (size > builder.capacity()) grow(size); + builder.resize(size); + } + + inline void addChunk() { + reserve(size() + chunkSize); + } + + inline void reserve(size_t size) { + if (size > builder.capacity()) { + grow(size); + } + } + +private: + kj::ArrayBuilder builder; + size_t chunkSize; + size_t maxCapacity; + + void grow(size_t minCapacity = 0) { + JSG_REQUIRE(minCapacity <= maxCapacity, RangeError, "Memory limit exceeded"); + setCapacity(kj::min(maxCapacity, kj::max(minCapacity, capacity() == 0 ? 4 : capacity() * 2))); + } + void setCapacity(size_t newSize) { + if (builder.size() > newSize) { + builder.truncate(newSize); + } + + kj::ArrayBuilder newBuilder = kj::heapArrayBuilder(newSize); + newBuilder.addAll(kj::mv(builder)); + builder = kj::mv(newBuilder); + } +}; + void ZlibContext::initialize(int _level, int _windowBits, int _memLevel, @@ -71,10 +162,11 @@ kj::Maybe ZlibContext::getError() const { // normal statuses, not fatal break; case Z_NEED_DICT: - if (dictionary.empty()) + if (dictionary.empty()) { return constructError("Missing dictionary"_kj); - else + } else { return constructError("Bad dictionary"_kj); + } default: // something else. return constructError("Zlib error"); @@ -92,7 +184,6 @@ kj::Maybe ZlibContext::setDictionary() { case ZlibMode::DEFLATE: case ZlibMode::DEFLATERAW: err = deflateSetDictionary(&stream, dictionary.begin(), dictionary.size()); - ; break; case ZlibMode::INFLATERAW: err = inflateSetDictionary(&stream, dictionary.begin(), dictionary.size()); @@ -449,5 +540,80 @@ void ZlibUtil::ZlibStream::reset(jsg::Lock& js) { emitError(js, kj::mv(error)); } } +class ZlibSyncOperation { +public: + ZlibSyncOperation(ZlibUtil::InputSource& data, ZlibUtil::Options&& opts, ZlibMode mode) + : result(opts.chunkSize.orDefault(Z_DEFAULT_CHUNK), + opts.maxOutputLength.orDefault(Z_MAX_CHUNK)) { + initContextFromOptions(kj::mv(opts), mode); + + KJ_SWITCH_ONEOF(data) { + KJ_CASE_ONEOF(dataBuf, kj::Array) { + // TODO: Do we absolutely need to use the caller's chunk size request? Need to figure out if + // it matters in zlib + ctx.stream.next_in = dataBuf.begin(); + ctx.stream.avail_in = dataBuf.size(); + } + + KJ_CASE_ONEOF(dataStr, jsg::NonCoercible) { + ctx.stream.next_in = dataStr.value.asBytes().begin(); + ctx.stream.avail_in = dataStr.value.asBytes().size(); + } + } + ctx.initializeZlib(); + // FIXME: we need to provide the error handling + } + + void initContextFromOptions(ZlibUtil::Options&& opts, ZlibMode mode) { + ctx.setMode(mode); + ctx.initialize(opts.level.orDefault(Z_DEFAULT_LEVEL), + opts.windowBits.orDefault(Z_DEFAULT_WINDOWBITS), + opts.memLevel.orDefault(Z_DEFAULT_MEMLEVEL), opts.strategy.orDefault(Z_DEFAULT_STRATEGY), + kj::mv(opts.dictionary)); + ctx.setFlush(opts.finishFlush.orDefault(Z_FINISH)); + } + + kj::Array processBuffer() { + do { + // TODO: We respect the chunk size to match node, but it's a bit silly.. should we just + // override it for perf? + result.addChunk(); + ctx.stream.next_out = result.end(); + ctx.stream.avail_out = result.available(); + + ctx.work(); + + KJ_IF_SOME(error, ctx.getError()) { + JSG_FAIL_REQUIRE(Error, error.message); + } + + result.returnUnused(ctx.getAvailOut()); + } while (ctx.getAvailOut() == 0); + + return result.releaseAsArray(); + } + + ~ZlibSyncOperation() { + // FIXME: we need to provide the error handling + ctx.close(); + } + +private: + ZlibContext ctx; + GrowableBuffer result; +}; + +kj::Array ZlibUtil::zlibSync(InputSource data, Options options, ZlibModeValue mode) { + return ZlibSyncOperation(data, kj::mv(options), static_cast(mode)).processBuffer(); +} +void ZlibUtil::zlibWithCb( + jsg::Lock& js, InputSource data, Options options, ZlibModeValue mode, CompressCallback cb) { + try { + cb(js, kj::none, zlibSync(kj::mv(data), kj::mv(options), mode)); + } catch (kj::Exception& ex) { + auto tunneledError = jsg::tunneledErrorType(ex.getDescription()); + cb(js, tunneledError.message, kj::none); + } +} } // namespace workerd::api::node diff --git a/src/workerd/api/node/zlib-util.h b/src/workerd/api/node/zlib-util.h index ecf34a2aa37..7aa4d40f13e 100644 --- a/src/workerd/api/node/zlib-util.h +++ b/src/workerd/api/node/zlib-util.h @@ -4,14 +4,16 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. #pragma once -#include #include +#include +#include -#include "zlib.h" #include #include +#include #include +#include #include namespace workerd::api::node { @@ -69,6 +71,15 @@ enum class ZlibMode : ZlibModeValue { BROTLI_ENCODE }; +// Zlib OS_CODE. Needed only for test fixtures +#if defined(__APPLE__) +static constexpr auto ZLIB_OS_CODE = 0x13; +#elif defined(__WIN32) +static constexpr auto ZLIB_OS_CODE = 0xa; +#else // non-Apple UNIX +static constexpr auto ZLIB_OS_CODE = 0x3; +#endif + struct CompressionError { CompressionError(kj::StringPtr _message, kj::StringPtr _code, int _err) : message(kj::str(_message)), @@ -86,7 +97,7 @@ class ZlibContext final { public: ZlibContext() = default; - KJ_DISALLOW_COPY(ZlibContext); + //KJ_DISALLOW_COPY(ZlibContext); void close(); void setBuffers(kj::ArrayPtr input, @@ -131,7 +142,7 @@ class ZlibContext final { jsg::Optional> _dictionary); kj::Maybe setParams(int level, int strategy); -private: + //private: bool initializeZlib(); kj::Maybe setDictionary(); @@ -234,11 +245,44 @@ class ZlibUtil final: public jsg::Object { } }; - uint32_t crc32Sync(kj::Array data, uint32_t value); + struct Options { + jsg::Optional flush; + jsg::Optional finishFlush; + jsg::Optional chunkSize; + jsg::Optional windowBits; + jsg::Optional level; + jsg::Optional memLevel; + jsg::Optional strategy; + jsg::Optional> dictionary; + // We'll handle info on the JS side for now + jsg::Optional maxOutputLength; + + JSG_STRUCT(flush, + finishFlush, + chunkSize, + windowBits, + level, + memLevel, + strategy, + dictionary, + maxOutputLength); + JSG_MEMORY_INFO(Options) {} + }; + + using InputSource = kj::OneOf, kj::Array>; + using CompressCallback = + jsg::Function, jsg::Optional>)>; + + uint32_t crc32Sync(InputSource data, uint32_t value); + void zlibWithCb( + jsg::Lock& js, InputSource data, Options options, ZlibModeValue mode, CompressCallback cb); + kj::Array zlibSync(InputSource data, Options options, ZlibModeValue mode); JSG_RESOURCE_TYPE(ZlibUtil) { JSG_METHOD_NAMED(crc32, crc32Sync); JSG_NESTED_TYPE(ZlibStream); + JSG_METHOD(zlibSync); + JSG_METHOD(zlibWithCb); // zlib.constants (part of the API contract for node:zlib) JSG_STATIC_CONSTANT_NAMED(CONST_Z_NO_FLUSH, Z_NO_FLUSH); @@ -280,6 +324,7 @@ class ZlibUtil final: public jsg::Object { CONST_BROTLI_DECODE, static_cast(ZlibMode::BROTLI_DECODE)); JSG_STATIC_CONSTANT_NAMED( CONST_BROTLI_ENCODE, static_cast(ZlibMode::BROTLI_ENCODE)); + JSG_STATIC_CONSTANT_NAMED(CONST_Z_MIN_WINDOWBITS, Z_MIN_WINDOWBITS); JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_WINDOWBITS, Z_MAX_WINDOWBITS); JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_WINDOWBITS, Z_DEFAULT_WINDOWBITS); @@ -293,6 +338,8 @@ class ZlibUtil final: public jsg::Object { JSG_STATIC_CONSTANT_NAMED(CONST_Z_MAX_LEVEL, Z_MAX_LEVEL); JSG_STATIC_CONSTANT_NAMED(CONST_Z_DEFAULT_LEVEL, Z_DEFAULT_LEVEL); + JSG_STATIC_CONSTANT_NAMED(ZLIB_OS_CODE, ZLIB_OS_CODE); + JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_PROCESS); JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_FLUSH); JSG_STATIC_CONSTANT_NAMED(CONST_BROTLI_OPERATION_FINISH, BROTLI_OPERATION_FINISH); @@ -389,6 +436,6 @@ class ZlibUtil final: public jsg::Object { }; }; -#define EW_NODE_ZLIB_ISOLATE_TYPES api::node::ZlibUtil, api::node::ZlibUtil::ZlibStream - +#define EW_NODE_ZLIB_ISOLATE_TYPES \ + api::node::ZlibUtil, api::node::ZlibUtil::ZlibStream, api::node::ZlibUtil::Options } // namespace workerd::api::node