Function: 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