@cosyte/astm 0.0.13 → 0.0.14

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/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 QuirkTolerance} may *name* any of them, but the safety gate refuses all
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 a {@link QuirkTolerance}. When present, the
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?: {
@@ -4407,7 +4407,8 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4407
4407
  * case of);
4408
4408
  * - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
4409
4409
  * continuous across every frame in the stream (as the decoder's sequence check
4410
- * expects);
4410
+ * expects); a caller may start the sequence elsewhere to compose one transfer
4411
+ * across several calls, but only at a number a frame can actually carry;
4411
4412
  * - the **240-byte text split**: a record longer than 240 bytes is split across
4412
4413
  * frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
4413
4414
  * control bytes are never counted toward the 240.
@@ -4432,6 +4433,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4432
4433
  * - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
4433
4434
  * structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
4434
4435
  * a byte cannot be carried inside a frame at all.
4436
+ * - `ASTM_FRAME_INVALID_START_FRAME_NUMBER`: `options.startFrameNumber` is not a
4437
+ * whole number from `0` to `7`. A frame's number is one ASCII digit, so a
4438
+ * value outside that range has no digit to be written as.
4435
4439
  *
4436
4440
  * @example
4437
4441
  * ```ts
@@ -4439,7 +4443,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4439
4443
  * const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
4440
4444
  * ```
4441
4445
  */
4442
- type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
4446
+ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE" | "ASTM_FRAME_INVALID_START_FRAME_NUMBER";
4443
4447
  /**
4444
4448
  * Thrown by {@link composeAstmFrames} when the input cannot be framed into a
4445
4449
  * spec-clean stream. Carries a stable code + positional context, never the
@@ -4471,6 +4475,20 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
4471
4475
  * before framing: which byte belongs in a clinical value is the sender's call, not
4472
4476
  * this library's, so it refuses rather than substituting or deleting one.
4473
4477
  *
4478
+ * **The start-frame-number case, and why an option gets an error of its own.** A
4479
+ * frame's number is a single ASCII digit, `FN_ZERO + n` for `n` in `0`-`7`, and
4480
+ * nothing used to check that `options.startFrameNumber` was one. What went out
4481
+ * instead was whatever byte that sum truncated to: measured on this package's own
4482
+ * round trip, `-1` wrote `/` into the frame-number position and `NaN`, `Infinity`
4483
+ * and `-Infinity` each wrote a `NUL` byte into every frame of the stream, after
4484
+ * which the decoder read no frame number at all and emitted **none** of the
4485
+ * records. Out-of-domain values that happened to land back on a digit were worse
4486
+ * in a quieter way: `1.5` and `257` both produced the byte-for-byte stream a
4487
+ * `startFrameNumber` of `1` produces, so the option silently accepted a value it
4488
+ * documented as invalid. This one refusal is about the caller's own option rather
4489
+ * than about record content, so its message names the value received. Nothing
4490
+ * from the stream is quoted.
4491
+ *
4474
4492
  * @example
4475
4493
  * ```ts
4476
4494
  * import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
@@ -4500,8 +4518,42 @@ declare class AstmFrameEncodeError extends Error {
4500
4518
  /** Options for {@link composeAstmFrames}. */
