@naeemo/capnp 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -105,6 +105,13 @@ function encodeStructPointer(offset, dataWords, pointerCount) {
105
105
  function encodeListPointer(offset, elementSize, elementCount) {
106
106
  return (BigInt(offset < 0 ? offset + 1073741824 : offset) & BigInt(1073741823)) << BigInt(2) | BigInt(1) | BigInt(elementSize) << BigInt(32) | BigInt(elementCount) << BigInt(35);
107
107
  }
108
+ /**
109
+ * 编码 Far 指针
110
+ */
111
+ function encodeFarPointer(segment, offset, doubleFar = false) {
112
+ const offsetBits = BigInt(offset) & BigInt(536870911);
113
+ return (BigInt(segment) & BigInt(4294967295)) << BigInt(32) | offsetBits << BigInt(3) | (doubleFar ? BigInt(1) << BigInt(2) : BigInt(0)) | BigInt(2);
114
+ }
108
115
 
109
116
  //#endregion
110
117
  //#region src/core/segment.ts
@@ -1689,6 +1696,18 @@ var WebSocketTransport = class WebSocketTransport {
1689
1696
  get connected() {
1690
1697
  return this._connected;
1691
1698
  }
1699
+ getCompressionState() {
1700
+ return {
1701
+ enabled: false,
1702
+ algorithm: "none",
1703
+ bytesSent: 0,
1704
+ bytesReceived: 0,
1705
+ uncompressedBytesSent: 0,
1706
+ uncompressedBytesReceived: 0,
1707
+ messagesCompressed: 0,
1708
+ messagesDecompressed: 0
1709
+ };
1710
+ }
1692
1711
  connect(url) {
1693
1712
  this.ws = new WebSocket(url);
1694
1713
  this.ws.binaryType = this.options.binaryType ?? "arraybuffer";
@@ -1845,6 +1864,432 @@ var WebSocketTransport = class WebSocketTransport {
1845
1864
  }
1846
1865
  };
1847
1866
 
1867
+ //#endregion
1868
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/util.js
1869
+ var require_util = /* @__PURE__ */ __commonJSMin(((exports) => {
1870
+ exports.hashU32 = function hashU32(a) {
1871
+ a = a | 0;
1872
+ a = a + 2127912214 + (a << 12) | 0;
1873
+ a = a ^ -949894596 ^ a >>> 19;
1874
+ a = a + 374761393 + (a << 5) | 0;
1875
+ a = a + -744332180 ^ a << 9;
1876
+ a = a + -42973499 + (a << 3) | 0;
1877
+ return a ^ -1252372727 ^ a >>> 16 | 0;
1878
+ };
1879
+ exports.readU64 = function readU64(b, n) {
1880
+ var x = 0;
1881
+ x |= b[n++] << 0;
1882
+ x |= b[n++] << 8;
1883
+ x |= b[n++] << 16;
1884
+ x |= b[n++] << 24;
1885
+ x |= b[n++] << 32;
1886
+ x |= b[n++] << 40;
1887
+ x |= b[n++] << 48;
1888
+ x |= b[n++] << 56;
1889
+ return x;
1890
+ };
1891
+ exports.readU32 = function readU32(b, n) {
1892
+ var x = 0;
1893
+ x |= b[n++] << 0;
1894
+ x |= b[n++] << 8;
1895
+ x |= b[n++] << 16;
1896
+ x |= b[n++] << 24;
1897
+ return x;
1898
+ };
1899
+ exports.writeU32 = function writeU32(b, n, x) {
1900
+ b[n++] = x >> 0 & 255;
1901
+ b[n++] = x >> 8 & 255;
1902
+ b[n++] = x >> 16 & 255;
1903
+ b[n++] = x >> 24 & 255;
1904
+ };
1905
+ exports.imul = function imul(a, b) {
1906
+ var ah = a >>> 16;
1907
+ var al = a & 65535;
1908
+ var bh = b >>> 16;
1909
+ var bl = b & 65535;
1910
+ return al * bl + (ah * bl + al * bh << 16) | 0;
1911
+ };
1912
+ }));
1913
+
1914
+ //#endregion
1915
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/xxh32.js
1916
+ var require_xxh32 = /* @__PURE__ */ __commonJSMin(((exports) => {
1917
+ var util = require_util();
1918
+ var prime1 = 2654435761;
1919
+ var prime2 = 2246822519;
1920
+ var prime3 = 3266489917;
1921
+ var prime4 = 668265263;
1922
+ var prime5 = 374761393;
1923
+ function rotl32(x, r) {
1924
+ x = x | 0;
1925
+ r = r | 0;
1926
+ return x >>> (32 - r | 0) | x << r | 0;
1927
+ }
1928
+ function rotmul32(h, r, m) {
1929
+ h = h | 0;
1930
+ r = r | 0;
1931
+ m = m | 0;
1932
+ return util.imul(h >>> (32 - r | 0) | h << r, m) | 0;
1933
+ }
1934
+ function shiftxor32(h, s) {
1935
+ h = h | 0;
1936
+ s = s | 0;
1937
+ return h >>> s ^ h | 0;
1938
+ }
1939
+ function xxhapply(h, src, m0, s, m1) {
1940
+ return rotmul32(util.imul(src, m0) + h, s, m1);
1941
+ }
1942
+ function xxh1(h, src, index) {
1943
+ return rotmul32(h + util.imul(src[index], prime5), 11, prime1);
1944
+ }
1945
+ function xxh4(h, src, index) {
1946
+ return xxhapply(h, util.readU32(src, index), prime3, 17, prime4);
1947
+ }
1948
+ function xxh16(h, src, index) {
1949
+ return [
1950
+ xxhapply(h[0], util.readU32(src, index + 0), prime2, 13, prime1),
1951
+ xxhapply(h[1], util.readU32(src, index + 4), prime2, 13, prime1),
1952
+ xxhapply(h[2], util.readU32(src, index + 8), prime2, 13, prime1),
1953
+ xxhapply(h[3], util.readU32(src, index + 12), prime2, 13, prime1)
1954
+ ];
1955
+ }
1956
+ function xxh32(seed, src, index, len) {
1957
+ var h, l = len;
1958
+ if (len >= 16) {
1959
+ h = [
1960
+ seed + prime1 + prime2,
1961
+ seed + prime2,
1962
+ seed,
1963
+ seed - prime1
1964
+ ];
1965
+ while (len >= 16) {
1966
+ h = xxh16(h, src, index);
1967
+ index += 16;
1968
+ len -= 16;
1969
+ }
1970
+ h = rotl32(h[0], 1) + rotl32(h[1], 7) + rotl32(h[2], 12) + rotl32(h[3], 18) + l;
1971
+ } else h = seed + prime5 + len >>> 0;
1972
+ while (len >= 4) {
1973
+ h = xxh4(h, src, index);
1974
+ index += 4;
1975
+ len -= 4;
1976
+ }
1977
+ while (len > 0) {
1978
+ h = xxh1(h, src, index);
1979
+ index++;
1980
+ len--;
1981
+ }
1982
+ h = shiftxor32(util.imul(shiftxor32(util.imul(shiftxor32(h, 15), prime2), 13), prime3), 16);
1983
+ return h >>> 0;
1984
+ }
1985
+ exports.hash = xxh32;
1986
+ }));
1987
+
1988
+ //#endregion
1989
+ //#region node_modules/.pnpm/lz4js@0.2.0/node_modules/lz4js/lz4.js
1990
+ var require_lz4 = /* @__PURE__ */ __commonJSMin(((exports) => {
1991
+ var xxhash = require_xxh32();
1992
+ var util = require_util();
1993
+ var minMatch = 4;
1994
+ var minLength = 13;
1995
+ var searchLimit = 5;
1996
+ var skipTrigger = 6;
1997
+ var hashSize = 65536;
1998
+ var mlBits = 4;
1999
+ var mlMask = (1 << mlBits) - 1;
2000
+ var runMask = 15;
2001
+ var blockBuf = makeBuffer(5 << 20);
2002
+ var hashTable = makeHashTable();
2003
+ var magicNum = 407708164;
2004
+ var fdContentChksum = 4;
2005
+ var fdContentSize = 8;
2006
+ var fdBlockChksum = 16;
2007
+ var fdVersion = 64;
2008
+ var fdVersionMask = 192;
2009
+ var bsUncompressed = 2147483648;
2010
+ var bsDefault = 7;
2011
+ var bsShift = 4;
2012
+ var bsMask = 7;
2013
+ var bsMap = {
2014
+ 4: 65536,
2015
+ 5: 262144,
2016
+ 6: 1048576,
2017
+ 7: 4194304
2018
+ };
2019
+ function makeHashTable() {
2020
+ try {
2021
+ return new Uint32Array(hashSize);
2022
+ } catch (error) {
2023
+ var hashTable = new Array(hashSize);
2024
+ for (var i = 0; i < hashSize; i++) hashTable[i] = 0;
2025
+ return hashTable;
2026
+ }
2027
+ }
2028
+ function clearHashTable(table) {
2029
+ for (var i = 0; i < hashSize; i++) hashTable[i] = 0;
2030
+ }
2031
+ function makeBuffer(size) {
2032
+ try {
2033
+ return new Uint8Array(size);
2034
+ } catch (error) {
2035
+ var buf = new Array(size);
2036
+ for (var i = 0; i < size; i++) buf[i] = 0;
2037
+ return buf;
2038
+ }
2039
+ }
2040
+ function sliceArray(array, start, end) {
2041
+ if (Uint8Array.prototype.slice) return array.slice(start, end);
2042
+ else {
2043
+ var len = array.length;
2044
+ start = start | 0;
2045
+ start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
2046
+ end = end === void 0 ? len : end | 0;
2047
+ end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
2048
+ var arraySlice = new Uint8Array(end - start);
2049
+ for (var i = start, n = 0; i < end;) arraySlice[n++] = array[i++];
2050
+ return arraySlice;
2051
+ }
2052
+ }
2053
+ exports.compressBound = function compressBound(n) {
2054
+ return n + n / 255 + 16 | 0;
2055
+ };
2056
+ exports.decompressBound = function decompressBound(src) {
2057
+ var sIndex = 0;
2058
+ if (util.readU32(src, sIndex) !== magicNum) throw new Error("invalid magic number");
2059
+ sIndex += 4;
2060
+ var descriptor = src[sIndex++];
2061
+ if ((descriptor & fdVersionMask) !== fdVersion) throw new Error("incompatible descriptor version " + (descriptor & fdVersionMask));
2062
+ var useBlockSum = (descriptor & fdBlockChksum) !== 0;
2063
+ var useContentSize = (descriptor & fdContentSize) !== 0;
2064
+ var bsIdx = src[sIndex++] >> bsShift & bsMask;
2065
+ if (bsMap[bsIdx] === void 0) throw new Error("invalid block size " + bsIdx);
2066
+ var maxBlockSize = bsMap[bsIdx];
2067
+ if (useContentSize) return util.readU64(src, sIndex);
2068
+ sIndex++;
2069
+ var maxSize = 0;
2070
+ while (true) {
2071
+ var blockSize = util.readU32(src, sIndex);
2072
+ sIndex += 4;
2073
+ if (blockSize & bsUncompressed) {
2074
+ blockSize &= ~bsUncompressed;
2075
+ maxSize += blockSize;
2076
+ } else maxSize += maxBlockSize;
2077
+ if (blockSize === 0) return maxSize;
2078
+ if (useBlockSum) sIndex += 4;
2079
+ sIndex += blockSize;
2080
+ }
2081
+ };
2082
+ exports.makeBuffer = makeBuffer;
2083
+ exports.decompressBlock = function decompressBlock(src, dst, sIndex, sLength, dIndex) {
2084
+ var mLength, mOffset, sEnd = sIndex + sLength, n, i;
2085
+ while (sIndex < sEnd) {
2086
+ var token = src[sIndex++];
2087
+ var literalCount = token >> 4;
2088
+ if (literalCount > 0) {
2089
+ if (literalCount === 15) while (true) {
2090
+ literalCount += src[sIndex];
2091
+ if (src[sIndex++] !== 255) break;
2092
+ }
2093
+ for (n = sIndex + literalCount; sIndex < n;) dst[dIndex++] = src[sIndex++];
2094
+ }
2095
+ if (sIndex >= sEnd) break;
2096
+ mLength = token & 15;
2097
+ mOffset = src[sIndex++] | src[sIndex++] << 8;
2098
+ if (mLength === 15) while (true) {
2099
+ mLength += src[sIndex];
2100
+ if (src[sIndex++] !== 255) break;
2101
+ }
2102
+ mLength += minMatch;
2103
+ for (i = dIndex - mOffset, n = i + mLength; i < n;) dst[dIndex++] = dst[i++] | 0;
2104
+ }
2105
+ return dIndex;
2106
+ };
2107
+ exports.compressBlock = function compressBlock(src, dst, sIndex, sLength, hashTable) {
2108
+ var mIndex, mAnchor, mLength, mOffset, mStep;
2109
+ var literalCount, dIndex = 0, sEnd = sLength + sIndex, n;
2110
+ mAnchor = sIndex;
2111
+ if (sLength >= minLength) {
2112
+ var searchMatchCount = (1 << skipTrigger) + 3;
2113
+ while (sIndex + minMatch < sEnd - searchLimit) {
2114
+ var seq = util.readU32(src, sIndex);
2115
+ var hash = util.hashU32(seq) >>> 0;
2116
+ hash = (hash >> 16 ^ hash) >>> 0 & 65535;
2117
+ mIndex = hashTable[hash] - 1;
2118
+ hashTable[hash] = sIndex + 1;
2119
+ if (mIndex < 0 || sIndex - mIndex >>> 16 > 0 || util.readU32(src, mIndex) !== seq) {
2120
+ mStep = searchMatchCount++ >> skipTrigger;
2121
+ sIndex += mStep;
2122
+ continue;
2123
+ }
2124
+ searchMatchCount = (1 << skipTrigger) + 3;
2125
+ literalCount = sIndex - mAnchor;
2126
+ mOffset = sIndex - mIndex;
2127
+ sIndex += minMatch;
2128
+ mIndex += minMatch;
2129
+ mLength = sIndex;
2130
+ while (sIndex < sEnd - searchLimit && src[sIndex] === src[mIndex]) {
2131
+ sIndex++;
2132
+ mIndex++;
2133
+ }
2134
+ mLength = sIndex - mLength;
2135
+ var token = mLength < mlMask ? mLength : mlMask;
2136
+ if (literalCount >= runMask) {
2137
+ dst[dIndex++] = (runMask << mlBits) + token;
2138
+ for (n = literalCount - runMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2139
+ dst[dIndex++] = n;
2140
+ } else dst[dIndex++] = (literalCount << mlBits) + token;
2141
+ for (var i = 0; i < literalCount; i++) dst[dIndex++] = src[mAnchor + i];
2142
+ dst[dIndex++] = mOffset;
2143
+ dst[dIndex++] = mOffset >> 8;
2144
+ if (mLength >= mlMask) {
2145
+ for (n = mLength - mlMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2146
+ dst[dIndex++] = n;
2147
+ }
2148
+ mAnchor = sIndex;
2149
+ }
2150
+ }
2151
+ if (mAnchor === 0) return 0;
2152
+ literalCount = sEnd - mAnchor;
2153
+ if (literalCount >= runMask) {
2154
+ dst[dIndex++] = runMask << mlBits;
2155
+ for (n = literalCount - runMask; n >= 255; n -= 255) dst[dIndex++] = 255;
2156
+ dst[dIndex++] = n;
2157
+ } else dst[dIndex++] = literalCount << mlBits;
2158
+ sIndex = mAnchor;
2159
+ while (sIndex < sEnd) dst[dIndex++] = src[sIndex++];
2160
+ return dIndex;
2161
+ };
2162
+ exports.decompressFrame = function decompressFrame(src, dst) {
2163
+ var useBlockSum, useContentSum, useContentSize, descriptor;
2164
+ var sIndex = 0;
2165
+ var dIndex = 0;
2166
+ if (util.readU32(src, sIndex) !== magicNum) throw new Error("invalid magic number");
2167
+ sIndex += 4;
2168
+ descriptor = src[sIndex++];
2169
+ if ((descriptor & fdVersionMask) !== fdVersion) throw new Error("incompatible descriptor version");
2170
+ useBlockSum = (descriptor & fdBlockChksum) !== 0;
2171
+ useContentSum = (descriptor & fdContentChksum) !== 0;
2172
+ useContentSize = (descriptor & fdContentSize) !== 0;
2173
+ if (bsMap[src[sIndex++] >> bsShift & bsMask] === void 0) throw new Error("invalid block size");
2174
+ if (useContentSize) sIndex += 8;
2175
+ sIndex++;
2176
+ while (true) {
2177
+ var compSize = util.readU32(src, sIndex);
2178
+ sIndex += 4;
2179
+ if (compSize === 0) break;
2180
+ if (useBlockSum) sIndex += 4;
2181
+ if ((compSize & bsUncompressed) !== 0) {
2182
+ compSize &= ~bsUncompressed;
2183
+ for (var j = 0; j < compSize; j++) dst[dIndex++] = src[sIndex++];
2184
+ } else {
2185
+ dIndex = exports.decompressBlock(src, dst, sIndex, compSize, dIndex);
2186
+ sIndex += compSize;
2187
+ }
2188
+ }
2189
+ if (useContentSum) sIndex += 4;
2190
+ return dIndex;
2191
+ };
2192
+ exports.compressFrame = function compressFrame(src, dst) {
2193
+ var dIndex = 0;
2194
+ util.writeU32(dst, dIndex, magicNum);
2195
+ dIndex += 4;
2196
+ dst[dIndex++] = fdVersion;
2197
+ dst[dIndex++] = bsDefault << bsShift;
2198
+ dst[dIndex] = xxhash.hash(0, dst, 4, dIndex - 4) >> 8;
2199
+ dIndex++;
2200
+ var maxBlockSize = bsMap[bsDefault];
2201
+ var remaining = src.length;
2202
+ var sIndex = 0;
2203
+ clearHashTable(hashTable);
2204
+ while (remaining > 0) {
2205
+ var compSize = 0;
2206
+ var blockSize = remaining > maxBlockSize ? maxBlockSize : remaining;
2207
+ compSize = exports.compressBlock(src, blockBuf, sIndex, blockSize, hashTable);
2208
+ if (compSize > blockSize || compSize === 0) {
2209
+ util.writeU32(dst, dIndex, 2147483648 | blockSize);
2210
+ dIndex += 4;
2211
+ for (var z = sIndex + blockSize; sIndex < z;) dst[dIndex++] = src[sIndex++];
2212
+ remaining -= blockSize;
2213
+ } else {
2214
+ util.writeU32(dst, dIndex, compSize);
2215
+ dIndex += 4;
2216
+ for (var j = 0; j < compSize;) dst[dIndex++] = blockBuf[j++];
2217
+ sIndex += blockSize;
2218
+ remaining -= blockSize;
2219
+ }
2220
+ }
2221
+ util.writeU32(dst, dIndex, 0);
2222
+ dIndex += 4;
2223
+ return dIndex;
2224
+ };
2225
+ exports.decompress = function decompress(src, maxSize) {
2226
+ var dst, size;
2227
+ if (maxSize === void 0) maxSize = exports.decompressBound(src);
2228
+ dst = exports.makeBuffer(maxSize);
2229
+ size = exports.decompressFrame(src, dst);
2230
+ if (size !== maxSize) dst = sliceArray(dst, 0, size);
2231
+ return dst;
2232
+ };
2233
+ exports.compress = function compress(src, maxSize) {
2234
+ var dst, size;
2235
+ if (maxSize === void 0) maxSize = exports.compressBound(src.length);
2236
+ dst = exports.makeBuffer(maxSize);
2237
+ size = exports.compressFrame(src, dst);
2238
+ if (size !== maxSize) dst = sliceArray(dst, 0, size);
2239
+ return dst;
2240
+ };
2241
+ }));
2242
+
2243
+ //#endregion
2244
+ //#region src/compression/lz4.ts
2245
+ var import_lz4 = require_lz4();
2246
+ /**
2247
+ * Default compression options
2248
+ */
2249
+ const DEFAULT_COMPRESSION_OPTIONS = {
2250
+ threshold: 1024,
2251
+ acceleration: 1,
2252
+ level: 0
2253
+ };
2254
+ /**
2255
+ * Compress data using LZ4
2256
+ * @param data - Data to compress
2257
+ * @param options - Compression options
2258
+ * @returns Compressed data, or null if compression failed or not beneficial
2259
+ */
2260
+ function compress(data, options = {}) {
2261
+ const opts = {
2262
+ ...DEFAULT_COMPRESSION_OPTIONS,
2263
+ ...options
2264
+ };
2265
+ if (data.length < opts.threshold) return null;
2266
+ try {
2267
+ const compressed = (0, import_lz4.compress)(Array.from(data));
2268
+ if (compressed.length >= data.length) return null;
2269
+ return new Uint8Array(compressed);
2270
+ } catch (_error) {
2271
+ return null;
2272
+ }
2273
+ }
2274
+ /**
2275
+ * Decompress LZ4 compressed data
2276
+ * @param data - Compressed data
2277
+ * @param originalSize - Original uncompressed size
2278
+ * @returns Decompressed data, or null if decompression failed
2279
+ */
2280
+ function decompress(data, originalSize) {
2281
+ try {
2282
+ const decompressed = (0, import_lz4.decompress)(Array.from(data), originalSize);
2283
+ return new Uint8Array(decompressed);
2284
+ } catch (_error) {
2285
+ return null;
2286
+ }
2287
+ }
2288
+ /**
2289
+ * Alias for decompress - same function, different name for API compatibility
2290
+ */
2291
+ const uncompress = decompress;
2292
+
1848
2293
  //#endregion
1849
2294
  //#region src/rpc/tcp-transport.ts
1850
2295
  /**
@@ -1853,11 +2298,20 @@ var WebSocketTransport = class WebSocketTransport {
1853
2298
  * Implements RpcTransport over raw TCP socket for C++ interop testing.
1854
2299
  * This allows direct communication with the official Cap'n Proto C++ implementation.
1855
2300
  */
2301
+ const FRAME_MAGIC = 1280980035;
2302
+ const ALGORITHM_LZ4 = 1;
2303
+ const CAPABILITY_VERSION = 1;
2304
+ const DEFAULT_COMPRESSION_THRESHOLD = 256;
1856
2305
  /**
1857
2306
  * TCP Transport for Cap'n Proto RPC
1858
2307
  *
1859
2308
  * Uses length-prefixed binary message framing compatible with Cap'n Proto C++ implementation.
1860
2309
  * Format: [4 bytes: message length (little-endian)] [N bytes: message data]
2310
+ *
2311
+ * With compression support:
2312
+ * - Connection establishment includes capability negotiation
2313
+ * - Messages larger than threshold are automatically compressed
2314
+ * - Frame header indicates compression algorithm used
1861
2315
  */
1862
2316
  var TcpTransport = class TcpTransport {
1863
2317
  socket = null;
@@ -1867,12 +2321,35 @@ var TcpTransport = class TcpTransport {
1867
2321
  pendingBuffer = Buffer.alloc(0);
1868
2322
  pendingLength = 0;
1869
2323
  hasPendingLength = false;
2324
+ compressionConfig;
2325
+ localSupportsLz4 = false;
2326
+ remoteSupportsLz4 = false;
2327
+ compressionEnabled = false;
2328
+ compressionState;
2329
+ negotiationComplete = false;
1870
2330
  onClose;
1871
2331
  onError;
1872
2332
  constructor(host, port, options = {}) {
1873
2333
  this.host = host;
1874
2334
  this.port = port;
1875
2335
  this.options = options;
2336
+ this.compressionConfig = {
2337
+ enabled: options.compression?.enabled ?? true,
2338
+ algorithm: options.compression?.algorithm ?? "lz4",
2339
+ thresholdBytes: options.compression?.thresholdBytes ?? DEFAULT_COMPRESSION_THRESHOLD,
2340
+ level: options.compression?.level ?? 1
2341
+ };
2342
+ this.localSupportsLz4 = this.compressionConfig.enabled && this.compressionConfig.algorithm === "lz4";
2343
+ this.compressionState = {
2344
+ enabled: false,
2345
+ algorithm: "none",
2346
+ bytesSent: 0,
2347
+ bytesReceived: 0,
2348
+ uncompressedBytesSent: 0,
2349
+ uncompressedBytesReceived: 0,
2350
+ messagesCompressed: 0,
2351
+ messagesDecompressed: 0
2352
+ };
1876
2353
  }
1877
2354
  static async connect(host, port, options) {
1878
2355
  const transport = new TcpTransport(host, port, options);
@@ -1887,6 +2364,9 @@ var TcpTransport = class TcpTransport {
1887
2364
  get connected() {
1888
2365
  return this._connected && this.socket?.readyState === "open";
1889
2366
  }
2367
+ getCompressionState() {
2368
+ return { ...this.compressionState };
2369
+ }
1890
2370
  connect() {
1891
2371
  return new Promise((resolve, reject) => {
1892
2372
  const timeout = setTimeout(() => {
@@ -1894,10 +2374,15 @@ var TcpTransport = class TcpTransport {
1894
2374
  reject(/* @__PURE__ */ new Error("Connection timeout"));
1895
2375
  }, this.options.connectTimeoutMs ?? 1e4);
1896
2376
  this.socket = new node_net.Socket();
1897
- this.socket.on("connect", () => {
2377
+ this.socket.on("connect", async () => {
1898
2378
  clearTimeout(timeout);
1899
2379
  this._connected = true;
1900
- resolve();
2380
+ try {
2381
+ await this.performCapabilityNegotiation();
2382
+ resolve();
2383
+ } catch (err) {
2384
+ reject(err instanceof Error ? err : new Error(String(err)));
2385
+ }
1901
2386
  });
1902
2387
  this.socket.on("data", (data) => {
1903
2388
  this.handleData(data);
@@ -1939,6 +2424,85 @@ var TcpTransport = class TcpTransport {
1939
2424
  socket.on("end", () => {
1940
2425
  this._connected = false;
1941
2426
  });
2427
+ if (this._connected) this.performCapabilityNegotiation().catch((err) => {
2428
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2429
+ });
2430
+ }
2431
+ /**
2432
+ * Perform capability negotiation with the remote peer
2433
+ */
2434
+ async performCapabilityNegotiation() {
2435
+ if (!this.socket) throw new Error("Socket not connected");
2436
+ if (this.host === "" && this.port === 0) {
2437
+ this.remoteSupportsLz4 = (await this.readCapabilityNegotiation()).supportsLz4;
2438
+ await this.sendCapabilityNegotiation();
2439
+ } else {
2440
+ await this.sendCapabilityNegotiation();
2441
+ this.remoteSupportsLz4 = (await this.readCapabilityNegotiation()).supportsLz4;
2442
+ }
2443
+ this.compressionEnabled = this.localSupportsLz4 && this.remoteSupportsLz4;
2444
+ this.compressionState.enabled = this.compressionEnabled;
2445
+ this.compressionState.algorithm = this.compressionEnabled ? "lz4" : "none";
2446
+ this.negotiationComplete = true;
2447
+ }
2448
+ /**
2449
+ * Send capability negotiation message
2450
+ */
2451
+ async sendCapabilityNegotiation() {
2452
+ const caps = {
2453
+ type: "capabilities",
2454
+ supportsLz4: this.localSupportsLz4,
2455
+ version: CAPABILITY_VERSION
2456
+ };
2457
+ const data = Buffer.from(JSON.stringify(caps), "utf-8");
2458
+ const frame = Buffer.allocUnsafe(4 + data.length);
2459
+ frame.writeUInt32LE(data.length, 0);
2460
+ frame.set(data, 4);
2461
+ return new Promise((resolve, reject) => {
2462
+ this.socket.write(frame, (err) => {
2463
+ if (err) reject(err);
2464
+ else resolve();
2465
+ });
2466
+ });
2467
+ }
2468
+ /**
2469
+ * Read capability negotiation message
2470
+ */
2471
+ async readCapabilityNegotiation() {
2472
+ return new Promise((resolve, reject) => {
2473
+ const timeout = setTimeout(() => {
2474
+ reject(/* @__PURE__ */ new Error("Capability negotiation timeout"));
2475
+ }, 5e3);
2476
+ const checkBuffer = () => {
2477
+ if (this.pendingBuffer.length < 4) {
2478
+ this.socket.listenerCount("data");
2479
+ const dataHandler = () => {
2480
+ this.socket.off("data", dataHandler);
2481
+ checkBuffer();
2482
+ };
2483
+ this.socket.once("data", dataHandler);
2484
+ return;
2485
+ }
2486
+ const length = this.pendingBuffer.readUInt32LE(0);
2487
+ if (this.pendingBuffer.length < 4 + length) {
2488
+ const dataHandler = () => {
2489
+ this.socket.off("data", dataHandler);
2490
+ checkBuffer();
2491
+ };
2492
+ this.socket.once("data", dataHandler);
2493
+ return;
2494
+ }
2495
+ clearTimeout(timeout);
2496
+ const capsData = this.pendingBuffer.subarray(4, 4 + length);
2497
+ this.pendingBuffer = this.pendingBuffer.subarray(4 + length);
2498
+ try {
2499
+ resolve(JSON.parse(capsData.toString("utf-8")));
2500
+ } catch (err) {
2501
+ reject(/* @__PURE__ */ new Error(`Failed to parse capability negotiation: ${err}`));
2502
+ }
2503
+ };
2504
+ checkBuffer();
2505
+ });
1942
2506
  }
1943
2507
  handleData(data) {
1944
2508
  this.pendingBuffer = Buffer.concat([this.pendingBuffer, data]);
@@ -1954,14 +2518,50 @@ var TcpTransport = class TcpTransport {
1954
2518
  }
1955
2519
  const totalLength = 4 + this.pendingLength;
1956
2520
  if (this.pendingBuffer.length < totalLength) break;
1957
- const messageData = this.pendingBuffer.subarray(4, totalLength);
1958
- this.pendingBuffer = this.pendingBuffer.subarray(totalLength);
1959
- this.hasPendingLength = false;
1960
- try {
1961
- const message = deserializeRpcMessage(new Uint8Array(messageData));
1962
- this.handleRpcMessage(message);
1963
- } catch (err) {
1964
- this.onError?.(err instanceof Error ? err : new Error(String(err)));
2521
+ if (this.pendingBuffer.readUInt32LE(4) === FRAME_MAGIC && this.negotiationComplete) {
2522
+ if (this.pendingLength < 16) {
2523
+ this.onError?.(/* @__PURE__ */ new Error("Invalid compressed frame"));
2524
+ this.close(/* @__PURE__ */ new Error("Invalid compressed frame"));
2525
+ return;
2526
+ }
2527
+ const headerOffset = 4;
2528
+ const originalSize = this.pendingBuffer.readUInt32LE(headerOffset + 4);
2529
+ const compressedSize = this.pendingBuffer.readUInt32LE(headerOffset + 8);
2530
+ const algorithm = this.pendingBuffer.readUInt32LE(headerOffset + 12);
2531
+ const frameTotalLength = 20 + compressedSize;
2532
+ if (this.pendingBuffer.length < frameTotalLength) break;
2533
+ const compressedData = this.pendingBuffer.subarray(headerOffset + 16, frameTotalLength);
2534
+ this.pendingBuffer = this.pendingBuffer.subarray(frameTotalLength);
2535
+ this.hasPendingLength = false;
2536
+ try {
2537
+ let messageData;
2538
+ if (algorithm === ALGORITHM_LZ4) {
2539
+ const decompressed = decompress(compressedData, originalSize);
2540
+ if (!decompressed) throw new Error("LZ4 decompression failed");
2541
+ messageData = decompressed;
2542
+ this.compressionState.messagesDecompressed++;
2543
+ this.compressionState.bytesReceived += compressedData.length;
2544
+ this.compressionState.uncompressedBytesReceived += messageData.length;
2545
+ } else {
2546
+ messageData = new Uint8Array(compressedData);
2547
+ this.compressionState.bytesReceived += compressedData.length;
2548
+ this.compressionState.uncompressedBytesReceived += compressedData.length;
2549
+ }
2550
+ const message = deserializeRpcMessage(messageData);
2551
+ this.handleRpcMessage(message);
2552
+ } catch (err) {
2553
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2554
+ }
2555
+ } else {
2556
+ const messageData = this.pendingBuffer.subarray(4, totalLength);
2557
+ this.pendingBuffer = this.pendingBuffer.subarray(totalLength);
2558
+ this.hasPendingLength = false;
2559
+ try {
2560
+ const message = deserializeRpcMessage(new Uint8Array(messageData));
2561
+ this.handleRpcMessage(message);
2562
+ } catch (err) {
2563
+ this.onError?.(err instanceof Error ? err : new Error(String(err)));
2564
+ }
1965
2565
  }
1966
2566
  }
1967
2567
  }
@@ -1973,10 +2573,31 @@ var TcpTransport = class TcpTransport {
1973
2573
  }
1974
2574
  async send(message) {
1975
2575
  if (!this.socket || this.socket.readyState !== "open") throw new Error("Socket not connected");
2576
+ if (!this.negotiationComplete) throw new Error("Capability negotiation not complete");
1976
2577
  const data = serializeRpcMessage(message);
1977
- const frame = Buffer.allocUnsafe(4 + data.length);
1978
- frame.writeUInt32LE(data.length, 0);
1979
- frame.set(data, 4);
2578
+ const shouldCompress = this.compressionEnabled && data.length >= this.compressionConfig.thresholdBytes;
2579
+ let frame;
2580
+ if (shouldCompress) {
2581
+ const compressed = compress(data, { threshold: 0 });
2582
+ if (!compressed) throw new Error("Compression failed");
2583
+ const frameLength = 16 + compressed.length;
2584
+ frame = Buffer.allocUnsafe(4 + frameLength);
2585
+ frame.writeUInt32LE(frameLength, 0);
2586
+ frame.writeUInt32LE(FRAME_MAGIC, 4);
2587
+ frame.writeUInt32LE(data.length, 8);
2588
+ frame.writeUInt32LE(compressed.length, 12);
2589
+ frame.writeUInt32LE(ALGORITHM_LZ4, 16);
2590
+ frame.set(compressed, 20);
2591
+ this.compressionState.bytesSent += compressed.length;
2592
+ this.compressionState.uncompressedBytesSent += data.length;
2593
+ this.compressionState.messagesCompressed++;
2594
+ } else {
2595
+ frame = Buffer.allocUnsafe(4 + data.length);
2596
+ frame.writeUInt32LE(data.length, 0);
2597
+ frame.set(data, 4);
2598
+ this.compressionState.bytesSent += data.length;
2599
+ this.compressionState.uncompressedBytesSent += data.length;
2600
+ }
1980
2601
  return new Promise((resolve, reject) => {
1981
2602
  this.socket.write(frame, (err) => {
1982
2603
  if (err) reject(err);
@@ -2044,6 +2665,18 @@ var EzRpcTransport = class EzRpcTransport {
2044
2665
  get connected() {
2045
2666
  return this._connected && this.socket !== null && !this.socket.destroyed;
2046
2667
  }
2668
+ getCompressionState() {
2669
+ return {
2670
+ enabled: false,
2671
+ algorithm: "none",
2672
+ bytesSent: 0,
2673
+ bytesReceived: 0,
2674
+ uncompressedBytesSent: 0,
2675
+ uncompressedBytesReceived: 0,
2676
+ messagesCompressed: 0,
2677
+ messagesDecompressed: 0
2678
+ };
2679
+ }
2047
2680
  doConnect() {
2048
2681
  return new Promise((resolve, reject) => {
2049
2682
  const timeout = setTimeout(() => {
@@ -9818,6 +10451,131 @@ var import_sender = /* @__PURE__ */ __toESM(require_sender(), 1);
9818
10451
  var import_websocket = /* @__PURE__ */ __toESM(require_websocket(), 1);
9819
10452
  var import_websocket_server = /* @__PURE__ */ __toESM(require_websocket_server(), 1);
9820
10453
 
10454
+ //#endregion
10455
+ //#region src/compression/frame.ts
10456
+ /**
10457
+ * LZ4 Frame Format Module
10458
+ *
10459
+ * Frame format: Magic(4) + Flags(1) + Length(4) + Payload
10460
+ * - Magic: 0x4C5A3401 (LZ4\0\x01)
10461
+ * - Flags: compression options and metadata
10462
+ * - Length: uncompressed data length (4 bytes, little-endian)
10463
+ * - Payload: compressed or uncompressed data
10464
+ */
10465
+ /**
10466
+ * Frame format constants
10467
+ */
10468
+ const LZ4_FRAME_MAGIC = 1280979969;
10469
+ const LZ4_FRAME_HEADER_SIZE = 9;
10470
+ /**
10471
+ * Frame flags
10472
+ */
10473
+ let FrameFlags = /* @__PURE__ */ function(FrameFlags) {
10474
+ /** Data is compressed */
10475
+ FrameFlags[FrameFlags["COMPRESSED"] = 1] = "COMPRESSED";
10476
+ /** Reserved for future use */
10477
+ FrameFlags[FrameFlags["RESERVED_1"] = 2] = "RESERVED_1";
10478
+ /** Reserved for future use */
10479
+ FrameFlags[FrameFlags["RESERVED_2"] = 4] = "RESERVED_2";
10480
+ /** Reserved for future use */
10481
+ FrameFlags[FrameFlags["RESERVED_3"] = 8] = "RESERVED_3";
10482
+ /** Reserved for future use */
10483
+ FrameFlags[FrameFlags["RESERVED_4"] = 16] = "RESERVED_4";
10484
+ /** Reserved for future use */
10485
+ FrameFlags[FrameFlags["RESERVED_5"] = 32] = "RESERVED_5";
10486
+ /** Reserved for future use */
10487
+ FrameFlags[FrameFlags["RESERVED_6"] = 64] = "RESERVED_6";
10488
+ /** Reserved for future use */
10489
+ FrameFlags[FrameFlags["RESERVED_7"] = 128] = "RESERVED_7";
10490
+ return FrameFlags;
10491
+ }({});
10492
+ /**
10493
+ * Check if data has LZ4 frame magic header
10494
+ */
10495
+ function hasFrameMagic(data) {
10496
+ if (data.length < 4) return false;
10497
+ return new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(0, true) === LZ4_FRAME_MAGIC;
10498
+ }
10499
+ /**
10500
+ * Parse frame header from data
10501
+ * @returns FrameHeader if valid, null otherwise
10502
+ */
10503
+ function parseFrameHeader(data) {
10504
+ if (data.length < LZ4_FRAME_HEADER_SIZE) return null;
10505
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
10506
+ const magic = view.getUint32(0, true);
10507
+ if (magic !== LZ4_FRAME_MAGIC) return null;
10508
+ return {
10509
+ magic,
10510
+ flags: view.getUint8(4),
10511
+ length: view.getUint32(5, true)
10512
+ };
10513
+ }
10514
+ /**
10515
+ * Decompress framed data
10516
+ * @param data - Framed data (with or without frame header)
10517
+ * @param decompressFn - Decompression function for compressed payloads
10518
+ * @returns Decompressed data, or null if invalid
10519
+ */
10520
+ function decompressFrame(data, decompressFn) {
10521
+ const header = parseFrameHeader(data);
10522
+ if (!header) return data;
10523
+ const payloadOffset = LZ4_FRAME_HEADER_SIZE;
10524
+ if (data.length - payloadOffset < 0) return null;
10525
+ const payload = data.subarray(payloadOffset);
10526
+ if (header.flags & FrameFlags.COMPRESSED) return decompressFn(payload, header.length);
10527
+ return payload;
10528
+ }
10529
+
10530
+ //#endregion
10531
+ //#region src/compression/index.ts
10532
+ /**
10533
+ * Create default compression config
10534
+ */
10535
+ function createCompressionConfig(config) {
10536
+ return {
10537
+ enabled: false,
10538
+ algorithm: "lz4",
10539
+ threshold: 1024,
10540
+ level: 0,
10541
+ highCompression: false,
10542
+ ...config
10543
+ };
10544
+ }
10545
+ /**
10546
+ * Create empty compression stats
10547
+ */
10548
+ function createCompressionStats() {
10549
+ return {
10550
+ messagesCompressed: 0,
10551
+ messagesDecompressed: 0,
10552
+ bytesOriginal: 0,
10553
+ bytesCompressed: 0,
10554
+ compressionRatio: 1,
10555
+ savingsPercent: 0
10556
+ };
10557
+ }
10558
+ /**
10559
+ * Check if data is a compression frame (has LZ4 magic)
10560
+ * Works with both Buffer and Uint8Array
10561
+ */
10562
+ function isCompressionFrame(data) {
10563
+ return hasFrameMagic(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
10564
+ }
10565
+ /**
10566
+ * Try to decompress, return original if not compressed or decompression fails
10567
+ * Works with both Buffer and Uint8Array
10568
+ * Assumes data has LZ4 frame header (created by compressFrame)
10569
+ */
10570
+ function tryDecompress(data) {
10571
+ const uint8Data = data instanceof Buffer ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : data;
10572
+ return decompressFrame(uint8Data, uncompress) ?? uint8Data;
10573
+ }
10574
+ /**
10575
+ * Default compression configuration
10576
+ */
10577
+ const DEFAULT_COMPRESSION_CONFIG = createCompressionConfig();
10578
+
9821
10579
  //#endregion
9822
10580
  //#region src/proxy/websocket-proxy.ts
9823
10581
  /**
@@ -9826,6 +10584,27 @@ var import_websocket_server = /* @__PURE__ */ __toESM(require_websocket_server()
9826
10584
  * Allows browsers to connect to native Cap'n Proto services (C++, etc.)
9827
10585
  * via WebSocket. Handles the protocol bridging between WebSocket (browser)
9828
10586
  * and raw TCP (Cap'n Proto services).
10587
+ *
10588
+ * Features:
10589
+ * - Independent compression configuration for WebSocket and TCP sides
10590
+ * - Automatic compression/decompression bridging
10591
+ * - Support for different compression settings on each side
10592
+ * - Backward compatibility with non-compressed peers
10593
+ */
10594
+ /**
10595
+ * Convert Buffer to Uint8Array without copying if possible
10596
+ */
10597
+ function bufferToUint8Array(buffer) {
10598
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
10599
+ }
10600
+ /**
10601
+ * Convert Uint8Array to Buffer without copying if possible
10602
+ */
10603
+ function uint8ArrayToBuffer(arr) {
10604
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
10605
+ }
10606
+ /**
10607
+ * Represents a single proxy connection between WebSocket client and TCP server
9829
10608
  */
9830
10609
  var ProxyConnection = class extends node_events.EventEmitter {
9831
10610
  ws;
@@ -9833,16 +10612,22 @@ var ProxyConnection = class extends node_events.EventEmitter {
9833
10612
  stats;
9834
10613
  options;
9835
10614
  closed = false;
10615
+ wsCompressionConfig;
10616
+ tcpCompressionConfig;
9836
10617
  constructor(ws, options) {
9837
10618
  super();
9838
10619
  this.ws = ws;
9839
10620
  this.options = options;
10621
+ this.wsCompressionConfig = createCompressionConfig(options.compression?.ws);
10622
+ this.tcpCompressionConfig = createCompressionConfig(options.compression?.tcp);
9840
10623
  this.stats = {
9841
10624
  wsMessagesIn: 0,
9842
10625
  wsMessagesOut: 0,
9843
10626
  tcpBytesIn: 0,
9844
10627
  tcpBytesOut: 0,
9845
- connectedAt: /* @__PURE__ */ new Date()
10628
+ connectedAt: /* @__PURE__ */ new Date(),
10629
+ wsCompression: createCompressionStats(),
10630
+ tcpCompression: createCompressionStats()
9846
10631
  };
9847
10632
  this.setupWebSocket();
9848
10633
  }
@@ -9870,6 +10655,7 @@ var ProxyConnection = class extends node_events.EventEmitter {
9870
10655
  });
9871
10656
  this.tcpSocket.on("connect", () => {
9872
10657
  this.log("Connected to target TCP service");
10658
+ this.log(`Compression - WS: ${this.wsCompressionConfig.enabled ? "enabled" : "disabled"}, TCP: ${this.tcpCompressionConfig.enabled ? "enabled" : "disabled"}`);
9873
10659
  this.emit("connected");
9874
10660
  });
9875
10661
  this.tcpSocket.on("data", (data) => {
@@ -9889,29 +10675,78 @@ var ProxyConnection = class extends node_events.EventEmitter {
9889
10675
  this.close();
9890
10676
  });
9891
10677
  }
10678
+ /**
10679
+ * Handle incoming message from WebSocket client
10680
+ * - Decompress if compressed (from browser)
10681
+ * - Re-compress for TCP side if TCP compression is enabled
10682
+ * - Forward to TCP server
10683
+ */
9892
10684
  handleWebSocketMessage(data) {
9893
10685
  if (this.closed) return;
9894
10686
  const buffer = Buffer.isBuffer(data) ? data : Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data);
9895
10687
  const maxSize = this.options.maxMessageSize ?? 16 * 1024 * 1024;
9896
- if (buffer.length > maxSize) {
9897
- this.log(`Message too large: ${buffer.length} bytes`);
10688
+ const uint8Data = bufferToUint8Array(buffer);
10689
+ const frameInfo = parseFrameHeader(uint8Data);
10690
+ const uncompressedSize = frameInfo ? frameInfo.length : buffer.length;
10691
+ if (uncompressedSize > maxSize) {
10692
+ this.log(`Message too large: ${uncompressedSize} bytes (max: ${maxSize})`);
9898
10693
  this.close();
9899
10694
  return;
9900
10695
  }
9901
10696
  this.stats.wsMessagesIn++;
9902
- this.stats.tcpBytesOut += buffer.length;
10697
+ let decompressedData;
10698
+ if (hasFrameMagic(uint8Data)) {
10699
+ const decompressed = decompressFrame(uint8Data, uncompress);
10700
+ if (decompressed !== null) {
10701
+ decompressedData = decompressed;
10702
+ this.log(`Decompressed WS message: ${buffer.length} -> ${decompressedData.length} bytes`);
10703
+ } else decompressedData = uint8Data;
10704
+ } else decompressedData = uint8Data;
10705
+ let dataToSend = decompressedData;
10706
+ if (this.tcpCompressionConfig.enabled) {
10707
+ const compressed = compress(decompressedData, { threshold: this.tcpCompressionConfig.threshold });
10708
+ if (compressed !== null) {
10709
+ dataToSend = compressed;
10710
+ this.log(`Compressed for TCP: ${decompressedData.length} -> ${dataToSend.length} bytes`);
10711
+ }
10712
+ }
10713
+ this.stats.tcpBytesOut += uint8ArrayToBuffer(dataToSend).length;
9903
10714
  if (this.tcpSocket?.writable) {
9904
- this.tcpSocket.write(buffer);
9905
- this.log(`Forwarded ${buffer.length} bytes to TCP`);
10715
+ const bufferToSend = uint8ArrayToBuffer(dataToSend);
10716
+ this.tcpSocket.write(bufferToSend);
10717
+ this.log(`Forwarded ${dataToSend.length} bytes to TCP (original: ${decompressedData.length})`);
9906
10718
  }
9907
10719
  }
10720
+ /**
10721
+ * Handle incoming data from TCP server
10722
+ * - Decompress if compressed (from native Cap'n Proto service)
10723
+ * - Re-compress for WebSocket side if WS compression is enabled
10724
+ * - Forward to WebSocket client
10725
+ */
9908
10726
  handleTcpData(data) {
9909
10727
  if (this.closed) return;
9910
10728
  this.stats.tcpBytesIn += data.length;
10729
+ const uint8Data = bufferToUint8Array(data);
10730
+ let decompressedData;
10731
+ if (hasFrameMagic(uint8Data)) {
10732
+ const decompressed = decompressFrame(uint8Data, uncompress);
10733
+ if (decompressed !== null) {
10734
+ decompressedData = decompressed;
10735
+ this.log(`Decompressed TCP message: ${data.length} -> ${decompressedData.length} bytes`);
10736
+ } else decompressedData = uint8Data;
10737
+ } else decompressedData = uint8Data;
10738
+ let dataToSend = decompressedData;
10739
+ if (this.wsCompressionConfig.enabled) {
10740
+ const compressed = compress(decompressedData, { threshold: this.wsCompressionConfig.threshold });
10741
+ if (compressed !== null) {
10742
+ dataToSend = compressed;
10743
+ this.log(`Compressed for WS: ${decompressedData.length} -> ${dataToSend.length} bytes`);
10744
+ }
10745
+ }
9911
10746
  this.stats.wsMessagesOut++;
9912
10747
  if (this.ws.readyState === import_websocket.default.OPEN) {
9913
- this.ws.send(data);
9914
- this.log(`Forwarded ${data.length} bytes to WebSocket`);
10748
+ this.ws.send(dataToSend);
10749
+ this.log(`Forwarded ${dataToSend.length} bytes to WebSocket (original: ${decompressedData.length})`);
9915
10750
  }
9916
10751
  }
9917
10752
  log(...args) {
@@ -9928,6 +10763,10 @@ var ProxyConnection = class extends node_events.EventEmitter {
9928
10763
  this.emit("closed");
9929
10764
  }
9930
10765
  };
10766
+ /**
10767
+ * WebSocket-to-TCP Proxy server
10768
+ * Manages multiple proxy connections
10769
+ */
9931
10770
  var CapnpWebSocketProxy = class extends node_events.EventEmitter {
9932
10771
  wss;
9933
10772
  connections = /* @__PURE__ */ new Map();
@@ -9985,6 +10824,9 @@ if (require("url").pathToFileURL(__filename).href === `file://${process.argv[1]}
9985
10824
  let targetHost = "localhost";
9986
10825
  let targetPort = 8081;
9987
10826
  let debug = false;
10827
+ let wsCompression = false;
10828
+ let tcpCompression = false;
10829
+ let compressionThreshold = 1024;
9988
10830
  for (let i = 0; i < args.length; i++) switch (args[i]) {
9989
10831
  case "--ws-port":
9990
10832
  case "-p":
@@ -10001,6 +10843,17 @@ if (require("url").pathToFileURL(__filename).href === `file://${process.argv[1]}
10001
10843
  case "-d":
10002
10844
  debug = true;
10003
10845
  break;
10846
+ case "--ws-compression":
10847
+ case "-w":
10848
+ wsCompression = true;
10849
+ break;
10850
+ case "--tcp-compression":
10851
+ case "-c":
10852
+ tcpCompression = true;
10853
+ break;
10854
+ case "--compression-threshold":
10855
+ compressionThreshold = Number.parseInt(args[++i], 10);
10856
+ break;
10004
10857
  case "--help":
10005
10858
  case "-h":
10006
10859
  console.log(`
@@ -10009,13 +10862,25 @@ Cap'n Proto WebSocket-to-TCP Proxy
10009
10862
  Usage: npx @naeemo/capnp proxy [options]
10010
10863
 
10011
10864
  Options:
10012
- -p, --ws-port <port> WebSocket server port (default: 8080)
10013
- -t, --target <host:port> Target TCP service (default: localhost:8081)
10014
- -d, --debug Enable debug logging
10015
- -h, --help Show this help
10865
+ -p, --ws-port <port> WebSocket server port (default: 8080)
10866
+ -t, --target <host:port> Target TCP service (default: localhost:8081)
10867
+ -d, --debug Enable debug logging
10868
+ -w, --ws-compression Enable WebSocket side compression
10869
+ -c, --tcp-compression Enable TCP side compression
10870
+ --compression-threshold <n> Minimum size to compress (default: 1024)
10871
+ -h, --help Show this help
10016
10872
 
10017
- Example:
10873
+ Examples:
10018
10874
  npx @naeemo/capnp proxy -p 9000 -t 192.168.1.100:7000
10875
+
10876
+ # Enable compression on WebSocket side only (for browser clients)
10877
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w
10878
+
10879
+ # Enable compression on both sides
10880
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w -c
10881
+
10882
+ # Enable compression with lower threshold
10883
+ npx @naeemo/capnp proxy -p 9000 -t localhost:7000 -w --compression-threshold 512
10019
10884
  `);
10020
10885
  process.exit(0);
10021
10886
  }
@@ -10023,11 +10888,26 @@ Example:
10023
10888
  wsPort,
10024
10889
  targetHost,
10025
10890
  targetPort,
10026
- debug
10891
+ debug,
10892
+ compression: {
10893
+ ws: {
10894
+ enabled: wsCompression,
10895
+ threshold: compressionThreshold
10896
+ },
10897
+ tcp: {
10898
+ enabled: tcpCompression,
10899
+ threshold: compressionThreshold
10900
+ }
10901
+ }
10027
10902
  });
10028
10903
  console.log("WebSocket-to-TCP Proxy started");
10029
10904
  console.log(` WebSocket: ws://localhost:${wsPort}`);
10030
10905
  console.log(` Target: ${targetHost}:${targetPort}`);
10906
+ if (wsCompression || tcpCompression) {
10907
+ console.log(" Compression:");
10908
+ console.log(` WebSocket: ${wsCompression ? "enabled" : "disabled"} (threshold: ${compressionThreshold})`);
10909
+ console.log(` TCP: ${tcpCompression ? "enabled" : "disabled"} (threshold: ${compressionThreshold})`);
10910
+ }
10031
10911
  proxy.on("connection", () => {
10032
10912
  console.log(`Active connections: ${proxy.getConnectionCount()}`);
10033
10913
  });
@@ -10049,6 +10929,7 @@ exports.BulkTransferManager = BulkTransferManager;
10049
10929
  exports.CapnpWebSocketProxy = CapnpWebSocketProxy;
10050
10930
  exports.ConnectionManager = ConnectionManager;
10051
10931
  exports.DEFAULT_BULK_CONFIG = DEFAULT_BULK_CONFIG;
10932
+ exports.DEFAULT_COMPRESSION_CONFIG = DEFAULT_COMPRESSION_CONFIG;
10052
10933
  exports.DEFAULT_ESCROW_CONFIG = DEFAULT_ESCROW_CONFIG;
10053
10934
  exports.DEFAULT_FLOW_CONTROL = DEFAULT_FLOW_CONTROL;
10054
10935
  exports.DEFAULT_JOIN_OPTIONS = DEFAULT_JOIN_OPTIONS;
@@ -10059,6 +10940,7 @@ exports.DropPolicy = DropPolicy;
10059
10940
  exports.ElementSize = ElementSize;
10060
10941
  exports.ExportTable = require_rpc_connection.ExportTable;
10061
10942
  exports.EzRpcTransport = EzRpcTransport;
10943
+ exports.FrameFlags = FrameFlags;
10062
10944
  exports.ImportTable = require_rpc_connection.ImportTable;
10063
10945
  exports.Level3Handlers = Level3Handlers;
10064
10946
  exports.Level4Handlers = Level4Handlers;
@@ -10097,8 +10979,11 @@ exports.UnionBuilder = UnionBuilder;
10097
10979
  exports.UnionReader = UnionReader;
10098
10980
  exports.WORD_SIZE = WORD_SIZE;
10099
10981
  exports.WebSocketTransport = WebSocketTransport;
10982
+ exports.compress = compress;
10100
10983
  exports.configureGlobalMemoryPool = configureGlobalMemoryPool;
10101
10984
  exports.createBulkTransferManager = createBulkTransferManager;
10985
+ exports.createCompressionConfig = createCompressionConfig;
10986
+ exports.createCompressionStats = createCompressionStats;
10102
10987
  exports.createDynamicReader = createDynamicReader;
10103
10988
  exports.createDynamicReaderByTypeId = createDynamicReaderByTypeId;
10104
10989
  exports.createDynamicReaderFromStruct = createDynamicReaderFromStruct;
@@ -10126,17 +11011,20 @@ exports.deserializeSturdyRef = deserializeSturdyRef;
10126
11011
  exports.disableDebug = require_rpc_connection.disableDebug;
10127
11012
  exports.dumpDynamicReader = dumpDynamicReader;
10128
11013
  exports.enableDebug = require_rpc_connection.enableDebug;
11014
+ exports.encodeFarPointer = encodeFarPointer;
10129
11015
  exports.encodeListPointer = encodeListPointer;
10130
11016
  exports.encodeStructPointer = encodeStructPointer;
10131
11017
  exports.fastCopy = fastCopy;
10132
11018
  exports.generateProvisionId = generateProvisionId;
10133
11019
  exports.generateVatId = generateVatId;
10134
11020
  exports.getGlobalMemoryPool = getGlobalMemoryPool;
11021
+ exports.isCompressionFrame = isCompressionFrame;
10135
11022
  exports.isDebugEnabled = require_rpc_connection.isDebugEnabled;
10136
11023
  exports.isPipelineClient = require_rpc_connection.isPipelineClient;
10137
11024
  exports.isSameBuffer = isSameBuffer;
10138
11025
  exports.isStream = isStream;
10139
11026
  exports.isSturdyRefValid = isSturdyRefValid;
11027
+ exports.parseFrameHeader = parseFrameHeader;
10140
11028
  exports.parseSchemaNodes = require_rpc_connection.parseSchemaNodes;
10141
11029
  exports.serializeDynamic = serializeDynamic;
10142
11030
  exports.serializeDynamicByTypeId = serializeDynamicByTypeId;
@@ -10148,4 +11036,6 @@ exports.serializeSchemaRequest = require_rpc_connection.serializeSchemaRequest;
10148
11036
  exports.serializeSchemaResponse = require_rpc_connection.serializeSchemaResponse;
10149
11037
  exports.serializeSturdyRef = serializeSturdyRef;
10150
11038
  exports.supportsStreaming = supportsStreaming;
11039
+ exports.tryDecompress = tryDecompress;
11040
+ exports.uncompress = uncompress;
10151
11041
  //# sourceMappingURL=index.cjs.map