@cosyte/astm 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +7 -1
- package/dist/index.cjs +27 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -25
- package/dist/index.d.ts +117 -25
- package/dist/index.mjs +27 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1103,7 +1103,7 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
|
|
|
1103
1103
|
* Any warning code from any of the three registries: the record layer
|
|
1104
1104
|
* (`ASTM_RECORD_*` / `ASTM_NONSTANDARD_DELIMITERS` / `ASTM_UNKNOWN_ESCAPE_SEQUENCE`),
|
|
1105
1105
|
* the frame codec (`ASTM_FRAME_*`), or the LTP protocol layer (`ASTM_LTP_*`). A
|
|
1106
|
-
* {@link
|
|
1106
|
+
* {@link AstmQuirkTolerance} may *name* any of them, but the safety gate refuses all
|
|
1107
1107
|
* but the small, benign, record-layer subset, so the union exists mainly to let
|
|
1108
1108
|
* the gate reject a frame/LTP code with a precise, typed message.
|
|
1109
1109
|
*/
|
|
@@ -1136,7 +1136,7 @@ interface AstmProfileProvenance {
|
|
|
1136
1136
|
readonly note?: string;
|
|
1137
1137
|
}
|
|
1138
1138
|
/**
|
|
1139
|
-
* Optional structural narrowing for
|
|
1139
|
+
* Optional structural narrowing for an {@link AstmQuirkTolerance}. When present, the
|
|
1140
1140
|
* tolerance applies only to warnings whose PHI-free {@link AstmPosition} matches
|
|
1141
1141
|
* every provided field, so a profile can expect a deviation on one record type
|
|
1142
1142
|
* (e.g. an unknown escape only inside `R` result values) without blanket-tolerating
|
|
@@ -1911,7 +1911,7 @@ interface AstmMessage {
|
|
|
1911
1911
|
* The vendor profile that was active for this parse, when one applied: its
|
|
1912
1912
|
* `name` and resolved `lineage` (attribution only; the profile object itself is
|
|
1913
1913
|
* not embedded). Absent when no profile applied. A profile that tolerated a
|
|
1914
|
-
* deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link warnings}, not
|
|
1914
|
+
* deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link AstmMessage.warnings}, not
|
|
1915
1915
|
* here.
|
|
1916
1916
|
*/
|
|
1917
1917
|
readonly profile?: {
|
|
@@ -2042,7 +2042,10 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2042
2042
|
* set (a non-canonical source is normalized to `H|\^&` by default). Normalizing sets the
|
|
2043
2043
|
* four delimiter **roles**; it does not delete a declaration's surplus characters, so a
|
|
2044
2044
|
* canonically-declared header that arrived carrying surplus keeps it on the default path
|
|
2045
|
-
* too
|
|
2045
|
+
* too, **except where that surplus could not be read back as surplus, when all of it is
|
|
2046
|
+
* dropped** (see `declarationResidual` below). Emit re-encodes what it read, so bytes
|
|
2047
|
+
* can differ without anything being lost: `O&BRIEN` in a canonical stream goes out as
|
|
2048
|
+
* `O&E&BRIEN`.
|
|
2046
2049
|
*
|
|
2047
2050
|
* **One delimiter set for the whole stream.** Every record (including `H`, `M` and `S`)
|
|
2048
2051
|
* is emitted against the delimiters being emitted with, and the header declares that same
|
|
@@ -2063,7 +2066,11 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2063
2066
|
* `H|\^&#` rather than losing the `#`. That holds on the **default** canonical path as
|
|
2064
2067
|
* well as when a set is passed explicitly: the surplus of a canonically-declared header
|
|
2065
2068
|
* survives normalization, because normalization is about the four roles and the surplus
|
|
2066
|
-
* has none.
|
|
2069
|
+
* has none. It stops holding where the surplus itself could not be read back, and then
|
|
2070
|
+
* **the whole surplus is dropped, not the offending character alone**, on the default
|
|
2071
|
+
* path as much as any other: `declarationResidual` states which surpluses those are and
|
|
2072
|
+
* why an all-or-nothing drop is the only one of the available answers that invents
|
|
2073
|
+
* nothing.
|
|
2067
2074
|
*
|
|
2068
2075
|
* **Never break framing.** A component leaf that contains a record terminator
|
|
2069
2076
|
* (`CR`/`LF`) cannot be escaped by the ASTM escape codec (only the four declared
|
|
@@ -2084,10 +2091,15 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2084
2091
|
* output, which is why they are refused at this one.
|
|
2085
2092
|
*
|
|
2086
2093
|
* **One position is not a modeled value and does not keep the byte:** the surplus
|
|
2087
|
-
* of a header's delimiter declaration, where
|
|
2094
|
+
* of a header's delimiter declaration, where a control character is dropped
|
|
2088
2095
|
* silently (see `declarationResidual` below, where that disposition is argued and
|
|
2089
2096
|
* where it long predates the frame-layer refusal). So "the record layer carries
|
|
2090
2097
|
* these bytes" is a statement about values, not about every byte of every line.
|
|
2098
|
+
* Measured, one character at a time in the surplus of an otherwise spec-clean
|
|
2099
|
+
* `H|\^&` header: **31 of the 33 C0/`DEL` characters reach that rule and are
|
|
2100
|
+
* dropped**, with `warnings: []` and no error. The two that do not are `CR` and
|
|
2101
|
+
* `LF`, which never reach it at all, because they end the record while it is being
|
|
2102
|
+
* read and so are never part of any surplus the model carries.
|
|
2091
2103
|
*
|
|
2092
2104
|
* **Check the delimiter set before writing.** Three conditions are required for the
|
|
2093
2105
|
* emitted bytes to read back as the records that produced them (each separator exactly
|
|
@@ -2166,10 +2178,12 @@ declare class AstmSerializeError extends Error {
|
|
|
2166
2178
|
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS" | "ASTM_EMIT_TYPE_LETTER_COLLISION";
|
|
2167
2179
|
/**
|
|
2168
2180
|
* Escape-encode one component leaf for spec-clean emit: the inverse of
|
|
2169
|
-
* `decodeEscapes`.
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2181
|
+
* `decodeEscapes`. Each character is read **once**, in one left-to-right pass, and
|
|
2182
|
+
* written either as itself or as a whole `escape` + mnemonic + `escape` triple
|
|
2183
|
+
* (`&` → `&E&`, the field / component / repeat delimiters → `&F&` / `&S&` / `&R&`).
|
|
2184
|
+
* Nothing this encoder writes is examined again, which is what makes it the exact
|
|
2185
|
+
* inverse of the decoder: the decoder reads the same output one token at a time,
|
|
2186
|
+
* so every triple it meets is a triple this encoder wrote.
|
|
2173
2187
|
*
|
|
2174
2188
|
* A `CR`/`LF` in the leaf has no escape mnemonic and would break framing, so it
|
|
2175
2189
|
* is rejected with an {@link AstmSerializeError} rather than emitted raw, as is a
|
|
@@ -2199,7 +2213,11 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
2199
2213
|
* The header (`H`) is special-cased: its delimiter-definition field is emitted as
|
|
2200
2214
|
* the **literal** declaration of `d`, never escaped, escaping it would corrupt the
|
|
2201
2215
|
* very declaration a reader depends on, followed by any characters the modeled
|
|
2202
|
-
* declaration carried beyond the three a reader takes its roles from.
|
|
2216
|
+
* declaration carried beyond the three a reader takes its roles from. Those surplus
|
|
2217
|
+
* characters are kept unless the surplus could not be read back as surplus, when
|
|
2218
|
+
* **all** of it is dropped: that covers a surplus carrying the field delimiter or
|
|
2219
|
+
* any control character, and it is silent, because emit has no warning channel.
|
|
2220
|
+
* Manufacturer
|
|
2203
2221
|
* (`M`) and scientific (`S`) records are reproduced **byte-identically** from their
|
|
2204
2222
|
* preserved `rawLine` when they are already in `d`, and re-encoded from their fields
|
|
2205
2223
|
* when they are not, so their fields never collapse into one on the next read.
|
|
@@ -2234,7 +2252,11 @@ declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string
|
|
|
2234
2252
|
* re-parsing it recovers every field. Passing `d` explicitly emits against that set
|
|
2235
2253
|
* instead, and the header declares it. Normalization replaces the four delimiter
|
|
2236
2254
|
* **roles**; a header declaration carrying characters beyond the three that hold a
|
|
2237
|
-
* role keeps them, on this path as on any other, rather than being truncated.
|
|
2255
|
+
* role keeps them, on this path as on any other, rather than being truncated. The
|
|
2256
|
+
* exception is a surplus that could not be read back as one, carrying the field
|
|
2257
|
+
* delimiter or any control character: there the **whole** surplus is dropped, on
|
|
2258
|
+
* every path including the default one, and silently, because emit returns a bare
|
|
2259
|
+
* string with no channel to say so.
|
|
2238
2260
|
*
|
|
2239
2261
|
* **`d` is checked before anything is written.** Each of the four separators must be
|
|
2240
2262
|
* exactly one character, none may be a record terminator, and no two may share a
|
|
@@ -4407,7 +4429,8 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4407
4429
|
* case of);
|
|
4408
4430
|
* - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
|
|
4409
4431
|
* continuous across every frame in the stream (as the decoder's sequence check
|
|
4410
|
-
* expects);
|
|
4432
|
+
* expects); a caller may start the sequence elsewhere to compose one transfer
|
|
4433
|
+
* across several calls, but only at a number a frame can actually carry;
|
|
4411
4434
|
* - the **240-byte text split**: a record longer than 240 bytes is split across
|
|
4412
4435
|
* frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
|
|
4413
4436
|
* control bytes are never counted toward the 240.
|
|
@@ -4432,6 +4455,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4432
4455
|
* - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
|
|
4433
4456
|
* structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
|
|
4434
4457
|
* a byte cannot be carried inside a frame at all.
|
|
4458
|
+
* - `ASTM_FRAME_INVALID_START_FRAME_NUMBER`: `options.startFrameNumber` is not a
|
|
4459
|
+
* whole number from `0` to `7`. A frame's number is one ASCII digit, so a
|
|
4460
|
+
* value outside that range has no digit to be written as.
|
|
4435
4461
|
*
|
|
4436
4462
|
* @example
|
|
4437
4463
|
* ```ts
|
|
@@ -4439,7 +4465,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4439
4465
|
* const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4440
4466
|
* ```
|
|
4441
4467
|
*/
|
|
4442
|
-
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
|
|
4468
|
+
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE" | "ASTM_FRAME_INVALID_START_FRAME_NUMBER";
|
|
4443
4469
|
/**
|
|
4444
4470
|
* Thrown by {@link composeAstmFrames} when the input cannot be framed into a
|
|
4445
4471
|
* spec-clean stream. Carries a stable code + positional context, never the
|
|
@@ -4471,6 +4497,20 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
|
|
|
4471
4497
|
* before framing: which byte belongs in a clinical value is the sender's call, not
|
|
4472
4498
|
* this library's, so it refuses rather than substituting or deleting one.
|
|
4473
4499
|
*
|
|
4500
|
+
* **The start-frame-number case, and why an option gets an error of its own.** A
|
|
4501
|
+
* frame's number is a single ASCII digit, `FN_ZERO + n` for `n` in `0`-`7`, and
|
|
4502
|
+
* nothing used to check that `options.startFrameNumber` was one. What went out
|
|
4503
|
+
* instead was whatever byte that sum truncated to: measured on this package's own
|
|
4504
|
+
* round trip, `-1` wrote `/` into the frame-number position and `NaN`, `Infinity`
|
|
4505
|
+
* and `-Infinity` each wrote a `NUL` byte into every frame of the stream, after
|
|
4506
|
+
* which the decoder read no frame number at all and emitted **none** of the
|
|
4507
|
+
* records. Out-of-domain values that happened to land back on a digit were worse
|
|
4508
|
+
* in a quieter way: `1.5` and `257` both produced the byte-for-byte stream a
|
|
4509
|
+
* `startFrameNumber` of `1` produces, so the option silently accepted a value it
|
|
4510
|
+
* documented as invalid. This one refusal is about the caller's own option rather
|
|
4511
|
+
* than about record content, so its message names the value received. Nothing
|
|
4512
|
+
* from the stream is quoted.
|
|
4513
|
+
*
|
|
4474
4514
|
* @example
|
|
4475
4515
|
* ```ts
|
|
4476
4516
|
* import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
|
|
@@ -4500,8 +4540,42 @@ declare class AstmFrameEncodeError extends Error {
|
|
|
4500
4540
|
/** Options for {@link composeAstmFrames}. */
|
|
4501
4541
|
interface ComposeFramesOptions {
|
|
4502
4542
|
/**
|
|
4503
|
-
* The frame number to start the sequence at
|
|
4504
|
-
*
|
|
4543
|
+
* The frame number to start the sequence at: a whole number from `0` to `7`.
|
|
4544
|
+
* Defaults to `1`, the number ASTM gives the first frame of a transfer and the
|
|
4545
|
+
* one {@link decodeAstmFrames} expects to read first.
|
|
4546
|
+
*
|
|
4547
|
+
* **Any other value writes a continuation rather than the start of a transfer,
|
|
4548
|
+
* and that is what the option is for**: composing one transfer across more than
|
|
4549
|
+
* one call. Concatenating `composeAstmFrames(head)` with
|
|
4550
|
+
* `composeAstmFrames(tail, { startFrameNumber: n })`, where `n` is the number
|
|
4551
|
+
* after the last frame `head` used, is byte-identical to composing the whole
|
|
4552
|
+
* list in a single call and decodes with an empty warnings array, across the
|
|
4553
|
+
* `7 → 0` rollover included.
|
|
4554
|
+
*
|
|
4555
|
+
* **Decoded on its own, a stream that starts anywhere but `1` opens on a
|
|
4556
|
+
* sequence gap.** The decoder never bridges a gap silently, so it warns
|
|
4557
|
+
* (`ASTM_FRAME_SEQUENCE_GAP`) and does not emit that first record.
|
|
4558
|
+
* **What {@link parseFramedAstm} does after that varies with the message
|
|
4559
|
+
* shape and no rule is offered for it here.** It may throw, under more than
|
|
4560
|
+
* one code, and it may return a message that is simply one record short. **The
|
|
4561
|
+
* one thing that does hold is that the record layer never reports the loss**:
|
|
4562
|
+
* `parseFramedAstm` hands the record parser only the frames the codec
|
|
4563
|
+
* vouched for, so `message.warnings` carries what the surviving records
|
|
4564
|
+
* warrant and nothing about the record that did not survive. **Read
|
|
4565
|
+
* `frameWarnings`.** That is the cost of the option rather than a defect in
|
|
4566
|
+
* the caller's records, and it is why `1` is the default.
|
|
4567
|
+
*
|
|
4568
|
+
* **Finding the number to continue from.** Nothing here returns it, and the
|
|
4569
|
+
* frame count is not it once a record splits: decode the part you just
|
|
4570
|
+
* composed and read the last frame's number, then add one modulo 8, as in
|
|
4571
|
+
* `((decodeAstmFrames(part).frames.at(-1)?.frameNumber ?? 0) + 1) % 8`. A frame
|
|
4572
|
+
* carries no number only when the stream ends immediately after its `STX`,
|
|
4573
|
+
* which is not something this encoder writes, so that fallback never fires on
|
|
4574
|
+
* a part composed here. Getting the number wrong costs the record at the join,
|
|
4575
|
+
* warned rather than silently.
|
|
4576
|
+
*
|
|
4577
|
+
* A value outside `0`-`7`, or one that is not a whole number, is refused with
|
|
4578
|
+
* `ASTM_FRAME_INVALID_START_FRAME_NUMBER`.
|
|
4505
4579
|
*/
|
|
4506
4580
|
readonly startFrameNumber?: number;
|
|
4507
4581
|
}
|
|
@@ -4539,6 +4613,17 @@ interface ComposeFramesOptions {
|
|
|
4539
4613
|
* absorbed into the previous one with an empty warnings array at both layers.
|
|
4540
4614
|
* Supplying the record as a `Uint8Array` does not route around this check.
|
|
4541
4615
|
*
|
|
4616
|
+
* **`options.startFrameNumber` is checked before *this function* reads a record**,
|
|
4617
|
+
* so on this entry point the refusal never depends on the caller's data.
|
|
4618
|
+
* ({@link serializeFramedAstm} serializes every record *before* it gets here, so
|
|
4619
|
+
* a record that cannot be serialized is refused first on that route.) It has to
|
|
4620
|
+
* be a whole number from `0` to `7`, because a frame's number is one ASCII digit;
|
|
4621
|
+
* anything else is `ASTM_FRAME_INVALID_START_FRAME_NUMBER` rather than whatever
|
|
4622
|
+
* byte the arithmetic truncated to. A value other than the default `1` writes a
|
|
4623
|
+
* **continuation** of a sequence already in progress, which is what the option is
|
|
4624
|
+
* for, and a continuation decoded on its own opens on a sequence gap: see
|
|
4625
|
+
* {@link ComposeFramesOptions}.
|
|
4626
|
+
*
|
|
4542
4627
|
* What is still **not** guaranteed: that an accepted record round-trips through
|
|
4543
4628
|
* the *record* layer. A delimiter colliding with a record's type letter, for one,
|
|
4544
4629
|
* frames and de-frames byte-exactly and still re-reads as a different record.
|
|
@@ -4547,10 +4632,12 @@ interface ComposeFramesOptions {
|
|
|
4547
4632
|
* `string`), one entry per complete record.
|
|
4548
4633
|
* @param options - Encode options.
|
|
4549
4634
|
* @returns The framed byte stream.
|
|
4550
|
-
* @throws {@link AstmFrameEncodeError} when
|
|
4551
|
-
*
|
|
4552
|
-
*
|
|
4553
|
-
*
|
|
4635
|
+
* @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
|
|
4636
|
+
* whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), the
|
|
4637
|
+
* list is empty, a record has no bytes (`ASTM_FRAME_EMPTY_RECORD`), a record
|
|
4638
|
+
* string holds a character above `U+00FF`
|
|
4639
|
+
* (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
|
|
4640
|
+
* `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
|
|
4554
4641
|
* @example
|
|
4555
4642
|
* ```ts
|
|
4556
4643
|
* import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
|
|
@@ -4625,7 +4712,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4625
4712
|
* delimiters, embedded delimiters re-escaped) and then framed **independently**
|
|
4626
4713
|
* (one record per `ETX`-closed frame run), so the framing exactly mirrors what
|
|
4627
4714
|
* {@link decodeAstmFrames} reassembles: `parseFramedAstm(serializeFramedAstm(msg))`
|
|
4628
|
-
* yields an equal message
|
|
4715
|
+
* yields an equal message **with the default `startFrameNumber`**. That clause is
|
|
4716
|
+
* load-bearing rather than pedantic: a non-default start writes a continuation of
|
|
4717
|
+
* a sequence already in progress, and a continuation read on its own opens on a
|
|
4718
|
+
* sequence gap, so the decoder does not emit its first record and this round trip
|
|
4719
|
+
* does not hold for it. See {@link ComposeFramesOptions}.
|
|
4629
4720
|
*
|
|
4630
4721
|
* A value carrying a character above `U+00FF` is **refused** here rather than
|
|
4631
4722
|
* framed: the record layer is happy to hold one, but a frame carries bytes and
|
|
@@ -4645,10 +4736,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4645
4736
|
* survive being written in the canonical set this function serializes with
|
|
4646
4737
|
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
|
|
4647
4738
|
* **different** delimiter set, since this function passes no set of its own.
|
|
4648
|
-
* @throws {@link AstmFrameEncodeError} when
|
|
4649
|
-
*
|
|
4650
|
-
* (`
|
|
4651
|
-
* `
|
|
4739
|
+
* @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
|
|
4740
|
+
* whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), there
|
|
4741
|
+
* are no records to frame (`ASTM_FRAME_EMPTY_RECORD`), a value holds a
|
|
4742
|
+
* character above `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record
|
|
4743
|
+
* holds an `STX`, `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
|
|
4652
4744
|
* @example
|
|
4653
4745
|
* ```ts
|
|
4654
4746
|
* import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
|
package/dist/index.d.ts
CHANGED
|
@@ -1103,7 +1103,7 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
|
|
|
1103
1103
|
* Any warning code from any of the three registries: the record layer
|
|
1104
1104
|
* (`ASTM_RECORD_*` / `ASTM_NONSTANDARD_DELIMITERS` / `ASTM_UNKNOWN_ESCAPE_SEQUENCE`),
|
|
1105
1105
|
* the frame codec (`ASTM_FRAME_*`), or the LTP protocol layer (`ASTM_LTP_*`). A
|
|
1106
|
-
* {@link
|
|
1106
|
+
* {@link AstmQuirkTolerance} may *name* any of them, but the safety gate refuses all
|
|
1107
1107
|
* but the small, benign, record-layer subset, so the union exists mainly to let
|
|
1108
1108
|
* the gate reject a frame/LTP code with a precise, typed message.
|
|
1109
1109
|
*/
|
|
@@ -1136,7 +1136,7 @@ interface AstmProfileProvenance {
|
|
|
1136
1136
|
readonly note?: string;
|
|
1137
1137
|
}
|
|
1138
1138
|
/**
|
|
1139
|
-
* Optional structural narrowing for
|
|
1139
|
+
* Optional structural narrowing for an {@link AstmQuirkTolerance}. When present, the
|
|
1140
1140
|
* tolerance applies only to warnings whose PHI-free {@link AstmPosition} matches
|
|
1141
1141
|
* every provided field, so a profile can expect a deviation on one record type
|
|
1142
1142
|
* (e.g. an unknown escape only inside `R` result values) without blanket-tolerating
|
|
@@ -1911,7 +1911,7 @@ interface AstmMessage {
|
|
|
1911
1911
|
* The vendor profile that was active for this parse, when one applied: its
|
|
1912
1912
|
* `name` and resolved `lineage` (attribution only; the profile object itself is
|
|
1913
1913
|
* not embedded). Absent when no profile applied. A profile that tolerated a
|
|
1914
|
-
* deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link warnings}, not
|
|
1914
|
+
* deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link AstmMessage.warnings}, not
|
|
1915
1915
|
* here.
|
|
1916
1916
|
*/
|
|
1917
1917
|
readonly profile?: {
|
|
@@ -2042,7 +2042,10 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2042
2042
|
* set (a non-canonical source is normalized to `H|\^&` by default). Normalizing sets the
|
|
2043
2043
|
* four delimiter **roles**; it does not delete a declaration's surplus characters, so a
|
|
2044
2044
|
* canonically-declared header that arrived carrying surplus keeps it on the default path
|
|
2045
|
-
* too
|
|
2045
|
+
* too, **except where that surplus could not be read back as surplus, when all of it is
|
|
2046
|
+
* dropped** (see `declarationResidual` below). Emit re-encodes what it read, so bytes
|
|
2047
|
+
* can differ without anything being lost: `O&BRIEN` in a canonical stream goes out as
|
|
2048
|
+
* `O&E&BRIEN`.
|
|
2046
2049
|
*
|
|
2047
2050
|
* **One delimiter set for the whole stream.** Every record (including `H`, `M` and `S`)
|
|
2048
2051
|
* is emitted against the delimiters being emitted with, and the header declares that same
|
|
@@ -2063,7 +2066,11 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2063
2066
|
* `H|\^&#` rather than losing the `#`. That holds on the **default** canonical path as
|
|
2064
2067
|
* well as when a set is passed explicitly: the surplus of a canonically-declared header
|
|
2065
2068
|
* survives normalization, because normalization is about the four roles and the surplus
|
|
2066
|
-
* has none.
|
|
2069
|
+
* has none. It stops holding where the surplus itself could not be read back, and then
|
|
2070
|
+
* **the whole surplus is dropped, not the offending character alone**, on the default
|
|
2071
|
+
* path as much as any other: `declarationResidual` states which surpluses those are and
|
|
2072
|
+
* why an all-or-nothing drop is the only one of the available answers that invents
|
|
2073
|
+
* nothing.
|
|
2067
2074
|
*
|
|
2068
2075
|
* **Never break framing.** A component leaf that contains a record terminator
|
|
2069
2076
|
* (`CR`/`LF`) cannot be escaped by the ASTM escape codec (only the four declared
|
|
@@ -2084,10 +2091,15 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2084
2091
|
* output, which is why they are refused at this one.
|
|
2085
2092
|
*
|
|
2086
2093
|
* **One position is not a modeled value and does not keep the byte:** the surplus
|
|
2087
|
-
* of a header's delimiter declaration, where
|
|
2094
|
+
* of a header's delimiter declaration, where a control character is dropped
|
|
2088
2095
|
* silently (see `declarationResidual` below, where that disposition is argued and
|
|
2089
2096
|
* where it long predates the frame-layer refusal). So "the record layer carries
|
|
2090
2097
|
* these bytes" is a statement about values, not about every byte of every line.
|
|
2098
|
+
* Measured, one character at a time in the surplus of an otherwise spec-clean
|
|
2099
|
+
* `H|\^&` header: **31 of the 33 C0/`DEL` characters reach that rule and are
|
|
2100
|
+
* dropped**, with `warnings: []` and no error. The two that do not are `CR` and
|
|
2101
|
+
* `LF`, which never reach it at all, because they end the record while it is being
|
|
2102
|
+
* read and so are never part of any surplus the model carries.
|
|
2091
2103
|
*
|
|
2092
2104
|
* **Check the delimiter set before writing.** Three conditions are required for the
|
|
2093
2105
|
* emitted bytes to read back as the records that produced them (each separator exactly
|
|
@@ -2166,10 +2178,12 @@ declare class AstmSerializeError extends Error {
|
|
|
2166
2178
|
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS" | "ASTM_EMIT_TYPE_LETTER_COLLISION";
|
|
2167
2179
|
/**
|
|
2168
2180
|
* Escape-encode one component leaf for spec-clean emit: the inverse of
|
|
2169
|
-
* `decodeEscapes`.
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2181
|
+
* `decodeEscapes`. Each character is read **once**, in one left-to-right pass, and
|
|
2182
|
+
* written either as itself or as a whole `escape` + mnemonic + `escape` triple
|
|
2183
|
+
* (`&` → `&E&`, the field / component / repeat delimiters → `&F&` / `&S&` / `&R&`).
|
|
2184
|
+
* Nothing this encoder writes is examined again, which is what makes it the exact
|
|
2185
|
+
* inverse of the decoder: the decoder reads the same output one token at a time,
|
|
2186
|
+
* so every triple it meets is a triple this encoder wrote.
|
|
2173
2187
|
*
|
|
2174
2188
|
* A `CR`/`LF` in the leaf has no escape mnemonic and would break framing, so it
|
|
2175
2189
|
* is rejected with an {@link AstmSerializeError} rather than emitted raw, as is a
|
|
@@ -2199,7 +2213,11 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
2199
2213
|
* The header (`H`) is special-cased: its delimiter-definition field is emitted as
|
|
2200
2214
|
* the **literal** declaration of `d`, never escaped, escaping it would corrupt the
|
|
2201
2215
|
* very declaration a reader depends on, followed by any characters the modeled
|
|
2202
|
-
* declaration carried beyond the three a reader takes its roles from.
|
|
2216
|
+
* declaration carried beyond the three a reader takes its roles from. Those surplus
|
|
2217
|
+
* characters are kept unless the surplus could not be read back as surplus, when
|
|
2218
|
+
* **all** of it is dropped: that covers a surplus carrying the field delimiter or
|
|
2219
|
+
* any control character, and it is silent, because emit has no warning channel.
|
|
2220
|
+
* Manufacturer
|
|
2203
2221
|
* (`M`) and scientific (`S`) records are reproduced **byte-identically** from their
|
|
2204
2222
|
* preserved `rawLine` when they are already in `d`, and re-encoded from their fields
|
|
2205
2223
|
* when they are not, so their fields never collapse into one on the next read.
|
|
@@ -2234,7 +2252,11 @@ declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string
|
|
|
2234
2252
|
* re-parsing it recovers every field. Passing `d` explicitly emits against that set
|
|
2235
2253
|
* instead, and the header declares it. Normalization replaces the four delimiter
|
|
2236
2254
|
* **roles**; a header declaration carrying characters beyond the three that hold a
|
|
2237
|
-
* role keeps them, on this path as on any other, rather than being truncated.
|
|
2255
|
+
* role keeps them, on this path as on any other, rather than being truncated. The
|
|
2256
|
+
* exception is a surplus that could not be read back as one, carrying the field
|
|
2257
|
+
* delimiter or any control character: there the **whole** surplus is dropped, on
|
|
2258
|
+
* every path including the default one, and silently, because emit returns a bare
|
|
2259
|
+
* string with no channel to say so.
|
|
2238
2260
|
*
|
|
2239
2261
|
* **`d` is checked before anything is written.** Each of the four separators must be
|
|
2240
2262
|
* exactly one character, none may be a record terminator, and no two may share a
|
|
@@ -4407,7 +4429,8 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4407
4429
|
* case of);
|
|
4408
4430
|
* - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
|
|
4409
4431
|
* continuous across every frame in the stream (as the decoder's sequence check
|
|
4410
|
-
* expects);
|
|
4432
|
+
* expects); a caller may start the sequence elsewhere to compose one transfer
|
|
4433
|
+
* across several calls, but only at a number a frame can actually carry;
|
|
4411
4434
|
* - the **240-byte text split**: a record longer than 240 bytes is split across
|
|
4412
4435
|
* frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
|
|
4413
4436
|
* control bytes are never counted toward the 240.
|
|
@@ -4432,6 +4455,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4432
4455
|
* - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
|
|
4433
4456
|
* structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
|
|
4434
4457
|
* a byte cannot be carried inside a frame at all.
|
|
4458
|
+
* - `ASTM_FRAME_INVALID_START_FRAME_NUMBER`: `options.startFrameNumber` is not a
|
|
4459
|
+
* whole number from `0` to `7`. A frame's number is one ASCII digit, so a
|
|
4460
|
+
* value outside that range has no digit to be written as.
|
|
4435
4461
|
*
|
|
4436
4462
|
* @example
|
|
4437
4463
|
* ```ts
|
|
@@ -4439,7 +4465,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4439
4465
|
* const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4440
4466
|
* ```
|
|
4441
4467
|
*/
|
|
4442
|
-
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
|
|
4468
|
+
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE" | "ASTM_FRAME_INVALID_START_FRAME_NUMBER";
|
|
4443
4469
|
/**
|
|
4444
4470
|
* Thrown by {@link composeAstmFrames} when the input cannot be framed into a
|
|
4445
4471
|
* spec-clean stream. Carries a stable code + positional context, never the
|
|
@@ -4471,6 +4497,20 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
|
|
|
4471
4497
|
* before framing: which byte belongs in a clinical value is the sender's call, not
|
|
4472
4498
|
* this library's, so it refuses rather than substituting or deleting one.
|
|
4473
4499
|
*
|
|
4500
|
+
* **The start-frame-number case, and why an option gets an error of its own.** A
|
|
4501
|
+
* frame's number is a single ASCII digit, `FN_ZERO + n` for `n` in `0`-`7`, and
|
|
4502
|
+
* nothing used to check that `options.startFrameNumber` was one. What went out
|
|
4503
|
+
* instead was whatever byte that sum truncated to: measured on this package's own
|
|
4504
|
+
* round trip, `-1` wrote `/` into the frame-number position and `NaN`, `Infinity`
|
|
4505
|
+
* and `-Infinity` each wrote a `NUL` byte into every frame of the stream, after
|
|
4506
|
+
* which the decoder read no frame number at all and emitted **none** of the
|
|
4507
|
+
* records. Out-of-domain values that happened to land back on a digit were worse
|
|
4508
|
+
* in a quieter way: `1.5` and `257` both produced the byte-for-byte stream a
|
|
4509
|
+
* `startFrameNumber` of `1` produces, so the option silently accepted a value it
|
|
4510
|
+
* documented as invalid. This one refusal is about the caller's own option rather
|
|
4511
|
+
* than about record content, so its message names the value received. Nothing
|
|
4512
|
+
* from the stream is quoted.
|
|
4513
|
+
*
|
|
4474
4514
|
* @example
|
|
4475
4515
|
* ```ts
|
|
4476
4516
|
* import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
|
|
@@ -4500,8 +4540,42 @@ declare class AstmFrameEncodeError extends Error {
|
|
|
4500
4540
|
/** Options for {@link composeAstmFrames}. */
|
|
4501
4541
|
interface ComposeFramesOptions {
|
|
4502
4542
|
/**
|
|
4503
|
-
* The frame number to start the sequence at
|
|
4504
|
-
*
|
|
4543
|
+
* The frame number to start the sequence at: a whole number from `0` to `7`.
|
|
4544
|
+
* Defaults to `1`, the number ASTM gives the first frame of a transfer and the
|
|
4545
|
+
* one {@link decodeAstmFrames} expects to read first.
|
|
4546
|
+
*
|
|
4547
|
+
* **Any other value writes a continuation rather than the start of a transfer,
|
|
4548
|
+
* and that is what the option is for**: composing one transfer across more than
|
|
4549
|
+
* one call. Concatenating `composeAstmFrames(head)` with
|
|
4550
|
+
* `composeAstmFrames(tail, { startFrameNumber: n })`, where `n` is the number
|
|
4551
|
+
* after the last frame `head` used, is byte-identical to composing the whole
|
|
4552
|
+
* list in a single call and decodes with an empty warnings array, across the
|
|
4553
|
+
* `7 → 0` rollover included.
|
|
4554
|
+
*
|
|
4555
|
+
* **Decoded on its own, a stream that starts anywhere but `1` opens on a
|
|
4556
|
+
* sequence gap.** The decoder never bridges a gap silently, so it warns
|
|
4557
|
+
* (`ASTM_FRAME_SEQUENCE_GAP`) and does not emit that first record.
|
|
4558
|
+
* **What {@link parseFramedAstm} does after that varies with the message
|
|
4559
|
+
* shape and no rule is offered for it here.** It may throw, under more than
|
|
4560
|
+
* one code, and it may return a message that is simply one record short. **The
|
|
4561
|
+
* one thing that does hold is that the record layer never reports the loss**:
|
|
4562
|
+
* `parseFramedAstm` hands the record parser only the frames the codec
|
|
4563
|
+
* vouched for, so `message.warnings` carries what the surviving records
|
|
4564
|
+
* warrant and nothing about the record that did not survive. **Read
|
|
4565
|
+
* `frameWarnings`.** That is the cost of the option rather than a defect in
|
|
4566
|
+
* the caller's records, and it is why `1` is the default.
|
|
4567
|
+
*
|
|
4568
|
+
* **Finding the number to continue from.** Nothing here returns it, and the
|
|
4569
|
+
* frame count is not it once a record splits: decode the part you just
|
|
4570
|
+
* composed and read the last frame's number, then add one modulo 8, as in
|
|
4571
|
+
* `((decodeAstmFrames(part).frames.at(-1)?.frameNumber ?? 0) + 1) % 8`. A frame
|
|
4572
|
+
* carries no number only when the stream ends immediately after its `STX`,
|
|
4573
|
+
* which is not something this encoder writes, so that fallback never fires on
|
|
4574
|
+
* a part composed here. Getting the number wrong costs the record at the join,
|
|
4575
|
+
* warned rather than silently.
|
|
4576
|
+
*
|
|
4577
|
+
* A value outside `0`-`7`, or one that is not a whole number, is refused with
|
|
4578
|
+
* `ASTM_FRAME_INVALID_START_FRAME_NUMBER`.
|
|
4505
4579
|
*/
|
|
4506
4580
|
readonly startFrameNumber?: number;
|
|
4507
4581
|
}
|
|
@@ -4539,6 +4613,17 @@ interface ComposeFramesOptions {
|
|
|
4539
4613
|
* absorbed into the previous one with an empty warnings array at both layers.
|
|
4540
4614
|
* Supplying the record as a `Uint8Array` does not route around this check.
|
|
4541
4615
|
*
|
|
4616
|
+
* **`options.startFrameNumber` is checked before *this function* reads a record**,
|
|
4617
|
+
* so on this entry point the refusal never depends on the caller's data.
|
|
4618
|
+
* ({@link serializeFramedAstm} serializes every record *before* it gets here, so
|
|
4619
|
+
* a record that cannot be serialized is refused first on that route.) It has to
|
|
4620
|
+
* be a whole number from `0` to `7`, because a frame's number is one ASCII digit;
|
|
4621
|
+
* anything else is `ASTM_FRAME_INVALID_START_FRAME_NUMBER` rather than whatever
|
|
4622
|
+
* byte the arithmetic truncated to. A value other than the default `1` writes a
|
|
4623
|
+
* **continuation** of a sequence already in progress, which is what the option is
|
|
4624
|
+
* for, and a continuation decoded on its own opens on a sequence gap: see
|
|
4625
|
+
* {@link ComposeFramesOptions}.
|
|
4626
|
+
*
|
|
4542
4627
|
* What is still **not** guaranteed: that an accepted record round-trips through
|
|
4543
4628
|
* the *record* layer. A delimiter colliding with a record's type letter, for one,
|
|
4544
4629
|
* frames and de-frames byte-exactly and still re-reads as a different record.
|
|
@@ -4547,10 +4632,12 @@ interface ComposeFramesOptions {
|
|
|
4547
4632
|
* `string`), one entry per complete record.
|
|
4548
4633
|
* @param options - Encode options.
|
|
4549
4634
|
* @returns The framed byte stream.
|
|
4550
|
-
* @throws {@link AstmFrameEncodeError} when
|
|
4551
|
-
*
|
|
4552
|
-
*
|
|
4553
|
-
*
|
|
4635
|
+
* @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
|
|
4636
|
+
* whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), the
|
|
4637
|
+
* list is empty, a record has no bytes (`ASTM_FRAME_EMPTY_RECORD`), a record
|
|
4638
|
+
* string holds a character above `U+00FF`
|
|
4639
|
+
* (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
|
|
4640
|
+
* `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
|
|
4554
4641
|
* @example
|
|
4555
4642
|
* ```ts
|
|
4556
4643
|
* import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
|
|
@@ -4625,7 +4712,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4625
4712
|
* delimiters, embedded delimiters re-escaped) and then framed **independently**
|
|
4626
4713
|
* (one record per `ETX`-closed frame run), so the framing exactly mirrors what
|
|
4627
4714
|
* {@link decodeAstmFrames} reassembles: `parseFramedAstm(serializeFramedAstm(msg))`
|
|
4628
|
-
* yields an equal message
|
|
4715
|
+
* yields an equal message **with the default `startFrameNumber`**. That clause is
|
|
4716
|
+
* load-bearing rather than pedantic: a non-default start writes a continuation of
|
|
4717
|
+
* a sequence already in progress, and a continuation read on its own opens on a
|
|
4718
|
+
* sequence gap, so the decoder does not emit its first record and this round trip
|
|
4719
|
+
* does not hold for it. See {@link ComposeFramesOptions}.
|
|
4629
4720
|
*
|
|
4630
4721
|
* A value carrying a character above `U+00FF` is **refused** here rather than
|
|
4631
4722
|
* framed: the record layer is happy to hold one, but a frame carries bytes and
|
|
@@ -4645,10 +4736,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4645
4736
|
* survive being written in the canonical set this function serializes with
|
|
4646
4737
|
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
|
|
4647
4738
|
* **different** delimiter set, since this function passes no set of its own.
|
|
4648
|
-
* @throws {@link AstmFrameEncodeError} when
|
|
4649
|
-
*
|
|
4650
|
-
* (`
|
|
4651
|
-
* `
|
|
4739
|
+
* @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
|
|
4740
|
+
* whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), there
|
|
4741
|
+
* are no records to frame (`ASTM_FRAME_EMPTY_RECORD`), a value holds a
|
|
4742
|
+
* character above `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record
|
|
4743
|
+
* holds an `STX`, `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
|
|
4652
4744
|
* @example
|
|
4653
4745
|
* ```ts
|
|
4654
4746
|
* import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
|
package/dist/index.mjs
CHANGED
|
@@ -1433,6 +1433,13 @@ function encodeComponent(leaf, d, recordIndex) {
|
|
|
1433
1433
|
assertEmittableDelimiters(d, recordIndex);
|
|
1434
1434
|
return encodeLeaf(leaf, d, recordIndex);
|
|
1435
1435
|
}
|
|
1436
|
+
function escapeMnemonic(ch, d) {
|
|
1437
|
+
if (ch === d.escape) return "E";
|
|
1438
|
+
if (ch === d.field) return "F";
|
|
1439
|
+
if (ch === d.component) return "S";
|
|
1440
|
+
if (ch === d.repeat) return "R";
|
|
1441
|
+
return void 0;
|
|
1442
|
+
}
|
|
1436
1443
|
function encodeLeaf(leaf, d, recordIndex) {
|
|
1437
1444
|
if (leaf.includes("\r") || leaf.includes("\n")) {
|
|
1438
1445
|
throw new AstmSerializeError(
|
|
@@ -1440,7 +1447,13 @@ function encodeLeaf(leaf, d, recordIndex) {
|
|
|
1440
1447
|
recordIndex
|
|
1441
1448
|
);
|
|
1442
1449
|
}
|
|
1443
|
-
|
|
1450
|
+
let out = "";
|
|
1451
|
+
for (let i = 0; i < leaf.length; i += 1) {
|
|
1452
|
+
const ch = leaf.charAt(i);
|
|
1453
|
+
const mnemonic = escapeMnemonic(ch, d);
|
|
1454
|
+
out += mnemonic === void 0 ? ch : d.escape + mnemonic + d.escape;
|
|
1455
|
+
}
|
|
1456
|
+
return out;
|
|
1444
1457
|
}
|
|
1445
1458
|
function encodeField(repeats, d, recordIndex) {
|
|
1446
1459
|
return repeats.map((rep) => rep.map((c) => encodeLeaf(c, d, recordIndex)).join(d.component)).join(d.repeat);
|
|
@@ -2288,6 +2301,15 @@ function assertFramable(bytes, recordIndex) {
|
|
|
2288
2301
|
}
|
|
2289
2302
|
}
|
|
2290
2303
|
}
|
|
2304
|
+
function assertStartFrameNumber(value) {
|
|
2305
|
+
if (!Number.isInteger(value) || value < 0 || value > FRAME_NUMBER_MODULUS - 1) {
|
|
2306
|
+
throw new AstmFrameEncodeError(
|
|
2307
|
+
`startFrameNumber must be a whole number from 0 to 7: a frame's number is one ASCII digit and the sequence is modulo 8. Received: ${String(value)}.`,
|
|
2308
|
+
void 0,
|
|
2309
|
+
"ASTM_FRAME_INVALID_START_FRAME_NUMBER"
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2291
2313
|
function encodeFrame(text, frameNumber, isFinal) {
|
|
2292
2314
|
const term = isFinal ? ETX : ETB;
|
|
2293
2315
|
const bytes = [STX, FN_ZERO + frameNumber, ...text, term];
|
|
@@ -2296,11 +2318,13 @@ function encodeFrame(text, frameNumber, isFinal) {
|
|
|
2296
2318
|
return [...bytes, hex.charCodeAt(0), hex.charCodeAt(1), CR, LF];
|
|
2297
2319
|
}
|
|
2298
2320
|
function composeAstmFrames(records, options = {}) {
|
|
2321
|
+
const start = options.startFrameNumber ?? FIRST_FRAME_NUMBER;
|
|
2322
|
+
assertStartFrameNumber(start);
|
|
2299
2323
|
if (records.length === 0) {
|
|
2300
2324
|
throw new AstmFrameEncodeError("Cannot frame an empty record list: nothing to transmit.");
|
|
2301
2325
|
}
|
|
2302
2326
|
const out = [];
|
|
2303
|
-
let frameNumber =
|
|
2327
|
+
let frameNumber = start;
|
|
2304
2328
|
records.forEach((record, recordIndex) => {
|
|
2305
2329
|
const bytes = toBytes(record, recordIndex);
|
|
2306
2330
|
if (bytes.length === 0) {
|
|
@@ -2345,7 +2369,7 @@ function concatBytes3(chunks) {
|
|
|
2345
2369
|
}
|
|
2346
2370
|
|
|
2347
2371
|
// src/index.ts
|
|
2348
|
-
var VERSION = "0.0.
|
|
2372
|
+
var VERSION = "0.0.15";
|
|
2349
2373
|
|
|
2350
2374
|
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, AstmAmbiguousStreamError, AstmFrameEncodeError, AstmFrameStrictError, AstmParseError, AstmProfileDefinitionError, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, FATAL_CODES, FRAME_WARNING_CODES, LIVD_WARNING_CODES, LTP_WARNING_CODES, SAFETY_CRITICAL_CODES, TOLERABLE_CODES, VERSION, WARNING_CODES, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
2351
2375
|
//# sourceMappingURL=index.mjs.map
|