Skip to main content
Version: v0.0.14

@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​

AmbiguousCode

message​

string

position​

AstmPosition

counts​
messageCount​

number

patientCount​

number

Returns​

AstmAmbiguousStreamError

Overrides​

Error.constructor

Properties​

code​

readonly code: AmbiguousCode

The stable discriminant: see AMBIGUOUS_CODES.

messageCount​

readonly messageCount: number

How many H … L messages the stream carries.

patientCount​

readonly patientCount: number

How many P records the message in question carries.

position​

readonly position: 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​

AstmFrameEncodeError

Overrides​

Error.constructor

Properties​

characterIndex?​

readonly optional characterIndex?: 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​

readonly code: AstmFrameEncodeErrorCode

Stable discriminant.

recordIndex?​

readonly optional recordIndex?: 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​

AstmFrameStrictError

Overrides​

Error.constructor

Properties​

warnings​

readonly warnings: readonly AstmFrameWarning[]


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​

FatalCode

message​

string

position​

AstmPosition

Returns​

AstmParseError

Overrides​

Error.constructor

Properties​

code​

readonly code: FatalCode

position​

readonly position: 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​

AstmProfileDefinitionError

Overrides​

Error.constructor

Properties​

profileName?​

readonly optional profileName?: 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​

AstmSerializeError

Overrides​

Error.constructor

Properties​

code​

readonly code: 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?​

readonly optional recordIndex?: 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​

AstmStrictError

Overrides​

Error.constructor

Properties​

warnings​

readonly warnings: readonly AstmRecordWarning[]

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?​

readonly optional code?: AbnormalFlagCode

The Table 0078 code, present only when the raw text is a recognized flag.

meaning​

readonly meaning: AbnormalFlagMeaning

The modeled meaning; "undefined" (never "normal") for an unrecognized flag.

raw​

readonly raw: string

The verbatim field text, exactly as received.

recognized​

readonly recognized: 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?​

readonly optional day?: number

hour?​

readonly optional hour?: number

minute?​

readonly optional minute?: number

month?​

readonly optional month?: number

precision​

readonly precision: AstmDatePrecision

How far the components are populated.

raw​

readonly raw: string

The raw digit string as it appeared on the wire.

second?​

readonly optional second?: number

truncated?​

readonly optional truncated?: 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​

readonly year: 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​

readonly components: readonly string[]

Components of the first repeat, each escape-decoded. Empty field → [""].

raw​

readonly raw: string

The exact field text as it appeared on the wire (escapes NOT decoded).

repeats​

readonly repeats: readonly readonly string[][]

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​

readonly byteOffset: number

Byte offset of the STX that opened this frame, within the decoded stream.

checksum​

readonly checksum: FrameChecksum

The checksum verdict.

frameNumber?​

readonly optional frameNumber?: 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​

readonly oversize: boolean

true when the frame's record text exceeded the 240-byte limit.

terminator?​

readonly optional terminator?: FrameTerminator

ETB (intermediate) or ETX (final); undefined for an unterminated frame.

text​

readonly text: Uint8Array

The frame's record-byte payload (may be empty). A copy, safe for the caller to retain.

trusted​

readonly trusted: 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​

readonly unterminated: 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​

readonly byteOffset: number

Byte offset of the STX that opened the frame, within the decoded stream.

frameNumber?​

readonly optional frameNumber?: 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​

readonly code: FrameWarningCode

message​

readonly message: string

Human-readable detail for logs. Never contains the frame's record bytes.

position​

readonly position: 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​

readonly code: LivdWarningCode

message​

readonly message: string

Human-readable detail for logs. Never contains a test code, a value, or a LOINC.

position​

readonly position: 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​

readonly code: LtpWarningCode

frameNumber?​

readonly optional frameNumber?: number

The frame's sequence number when the warning is frame-scoped; absent otherwise.

message​

readonly message: 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​

readonly classification: 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​

readonly delimiters: 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.

readonly header: HeaderRecord

The first header in the stream. A multi-header stream's later headers are in AstmMessage.records.

profile?​

readonly optional profile?: 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​

readonly lineage: readonly string[]

name​

readonly name: string

records​

readonly records: readonly AstmRecord[]

warnings​

readonly warnings: readonly AstmRecordWarning[]


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​

readonly hasOrders: boolean

