Skip to main content
Version: v0.0.9

Type Alias: X12OrphanAnchor

X12OrphanAnchor = { groupIndex: number; kind: "interchange"; } | { groupIndex: number; kind: "group"; transactionIndex: number; } | { groupIndex: number; kind: "transaction"; segmentOffset: number; transactionIndex: number; }

Where an orphan sat in the STRUCTURE of the interchange, as opposed to where it sat in the input byte stream. This is what lets serializeX12 put an orphan back without guessing.

X12OrphanSegment.segmentIndex cannot do that job. It indexes the INPUT stream, and the emit is not in input order: ta1Segments are hoisted ahead of the groups, and a doubled terminator's zero-length segment occupies an input index that is never emitted. Either one shifts the output's indices away from the input's, so replaying by index splices the orphan into whatever occupies that slot - measurably, into an 835's ST..SE body, where a re-parse reports nothing. An anchor names a slot in the typed tree instead, which survives both reorderings because it does not mention bytes.

Three kinds, one per structural level:

  • "interchange" - between the ISA and the IEA but outside every functional group. groupIndex is the number of groups that had already closed, so the orphan is emitted immediately before ix.groups[groupIndex] (or before the IEA when groupIndex === ix.groups.length).
  • "group" - inside ix.groups[groupIndex] but outside every transaction set in it. transactionIndex is the number of transactions that had already closed, so the orphan is emitted immediately before that group's transactions[transactionIndex] (or before its GE when transactionIndex === transactions.length).
  • "transaction" - inside an open ST..SE, which only a TA1 can be, since every other segment arriving there is body content. segmentOffset is the number of rawSegments already collected, so the orphan is emitted immediately before rawSegments[segmentOffset]. It is never 0 (the ST is always rawSegments[0]) and never exceeds rawSegments.length. Such an orphan is written back BETWEEN the ST and the SE, so serializeX12 counts it toward SE-01 in spec-clean mode even though it is not on tx.rawSegments - it is a segment of that transaction set per X12.6.

An anchor is a POSITION, so reshaping the model invalidates it. The indices address ix.groups, that group's transactions, and that transaction's rawSegments as they stand on the interchange you pass to serializeX12. Filter or reorder any of those and an orphan's anchor may still resolve while naming a different slot, and it will be emitted there with no warning. An anchor that resolves to nothing is emitted at interchange level before the IEA rather than dropped. Re-parse rather than hand-edit if you need anchors to stay meaningful.

Union Members

Type Literal

{ groupIndex: number; kind: "interchange"; }

groupIndex

readonly groupIndex: number

How many functional groups had closed before this segment arrived.

kind

readonly kind: "interchange"


Type Literal

{ groupIndex: number; kind: "group"; transactionIndex: number; }

groupIndex

readonly groupIndex: number

Index into ix.groups of the group that was open.

kind

readonly kind: "group"

transactionIndex

readonly transactionIndex: number

How many transaction sets in that group had closed.


Type Literal

{ groupIndex: number; kind: "transaction"; segmentOffset: number; transactionIndex: number; }

groupIndex

readonly groupIndex: number

Index into ix.groups of the group that was open.

kind

readonly kind: "transaction"

segmentOffset

readonly segmentOffset: number

How many of that set's rawSegments had been collected.

transactionIndex

readonly transactionIndex: number

Index into that group's transactions of the set that was open.

Example

import { parseX12 } from "@cosyte/x12";
const ix = parseX12(raw);
for (const o of ix.orphanSegments) {
if (o.anchor.kind === "group") console.warn(o.anchor.groupIndex);
}