4501
4519
  interface ComposeFramesOptions {
4502
4520
  /**
4503
- * The frame number to start the sequence at (`0`–`7`). Defaults to
4504
- * {@link FIRST_FRAME_NUMBER} (`1`): the ASTM convention.
4521
+ * The frame number to start the sequence at: a whole number from `0` to `7`.
4522
+ * Defaults to `1`, the number ASTM gives the first frame of a transfer and the
4523
+ * one {@link decodeAstmFrames} expects to read first.
4524
+ *
4525
+ * **Any other value writes a continuation rather than the start of a transfer,
4526
+ * and that is what the option is for**: composing one transfer across more than
4527
+ * one call. Concatenating `composeAstmFrames(head)` with
4528
+ * `composeAstmFrames(tail, { startFrameNumber: n })`, where `n` is the number
4529
+ * after the last frame `head` used, is byte-identical to composing the whole
4530
+ * list in a single call and decodes with an empty warnings array, across the
4531
+ * `7 → 0` rollover included.
4532
+ *
4533
+ * **Decoded on its own, a stream that starts anywhere but `1` opens on a
4534
+ * sequence gap.** The decoder never bridges a gap silently, so it warns
4535
+ * (`ASTM_FRAME_SEQUENCE_GAP`) and does not emit that first record.
4536
+ * **What {@link parseFramedAstm} does after that varies with the message
4537
+ * shape and no rule is offered for it here.** It may throw, under more than
4538
+ * one code, and it may return a message that is simply one record short. **The
4539
+ * one thing that does hold is that the record layer never reports the loss**:
4540
+ * `parseFramedAstm` hands the record parser only the frames the codec
4541
+ * vouched for, so `message.warnings` carries what the surviving records
4542
+ * warrant and nothing about the record that did not survive. **Read
4543
+ * `frameWarnings`.** That is the cost of the option rather than a defect in
4544
+ * the caller's records, and it is why `1` is the default.
4545
+ *
4546
+ * **Finding the number to continue from.** Nothing here returns it, and the
4547
+ * frame count is not it once a record splits: decode the part you just
4548
+ * composed and read the last frame's number, then add one modulo 8, as in
4549
+ * `((decodeAstmFrames(part).frames.at(-1)?.frameNumber ?? 0) + 1) % 8`. A frame
4550
+ * carries no number only when the stream ends immediately after its `STX`,
4551
+ * which is not something this encoder writes, so that fallback never fires on
4552
+ * a part composed here. Getting the number wrong costs the record at the join,
4553
+ * warned rather than silently.
4554
+ *
4555
+ * A value outside `0`-`7`, or one that is not a whole number, is refused with
4556
+ * `ASTM_FRAME_INVALID_START_FRAME_NUMBER`.
4505
4557
  */
4506
4558
  readonly startFrameNumber?: number;
4507
4559
  }
@@ -4539,6 +4591,17 @@ interface ComposeFramesOptions {
4539
4591
  * absorbed into the previous one with an empty warnings array at both layers.
4540
4592
  * Supplying the record as a `Uint8Array` does not route around this check.
4541
4593
  *
4594
+ * **`options.startFrameNumber` is checked before *this function* reads a record**,
4595
+ * so on this entry point the refusal never depends on the caller's data.
4596
+ * ({@link serializeFramedAstm} serializes every record *before* it gets here, so
4597
+ * a record that cannot be serialized is refused first on that route.) It has to
4598
+ * be a whole number from `0` to `7`, because a frame's number is one ASCII digit;
4599
+ * anything else is `ASTM_FRAME_INVALID_START_FRAME_NUMBER` rather than whatever
4600
+ * byte the arithmetic truncated to. A value other than the default `1` writes a
4601
+ * **continuation** of a sequence already in progress, which is what the option is
4602
+ * for, and a continuation decoded on its own opens on a sequence gap: see
4603
+ * {@link ComposeFramesOptions}.
4604
+ *
4542
4605
  * What is still **not** guaranteed: that an accepted record round-trips through
4543
4606
  * the *record* layer. A delimiter colliding with a record's type letter, for one,
4544
4607
  * frames and de-frames byte-exactly and still re-reads as a different record.
@@ -4547,10 +4610,12 @@ interface ComposeFramesOptions {
4547
4610
  * `string`), one entry per complete record.
4548
4611
  * @param options - Encode options.
4549
4612
  * @returns The framed byte stream.
4550
- * @throws {@link AstmFrameEncodeError} when the list is empty, a record has no
4551
- * bytes (`ASTM_FRAME_EMPTY_RECORD`), a record string holds a character above
4552
- * `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`,
4553
- * `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4613
+ * @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
4614
+ * whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), the
4615
+ * list is empty, a record has no bytes (`ASTM_FRAME_EMPTY_RECORD`), a record
4616
+ * string holds a character above `U+00FF`
4617
+ * (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
4618
+ * `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4554
4619
  * @example
4555
4620
  * ```ts
4556
4621
  * import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
@@ -4625,7 +4690,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
4625
4690
  * delimiters, embedded delimiters re-escaped) and then framed **independently**
4626
4691
  * (one record per `ETX`-closed frame run), so the framing exactly mirrors what
4627
4692
  * {@link decodeAstmFrames} reassembles: `parseFramedAstm(serializeFramedAstm(msg))`
4628
- * yields an equal message.
4693
+ * yields an equal message **with the default `startFrameNumber`**. That clause is
4694
+ * load-bearing rather than pedantic: a non-default start writes a continuation of
4695
+ * a sequence already in progress, and a continuation read on its own opens on a
4696
+ * sequence gap, so the decoder does not emit its first record and this round trip
4697
+ * does not hold for it. See {@link ComposeFramesOptions}.
4629
4698
  *
4630
4699
  * A value carrying a character above `U+00FF` is **refused** here rather than
4631
4700
  * framed: the record layer is happy to hold one, but a frame carries bytes and
@@ -4645,10 +4714,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
4645
4714
  * survive being written in the canonical set this function serializes with
4646
4715
  * (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
4647
4716
  * **different** delimiter set, since this function passes no set of its own.
4648
- * @throws {@link AstmFrameEncodeError} when there are no records to frame
4649
- * (`ASTM_FRAME_EMPTY_RECORD`), a value holds a character above `U+00FF`
4650
- * (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
4651
- * `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4717
+ * @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
4718
+ * whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), there
4719
+ * are no records to frame (`ASTM_FRAME_EMPTY_RECORD`), a value holds a
4720
+ * character above `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record
4721
+ * holds an `STX`, `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4652
4722
  * @example
