hadars 0.2.2-rc.2 → 0.3.1

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/README.md CHANGED
@@ -19,30 +19,30 @@ Bring your own router (or none), keep your components as plain React, and get SS
19
19
  ## Benchmarks
20
20
 
21
21
  <!-- BENCHMARK_START -->
22
- > Last run: 2026-03-23 · 120s · 100 connections · Bun runtime
23
- > hadars is **8.7x faster** in requests/sec
22
+ > Last run: 2026-03-25 · 120s · 100 connections · Bun runtime
23
+ > hadars is **8.9x faster** in requests/sec
24
24
 
25
25
  **Throughput** (autocannon, 120s)
26
26
 
27
27
  | Metric | hadars | Next.js |
28
28
  |---|---:|---:|
29
- | Requests/sec | **148** | 17 |
30
- | Latency median | **639 ms** | 2754 ms |
31
- | Latency p99 | **976 ms** | 4040 ms |
32
- | Throughput | **42.23** MB/s | 9.49 MB/s |
33
- | Peak RSS | 987.9 MB | **476.9 MB** |
34
- | Avg RSS | 775.3 MB | **424.5 MB** |
29
+ | Requests/sec | **151** | 17 |
30
+ | Latency median | **642 ms** | 2747 ms |
31
+ | Latency p99 | **959 ms** | 4019 ms |
32
+ | Throughput | **43.05** MB/s | 9.5 MB/s |
33
+ | Peak RSS | 950.3 MB | **478.5 MB** |
34
+ | Avg RSS | 763.3 MB | **426.4 MB** |
35
35
  | Build time | 0.7 s | 6.0 s |
36
36
 
37
37
  **Page load** (Playwright · Chromium headless · median)
38
38
 
39
39
  | Metric | hadars | Next.js |
40
40
  |---|---:|---:|
41
- | TTFB | **19 ms** | 46 ms |
42
- | FCP | **96 ms** | 140 ms |
43
- | DOMContentLoaded | **40 ms** | 128 ms |
44
- | Load | **124 ms** | 174 ms |
45
- | Peak RSS | 468.0 MB | **297.0 MB** |
41
+ | TTFB | **19 ms** | 42 ms |
42
+ | FCP | **96 ms** | 136 ms |
43
+ | DOMContentLoaded | **39 ms** | 127 ms |
44
+ | Load | **122 ms** | 173 ms |
45
+ | Peak RSS | 476.8 MB | **289.5 MB** |
46
46
  <!-- BENCHMARK_END -->
47
47
 
48
48
  ## Quick start
@@ -264,8 +264,18 @@ async function transformStream(data, stream) {
264
264
  }
265
265
  return out;
266
266
  }
267
- var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
268
- var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
267
+ async function zlibGzip(d) {
268
+ const zlib = await import("node:zlib");
269
+ const { promisify } = await import("node:util");
270
+ return promisify(zlib.gzip)(d);
271
+ }
272
+ async function zlibGunzip(d) {
273
+ const zlib = await import("node:zlib");
274
+ const { promisify } = await import("node:util");
275
+ return promisify(zlib.gunzip)(d);
276
+ }
277
+ var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
278
+ var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
269
279
  async function buildCacheEntry(res, ttl) {
270
280
  const buf = await res.arrayBuffer();
271
281
  const body = await gzipCompress(new Uint8Array(buf));
package/dist/cli.js CHANGED
@@ -1626,27 +1626,8 @@ function withRequestLogging(handler) {
1626
1626
  return res;
1627
1627
  };
1628
1628
  }