At least one O (order) record is present.

hasQuery​

readonly hasQuery: boolean

At least one Q (request-information) record is present.

hasResults​

readonly hasResults: boolean

At least one R (result) record is present.

hasUnrecognized​

readonly hasUnrecognized: 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​

readonly isHostQueryRequest: 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​

readonly kind: 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?​

readonly optional profile?: 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?​

readonly optional strict?: 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?​

readonly optional componentIndex?: number

1-based component index within the field, when the deviation is component-scoped.

fieldIndex?​

readonly optional fieldIndex?: number

1-based field index within the record (ASTM fields are 1-indexed).

recordIndex​

readonly recordIndex: number

0-based ordinal of the record within the message.

recordType?​

readonly optional recordType?: 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?​

readonly optional describe?: () => string

Multi-line human-readable summary; always present on factory-built profiles.

Returns​

string

description?​

readonly optional description?: string

Optional human-readable description.

lineage​

readonly lineage: readonly string[]

Resolved lineage: [...parents, name], first-occurrence deduped.

name​

readonly name: string

The profile's unique name (registry key / attribution label).

provenance?​

readonly optional provenance?: AstmProfileProvenance

The cited public grounding for this profile's quirks (absent for default).

tolerate​

readonly tolerate: readonly AstmQuirkTolerance[]

The expected, non-safety-critical deviations this profile tolerates.

transport?​

readonly optional transport?: 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?​

readonly optional note?: string

Optional clarifying note about what in the source grounds the quirks.

reference​

readonly reference: string

A citation the grounding can be traced to: a URL, DOI, or repo+path.

retrieved?​

readonly optional retrieved?: string

When the grounding was last verified (ISO date) or the pinned commit SHA.

source​

readonly source: 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?​

readonly optional fieldIndex?: number

Match only warnings carrying this 1-based field index in their position.

recordType?​

readonly optional recordType?: 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​

readonly code: AnyAstmWarningCode

The existing, non-safety-critical warning code this profile expects.

match?​

readonly optional match?: AstmQuirkMatch

Optional structural narrowing (record type / field index).

rationale​

readonly rationale: 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​

readonly code: WarningCode

expected?​

readonly optional expected?: 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​

readonly message: string

Human-readable detail for logs. Never contains a field value.

position​

readonly position: AstmPosition

profile?​

readonly optional profile?: string

The name of the AstmProfile that tolerated this warning, when expected.

toleratedCode?​

readonly optional toleratedCode?: 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​

readonly comments: readonly CommentRecord[]

Every C (comment) record in this message, in wire order.

delimiters​

readonly delimiters: 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​

readonly header: HeaderRecord

This message's header record.

index​

readonly index: number

0-based ordinal of this message within the stream.

orders​

readonly orders: readonly OrderRecord[]

Every O (order) record in this message, in wire order.

patient​

readonly patient: 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​

readonly patients: readonly PatientRecord[]

Every P record in this message, in wire order (usually zero or one).

queries​

readonly queries: readonly QueryRecord[]

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​

readonly records: readonly AstmRecord[]

Every record of this message in wire order, the header first.

results​

readonly results: readonly ResultRecord[]

Every R (result) record in this message, in wire order.


CommentInput​

Input for a C (comment) record.

Properties​

commentType?​

readonly optional commentType?: string

Field 5: comment type code, emitted verbatim.

seq?​

readonly optional seq?: string

source?​

readonly optional source?: string

Field 3: comment source.

text?​

readonly optional text?: string

Field 4: comment text (a single component). Use CommentInput.textComponents for a structured comment.

textComponents?​

readonly optional textComponents?: readonly string[]

Field 4: comment text as explicit components (takes precedence over text when set).

type​

readonly type: "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​

readonly attachedToRoot: boolean

true when no valid parent preceded: the comment is attached to the message root (and warned).

commentType?​

readonly optional commentType?: 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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

parentIndex?​

readonly optional parentIndex?: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

seq?​

readonly optional seq?: string

Field 2: sequence number.

source?​

readonly optional source?: string

Field 3: comment source (who/what produced it), surfaced verbatim.

text?​

readonly optional text?: 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?​

readonly optional textComponents?: readonly string[]

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​

readonly type: "C"

The record's raw type letter.

Overrides​

RecordBase.type


ComposeFramesOptions​

