@cosyte/astm
Classes
AstmAmbiguousStreamError
Thrown by a flat extractor when the stream it was handed does not determine a single answer: either because it carries several messages, or because its one message carries several patients.
The throw is the fix. A caller who gets this error is strictly better off than one who silently received another patient's results, and the remedy is mechanical: walk messages and read each message's own records.
PHI: carries a stable code, a position, and two counts. Never a field value, never a patient identifier.
Example
import { parseAstmRecords, messages, results, AstmAmbiguousStreamError } from "@cosyte/astm";
const msg = parseAstmRecords(raw);
try {
results(msg);
} catch (err) {
if (err instanceof AstmAmbiguousStreamError) {
for (const m of messages(msg)) m.results.length;
}
}
Extends
Error
Constructors
Constructor
new AstmAmbiguousStreamError(
code,message,position,counts):AstmAmbiguousStreamError
Internal
Parameters
code
message
string
position
counts
messageCount
number
patientCount
number
Returns
Overrides
Error.constructor
Properties
code
readonlycode:AmbiguousCode
The stable discriminant: see AMBIGUOUS_CODES.
messageCount
readonlymessageCount:number
How many H … L messages the stream carries.
patientCount
readonlypatientCount:number
How many P records the message in question carries.
position
readonlyposition:AstmPosition
Where the ambiguity became visible: the second header, or the second P. Value-free.
AstmFrameEncodeError
Thrown by composeAstmFrames when the input cannot be framed into a spec-clean stream. Carries a stable code + positional context, never the record bytes and never the offending character (PHI discipline).
The unencodable-character case, and why it is a refusal. A record passed as
a string is a byte string: character i becomes byte i, which is the
exact inverse of how this package turns record bytes back into a string (one
String.fromCharCode per byte, so every byte survives 1:1). A character above
U+00FF has no byte to become. Turning one into bytes takes a character
encoding, and nothing this package reads from an ASTM stream says which one:
it reads no character-set declaration from any record, so picking one would be
a guess at bytes the caller never supplied. Emit also has no warning channel,
so a warning here could only be ignored while the wrong bytes still shipped.
Framing content outside Latin-1. Encode it yourself, with the code page
your instrument actually uses, and hand composeAstmFrames the
Uint8Array: it accepts bytes directly and writes them through untouched.
The refusal removes no capability, it routes you to the parameter that already
carried it.
The reserved-byte case, and why it has no escape hatch. STX, ETB and
ETX are what decodeAstmFrames reads as frame structure: STX opens
a frame, and the first ETB/ETX after it is the end of that frame's text.
A record carrying one of those bytes therefore cannot be framed and read back,
whichever form it arrives in, and this layer has no escape sequence to hide one
behind. Passing bytes instead of a string does not route around it, because
the byte is the problem rather than the encoding. Take the byte out of the value
before framing: which byte belongs in a clinical value is the sender's call, not
this library's, so it refuses rather than substituting or deleting one.
The start-frame-number case, and why an option gets an error of its own. A
frame's number is a single ASCII digit, FN_ZERO + n for n in 0-7, and
nothing used to check that options.startFrameNumber was one. What went out
instead was whatever byte that sum truncated to: measured on this package's own
round trip, -1 wrote / into the frame-number position and NaN, Infinity
and -Infinity each wrote a NUL byte into every frame of the stream, after
which the decoder read no frame number at all and emitted none of the
records. Out-of-domain values that happened to land back on a digit were worse
in a quieter way: 1.5 and 257 both produced the byte-for-byte stream a
startFrameNumber of 1 produces, so the option silently accepted a value it
documented as invalid. This one refusal is about the caller's own option rather
than about record content, so its message names the value received. Nothing
from the stream is quoted.
Example
import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
try {
composeAstmFrames([]);
} catch (err) {
if (err instanceof AstmFrameEncodeError) err.code; // "ASTM_FRAME_EMPTY_RECORD"
}
Extends
Error
Constructors
Constructor
new AstmFrameEncodeError(
message,recordIndex?,code?,characterIndex?):AstmFrameEncodeError
Internal
Parameters
message
string
recordIndex?
number
code?
AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD"
characterIndex?
number
Returns
Overrides
Error.constructor
Properties
characterIndex?
readonlyoptionalcharacterIndex?:number
Position of the offending character within that record, when applicable:
its index in the string, never the character itself and never its code
point. Enough to find it in the caller's own data. For a record supplied as
a Uint8Array this is the offending byte's index, which is the same
position: a record string is a byte string, so the two indices coincide.
code
readonlycode:AstmFrameEncodeErrorCode
Stable discriminant.
recordIndex?
readonlyoptionalrecordIndex?:number
Index of the offending record within the input, when applicable.
AstmFrameStrictError
Thrown by decodeAstmFrames in strict mode when the lenient codec would
otherwise have accumulated one or more frame warnings (a bad checksum, a
sequence gap, an unterminated frame, or an oversize frame). Carries every
warning (code + position, never the frame's record bytes) so a caller can see
each deviation.
Example
import { decodeAstmFrames, AstmFrameStrictError } from "@cosyte/astm";
try {
decodeAstmFrames(someFramedBytes, { strict: true });
} catch (err) {
if (err instanceof AstmFrameStrictError) err.warnings.length; // >= 1
}
Extends
Error
Constructors
Constructor
new AstmFrameStrictError(
warnings):AstmFrameStrictError
Internal
Parameters
warnings
readonly AstmFrameWarning[]
Returns
Overrides
Error.constructor
Properties
warnings
readonlywarnings: readonlyAstmFrameWarning[]
AstmParseError
Thrown by parseAstmRecords when the input violates one of the Tier-3
unrecoverable structural rules (empty input, no leading H, or an H that
cannot declare its delimiters). Carries positional context so consumers can
log an actionable error.
PHI: the error carries a stable code + position only, never a field value.
Example
import { parseAstmRecords, AstmParseError } from "@cosyte/astm";
try {
parseAstmRecords("P|1");
} catch (err) {
if (err instanceof AstmParseError && err.code === "ASTM_RECORD_NO_HEADER") {
// err.position is available; err carries no field value
}
}
Extends
Error
Constructors
Constructor
new AstmParseError(
code,message,position):AstmParseError
Internal
Construct a new AstmParseError. Both fields are required so every thrower
populates positional context and no PHI-bearing snippet is ever attached.
Parameters
code
message
string
position
Returns
Overrides
Error.constructor
Properties
code
readonlycode:FatalCode
position
readonlyposition:AstmPosition
AstmProfileDefinitionError
Thrown by defineAstmProfile when a profile definition is invalid: a missing or
empty name, an unknown option key, an invalid transport value, or a
tolerate entry whose code is unknown, whose rationale is empty, or, the
load-bearing safety rule, whose code is safety-critical (a result value,
abnormal flag, result status, reference range, units, patient/comment context,
message-kind, code system, an unrecognized record type, or any frame/LTP
integrity warning). Carries the
offending profile's name when it is known, so a multi-profile build names the
culprit.
Example
import { defineAstmProfile, AstmProfileDefinitionError } from "@cosyte/astm";
try {
defineAstmProfile({
name: "unsafe",
tolerate: [{ code: "ASTM_FRAME_BAD_CHECKSUM", rationale: "no" }],
});
} catch (err) {
if (err instanceof AstmProfileDefinitionError) err.profileName; // "unsafe"
}
Extends
Error
Constructors
Constructor
new AstmProfileDefinitionError(
message,profileName?):AstmProfileDefinitionError
Internal
Construct a new AstmProfileDefinitionError.
Parameters
message
string
A human-readable explanation of what was invalid.
profileName?
string
The offending profile's name, when known.
Returns
Overrides
Error.constructor
Properties
profileName?
readonlyoptionalprofileName?:string
The offending profile's name, when it could be read.
AstmSerializeError
Thrown by the record/frame emit side when a value cannot be serialized into a
spec-clean stream: specifically when a component contains a record terminator
(CR/LF), which the ASTM escape codec cannot encode and which would break
framing if emitted raw. Carries a stable code + positional context, never the
offending value (PHI discipline).
Example
import { serializeAstmRecord, AstmSerializeError, parseAstmRecords } from "@cosyte/astm";
const rec = parseAstmRecords("H|\\^&\rL|1\r").records[1]!;
try {
serializeAstmRecord(rec);
} catch (err) {
if (err instanceof AstmSerializeError) err.code; // "ASTM_EMIT_UNENCODABLE_VALUE"
}
Extends
Error
Constructors
Constructor
new AstmSerializeError(
message,recordIndex?,code?):AstmSerializeError
Internal
Parameters
message
string
recordIndex?
number
code?
AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE"
Returns
Overrides
Error.constructor
Properties
code
readonlycode:AstmSerializeErrorCode
Stable discriminant. ASTM_EMIT_UNENCODABLE_VALUE for a CR/LF in a value;
ASTM_EMIT_INVALID_DELIMITERS for a delimiter set that cannot be emitted
reversibly; ASTM_EMIT_TYPE_LETTER_COLLISION for a record whose own type
letter the set being emitted with would escape away.
recordIndex?
readonlyoptionalrecordIndex?:number
0-based ordinal of the record within the message, when known.
AstmStrictError
Thrown by parseAstmRecords in strict mode when the lenient parser
would otherwise have accumulated one or more Tier-2 warnings. Carries the
warnings (code + position, never a value) so a caller can see every deviation.
Example
import { parseAstmRecords, AstmStrictError } from "@cosyte/astm";
try {
parseAstmRecords("H|\\^&\rZ|1\r", { strict: true });
} catch (err) {
if (err instanceof AstmStrictError) err.warnings.length; // >= 1
}
Extends
Error
Constructors
Constructor
new AstmStrictError(
warnings):AstmStrictError
Internal
Parameters
warnings
readonly AstmRecordWarning[]
Returns
Overrides
Error.constructor
Properties
warnings
readonlywarnings: readonlyAstmRecordWarning[]
Interfaces
AbnormalFlag
A recognized (or explicitly unrecognized) abnormal flag. The raw field text
is always preserved; recognized is false and meaning is "undefined"
for any letter outside Table 0078, the flag is surfaced, never dropped, and
never coerced to normal.
Example
import { interpretAbnormalFlag } from "@cosyte/astm";
const f = interpretAbnormalFlag("HH");
f.meaning; // "critically-above-normal"
f.recognized; // true
Properties
code?
readonlyoptionalcode?:AbnormalFlagCode
The Table 0078 code, present only when the raw text is a recognized flag.
meaning
readonlymeaning:AbnormalFlagMeaning
The modeled meaning; "undefined" (never "normal") for an unrecognized flag.
raw
readonlyraw:string
The verbatim field text, exactly as received.
recognized
readonlyrecognized:boolean
Whether the raw text matched a Table 0078 flag.
AstmDate
A parsed ASTM date/time. Immutable plain data; the populated fields extend
exactly as far as AstmDate.precision. Absent components are left
undefined rather than defaulted, so a consumer can tell "midnight" from
"no time given". No timezone is modeled: the value is instrument-local.
Example
import { parseAstmDate } from "@cosyte/astm";
const d = parseAstmDate("20240315");
d?.precision; // "day"
d?.hour; // undefined (not 0)
Properties
day?
readonlyoptionalday?:number
hour?
readonlyoptionalhour?:number
minute?
readonlyoptionalminute?:number
month?
readonlyoptionalmonth?:number
precision
readonlyprecision:AstmDatePrecision
How far the components are populated.
raw
readonlyraw:string
The raw digit string as it appeared on the wire.
second?
readonlyoptionalsecond?:number
truncated?
readonlyoptionaltruncated?:true
true when the digit run does not align to a whole-component boundary: an odd number of
digits that cuts a two-digit component (month/day/hour/minute/second) in half (lengths 5, 7, 9,
11, 13). The full run is preserved in AstmDate.raw and the structured value is truncated
to the last complete component: the dangling digit is never zero-filled into a fabricated
time. Absent (never false) for a clean value. A caller surfaces this as a value-free
ASTM_RECORD_PARTIAL_TIMESTAMP warning.
year
readonlyyear:number
AstmField
One ASTM field, as split from a record. The tree holds decoded component strings (escape sequences already resolved), while AstmField.raw preserves the exact wire text of the field for round-trip and audit.
components is the first repeat's components (the common single-repeat case);
repeats holds every repeat when a field uses the repeat delimiter.
Properties
components
readonlycomponents: readonlystring[]
Components of the first repeat, each escape-decoded. Empty field → [""].
raw
readonlyraw:string
The exact field text as it appeared on the wire (escapes NOT decoded).
repeats
readonlyrepeats: readonly readonlystring[][]
Every repeat, each an array of decoded components. repeats[0] === components.
AstmFrame
One decoded ASTM frame. text is the frame's record-byte payload (the bytes
between the frame number and the terminator, escapes untouched, this is the
framing layer, not the record layer). trusted is the single flag a consumer
gates on: it is true only for a fully-terminated frame whose checksum
validated, and such frames are the only ones reassembled into records.
Properties
byteOffset
readonlybyteOffset:number
Byte offset of the STX that opened this frame, within the decoded stream.
checksum
readonlychecksum:FrameChecksum
The checksum verdict.
frameNumber?
readonlyoptionalframeNumber?:number
The frame's sequence number as read from the FN byte. Normally 0–7; a
value outside that range means the FN byte was not a 0–7 digit (a
corruption that also trips a sequence-gap warning). undefined when the frame
was too truncated to carry a frame number at all.
oversize
readonlyoversize:boolean
true when the frame's record text exceeded the 240-byte limit.
terminator?
readonlyoptionalterminator?:FrameTerminator
ETB (intermediate) or ETX (final); undefined for an unterminated frame.
text
readonlytext:Uint8Array
The frame's record-byte payload (may be empty). A copy, safe for the caller to retain.
trusted
readonlytrusted:boolean
true only when the frame was fully terminated and its checksum validated.
Only trusted frames are reassembled into DecodeAstmFramesResult.records;
an untrusted frame is surfaced here but never merged.
unterminated
readonlyunterminated:boolean
true when no valid terminator + checksum was found for this frame (a truncated/partial frame).
AstmFramePosition
Where in a framed byte stream a warning or error originated. Both members are positional; neither is a value.
Example
import type { AstmFramePosition } from "@cosyte/astm";
const at: AstmFramePosition = { frameNumber: 2, byteOffset: 251 };
Properties
byteOffset
readonlybyteOffset:number
Byte offset of the STX that opened the frame, within the decoded stream.
frameNumber?
readonlyoptionalframeNumber?:number
The frame's sequence number (0–7) when it could be read, or undefined
when the frame was so truncated the number was not present.
AstmFrameWarning
A single frame-codec warning: a stable code, a value-free human-readable message, and positional context (frame number + byte offset).
Example
import type { AstmFrameWarning } from "@cosyte/astm";
const w: AstmFrameWarning = {
code: "ASTM_FRAME_BAD_CHECKSUM",
message: "Frame checksum mismatch.",
position: { frameNumber: 2, byteOffset: 251 },
};
Properties
code
readonlycode:FrameWarningCode
message
readonlymessage:string
Human-readable detail for logs. Never contains the frame's record bytes.
position
readonlyposition:AstmFramePosition
AstmLivdWarning
A single terminology (LIVD) warning: a stable code, a value-free human-readable message, and positional context. Never carries a test code, a value, or a LOINC.
Example
import type { AstmLivdWarning } from "@cosyte/astm";
const w: AstmLivdWarning = {
code: "ASTM_LIVD_UNMAPPED_CODE",
message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
position: { recordIndex: 3, recordType: "R" },
};
Properties
code
readonlycode:LivdWarningCode
message
readonlymessage:string
Human-readable detail for logs. Never contains a test code, a value, or a LOINC.
position
readonlyposition:AstmPosition
AstmLtpWarning
A single LTP protocol / transport warning: a stable code, a value-free human-readable message, and, for a frame-scoped deviation, the frame number only. Never carries a frame's record bytes.
Example
import type { AstmLtpWarning } from "@cosyte/astm";
const w: AstmLtpWarning = {
code: "ASTM_LTP_FRAME_REJECTED",
message: "Frame rejected, NAK sent, retransmit expected.",
frameNumber: 2,
};
Properties
code
readonlycode:LtpWarningCode
frameNumber?
readonlyoptionalframeNumber?:number
The frame's sequence number when the warning is frame-scoped; absent otherwise.
message
readonlymessage:string
Human-readable detail for logs. Never contains the frame's record bytes.
AstmMessage
A parsed ASTM message: the header, the ordered records (the header is also
records[0]), the resolved delimiters, and the accumulated warnings.
Example
import { parseAstmRecords } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rL|1\r");
msg.header.delimiters.field; // "|"
msg.records.length; // 2
Properties
classification
readonlyclassification:AstmMessageClassification
The host-query classification of this message: whether it is a request (Q
present), a result upload, an order download, or indeterminate. Gate on
AstmMessageClassification.isHostQueryRequest before reading records as
results.
delimiters
readonlydelimiters:Delimiters
The delimiters the first header declared.
A stream may carry several messages (H … L), and a later header may declare a different set,
which is honored from that header onward. So this is not necessarily the set every record was
read with: each header reports its own on HeaderRecord.delimiters, and a change is
flagged ASTM_RECORD_DELIMITERS_REDECLARED.
header
readonlyheader:HeaderRecord
The first header in the stream. A multi-header stream's later headers are in AstmMessage.records.
profile?
readonlyoptionalprofile?:object
The vendor profile that was active for this parse, when one applied: its
name and resolved lineage (attribution only; the profile object itself is
not embedded). Absent when no profile applied. A profile that tolerated a
deviation shows up as a PROFILE_QUIRK_APPLIED entry in AstmMessage.warnings, not
here.
lineage
readonlylineage: readonlystring[]
name
readonlyname:string
records
readonlyrecords: readonlyAstmRecord[]
warnings
readonlywarnings: readonlyAstmRecordWarning[]
AstmMessageClassification
The message-level classification from the host-query flow. isHostQueryRequest
is the single boolean a consumer should gate on before treating records as
results: it is true iff a Q record is present.
Example
import { parseAstmRecords } from "@cosyte/astm";
const req = parseAstmRecords("H|\\^&\rP|1\rQ|1|^SPEC-7||ALL\rL|1\r");
req.classification.kind; // "host-query"
req.classification.isHostQueryRequest; // true, never read its records as results
Properties
hasOrders
readonlyhasOrders:boolean
At least one O (order) record is present.
hasQuery
readonlyhasQuery:boolean
At least one Q (request-information) record is present.
hasResults
readonlyhasResults:boolean
At least one R (result) record is present.
hasUnrecognized
readonlyhasUnrecognized:boolean
At least one record's type letter was not recognized (an UnsupportedRecord), so the letter counts above are known to be incomplete.
Any of them may have been a Q, which is why a message carrying one is classified
indeterminate unless a Q was read outright. ASTM_RECORD_UNKNOWN_TYPE reports the same
condition on AstmMessage.warnings.
isHostQueryRequest
readonlyisHostQueryRequest:boolean
true iff kind === "host-query" (a Q record was read), the safety surface: gate on this
before treating records as results, so a query is never misread as a result upload.
false is not a warrant that the message is a result set. Read kind for that: it is
indeterminate whenever the reader could not account for every type letter.
kind
readonlykind:AstmMessageKind
The message kind.
indeterminate also covers a message the reader declines to classify: when
AstmMessageClassification.hasUnrecognized is true and no Q was read, the kind is
withheld rather than guessed, because the unreadable letter may have been that Q. The has*
flags below stay truthful in that case, so a caller that wants the raw counts still has them.
AstmParseOptions
Options for parseAstmRecords. Lenient by default (Postel's Law).
Properties
profile?
readonlyoptionalprofile?:AstmProfile|null
An active vendor AstmProfile. Its quirk tolerances downgrade the
deviations it expects to PROFILE_QUIRK_APPLIED warnings (values are never
altered). An explicit profile wins over the process-scoped default
(setDefaultAstmProfile); pass null to opt out of the default for a
single call. Omit to use the default, or no profile when none is registered.
strict?
readonlyoptionalstrict?:boolean
When true, escalate any Tier-2 deviation to a thrown AstmStrictError
instead of accumulating a warning. Off by default. A deviation a profile
expected (a PROFILE_QUIRK_APPLIED) does not escalate: it is known,
benign, and still recorded.
AstmPosition
Where in a record stream a warning or fatal originated. Every field is positional; none is a value.
Example
import type { AstmPosition } from "@cosyte/astm";
const at: AstmPosition = { recordIndex: 3, recordType: "R", fieldIndex: 4 };
Properties
componentIndex?
readonlyoptionalcomponentIndex?:number
1-based component index within the field, when the deviation is component-scoped.
fieldIndex?
readonlyoptionalfieldIndex?:number
1-based field index within the record (ASTM fields are 1-indexed).
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
recordType?
readonlyoptionalrecordType?:string
The record's type letter (H/P/O/R/L/…), when known.
AstmProfile
A frozen, immutable vendor/conformance profile. Produced by
defineAstmProfile; consumers pass it to parseAstmRecords(raw, { profile })
(or register it as the process default), and feed its AstmProfile.transport
to detectFraming(bytes, { override }). Hand-authoring the object literal is
supported but discouraged: the factory validates the safety rules and attaches
describe().
Example
import { parseAstmRecords, astmProfiles } from "@cosyte/astm";
const msg = parseAstmRecords(raw, { profile: astmProfiles.referenceCorpus });
msg.profile?.name; // "referenceCorpus"
Properties
describe?
readonlyoptionaldescribe?: () =>string
Multi-line human-readable summary; always present on factory-built profiles.
Returns
string
description?
readonlyoptionaldescription?:string
Optional human-readable description.
lineage
readonlylineage: readonlystring[]
Resolved lineage: [...parents, name], first-occurrence deduped.
name
readonlyname:string
The profile's unique name (registry key / attribution label).
provenance?
readonlyoptionalprovenance?:AstmProfileProvenance
The cited public grounding for this profile's quirks (absent for default).
tolerate
readonlytolerate: readonlyAstmQuirkTolerance[]
The expected, non-safety-critical deviations this profile tolerates.
transport?
readonlyoptionaltransport?:AstmFraming
The raw-vs-framed-TCP override. When set, a consumer forces this framing via
detectFraming(bytes, { override: profile.transport }), the way a profile
that knows a vendor's transport reality (e.g. framing dropped over raw TCP)
bypasses leading-byte auto-detection. Absent means "let detection decide."
AstmProfileProvenance
Provenance for an AstmProfile: the real, cited public artifact a profile's quirks are grounded in. A quirk is encoded only when a real (de-identified) document or a redistributable reference corpus grounds it; this record is where that grounding is stated, so a reviewer can trace every tolerated deviation back to evidence rather than invention.
Example
import type { AstmProfileProvenance } from "@cosyte/astm";
const prov: AstmProfileProvenance = {
source: "kxepal/python-astm codec.py",
reference: "https://github.com/kxepal/python-astm/blob/master/astm/codec.py",
retrieved: "2026-07-21",
};
Properties
note?
readonlyoptionalnote?:string
Optional clarifying note about what in the source grounds the quirks.
reference
readonlyreference:string
A citation the grounding can be traced to: a URL, DOI, or repo+path.
retrieved?
readonlyoptionalretrieved?:string
When the grounding was last verified (ISO date) or the pinned commit SHA.
source
readonlysource:string
Short human-readable name of the grounding source (corpus, transcript, or spec).
AstmQuirkMatch
Optional structural narrowing for an AstmQuirkTolerance. When present, the
tolerance applies only to warnings whose PHI-free AstmPosition matches
every provided field, so a profile can expect a deviation on one record type
(e.g. an unknown escape only inside R result values) without blanket-tolerating
it everywhere. Matching is on structural identifiers only (record-type
letter, 1-based field index); there is no matching on any field value, by
construction.
Example
import type { AstmQuirkMatch } from "@cosyte/astm";
const onlyResultUnits: AstmQuirkMatch = { recordType: "R", fieldIndex: 5 };
Properties
fieldIndex?
readonlyoptionalfieldIndex?:number
Match only warnings carrying this 1-based field index in their position.
recordType?
readonlyoptionalrecordType?:string
Match only warnings carrying this record-type letter in their position.
AstmQuirkTolerance
One expected deviation declared by a profile. code names an existing,
non-safety-critical warning code the profile expects; rationale documents
why (grounded in the profile's AstmProfileProvenance); optional match
narrows it to a structural location. defineAstmProfile throws if code is
safety-critical or is not a real warning code, or if rationale is empty.
Example
import type { AstmQuirkTolerance } from "@cosyte/astm";
const t: AstmQuirkTolerance = {
code: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
rationale: "Corpus stacks that treat '&' as literal data emit non-standard escape bodies.",
};
Properties
code
readonlycode:AnyAstmWarningCode
The existing, non-safety-critical warning code this profile expects.
match?
readonlyoptionalmatch?:AstmQuirkMatch
Optional structural narrowing (record type / field index).
rationale
readonlyrationale:string
Why the profile expects this deviation: grounded in its provenance.
AstmRecordWarning
A single Tier-2 warning: a stable code, a value-free human-readable message,
and positional context. Plain data, accumulated onto AstmMessage.warnings.
Example
import type { AstmRecordWarning } from "@cosyte/astm";
const w: AstmRecordWarning = {
code: "ASTM_RECORD_UNKNOWN_TYPE",
message: "Unknown record type.",
position: { recordIndex: 2, recordType: "Z" },
};
Properties
code
readonlycode:WarningCode
expected?
readonlyoptionalexpected?:boolean
true when an active vendor AstmProfile expected this deviation and re-badged it as a
WARNING_CODES.PROFILE_QUIRK_APPLIED. An expected warning does not escalate to a
thrown AstmStrictError in strict mode (the whole point of the profile is that this deviation is
known and benign): it is still recorded, so nothing is hidden. Absent on an untolerated warning.
message
readonlymessage:string
Human-readable detail for logs. Never contains a field value.
position
readonlyposition:AstmPosition
profile?
readonlyoptionalprofile?:string
The name of the AstmProfile that tolerated this warning, when expected.
toleratedCode?
readonlyoptionaltoleratedCode?:WarningCode
When code is WARNING_CODES.PROFILE_QUIRK_APPLIED, the original warning code the profile
tolerated, so a consumer can still see which deviation was re-badged as expected.
AstmStreamMessage
One H … L message inside a parsed stream, with its own records and nothing else.
This is the unit the flat extractors were always assumed to be reading. AstmMessage
models the whole stream; this models a message within it.
Properties
comments
readonlycomments: readonlyCommentRecord[]
Every C (comment) record in this message, in wire order.
delimiters
readonlydelimiters:Delimiters
The delimiter set in force for this message: the header's own resolved set, which is the set its records were actually read with. When a later header's declaration was unusable, this is the set that stayed in force, never a guessed one.
header
readonlyheader:HeaderRecord
This message's header record.
index
readonlyindex:number
0-based ordinal of this message within the stream.
orders
readonlyorders: readonlyOrderRecord[]
Every O (order) record in this message, in wire order.
patient
readonlypatient:PatientRecord|undefined
This message's patient, but only when the message determines one: the single P
when it carries exactly one, and undefined when it carries none or several.
patients.length distinguishes those two cases; there is no third meaning.
A message carrying several patients is not answered with the first one, because
"the first P" is precisely the guess that files a result against the wrong person.
patients
readonlypatients: readonlyPatientRecord[]
Every P record in this message, in wire order (usually zero or one).
queries
readonlyqueries: readonlyQueryRecord[]
Every Q (request-information) record in this message, in wire order.
For this message's host-query classification, call classifyMessage(m.records). The
classification on the parsed model is folded over the whole stream, so it is not
per-message and is not mirrored here; deriving it from a message's own records is.
records
readonlyrecords: readonlyAstmRecord[]
Every record of this message in wire order, the header first.
results
readonlyresults: readonlyResultRecord[]
Every R (result) record in this message, in wire order.
CommentInput
Input for a C (comment) record.
Properties
commentType?
readonlyoptionalcommentType?:string
Field 5: comment type code, emitted verbatim.
seq?
readonlyoptionalseq?:string
source?
readonlyoptionalsource?:string
Field 3: comment source.
text?
readonlyoptionaltext?:string
Field 4: comment text (a single component). Use CommentInput.textComponents for a structured comment.
textComponents?
readonlyoptionaltextComponents?: readonlystring[]
Field 4: comment text as explicit components (takes precedence over text when set).
type
readonlytype:"C"
CommentRecord
The C (comment) record: free-text context attached to a parent record.
A comment is attached by position to the immediately-preceding H/P/O/R record
(CommentRecord.parentIndex); consecutive comments share that parent. Fail-safe: a
comment with no valid preceding parent is an orphan: it is attached to the message root
(CommentRecord.attachedToRoot true) and a value-free ASTM_RECORD_ORPHAN_COMMENT warning
fires, never silently dropped, so a comment carrying (e.g.) "QC / non-compliant" context can
never float away unnoticed.
Extends
RecordBase
Properties
attachedToRoot
readonlyattachedToRoot:boolean
true when no valid parent preceded: the comment is attached to the message root (and warned).
commentType?
readonlyoptionalcommentType?:string
Field 5: comment type code, surfaced verbatim. [OSS-derived]: I (instrument) is the only
value seen in the permissively-licensed real transcripts; other values (e.g. G/T/P) are
defined only in the paywalled CLSI LIS02-A2 and are not interpreted here: the raw code is
surfaced, never mapped to a guessed meaning.
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
parentIndex?
readonlyoptionalparentIndex?:number
The recordIndex of the H/P/O/R record this comment is attached to, or undefined when
the comment is an orphan attached to the message root (see CommentRecord.attachedToRoot).
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
seq?
readonlyoptionalseq?:string
Field 2: sequence number.
source?
readonlyoptionalsource?:string
Field 3: comment source (who/what produced it), surfaced verbatim.
text?
readonlyoptionaltext?:string
Field 4: the comment text, surfaced as the full field text (all components), never truncated to the first component. The component structure is in CommentRecord.textComponents.
textComponents?
readonlyoptionaltextComponents?: readonlystring[]
Field 4: the comment text split into its decoded components (comment text is component-capable; multiple components are a normal structured comment, not an ambiguity). Present only when the field carried more than one component.
type
readonlytype:"C"
The record's raw type letter.
Overrides
RecordBase.type
ComposeFramesOptions
Options for composeAstmFrames.
Properties
startFrameNumber?
readonlyoptionalstartFrameNumber?:number
The frame number to start the sequence at: a whole number from 0 to 7.
Defaults to 1, the number ASTM gives the first frame of a transfer and the
one decodeAstmFrames expects to read first.
Any other value writes a continuation rather than the start of a transfer,
and that is what the option is for: composing one transfer across more than
one call. Concatenating composeAstmFrames(head) with
composeAstmFrames(tail, { startFrameNumber: n }), where n is the number
after the last frame head used, is byte-identical to composing the whole
list in a single call and decodes with an empty warnings array, across the
7 → 0 rollover included.
Decoded on its own, a stream that starts anywhere but 1 opens on a
sequence gap. The decoder never bridges a gap silently, so it warns
(ASTM_FRAME_SEQUENCE_GAP) and does not emit that first record.
What parseFramedAstm does after that varies with the message
shape and no rule is offered for it here. It may throw, under more than
one code, and it may return a message that is simply one record short. The
one thing that does hold is that the record layer never reports the loss:
parseFramedAstm hands the record parser only the frames the codec
vouched for, so message.warnings carries what the surviving records
warrant and nothing about the record that did not survive. Read
frameWarnings. That is the cost of the option rather than a defect in
the caller's records, and it is why 1 is the default.
Finding the number to continue from. Nothing here returns it, and the
frame count is not it once a record splits: decode the part you just
composed and read the last frame's number, then add one modulo 8, as in
((decodeAstmFrames(part).frames.at(-1)?.frameNumber ?? 0) + 1) % 8. A frame
carries no number only when the stream ends immediately after its STX,
which is not something this encoder writes, so that fallback never fires on
a part composed here. Getting the number wrong costs the record at the join,
warned rather than silently.
A value outside 0-7, or one that is not a whole number, is refused with
ASTM_FRAME_INVALID_START_FRAME_NUMBER.
DecodeAstmFramesResult
The result of decodeAstmFrames: the reassembled record bytes, every decoded frame, and the accumulated frame warnings.
Properties
frames
readonlyframes: readonlyAstmFrame[]
Every decoded frame, trusted or not, in wire order.
records
readonlyrecords: readonlyUint8Array<ArrayBufferLike>[]
The reassembled record byte-strings: one entry per complete record (a run of
frames closed by an ETX), in wire order. Only clean reassemblies appear:
a record whose frames included a bad checksum, a sequence gap, or an
unterminated frame is omitted (its frames are still in DecodeAstmFramesResult.frames,
flagged). Each entry is ready to hand to parseAstmRecords.
warnings
readonlywarnings: readonlyAstmFrameWarning[]
The frame warnings accumulated during a lenient decode (empty in a clean decode).
DefineAstmProfileOptions
Options accepted by defineAstmProfile. Mirrors the AstmProfile
shape minus the derived lineage/describe, plus the extends input key. Every
field except name is optional.
Example
import { defineAstmProfile, type DefineAstmProfileOptions } from "@cosyte/astm";
const opts: DefineAstmProfileOptions = {
name: "my-analyzer",
transport: "raw",
tolerate: [{ code: "ASTM_NONSTANDARD_DELIMITERS", rationale: "declares its own set" }],
};
const p = defineAstmProfile(opts);
Properties
description?
readonlyoptionaldescription?:string
extends?
readonlyoptionalextends?:AstmProfile| readonlyAstmProfile[]
name
readonlyname:string
provenance?
readonlyoptionalprovenance?:AstmProfileProvenance
tolerate?
readonlyoptionaltolerate?: readonlyAstmQuirkTolerance[]
transport?
readonlyoptionaltransport?:AstmFraming
Delimiters
The four ASTM delimiters resolved from an H record. Immutable; carried on
the parsed message as delimiter provenance.
Example
import { readDelimiters } from "@cosyte/astm";
const d = readDelimiters("H|\\^&");
d.field; // "|"
d.repeat; // "\\"
d.component; // "^"
d.escape; // "&"
Properties
component
readonlycomponent:string
Component separator: ASTM ^ by default.
escape
readonlyescape:string
Escape character: ASTM & by default (introduces &F&/&S&/&R&/&E&).
field
readonlyfield:string
Field separator: the char immediately after H in the header.
repeat
readonlyrepeat:string
Repeat (repetition) separator: ASTM `` by default.
DetectFramingOptions
Options for detectFraming.
Properties
override?
readonlyoptionaloverride?:AstmFraming
A profile-supplied override. When set, detection is bypassed entirely and this value is returned with no warning: the way a vendor profile forces raw for a cobas b121 or framed for a cobas 4800 regardless of the leading byte.
DetectFramingResult
The result of detectFraming: the decided framing plus any warning
(exactly one ASTM_LTP_AMBIGUOUS_TRANSPORT when the lead was unrecognizable and
the mode was defaulted; empty otherwise).
Properties
defaulted
readonlydefaulted:boolean
true when the lead byte was unrecognizable and framing was defaulted (not inferred).
framing
readonlyframing:AstmFraming
The decided transport framing.
warnings
readonlywarnings: readonlyAstmLtpWarning[]
The detection warnings (a single ambiguity warning, or none).
FrameChecksum
A frame's checksum verdict: the modulo-256 value recomputed from the frame's
bytes, the value declared on the wire (or undefined when it was unreadable),
and whether they matched.
Properties
computed
readonlycomputed:number
The modulo-256 checksum recomputed over the frame (frame number through terminator).
declared?
readonlyoptionaldeclared?:number
The checksum declared on the wire (the two hex chars after the terminator), read
case-insensitively; undefined when those bytes were missing or not hex.
valid
readonlyvalid:boolean
true only when a declared checksum was present and equal to computed.
FramedAstmResult
The result of parseFramedAstm: the parsed message, plus the frame-layer detail (every decoded frame and the frame warnings) so a consumer keeps full visibility into the transport below the records.
Properties
frames
readonlyframes: readonlyAstmFrame[]
Every decoded frame, trusted or not, in wire order.
frameWarnings
readonlyframeWarnings: readonlyAstmFrameWarning[]
The frame-layer warnings (bad checksum, sequence gap, unterminated, oversize).
message
readonlymessage:AstmMessage
The message parsed from the trusted, reassembled record bytes.
FrameOptions
Options for decodeAstmFrames. Lenient by default (Postel's Law).
Properties
strict?
readonlyoptionalstrict?:boolean
When true, escalate any tolerated frame deviation (bad checksum, sequence
gap, unterminated, oversize) to a thrown AstmFrameStrictError instead
of accumulating a warning. Off by default.
HeaderInput
Optional header fields (after the delimiter declaration).
Properties
fields?
readonlyoptionalfields?: readonlystring[]
Extra H-record fields (field 3 onward), emitted verbatim in order. Field 2 is always \^&.
HeaderRecord
The H (header) record. Carries the delimiters it declared as provenance.
Extends
RecordBase
Properties
delimiters
readonlydelimiters:Delimiters
The four delimiters this header put into force: the set its own fields, and every record after it, were read with.
A stream may carry several messages (H … L) and each header declares its own set, so on a
multi-header stream these can differ between headers; AstmMessage.delimiters is the
first header's. When a later header cannot declare a usable set, the set already in force is
kept and reported here (with an ASTM_RECORD_UNREADABLE_REDECLARATION warning) rather than a
guessed one.
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
rawLine
readonlyrawLine:string
The header's exact wire text (terminator excluded), kept as provenance: the delimiters are read from this raw text, never from RecordBase.fields.
It is not what the serializer emits from. To change a header field, edit
RecordBase.fields: fields[0] is the type letter, fields[1] is the
delimiter declaration, and the header's ASTM data fields follow from fields[2].
Editing this string instead has no effect on emit.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
type
readonlytype:"H"
The record's raw type letter.
Overrides
RecordBase.type
LivdAnnotation
One record's LIVD annotation, which record, the code that was looked up (verbatim), how it was recognized, and the mapping outcome. Additive: it points at the record by index and never replaces it.
Example
import type { LivdAnnotation } from "@cosyte/astm";
const a: LivdAnnotation = {
recordIndex: 3,
recordType: "R",
reportedCode: "687",
provenance: "local-code",
mapping: { status: "mapped", loinc: "1920-8", source: "livd", derived: true },
};
Properties
mapping
readonlymapping:LivdMapping
The mapping outcome: never a guessed LOINC.
provenance
readonlyprovenance:UniversalTestIdProvenance
How the reported code was recognized in the Universal Test ID (provenance only, never a lookup).
recordIndex
readonlyrecordIndex:number
The recordIndex of the annotated R/O record.
recordType
readonlyrecordType:"O"|"R"
The annotated record's type.
reportedCode?
readonlyoptionalreportedCode?:string
The reported primary code that was looked up, verbatim; absent when the record carried no code.
LivdCatalog
An immutable, consumer-supplied LIVD catalog. Look a vendor code up with LivdCatalog.lookup; the catalog never picks between conflicting LOINCs and never mutates. Build one with defineLivdCatalog.
Properties
size
readonlysize:number
The number of distinct vendor codes indexed (not the number of input rows).
Methods
lookup()
lookup(
vendorCode):LivdLookup
Look a vendor code up, verbatim (exact, case-sensitive). Returns mapped on a
single-LOINC hit, unmapped on a miss, and ambiguous when the code carries
more than one distinct LOINC: never a guess.
Parameters
vendorCode
string
The reported vendor/local test code.
Returns
The lookup outcome.
LivdEntry
One LIVD mapping row a consumer supplies: a vendor test code and the LOINC it maps to, plus optional human-readable / provenance fields. Modeled on the IICC LIVD digital format's data elements.
Example
import type { LivdEntry } from "@cosyte/astm";
const e: LivdEntry = { vendorCode: "687", loinc: "1920-8", loincLongName: "AST" };
Properties
loinc
readonlyloinc:string
The LOINC Code this vendor code maps to (e.g. "1920-8"). Taken from the consumer's catalog
as-is and never validated, altered, or invented: the parser does not ship a LOINC table and
cannot check it; it only carries what the catalog says.
loincLongName?
readonlyoptionalloincLongName?:string
The LOINC Long Common Name, when the catalog supplies it: an optional human-readable label.
manufacturer?
readonlyoptionalmanufacturer?:string
The device Manufacturer, when the catalog scopes the mapping to a device (optional provenance).
model?
readonlyoptionalmodel?:string
The device Model, when the catalog scopes the mapping to a device (optional provenance).
vendorAnalyteName?
readonlyoptionalvendorAnalyteName?:string
The Vendor Analyte Name: the vendor's human-readable analyte label, when supplied.
vendorCode
readonlyvendorCode:string
The Vendor Analyte Code: the vendor transmission code the instrument sends (the local code in an ASTM Universal Test ID, component 4). The mapping key; compared verbatim (exact, case-sensitive) against the reported code, never normalized or fuzzy-matched.
LivdResult
The result of applyLivd: the per-record annotations (one per R/O
record) and the value-free warnings for every unmapped or ambiguous code. Both
arrays are deeply frozen; the source message is untouched.
Properties
annotations
readonlyannotations: readonlyLivdAnnotation[]
One annotation per R/O record, in wire order.
warnings
readonlywarnings: readonlyAstmLivdWarning[]
A value-free warning per unmapped/ambiguous code: never per mapped/inline-loinc/no-code.
LtpState
The reducer's immutable session state. Every ltpReduce call returns a new,
frozen LtpState; the previous one is never mutated.
Properties
expectedFrame
readonlyexpectedFrame:number
The next frame number the receiver expects (1 → … → 7 → 0 → …), meaningful in
transfer. A trusted frame carrying this number is accepted and appended; any
other number is a duplicate retransmit (idempotent re-ACK) or an out-of-sequence
frame (rejected with NAK), never silently bridged.
lastAcceptedFrame?
readonlyoptionallastAcceptedFrame?:number
The frame number of the last frame accepted into the current record, used to
recognise a duplicate retransmit; undefined before the first frame of a
transfer is accepted.
openRecord
readonlyopenRecord:Uint8Array
The bytes accumulated so far for the in-progress record: the concatenation of
the ETB frames accepted since the last ETX. Empty when no record is open.
These bytes are never delivered on their own: only an ETX completes a
record, at which point they (plus the final frame's text) become one entry in
LtpState.records. An EOT or an ENQ restart discards them unread.
phase
readonlyphase:LtpPhase
The current protocol phase.
recordOpen
readonlyrecordOpen:boolean
true when a record is mid-reassembly (an ETB frame was accepted, awaiting its ETX).
records
readonlyrecords: readonlyUint8Array<ArrayBufferLike>[]
The reassembled bytes of every complete record delivered so far in the
session (each closed by an ETX frame with a clean run). Concatenate these and
hand them to parseAstmRecords to get the message. Only trusted, in-sequence
frames contribute; a rejected or partial record never appears.
LtpTransition
The result of one ltpReduce step: the next LtpState, the LtpActions to perform (in order), and any AstmLtpWarnings raised.
Properties
actions
readonlyactions: readonlyLtpAction[]
The actions the consumer should take, in order.
state
readonlystate:LtpState
The next session state (frozen).
warnings
readonlywarnings: readonlyAstmLtpWarning[]
Warnings raised by this step (empty on a clean, expected event).
ManufacturerRecord
The M (manufacturer) record: vendor-defined free-form data
(QC / calibration / maintenance), surfaced VERBATIM and never interpreted
into typed clinical fields.
Interpreting a vendor M record as clinical data would be a fabrication, so
this record carries no typed accessors at all: the exact wire text is in
ManufacturerRecord.rawLine (byte-preserving) and the tokenized tree in
RecordBase.fields. Nothing is parsed into a value, a code, or a unit.
Extends
RecordBase
Properties
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
rawLine
readonlyrawLine:string
The record's exact wire text (terminator excluded), preserved byte-for-byte.
Emit reproduces these bytes exactly whenever a reader using the delimiters being
emitted against would recover the fields this record models: always the case when
the record is already in those delimiters, and also when it carries no delimiter
either set would split on. Otherwise the record is re-encoded from
RecordBase.fields, so the row can never go out in delimiters the header does
not declare. To change a value, edit fields; editing this string has no effect on emit.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
type
readonlytype:"M"
The record's raw type letter.
Overrides
RecordBase.type
MessageInput
The message to build: an optional header, the body records, and an optional terminator code.
Properties
header?
readonlyoptionalheader?:HeaderInput
Header fields; the canonical H|\^& declaration is always emitted.
records
readonlyrecords: readonlyAstmRecordInput[]
The body records, in order. H and L are supplied by the builder.
terminationCode?
readonlyoptionalterminationCode?:string
Field 3 of the auto-appended L (terminator) record: the termination code
(e.g. N normal). Omitted by default (not defaulted to a value the caller
did not choose); the L seq is always emitted.
OrderInput
Input for an O (order) record.
Properties
actionCode?
readonlyoptionalactionCode?:string
Field 12: action code, emitted verbatim.
instrumentSpecimenId?
readonlyoptionalinstrumentSpecimenId?:string
Field 4: instrument specimen ID.
priority?
readonlyoptionalpriority?:string
Field 6: priority, emitted verbatim.
reportType?
readonlyoptionalreportType?:string
Field 26: report type, emitted verbatim.
seq?
readonlyoptionalseq?:string
specimenId?
readonlyoptionalspecimenId?:string
Field 3: specimen / accession ID.
type
readonlytype:"O"
universalTestId?
readonlyoptionaluniversalTestId?: readonlystring[]
Field 5: Universal Test ID components (verbatim, in order; e.g. ["", "", "", "687"]).
OrderRecord
The O (order) record: binds a result to a specimen.
Extends
RecordBase
Properties
actionCode?
readonlyoptionalactionCode?:string
Field 12: action code, surfaced raw (e.g. C cancel, A add, N new). The exact field index
(~12) and the code set are [OSS-derived] (paywalled): surfaced verbatim, never interpreted.
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
instrumentSpecimenId?
readonlyoptionalinstrumentSpecimenId?:string
Field 4: instrument specimen ID.
priority?
readonlyoptionalpriority?:string
Field 6: priority, surfaced raw (e.g. S STAT, R routine, A ASAP). Vendor letters vary;
the code set is [OSS-derived] (the exact enumeration is in the paywalled CLSI LIS02-A2), so the
value is surfaced verbatim and never mapped to a guessed meaning.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
reportType?
readonlyoptionalreportType?:string
Field 26: report type, surfaced raw (e.g. F final, P preliminary, X cancel). The exact
field index (~26) and the code set are [OSS-derived] (paywalled): surfaced verbatim.
seq?
readonlyoptionalseq?:string
Field 2: sequence number.
specimenId?
readonlyoptionalspecimenId?:string
Field 3: specimen / accession ID.
type
readonlytype:"O"
The record's raw type letter.
Overrides
RecordBase.type
universalTestId?
readonlyoptionaluniversalTestId?:UniversalTestId
Field 5: Universal Test ID (same caret structure as a result's).
PatientInput
Input for a P (patient) record. The three IDs stay distinct: none defaults from another.
Properties
birthDate?
readonlyoptionalbirthDate?:string
Field 8: birthdate (YYYYMMDDHHMMSS), emitted verbatim, never reformatted.
laboratoryAssignedId?
readonlyoptionallaboratoryAssignedId?:string
Field 4: laboratory-assigned patient ID.
mothersMaidenName?
readonlyoptionalmothersMaidenName?:string
Field 7: mother's maiden name.
name?
readonlyoptionalname?:PatientNameInput
Field 6: patient name (Last^First^Middle).
patientIdThree?
readonlyoptionalpatientIdThree?:string
Field 5: a third patient identifier.
practiceAssignedId?
readonlyoptionalpracticeAssignedId?:string
Field 3: practice-assigned patient ID.
seq?
readonlyoptionalseq?:string
Structural sequence number; auto-computed when omitted.
sex?
readonlyoptionalsex?:string
Field 9: sex, emitted verbatim (never defaulted).
type
readonlytype:"P"
PatientName
A patient name (Last^First^Middle), each component surfaced verbatim.
Properties
first?
readonlyoptionalfirst?:string
last?
readonlyoptionallast?:string
middle?
readonlyoptionalmiddle?:string
raw
readonlyraw:string
PatientNameInput
A patient name split into its components; only the supplied parts are emitted.
Properties
first?
readonlyoptionalfirst?:string
last?
readonlyoptionallast?:string
middle?
readonlyoptionalmiddle?:string
PatientRecord
The P (patient) record.
Safety: the practice-assigned ID (field 3) and the laboratory-assigned ID (field 4) are modeled as distinct fields and never collapsed, conflating them is the primary result-misfiling path.
Extends
RecordBase
Properties
birthDate?
readonlyoptionalbirthDate?:AstmDate
Field 8: birthdate (YYYYMMDDHHMMSS, precision-preserving; a truncated run sets truncated).
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
laboratoryAssignedId?
readonlyoptionallaboratoryAssignedId?:string
Field 4: laboratory-assigned patient ID. Distinct from PatientRecord.practiceAssignedId.
mothersMaidenName?
readonlyoptionalmothersMaidenName?:string
Field 7: mother's maiden name, surfaced verbatim (a surname component; PHI).
name?
readonlyoptionalname?:PatientName
Field 6: patient name (Last^First^Middle).
patientIdThree?
readonlyoptionalpatientIdThree?:string
Field 5: a third patient identifier (e.g. a national/alternate ID), surfaced verbatim. Kept separate from the practice- and laboratory-assigned IDs: the three never collapse into one.
practiceAssignedId?
readonlyoptionalpracticeAssignedId?:string
Field 3: practice-assigned patient ID. Distinct from PatientRecord.laboratoryAssignedId.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
seq?
readonlyoptionalseq?:string
Field 2: sequence number.
sex?
readonlyoptionalsex?:string
Field 9: sex, surfaced raw (M/F/U/vendor value).
type
readonlytype:"P"
The record's raw type letter.
Overrides
RecordBase.type
QueryInput
Input for a Q (request-information) record.
Properties
endingRangeId?
readonlyoptionalendingRangeId?:string
Field 4: ending range ID, emitted verbatim.
queriesAllTests?
readonlyoptionalqueriesAllTests?:boolean
Field 5: emit the literal ALL universal-query keyword instead of a Universal Test ID.
requestInformationStatus?
readonlyoptionalrequestInformationStatus?:string
Field 13: request-information status, emitted verbatim.
seq?
readonlyoptionalseq?:string
startingRangeId?
readonlyoptionalstartingRangeId?:string
Field 3: starting range ID, emitted verbatim.
type
readonlytype:"Q"
universalTestId?
readonlyoptionaluniversalTestId?: readonlystring[]
Field 5: Universal Test ID components; ignored when QueryInput.queriesAllTests is set.
QueryRecord
The Q (Request Information) record: the host-query request.
A Q record asks the LIS for information (e.g. outstanding orders for a
specimen); its presence classifies the whole message as a request, never a
result set (see AstmMessage.classification). Its safety-relevant
fields, the starting/ending range ID and the request-information status, are
surfaced verbatim; the field positions are the public ASTM E1394 layout,
but their internal structure and code meanings are [OSS-derived / paywalled]
and are therefore never interpreted or guessed.
Extends
RecordBase
Properties
endingRangeId?
readonlyoptionalendingRangeId?:string
Field 4: ending range ID number, surfaced verbatim (same [OSS-derived] caveat as field 3).
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
queriesAllTests
readonlyqueriesAllTests:boolean
true when field 5 is the literal ALL universal-query keyword (case-insensitive). [OSS-derived / paywalled]: the token is surfaced because it appears in the OSS references, but its exact
host-query behavior (which tests a bare ALL selects, and whether the vendor answers with a full
H/P/O/L or a P/O-only response) is paywalled and vendor-specific, not decided here.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
requestInformationStatus?
readonlyoptionalrequestInformationStatus?:string
Field 13: request-information status code(s), surfaced verbatim. The status code set is
[OSS-derived / paywalled]: with no publicly-groundable enumeration, this
parser recognizes none of them and interprets nothing, every present status is surfaced raw
and flagged with a value-free ASTM_RECORD_UNINTERPRETED_QUERY_STATUS warning. Never mapped to a
guessed meaning.
seq?
readonlyoptionalseq?:string
Field 2: sequence number.
startingRangeId?
readonlyoptionalstartingRangeId?:string
Field 3: starting range ID number, surfaced as the full verbatim field text (never truncated
to a component). Its caret component structure (e.g. patient ID ^ specimen ID) is
[OSS-derived / paywalled]: the raw split is available via RecordBase.fields, but the
meaning of each component is never assigned here.
type
readonlytype:"Q"
The record's raw type letter.
Overrides
RecordBase.type
universalTestId?
readonlyoptionaluniversalTestId?:UniversalTestId
Field 5: Universal Test ID (the same caret structure as an O/R record's), recognized by
provenance only. When the field is the literal universal-query keyword, QueryRecord.queriesAllTests
is set instead: see its caveat.
ReferenceRange
A parsed reference range. Bounds are surfaced as the verbatim numeric text (never coerced to a float, never rounded, never converted) so nothing is lost or fabricated.
[OSS-derived]: the exact lower/upper delimiter (-) and the open-ended
<x/>x forms are taken from the permissively-licensed OSS reference parsers
and cross-verified vendor transcripts; they are not confirmed against the
purchased CLSI LIS02-A2. Anything that does not match these
forms is surfaced verbatim as unparsed, never guessed into a bound.
Example
import { parseReferenceRange } from "@cosyte/astm";
const r = parseReferenceRange("3.5-5.0");
r.kind; // "closed"
r.low; // "3.5"
r.high; // "5.0"
Properties
high?
readonlyoptionalhigh?:string
The upper bound, verbatim numeric text (for closed and open-low).
kind
readonlykind:ReferenceRangeKind
The recognized shape, or "unparsed" when the text matched no known form.
low?
readonlyoptionallow?:string
The lower bound, verbatim numeric text (for closed and open-high).
raw
readonlyraw:string
The verbatim field text, exactly as received.
ResultInput
Input for an R (result) record. No clinical field is defaulted; unsupplied ⇒ empty.
Properties
abnormalFlags?
readonlyoptionalabnormalFlags?:string
Field 7: abnormal flags, emitted verbatim (never defaulted to N).
completedAt?
readonlyoptionalcompletedAt?:string
Field 13: test-completed timestamp, verbatim.
instrument?
readonlyoptionalinstrument?:string
Field 14: instrument identifier.
operator?
readonlyoptionaloperator?:string
Field 11: operator.
referenceRange?
readonlyoptionalreferenceRange?:string
Field 6: reference range, emitted verbatim.
resultStatus?
readonlyoptionalresultStatus?:string
Field 9: result status, emitted verbatim (never defaulted to F).
seq?
readonlyoptionalseq?:string
startedAt?
readonlyoptionalstartedAt?:string
Field 12: test-started timestamp (YYYYMMDDHHMMSS), verbatim.
type
readonlytype:"R"
units?
readonlyoptionalunits?:string
Field 5: units (vendor free text; never defaulted, guessed, or converted).
universalTestId?
readonlyoptionaluniversalTestId?: readonlystring[]
Field 3: Universal Test ID components (verbatim).
value?
readonlyoptionalvalue?:string
Field 4: the measured value, emitted verbatim (never defaulted).
ResultRecord
The R (result) record: the value itself.
The raw safety-critical fields (value, units, referenceRange,
abnormalFlags, resultStatus) are always surfaced exactly as received.
The modeled, fail-safe semantics sit alongside them: flag
(Table 0078, undefined never coerced to normal), status (a C/X never
reads as active-final; an absent status is unspecified, never final), and
range (open/closed bounds surfaced verbatim, never fabricated). The raw
strings and the modeled views coexist: nothing is collapsed or reconciled.
Extends
RecordBase
Properties
abnormalFlags?
readonlyoptionalabnormalFlags?:string
Field 7: abnormal flags, surfaced raw (HL7 Table 0078 values).
completedAt?
readonlyoptionalcompletedAt?:AstmDate
Field 13: test completed timestamp.
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
flag?
readonlyoptionalflag?:AbnormalFlag
Field 7: the abnormal flag interpreted against HL7 Table 0078, present only when the field
carried a value. An unrecognized flag is { recognized: false, meaning: "undefined" }, surfaced,
never dropped, and never coerced to normal.
instrument?
readonlyoptionalinstrument?:string
Field 14: instrument identifier.
operator?
readonlyoptionaloperator?:string
Field 11: operator.
range?
readonlyoptionalrange?:ReferenceRange
Field 6: the reference range parsed into low/high (or open-ended) bounds, present only when the
field carried a value. An unparseable range is kind: "unparsed" with the raw text preserved and
no bound fabricated. Bounds are verbatim numeric text, never coerced to floats.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
referenceRange?
readonlyoptionalreferenceRange?:string
Field 6: reference range, surfaced raw.
resultStatus?
readonlyoptionalresultStatus?:string
Field 9: result status, surfaced raw (F/C/X/…).
seq?
readonlyoptionalseq?:string
Field 2: sequence number.
startedAt?
readonlyoptionalstartedAt?:AstmDate
Field 12: test started timestamp.
status
readonlystatus:ResultStatus
Field 9: the modeled result status. Always present (an absent field yields a typed
unspecified, never assumed final), so status.isActiveFinal is always a reliable boolean: it
is true only for a plain F, and false for a correction (C), a cancellation (X), a
partial/preliminary/pending, an absent, or an unrecognized status.
type
readonlytype:"R"
The record's raw type letter.
Overrides
RecordBase.type
units?
readonlyoptionalunits?:string
Field 5: units (vendor free text; a missing unit is not defaulted).
universalTestId?
readonlyoptionaluniversalTestId?:UniversalTestId
Field 3: Universal Test ID (local code in component 4 is the primary identifier).
value?
readonlyoptionalvalue?:string
Field 4: the measured value. In the ordinary single-component case this is the decoded scalar.
When the value field carried an unescaped component delimiter (an ambiguity), this is the
full raw field text, never a truncated component, and ResultRecord.valueComponents
plus an ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT warning are also present.
valueComponents?
readonlyoptionalvalueComponents?: readonlystring[]
Field 4 split into components, present only when the value field carried an unescaped component delimiter: i.e. it read as more than one component. Surfaced alongside the full raw value (and a warning) so an ambiguous split is visible, never resolved silently.
ResultStatus
A modeled result status. The three booleans are the safety surface a consumer reads instead of string-matching the code:
isActiveFinalistrueonly for a plainF(final). It isfalsefor a correction (C), a cancellation (X), a preliminary/partial/pending result, an absent status, and an unrecognized one, so a superseded or cancelled result can never read as current/final.supersedesistrueforC: this value replaces a previously transmitted one.cancelledistrueforX: the result cannot be done / was cancelled.
Example
import { interpretResultStatus } from "@cosyte/astm";
interpretResultStatus("C").isActiveFinal; // false (a correction is not active-final)
interpretResultStatus("X").cancelled; // true
interpretResultStatus(undefined).meaning; // "unspecified" (never "final")
Properties
cancelled
readonlycancelled:boolean
true for X: the result cannot be done / was cancelled.
code?
readonlyoptionalcode?:ResultStatusCode
The recognized status code, present only when the raw text is a known status.
isActiveFinal
readonlyisActiveFinal:boolean
true only for a plain F (final): never for C, X, absent, or unrecognized.
meaning
readonlymeaning:ResultStatusMeaning
The modeled meaning; "unspecified" when absent, "undefined" when unrecognized.
raw?
readonlyoptionalraw?:string
The verbatim field text, present only when field 9 carried a value.
recognized
readonlyrecognized:boolean
Whether the raw text matched a recognized status letter.
supersedes
readonlysupersedes:boolean
true for C: this value supersedes a previously transmitted result.
ScientificRecord
The S (scientific) record: vendor-defined free-form data, surfaced
VERBATIM and never interpreted into typed clinical fields (same posture
as ManufacturerRecord).
Extends
RecordBase
Properties
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
rawLine
readonlyrawLine:string
The record's exact wire text (terminator excluded), preserved byte-for-byte.
Emit reproduces these bytes exactly whenever a reader using the delimiters being
emitted against would recover the fields this record models: always the case when
the record is already in those delimiters, and also when it carries no delimiter
either set would split on. Otherwise the record is re-encoded from
RecordBase.fields, so the row can never go out in delimiters the header does
not declare. To change a value, edit fields; editing this string has no effect on emit.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
type
readonlytype:"S"
The record's raw type letter.
Overrides
RecordBase.type
TerminatorRecord
The L (terminator) record: closes a message.
Extends
RecordBase
Properties
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
type
readonlytype:"L"
The record's raw type letter.
Overrides
RecordBase.type
UniversalTestId
A recognized ASTM Universal Test ID. All components are surfaced verbatim (already escape-decoded by the tokenizer); nothing is looked up.
Example
import { recognizeUniversalTestId } from "@cosyte/astm";
const u = recognizeUniversalTestId(["", "", "", "687"]);
u.localCode; // "687"
u.provenance; // "local-code"
Properties
codingScheme?
readonlyoptionalcodingScheme?:string
Component 3: the coding-scheme selector, when present.
components
readonlycomponents: readonlystring[]
The field's components, verbatim and in order.
localCode?
readonlyoptionallocalCode?:string
Component 4, the vendor/local code: the primary identifier when no inline LOINC is given.
loincCandidate?
readonlyoptionalloincCandidate?:string
Component 1 when populated: a candidate LOINC (provenance only, never validated).
provenance
readonlyprovenance:UniversalTestIdProvenance
Where the primary identifier came from.
testName?
readonlyoptionaltestName?:string
Component 2: the test / battery name, when present.
UnsupportedRecord
Any record whose type letter is not modeled (a genuinely unknown letter).
H/P/O/R/C/Q/M/S/L are all modeled; anything else is surfaced
with its raw fields intact and flagged with an
ASTM_RECORD_UNKNOWN_TYPE warning, never dropped.
One of these may be a header. Message grouping decides where a message
starts by reading the type letter, so a header the reader could not recognize
arrives here instead, opens no message, and the messages either side of it are
grouped as one. Check rawType before trusting a split.
And one of these may be a Q. Message classification counts type letters
too, so a message carrying an unsupported record is classified indeterminate
unless a Q was read outright: see
AstmMessageClassification.hasUnrecognized.
A header that arrives here also failed to re-scope the delimiters. The
records after it are read with the set already in force, and where that set is
not theirs they do not split at all, which is reported per record as
ASTM_RECORD_FIELDS_UNSEPARATED.
Extends
RecordBase
Properties
fields
readonlyfields: readonlyAstmField[]
The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.
Inherited from
RecordBase.fields
rawType
readonlyrawType:string
The raw type letter as it appeared on the wire.
recordIndex
readonlyrecordIndex:number
0-based ordinal of the record within the message.
Inherited from
RecordBase.recordIndex
type
readonlytype:"unsupported"
The record's raw type letter.
Overrides
RecordBase.type
VerbatimInput
Input for an M (manufacturer) or S (scientific) record: vendor-defined
free-form data. Emitted verbatim from the caller's fields; never interpreted.
Properties
fields
readonlyfields: readonlystring[]
Data fields (after the type letter), emitted verbatim in order.
type
readonlytype:"M"|"S"
Type Aliases
AbnormalFlagCode
AbnormalFlagCode =
"L"|"H"|"LL"|"HH"|"<"|">"|"N"|"A"|"AA"|"U"|"D"|"B"|"W"|"S"|"R"|"I"
The recognized abnormal-flag letters, from HL7 v2.2 Table 0078 (the flag
value set, a published fact set, not CLSI prose). Read in the context of an
R record's field 7: here S/R/I are the microbiology susceptibility
codes and R is not the repeat delimiter.
AbnormalFlagMeaning
AbnormalFlagMeaning =
"below-normal"|"above-normal"|"critically-below-normal"|"critically-above-normal"|"below-scale"|"above-scale"|"normal"|"abnormal"|"very-abnormal"|"significant-change-up"|"significant-change-down"|"better"|"worse"|"susceptible"|"resistant"|"intermediate"|"undefined"
The modeled meaning of a recognized AbnormalFlagCode, plus the two
fail-safe sentinels: undefined (a flag was present but is not in Table 0078),
which is never collapsed to normal.
AmbiguousCode
AmbiguousCode = typeof
AMBIGUOUS_CODES[keyof typeofAMBIGUOUS_CODES]
A value from AMBIGUOUS_CODES: the discriminant on AstmAmbiguousStreamError.
AnyAstmWarningCode
AnyAstmWarningCode =
WarningCode|FrameWarningCode|LtpWarningCode
Any warning code from any of the three registries: the record layer
(ASTM_RECORD_* / ASTM_NONSTANDARD_DELIMITERS / ASTM_UNKNOWN_ESCAPE_SEQUENCE),
the frame codec (ASTM_FRAME_*), or the LTP protocol layer (ASTM_LTP_*). A
AstmQuirkTolerance may name any of them, but the safety gate refuses all
but the small, benign, record-layer subset, so the union exists mainly to let
the gate reject a frame/LTP code with a precise, typed message.
AstmDatePrecision
AstmDatePrecision =
"year"|"month"|"day"|"hour"|"minute"|"second"
The precision to which an AstmDate is populated.
AstmFrameEncodeErrorCode
AstmFrameEncodeErrorCode =
"ASTM_FRAME_EMPTY_RECORD"|"ASTM_FRAME_UNENCODABLE_CHARACTER"|"ASTM_FRAME_RESERVED_BYTE"|"ASTM_FRAME_INVALID_START_FRAME_NUMBER"
The reasons composeAstmFrames refuses rather than emitting bytes that are not the record it was handed.
ASTM_FRAME_EMPTY_RECORD: an empty record list, or a record with no bytes. A frame must carry a record; an empty one is a structural error, never an empty frame.ASTM_FRAME_UNENCODABLE_CHARACTER: a record given as astringholds a character aboveU+00FF, which has no single byte to stand for.ASTM_FRAME_RESERVED_BYTE: a record holds a byte this layer reads as frame structure (STX,ETBorETX). Framing has no escape mechanism, so such a byte cannot be carried inside a frame at all.ASTM_FRAME_INVALID_START_FRAME_NUMBER:options.startFrameNumberis not a whole number from0to7. A frame's number is one ASCII digit, so a value outside that range has no digit to be written as.
Example
import type { AstmFrameEncodeErrorCode } from "@cosyte/astm";
const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
AstmFraming
AstmFraming =
"framed"|"raw"
The transport framing of an ASTM byte stream: "framed" (E1381 STX/checksum
frames, the serial and framed-TCP realities) or "raw" (framing dropped,
de-framed record bytes streamed directly, the raw-TCP reality).
AstmMessageKind
AstmMessageKind =
"host-query"|"results"|"orders"|"indeterminate"
How a message is classified by the host-query flow.
host-query(the message carries at least oneQrecord): it is a request for information, and must never be read as a result set (the load-bearing safety distinction of this layer).Qdominates: aQpresent classifies the message as a request even if a result record is also present (an anomaly, separately warned), so aQ-bearing message is never silently treated as a result upload.results(noQ, at least oneRresult record): a result upload / response.orders(noQ, noR, at least oneOorder record): an order download, or a query response before results are attached.indeterminate: none of the above (e.g. header + terminator only). Not guessed into one of the other kinds.
AstmRecord
AstmRecord =
HeaderRecord|PatientRecord|OrderRecord|ResultRecord|CommentRecord|QueryRecord|ManufacturerRecord|ScientificRecord|TerminatorRecord|UnsupportedRecord
The discriminated union of every parsed record.
AstmRecordInput
AstmRecordInput =
PatientInput|OrderInput|ResultInput|CommentInput|QueryInput|VerbatimInput
Any record the message builder can emit (the terminator L is appended automatically).
AstmSerializeErrorCode
AstmSerializeErrorCode =
"ASTM_EMIT_UNENCODABLE_VALUE"|"ASTM_EMIT_INVALID_DELIMITERS"|"ASTM_EMIT_TYPE_LETTER_COLLISION"
The reasons emit refuses rather than writing a stream that cannot be read back.
ASTM_EMIT_UNENCODABLE_VALUE: a component holds a record terminator (CR/LF), which the escape codec has no mnemonic for.ASTM_EMIT_INVALID_DELIMITERS: the delimiter set to emit against failed one of the three conditions readback requires (see serializeAstmRecords). Those conditions are checked against the set alone, so this code means a set was rejected, not that every unreversible set is.ASTM_EMIT_TYPE_LETTER_COLLISION: this record's type letter would not be the first character of its own emitted line, so the record would read back as a different record. Raised per record rather than per set, because whether a set collides depends on which record is being written: a record read under one delimiter set and written under another can hit it with no set passed at all, so it is not avoided by emitting on the default canonical path.
Example
import type { AstmSerializeErrorCode } from "@cosyte/astm";
const code: AstmSerializeErrorCode = "ASTM_EMIT_INVALID_DELIMITERS";
FatalCode
FatalCode = typeof
FATAL_CODES[keyof typeofFATAL_CODES]
A value from FATAL_CODES: the type carried by a thrown AstmParseError.
FrameTerminator
FrameTerminator =
"ETB"|"ETX"
Which terminator closed a frame: ETB (record continues) or ETX (record complete).
FrameWarningCode
FrameWarningCode = typeof
FRAME_WARNING_CODES[keyof typeofFRAME_WARNING_CODES]
Discriminant type for AstmFrameWarning.code. Narrowing by this code lets
consumers write exhaustive switch blocks against FRAME_WARNING_CODES.
LivdLookup
LivdLookup = {
loinc:string;loincLongName?:string;status:"mapped"; } | {status:"unmapped"; } | {candidates: readonlystring[];status:"ambiguous"; }
The outcome of looking a vendor code up in a LivdCatalog. A distinct
value per safe disposition: a hit is mapped; a miss is unmapped; a code that
matched more than one distinct LOINC is ambiguous with the candidates
surfaced but none chosen. There is deliberately no "guessed" case.
Union Members
Type Literal
{ loinc: string; loincLongName?: string; status: "mapped"; }
Exactly one LOINC (one entry, or several entries that all agree on the same LOINC).
Type Literal
{ status: "unmapped"; }
No entry for this code: a miss. The code stays verbatim; no LOINC is invented.
Type Literal
{ candidates: readonly string[]; status: "ambiguous"; }
More than one distinct LOINC: surfaced for inspection, never resolved to one.
LivdMapping
LivdMapping = {
derived:true;loinc:string;loincLongName?:string;source:"livd";status:"mapped"; } | {loinc:string;source:"wire";status:"inline-loinc"; } | {status:"unmapped"; } | {candidates: readonlystring[];status:"ambiguous"; } | {status:"no-code"; }
The outcome of annotating one record's Universal Test ID against a LIVD catalog. A distinct case per disposition; there is no case in which a LOINC is guessed.
mapped, the vendor/local code resolved to a single LOINC via the catalog (labeledderived: true,source: "livd").inline-loinc, the wire itself carried a LOINC in the Universal Test ID's slot (component 1); surfacedsource: "wire", not derived and not validated.unmapped: a vendor/local code with no catalog entry.ambiguous: a vendor/local code matching more than one distinct LOINC; the candidates are surfaced but none is chosen.no-code: the record carried no usable test code at all (name-only/empty), so there was nothing to map.
LivdWarningCode
LivdWarningCode = typeof
LIVD_WARNING_CODES[keyof typeofLIVD_WARNING_CODES]
Discriminant type for AstmLivdWarning.code. Narrowing by this code lets
consumers write exhaustive switch blocks against LIVD_WARNING_CODES.
LtpAction
LtpAction = {
type:"sendAck"; } | {type:"sendNak"; } | {type:"sendEot"; } | {record:Uint8Array;type:"deliverRecord"; }
An action the reducer tells the consumer to take. The consumer performs the I/O
(write the byte to the socket, hand the record to the parser); the reducer only
decides. sendAck/sendNak/sendEot are single control bytes; deliverRecord
carries the freshly-completed record's reassembled bytes.
LtpEvent
LtpEvent = {
type:"enq"; } | {type:"ack"; } | {type:"nak"; } | {type:"eot"; } | {frame:AstmFrame;type:"frame"; }
An inbound protocol event the consumer feeds the reducer: one of the four LTP
control signals (ENQ/ACK/NAK/EOT) read off the wire, or a frame the
consumer already decoded with decodeAstmFrames (the reducer reuses the codec's
trusted/checksum verdict, it never re-derives it).
LtpPhase
LtpPhase =
"neutral"|"transfer"
The protocol phase. The line is either neutral (idle, awaiting an ENQ to
establish, or reset there after an EOT) or in transfer (establishment
accepted; frames and their per-frame ACK/NAK flow until the sender's EOT).
The classic three-phase LIS01 model (establishment → transfer → termination)
collapses to these two, since establishment is the neutral → transfer edge and
termination is the transfer → neutral edge.
LtpWarningCode
LtpWarningCode = typeof
LTP_WARNING_CODES[keyof typeofLTP_WARNING_CODES]
Discriminant type for AstmLtpWarning.code. Narrowing by this code lets
consumers write exhaustive switch blocks against LTP_WARNING_CODES.
ReferenceRangeKind
ReferenceRangeKind =
"closed"|"open-low"|"open-high"|"unparsed"
The shape of a parsed reference range.
closed: both a low and a high bound (3.5-5.0).open-low: an upper bound only (<5), so everything at or belowhigh.open-high: a lower bound only (>10), so everything at or abovelow.unparsed: the text did not match a recognized form; both bounds are absent and the raw text is surfaced. A bound is never fabricated.
ResultStatusCode
ResultStatusCode =
"F"|"C"|"P"|"R"|"S"|"I"|"X"
The recognized result-status letters (R-record field 9). The clinically
load-bearing members are C (correction, supersedes a previously
transmitted value) and X (cannot be done / cancelled).
ResultStatusMeaning
ResultStatusMeaning =
"final"|"correction"|"preliminary"|"previously-transmitted"|"partial"|"pending"|"cancelled"|"unspecified"|"undefined"
The modeled meaning of a result status, plus two fail-safe sentinels:
unspecified (the field was absent, never assumed final) and
undefined (a letter was present but is not a recognized status).
UniversalTestIdProvenance
UniversalTestIdProvenance =
"inline-loinc-candidate"|"local-code"|"name-only"|"empty"
Where a Universal Test ID's usable identifier came from.
UnknownEscapeSink
UnknownEscapeSink = () =>
void
A callback the codec calls when it encounters an escape sequence whose body is
not one of the four recognized mnemonics. The sequence is preserved verbatim;
the callback lets the parser surface a value-free ASTM_UNKNOWN_ESCAPE_SEQUENCE
warning. Optional so the codec can be used purely.
Returns
void
UnpairedEscapeSink
UnpairedEscapeSink = () =>
void
A callback the codec calls when it encounters an escape character that does not
head a three-character escape sequence. The character is preserved verbatim as a
literal; the callback lets the parser surface a value-free
ASTM_UNPAIRED_ESCAPE_CHARACTER warning. Optional so the codec can be used purely.
Returns
void
WarningCode
WarningCode = typeof
WARNING_CODES[keyof typeofWARNING_CODES]
Discriminant type for AstmRecordWarning.code. Narrowing by this code
lets consumers write exhaustive switch blocks and guarantees a typo-free
comparison against WARNING_CODES.
Variables
ALL_ASTM_WARNING_CODES
constALL_ASTM_WARNING_CODES:ReadonlySet<AnyAstmWarningCode>
The set of every real warning code, for O(1) membership checks (used by the validator to distinguish "unknown code" from "known but forbidden"). Frozen.
Example
import { ALL_ASTM_WARNING_CODES } from "@cosyte/astm";
ALL_ASTM_WARNING_CODES.has("ASTM_LTP_FRAME_REJECTED"); // true
AMBIGUOUS_CODES
constAMBIGUOUS_CODES:object
Stable codes for the two ways a flat, stream-scoped accessor can fail to name a single answer. Renaming a code is a breaking change.
Type Declaration
ASTM_AMBIGUOUS_MULTI_MESSAGE
readonlyASTM_AMBIGUOUS_MULTI_MESSAGE:"ASTM_AMBIGUOUS_MULTI_MESSAGE"="ASTM_AMBIGUOUS_MULTI_MESSAGE"
The stream carries more than one H … L message, so a stream-wide answer spans patients.
ASTM_AMBIGUOUS_MULTI_PATIENT
readonlyASTM_AMBIGUOUS_MULTI_PATIENT:"ASTM_AMBIGUOUS_MULTI_PATIENT"="ASTM_AMBIGUOUS_MULTI_PATIENT"
The one message in the stream carries more than one P, so "the patient" is not determined.
Example
import { parseAstmRecords, patient, AMBIGUOUS_CODES, AstmAmbiguousStreamError } from "@cosyte/astm";
try {
patient(parseAstmRecords(raw));
} catch (err) {
if (err instanceof AstmAmbiguousStreamError) {
err.code === AMBIGUOUS_CODES.ASTM_AMBIGUOUS_MULTI_MESSAGE;
}
}
ASTM_ACK
constASTM_ACK:6=0x06
Acknowledge (0x06): the receiver accepted the last establishment or frame.
ASTM_ENQ
constASTM_ENQ:5=0x05
Enquiry (0x05): the sender's request to establish a transfer.
ASTM_EOT
constASTM_EOT:4=0x04
End of transmission (0x04): the sender terminated the transfer; the line returns to neutral.
ASTM_NAK
constASTM_NAK:21=0x15
Negative acknowledge (0x15): the receiver rejected the last frame; retransmit, do not accept.
astmProfiles
constastmProfiles:object
Namespace object exposing the built-in profiles: the conservative default
baseline plus the evidence-backed referenceCorpus (grounded firsthand in the
redistributable OSS reference corpus), each authored via the public
defineAstmProfile() API and carrying its cited provenance. Named per-vendor
profiles (cobas / Sysmex / …) are deferred pending a firsthand
vendor-attributed quirk document: the engine fully supports them; we do not ship
ungrounded ones.
Type Declaration
default
readonlydefault:AstmProfile
referenceCorpus
readonlyreferenceCorpus:AstmProfile
Example
import { parseAstmRecords, astmProfiles } from "@cosyte/astm";
const msg = parseAstmRecords(raw, { profile: astmProfiles.referenceCorpus });
msg.profile?.name; // "referenceCorpus"
CANONICAL_DELIMITERS
constCANONICAL_DELIMITERS:Delimiters
The canonical ASTM delimiter set (H|\^&). Used only as documentation and as
a comparison baseline for the non-standard-delimiter warning: never as a
parse-time default (delimiters are always read from the header).
Example
import { CANONICAL_DELIMITERS } from "@cosyte/astm";
CANONICAL_DELIMITERS.repeat; // "\\"
FATAL_CODES
constFATAL_CODES:object
Stable string codes for every Tier-3 fatal the record parser may throw.
Consumers narrow on err.code to react to specific structural failures.
Renaming a code is a breaking change.
Type Declaration
ASTM_RECORD_NO_HEADER
readonlyASTM_RECORD_NO_HEADER:"ASTM_RECORD_NO_HEADER"="ASTM_RECORD_NO_HEADER"
The first record is not an H (header) record: an ASTM message must lead with H.
ASTM_RECORD_UNDECLARED_DELIMITERS
readonlyASTM_RECORD_UNDECLARED_DELIMITERS:"ASTM_RECORD_UNDECLARED_DELIMITERS"="ASTM_RECORD_UNDECLARED_DELIMITERS"
The H record is too short to declare the four delimiters (field/repeat/component/escape).
EMPTY_INPUT
readonlyEMPTY_INPUT:"EMPTY_INPUT"="EMPTY_INPUT"
Input was empty or whitespace-only: there is nothing to parse. Shared across layers.
Example
import { parseAstmRecords, FATAL_CODES, AstmParseError } from "@cosyte/astm";
try {
parseAstmRecords("");
} catch (err) {
if (err instanceof AstmParseError && err.code === FATAL_CODES.EMPTY_INPUT) {
// handle empty input
}
}
FRAME_WARNING_CODES
constFRAME_WARNING_CODES:object
Stable string codes for every frame-codec warning. key === value so
Object.values(...) yields a stable snapshot set.
Type Declaration
ASTM_FRAME_BAD_CHECKSUM
readonlyASTM_FRAME_BAD_CHECKSUM:"ASTM_FRAME_BAD_CHECKSUM"="ASTM_FRAME_BAD_CHECKSUM"
A frame's two-hex-char checksum did not match the modulo-256 sum recomputed over its bytes. The
frame is surfaced with trusted: false and its text is never merged into a reassembled
record (default warn in lenient mode, escalated to a thrown error in strict). Corruption is
never silently trusted: the "checksums are routinely not validated" claim was refuted; we
validate.
ASTM_FRAME_OVERSIZE
readonlyASTM_FRAME_OVERSIZE:"ASTM_FRAME_OVERSIZE"="ASTM_FRAME_OVERSIZE"
A frame's record text exceeded the 240-byte limit without a split. The frame is still surfaced
(and, if its checksum validates, reassembled): the deviation is flagged, not silently dropped
(warn in lenient, thrown in strict).
ASTM_FRAME_SEQUENCE_GAP
readonlyASTM_FRAME_SEQUENCE_GAP:"ASTM_FRAME_SEQUENCE_GAP"="ASTM_FRAME_SEQUENCE_GAP"
A frame's sequence number was not the expected next value (1 → … → 7 → 0 → …): a frame was
possibly dropped. The stream is never silently concatenated across the gap as if contiguous;
the in-progress record is tainted and not emitted as a clean reassembly.
ASTM_FRAME_UNTERMINATED
readonlyASTM_FRAME_UNTERMINATED:"ASTM_FRAME_UNTERMINATED"="ASTM_FRAME_UNTERMINATED"
A frame opened with STX but no valid terminator + checksum was found before the stream ended
(or before the next STX), or a record's frames ended on an intermediate ETB with no final
ETX. The partial bytes are surfaced flagged untrusted and no partial record is invented
(warn in lenient, thrown in strict).
Example
import { decodeAstmFrames, FRAME_WARNING_CODES } from "@cosyte/astm";
const bytes = new Uint8Array([]); // ...a framed stream...
void bytes;
FRAME_WARNING_CODES.ASTM_FRAME_BAD_CHECKSUM; // "ASTM_FRAME_BAD_CHECKSUM"
LIVD_WARNING_CODES
constLIVD_WARNING_CODES:object
Stable string codes for every terminology (LIVD) warning. key === value so
Object.values(...) yields a stable snapshot set.
Type Declaration
ASTM_LIVD_AMBIGUOUS_MAPPING
readonlyASTM_LIVD_AMBIGUOUS_MAPPING:"ASTM_LIVD_AMBIGUOUS_MAPPING"="ASTM_LIVD_AMBIGUOUS_MAPPING"
A vendor/local test code matched more than one distinct LOINC in the catalog (e.g. the same
transmission code used by two devices for different analytes). The mapping is ambiguous and the
candidate LOINCs are surfaced for inspection, but none is chosen: refusing to pick is the
fail-safe, since guessing wrong mis-identifies the test.
ASTM_LIVD_UNMAPPED_CODE
readonlyASTM_LIVD_UNMAPPED_CODE:"ASTM_LIVD_UNMAPPED_CODE"="ASTM_LIVD_UNMAPPED_CODE"
A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held no
entry for it. The code stays surfaced verbatim and the mapping is unmapped: a LOINC is
never guessed. Purely advisory: the raw code and value are untouched.
Example
import { LIVD_WARNING_CODES } from "@cosyte/astm";
LIVD_WARNING_CODES.ASTM_LIVD_UNMAPPED_CODE; // "ASTM_LIVD_UNMAPPED_CODE"
LTP_WARNING_CODES
constLTP_WARNING_CODES:object
Stable string codes for every LTP protocol / transport warning. key === value
so Object.values(...) yields a stable snapshot set.
Type Declaration
ASTM_LTP_AMBIGUOUS_TRANSPORT
readonlyASTM_LTP_AMBIGUOUS_TRANSPORT:"ASTM_LTP_AMBIGUOUS_TRANSPORT"="ASTM_LTP_AMBIGUOUS_TRANSPORT"
The transport detector could not tell a framed stream from a raw (unframed) one from its leading
byte: it was neither STX/ENQ nor a bare record letter. It defaults to framed and warns,
never guessing silently into data loss; a profile override forces the mode. (cobas b121 drops
framing over TCP; cobas 4800 / Iguana retain it, both realities exist, so an unrecognizable lead
is defaulted, not assumed.)
ASTM_LTP_FRAME_REJECTED
readonlyASTM_LTP_FRAME_REJECTED:"ASTM_LTP_FRAME_REJECTED"="ASTM_LTP_FRAME_REJECTED"
The receiver rejected a received frame with a NAK instead of accepting it: because its
checksum failed, it was unterminated, or its frame number was out of sequence. The frame's text
is never appended to the record and the transfer does not advance; the receiver awaits the
sender's retransmit. This is the protocol-level face of the frame codec's fail-safe: a bad frame
drives retransmit, not acceptance.
ASTM_LTP_UNEXPECTED_EVENT
readonlyASTM_LTP_UNEXPECTED_EVENT:"ASTM_LTP_UNEXPECTED_EVENT"="ASTM_LTP_UNEXPECTED_EVENT"
A control event arrived in a protocol state that did not expect it: e.g. an inbound ACK/NAK
at a receiver (which sends, never receives, those), or an ENQ mid-transfer. The event is
surfaced and handled defensively (an unexpected ACK/NAK is never read as acceptance of
data); it never advances the transfer as if valid.
Example
import { LTP_WARNING_CODES } from "@cosyte/astm";
LTP_WARNING_CODES.ASTM_LTP_FRAME_REJECTED; // "ASTM_LTP_FRAME_REJECTED"
SAFETY_CRITICAL_CODES
constSAFETY_CRITICAL_CODES:ReadonlySet<AnyAstmWarningCode>
The forbidden set: computed as every known code minus the tolerable allow-list, so it is complete by construction. Frozen. A code appears here iff it is a real warning code that is not in TOLERABLE_CODES.
Example
import { SAFETY_CRITICAL_CODES } from "@cosyte/astm";
SAFETY_CRITICAL_CODES.has("ASTM_RECORD_UNDEFINED_RESULT_STATUS"); // true
SAFETY_CRITICAL_CODES.has("ASTM_RECORD_UNKNOWN_TYPE"); // true
SAFETY_CRITICAL_CODES.has("ASTM_FRAME_BAD_CHECKSUM"); // true
TOLERABLE_CODES
constTOLERABLE_CODES:ReadonlySet<AnyAstmWarningCode>
The only warning codes a profile may list in its tolerate set: benign
structural or syntactic vendor noise that cannot alter, drop, or fabricate an
extracted value, and whose reported condition nothing else in this package
reads: not to decide where one message ends and the next begins, and not to
decide what kind of message it is. Frozen so it cannot be mutated at runtime to
smuggle a code in. Adding to this set is a deliberate, reviewable act, and an
addition has to satisfy both halves of that test, not just the first.
ASTM_RECORD_UNKNOWN_TYPE is not on this list, and used to be. An
unrecognized record type may be a header the reader did not recognize as one, in
which case two messages have been read as one, or a Q it did not recognize, in
which case the message kind is no longer knowable. Tolerating it can quiet the
only report either happened.
ASTM_RECORD_FIELDS_UNSEPARATED is not on this list either, and never was. It
reports a record the delimiters in force could not split, so every modeled field
of that record is missing: it fails the first half of the test outright.
Example
import { TOLERABLE_CODES } from "@cosyte/astm";
TOLERABLE_CODES.has("ASTM_UNKNOWN_ESCAPE_SEQUENCE"); // true
TOLERABLE_CODES.has("ASTM_RECORD_UNKNOWN_TYPE"); // false
TOLERABLE_CODES.has("ASTM_FRAME_BAD_CHECKSUM"); // false
VERSION
constVERSION:string="0.0.15"
Library version string, kept in lockstep with package.json#version.
Changesets owns the bump and rewrites package.json only, so the release version script runs
scripts/sync-version.mjs to rewrite this declaration in the same commit, and
test/sanity.test.ts compares the two so a skipped sync goes red instead of shipping a version
string that lies.
Example
import { VERSION } from "@cosyte/astm";
console.log(VERSION);
WARNING_CODES
constWARNING_CODES:object
Stable string codes for every Tier-2 warning the record parser may emit.
key === value so Object.values(...) yields a stable snapshot set.
Type Declaration
ASTM_NONSTANDARD_DELIMITERS
readonlyASTM_NONSTANDARD_DELIMITERS:"ASTM_NONSTANDARD_DELIMITERS"="ASTM_NONSTANDARD_DELIMITERS"
The header declared delimiters other than the canonical H|\^&: tolerated, noted.
ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND
readonlyASTM_RECORD_AMBIGUOUS_MESSAGE_KIND:"ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND"="ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND"
A message carried both a Q (request) and an R (result) record: a contradictory shape. The
message is classified host-query (the Q dominates, so it is never read as a result set) and
this warning flags the anomaly. Positional context only; no field value.
ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT
readonlyASTM_RECORD_AMBIGUOUS_VALUE_SPLIT:"ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT"="ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT"
A result value field carried an unescaped component delimiter, so it split into more than one component. Both the full raw value and the split are surfaced and this warning fires: the ambiguity is never resolved silently into a truncated value.
ASTM_RECORD_DELIMITERS_REDECLARED
readonlyASTM_RECORD_DELIMITERS_REDECLARED:"ASTM_RECORD_DELIMITERS_REDECLARED"="ASTM_RECORD_DELIMITERS_REDECLARED"
A later H (header) record declared a different delimiter set from the one in force, and the
parser followed it: that header and every record after it are read with the newly-declared
delimiters, until the next H. Records already read keep the set that was in force when they were
read: a redeclaration never reinterprets bytes that have already been consumed.
A stream carrying several messages back to back is ordinary, and a header that simply repeats the delimiters already in force is a no-op that warns nothing. This code fires only when the set actually changes, because that is the point at which a reader still using the old set would begin merging fields together.
ASTM_RECORD_FIELDS_UNSEPARATED
readonlyASTM_RECORD_FIELDS_UNSEPARATED:"ASTM_RECORD_FIELDS_UNSEPARATED"="ASTM_RECORD_FIELDS_UNSEPARATED"
The delimiters in force found no field separator at all in a record that carries content beyond its type letter, so the whole record read back as a single field and none of its modeled fields could be recovered. The raw line is surfaced intact and nothing is dropped, but on a result record it means the value, the units and the status are all absent from the parsed model.
This is one signature of a record being read with a delimiter set that does not belong to it, which is how a delimiter-scoping mistake turns into lost values. It is reported rather than repaired, because recovering the fields would mean guessing which set the sender meant.
Its absence is not evidence that a record was read in its own set. This tests one of the
four delimiter roles, the field separator, and only in its total form, where that separator
occurs in the line (unescaped). Two whole classes of the same loss are outside it: a foreign set
whose field separator happens to occur somewhere in the line still splits (on the wrong
boundaries, silently, and this can happen to one record inside a run of these warnings); and a
set differing in the repeat, component or escape role usually splits into fields normally,
where a mis-split component can cost a test identity while the value survives, and where an
&X& sequence whose body is a delimiter is an opaque atom, so that delimiter does not split
and every field after it shifts. The escape role's worst case has narrowed and not
disappeared: an escape character heading no sequence is now read as a literal and reported
under WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER rather than merging the rest of the
record, but a delimiter swallowed inside an &X& body is reported only by
WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE, which is tolerable. Treat this code as a
report that one record definitely lost its fields, never as a sweep that would have fired if
any had.
ASTM_RECORD_ORPHAN_COMMENT
readonlyASTM_RECORD_ORPHAN_COMMENT:"ASTM_RECORD_ORPHAN_COMMENT"="ASTM_RECORD_ORPHAN_COMMENT"
A C (comment) record had no valid preceding H/P/O/R parent: an orphan. The comment
is attached to the message root (attachedToRoot: true) and surfaced, never dropped.
ASTM_RECORD_PARTIAL_TIMESTAMP
readonlyASTM_RECORD_PARTIAL_TIMESTAMP:"ASTM_RECORD_PARTIAL_TIMESTAMP"="ASTM_RECORD_PARTIAL_TIMESTAMP"
A YYYYMMDDHHMMSS timestamp had an odd digit run that truncates a two-digit component in half
(e.g. a partial day/hour). The raw run is preserved and the structured value stops at the last
complete component: the dangling digit is never zero-filled into a fabricated time.
ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG
readonlyASTM_RECORD_UNDEFINED_ABNORMAL_FLAG:"ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG"="ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG"
A result's abnormal-flag field (R field 7) carried a letter outside HL7 Table 0078. The flag is
surfaced as undefined: never dropped, and never coerced to normal (a clinical error).
ASTM_RECORD_UNDEFINED_RESULT_STATUS
readonlyASTM_RECORD_UNDEFINED_RESULT_STATUS:"ASTM_RECORD_UNDEFINED_RESULT_STATUS"="ASTM_RECORD_UNDEFINED_RESULT_STATUS"
A result's status field (R field 9) carried a letter that is not a recognized status. It is
surfaced as undefined and, like every non-F status, never reads as active-final.
ASTM_RECORD_UNINTERPRETED_QUERY_STATUS
readonlyASTM_RECORD_UNINTERPRETED_QUERY_STATUS:"ASTM_RECORD_UNINTERPRETED_QUERY_STATUS"="ASTM_RECORD_UNINTERPRETED_QUERY_STATUS"
A Q (request-information) record carried a request-information status code (field 13). The code
set is [OSS-derived / paywalled] with no publicly-groundable enumeration, so the parser
interprets none of them: the status is surfaced verbatim and this value-free warning flags that
it was passed through uninterpreted, never mapped to a guessed meaning.
ASTM_RECORD_UNITS_ABSENT
readonlyASTM_RECORD_UNITS_ABSENT:"ASTM_RECORD_UNITS_ABSENT"="ASTM_RECORD_UNITS_ABSENT"
A result carried a numeric value but no units (R field 5 empty). Units are vendor free text
(not UCUM); a missing unit is flagged here and never defaulted, guessed, or converted.
ASTM_RECORD_UNKNOWN_TYPE
readonlyASTM_RECORD_UNKNOWN_TYPE:"ASTM_RECORD_UNKNOWN_TYPE"="ASTM_RECORD_UNKNOWN_TYPE"
A record's type letter is not one of the modeled types: surfaced as an unsupported record, never dropped. Treat it as a possible lost message boundary, because a header the reader did not recognize as one does not open a new message.
ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE
readonlyASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE:"ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE"="ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE"
A result's reference-range field (R field 6) did not match a recognized form (low-high,
<high, >low). The text is surfaced verbatim as unparsed: a bound is never fabricated.
ASTM_RECORD_UNREADABLE_REDECLARATION
readonlyASTM_RECORD_UNREADABLE_REDECLARATION:"ASTM_RECORD_UNREADABLE_REDECLARATION"="ASTM_RECORD_UNREADABLE_REDECLARATION"
A later H (header) record could not declare a usable delimiter set: it was too short, or the
field separator it named also appeared among the other three, leaving the four roles
indistinguishable. The delimiters already in force are kept and every record is still surfaced;
a set is never guessed and no record is dropped.
The same condition on the first header is unrecoverable and remains the
ASTM_RECORD_UNDECLARED_DELIMITERS fatal: there is no earlier set to fall back to.
ASTM_UNKNOWN_ESCAPE_SEQUENCE
readonlyASTM_UNKNOWN_ESCAPE_SEQUENCE:"ASTM_UNKNOWN_ESCAPE_SEQUENCE"="ASTM_UNKNOWN_ESCAPE_SEQUENCE"
An escape sequence body was not one of &F&/&S&/&R&/&E&: preserved verbatim.
ASTM_UNPAIRED_ESCAPE_CHARACTER
readonlyASTM_UNPAIRED_ESCAPE_CHARACTER:"ASTM_UNPAIRED_ESCAPE_CHARACTER"="ASTM_UNPAIRED_ESCAPE_CHARACTER"
An escape character appeared where no escape sequence starts (an escape sequence is the escape
character, one body character, and the escape character again). It is read as the literal
character it is and kept byte-for-byte in the decoded value. Nothing is dropped and no byte is
invented: this flags that the sender did not write the character the spec-clean way, which
is &E&.
What it replaced is the reason it exists. The codec used to read such a character as the
opening of a sequence that never closed and merge the whole remainder of the record into the
field it sat in, reporting nothing at all. One & in a result value cost the units, the
abnormal flag and the status together and left the status reading unspecified rather than
final; one in a surname cost the patient's birth date and sex. Emit then re-escaped the merged
text into a spec-clean-looking line that read back as the same wrong value, so the mis-read
survived a round trip without ever surfacing.
This code is not a statement about the rest of the record. It reports one character. A
different escape character in the same record may still head a real three-character sequence,
and if that sequence's body happens to be a delimiter (&|& under the canonical set) the atom
rule means that delimiter does not split. That case is reported separately, under
WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE, and it can still cost a field boundary.
PROFILE_QUIRK_APPLIED
readonlyPROFILE_QUIRK_APPLIED:"PROFILE_QUIRK_APPLIED"="PROFILE_QUIRK_APPLIED"
The downgraded form an active vendor AstmProfile produces from a deviation it expects
(see src/profiles/). The original warning is never dropped: its code moves to
AstmRecordWarning.toleratedCode, the warning is re-badged PROFILE_QUIRK_APPLIED with
expected: true and the tolerating profile named, so a consumer can filter known, grounded noise
while the fact of the deviation, and where it was, survives. A profile can only ever reach this
path for a non-safety-critical code (enforced at profile-definition time); a safety-critical
deviation (a result value, flag, status, patient identifier, code system, or a frame-integrity
warning) can never be tolerated, so it can never be re-badged here.
Example
import { parseAstmRecords, WARNING_CODES } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rL|1\r");
msg.warnings.some((w) => w.code === WARNING_CODES.ASTM_RECORD_UNKNOWN_TYPE);
Functions
ambiguousMessageKind()
ambiguousMessageKind(
position):AstmRecordWarning
Build an ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND warning. Emitted when a message
carries both a Q (request) and an R (result) record; the message is
classified as a host-query request (the Q dominates) and the anomaly is
flagged.
Parameters
position
Returns
Example
import { ambiguousMessageKind } from "@cosyte/astm";
ambiguousMessageKind({ recordIndex: 0, recordType: "H" });
ambiguousValueSplit()
ambiguousValueSplit(
position):AstmRecordWarning
Build an ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT warning. Emitted when a result
value field split on an unescaped component delimiter: the full raw value and
the split are both surfaced, never a silent truncation.
Parameters
position
Returns
Example
import { ambiguousValueSplit } from "@cosyte/astm";
ambiguousValueSplit({ recordIndex: 3, recordType: "R", fieldIndex: 4 });
applyAstmProfile()
applyAstmProfile(
profile,warning):AstmRecordWarning
Apply a profile to a single warning. Returns a downgraded PROFILE_QUIRK_APPLIED
warning when the profile expects this deviation; otherwise returns the original
warning unchanged (referential identity preserved, so an un-tolerated warning is
never reallocated). A warning that is already expected (e.g. re-processed) is
passed through untouched.
The safety gate is re-checked here, and that is not redundant.
defineAstmProfile refuses a safety-critical code at definition time, but
AstmProfile is a plain interface: a hand-authored object literal
type-checks, and parseAstmRecords(raw, { profile }) and setDefaultAstmProfile
both accept one without re-running the factory. A definition-time-only gate
therefore guards a door that has a second entrance. A safety-critical code is
not downgraded here whatever the profile says, so the original warning
survives and strict still escalates it. Silently declining to downgrade, rather
than throwing, is the fail-safe direction: the signal is preserved either way, and
a parse is not turned into an exception by a profile the caller built by hand.
Parameters
profile
The active profile.
warning
One accumulated warning.
Returns
The re-badged warning when tolerated, else the original.
Example
import { applyAstmProfile, astmProfiles, unknownEscapeSequence } from "@cosyte/astm";
const w = unknownEscapeSequence({ recordIndex: 4, recordType: "R", fieldIndex: 5 });
const out = applyAstmProfile(astmProfiles.referenceCorpus, w);
out.code; // "PROFILE_QUIRK_APPLIED"
out.toleratedCode; // "ASTM_UNKNOWN_ESCAPE_SEQUENCE"
applyAstmProfileToWarnings()
applyAstmProfileToWarnings(
warnings,profile):AstmRecordWarning[]
Apply a profile across a whole warning list, returning a NEW array (the input is
never mutated). Returned unchanged (same reference contents, a shallow copy) when
profile is undefined, so the no-profile path pays only a copy. Every tolerated
warning is re-badged; every other warning passes through by identity, nothing is
dropped, reordered, or reallocated beyond the re-badge.
Parameters
warnings
readonly AstmRecordWarning[]
The accumulated record warnings.
profile
AstmProfile | undefined
The active profile, or undefined for no transform.
Returns
A new array with tolerated warnings downgraded.
Example
import { applyAstmProfileToWarnings, astmProfiles } from "@cosyte/astm";
const out = applyAstmProfileToWarnings(msgWarnings, astmProfiles.referenceCorpus);
applyLivd()
applyLivd(
msg,catalog):LivdResult
Apply a consumer-supplied LIVD catalog to a parsed message, producing an
additive, advisory layer of LOINC annotations over its R (result) and O
(order) records. The source message is never mutated; the raw reported codes and
values stay exactly as parsed.
Fail-safe: with no matching entry a code is unmapped (+ an
ASTM_LIVD_UNMAPPED_CODE warning); with a conflicting mapping it is ambiguous
(+ an ASTM_LIVD_AMBIGUOUS_MAPPING warning); a LOINC is never guessed. When
the wire already carried an inline LOINC candidate, it is surfaced from the wire
(never overwritten by the catalog).
Parameters
msg
A parsed ASTM message.
catalog
The consumer-supplied LIVD catalog (build with defineLivdCatalog).
Returns
The annotations and value-free warnings (deeply frozen).
Example
import { parseAstmRecords, defineLivdCatalog, applyLivd } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
const catalog = defineLivdCatalog([{ vendorCode: "687", loinc: "1920-8", loincLongName: "AST" }]);
const { annotations } = applyLivd(msg, catalog);
annotations[0]?.mapping; // { status: "mapped", loinc: "1920-8", loincLongName: "AST", source: "livd", derived: true }
astmDateToLocalISO()
astmDateToLocalISO(
d):string
Render an AstmDate as an ISO-8601-like string truncated to its
precision, with no Z and no offset: because ASTM carries no
timezone and appending one would fabricate information. A consumer that knows
the instrument's zone can attach it; this function never assumes UTC.
Parameters
d
The date to render.
Returns
string
e.g. "2024-03-15T09:30" (minute precision) or "2024-03" (month).
Example
import { astmDateToLocalISO, parseAstmDate } from "@cosyte/astm";
astmDateToLocalISO(parseAstmDate("20240315")!); // "2024-03-15"
attachComments()
attachComments(
records,warnings):AstmRecord[]
Attach every C (comment) record to its parent by position: the immediately-preceding
H/P/O/R record; consecutive comments share that parent. Fail-safe: a comment with no
valid preceding parent is an orphan, it is attached to the message root (attachedToRoot: true) with an ASTM_RECORD_ORPHAN_COMMENT warning, never dropped. Returns a new array; the
non-comment records pass through unchanged.
Parameters
records
readonly AstmRecord[]
The built records, in wire order.
warnings
The accumulator an orphan comment warns onto.
Returns
The records with each comment's parentIndex / attachedToRoot resolved.
Example
import { attachComments } from "@cosyte/astm";
// A comment with no preceding H/P/O/R is an orphan attached to the root.
const warnings: import("@cosyte/astm").AstmRecordWarning[] = [];
const out = attachComments(
[{ type: "C", recordIndex: 0, fields: [], attachedToRoot: false }],
warnings,
);
(out[0] as { attachedToRoot: boolean }).attachedToRoot; // true
buildAstmMessage()
buildAstmMessage(
input):string
Build a spec-clean ASTM/CLSI-LIS02 record stream from typed input.
The builder emits the canonical H|\^& header (plus any header fields), then
every body record with each supplied field at its correct 1-based position and
every value escape-encoded, then an L terminator. Nothing clinical is
defaulted (an unsupplied field is empty, never a guessed value) and the
structural pieces (delimiters, record types, sequence counters, terminator) are
computed. The result parses back to an equal message: build → parse fidelity by
construction.
Parameters
input
The message to build.
Returns
string
The serialized, CR-terminated record stream.
Throws
AstmSerializeError when a value contains an unencodable CR/LF.
Example
import { buildAstmMessage, parseAstmRecords, results } from "@cosyte/astm";
const raw = buildAstmMessage({
records: [{ type: "R", universalTestId: ["", "", "", "687"], value: "28.6", units: "U/L" }],
});
results(parseAstmRecords(raw))[0]?.value; // "28.6"
classifyMessage()
classifyMessage(
records):AstmMessageClassification
Classify a record stream by the host-query flow. Pure and total: it only reads the record type letters, never a field value.
Q dominates: a message with any Q record is a host-query request even when
a result (R) record is also present, so a query is never misread as a result
upload. (The Q+R anomaly is separately warned at parse time.)
An unsupported record letter with no Q alongside it yields indeterminate,
never results or orders: an unreadable letter may have been the Q, and
claiming a positive kind over it is how a query comes to read as a result set.
AstmMessageClassification.hasUnrecognized reports why the answer is
withheld, and the has* counts stay truthful throughout.
Parameters
records
readonly AstmRecord[]
The parsed records, in wire order.
Returns
The message classification.
Example
import { classifyMessage, parseAstmRecords } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rP|1\rQ|1|^SPEC-7||ALL\rL|1\r");
classifyMessage(msg.records).kind; // "host-query"
comments()
comments(
msg): readonlyCommentRecord[]
Every comment (C) record in the message, in wire order. Each carries the
parentIndex of the H/P/O/R it attaches to (or attachedToRoot when
it is an orphan): use commentsFor to get the comments of one record.
Parameters
msg
A parsed single-message stream.
Returns
readonly CommentRecord[]
The comment records (possibly empty).
Throws
AstmAmbiguousStreamError (ASTM_AMBIGUOUS_MULTI_MESSAGE) when the stream
carries more than one message: see messages.
Example
import { parseAstmRecords, comments } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|5|U/L||||F\rC|1|I|checked|G\rL|1\r");
comments(msg)[0]?.text; // "checked"
commentsFor()
commentsFor(
msg,parent): readonlyCommentRecord[]
The comment (C) records attached to a given parent record, in wire order.
Unlike the other extractors this works on any stream, single- or multi-message: the
parent record it is handed already names the message, so there is nothing to disambiguate.
Returns the comments whose parentIndex is that record's recordIndex, so a
comment carrying (e.g.) QC context is read against the record it modifies:
never floated to the wrong one.
Parameters
msg
A parsed message.
parent
The H/P/O/R record whose comments to collect.
Returns
readonly CommentRecord[]
The attached comment records (possibly empty).
Example
import { parseAstmRecords, results, commentsFor } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|5|U/L||||F\rC|1|I|checked|G\rL|1\r");
commentsFor(msg, results(msg)[0]!)[0]?.text; // "checked"
composeAstmFrames()
composeAstmFrames(
records,options?):Uint8Array
Frame reassembled record bytes into a spec-clean ASTM/CLSI-LIS01 byte stream: the inverse of decodeAstmFrames.
Each record is split at 240 text bytes into ETB-closed intermediate frames
and a final ETX frame; frame numbers run 1, 2, … 7, 0, 1 … continuously
across the whole stream; every frame's modulo-256 checksum is computed and
emitted uppercase.
A record in either accepted form is written through unchanged, or refused. A
record given as a Uint8Array is already bytes. A record given as a string is
a byte string, character i becoming byte i, the exact inverse of how a
decoded record becomes a string again. A character above U+00FF is not a byte,
so it is refused with ASTM_FRAME_UNENCODABLE_CHARACTER rather than replaced by
a different character (it used to be truncated to its low byte, which is another
ordinary character, and reached the wire silently). To frame content outside
Latin-1, encode it with the character encoding your instrument uses and pass the
resulting Uint8Array. Those two forms are the whole of what records accepts.
A caller reaching this from JavaScript with some other typed array is outside
the signature and gets no such refusal: each element is still written as its low
byte, the same substitution described above. The reserved-byte check below does
not reach that route either, because it compares elements against the three byte
values and an element like 0x0103 is not one of them, though its low byte
becomes an ETX on the wire. Pass a Uint8Array or a string.
A record carrying a frame-structure byte is refused too, in either form.
STX, ETB and ETX are what the decoder reads as the shape of a frame, and
framing has no escape sequence to hide one behind, so a record holding one is
ASTM_FRAME_RESERVED_BYTE rather than a frame truncated at that byte. It used
to be written as given, and the result was not reliably loud: with the two
following bytes matching, the truncated frame verifies and a whole record is
absorbed into the previous one with an empty warnings array at both layers.
Supplying the record as a Uint8Array does not route around this check.
options.startFrameNumber is checked before this function reads a record,
so on this entry point the refusal never depends on the caller's data.
(serializeFramedAstm serializes every record before it gets here, so
a record that cannot be serialized is refused first on that route.) It has to
be a whole number from 0 to 7, because a frame's number is one ASCII digit;
anything else is ASTM_FRAME_INVALID_START_FRAME_NUMBER rather than whatever
byte the arithmetic truncated to. A value other than the default 1 writes a
continuation of a sequence already in progress, which is what the option is
for, and a continuation decoded on its own opens on a sequence gap: see
ComposeFramesOptions.
What is still not guaranteed: that an accepted record round-trips through the record layer. A delimiter colliding with a record's type letter, for one, frames and de-frames byte-exactly and still re-reads as a different record.
Parameters
records
readonly (string | Uint8Array<ArrayBufferLike>)[]
The reassembled record byte-strings (Uint8Array or latin1
string), one entry per complete record.
options?
ComposeFramesOptions = {}
Encode options.
Returns
Uint8Array
The framed byte stream.
Throws
AstmFrameEncodeError when options.startFrameNumber is not a
whole number from 0 to 7 (ASTM_FRAME_INVALID_START_FRAME_NUMBER), the
list is empty, a record has no bytes (ASTM_FRAME_EMPTY_RECORD), a record
string holds a character above U+00FF
(ASTM_FRAME_UNENCODABLE_CHARACTER), or a record holds an STX, ETB or
ETX byte (ASTM_FRAME_RESERVED_BYTE).
Example
import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
const records = [new TextEncoder().encode("H|\\^&\r"), new TextEncoder().encode("L|1\r")];
const bytes = composeAstmFrames(records);
decodeAstmFrames(bytes).records.length; // 2
computeChecksum()
computeChecksum(
bytes,start,endInclusive):number
Sum bytes [start, endInclusive] of bytes modulo 256: the ASTM frame
checksum span (frame number through the terminator, inclusive).
Parameters
bytes
Uint8Array
The full decoded byte stream.
start
number
First index to include (the frame-number byte, one past STX).
endInclusive
number
Last index to include (the ETB/ETX terminator byte).
Returns
number
The checksum in 0–255.
Example
import { computeChecksum } from "@cosyte/astm";
// bytes: STX '1' 'A' ETX → sum '1' + 'A' + ETX, mod 256.
computeChecksum(new Uint8Array([0x02, 0x31, 0x41, 0x03]), 1, 3); // 0x75
decodeAstmFrames()
decodeAstmFrames(
bytes,options?):DecodeAstmFramesResult
Decode a framed ASTM byte stream into frames + reassembled record bytes.
Bytes outside a frame (before the first STX, or between a frame's trailing
LF and the next STX) are skipped: in ASTM they are low-level transfer
control (ENQ/ACK/NAK/EOT), never record content, so skipping them is
not data loss.
Parameters
bytes
Uint8Array
The framed byte stream.
options?
FrameOptions = {}
Decode options; lenient unless strict is set.
Returns
The decoded frames, the reassembled trusted record bytes, and any warnings.
Throws
AstmParseError with EMPTY_INPUT when bytes is empty (both modes).
Throws
AstmFrameStrictError when strict is set and any deviation occurred.
Example
import { decodeAstmFrames } from "@cosyte/astm";
// One final frame carrying the record text "R|1|": checksum "AF" over FN..ETX (mod 256).
const bytes = new Uint8Array([0x02, 0x31, 0x52, 0x7c, 0x31, 0x7c, 0x03, 0x41, 0x46, 0x0d, 0x0a]);
const { records, frames } = decodeAstmFrames(bytes);
frames[0]?.frameNumber; // 1
records.length; // 1
decodeEscapes()
decodeEscapes(
leaf,d,onUnknown?,onUnpaired?):string
Decode the four recognized ASTM escape mnemonics in a single already-split
leaf (a component string), substituting the active delimiters read from the
header. Unrecognized &X& bodies are preserved verbatim and reported through
onUnknown (never dropped, never guessed), and an escape character that heads
no sequence at all is preserved as a literal and reported through onUnpaired.
This runs after splitting, so a decoded delimiter becomes ordinary literal text and can never introduce a new split boundary.
Parameters
leaf
string
One component string, escape-aware split already applied.
d
The delimiters resolved from the header.
onUnknown?
Called once per unrecognized escape body encountered.
onUnpaired?
Called once per escape character that heads no sequence.
Returns
string
The decoded string.
Example
import { decodeEscapes, CANONICAL_DELIMITERS } from "@cosyte/astm";
decodeEscapes("1&S&40", CANONICAL_DELIMITERS); // "1^40"
decodeEscapes("O&Brien", CANONICAL_DELIMITERS); // "O&Brien" (literal, reported)
deepFreeze()
deepFreeze<
T>(value):T
Recursively freeze an object graph (objects and arrays), returning the same reference typed as deeply readonly. Cyclic graphs are not produced by the parser, so a simple recursive walk suffices.
Type Parameters
T
T
Parameters
value
T
The value to freeze in place.
Returns
T
The same value, now deeply frozen.
Example
import { deepFreeze } from "@cosyte/astm";
const frozen = deepFreeze({ a: [1, 2] });
Object.isFrozen(frozen.a); // true
defineAstmProfile()
defineAstmProfile(
opts):AstmProfile
Build a frozen AstmProfile from a validated options object. Throws
AstmProfileDefinitionError on a bad name, an unknown option key, an
invalid transport, or an invalid tolerate entry, including the safety
rule: a profile may never tolerate a safety-critical warning code (default-deny
across all three registries).
extends composes profiles: lineage, tolerate, transport, provenance, and
description merge (parents left-to-right, then self; scalars are child-wins).
The merged tolerate set is re-validated so a safety-critical code cannot sneak
in via a hand-crafted parent.
Parameters
opts
The profile definition; see DefineAstmProfileOptions.
Returns
A frozen, immutable profile with describe() attached.
Throws
AstmProfileDefinitionError on any invalid definition.
Example
import { defineAstmProfile } from "@cosyte/astm";
const site = defineAstmProfile({
name: "acme-analyzer",
description: "Acme's inbound raw-TCP analyzer",
transport: "raw",
tolerate: [
{ code: "ASTM_NONSTANDARD_DELIMITERS", rationale: "declares its own delimiter set" },
],
provenance: { source: "Acme host-interface manual", reference: "internal-2026" },
});
site.lineage; // ["acme-analyzer"]
console.log(site.describe?.());
defineLivdCatalog()
defineLivdCatalog(
entries):LivdCatalog
Build a LivdCatalog from LIVD entries a consumer supplies. Entries are
indexed by their vendorCode (verbatim). When several entries share a vendor
code:
- all agreeing on the same
loinc→ a singlemappedresult (the first entry's optionalloincLongNameis kept); - disagreeing (two distinct LOINCs) → an
ambiguousresult carrying every distinct candidate, and no choice between them.
The returned catalog is deeply frozen; nothing is mutated after construction.
Parameters
entries
readonly LivdEntry[]
The consumer's LIVD mapping rows.
Returns
An immutable catalog.
Example
import { defineLivdCatalog } from "@cosyte/astm";
const catalog = defineLivdCatalog([
{ vendorCode: "687", loinc: "1920-8", loincLongName: "AST" },
{ vendorCode: "690", loinc: "1742-6", loincLongName: "ALT" },
]);
catalog.lookup("687"); // { status: "mapped", loinc: "1920-8", loincLongName: "AST" }
catalog.lookup("999"); // { status: "unmapped" }
delimitersRedeclared()
delimitersRedeclared(
position):AstmRecordWarning
Build an ASTM_RECORD_DELIMITERS_REDECLARED warning. Emitted when a later H
record declares a delimiter set different from the one in force; the new set is
honored from that header onward. Positional context only: never the delimiters
themselves.
Parameters
position
Returns
Example
import { delimitersRedeclared } from "@cosyte/astm";
delimitersRedeclared({ recordIndex: 5, recordType: "H" });
detectFraming()
detectFraming(
bytes,options?):DetectFramingResult
Detect whether an ASTM byte stream is framed or raw from its leading byte.
- Leading
STX(0x02) orENQ(0x05) ⇒"framed". - A leading bare record letter (
H/P/O/R/C/Q/M/S/L) ⇒"raw". - Anything else (including an empty stream) is ambiguous ⇒ defaults to
"framed"and emits oneASTM_LTP_AMBIGUOUS_TRANSPORTwarning.
An override short-circuits all of the above, returning the forced mode with no
warning.
Parameters
bytes
Uint8Array
The stream to inspect (only the leading byte is read).
options?
DetectFramingOptions = {}
Detection options; an override forces the result.
Returns
The decided framing, whether it was defaulted, and any warning.
Example
import { detectFraming } from "@cosyte/astm";
detectFraming(new Uint8Array([0x02])).framing; // "framed" (STX)
detectFraming(new Uint8Array([0x48])).framing; // "raw" (leading "H")
detectFraming(new Uint8Array([0x2a])).framing; // "framed" (ambiguous → defaulted, + warning)
detectFraming(new Uint8Array([0x48]), { override: "framed" }).framing; // "framed" (forced)
encodeComponent()
encodeComponent(
leaf,d,recordIndex?):string
Escape-encode one component leaf for spec-clean emit: the inverse of
decodeEscapes. Each character is read once, in one left-to-right pass, and
written either as itself or as a whole escape + mnemonic + escape triple
(& → &E&, the field / component / repeat delimiters → &F& / &S& / &R&).
Nothing this encoder writes is examined again, which is what makes it the exact
inverse of the decoder: the decoder reads the same output one token at a time,
so every triple it meets is a triple this encoder wrote.
A CR/LF in the leaf has no escape mnemonic and would break framing, so it
is rejected with an AstmSerializeError rather than emitted raw, as is a
delimiter set the result could not be read back with.
Parameters
leaf
string
One already-decoded component string.
d
The delimiters to emit against (canonical for spec-clean output).
recordIndex?
number
The enclosing record's index, for error context.
Returns
string
The escaped component text.
Throws
AstmSerializeError for a CR/LF in the leaf
(ASTM_EMIT_UNENCODABLE_VALUE), or for a delimiter set failing one of the
three conditions readback requires (ASTM_EMIT_INVALID_DELIMITERS). This
helper takes a leaf and no record, so it never raises
ASTM_EMIT_TYPE_LETTER_COLLISION: a caller assembling a record line out of it
is outside that check and gets no guarantee that a type letter survives.
Example
import { encodeComponent, CANONICAL_DELIMITERS } from "@cosyte/astm";
encodeComponent("1^40", CANONICAL_DELIMITERS); // "1&S&40"
fieldScalar()
fieldScalar(
field):string|undefined
The primary scalar of a field: its first repeat's first component, decoded.
Returns undefined for a truly empty field so callers can distinguish
"absent" from a value: never defaulting a missing value.
Parameters
field
AstmField | undefined
Returns
string | undefined
Example
import { fieldScalar, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
const f = tokenizeRecord("R|1|^^^687|28.6", CANONICAL_DELIMITERS);
fieldScalar(f[3]); // "28.6"
fieldsUnseparated()
fieldsUnseparated(
position):AstmRecordWarning
Build an ASTM_RECORD_FIELDS_UNSEPARATED warning. The raw line is surfaced
intact; what is lost is every modeled field of the record, because the
delimiters in force never split it.
This one is not cosmetic either. A record with content but no field separator is a record being read with a delimiter set that does not belong to it, and on a result record that costs the value, the units and the status in one go. The fields are not reconstructed, because doing so would mean guessing which set the sender meant, so a profile is not permitted to tolerate this code.
It is a report, not a sweep. See WARNING_CODES for the two classes of the same loss it does not see: its absence never certifies a record split correctly.
Parameters
position
Returns
Example
import { fieldsUnseparated } from "@cosyte/astm";
fieldsUnseparated({ recordIndex: 4, recordType: "R" });
frameBadChecksum()
frameBadChecksum(
position):AstmFrameWarning
Build an ASTM_FRAME_BAD_CHECKSUM warning. The frame is surfaced flagged
untrusted and never merged into a record; the warning carries position only.
Parameters
position
Returns
Example
import { frameBadChecksum } from "@cosyte/astm";
frameBadChecksum({ frameNumber: 2, byteOffset: 251 });
frameOversize()
frameOversize(
position):AstmFrameWarning
Build an ASTM_FRAME_OVERSIZE warning. The 240-byte text limit was exceeded
without a split; the frame is flagged, not silently dropped.
Parameters
position
Returns
Example
import { frameOversize } from "@cosyte/astm";
frameOversize({ frameNumber: 1, byteOffset: 0 });
frameSequenceGap()
frameSequenceGap(
position):AstmFrameWarning
Build an ASTM_FRAME_SEQUENCE_GAP warning. A frame was possibly dropped; the
record is not concatenated across the gap as if contiguous.
Parameters
position
Returns
Example
import { frameSequenceGap } from "@cosyte/astm";
frameSequenceGap({ frameNumber: 3, byteOffset: 502 });
frameUnterminated()
frameUnterminated(
position):AstmFrameWarning
Build an ASTM_FRAME_UNTERMINATED warning. The partial bytes are surfaced
flagged untrusted; no partial record is invented.
Parameters
position
Returns
Example
import { frameUnterminated } from "@cosyte/astm";
frameUnterminated({ byteOffset: 730 });
getAstmProfile()
getAstmProfile(
name):AstmProfile|undefined
Look up a built-in profile by name. Returns undefined when no built-in has that
name (a user-defined profile is not in this registry, pass it directly).
Parameters
name
string
The built-in profile name.
Returns
AstmProfile | undefined
The profile, or undefined.
Example
import { getAstmProfile } from "@cosyte/astm";
getAstmProfile("referenceCorpus")?.provenance?.source;
getDefaultAstmProfile()
getDefaultAstmProfile():
AstmProfile|undefined
Return the current process-scoped default profile, or undefined if none is
registered.
Returns
AstmProfile | undefined
The default profile, or undefined.
Example
import { getDefaultAstmProfile } from "@cosyte/astm";
const p = getDefaultAstmProfile();
if (p !== undefined) console.log("default profile:", p.name);
interpretAbnormalFlag()
interpretAbnormalFlag(
raw):AbnormalFlag
Interpret an R-record abnormal-flag field (field 7) against HL7 Table 0078.
Leading/trailing whitespace is ignored for the lookup but the raw text is
preserved verbatim. An unrecognized flag yields { recognized: false, meaning: "undefined" }, surfaced, never dropped, and never "normal".
Parameters
raw
string
The verbatim field-7 text.
Returns
The interpreted flag.
Example
import { interpretAbnormalFlag } from "@cosyte/astm";
interpretAbnormalFlag("U").meaning; // "significant-change-up"
interpretAbnormalFlag("ZZ").meaning; // "undefined" (never "normal")
interpretResultStatus()
interpretResultStatus(
raw):ResultStatus
Interpret an R-record result-status field (field 9).
An absent status (undefined or empty) is typed unspecified: never
assumed final. A present but unrecognized letter is undefined
(recognized false). In every non-F case isActiveFinal is false, so a
correction, a cancellation, or an unknown status can never read as current.
Parameters
raw
string | undefined
The verbatim field-9 text, or undefined/empty when absent.
Returns
The modeled status.
Example
import { interpretResultStatus } from "@cosyte/astm";
const s = interpretResultStatus("F");
s.isActiveFinal; // true
s.supersedes; // false
isNonStandard()
isNonStandard(
d):boolean
Whether a resolved delimiter set differs from the canonical H|\^&. A true
result is a tolerated vendor quirk, surfaced as a value-free warning so a
consumer can notice a non-standard stream without the parser refusing it.
Parameters
d
Returns
boolean
Example
import { isNonStandard, readDelimiters } from "@cosyte/astm";
isNonStandard(readDelimiters("H|\\^&")!); // false
isSafetyCriticalCode()
isSafetyCriticalCode(
code):boolean
True when code is a known warning code that is not tolerable: i.e. a
profile may never list it. A code that is not a real warning code at all returns
false here (the validator reports "unknown code" separately, a distinct
failure with a distinct message).
Parameters
code
string
The warning code to test.
Returns
boolean
true iff the code is a real code outside the tolerable allow-list.
Example
import { isSafetyCriticalCode } from "@cosyte/astm";
isSafetyCriticalCode("ASTM_UNKNOWN_ESCAPE_SEQUENCE"); // false (tolerable)
isSafetyCriticalCode("ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT"); // true
isSafetyCriticalCode("ASTM_RECORD_UNKNOWN_TYPE"); // true (it can mean a lost message boundary)
listAstmProfiles()
listAstmProfiles(): readonly
string[]
The names of every built-in profile, in registration order.
Returns
readonly string[]
The built-in names.
Example
import { listAstmProfiles } from "@cosyte/astm";
listAstmProfiles(); // ["default", "referenceCorpus"]
livdAmbiguousMapping()
livdAmbiguousMapping(
position):AstmLivdWarning
Build an ASTM_LIVD_AMBIGUOUS_MAPPING warning. The code matched multiple distinct
LOINCs; none is chosen (refusing to pick is the fail-safe).
Parameters
position
Where the ambiguous code was seen (record ordinal + type; never the code).
Returns
The warning.
Example
import { livdAmbiguousMapping } from "@cosyte/astm";
livdAmbiguousMapping({ recordIndex: 4, recordType: "O" });
livdUnmappedCode()
livdUnmappedCode(
position):AstmLivdWarning
Build an ASTM_LIVD_UNMAPPED_CODE warning. The reported code stays verbatim and
the mapping is unmapped; no LOINC is fabricated.
Parameters
position
Where the unmapped code was seen (record ordinal + type; never the code).
Returns
The warning.
Example
import { livdUnmappedCode } from "@cosyte/astm";
livdUnmappedCode({ recordIndex: 3, recordType: "R" });
lookupLivdForRecord()
lookupLivdForRecord(
record,catalog):LivdAnnotation
Annotate one R or O record against a LIVD catalog. The building block of
applyLivd; useful when a consumer holds a single record. Never mutates
the record and never fabricates a LOINC.
Parameters
record
The result/order record to annotate.
catalog
The consumer-supplied LIVD catalog.
Returns
The record's annotation.
Example
import { parseAstmRecords, results, defineLivdCatalog, lookupLivdForRecord } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
const catalog = defineLivdCatalog([{ vendorCode: "687", loinc: "1920-8" }]);
lookupLivdForRecord(results(msg)[0]!, catalog).mapping.status; // "mapped"
ltpAmbiguousTransport()
ltpAmbiguousTransport():
AstmLtpWarning
Build an ASTM_LTP_AMBIGUOUS_TRANSPORT warning. The detector defaulted to
framed; a profile override can force raw.
Returns
Example
import { ltpAmbiguousTransport } from "@cosyte/astm";
ltpAmbiguousTransport();
ltpFrameRejected()
ltpFrameRejected(
frameNumber?):AstmLtpWarning
Build an ASTM_LTP_FRAME_REJECTED warning. The receiver sent a NAK; the
frame's text was never appended and the transfer did not advance.
Parameters
frameNumber?
number
The rejected frame's sequence number, when it could be read.
Returns
Example
import { ltpFrameRejected } from "@cosyte/astm";
ltpFrameRejected(2);
ltpInitialState()
ltpInitialState():
LtpState
The neutral starting state for a fresh session: line idle, no record in flight, nothing delivered yet. A consumer creates one of these when a connection opens and folds inbound events through ltpReduce from here.
Returns
A frozen neutral LtpState.
Example
import { ltpInitialState, ltpReduce } from "@cosyte/astm";
let state = ltpInitialState();
const step = ltpReduce(state, { type: "enq" });
step.actions; // [{ type: "sendAck" }]
state = step.state;
ltpReduce()
ltpReduce(
state,event):LtpTransition
Advance the LTP session by one inbound event: a pure function, no I/O.
Behaviour by phase:
- neutral:
enqaccepts establishment (sendAck, enter transfer);eotis a benign line reset (no-op);ack/nakare unexpected at a receiver (surfaced, never read as acceptance); aframebefore establishment is tolerated (Postel's Law), the session auto-establishes and processes it, with a warning. - transfer: a
frameis accepted (sendAck, appended, sequence advanced) only when it is trusted and carries the expected number; a duplicate of the last accepted frame is idempotently re-ACKed without re-appending; a bad or out-of-sequence frame isNAKed and dropped.eotterminates the transfer and returns to neutral (a record left open on anETBis not delivered).enqrestarts establishment.ack/nakare unexpected.
Parameters
state
The current session state.
event
The inbound event.
Returns
The next state, the actions to take, and any warnings.
Example
import { ltpInitialState, ltpReduce } from "@cosyte/astm";
// A bad-checksum frame is NAKed, never ACKed, and never appended.
const est = ltpReduce(ltpInitialState(), { type: "enq" }).state;
const bad = { trusted: false } as never;
ltpReduce(est, { type: "frame", frame: bad }).actions; // [{ type: "sendNak" }]
ltpUnexpectedEvent()
ltpUnexpectedEvent(
frameNumber?):AstmLtpWarning
Build an ASTM_LTP_UNEXPECTED_EVENT warning. The event was surfaced and handled
defensively; it never advanced the transfer as if valid.
Parameters
frameNumber?
number
The frame number, when the unexpected event was a frame; omitted otherwise.
Returns
Example
import { ltpUnexpectedEvent } from "@cosyte/astm";
ltpUnexpectedEvent();
messages()
messages(
msg): readonlyAstmStreamMessage[]
Split a parsed stream into the H … L messages it carries.
This is the safe path and it never throws: a single-message stream yields exactly one entry, and pairing a patient with a result inside one entry is correct by construction.
Returns an array, not an iterator: the stream is already fully parsed and frozen, so
there is no work to defer, and every other extractor in this package returns a
readonly array.
Parameters
msg
A parsed stream.
Returns
readonly AstmStreamMessage[]
The messages, in wire order; never empty for a parsed stream.
Example
import { parseAstmRecords, messages } from "@cosyte/astm";
const stream = parseAstmRecords("H|\\^&\rP|1|PRAC-1\rR|1|^^^687|1.0|U/L||N||F\rL|1\r");
for (const m of messages(stream)) {
m.patient?.practiceAssignedId; // "PRAC-1"
m.results[0]?.value; // "1.0"
}
nonStandardDelimiters()
nonStandardDelimiters(
position):AstmRecordWarning
Build an ASTM_NONSTANDARD_DELIMITERS warning. The declared delimiters are
used as-is; this only flags that they differ from the canonical set.
Parameters
position
Returns
Example
import { nonStandardDelimiters } from "@cosyte/astm";
nonStandardDelimiters({ recordIndex: 0, recordType: "H" });
orders()
orders(
msg): readonlyOrderRecord[]
Every order (O) record in the message, in wire order.
Parameters
msg
A parsed single-message stream.
Returns
readonly OrderRecord[]
The order records (possibly empty).
Throws
AstmAmbiguousStreamError (ASTM_AMBIGUOUS_MULTI_MESSAGE) when the stream
carries more than one message: see messages.
Example
import { parseAstmRecords, orders } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rO|1|ACC-42||^^^687|R\rL|1\r");
orders(msg)[0]?.specimenId; // "ACC-42"
orphanComment()
orphanComment(
position):AstmRecordWarning
Build an ASTM_RECORD_ORPHAN_COMMENT warning. Emitted when a C record had no
valid preceding H/P/O/R parent; the comment is attached to the message
root and surfaced, never dropped.
Parameters
position
Returns
Example
import { orphanComment } from "@cosyte/astm";
orphanComment({ recordIndex: 5, recordType: "C" });
parseAstmDate()
parseAstmDate(
raw):AstmDate|undefined
Parse an ASTM YYYYMMDDHHMMSS value, preserving whatever precision is
present. Returns undefined for a value that is not a usable timestamp (fewer
than four leading digits for the year, or non-digit content): a caller keeps
the raw field text either way, so nothing is lost. A partial value is parsed,
never rejected.
Extra trailing digits beyond seconds (fractional seconds some vendors append) are ignored for the structured value; the full input remains in AstmDate.raw.
Parameters
raw
string
The raw field text.
Returns
AstmDate | undefined
The parsed date, or undefined when it is not a timestamp.
Example
import { parseAstmDate } from "@cosyte/astm";
parseAstmDate("20240315093000")?.precision; // "second"
parseAstmDate("202403")?.precision; // "month"
parseAstmRecords()
parseAstmRecords(
raw,options?):AstmMessage
Parse an ASTM/CLSI-LIS02 record stream into an immutable AstmMessage.
The stream is a sequence of records separated by CR (with LF/CRLF
tolerated); the first record must be an H header, which declares the
delimiters. Lenient by default: set strict to reject any tolerated deviation.
Several messages in one stream. A message runs H … L, so a stream may carry
more than one, and each H declares the delimiters for the records that follow it.
A later header that declares a different set is honored from that header onward
and flagged ASTM_RECORD_DELIMITERS_REDECLARED; records already read keep the set
they were read with, so a redeclaration never reinterprets earlier bytes. A header
that merely restates the set in force is a no-op and warns nothing. A later header
that cannot declare a usable set keeps the delimiters already in force and warns
ASTM_RECORD_UNREADABLE_REDECLARATION: a set is never guessed, and no record is
dropped. Per-header sets are on each HeaderRecord.delimiters;
AstmMessage.delimiters is the first header's.
Parameters
raw
string | Uint8Array<ArrayBufferLike>
The de-framed record bytes, as a string or Uint8Array (decoded
latin1 so byte values survive 1:1).
options?
AstmParseOptions = {}
Parse options; lenient unless strict is set.
Returns
The parsed, deeply-frozen message.
Throws
AstmParseError on a Tier-3 fatal (EMPTY_INPUT,
ASTM_RECORD_NO_HEADER, ASTM_RECORD_UNDECLARED_DELIMITERS).
Throws
AstmStrictError when strict is set and a deviation occurs.
Example
import { parseAstmRecords, results } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rP|1\rO|1|ACC\rR|1|^^^687|28.6|U/L||N||F\rL|1|N\r");
results(msg)[0]?.value; // "28.6"
parseChecksumHex()
parseChecksumHex(
bytes,i0,i1):number|undefined
Read the two-hex-char checksum at bytes[i0]/bytes[i1], case-insensitively
(a lowercase checksum is a tolerated real-vendor quirk). Returns undefined when
either position is out of range or not a hex digit: the caller then treats the
frame as having no readable declared checksum, never as a match.
Parameters
bytes
Uint8Array
The full decoded byte stream.
i0
number
Index of the high hex nibble (immediately after the terminator).
i1
number
Index of the low hex nibble.
Returns
number | undefined
The declared checksum in 0–255, or undefined if unreadable.
Example
import { parseChecksumHex } from "@cosyte/astm";
parseChecksumHex(new Uint8Array([0x37, 0x35]), 0, 1); // 0x75 ("75")
parseChecksumHex(new Uint8Array([0x37, 0x35]), 5, 6); // undefined (out of range)
parseFramedAstm()
parseFramedAstm(
bytes,options?):FramedAstmResult
Decode a framed ASTM byte stream and parse its reassembled records in one call.
Only trusted, contiguous frames are reassembled (a bad-checksum frame, a
sequence gap, or an unterminated frame is surfaced in frames/frameWarnings
but never fed to the record parser), so the parsed message reflects only bytes
the framing layer vouched for.
Parameters
bytes
Uint8Array
The framed byte stream.
options?
FrameOptions & AstmParseOptions = {}
Frame decode options and record parse options (both layers
honor a shared strict).
Returns
The parsed message plus the frame-layer detail.
Throws
AstmParseError EMPTY_INPUT when the stream is empty, or when no
trusted records could be reassembled (nothing to parse).
Throws
AstmFrameStrictError / AstmStrictError in strict mode on
a frame or record deviation, respectively.
Example
import { parseFramedAstm } from "@cosyte/astm";
// A single final frame carrying "H|\^&\r": checksum "E5" over FN..ETX (mod 256).
const bytes = new Uint8Array([
0x02, 0x31, 0x48, 0x7c, 0x5c, 0x5e, 0x26, 0x0d, 0x03, 0x45, 0x35, 0x0d, 0x0a,
]);
const { message, frames } = parseFramedAstm(bytes);
message.header.delimiters.field; // "|"
frames.length; // 1
parseReferenceRange()
parseReferenceRange(
raw):ReferenceRange
Parse an R-record reference-range field (field 6) into a low/high (or
open-ended) pair.
Recognized forms are low-high (closed), <high (open-low), and >low
(open-high). Anything else, including an ambiguous multi-dash string or a
bare non-numeric token, is returned as kind: "unparsed" with the raw text
preserved and no bound invented. Bounds are surfaced as verbatim text, not
coerced to numbers.
Parameters
raw
string
The verbatim field-6 text.
Returns
The parsed reference range.
Example
import { parseReferenceRange } from "@cosyte/astm";
parseReferenceRange("<5").kind; // "open-low"
parseReferenceRange(">10").low; // "10"
parseReferenceRange("weird").kind; // "unparsed" (never a fabricated bound)
partialTimestamp()
partialTimestamp(
position):AstmRecordWarning
Build an ASTM_RECORD_PARTIAL_TIMESTAMP warning. Emitted when a
YYYYMMDDHHMMSS value had an odd digit run that truncates a component; the raw
run is preserved and no time is fabricated.
Parameters
position
Returns
Example
import { partialTimestamp } from "@cosyte/astm";
partialTimestamp({ recordIndex: 2, recordType: "P", fieldIndex: 8 });
patient()
patient(
msg):PatientRecord|undefined
The message's patient (P) record, or undefined when the message carries none.
This is the identity a result files against, so it is answered only when the stream
determines exactly one. It refuses the two shapes where "the patient" is a guess: a
stream carrying several messages, and a single message carrying several P records. In
both, the old behaviour, the first P in the stream, is the wrong-patient path itself.
Parameters
msg
A parsed single-message stream carrying at most one patient.
Returns
PatientRecord | undefined
The patient record, or undefined when the message carries none.
Throws
AstmAmbiguousStreamError (ASTM_AMBIGUOUS_MULTI_MESSAGE) when the stream
carries more than one message, or (ASTM_AMBIGUOUS_MULTI_PATIENT) when its one message
carries more than one P. Use messages and read patients in both cases.
Example
import { parseAstmRecords, patient } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rP|1|PRAC|LAB\rL|1\r");
patient(msg)?.practiceAssignedId; // "PRAC"
primaryCode()
primaryCode(
u):string|undefined
The primary code to key a result on: the inline LOINC candidate when a vendor
supplied one, otherwise the local (vendor) code. Returns undefined when the
field carries no code at all (name-only or empty): never a guess.
Parameters
u
A recognized Universal Test ID.
Returns
string | undefined
The primary code, or undefined.
Example
import { primaryCode, recognizeUniversalTestId } from "@cosyte/astm";
primaryCode(recognizeUniversalTestId(["2345-7", "Glucose", "LN", "687"])); // "2345-7"
profileQuirkApplied()
profileQuirkApplied(
original,profileName):AstmRecordWarning
Build a PROFILE_QUIRK_APPLIED warning: the downgraded form an active vendor profile produces from
a deviation it expects. The original warning is not dropped: its code moves to
toleratedCode, the warning is re-badged PROFILE_QUIRK_APPLIED, expected is set, and the
tolerating profile is named. The original position and message are preserved (both PHI-free by
the same construction as every other factory), so a consumer can filter known, grounded noise while
the fact of the deviation, and where it was, survive. A profile can only ever reach this path for a
non-safety-critical code (enforced at profile-definition time by the safety gate).
Parameters
original
The warning the profile tolerated.
profileName
string
The name of the tolerating profile.
Returns
The re-badged, still-informative warning.
Example
import { profileQuirkApplied, unknownEscapeSequence } from "@cosyte/astm";
const original = unknownEscapeSequence({ recordIndex: 4, recordType: "R", fieldIndex: 5 });
const w = profileQuirkApplied(original, "referenceCorpus");
w.code; // "PROFILE_QUIRK_APPLIED"
w.toleratedCode; // "ASTM_UNKNOWN_ESCAPE_SEQUENCE"
query()
query(
msg): readonlyQueryRecord[]
Every request-information (Q) record in the message, in wire order. A non-empty
result means the message is a host-query request, not a result set: see
AstmMessage.classification (isHostQueryRequest).
Parameters
msg
A parsed single-message stream.
Returns
readonly QueryRecord[]
The query records (possibly empty).
Throws
AstmAmbiguousStreamError (ASTM_AMBIGUOUS_MULTI_MESSAGE) when the stream
carries more than one message: see messages.
Example
import { parseAstmRecords, query } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rP|1\rQ|1|^SPEC-7||ALL\rL|1\r");
query(msg)[0]?.queriesAllTests; // true
readDelimiters()
readDelimiters(
headerRecord):Delimiters|undefined
Read the four delimiters from a header record's raw text (a single H
record, its terminator already stripped).
Returns undefined when the record cannot declare all four delimiters: it is
shorter than H + a field separator + a 3-char delimiter definition, or the
field separator it names also appears among the other three.
What the caller does with that depends on which header it is. On the first
header it is the ASTM_RECORD_UNDECLARED_DELIMITERS fatal, because there is no
earlier set to fall back to. On any later header (a stream may carry several
messages, each declaring its own set) the delimiters already in force are kept
and an ASTM_RECORD_UNREADABLE_REDECLARATION warning is raised instead; a set is
never guessed and no record is dropped.
This function does not throw: delimiter resolution and the escalation decision are kept separate so the reader stays pure and testable.
Parameters
headerRecord
string
The raw H record text (no trailing CR/LF).
Returns
Delimiters | undefined
The resolved delimiters, or a declared failure.
Example
import { readDelimiters } from "@cosyte/astm";
const d = readDelimiters("H|\\^&|||sender");
d.field; // "|"
recognizeUniversalTestId()
recognizeUniversalTestId(
components):UniversalTestId
Recognize a Universal Test ID from a field's already-decoded components.
Parameters
components
readonly string[]
The component strings (escape-decoded), in order.
Returns
The recognized, provenance-tagged Universal Test ID.
Example
import { recognizeUniversalTestId, primaryCode } from "@cosyte/astm";
primaryCode(recognizeUniversalTestId(["", "Glucose", "L", "687"])); // "687"
resolveProfileTransport()
resolveProfileTransport(
profile):AstmFraming|undefined
The profile's transport override, if any: the value a consumer feeds to
detectFraming(bytes, { override }) to force framed/raw and bypass leading-byte
auto-detection. undefined means "let detection decide."
Parameters
profile
AstmProfile | undefined
The active profile, or undefined.
Returns
AstmFraming | undefined
"framed" / "raw" when the profile forces one, else undefined.
Example
import { resolveProfileTransport, detectFraming, astmProfiles } from "@cosyte/astm";
const override = resolveProfileTransport(myRawTcpProfile);
const { framing } = detectFraming(bytes, override !== undefined ? { override } : {});
results()
results(
msg): readonlyResultRecord[]
Every result (R) record in the message, in wire order.
Parameters
msg
A parsed single-message stream.
Returns
readonly ResultRecord[]
The result records (possibly empty).
Throws
AstmAmbiguousStreamError (ASTM_AMBIGUOUS_MULTI_MESSAGE) when the stream
carries more than one message: see messages.
Example
import { parseAstmRecords, results } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
results(msg)[0]?.units; // "U/L"
serializeAstmRecord()
serializeAstmRecord(
record,d?):string
Serialize a single ASTM record to its spec-clean wire text (no trailing terminator). Emits with the given delimiters, defaulting to the canonical set.
The header (H) is special-cased: its delimiter-definition field is emitted as
the literal declaration of d, never escaped, escaping it would corrupt the
very declaration a reader depends on, followed by any characters the modeled
declaration carried beyond the three a reader takes its roles from. Those surplus
characters are kept unless the surplus could not be read back as surplus, when
all of it is dropped: that covers a surplus carrying the field delimiter or
any control character, and it is silent, because emit has no warning channel.
Manufacturer
(M) and scientific (S) records are reproduced byte-identically from their
preserved rawLine when they are already in d, and re-encoded from their fields
when they are not, so their fields never collapse into one on the next read.
Parameters
record
The record to serialize.
d?
Delimiters = CANONICAL_DELIMITERS
The delimiters to emit against; defaults to H|\^&.
Returns
string
The record's wire text, terminator excluded.
Throws
AstmSerializeError when a component contains an unencodable CR/LF
(ASTM_EMIT_UNENCODABLE_VALUE), when d fails one of the three conditions
readback requires (ASTM_EMIT_INVALID_DELIMITERS), or when this record's type
letter would not be the first character of its own emitted line
(ASTM_EMIT_TYPE_LETTER_COLLISION), which would make it read back as a
different record. That last one is a transcoding condition and needs no d
argument to fire: a record read under a different delimiter set can carry a
type letter the canonical set escapes away.
Example
import { serializeAstmRecord, parseAstmRecords } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
serializeAstmRecord(msg.records[1]!); // "R|1|^^^687|28.6|U/L||N||F"
serializeAstmRecords()
serializeAstmRecords(
input,d?):string
Serialize a whole ASTM message (or a bare record list) to a spec-clean,
CR-terminated record stream: the inverse of parseAstmRecords.
Emit is conservative: the canonical H|\^& delimiters, every embedded
delimiter re-escaped, each record closed with a CR. A message parsed with
non-canonical delimiters is normalized to the canonical set on emit: every
record, M and S included, so the emitted stream is in one delimiter set and
re-parsing it recovers every field. Passing d explicitly emits against that set
instead, and the header declares it. Normalization replaces the four delimiter
roles; a header declaration carrying characters beyond the three that hold a
role keeps them, on this path as on any other, rather than being truncated. The
exception is a surplus that could not be read back as one, carrying the field
delimiter or any control character: there the whole surplus is dropped, on
every path including the default one, and silently, because emit returns a bare
string with no channel to say so.
d is checked before anything is written. Each of the four separators must be
exactly one character, none may be a record terminator, and no two may share a
character: otherwise the emitted bytes cannot be read back as the records that
produced them, and emit has no warning channel with which to say so. A set that
fails is an AstmSerializeError with code ASTM_EMIT_INVALID_DELIMITERS.
This is stricter than the parser, which reads some sets it cannot reverse, so
serializeAstmRecords(msg, msg.delimiters) can refuse a message that parsed:
in exactly the cases where it used to emit a stream that read back wrong.
And each record is checked against the set it is being written with. Those
three conditions read the set alone, so they cannot see that field = R
escapes an R record's own type letter away. Every record's emitted line is
therefore checked to start with the letter the record models, and one that does
not is ASTM_EMIT_TYPE_LETTER_COLLISION rather than a stream that reads back as
different records. What neither check promises is that every field lands where
it did: an escape sequence whose body is a delimiter is an opaque atom, so that
delimiter never becomes a boundary, and the parse side reports that
(ASTM_UNKNOWN_ESCAPE_SEQUENCE) rather than emit refusing it.
Parameters
input
AstmMessage | readonly AstmRecord[]
A parsed AstmMessage or a list of AstmRecords.
d?
Delimiters = CANONICAL_DELIMITERS
The delimiters to emit against; defaults to the canonical H|\^& set.
Returns
string
The serialized record stream (CR after every record).
Throws
AstmSerializeError when a component contains an unencodable CR/LF
(ASTM_EMIT_UNENCODABLE_VALUE), when d fails one of the three conditions
readback requires (ASTM_EMIT_INVALID_DELIMITERS), or when a record's type
letter would not be the first character of its own emitted line
(ASTM_EMIT_TYPE_LETTER_COLLISION). The last of the three fires on the
default canonical path as well: it compares each record against the set
being emitted with, so a record read under a different set can carry a type
letter the canonical set escapes away. Omitting d is not a way around it.
Example
import { parseAstmRecords, serializeAstmRecords } from "@cosyte/astm";
const raw = "H|\\^&\rP|1\rR|1|^^^687|28.6|U/L||N||F\rL|1\r";
serializeAstmRecords(parseAstmRecords(raw)); // === raw
serializeField()
serializeField(
field,d?):string
Serialize a single AstmField to its spec-clean wire text, re-escaping each component. A low-level helper for callers assembling a field outside a whole record.
Parameters
field
The field to serialize.
d?
Delimiters = CANONICAL_DELIMITERS
The delimiters to emit against; defaults to H|\^&.
Returns
string
The escaped field text.
Throws
AstmSerializeError when a component contains an unencodable CR/LF
(ASTM_EMIT_UNENCODABLE_VALUE), or when d fails one of the three conditions
readback requires (ASTM_EMIT_INVALID_DELIMITERS). Like
encodeComponent this takes no record, so it never raises
ASTM_EMIT_TYPE_LETTER_COLLISION: encoding a record's type-letter field
through it will escape that letter away without objecting.
Example
import { serializeField, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
const fields = tokenizeRecord("R|1|^^^687|1&S&40", CANONICAL_DELIMITERS);
serializeField(fields[3]!); // "1&S&40"
serializeFramedAstm()
serializeFramedAstm(
input,options?):Uint8Array
Serialize an ASTM message (or a bare record list) and frame it into a spec-clean byte stream in one call: the inverse of parseFramedAstm, composing the two emit layers at the edge.
Each record is serialized to spec-clean, CR-terminated wire text (canonical
delimiters, embedded delimiters re-escaped) and then framed independently
(one record per ETX-closed frame run), so the framing exactly mirrors what
decodeAstmFrames reassembles: parseFramedAstm(serializeFramedAstm(msg))
yields an equal message with the default startFrameNumber. That clause is
load-bearing rather than pedantic: a non-default start writes a continuation of
a sequence already in progress, and a continuation read on its own opens on a
sequence gap, so the decoder does not emit its first record and this round trip
does not hold for it. See ComposeFramesOptions.
A value carrying a character above U+00FF is refused here rather than
framed: the record layer is happy to hold one, but a frame carries bytes and
nothing in the message says which character encoding to turn it into.
A value carrying a raw STX, ETB or ETX is refused too, and being Latin-1
is no exemption: those three are what decodeAstmFrames reads as the
shape of a frame, and framing has no escape sequence to hide one behind. The
record layer is happy to hold those as well, so this is a message that
serializes to records perfectly well and cannot be framed.
Parameters
input
AstmMessage | readonly AstmRecord[]
A parsed AstmMessage or a list of AstmRecords.
options?
ComposeFramesOptions = {}
Frame-encode options.
Returns
Uint8Array
The framed byte stream.
Throws
AstmSerializeError when a value contains an unencodable CR/LF
(ASTM_EMIT_UNENCODABLE_VALUE), or when a record's own type letter would not
survive being written in the canonical set this function serializes with
(ASTM_EMIT_TYPE_LETTER_COLLISION). The second reaches messages parsed under a
different delimiter set, since this function passes no set of its own.
Throws
AstmFrameEncodeError when options.startFrameNumber is not a
whole number from 0 to 7 (ASTM_FRAME_INVALID_START_FRAME_NUMBER), there
are no records to frame (ASTM_FRAME_EMPTY_RECORD), a value holds a
character above U+00FF (ASTM_FRAME_UNENCODABLE_CHARACTER), or a record
holds an STX, ETB or ETX byte (ASTM_FRAME_RESERVED_BYTE).
Example
import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
const bytes = serializeFramedAstm(msg);
parseFramedAstm(bytes).message.records.length; // 3
setDefaultAstmProfile()
setDefaultAstmProfile(
profile):void
Register a process-scoped default profile that parseAstmRecords(raw) applies
when no explicit profile option is passed. Pass null (or undefined) to
clear. An explicit parseAstmRecords(raw, { profile }) always wins;
{ profile: null } opts out of the default for a single call.
Test hygiene: the only mutable module-scoped state here, tests that call this MUST clear it in teardown or default-profile bleed infects later tests.
Parameters
profile
AstmProfile | null
The profile to register as default, or null to clear.
Returns
void
Example
import { setDefaultAstmProfile, astmProfiles, parseAstmRecords } from "@cosyte/astm";
setDefaultAstmProfile(astmProfiles.referenceCorpus);
const msg = parseAstmRecords(raw); // uses referenceCorpus
setDefaultAstmProfile(null); // clear
splitEscapeAware()
splitEscapeAware(
text,delimiter,escape):string[]
Split text on delimiter, treating an escape sequence (escape, one body
character, escape) as an opaque atom so a delimiter that appears inside an
escape body never causes a split. Returns the raw (still-encoded) segments:
decoding is the caller's next step, per the escape-aware-split-then-decode
contract.
For the four canonical mnemonics the opacity is belt-and-suspenders (their bodies are letters, not delimiters), but it makes the "an escaped delimiter is one token" guarantee hold for any declared delimiter set, including adversarial input. A single body character is all that guarantee needs.
An escape character that heads no sequence is not an escape: it is ordinary text, and it opens no atom. Reading it as the opening of a sequence that never closes is what used to merge the whole remainder of a record into one field. Decoding the resulting leaf is what reports it, so this function stays a pure split. Note the two rules together: a delimiter after such a character does split, and a delimiter sitting inside a real three-character atom does not.
Parameters
text
string
The field or repeat string to split.
delimiter
string
The delimiter to split on.
escape
string
The active escape character.
Returns
string[]
The raw segments, in order.
Example
import { splitEscapeAware } from "@cosyte/astm";
splitEscapeAware("a^b^c", "^", "&"); // ["a", "b", "c"]
splitEscapeAware("1&S&40", "^", "&"); // ["1&S&40"] (escape body is opaque)
splitEscapeAware("O&Brien^John", "^", "&"); // ["O&Brien", "John"] (unpaired: literal)
toChecksumHex()
toChecksumHex(
checksum):string
Format a checksum byte as the two uppercase hex characters ASTM puts on the wire (the conservative-emit form). Decode accepts lowercase; emit is uppercase.
Parameters
checksum
number
A value in 0–255.
Returns
string
Two uppercase hex characters, zero-padded (e.g. "0A", "75").
Example
import { toChecksumHex } from "@cosyte/astm";
toChecksumHex(0x0a); // "0A"
tokenizeHeader()
tokenizeHeader(
record,d,onUnknownEscape?,onUnpairedEscape?):AstmField[]
Tokenize an H (header) record into its fields.
A header cannot go through tokenizeRecord: its second field is the delimiter declaration, which carries all three non-field delimiters literally rather than as escape sequences. Run through the generic tokenizer the declaration would be split on its own repeat and component characters and its escape character would be decoded and reported as unpaired, so what the header declares would come back as fragments plus a spurious warning. This tokenizer instead takes the declaration verbatim as one opaque field, never decoded, and applies the ordinary escape-aware tokenizer to the data portion that follows it.
fields[0] is the type-letter field and fields[1] is the delimiter
declaration (verbatim, never escape-decoded); the header's ASTM data fields
follow from fields[2].
Parameters
record
string
The raw H record text (terminator already stripped).
d
The delimiters declared by this header.
onUnknownEscape?
(fieldIndex) => void
Called with the 0-based whole-record field index for each unrecognized escape sequence in the data portion.
onUnpairedEscape?
(fieldIndex) => void
Called with the 0-based whole-record field index for each unpaired escape character in the data portion. The declaration itself is opaque, so the escape character it names never reports here.
Returns
The header's fields.
Example
import { tokenizeHeader, CANONICAL_DELIMITERS } from "@cosyte/astm";
const fields = tokenizeHeader("H|\\^&|||sender", CANONICAL_DELIMITERS);
fields[1].raw; // "\\^&"
fields[4].raw; // "sender"
tokenizeRecord()
tokenizeRecord(
record,d,onUnknownEscape?,onUnpairedEscape?):AstmField[]
Tokenize a single record string (its terminator already stripped) into its
fields. fields[0] is the type-letter field; ASTM data fields follow at
1-based indices.
Parameters
record
string
The raw record text.
d
The delimiters resolved from the header.
onUnknownEscape?
(fieldIndex) => void
Called (with the 0-based field index) for each unrecognized escape sequence encountered, so the caller can warn.
onUnpairedEscape?
(fieldIndex) => void
Called (with the 0-based field index) for each escape character that heads no escape sequence and was therefore read as a literal.
Returns
The record's fields.
Example
import { tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
const fields = tokenizeRecord("R|1|^^^687|28.6|U/L", CANONICAL_DELIMITERS);
fields[3].components[0]; // "28.6"
undefinedAbnormalFlag()
undefinedAbnormalFlag(
position):AstmRecordWarning
Build an ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG warning. The flag is surfaced as
undefined (never coerced to normal); the warning carries only the position.
Parameters
position
Returns
Example
import { undefinedAbnormalFlag } from "@cosyte/astm";
undefinedAbnormalFlag({ recordIndex: 4, recordType: "R", fieldIndex: 7 });
undefinedResultStatus()
undefinedResultStatus(
position):AstmRecordWarning
Build an ASTM_RECORD_UNDEFINED_RESULT_STATUS warning. The status is surfaced
as undefined and, like every non-final status, never reads as active-final.
Parameters
position
Returns
Example
import { undefinedResultStatus } from "@cosyte/astm";
undefinedResultStatus({ recordIndex: 4, recordType: "R", fieldIndex: 9 });
uninterpretedQueryStatus()
uninterpretedQueryStatus(
position):AstmRecordWarning
Build an ASTM_RECORD_UNINTERPRETED_QUERY_STATUS warning. Emitted when a Q
record carries a request-information status code; the code set is paywalled, so
the status is surfaced verbatim and never mapped to a guessed meaning.
Parameters
position
Returns
Example
import { uninterpretedQueryStatus } from "@cosyte/astm";
uninterpretedQueryStatus({ recordIndex: 2, recordType: "Q", fieldIndex: 13 });
unitsAbsent()
unitsAbsent(
position):AstmRecordWarning
Build an ASTM_RECORD_UNITS_ABSENT warning. Emitted when a result carries a
numeric value but no units; units are never defaulted, guessed, or converted.
Parameters
position
Returns
Example
import { unitsAbsent } from "@cosyte/astm";
unitsAbsent({ recordIndex: 4, recordType: "R", fieldIndex: 5 });
unknownEscapeSequence()
unknownEscapeSequence(
position):AstmRecordWarning
Build an ASTM_UNKNOWN_ESCAPE_SEQUENCE warning. The sequence is preserved
verbatim in the decoded value; the warning body carries neither the sequence
nor its surrounding text.
Parameters
position
Returns
Example
import { unknownEscapeSequence } from "@cosyte/astm";
unknownEscapeSequence({ recordIndex: 4, recordType: "R", fieldIndex: 4 });
unknownRecordType()
unknownRecordType(
position):AstmRecordWarning
Build an ASTM_RECORD_UNKNOWN_TYPE warning. The record is still surfaced (as
an unsupported record), never dropped.
This one is not cosmetic. Message grouping decides where a message starts by reading each record's type letter, so an unrecognized letter may be a header the reader failed to see, and two messages then read as one. A profile is therefore not permitted to tolerate this code.
Parameters
position
Returns
Example
import { unknownRecordType } from "@cosyte/astm";
unknownRecordType({ recordIndex: 3, recordType: "Z" });
unpairedEscapeCharacter()
unpairedEscapeCharacter(
position):AstmRecordWarning
Build an ASTM_UNPAIRED_ESCAPE_CHARACTER warning. The character is preserved
verbatim as a literal in the decoded value and opens no atom, so it does not
merge the rest of the record; the warning body carries neither the character's
surroundings nor any field value.
It is a statement about that one character, not about the record. A different escape character in the same record may head a real three-character sequence, and if that sequence's body is a delimiter, that delimiter does not split. See WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER.
A profile may tolerate this code: the value it reports is byte-identical with the warning and without it, because reading the character as a literal is the parse, not a consequence of the warning.
Parameters
position
Returns
Example
import { unpairedEscapeCharacter } from "@cosyte/astm";
unpairedEscapeCharacter({ recordIndex: 4, recordType: "R", fieldIndex: 4 });
unparseableReferenceRange()
unparseableReferenceRange(
position):AstmRecordWarning
Build an ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE warning. The range text is
surfaced verbatim as unparsed; no bound is fabricated.
Parameters
position
Returns
Example
import { unparseableReferenceRange } from "@cosyte/astm";
unparseableReferenceRange({ recordIndex: 4, recordType: "R", fieldIndex: 6 });
unreadableRedeclaration()
unreadableRedeclaration(
position):AstmRecordWarning
Build an ASTM_RECORD_UNREADABLE_REDECLARATION warning. Emitted when a later
H record cannot declare a usable delimiter set; the set already in force is
kept and every record is still surfaced.
Parameters
position
Returns
Example
import { unreadableRedeclaration } from "@cosyte/astm";
unreadableRedeclaration({ recordIndex: 2, recordType: "H" });