Skip to main content
Version: v0.1.16

Read a large file one transaction set at a time

You have a file of many transaction sets (a day of remittances, a batch of claims) that is too large to hold whole, or you only need some of what is in it. readTransactionSets reads the file as it arrives and hands you one transaction set at a time, and parseX12 splits a transaction set's segments only when you first read them.

Read as the file arrives​

readTransactionSets takes any iterable or async iterable of chunks, such as a Node Readable, and yields each transaction set as soon as its SE closes it. It keeps nothing it has yielded, so the memory it holds is about one transaction set, whatever the size of the file.

import { get835, readTransactionSets } from "@cosyte/x12";

const isa =
"ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER " +
"*260601*1200*^*00501*000000001*0*P*:~";
const chunks = [
`${isa}GS*HP*SENDER*RECEIVER*20260601*1200*1*X*005010X221A1~ST*835*0001~BPR*I*10*C*CHK`,
"************20260601~TRN*1*CHECK1*9999999999~N1*PR*SAMPLE PAYER~N1*PE*SAMPLE CLINIC*XX*",
"1234567893~SE*6*0001~GE*1*1~IEA*1*000000001~",
];

const payments: string[] = [];
for await (const { delimiters, transaction } of readTransactionSets(chunks)) {
const remit = get835(delimiters, transaction);
payments.push(remit?.payment.totalActualPayment?.toString() ?? "");
}
payments; // => ["10"]

A chunk may end anywhere, inside a segment or inside the ISA. Bytes are read one character per byte, as parseX12 reads a Buffer. Each yielded item carries the interchange's delimiters and isa, the indexes of its group and of itself in that group, and the transaction set itself, which is the one parseX12 of the whole file holds at the same place.

Warnings reach onWarning in the order and at the positions parseX12 reports them, as they are found: a warning on a transaction set before the set is yielded, and one about its group or the interchange (a count or control number that does not match, a missing GE or IEA) when the stream reaches it. { strict: true } throws the first warning, and the four fatal errors are the ones parseX12 throws for the same input.

The stream yields transaction sets only. A TA1 at the envelope level, a segment outside every transaction set and bytes after the IEA are reported through their warnings and not kept; read the file with parseX12 when you need them.

What parseX12 builds, and when​

parseX12 frames every segment and finds every warning during the parse, but leaves the split of a segment into its elements for the first read of its transaction set's segments, which splits all of that set's segments at once. A parse that reads a few transaction sets of a large interchange splits only those. Typed readers such as get835 read segments, so they split the set they read.

segments is an own enumerable property in the place it always was: Object.keys, spread, JSON.stringify and destructuring see what they saw before, and after its first read it is a plain property. Pass { lazy: false } to split every segment during the parse instead. The result is the same either way.

The built-in profiles are built the same way: profiles.availity is built the first time it is read, and every read after it answers the same profile.