Options for composeAstmFrames.

Properties​

startFrameNumber?​

readonly optional startFrameNumber?: 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​

readonly frames: readonly AstmFrame[]

Every decoded frame, trusted or not, in wire order.

records​

readonly records: readonly Uint8Array<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​

readonly warnings: readonly AstmFrameWarning[]

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?​

readonly optional description?: string

extends?​

readonly optional extends?: AstmProfile | readonly AstmProfile[]

name​

readonly name: string

provenance?​

readonly optional provenance?: AstmProfileProvenance

tolerate?​

readonly optional tolerate?: readonly AstmQuirkTolerance[]

transport?​

readonly optional transport?: 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​

readonly component: string

Component separator: ASTM ^ by default.

escape​

readonly escape: string

Escape character: ASTM & by default (introduces &F&/&S&/&R&/&E&).

field​

readonly field: string

Field separator: the char immediately after H in the header.

repeat​

readonly repeat: string

Repeat (repetition) separator: ASTM `` by default.


DetectFramingOptions​

Options for detectFraming.

Properties​

override?​

readonly optional override?: 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​

readonly defaulted: boolean

true when the lead byte was unrecognizable and framing was defaulted (not inferred).

framing​

readonly framing: AstmFraming

The decided transport framing.

warnings​

readonly warnings: readonly AstmLtpWarning[]

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​

readonly computed: number

The modulo-256 checksum recomputed over the frame (frame number through terminator).

declared?​

readonly optional declared?: 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​

readonly valid: 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​

readonly frames: readonly AstmFrame[]

Every decoded frame, trusted or not, in wire order.

frameWarnings​

readonly frameWarnings: readonly AstmFrameWarning[]

The frame-layer warnings (bad checksum, sequence gap, unterminated, oversize).

message​

readonly message: AstmMessage

The message parsed from the trusted, reassembled record bytes.


FrameOptions​

