Skip to main content
Version: v0.0.14

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