4653
4723
  * ```ts
4654
4724
  * 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 QuirkTolerance} may *name* any of them, but the safety gate refuses all
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 a {@link QuirkTolerance}. When present, the
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?: {
@@ -4407,7 +4407,8 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4407
4407
  * case of);
4408
4408
  * - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
4409
4409
  * continuous across every frame in the stream (as the decoder's sequence check
4410
- * expects);
4410
+ * expects); a caller may start the sequence elsewhere to compose one transfer
4411
+ * across several calls, but only at a number a frame can actually carry;
4411
4412
  * - the **240-byte text split**: a record longer than 240 bytes is split across
4412
4413
  * frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
4413
4414
  * control bytes are never counted toward the 240.
@@ -4432,6 +4433,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4432
4433
  * - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
4433
4434
  * structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
4434
4435
  * a byte cannot be carried inside a frame at all.
4436
+ * - `ASTM_FRAME_INVALID_START_FRAME_NUMBER`: `options.startFrameNumber` is not a
4437
+ * whole number from `0` to `7`. A frame's number is one ASCII digit, so a
4438
+ * value outside that range has no digit to be written as.
4435
4439
  *
4436
4440
  * @example
4437
4441
  * ```ts
@@ -4439,7 +4443,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
4439
4443
  * const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
4440
4444
  * ```
4441
4445
  */
4442
- type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
4446
+ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE" | "ASTM_FRAME_INVALID_START_FRAME_NUMBER";
4443
4447
  /**
4444
4448
  * Thrown by {@link composeAstmFrames} when the input cannot be framed into a
4445
4449
  * spec-clean stream. Carries a stable code + positional context, never the
@@ -4471,6 +4475,20 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
4471
4475
  * before framing: which byte belongs in a clinical value is the sender's call, not
4472
4476
  * this library's, so it refuses rather than substituting or deleting one.
4473
4477
  *
4478
+ * **The start-frame-number case, and why an option gets an error of its own.** A
4479
+ * frame's number is a single ASCII digit, `FN_ZERO + n` for `n` in `0`-`7`, and
4480
+ * nothing used to check that `options.startFrameNumber` was one. What went out
4481
+ * instead was whatever byte that sum truncated to: measured on this package's own
4482
+ * round trip, `-1` wrote `/` into the frame-number position and `NaN`, `Infinity`
4483
+ * and `-Infinity` each wrote a `NUL` byte into every frame of the stream, after
4484
+ * which the decoder read no frame number at all and emitted **none** of the
4485
+ * records. Out-of-domain values that happened to land back on a digit were worse
4486
+ * in a quieter way: `1.5` and `257` both produced the byte-for-byte stream a
4487
+ * `startFrameNumber` of `1` produces, so the option silently accepted a value it
4488
+ * documented as invalid. This one refusal is about the caller's own option rather
4489
+ * than about record content, so its message names the value received. Nothing
4490
+ * from the stream is quoted.
4491
+ *
4474
4492
  * @example
4475
4493
  * ```ts
4476
4494
  * import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
