How the Stanza X12 streaming parser compares with the most established open-source Node parsers, and where it does and does not compete with commercial EDI products.
- File: 500.5 MB of synthetic 837P claims, 255,000 transactions, ~1,963 bytes
per transaction, generated by
bin/generate.mjs(seed 42). - Hardware: Intel Core i7-10700K @ 3.80 GHz, 31 GiB RAM, Linux 7.2.5, Node v26.8.2.
- Each parser runs in a fresh process, sequentially, never in parallel.
- Output is discarded while parsing: the measurement is parse cost, not
retention policy.
itemscounts emitted units (Stanza emits parsed transactions, the libraries emit raw segments). peak_heap_mbsamplesprocess.memoryUsage().heapUsedevery 25 ms. The "capped" run additionally enforces--max-old-space-size=25, which aborts the process if old-space allocation exceeds the bound.- Versions:
node-x12@1.7.1,x12-parser@1.3.0.
Reproduce:
npm run build
node bin/generate.mjs --transactions 255000 --claims 5 --out data/claims.x12
node bin/compare.mjs --file data/claims.x12 --cap 25
node bin/compare.mjs --file data/claims.x12Capped at 25 MB old space:
| parser | mode | result | wall | items | peak heap |
|---|---|---|---|---|---|
| Stanza | streaming, domain parsing | ok | 14.2 s | 255,001 transactions | 22.2 MB |
| node-x12 | streaming, lax | ok | 20.3 s | 20,655,000 segments | 26.2 MB |
| x12-parser | streaming | ok | 39.2 s | 20,655,000 segments | 32.8 MB |
| node-x12 | streaming, strict | fails on valid input | - | - | - |
Uncapped (default V8 heap):
| parser | mode | result | wall | items | peak heap |
|---|---|---|---|---|---|
| Stanza | streaming, domain parsing | ok | 13.4 s | 255,001 transactions | 94.7 MB |
| node-x12 | streaming, lax | ok | 13.6 s | 20,655,000 segments | 35.4 MB |
| x12-parser | streaming | ok | 32.3 s | 20,655,000 segments | 32.9 MB |
- On raw throughput with memory to spare, Stanza and node-x12 are statistically tied. "Fastest parser" would be an overclaim.
- Under a hard 25 MB ceiling, Stanza holds its uncapped speed (14.2 s) while node-x12 slows by 50% (13.6 s to 20.3 s) under GC pressure and x12-parser is roughly 2.7x slower than Stanza.
- Stanza emits 255k parsed transaction frames; the libraries emit 20.65M raw segment objects. Stanza does more semantic work per unit and produces 81x fewer objects for the consumer to handle.
- Stanza's uncapped heap is higher because it lets V8 grow when allowed. The capped run is the stronger evidence: the same workload completes inside a 25 MB old-space bound.
-
The parsers do not do identical work. Stanza maps 837P claims into typed frames with SNIP element checks (for example NPI Luhn validation). node-x12 and x12-parser build generic segment trees. That difference is the point: more work, fewer objects, same or better speed.
-
node-x12strict streaming mode throws on a valid single-envelope file:X12 Standard: An EDI document must contain at least one functional group. Its stream path pushes segments without building the interchange object model and then validates that model at flush. Measurements above use lax mode. To reproduce the strict failure:node -e "const {X12Parser}=require('node-x12');const fs=require('fs'); const p=new X12Parser(true);fs.createReadStream('data/claims.x12').pipe(p) .on('data',()=>{}).on('error',e=>console.log(e.message));"
-
node-x12's documented large-file flow retains every emitted segment and calls
getInterchangeFromSegmentsat the end. We discard output for every parser, because retention is a consumer decision, not a parsing cost. Retaining 20.65M segment objects would change the memory picture substantially. -
Single machine, single corpus, synthetic data. Throughput varies with CPU and Node version; run the commands above to measure your own environment.
| Stanza parser | node-x12 | x12-parser | Commercial EDI platforms | |
|---|---|---|---|---|
| License | BSL 1.1 (converts to Apache 2.0) | MIT | MIT | Proprietary |
| Model | Hosted API, parser source visible | Library | Library | Platform / managed service |
| Transaction coverage | 837P, 835, 270/271 | Generic X12 structure | Generic X12 structure | Broad: 837/835/270/271/276/277/820/834/997... |
| Domain validation | 837/835/271 field mapping, SNIP element checks (NPI Luhn and more) | Structural only | Structural only | Full TR3 situational rules, SNIP levels, acknowledgments |
| Streaming | Yes, bounded memory | Yes (see caveats) | Yes | Varies |
| Edge/serverless | Yes, designed for V8 isolates | Runtime-agnostic library | Runtime-agnostic library | Usually server-side |
| Typed SDKs | TypeScript, Python, C#, Rust on four registries | TypeScript types | TypeScript types | Varies |
| MCP / agent tooling | Yes (@stanzaapi/mcp) |
No | No | Rare |
| Trading-partner connectivity | No | No | No | Yes: AS2, SFTP, VAN, clearinghouse networks |
| Mapping / translation | No | Library-level mapping | No | Yes: visual mapping, transformation pipelines |
| Support / compliance programs | Self-serve | Community | Community | Enterprise support, BAAs, audits |
- Connectivity: AS2, SFTP, VANs, and clearinghouse networks are how EDI actually moves between partners. Stanza parses and validates payloads; it does not transport them.
- Mapping and translation: enterprise suites offer visual mapping, code lists, and transformation pipelines. Stanza hands you typed JSON.
- Full spec validation: TR3 situational rules, acknowledgment generation (997/999), and complete SNIP levels are not implemented. Validation is element-level, not claim-adjudication-level.
- Coverage: four healthcare transaction sets, not twenty.
- Ecosystem: one maintainer, no third-party plugin ecosystem, no enterprise support desk, no independent security audit.
- It does not validate ST02/SE02 transaction control consistency. node-x12
catches this in strict mode; Stanza currently accepts it. This surfaced while
building this comparison and is tracked in the adversarial suite
(
structure.st-se-control-mismatch). - The BSL license is source-available, not OSI open source. Libraries above are MIT; if license permissiveness is your priority, they win.
- Benchmarks are single-machine and synthetic. They are reproducible, not independently replicated.
- The tokenizer avoids regular expressions, which is good engineering but not a novel technique. Every high-performance parser does this.
- Verified speed under a hard memory ceiling, which is the constraint that breaks parsers in serverless and edge runtimes.
- Domain parsing (typed 837/835/271 output) at library-level speed.
- Distribution beyond one language: typed clients on npm, PyPI, NuGet, and crates.io, plus an MCP server.
- Breadth across standards in one platform: X12 alongside ISO 20022, PEPPOL, GS1, DSCSA, and others.
- A public benchmark and adversarial test suite, which none of the comparison targets publish.