1629
- var COMPRESSIBLE_RE = /^text\/|\/json|\/javascript|\/xml|\/wasm/;
1630
- function withCompression(handler) {
1631
- return async (req, ctx) => {
1632
- const res = await handler(req, ctx);
1633
- if (!res || !res.body) return res;
1634
- const accept = req.headers.get("Accept-Encoding") ?? "";
1635
- if (!accept.includes("gzip")) return res;
1636
- if (res.headers.has("content-encoding")) return res;
1637
- const ct = res.headers.get("content-type") ?? "";
1638
- if (!COMPRESSIBLE_RE.test(ct)) return res;
1639
- const compressed = res.body.pipeThrough(
1640
- new globalThis.CompressionStream("gzip")
1641
- );
1642
- const headers = new Headers(res.headers);
1643
- headers.set("content-encoding", "gzip");
1644
- headers.delete("content-length");
1645
- return new Response(compressed, { status: res.status, headers });
1646
- };
1647
- }
1648
1629
  async function serve(port, fetchHandler, websocket) {
1649
- fetchHandler = withCompression(withRequestLogging(fetchHandler));
1630
+ fetchHandler = withRequestLogging(fetchHandler);
1650
1631
  if (isBun) {
1651
1632
  globalThis.Bun.serve({
1652
1633
  port,
@@ -1839,8 +1820,18 @@ async function transformStream(data, stream) {
1839
1820
  }
1840
1821
  return out;
1841
1822
  }
1842
- var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
1843
- var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
1823
+ async function zlibGzip(d) {
1824
+ const zlib = await import("node:zlib");
1825
+ const { promisify } = await import("node:util");
1826
+ return promisify(zlib.gzip)(d);
1827
+ }
1828
+ async function zlibGunzip(d) {
1829
+ const zlib = await import("node:zlib");
1830
+ const { promisify } = await import("node:util");
1831
+ return promisify(zlib.gunzip)(d);
1832
+ }
1833
+ var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
1834
+ var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
1844
1835
  async function buildCacheEntry(res, ttl) {
1845
1836
  const buf = await res.arrayBuffer();
1846
1837
  const body = await gzipCompress(new Uint8Array(buf));
@@ -1279,8 +1279,18 @@ async function transformStream(data, stream) {
1279
1279
  }
1280
1280
  return out;
1281
1281
  }
1282
- var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
1283
- var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
1282
+ async function zlibGzip(d) {
1283
+ const zlib = await import("zlib");
1284
+ const { promisify } = await import("util");
1285
+ return promisify(zlib.gzip)(d);
1286
+ }
1287
+ async function zlibGunzip(d) {
1288
+ const zlib = await import("zlib");
1289
+ const { promisify } = await import("util");
1290
+ return promisify(zlib.gunzip)(d);
1291
+ }
1292
+ var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
1293
+ var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
1284
1294
  async function buildCacheEntry(res, ttl) {
1285
1295
  const buf = await res.arrayBuffer();
1286
1296
  const body = await gzipCompress(new Uint8Array(buf));
@@ -6,7 +6,7 @@ import {
6
6
  getReactResponse,
7
7
  makePrecontentHtmlGetter,
8
8
  parseRequest
9
- } from "./chunk-H72BZXOA.js";
9
+ } from "./chunk-2KJRDPCN.js";
10
10
  import "./chunk-LY5MTHFV.js";
11
11
  import "./chunk-OS3V4CPN.js";
12
12
 
package/dist/lambda.cjs CHANGED
@@ -1319,8 +1319,18 @@ async function transformStream(data, stream) {
1319
1319
  }
1320
1320
  return out;
1321
1321
  }
1322
- var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
1323
- var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
1322
+ async function zlibGzip(d) {
1323
+ const zlib = await import("zlib");
1324
+ const { promisify } = await import("util");
1325
+ return promisify(zlib.gzip)(d);
1326
+ }
1327
+ async function zlibGunzip(d) {
1328
+ const zlib = await import("zlib");
1329
+ const { promisify } = await import("util");
1330
+ return promisify(zlib.gunzip)(d);
1331
+ }
1332
+ var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
1333
+ var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
1324
1334
  async function buildCacheEntry(res, ttl) {
1325
1335
  const buf = await res.arrayBuffer();
1326
1336
  const body = await gzipCompress(new Uint8Array(buf));
package/dist/lambda.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getReactResponse,
7
7
  makePrecontentHtmlGetter,
8
8
  parseRequest
9
- } from "./chunk-H72BZXOA.js";
9
+ } from "./chunk-2KJRDPCN.js";
10
10
  import "./chunk-LY5MTHFV.js";
11
11
  import "./chunk-OS3V4CPN.js";
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "0.2.2-rc.2",
3
+ "version": "0.3.1",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -58,33 +58,6 @@ function withRequestLogging(handler: FetchHandler): FetchHandler {
58
58
  };
59
59
  }