Options for decodeAstmFrames. Lenient by default (Postel's Law).

Properties​

strict?​

readonly optional strict?: 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?​

readonly optional fields?: readonly string[]

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​

readonly delimiters: 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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

rawLine​

readonly rawLine: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

type​

readonly type: "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​

readonly mapping: LivdMapping

The mapping outcome: never a guessed LOINC.

provenance​

readonly provenance: UniversalTestIdProvenance

How the reported code was recognized in the Universal Test ID (provenance only, never a lookup).

recordIndex​

readonly recordIndex: number

The recordIndex of the annotated R/O record.

recordType​

readonly recordType: "O" | "R"

The annotated record's type.

reportedCode?​

readonly optional reportedCode?: 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​

readonly size: 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​

LivdLookup

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​

readonly loinc: 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?​

readonly optional loincLongName?: string

The LOINC Long Common Name, when the catalog supplies it: an optional human-readable label.

manufacturer?​

readonly optional manufacturer?: string

The device Manufacturer, when the catalog scopes the mapping to a device (optional provenance).

model?​

readonly optional model?: string

The device Model, when the catalog scopes the mapping to a device (optional provenance).

vendorAnalyteName?​

readonly optional vendorAnalyteName?: string

The Vendor Analyte Name: the vendor's human-readable analyte label, when supplied.

vendorCode​

readonly vendorCode: 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​

readonly annotations: readonly LivdAnnotation[]

One annotation per R/O record, in wire order.

warnings​

readonly warnings: readonly AstmLivdWarning[]

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​

readonly expectedFrame: 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?​

readonly optional lastAcceptedFrame?: 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​

readonly openRecord: 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​

readonly phase: LtpPhase

The current protocol phase.

recordOpen​

readonly recordOpen: boolean

true when a record is mid-reassembly (an ETB frame was accepted, awaiting its ETX).

records​

readonly records: readonly Uint8Array<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​

readonly actions: readonly LtpAction[]

The actions the consumer should take, in order.

state​

readonly state: LtpState

The next session state (frozen).

warnings​

readonly warnings: readonly AstmLtpWarning[]

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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

rawLine​

readonly rawLine: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

type​

readonly type: "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?​

readonly optional header?: HeaderInput

Header fields; the canonical H|\^& declaration is always emitted.

records​

readonly records: readonly AstmRecordInput[]

The body records, in order. H and L are supplied by the builder.

terminationCode?​

readonly optional terminationCode?: 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?​

readonly optional actionCode?: string

Field 12: action code, emitted verbatim.

instrumentSpecimenId?​

readonly optional instrumentSpecimenId?: string

Field 4: instrument specimen ID.

priority?​

readonly optional priority?: string

Field 6: priority, emitted verbatim.

reportType?​

readonly optional reportType?: string

Field 26: report type, emitted verbatim.

seq?​

readonly optional seq?: string

specimenId?​

readonly optional specimenId?: string

Field 3: specimen / accession ID.

type​

readonly type: "O"

universalTestId?​

readonly optional universalTestId?: readonly string[]

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?​

readonly optional actionCode?: 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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

instrumentSpecimenId?​

readonly optional instrumentSpecimenId?: string

Field 4: instrument specimen ID.

priority?​

readonly optional priority?: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

reportType?​

readonly optional reportType?: 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?​

readonly optional seq?: string

Field 2: sequence number.

specimenId?​

readonly optional specimenId?: string

Field 3: specimen / accession ID.

type​

readonly type: "O"

The record's raw type letter.

Overrides​

RecordBase.type

universalTestId?​

readonly optional universalTestId?: 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?​

readonly optional birthDate?: string

Field 8: birthdate (YYYYMMDDHHMMSS), emitted verbatim, never reformatted.

laboratoryAssignedId?​

readonly optional laboratoryAssignedId?: string

Field 4: laboratory-assigned patient ID.

mothersMaidenName?​

readonly optional mothersMaidenName?: string

Field 7: mother's maiden name.

name?​

readonly optional name?: PatientNameInput

Field 6: patient name (Last^First^Middle).

patientIdThree?​

readonly optional patientIdThree?: string

Field 5: a third patient identifier.

practiceAssignedId?​

readonly optional practiceAssignedId?: string

Field 3: practice-assigned patient ID.

seq?​

readonly optional seq?: string

Structural sequence number; auto-computed when omitted.

sex?​

readonly optional sex?: string

Field 9: sex, emitted verbatim (never defaulted).

type​

readonly type: "P"


PatientName​

A patient name (Last^First^Middle), each component surfaced verbatim.

Properties​

first?​

readonly optional first?: string

last?​

readonly optional last?: string

middle?​

readonly optional middle?: string

raw​

readonly raw: string


PatientNameInput​

A patient name split into its components; only the supplied parts are emitted.

Properties​

first?​

readonly optional first?: string

last?​

readonly optional last?: string

middle?​

readonly optional middle?: 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?​

readonly optional birthDate?: AstmDate

Field 8: birthdate (YYYYMMDDHHMMSS, precision-preserving; a truncated run sets truncated).

fields​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

laboratoryAssignedId?​

readonly optional laboratoryAssignedId?: string

Field 4: laboratory-assigned patient ID. Distinct from PatientRecord.practiceAssignedId.

mothersMaidenName?​

readonly optional mothersMaidenName?: string

Field 7: mother's maiden name, surfaced verbatim (a surname component; PHI).

name?​

readonly optional name?: PatientName

Field 6: patient name (Last^First^Middle).

patientIdThree?​

readonly optional patientIdThree?: 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?​

readonly optional practiceAssignedId?: string

Field 3: practice-assigned patient ID. Distinct from PatientRecord.laboratoryAssignedId.

recordIndex​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

seq?​

readonly optional seq?: string

Field 2: sequence number.

sex?​

readonly optional sex?: string

Field 9: sex, surfaced raw (M/F/U/vendor value).

type​

readonly type: "P"

The record's raw type letter.

Overrides​

RecordBase.type


QueryInput​

Input for a Q (request-information) record.

Properties​

endingRangeId?​

readonly optional endingRangeId?: string

Field 4: ending range ID, emitted verbatim.

queriesAllTests?​

readonly optional queriesAllTests?: boolean

Field 5: emit the literal ALL universal-query keyword instead of a Universal Test ID.

requestInformationStatus?​

readonly optional requestInformationStatus?: string

Field 13: request-information status, emitted verbatim.

seq?​

readonly optional seq?: string

startingRangeId?​

readonly optional startingRangeId?: string

Field 3: starting range ID, emitted verbatim.

type​

readonly type: "Q"

universalTestId?​

readonly optional universalTestId?: readonly string[]

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?​

readonly optional endingRangeId?: string

Field 4: ending range ID number, surfaced verbatim (same [OSS-derived] caveat as field 3).

fields​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

queriesAllTests​

readonly queriesAllTests: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

requestInformationStatus?​

readonly optional requestInformationStatus?: 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?​

readonly optional seq?: string

Field 2: sequence number.

startingRangeId?​

readonly optional startingRangeId?: 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​

readonly type: "Q"

The record's raw type letter.

Overrides​

RecordBase.type

universalTestId?​

readonly optional universalTestId?: 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?​

readonly optional high?: string

The upper bound, verbatim numeric text (for closed and open-low).

kind​

readonly kind: ReferenceRangeKind

The recognized shape, or "unparsed" when the text matched no known form.

low?​

readonly optional low?: string

The lower bound, verbatim numeric text (for closed and open-high).

raw​

readonly raw: string

The verbatim field text, exactly as received.


ResultInput​

Input for an R (result) record. No clinical field is defaulted; unsupplied ⇒ empty.

Properties​

abnormalFlags?​

readonly optional abnormalFlags?: string

Field 7: abnormal flags, emitted verbatim (never defaulted to N).

completedAt?​

readonly optional completedAt?: string

Field 13: test-completed timestamp, verbatim.

instrument?​

readonly optional instrument?: string

Field 14: instrument identifier.

operator?​

readonly optional operator?: string

Field 11: operator.

referenceRange?​

readonly optional referenceRange?: string

Field 6: reference range, emitted verbatim.

resultStatus?​

readonly optional resultStatus?: string

Field 9: result status, emitted verbatim (never defaulted to F).

seq?​

readonly optional seq?: string

startedAt?​

readonly optional startedAt?: string

Field 12: test-started timestamp (YYYYMMDDHHMMSS), verbatim.

type​

readonly type: "R"

units?​

readonly optional units?: string

Field 5: units (vendor free text; never defaulted, guessed, or converted).

universalTestId?​

readonly optional universalTestId?: readonly string[]

Field 3: Universal Test ID components (verbatim).

value?​

readonly optional value?: 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?​

readonly optional abnormalFlags?: string

Field 7: abnormal flags, surfaced raw (HL7 Table 0078 values).

completedAt?​

readonly optional completedAt?: AstmDate

Field 13: test completed timestamp.

fields​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

flag?​

readonly optional flag?: 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?​

readonly optional instrument?: string

Field 14: instrument identifier.

operator?​

readonly optional operator?: string

Field 11: operator.

range?​

readonly optional range?: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

referenceRange?​

readonly optional referenceRange?: string

Field 6: reference range, surfaced raw.

resultStatus?​

readonly optional resultStatus?: string

Field 9: result status, surfaced raw (F/C/X/…).

seq?​

readonly optional seq?: string

Field 2: sequence number.

startedAt?​

readonly optional startedAt?: AstmDate

Field 12: test started timestamp.

status​

readonly status: 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​

readonly type: "R"

The record's raw type letter.

Overrides​

RecordBase.type

units?​

readonly optional units?: string

Field 5: units (vendor free text; a missing unit is not defaulted).

universalTestId?​

readonly optional universalTestId?: UniversalTestId

Field 3: Universal Test ID (local code in component 4 is the primary identifier).

value?​

readonly optional value?: 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?​

readonly optional valueComponents?: readonly string[]

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:

  • isActiveFinal is true only for a plain F (final). It is false for 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.
  • supersedes is true for C: this value replaces a previously transmitted one.
  • cancelled is true for X: 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​

readonly cancelled: boolean

true for X: the result cannot be done / was cancelled.

code?​

readonly optional code?: ResultStatusCode

The recognized status code, present only when the raw text is a known status.

isActiveFinal​

readonly isActiveFinal: boolean

true only for a plain F (final): never for C, X, absent, or unrecognized.

meaning​

readonly meaning: ResultStatusMeaning

The modeled meaning; "unspecified" when absent, "undefined" when unrecognized.

raw?​

readonly optional raw?: string

The verbatim field text, present only when field 9 carried a value.

recognized​

readonly recognized: boolean

Whether the raw text matched a recognized status letter.

supersedes​

readonly supersedes: 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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

rawLine​

readonly rawLine: 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​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

type​

readonly type: "S"

The record's raw type letter.

Overrides​

RecordBase.type


TerminatorRecord​

The L (terminator) record: closes a message.

Extends​

  • RecordBase

Properties​

fields​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

recordIndex​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

type​

readonly type: "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?​

readonly optional codingScheme?: string

Component 3: the coding-scheme selector, when present.

components​

readonly components: readonly string[]

The field's components, verbatim and in order.

localCode?​

readonly optional localCode?: string

Component 4, the vendor/local code: the primary identifier when no inline LOINC is given.

loincCandidate?​

readonly optional loincCandidate?: string

Component 1 when populated: a candidate LOINC (provenance only, never validated).

provenance​

readonly provenance: UniversalTestIdProvenance

Where the primary identifier came from.

testName?​

readonly optional testName?: 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​

readonly fields: readonly AstmField[]

The record's fields. fields[0] is the type-letter field; data fields are 1-indexed after it.

Inherited from​

RecordBase.fields

rawType​

readonly rawType: string

The raw type letter as it appeared on the wire.

recordIndex​

readonly recordIndex: number

0-based ordinal of the record within the message.

Inherited from​

RecordBase.recordIndex

type​

readonly type: "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​

readonly fields: readonly string[]

Data fields (after the type letter), emitted verbatim in order.

type​

readonly type: "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 typeof AMBIGUOUS_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 a string holds a character above U+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, ETB or ETX). Framing has no escape mechanism, so such a byte cannot be carried inside a frame at all.
  • ASTM_FRAME_INVALID_START_FRAME_NUMBER: options.startFrameNumber is not a whole number from 0 to 7. 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 one Q record): it is a request for information, and must never be read as a result set (the load-bearing safety distinction of this layer). Q dominates: a Q present classifies the message as a request even if a result record is also present (an anomaly, separately warned), so a Q-bearing message is never silently treated as a result upload.
  • results (no Q, at least one R result record): a result upload / response.
  • orders (no Q, no R, at least one O order 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 typeof FATAL_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 typeof FRAME_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: readonly string[]; 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: readonly string[]; 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 (labeled derived: true, source: "livd").
  • inline-loinc, the wire itself carried a LOINC in the Universal Test ID's slot (component 1); surfaced source: "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 typeof LIVD_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 typeof LTP_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 below high.
  • open-high: a lower bound only (>10), so everything at or above low.
  • 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 typeof WARNING_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​

const ALL_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​

const AMBIGUOUS_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​

readonly ASTM_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​

readonly ASTM_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​

const ASTM_ACK: 6 = 0x06

Acknowledge (0x06): the receiver accepted the last establishment or frame.


ASTM_ENQ​

const ASTM_ENQ: 5 = 0x05

Enquiry (0x05): the sender's request to establish a transfer.


ASTM_EOT​

const ASTM_EOT: 4 = 0x04

End of transmission (0x04): the sender terminated the transfer; the line returns to neutral.


ASTM_NAK​

const ASTM_NAK: 21 = 0x15

Negative acknowledge (0x15): the receiver rejected the last frame; retransmit, do not accept.


astmProfiles​

const astmProfiles: 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​

readonly default: AstmProfile

referenceCorpus​

readonly referenceCorpus: AstmProfile

Example​

import { parseAstmRecords, astmProfiles } from "@cosyte/astm";
const msg = parseAstmRecords(raw, { profile: astmProfiles.referenceCorpus });
msg.profile?.name; // "referenceCorpus"

CANONICAL_DELIMITERS​

const CANONICAL_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​

const FATAL_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​

readonly ASTM_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​

readonly ASTM_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​

readonly EMPTY_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​

const FRAME_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

const LIVD_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​

readonly ASTM_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​

readonly ASTM_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​

const LTP_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

const SAFETY_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​

const TOLERABLE_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​

const VERSION: string = "0.0.14"

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​

const WARNING_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​

readonly ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS" = "ASTM_NONSTANDARD_DELIMITERS"

The header declared delimiters other than the canonical H|\^&: tolerated, noted.

ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly ASTM_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​

readonly PROFILE_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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmProfile

The active profile.

warning​

AstmRecordWarning

One accumulated warning.

Returns​

AstmRecordWarning

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​

AstmRecordWarning[]

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​

AstmMessage

A parsed ASTM message.

catalog​

LivdCatalog

The consumer-supplied LIVD catalog (build with defineLivdCatalog).

Returns​

LivdResult

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​

AstmDate

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​

AstmRecordWarning[]

The accumulator an orphan comment warns onto.

Returns​

AstmRecord[]

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​

MessageInput

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​

AstmMessageClassification

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): readonly CommentRecord[]

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​

