{"_id":"lzo1x","_rev":"3-e08197d1d1fcd512004ec5fb651fbf47","name":"lzo1x","dist-tags":{"latest":"1.0.1"},"versions":{"0.1.0":{"name":"lzo1x","version":"0.1.0","keywords":["lzo","lzo1x","lzo1x-1","compression","decompression","compress","decompress","typescript","isomorphic","browser","esm"],"author":{"name":"Mannes Brak"},"license":"MIT","_id":"lzo1x@0.1.0","maintainers":[{"name":"bankersman","email":"broodje@warmvle.es"}],"homepage":"https://github.com/mbtech-nl/lzo1x","bugs":{"url":"https://github.com/mbtech-nl/lzo1x/issues"},"dist":{"shasum":"e4c4cbc1f724f9b4e512cca3c572dda2c04f8bb6","tarball":"https://registry.npmjs.org/lzo1x/-/lzo1x-0.1.0.tgz","fileCount":15,"integrity":"sha512-dd96o4gT6zXDKkeFZ/kpqJMrdQuPlYDx9StTE0EcgR8x0FPmHhVOKooaKkanOa6ZbMzDhRiMNj/6hvS/GaBI1Q==","signatures":[{"sig":"MEYCIQDRv8JRquBHGccO9vBsh8/Fm0EUGmw2JcfR+kTkT3fX/AIhAJjX3OK65RLRuctTg94FtVOk8mlmWjF/NphjEH4zlefR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41512},"type":"module","_from":"file:lzo1x-0.1.0.tgz","engines":{"node":">=20.9.0","pnpm":">=9.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":[{"url":"https://github.com/sponsors/mannes","type":"github"},{"url":"https://ko-fi.com/mannes","type":"ko-fi"}],"scripts":{"lint":"eslint src","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"prettier --write src","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"bankersman","email":"broodje@warmvle.es"},"prettier":"@mbtech-nl/prettier-config","_resolved":"/tmp/907dfc5b030870ce6a0fa41a45b2d073/lzo1x-0.1.0.tgz","_integrity":"sha512-dd96o4gT6zXDKkeFZ/kpqJMrdQuPlYDx9StTE0EcgR8x0FPmHhVOKooaKkanOa6ZbMzDhRiMNj/6hvS/GaBI1Q==","repository":{"url":"git+https://github.com/mbtech-nl/lzo1x.git","type":"git"},"_npmVersion":"11.9.0","description":"Pure-TypeScript clean-room implementation of LZO1X-1 compression and decompression, isomorphic (Node + browser)","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","node-gyp":"^12.3.0","prettier":"^3.0.0","typescript":"~5.5.0","@types/node":"^25.6.0","@mbtech-nl/tsconfig":"^1.0.0","@vitest/coverage-v8":"^2.0.0","@mbtech-nl/eslint-config":"^1.0.1","@mbtech-nl/prettier-config":"^1.0.0"},"optionalDependencies":{"lzo":"^0.4.11"},"_npmOperationalInternal":{"tmp":"tmp/lzo1x_0.1.0_1779986665145_0.8760068343907579","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"lzo1x","version":"1.0.0","keywords":["lzo","lzo1x","lzo1x-1","compression","decompression","compress","decompress","typescript","isomorphic","browser","esm"],"author":{"name":"Mannes Brak"},"license":"MIT","_id":"lzo1x@1.0.0","maintainers":[{"name":"bankersman","email":"broodje@warmvle.es"}],"homepage":"https://github.com/mbtech-nl/lzo1x","bugs":{"url":"https://github.com/mbtech-nl/lzo1x/issues"},"dist":{"shasum":"4b508d3ddd9f0303d907327a04253aec3f2ea3e3","tarball":"https://registry.npmjs.org/lzo1x/-/lzo1x-1.0.0.tgz","fileCount":15,"integrity":"sha512-yDte65svknLciRzJHU8aT+mgFGRCKyx+/1ZN+zVV45peBoPPj7LYC7PJrGUuhIZdjxfV3f6tIkgc94iQ2gWS4w==","signatures":[{"sig":"MEUCIA31aVoyurNHE7dHVjW4BAvz38eAu2f/Bdb0Femv5+41AiEAjBrFdC1UeY1MbLh1DIXo+kktU9BRsSpESBpiBG/tXJU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/lzo1x@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":41512},"type":"module","_from":"file:lzo1x-1.0.0.tgz","engines":{"node":">=20.9.0","pnpm":">=9.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"funding":[{"url":"https://github.com/sponsors/mannes","type":"github"},{"url":"https://ko-fi.com/mannes","type":"ko-fi"}],"scripts":{"lint":"eslint src","test":"vitest run","build":"tsc -p tsconfig.build.json","format":"prettier --write src","typecheck":"tsc -p tsconfig.json --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:36135cc8-70fd-4175-8d63-3a67fc5d2d3e"}},"prettier":"@mbtech-nl/prettier-config","_resolved":"/tmp/43d5017c6d6bbdb412626a53efd783ab/lzo1x-1.0.0.tgz","_integrity":"sha512-yDte65svknLciRzJHU8aT+mgFGRCKyx+/1ZN+zVV45peBoPPj7LYC7PJrGUuhIZdjxfV3f6tIkgc94iQ2gWS4w==","repository":{"url":"git+https://github.com/mbtech-nl/lzo1x.git","type":"git"},"_npmVersion":"11.13.0","description":"Pure-TypeScript clean-room implementation of LZO1X-1 compression and decompression, isomorphic (Node + browser)","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.0.0","vitest":"^2.0.0","node-gyp":"^12.3.0","prettier":"^3.0.0","typescript":"~5.5.0","@types/node":"^25.6.0","@mbtech-nl/tsconfig":"^1.0.0","@vitest/coverage-v8":"^2.0.0","@mbtech-nl/eslint-config":"^1.0.1","@mbtech-nl/prettier-config":"^1.0.0"},"optionalDependencies":{"lzo":"^0.4.11"},"_npmOperationalInternal":{"tmp":"tmp/lzo1x_1.0.0_1779987614869_0.03221931093363506","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"lzo1x","version":"1.0.1","description":"Pure-TypeScript clean-room implementation of LZO1X-1 compression and decompression, isomorphic (Node + browser)","keywords":["lzo","lzo1x","lzo1x-1","compression","decompression","compress","decompress","typescript","isomorphic","browser","esm","cjs"],"license":"MIT","author":{"name":"Mannes Brak"},"homepage":"https://github.com/mbtech-nl/lzo1x","repository":{"type":"git","url":"git+https://github.com/mbtech-nl/lzo1x.git"},"bugs":{"url":"https://github.com/mbtech-nl/lzo1x/issues"},"engines":{"node":">=20.9.0","pnpm":">=9.0.0"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"sideEffects":false,"prettier":"@mbtech-nl/prettier-config","devDependencies":{"@mbtech-nl/eslint-config":"^1.0.1","@mbtech-nl/prettier-config":"^1.0.0","@mbtech-nl/tsconfig":"^1.0.0","@types/node":"^25.6.0","@vitest/coverage-v8":"^2.0.0","eslint":"^9.0.0","node-gyp":"^12.3.0","prettier":"^3.0.0","tsup":"^8.5.1","typescript":"~5.5.0","vitest":"^2.0.0"},"optionalDependencies":{"lzo":"^0.4.11"},"funding":[{"type":"github","url":"https://github.com/sponsors/mannes"},{"type":"ko-fi","url":"https://ko-fi.com/mannes"}],"scripts":{"build":"tsup","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"eslint src","format":"prettier --write src"},"_id":"lzo1x@1.0.1","_integrity":"sha512-Zlk+Fn2L47oOVFudFxy/hguB1y3Ai3exmwyBu01kTTwbkd+Jmx8BoBS0yQfob47tKekWUofy3iRJx5HBxeTkdA==","_resolved":"/tmp/c0e4eac224af0d01ca5b24daa7f0a7a7/lzo1x-1.0.1.tgz","_from":"file:lzo1x-1.0.1.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-Zlk+Fn2L47oOVFudFxy/hguB1y3Ai3exmwyBu01kTTwbkd+Jmx8BoBS0yQfob47tKekWUofy3iRJx5HBxeTkdA==","shasum":"5b091267c1a990a8c0d844550af091251bbbba21","tarball":"https://registry.npmjs.org/lzo1x/-/lzo1x-1.0.1.tgz","fileCount":9,"unpackedSize":81545,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/lzo1x@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDQnU4O4AvEYw0x9Lnh+Tv0nfEsrgmx2HqbBCbBS47QTAiAaJ3ID62rra/KIXrFwz6xczrVNqRt/JxVVR6QXnyqoUQ=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:36135cc8-70fd-4175-8d63-3a67fc5d2d3e"}},"directories":{},"maintainers":[{"name":"bankersman","email":"broodje@warmvle.es"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/lzo1x_1.0.1_1779997310677_0.6714422046742208"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-28T16:44:25.077Z","modified":"2026-05-28T19:41:51.217Z","0.1.0":"2026-05-28T16:44:25.304Z","1.0.0":"2026-05-28T17:00:15.043Z","1.0.1":"2026-05-28T19:41:50.851Z"},"bugs":{"url":"https://github.com/mbtech-nl/lzo1x/issues"},"author":{"name":"Mannes Brak"},"license":"MIT","homepage":"https://github.com/mbtech-nl/lzo1x","keywords":["lzo","lzo1x","lzo1x-1","compression","decompression","compress","decompress","typescript","isomorphic","browser","esm","cjs"],"repository":{"type":"git","url":"git+https://github.com/mbtech-nl/lzo1x.git"},"description":"Pure-TypeScript clean-room implementation of LZO1X-1 compression and decompression, isomorphic (Node + browser)","maintainers":[{"name":"bankersman","email":"broodje@warmvle.es"}],"readme":"# lzo1x\n\n[![CI](https://github.com/mbtech-nl/lzo1x/actions/workflows/ci.yml/badge.svg)](https://github.com/mbtech-nl/lzo1x/actions/workflows/ci.yml)\n[![Coverage](https://codecov.io/gh/mbtech-nl/lzo1x/branch/main/graph/badge.svg)](https://codecov.io/gh/mbtech-nl/lzo1x)\n[![npm](https://img.shields.io/npm/v/lzo1x.svg)](https://www.npmjs.com/package/lzo1x)\n\nPure-TypeScript, MIT-licensed, clean-room implementation of **LZO1X-1** compression and decompression. Isomorphic (Node + modern browsers), zero runtime dependencies, ESM-only.\n\n## Why this over `lzo` / miniLZO?\n\nThe canonical option for LZO in Node is the [`lzo`](https://www.npmjs.com/package/lzo) npm package, which wraps the C miniLZO library via `node-gyp`. It's fast and battle-tested — but:\n\n- **License.** miniLZO (and the `lzo` binding) is GPLv2. That is a hard non-starter for a lot of commercial codebases. This package is MIT, clean-room from the public LZO1X-1 stream spec.\n- **Runtime reach.** Native bindings only work on Node, only on platforms with a working compiler toolchain, and only after a successful `node-gyp` build. This package is pure TypeScript — runs in browsers, Cloudflare Workers, Deno, Bun, Electron renderers, and any Node version without rebuilds.\n- **Install footprint.** Zero runtime dependencies, no compile step, no postinstall script, no prebuilt-binary download dance. Just code.\n- **Types.** Ships its own `.d.ts`. No `@types/lzo` shim required.\n- **Conformity.** Every release is cross-validated against the actual miniLZO binding over ~2050 payloads in both directions (see [Testing](#testing)). If `lzo` accepts it, so do we; if we produce it, `lzo` decompresses it.\n\nWhen you should pick `lzo` instead: you're Node-only, GPL is fine, you need LZO1X-999 / LZO1Y / LZO1Z, or you're moving multi-GB/s of data and the ~10× speed gap of native C over JS matters more than reach.\n\n## Install\n\n```bash\npnpm add lzo1x\n```\n\n## API\n\n```ts\nimport { lzo1xCompress, lzo1xDecompress } from 'lzo1x';\n\nconst compressed = lzo1xCompress(input); // Uint8Array → Uint8Array\nconst restored = lzo1xDecompress(compressed); // dynamic-grow\nconst restored2 = lzo1xDecompress(compressed, input.length); // pre-sized, throws RangeError on mismatch\n```\n\nThat is the entire library. No streaming, no async, no LZO1Y/LZO1Z, no LZO1X-999.\n\n`lzo1xCompress` always produces output ≤ `input.length + ceil(input.length / 16) + 67` bytes (the published LZO1X worst case).\n\n`lzo1xDecompress` throws `RangeError` on truncated/corrupt input or on `expectedOutputLength` mismatch.\n\n## LZO1X-1 stream format — one-pager\n\nThe stream is a sequence of **(literal-run, match)** pairs, driven by a single token byte per match. After the last match the stream is terminated by an **M4 end marker** (`0x11 0x00 0x00`).\n\n### Token byte layout\n\nThe token's high bits select the encoding family:\n\n| Token range     | Family                                                               | Encoding |\n| --------------- | -------------------------------------------------------------------- | -------- |\n| `0..15`         | (after-match) literal-only top-up — see below                        |\n| `0..15` (first) | First-frame long literal — `t < 16` triggers extended literal-length |\n| `16..63`        | **M4** — long match (≥ 9 bytes from a far distance)                  |\n| `64..127`       | **M1** — 2-byte literal-distance match, len = 3..4                   |\n| `128..191`      | **M2** — short match, len = 3..4, distance ≤ 2048                    |\n| `192..255`      | **M3** — len 3..8, distance ≤ 2048                                   |\n\n(The \"M1..M4\" naming is the canonical LZO1X terminology.)\n\n### Length / distance encoding ladder\n\nWhen a length field's bits in the token are zero, the actual length is encoded by a run of `0x00` bytes (each contributes 255) followed by a non-zero terminator. The same trick is used for literal-run length after a match (low 2 bits of the previous token), and for match length on M3/M4.\n\n### \"State\" — the literal-run-after-match path\n\nThe low 2 bits of every match token (`state`) carry the number of literal bytes (`0..3`) that immediately follow the match without their own token. When `state == 0` the next byte starts a fresh token; when `state > 0` those literals are copied raw and the byte right after is the next match's token.\n\n### End marker\n\nThe decoder MUST see exactly `0x11 0x00 0x00` (token = M4 with len-bits = 1, then two zero distance bytes — interpreted by the decoder as \"stop\"). Anything after is rejected.\n\n### First-frame quirk\n\nThe very first token has no preceding match. If it is `< 16` it encodes the leading literal run directly (with the length ladder for `t == 0`). If `>= 16` it is a normal match token (rare in practice).\n\n## Implementation notes\n\n- Compressor is the **LZO1X-1** variant (canonical \"fast\" mode): a single-pass greedy matcher with a **13-bit (8192-slot) hash table** keyed on 4 input bytes. This is what the spec calls a \"64 KB working set\" — the 64 KB is the match-distance window, not the table size.\n- The hash function is `((b[i]*2654435761) >>> (32-13)) & 0x1FFF` (Knuth multiplicative hash).\n- Minimum match length is **3** bytes. Below that, we emit literals.\n- The \"trailing literals\" rule: the last `M2_MAX_LEN + 5` (≈ 20) bytes of input are always emitted as literals, never as the tail of a match. This keeps the decoder's wildcopy safe.\n\n## Performance\n\nOn a typical developer laptop, both directions run at roughly **400-500 MB/s** on cache-warm 64 KB buffers. There is no SIMD path; the inner loops are byte-at-a-time Uint8Array reads. Most callers will be I/O-bound or matcher-bound on cold inputs long before they hit the JS interpreter ceiling.\n\n## Browser support\n\nPure TypeScript, zero runtime dependencies, no DOM/Node-only APIs. Runs anywhere `Uint8Array` does.\n\n## Testing\n\nFive test streets under `src/__tests__/`:\n\n1. `format.test.ts` — Hand-crafted inputs that exercise every M1/M2/M3/M4 path and the length ladders.\n2. `roundtrip.test.ts` — Deterministic-RNG inputs at 1, 16, 256, 4096, 65535, 131072 bytes; `decompress(compress(x)) === x`.\n3. `oracle-minilzo.test.ts` — Cross-validates against the native `lzo` npm binding (miniLZO) over **~2050 payloads** in both directions: random-LCG sweeps across 9 size bands, whitened high-entropy inputs, single-byte runs for every byte value, short repeating patterns, small-alphabet text-like data, sparse-zeros mixes, and hand-picked inputs that force decoder-only token paths. Runs in CI on every push; locally self-skips if the binding fails to build.\n4. `captured-frames.test.ts` — Real on-the-wire LZO frames captured from a Niimbot B2 Pro printer over BLE. Self-skips if the research path is absent.\n5. `api.test.ts` — Error semantics, worst-case size bound.\n\n## Prior art\n\nOther browser-capable peers: [`lzo-wasm`](https://github.com/tidepool-org/lzo-wasm) (WASM, decompress-only, BSD-2 wrapping LGPL-3 FFmpeg code) and [`lzo-ts`](https://github.com/thaumictom/lzo-ts) (TS port of `minilzo-js`, GPL-3.0, compress + decompress).\n\n## Licence\n\nMIT — see [`LICENSE`](./LICENSE).\n","readmeFilename":"README.md"}