You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(wasm): wire memlimit option through unxzAsync/unxz (#111)
* feat(wasm): wire memlimit option through unxzAsync/unxz
LZMAOptions previously had no memlimit field, so WASM decoders silently
ignored any caller-provided limit (TODO at src/wasm/decompress.ts:26).
- Add `memlimit?: number | bigint` to LZMAOptions with full TSDoc
- Thread through unxzAsync + callback variant to streamBufferDecode
- Adjust XzStream._opts type to keep memlimit optional in the stream
class (Required<LZMAOptions> would break otherwise)
- 8 new tests in test/wasm/decompress-memlimit.test.ts covering both
number and bigint inputs, sync/callback variants, and the default
Native parity: the N-API binding (InitializeDecoder) still hardcodes
UINT64_MAX and ignores opts.memlimit. Tracked as a separate MEDIUM
TODO; out of scope here to keep the change tight.
* fix(wasm): address review findings on memlimit option (PR #111)
Applies opus + Copilot review findings (4M + 4S):
- Validate `number` memlimit before BigInt() coercion. Reject NaN,
Infinity, fractional, and negative values with LZMAOptionsError
instead of letting RangeError leak from BigInt() (F-001).
- Replace `Omit<Required<LZMAOptions>, 'memlimit'> & Pick<...>`
smell on XzStream._opts with an internal ResolvedLZMAOptions
interface that lists the genuinely-optional memlimit explicitly
(F-002).
- Rewrite callback-variant memlimit tests as async so makeFixture()
rejections propagate to vitest instead of hanging the Promise
(C-001 / C-002).
- Assert byte-equality vs original buffer in callback success test
(was only `toBeDefined()`) (C-003).
- Rework memlimit TSDoc: WASM/native asymmetry warning moved to top,
Default split into "WASM default: 256 MiB" and "Native: ignored"
(F-003 / C-005).
- Refresh stale "Required<LZMAOptions>" comment in lzma.ts to match
the new ResolvedLZMAOptions type (F-004 / C-004).
- Fix fixture comment "~4 MB" -> "~8 MiB" (preset 6 dict size) and
explain why the AC holds regardless of exact size (F-005).
- Document the default-path test coverage gap in a 3-line code
comment — proves no-throw, not that default IS specifically
256 MiB (F-006).
584/584 tests pass (12 memlimit, +4 vs round 1).
* fix(wasm): address Copilot round-2 findings on memlimit (PR #111)
5 findings from Copilot review of round-1 fixes (3M + 1S + 1L):
- Remove `xzAsync` from `LZMAOptions.memlimit` honored-by list and
refresh internal lzma.ts comment — xzAsync is compression-only and
doesn't read memlimit (C-2-001 / C-2-002).
- Lift `validateMemlimit` to run as the first statement of every
decoder ingestion path: `decoderInit`, `autoDecoderInit`, and
`streamBufferDecode` — round 1 only guarded `streamBufferDecode`,
leaving the streaming decoder entry points able to throw raw
RangeError on NaN/Infinity/fractional or accept negative values
(C-2-005). +12 tests prove rejection on each path.
- Reject `number > Number.MAX_SAFE_INTEGER` in `validateMemlimit` —
values above 2^53-1 lose precision when coerced to bigint and
produce an unintended limit. TSDoc directs callers to use bigint
for large limits (C-2-004). +4 tests cover both sides of the
boundary.
- Replace hardcoded `errno: 8` with `LZMA_OPTIONS_ERROR` constant
imported from `src/errors.ts` (C-2-003).
Pre-push opus senior review (§2.10 round-3 gate): SAFE-TO-PUSH —
all five claims verified against working tree, zero new S/M/L.
600/600 tests pass (16 added vs round 1).
Polish item deferred to TODO.md (LOW): bigint branch lacks UINT64_MAX
upper-bound guard; benign while native ignores memlimit; becomes
load-bearing once native parity is wired.
* docs(wasm): tighten memlimit comments and dedupe JSDoc (PR #111)
Round-3 Copilot doc-only findings (3L + 1S):
- Remove three duplicate JSDoc blocks from `decoderInit`,
`autoDecoderInit`, and `validateMemlimit`. Earlier rounds
used `insert_symbol` instead of `write_symbol`, leaving the
original short doc next to the new validation-aware block.
Keep the complete block in each case (C-3-001/2/3).
- Fix a missed-spot stale comment in `src/lzma.ts:370` still
listing `xzAsync` as honouring memlimit. `xzAsync` is
compression-only; the round-2 cleanup at line 311 missed
this second occurrence (C-3-004).
Sweep confirms no other "xzAsync honours memlimit" claims
remain across `src/lzma.ts`, `src/wasm/bindings.ts`, and
`src/types.ts`.
600+ tests pass, tsc clean. No behaviour change.
When the compressed stream would require more memory than the limit, the promise rejects with `LZMAMemoryLimitError` (`errno === LZMA_MEMLIMIT_ERROR === 6`).
14
+
15
+
**Accepted types:**`number | bigint` (both coerced to `bigint` for the WASM C ABI).
**Native parity:** The native Node.js binding (`InitializeDecoder`) still hardcodes `UINT64_MAX` and ignores `memlimit`. This is WASM-only for now; native tracking in TODO.md.
Copy file name to clipboardExpand all lines: TODO.md
+8-2Lines changed: 8 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,14 +10,20 @@ _None_
10
10
11
11
## Pending - MEDIUM
12
12
13
-
_None_
13
+
-[ ][tar-xz] True streaming for Node `extract()`/`list()` — replace `Buffer.concat` accumulation (extract.ts:59,91 + list.ts:26) with incremental header→content parsing so memory stays O(largest entry) instead of O(archive). Public README v6.0.0 advertises this as a "planned optimization" — Priority: M
14
+
-[ ][Native] Wire `memlimit` in `src/bindings/node-liblzma.cpp``InitializeDecoder` — currently hardcodes `UINT64_MAX`; should read `opts.memlimit` and call `lzma_stream_decoder(stream, memlimit, flags)`. WASM already supports it. — Priority: M
15
+
-[→][WASM] Wire `memlimit` through `LZMAOptions` and `unxzAsync` — moved to In Progress (2026-04-28) → ✅ completed (2026-04-28)
14
16
15
17
## Pending - LOW (Nice to Have)
16
18
17
-
_None_
19
+
-[ ][WASM]`validateMemlimit` symmetry — bigint branch has no UINT64_MAX upper-bound guard (only the `number` branch checks `MAX_SAFE_INTEGER`). Currently benign because native side hardcodes UINT64_MAX and `lzma_stream_decoder` reads `uint64_t` (so `2n ** 65n` would silently wrap to a benign-but-unintended ceiling, no security/leak/crash). Becomes load-bearing once `[Native] Wire memlimit` lands — fix together. Priority: L (defer until native parity work).
18
20
19
21
## Completed
20
22
23
+
-[x] ✅ [WASM] PR #111 Round 3 Copilot fixes — C-3-001/2/3 duplicate JSDoc blocks removed from decoderInit/autoDecoderInit/validateMemlimit, C-3-004 stale xzAsync/unxzAsync comment fixed in lzma.ts:370; tsc+memlimit+full suite pass (2026-04-28)
-[x] ✅ [WASM] Wire `memlimit` through `LZMAOptions` → `unxzAsync`/`unxz` — `LZMAMemoryLimitError` thrown when limit exceeded; 8 new tests in `test/wasm/decompress-memlimit.test.ts`; TSDoc with parity note (2026-04-28)
21
27
-[x] ✅ [tar-xz v6] Universal stream-first redesign: `create()`/`extract()`/`list()` with `AsyncIterable<Uint8Array>`, identical Node/Browser signatures, `tar-xz/file` subpath for fs helpers — published as `tar-xz@6.0.0` + `nxz-cli@6.0.0` (2026-04-27)
-[x] ✅ [Infra] Independent versioning per workspace package: `release.yml`/`publish.yml` accept `target_package` input, no cross-package version sync; proven in prod — `tar-xz@6.0.0` published without bumping `node-liblzma` (still at 5.0.0) (2026-04-27)
Copy file name to clipboardExpand all lines: src/lzma.ts
+26-1Lines changed: 26 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -301,8 +301,29 @@ export type {
301
301
*
302
302
* Emits `progress` event after each chunk with `{bytesRead, bytesWritten}` info.
303
303
*/
304
+
305
+
/**
306
+
* Internal resolved options for XzStream instances.
307
+
*
308
+
* All fields are required (defaults applied in constructor) EXCEPT memlimit,
309
+
* which is genuinely optional: the native binding ignores it (UINT64_MAX
310
+
* hardcoded; see TODO "[Native] Wire memlimit in src/bindings/node-liblzma.cpp").
311
+
* Only the WASM Buffer API decompression paths (unxz/unxzAsync/streamBufferDecode) honour memlimit; xzAsync is compression-only and ignores this field.
312
+
*/
313
+
interfaceResolvedLZMAOptions{
314
+
check: number;
315
+
preset: number;
316
+
filters: number[];
317
+
mode: number;
318
+
threads: number;
319
+
chunkSize: number;
320
+
flushFlag: number;
321
+
/** Honoured only by the WASM Buffer API; native streams ignore this field. */
0 commit comments