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.