AstmMessage

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): readonly CommentRecord[]

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​

AstmMessage

A parsed message.

parent​

AstmRecord

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​

DecodeAstmFramesResult

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​

Delimiters

The delimiters resolved from the header.

onUnknown?​

UnknownEscapeSink

Called once per unrecognized escape body encountered.

onUnpaired?​

UnpairedEscapeSink

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​

DefineAstmProfileOptions

The profile definition; see DefineAstmProfileOptions.

Returns​

AstmProfile

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 single mapped result (the first entry's optional loincLongName is kept);
  • disagreeing (two distinct LOINCs) → an ambiguous result 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​

LivdCatalog

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​

AstmPosition

Returns​

AstmRecordWarning

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) or ENQ (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 one ASTM_LTP_AMBIGUOUS_TRANSPORT warning.

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​

DetectFramingResult

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. The escape character itself is encoded first (& → &E&) so a later delimiter substitution can never double-encode the & it just introduced; then the field / component / repeat delimiters map to their mnemonics.

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​

Delimiters

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmFramePosition

Returns​

AstmFrameWarning

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​

AstmFramePosition

Returns​

AstmFrameWarning

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​

AstmFramePosition

Returns​

AstmFrameWarning

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​

AstmFramePosition

Returns​

AstmFrameWarning

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​

AbnormalFlag

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​

ResultStatus

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​

Delimiters

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​

AstmPosition

Where the ambiguous code was seen (record ordinal + type; never the code).

Returns​

AstmLivdWarning

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​

AstmPosition

Where the unmapped code was seen (record ordinal + type; never the code).

Returns​

AstmLivdWarning

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​

OrderRecord | ResultRecord

The result/order record to annotate.

catalog​

LivdCatalog

The consumer-supplied LIVD catalog.

Returns​

LivdAnnotation

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​

AstmLtpWarning

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​

AstmLtpWarning

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​

LtpState

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: enq accepts establishment (sendAck, enter transfer); eot is a benign line reset (no-op); ack/nak are unexpected at a receiver (surfaced, never read as acceptance); a frame before establishment is tolerated (Postel's Law), the session auto-establishes and processes it, with a warning.
  • transfer: a frame is 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 is NAKed and dropped. eot terminates the transfer and returns to neutral (a record left open on an ETB is not delivered). enq restarts establishment. ack/nak are unexpected.

Parameters​

state​

LtpState

The current session state.

event​

LtpEvent

The inbound event.

Returns​

LtpTransition

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​

AstmLtpWarning

Example​

import { ltpUnexpectedEvent } from "@cosyte/astm";
ltpUnexpectedEvent();

messages()​

messages(msg): readonly AstmStreamMessage[]

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​

AstmMessage

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​

AstmPosition

Returns​

AstmRecordWarning

Example​

import { nonStandardDelimiters } from "@cosyte/astm";
nonStandardDelimiters({ recordIndex: 0, recordType: "H" });

orders()​

orders(msg): readonly OrderRecord[]

Every order (O) record in the message, in wire order.

Parameters​

msg​

AstmMessage

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmMessage

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​

FramedAstmResult

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​

ReferenceRange

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmMessage

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​

UniversalTestId

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​

AstmRecordWarning

The warning the profile tolerated.

profileName​

string

The name of the tolerating profile.

Returns​

AstmRecordWarning

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): readonly QueryRecord[]

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​

AstmMessage

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​

UniversalTestId

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): readonly ResultRecord[]

Every result (R) record in the message, in wire order.

Parameters​

msg​

AstmMessage

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. 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​

AstmRecord

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.

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​

AstmField

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​

Delimiters

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​

AstmField[]

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​

Delimiters

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​

AstmField[]

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

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​

AstmPosition

Returns​

AstmRecordWarning

Example​

import { unreadableRedeclaration } from "@cosyte/astm";
unreadableRedeclaration({ recordIndex: 2, recordType: "H" });