@@ -4500,8 +4518,42 @@ declare class AstmFrameEncodeError extends Error {
4500
4518
  /** Options for {@link composeAstmFrames}. */
4501
4519
  interface ComposeFramesOptions {
4502
4520
  /**
4503
- * The frame number to start the sequence at (`0`–`7`). Defaults to
4504
- * {@link FIRST_FRAME_NUMBER} (`1`): the ASTM convention.
4521
+ * The frame number to start the sequence at: a whole number from `0` to `7`.
4522
+ * Defaults to `1`, the number ASTM gives the first frame of a transfer and the
4523
+ * one {@link decodeAstmFrames} expects to read first.
4524
+ *
4525
+ * **Any other value writes a continuation rather than the start of a transfer,
4526
+ * and that is what the option is for**: composing one transfer across more than
4527
+ * one call. Concatenating `composeAstmFrames(head)` with
4528
+ * `composeAstmFrames(tail, { startFrameNumber: n })`, where `n` is the number
4529
+ * after the last frame `head` used, is byte-identical to composing the whole
4530
+ * list in a single call and decodes with an empty warnings array, across the
4531
+ * `7 → 0` rollover included.
4532
+ *
4533
+ * **Decoded on its own, a stream that starts anywhere but `1` opens on a
4534
+ * sequence gap.** The decoder never bridges a gap silently, so it warns
4535
+ * (`ASTM_FRAME_SEQUENCE_GAP`) and does not emit that first record.
4536
+ * **What {@link parseFramedAstm} does after that varies with the message
4537
+ * shape and no rule is offered for it here.** It may throw, under more than
4538
+ * one code, and it may return a message that is simply one record short. **The
4539
+ * one thing that does hold is that the record layer never reports the loss**:
4540
+ * `parseFramedAstm` hands the record parser only the frames the codec
4541
+ * vouched for, so `message.warnings` carries what the surviving records
4542
+ * warrant and nothing about the record that did not survive. **Read
4543
+ * `frameWarnings`.** That is the cost of the option rather than a defect in
4544
+ * the caller's records, and it is why `1` is the default.
4545
+ *
4546
+ * **Finding the number to continue from.** Nothing here returns it, and the
4547
+ * frame count is not it once a record splits: decode the part you just
4548
+ * composed and read the last frame's number, then add one modulo 8, as in
4549
+ * `((decodeAstmFrames(part).frames.at(-1)?.frameNumber ?? 0) + 1) % 8`. A frame
4550
+ * carries no number only when the stream ends immediately after its `STX`,
4551
+ * which is not something this encoder writes, so that fallback never fires on
4552
+ * a part composed here. Getting the number wrong costs the record at the join,
4553
+ * warned rather than silently.
4554
+ *
4555
+ * A value outside `0`-`7`, or one that is not a whole number, is refused with
4556
+ * `ASTM_FRAME_INVALID_START_FRAME_NUMBER`.
4505
4557
  */
4506
4558
  readonly startFrameNumber?: number;
4507
4559
  }
@@ -4539,6 +4591,17 @@ interface ComposeFramesOptions {
4539
4591
  * absorbed into the previous one with an empty warnings array at both layers.
4540
4592
  * Supplying the record as a `Uint8Array` does not route around this check.
4541
4593
  *
4594
+ * **`options.startFrameNumber` is checked before *this function* reads a record**,
4595
+ * so on this entry point the refusal never depends on the caller's data.
4596
+ * ({@link serializeFramedAstm} serializes every record *before* it gets here, so
4597
+ * a record that cannot be serialized is refused first on that route.) It has to
4598
+ * be a whole number from `0` to `7`, because a frame's number is one ASCII digit;
4599
+ * anything else is `ASTM_FRAME_INVALID_START_FRAME_NUMBER` rather than whatever
4600
+ * byte the arithmetic truncated to. A value other than the default `1` writes a
4601
+ * **continuation** of a sequence already in progress, which is what the option is
4602
+ * for, and a continuation decoded on its own opens on a sequence gap: see
4603
+ * {@link ComposeFramesOptions}.
4604
+ *
4542
4605
  * What is still **not** guaranteed: that an accepted record round-trips through
4543
4606
  * the *record* layer. A delimiter colliding with a record's type letter, for one,
4544
4607
  * frames and de-frames byte-exactly and still re-reads as a different record.
@@ -4547,10 +4610,12 @@ interface ComposeFramesOptions {
4547
4610
  * `string`), one entry per complete record.
4548
4611
  * @param options - Encode options.
4549
4612
  * @returns The framed byte stream.
4550
- * @throws {@link AstmFrameEncodeError} when the list is empty, a record has no
4551
- * bytes (`ASTM_FRAME_EMPTY_RECORD`), a record string holds a character above
4552
- * `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`,
4553
- * `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4613
+ * @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
4614
+ * whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), the
4615
+ * list is empty, a record has no bytes (`ASTM_FRAME_EMPTY_RECORD`), a record
4616
+ * string holds a character above `U+00FF`
4617
+ * (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
4618
+ * `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4554
4619
  * @example
4555
4620
  * ```ts
4556
4621
  * import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
@@ -4625,7 +4690,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
4625
4690
  * delimiters, embedded delimiters re-escaped) and then framed **independently**
4626
4691
  * (one record per `ETX`-closed frame run), so the framing exactly mirrors what
4627
4692
  * {@link decodeAstmFrames} reassembles: `parseFramedAstm(serializeFramedAstm(msg))`
4628
- * yields an equal message.
4693
+ * yields an equal message **with the default `startFrameNumber`**. That clause is
4694
+ * load-bearing rather than pedantic: a non-default start writes a continuation of
4695
+ * a sequence already in progress, and a continuation read on its own opens on a
4696
+ * sequence gap, so the decoder does not emit its first record and this round trip
4697
+ * does not hold for it. See {@link ComposeFramesOptions}.
4629
4698
  *
4630
4699
  * A value carrying a character above `U+00FF` is **refused** here rather than
4631
4700
  * framed: the record layer is happy to hold one, but a frame carries bytes and
@@ -4645,10 +4714,11 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
4645
4714
  * survive being written in the canonical set this function serializes with
4646
4715
  * (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
4647
4716
  * **different** delimiter set, since this function passes no set of its own.
4648
- * @throws {@link AstmFrameEncodeError} when there are no records to frame
4649
- * (`ASTM_FRAME_EMPTY_RECORD`), a value holds a character above `U+00FF`
4650
- * (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record holds an `STX`, `ETB` or
4651
- * `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4717
+ * @throws {@link AstmFrameEncodeError} when `options.startFrameNumber` is not a
4718
+ * whole number from `0` to `7` (`ASTM_FRAME_INVALID_START_FRAME_NUMBER`), there
4719
+ * are no records to frame (`ASTM_FRAME_EMPTY_RECORD`), a value holds a
4720
+ * character above `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`), or a record
4721
+ * holds an `STX`, `ETB` or `ETX` byte (`ASTM_FRAME_RESERVED_BYTE`).
4652
4722
  * @example
4653
4723
  * ```ts
4654
4724
  * import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
package/dist/index.mjs CHANGED
@@ -2288,6 +2288,15 @@ function assertFramable(bytes, recordIndex) {
2288
2288
  }
2289
2289
  }
2290
2290
  }
2291
+ function assertStartFrameNumber(value) {
2292
+ if (!Number.isInteger(value) || value < 0 || value > FRAME_NUMBER_MODULUS - 1) {
2293
+ throw new AstmFrameEncodeError(
2294
+ `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)}.`,
2295
+ void 0,
2296
+ "ASTM_FRAME_INVALID_START_FRAME_NUMBER"
2297
+ );
2298
+ }
2299
+ }
2291
2300
  function encodeFrame(text, frameNumber, isFinal) {
2292
2301
  const term = isFinal ? ETX : ETB;
2293
2302
  const bytes = [STX, FN_ZERO + frameNumber, ...text, term];
@@ -2296,11 +2305,13 @@ function encodeFrame(text, frameNumber, isFinal) {
2296
2305
  return [...bytes, hex.charCodeAt(0), hex.charCodeAt(1), CR, LF];
2297
2306
  }
2298
2307
  function composeAstmFrames(records, options = {}) {
2308
+ const start = options.startFrameNumber ?? FIRST_FRAME_NUMBER;
2309
+ assertStartFrameNumber(start);
2299
2310
  if (records.length === 0) {
2300
2311
  throw new AstmFrameEncodeError("Cannot frame an empty record list: nothing to transmit.");
2301
2312
  }
2302
2313
  const out = [];
2303
- let frameNumber = options.startFrameNumber ?? FIRST_FRAME_NUMBER;
2314
+ let frameNumber = start;
2304
2315
  records.forEach((record, recordIndex) => {
2305
2316
  const bytes = toBytes(record, recordIndex);
2306
2317
  if (bytes.length === 0) {
@@ -2345,7 +2356,7 @@ function concatBytes3(chunks) {
2345
2356
  }
2346
2357
 
2347
2358
  // src/index.ts
2348
- var VERSION = "0.0.13";
2359
+ var VERSION = "0.0.14";
2349
2360
 
2350
2361
  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
2362
  //# sourceMappingURL=index.mjs.map