60
60
 
61
- const COMPRESSIBLE_RE = /^text\/|\/json|\/javascript|\/xml|\/wasm/;
62
-
63
- /**
64
- * Wraps a handler to apply on-the-fly gzip compression via CompressionStream.
65
- * Unlike a buffering approach, this pipes the response ReadableStream through
66
- * a CompressionStream so bytes are compressed and flushed as they arrive —
67
- * streaming responses remain streaming.
68
- */
69
- function withCompression(handler: FetchHandler): FetchHandler {
70
- return async (req, ctx) => {
71
- const res = await handler(req, ctx);
72
- if (!res || !res.body) return res;
73
- const accept = req.headers.get('Accept-Encoding') ?? '';
74
- if (!accept.includes('gzip')) return res;
75
- // Skip if already encoded (e.g. pre-compressed cache entry).
76
- if (res.headers.has('content-encoding')) return res;
77
- const ct = res.headers.get('content-type') ?? '';
78
- if (!COMPRESSIBLE_RE.test(ct)) return res;
79
- const compressed = res.body.pipeThrough(
80
- new (globalThis as any).CompressionStream('gzip'),
81
- );
82
- const headers = new Headers(res.headers);
83
- headers.set('content-encoding', 'gzip');
84
- headers.delete('content-length'); // length is unknown after compression
85
- return new Response(compressed, { status: res.status, headers });
86
- };
87
- }
88
61
 
89
62
  export async function serve(
90
63
  port: number,
@@ -92,7 +65,7 @@ export async function serve(
92
65
  /** Bun WebSocketHandler — ignored on Deno and Node.js. */
93
66
  websocket?: unknown,
94
67
  ): Promise<void> {
95
- fetchHandler = withCompression(withRequestLogging(fetchHandler));
68
+ fetchHandler = withRequestLogging(fetchHandler);
96
69
 
97
70
  // ── Bun ────────────────────────────────────────────────────────────────
98
71
  if (isBun) {
@@ -142,8 +142,28 @@ async function transformStream(
142
142
  return out;
143
143
  }
144
144
 
145
- export const gzipCompress = (d: Uint8Array) => transformStream(d, new (globalThis as any).CompressionStream('gzip'));
146
- export const gzipDecompress = (d: Uint8Array) => transformStream(d, new (globalThis as any).DecompressionStream('gzip'));
145
+ // Use the Web Streams CompressionStream when available (Bun, Deno, Node 18).
146
+ // Fall back to node:zlib on older Node versions.
147
+ async function zlibGzip(d: Uint8Array): Promise<Uint8Array> {
148
+ const zlib = await import('node:zlib');
149
+ const { promisify } = await import('node:util');
150
+ return promisify(zlib.gzip)(d) as Promise<Uint8Array>;
151
+ }
152
+ async function zlibGunzip(d: Uint8Array): Promise<Uint8Array> {
153
+ const zlib = await import('node:zlib');
154
+ const { promisify } = await import('node:util');
155
+ return promisify(zlib.gunzip)(d) as Promise<Uint8Array>;
156
+ }
157
+
158
+ export const gzipCompress = (d: Uint8Array): Promise<Uint8Array> =>
159
+ (globalThis as any).CompressionStream
160
+ ? transformStream(d, new (globalThis as any).CompressionStream('gzip'))
161
+ : zlibGzip(d);
162
+
163
+ export const gzipDecompress = (d: Uint8Array): Promise<Uint8Array> =>
164
+ (globalThis as any).DecompressionStream
165
+ ? transformStream(d, new (globalThis as any).DecompressionStream('gzip'))
166
+ : zlibGunzip(d);
147
167
 
148
168
  export async function buildCacheEntry(res: Response, ttl: number | undefined): Promise<CacheEntry> {
149
169
  const buf = await res.arrayBuffer();