@cosyte/astm 0.0.11 → 0.0.13
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 +127 -0
- package/README.md +13 -2
- package/dist/index.cjs +35 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -36
- package/dist/index.d.ts +127 -36
- package/dist/index.mjs +35 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2071,15 +2071,43 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2071
2071
|
* The serializer refuses: an embedded `CR`/`LF` is a typed {@link AstmSerializeError},
|
|
2072
2072
|
* never emitted raw.
|
|
2073
2073
|
*
|
|
2074
|
+
* **A value keeps the bytes the *frame* layer reserves, on purpose.** `STX`, `ETB`
|
|
2075
|
+
* and `ETX` are deliberately **not** refused here. They break a record only once it
|
|
2076
|
+
* is framed, and this layer returns a `string`, which is not yet on any wire: a
|
|
2077
|
+
* consumer on a raw transport, which is a real ASTM deployment this library models
|
|
2078
|
+
* (`detectFraming`), gets such a value back byte for byte through parse and emit,
|
|
2079
|
+
* measured on every modeled value. Refusing it here would take a
|
|
2080
|
+
* byte the caller genuinely supplied away from consumers who never frame anything.
|
|
2081
|
+
* `composeAstmFrames` is the total gate on the framed route, including through
|
|
2082
|
+
* `serializeFramedAstm`, and refuses it there (`ASTM_FRAME_RESERVED_BYTE`).
|
|
2083
|
+
* `CR`/`LF` are different: they end a *record*, so they corrupt this layer's own
|
|
2084
|
+
* output, which is why they are refused at this one.
|
|
2085
|
+
*
|
|
2086
|
+
* **One position is not a modeled value and does not keep the byte:** the surplus
|
|
2087
|
+
* of a header's delimiter declaration, where any control character is dropped
|
|
2088
|
+
* silently (see `declarationResidual` below, where that disposition is argued and
|
|
2089
|
+
* where it long predates the frame-layer refusal). So "the record layer carries
|
|
2090
|
+
* these bytes" is a statement about values, not about every byte of every line.
|
|
2091
|
+
*
|
|
2074
2092
|
* **Check the delimiter set before writing.** Three conditions are required for the
|
|
2075
2093
|
* emitted bytes to read back as the records that produced them (each separator exactly
|
|
2076
2094
|
* one character, no separator a `CR`/`LF`, no two separators the same character) and a
|
|
2077
2095
|
* set failing any of them is a typed {@link AstmSerializeError}
|
|
2078
2096
|
* (`ASTM_EMIT_INVALID_DELIMITERS`) rather than output this library's own parser would
|
|
2079
|
-
* reject or silently re-read with a different field tree. These are
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2097
|
+
* reject or silently re-read with a different field tree. These are conditions on the
|
|
2098
|
+
* **set alone**, so they are not a readback guarantee on their own: a set can pass all
|
|
2099
|
+
* three and still collide with a particular record's type letter, which is why that is
|
|
2100
|
+
* checked per record, on the bytes actually written
|
|
2101
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). That one is a **transcoding** condition, not
|
|
2102
|
+
* merely a caller's choice of set, so it is reachable on the default canonical path
|
|
2103
|
+
* too, when a record was read under a set other than the one being emitted.
|
|
2104
|
+
*
|
|
2105
|
+
* **What is still not guaranteed.** Together the two checks guarantee that every
|
|
2106
|
+
* record re-reads as its own *type*. They do not guarantee that every *field* of it
|
|
2107
|
+
* lands where it did: an escape sequence whose body is itself a delimiter is read as
|
|
2108
|
+
* one opaque atom, so that delimiter never becomes a boundary and the fields after it
|
|
2109
|
+
* shift. That is reported on the parse side (`ASTM_UNKNOWN_ESCAPE_SEQUENCE`) and is
|
|
2110
|
+
* not what these refusals cover.
|
|
2083
2111
|
*/
|
|
2084
2112
|
|
|
2085
2113
|
/**
|
|
@@ -2104,7 +2132,8 @@ declare class AstmSerializeError extends Error {
|
|
|
2104
2132
|
/**
|
|
2105
2133
|
* Stable discriminant. `ASTM_EMIT_UNENCODABLE_VALUE` for a `CR`/`LF` in a value;
|
|
2106
2134
|
* `ASTM_EMIT_INVALID_DELIMITERS` for a delimiter set that cannot be emitted
|
|
2107
|
-
* reversibly
|
|
2135
|
+
* reversibly; `ASTM_EMIT_TYPE_LETTER_COLLISION` for a record whose own type
|
|
2136
|
+
* letter the set being emitted with would escape away.
|
|
2108
2137
|
*/
|
|
2109
2138
|
readonly code: AstmSerializeErrorCode;
|
|
2110
2139
|
/** 0-based ordinal of the record within the message, when known. */
|
|
@@ -2119,8 +2148,14 @@ declare class AstmSerializeError extends Error {
|
|
|
2119
2148
|
* (`CR`/`LF`), which the escape codec has no mnemonic for.
|
|
2120
2149
|
* - `ASTM_EMIT_INVALID_DELIMITERS`: the delimiter set to emit against failed one
|
|
2121
2150
|
* of the three conditions readback requires (see {@link serializeAstmRecords}).
|
|
2122
|
-
* Those conditions are
|
|
2151
|
+
* Those conditions are checked against the *set alone*, so this code means a
|
|
2123
2152
|
* set was rejected, not that every unreversible set is.
|
|
2153
|
+
* - `ASTM_EMIT_TYPE_LETTER_COLLISION`: this record's type letter would not be the
|
|
2154
|
+
* first character of its own emitted line, so the record would read back as a
|
|
2155
|
+
* **different** record. Raised per record rather than per set, because whether
|
|
2156
|
+
* a set collides depends on which record is being written: a record read under
|
|
2157
|
+
* one delimiter set and written under another can hit it with **no** set passed
|
|
2158
|
+
* at all, so it is not avoided by emitting on the default canonical path.
|
|
2124
2159
|
*
|
|
2125
2160
|
* @example
|
|
2126
2161
|
* ```ts
|
|
@@ -2128,7 +2163,7 @@ declare class AstmSerializeError extends Error {
|
|
|
2128
2163
|
* const code: AstmSerializeErrorCode = "ASTM_EMIT_INVALID_DELIMITERS";
|
|
2129
2164
|
* ```
|
|
2130
2165
|
*/
|
|
2131
|
-
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS";
|
|
2166
|
+
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS" | "ASTM_EMIT_TYPE_LETTER_COLLISION";
|
|
2132
2167
|
/**
|
|
2133
2168
|
* Escape-encode one component leaf for spec-clean emit: the inverse of
|
|
2134
2169
|
* `decodeEscapes`. The **escape character itself is encoded first** (`&` → `&E&`)
|
|
@@ -2146,8 +2181,10 @@ type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID
|
|
|
2146
2181
|
* @returns The escaped component text.
|
|
2147
2182
|
* @throws {@link AstmSerializeError} for a `CR`/`LF` in the leaf
|
|
2148
2183
|
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or for a delimiter set failing one of the
|
|
2149
|
-
* three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2150
|
-
*
|
|
2184
|
+
* three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`). This
|
|
2185
|
+
* helper takes a leaf and no record, so it never raises
|
|
2186
|
+
* `ASTM_EMIT_TYPE_LETTER_COLLISION`: a caller assembling a record line out of it
|
|
2187
|
+
* is outside that check and gets no guarantee that a type letter survives.
|
|
2151
2188
|
* @example
|
|
2152
2189
|
* ```ts
|
|
2153
2190
|
* import { encodeComponent, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -2171,9 +2208,13 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
2171
2208
|
* @param d - The delimiters to emit against; defaults to `H|\^&`.
|
|
2172
2209
|
* @returns The record's wire text, terminator excluded.
|
|
2173
2210
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2174
|
-
* (`ASTM_EMIT_UNENCODABLE_VALUE`),
|
|
2175
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2176
|
-
*
|
|
2211
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), when `d` fails one of the three conditions
|
|
2212
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), or when this record's type
|
|
2213
|
+
* letter would not be the first character of its own emitted line
|
|
2214
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`), which would make it read back as a
|
|
2215
|
+
* different record. That last one is a transcoding condition and needs no `d`
|
|
2216
|
+
* argument to fire: a record read under a different delimiter set can carry a
|
|
2217
|
+
* type letter the canonical set escapes away.
|
|
2177
2218
|
* @example
|
|
2178
2219
|
* ```ts
|
|
2179
2220
|
* import { serializeAstmRecord, parseAstmRecords } from "@cosyte/astm";
|
|
@@ -2202,18 +2243,29 @@ declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string
|
|
|
2202
2243
|
* fails is an {@link AstmSerializeError} with code `ASTM_EMIT_INVALID_DELIMITERS`.
|
|
2203
2244
|
* This is stricter than the parser, which reads some sets it cannot reverse, so
|
|
2204
2245
|
* `serializeAstmRecords(msg, msg.delimiters)` can refuse a message that parsed:
|
|
2205
|
-
* in exactly the cases where it used to emit a stream that read back wrong.
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2246
|
+
* in exactly the cases where it used to emit a stream that read back wrong.
|
|
2247
|
+
*
|
|
2248
|
+
* **And each record is checked against the set it is being written with.** Those
|
|
2249
|
+
* three conditions read the set alone, so they cannot see that `field` = `R`
|
|
2250
|
+
* escapes an `R` record's own type letter away. Every record's emitted line is
|
|
2251
|
+
* therefore checked to start with the letter the record models, and one that does
|
|
2252
|
+
* not is `ASTM_EMIT_TYPE_LETTER_COLLISION` rather than a stream that reads back as
|
|
2253
|
+
* different records. What neither check promises is that every *field* lands where
|
|
2254
|
+
* it did: an escape sequence whose body is a delimiter is an opaque atom, so that
|
|
2255
|
+
* delimiter never becomes a boundary, and the parse side reports that
|
|
2256
|
+
* (`ASTM_UNKNOWN_ESCAPE_SEQUENCE`) rather than emit refusing it.
|
|
2209
2257
|
*
|
|
2210
2258
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
2211
2259
|
* @param d - The delimiters to emit against; defaults to the canonical `H|\^&` set.
|
|
2212
2260
|
* @returns The serialized record stream (`CR` after every record).
|
|
2213
2261
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2214
|
-
* (`ASTM_EMIT_UNENCODABLE_VALUE`),
|
|
2215
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2216
|
-
*
|
|
2262
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), when `d` fails one of the three conditions
|
|
2263
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), or when a record's type
|
|
2264
|
+
* letter would not be the first character of its own emitted line
|
|
2265
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The last of the three fires on the
|
|
2266
|
+
* **default** canonical path as well: it compares each record against the set
|
|
2267
|
+
* being emitted with, so a record read under a different set can carry a type
|
|
2268
|
+
* letter the canonical set escapes away. Omitting `d` is not a way around it.
|
|
2217
2269
|
* @example
|
|
2218
2270
|
* ```ts
|
|
2219
2271
|
* import { parseAstmRecords, serializeAstmRecords } from "@cosyte/astm";
|
|
@@ -2232,8 +2284,10 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
|
|
|
2232
2284
|
* @returns The escaped field text.
|
|
2233
2285
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2234
2286
|
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
|
|
2235
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2236
|
-
*
|
|
2287
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`). Like
|
|
2288
|
+
* {@link encodeComponent} this takes no record, so it never raises
|
|
2289
|
+
* `ASTM_EMIT_TYPE_LETTER_COLLISION`: encoding a record's type-letter field
|
|
2290
|
+
* through it will escape that letter away without objecting.
|
|
2237
2291
|
* @example
|
|
2238
2292
|
* ```ts
|
|
2239
2293
|
* import { serializeField, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -4375,6 +4429,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4375
4429
|
* empty frame.
|
|
4376
4430
|
* - `ASTM_FRAME_UNENCODABLE_CHARACTER`: a record given as a `string` holds a
|
|
4377
4431
|
* character above `U+00FF`, which has no single byte to stand for.
|
|
4432
|
+
* - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
|
|
4433
|
+
* structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
|
|
4434
|
+
* a byte cannot be carried inside a frame at all.
|
|
4378
4435
|
*
|
|
4379
4436
|
* @example
|
|
4380
4437
|
* ```ts
|
|
@@ -4382,7 +4439,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4382
4439
|
* const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4383
4440
|
* ```
|
|
4384
4441
|
*/
|
|
4385
|
-
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4442
|
+
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
|
|
4386
4443
|
/**
|
|
4387
4444
|
* Thrown by {@link composeAstmFrames} when the input cannot be framed into a
|
|
4388
4445
|
* spec-clean stream. Carries a stable code + positional context, never the
|
|
@@ -4404,6 +4461,16 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
|
|
|
4404
4461
|
* The refusal removes no capability, it routes you to the parameter that already
|
|
4405
4462
|
* carried it.
|
|
4406
4463
|
*
|
|
4464
|
+
* **The reserved-byte case, and why it has no escape hatch.** `STX`, `ETB` and
|
|
4465
|
+
* `ETX` are what {@link decodeAstmFrames} reads as frame structure: `STX` opens
|
|
4466
|
+
* a frame, and the first `ETB`/`ETX` after it *is* the end of that frame's text.
|
|
4467
|
+
* A record carrying one of those bytes therefore cannot be framed and read back,
|
|
4468
|
+
* whichever form it arrives in, and this layer has no escape sequence to hide one
|
|
4469
|
+
* behind. Passing bytes instead of a string does **not** route around it, because
|
|
4470
|
+
* the byte is the problem rather than the encoding. Take the byte out of the value
|
|
4471
|
+
* before framing: which byte belongs in a clinical value is the sender's call, not
|
|
4472
|
+
* this library's, so it refuses rather than substituting or deleting one.
|
|
4473
|
+
*
|
|
4407
4474
|
* @example
|
|
4408
4475
|
* ```ts
|
|
4409
4476
|
* import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
|
|
@@ -4422,7 +4489,9 @@ declare class AstmFrameEncodeError extends Error {
|
|
|
4422
4489
|
/**
|
|
4423
4490
|
* Position of the offending character within that record, when applicable:
|
|
4424
4491
|
* its index in the string, never the character itself and never its code
|
|
4425
|
-
* point. Enough to find it in the caller's own data.
|
|
4492
|
+
* point. Enough to find it in the caller's own data. For a record supplied as
|
|
4493
|
+
* a `Uint8Array` this is the offending byte's index, which is the same
|
|
4494
|
+
* position: a record string is a byte string, so the two indices coincide.
|
|
4426
4495
|
*/
|
|
4427
4496
|
readonly characterIndex?: number;
|
|
4428
4497
|
/** @internal */
|
|
@@ -4456,20 +4525,32 @@ interface ComposeFramesOptions {
|
|
|
4456
4525
|
* resulting `Uint8Array`. Those two forms are the whole of what `records` accepts.
|
|
4457
4526
|
* A caller reaching this from JavaScript with some **other** typed array is outside
|
|
4458
4527
|
* the signature and gets no such refusal: each element is still written as its low
|
|
4459
|
-
* byte, the same substitution described above.
|
|
4460
|
-
*
|
|
4461
|
-
*
|
|
4462
|
-
*
|
|
4463
|
-
*
|
|
4464
|
-
*
|
|
4528
|
+
* byte, the same substitution described above. The reserved-byte check below does
|
|
4529
|
+
* not reach that route either, because it compares elements against the three byte
|
|
4530
|
+
* values and an element like `0x0103` is not one of them, though its low byte
|
|
4531
|
+
* becomes an `ETX` on the wire. Pass a `Uint8Array` or a string.
|
|
4532
|
+
*
|
|
4533
|
+
* **A record carrying a frame-structure byte is refused too, in either form.**
|
|
4534
|
+
* `STX`, `ETB` and `ETX` are what the decoder reads as the shape of a frame, and
|
|
4535
|
+
* framing has no escape sequence to hide one behind, so a record holding one is
|
|
4536
|
+
* `ASTM_FRAME_RESERVED_BYTE` rather than a frame truncated at that byte. It used
|
|
4537
|
+
* to be written as given, and the result was not reliably loud: with the two
|
|
4538
|
+
* following bytes matching, the truncated frame verifies and a whole record is
|
|
4539
|
+
* absorbed into the previous one with an empty warnings array at both layers.
|
|
4540
|
+
* Supplying the record as a `Uint8Array` does not route around this check.
|
|
4541
|
+
*
|
|
4542
|
+
* What is still **not** guaranteed: that an accepted record round-trips through
|
|
4543
|
+
* the *record* layer. A delimiter colliding with a record's type letter, for one,
|
|
4544
|
+
* frames and de-frames byte-exactly and still re-reads as a different record.
|
|
4465
4545
|
*
|
|
4466
4546
|
* @param records - The reassembled record byte-strings (`Uint8Array` or latin1
|
|
4467
4547
|
* `string`), one entry per complete record.
|
|
4468
4548
|
* @param options - Encode options.
|
|
4469
4549
|
* @returns The framed byte stream.
|
|
4470
4550
|
* @throws {@link AstmFrameEncodeError} when the list is empty, a record has no
|
|
4471
|
-
* bytes (`ASTM_FRAME_EMPTY_RECORD`),
|
|
4472
|
-
* `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`)
|
|
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`).
|
|
4473
4554
|
* @example
|
|
4474
4555
|
* ```ts
|
|
4475
4556
|
* import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
|
|
@@ -4548,16 +4629,26 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4548
4629
|
*
|
|
4549
4630
|
* A value carrying a character above `U+00FF` is **refused** here rather than
|
|
4550
4631
|
* framed: the record layer is happy to hold one, but a frame carries bytes and
|
|
4551
|
-
* nothing in the message says which character encoding to turn it into.
|
|
4552
|
-
*
|
|
4632
|
+
* nothing in the message says which character encoding to turn it into.
|
|
4633
|
+
*
|
|
4634
|
+
* A value carrying a raw `STX`, `ETB` or `ETX` is refused too, and being Latin-1
|
|
4635
|
+
* is no exemption: those three are what {@link decodeAstmFrames} reads as the
|
|
4636
|
+
* shape of a frame, and framing has no escape sequence to hide one behind. The
|
|
4637
|
+
* record layer is happy to hold those as well, so this is a message that
|
|
4638
|
+
* serializes to records perfectly well and cannot be framed.
|
|
4553
4639
|
*
|
|
4554
4640
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
4555
4641
|
* @param options - Frame-encode options.
|
|
4556
4642
|
* @returns The framed byte stream.
|
|
4557
|
-
* @throws {@link AstmSerializeError} when a value contains an unencodable `CR`/`LF
|
|
4643
|
+
* @throws {@link AstmSerializeError} when a value contains an unencodable `CR`/`LF`
|
|
4644
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when a record's own type letter would not
|
|
4645
|
+
* survive being written in the canonical set this function serializes with
|
|
4646
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
|
|
4647
|
+
* **different** delimiter set, since this function passes no set of its own.
|
|
4558
4648
|
* @throws {@link AstmFrameEncodeError} when there are no records to frame
|
|
4559
|
-
* (`ASTM_FRAME_EMPTY_RECORD`),
|
|
4560
|
-
* (`ASTM_FRAME_UNENCODABLE_CHARACTER`)
|
|
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`).
|
|
4561
4652
|
* @example
|
|
4562
4653
|
* ```ts
|
|
4563
4654
|
* import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
|
package/dist/index.d.ts
CHANGED
|
@@ -2071,15 +2071,43 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
2071
2071
|
* The serializer refuses: an embedded `CR`/`LF` is a typed {@link AstmSerializeError},
|
|
2072
2072
|
* never emitted raw.
|
|
2073
2073
|
*
|
|
2074
|
+
* **A value keeps the bytes the *frame* layer reserves, on purpose.** `STX`, `ETB`
|
|
2075
|
+
* and `ETX` are deliberately **not** refused here. They break a record only once it
|
|
2076
|
+
* is framed, and this layer returns a `string`, which is not yet on any wire: a
|
|
2077
|
+
* consumer on a raw transport, which is a real ASTM deployment this library models
|
|
2078
|
+
* (`detectFraming`), gets such a value back byte for byte through parse and emit,
|
|
2079
|
+
* measured on every modeled value. Refusing it here would take a
|
|
2080
|
+
* byte the caller genuinely supplied away from consumers who never frame anything.
|
|
2081
|
+
* `composeAstmFrames` is the total gate on the framed route, including through
|
|
2082
|
+
* `serializeFramedAstm`, and refuses it there (`ASTM_FRAME_RESERVED_BYTE`).
|
|
2083
|
+
* `CR`/`LF` are different: they end a *record*, so they corrupt this layer's own
|
|
2084
|
+
* output, which is why they are refused at this one.
|
|
2085
|
+
*
|
|
2086
|
+
* **One position is not a modeled value and does not keep the byte:** the surplus
|
|
2087
|
+
* of a header's delimiter declaration, where any control character is dropped
|
|
2088
|
+
* silently (see `declarationResidual` below, where that disposition is argued and
|
|
2089
|
+
* where it long predates the frame-layer refusal). So "the record layer carries
|
|
2090
|
+
* these bytes" is a statement about values, not about every byte of every line.
|
|
2091
|
+
*
|
|
2074
2092
|
* **Check the delimiter set before writing.** Three conditions are required for the
|
|
2075
2093
|
* emitted bytes to read back as the records that produced them (each separator exactly
|
|
2076
2094
|
* one character, no separator a `CR`/`LF`, no two separators the same character) and a
|
|
2077
2095
|
* set failing any of them is a typed {@link AstmSerializeError}
|
|
2078
2096
|
* (`ASTM_EMIT_INVALID_DELIMITERS`) rather than output this library's own parser would
|
|
2079
|
-
* reject or silently re-read with a different field tree. These are
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2097
|
+
* reject or silently re-read with a different field tree. These are conditions on the
|
|
2098
|
+
* **set alone**, so they are not a readback guarantee on their own: a set can pass all
|
|
2099
|
+
* three and still collide with a particular record's type letter, which is why that is
|
|
2100
|
+
* checked per record, on the bytes actually written
|
|
2101
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). That one is a **transcoding** condition, not
|
|
2102
|
+
* merely a caller's choice of set, so it is reachable on the default canonical path
|
|
2103
|
+
* too, when a record was read under a set other than the one being emitted.
|
|
2104
|
+
*
|
|
2105
|
+
* **What is still not guaranteed.** Together the two checks guarantee that every
|
|
2106
|
+
* record re-reads as its own *type*. They do not guarantee that every *field* of it
|
|
2107
|
+
* lands where it did: an escape sequence whose body is itself a delimiter is read as
|
|
2108
|
+
* one opaque atom, so that delimiter never becomes a boundary and the fields after it
|
|
2109
|
+
* shift. That is reported on the parse side (`ASTM_UNKNOWN_ESCAPE_SEQUENCE`) and is
|
|
2110
|
+
* not what these refusals cover.
|
|
2083
2111
|
*/
|
|
2084
2112
|
|
|
2085
2113
|
/**
|
|
@@ -2104,7 +2132,8 @@ declare class AstmSerializeError extends Error {
|
|
|
2104
2132
|
/**
|
|
2105
2133
|
* Stable discriminant. `ASTM_EMIT_UNENCODABLE_VALUE` for a `CR`/`LF` in a value;
|
|
2106
2134
|
* `ASTM_EMIT_INVALID_DELIMITERS` for a delimiter set that cannot be emitted
|
|
2107
|
-
* reversibly
|
|
2135
|
+
* reversibly; `ASTM_EMIT_TYPE_LETTER_COLLISION` for a record whose own type
|
|
2136
|
+
* letter the set being emitted with would escape away.
|
|
2108
2137
|
*/
|
|
2109
2138
|
readonly code: AstmSerializeErrorCode;
|
|
2110
2139
|
/** 0-based ordinal of the record within the message, when known. */
|
|
@@ -2119,8 +2148,14 @@ declare class AstmSerializeError extends Error {
|
|
|
2119
2148
|
* (`CR`/`LF`), which the escape codec has no mnemonic for.
|
|
2120
2149
|
* - `ASTM_EMIT_INVALID_DELIMITERS`: the delimiter set to emit against failed one
|
|
2121
2150
|
* of the three conditions readback requires (see {@link serializeAstmRecords}).
|
|
2122
|
-
* Those conditions are
|
|
2151
|
+
* Those conditions are checked against the *set alone*, so this code means a
|
|
2123
2152
|
* set was rejected, not that every unreversible set is.
|
|
2153
|
+
* - `ASTM_EMIT_TYPE_LETTER_COLLISION`: this record's type letter would not be the
|
|
2154
|
+
* first character of its own emitted line, so the record would read back as a
|
|
2155
|
+
* **different** record. Raised per record rather than per set, because whether
|
|
2156
|
+
* a set collides depends on which record is being written: a record read under
|
|
2157
|
+
* one delimiter set and written under another can hit it with **no** set passed
|
|
2158
|
+
* at all, so it is not avoided by emitting on the default canonical path.
|
|
2124
2159
|
*
|
|
2125
2160
|
* @example
|
|
2126
2161
|
* ```ts
|
|
@@ -2128,7 +2163,7 @@ declare class AstmSerializeError extends Error {
|
|
|
2128
2163
|
* const code: AstmSerializeErrorCode = "ASTM_EMIT_INVALID_DELIMITERS";
|
|
2129
2164
|
* ```
|
|
2130
2165
|
*/
|
|
2131
|
-
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS";
|
|
2166
|
+
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS" | "ASTM_EMIT_TYPE_LETTER_COLLISION";
|
|
2132
2167
|
/**
|
|
2133
2168
|
* Escape-encode one component leaf for spec-clean emit: the inverse of
|
|
2134
2169
|
* `decodeEscapes`. The **escape character itself is encoded first** (`&` → `&E&`)
|
|
@@ -2146,8 +2181,10 @@ type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID
|
|
|
2146
2181
|
* @returns The escaped component text.
|
|
2147
2182
|
* @throws {@link AstmSerializeError} for a `CR`/`LF` in the leaf
|
|
2148
2183
|
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or for a delimiter set failing one of the
|
|
2149
|
-
* three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2150
|
-
*
|
|
2184
|
+
* three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`). This
|
|
2185
|
+
* helper takes a leaf and no record, so it never raises
|
|
2186
|
+
* `ASTM_EMIT_TYPE_LETTER_COLLISION`: a caller assembling a record line out of it
|
|
2187
|
+
* is outside that check and gets no guarantee that a type letter survives.
|
|
2151
2188
|
* @example
|
|
2152
2189
|
* ```ts
|
|
2153
2190
|
* import { encodeComponent, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -2171,9 +2208,13 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
2171
2208
|
* @param d - The delimiters to emit against; defaults to `H|\^&`.
|
|
2172
2209
|
* @returns The record's wire text, terminator excluded.
|
|
2173
2210
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2174
|
-
* (`ASTM_EMIT_UNENCODABLE_VALUE`),
|
|
2175
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2176
|
-
*
|
|
2211
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), when `d` fails one of the three conditions
|
|
2212
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), or when this record's type
|
|
2213
|
+
* letter would not be the first character of its own emitted line
|
|
2214
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`), which would make it read back as a
|
|
2215
|
+
* different record. That last one is a transcoding condition and needs no `d`
|
|
2216
|
+
* argument to fire: a record read under a different delimiter set can carry a
|
|
2217
|
+
* type letter the canonical set escapes away.
|
|
2177
2218
|
* @example
|
|
2178
2219
|
* ```ts
|
|
2179
2220
|
* import { serializeAstmRecord, parseAstmRecords } from "@cosyte/astm";
|
|
@@ -2202,18 +2243,29 @@ declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string
|
|
|
2202
2243
|
* fails is an {@link AstmSerializeError} with code `ASTM_EMIT_INVALID_DELIMITERS`.
|
|
2203
2244
|
* This is stricter than the parser, which reads some sets it cannot reverse, so
|
|
2204
2245
|
* `serializeAstmRecords(msg, msg.delimiters)` can refuse a message that parsed:
|
|
2205
|
-
* in exactly the cases where it used to emit a stream that read back wrong.
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2246
|
+
* in exactly the cases where it used to emit a stream that read back wrong.
|
|
2247
|
+
*
|
|
2248
|
+
* **And each record is checked against the set it is being written with.** Those
|
|
2249
|
+
* three conditions read the set alone, so they cannot see that `field` = `R`
|
|
2250
|
+
* escapes an `R` record's own type letter away. Every record's emitted line is
|
|
2251
|
+
* therefore checked to start with the letter the record models, and one that does
|
|
2252
|
+
* not is `ASTM_EMIT_TYPE_LETTER_COLLISION` rather than a stream that reads back as
|
|
2253
|
+
* different records. What neither check promises is that every *field* lands where
|
|
2254
|
+
* it did: an escape sequence whose body is a delimiter is an opaque atom, so that
|
|
2255
|
+
* delimiter never becomes a boundary, and the parse side reports that
|
|
2256
|
+
* (`ASTM_UNKNOWN_ESCAPE_SEQUENCE`) rather than emit refusing it.
|
|
2209
2257
|
*
|
|
2210
2258
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
2211
2259
|
* @param d - The delimiters to emit against; defaults to the canonical `H|\^&` set.
|
|
2212
2260
|
* @returns The serialized record stream (`CR` after every record).
|
|
2213
2261
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2214
|
-
* (`ASTM_EMIT_UNENCODABLE_VALUE`),
|
|
2215
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2216
|
-
*
|
|
2262
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), when `d` fails one of the three conditions
|
|
2263
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), or when a record's type
|
|
2264
|
+
* letter would not be the first character of its own emitted line
|
|
2265
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The last of the three fires on the
|
|
2266
|
+
* **default** canonical path as well: it compares each record against the set
|
|
2267
|
+
* being emitted with, so a record read under a different set can carry a type
|
|
2268
|
+
* letter the canonical set escapes away. Omitting `d` is not a way around it.
|
|
2217
2269
|
* @example
|
|
2218
2270
|
* ```ts
|
|
2219
2271
|
* import { parseAstmRecords, serializeAstmRecords } from "@cosyte/astm";
|
|
@@ -2232,8 +2284,10 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
|
|
|
2232
2284
|
* @returns The escaped field text.
|
|
2233
2285
|
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2234
2286
|
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
|
|
2235
|
-
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`)
|
|
2236
|
-
*
|
|
2287
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`). Like
|
|
2288
|
+
* {@link encodeComponent} this takes no record, so it never raises
|
|
2289
|
+
* `ASTM_EMIT_TYPE_LETTER_COLLISION`: encoding a record's type-letter field
|
|
2290
|
+
* through it will escape that letter away without objecting.
|
|
2237
2291
|
* @example
|
|
2238
2292
|
* ```ts
|
|
2239
2293
|
* import { serializeField, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -4375,6 +4429,9 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4375
4429
|
* empty frame.
|
|
4376
4430
|
* - `ASTM_FRAME_UNENCODABLE_CHARACTER`: a record given as a `string` holds a
|
|
4377
4431
|
* character above `U+00FF`, which has no single byte to stand for.
|
|
4432
|
+
* - `ASTM_FRAME_RESERVED_BYTE`: a record holds a byte this layer reads as frame
|
|
4433
|
+
* structure (`STX`, `ETB` or `ETX`). Framing has no escape mechanism, so such
|
|
4434
|
+
* a byte cannot be carried inside a frame at all.
|
|
4378
4435
|
*
|
|
4379
4436
|
* @example
|
|
4380
4437
|
* ```ts
|
|
@@ -4382,7 +4439,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
4382
4439
|
* const code: AstmFrameEncodeErrorCode = "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4383
4440
|
* ```
|
|
4384
4441
|
*/
|
|
4385
|
-
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER";
|
|
4442
|
+
type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODABLE_CHARACTER" | "ASTM_FRAME_RESERVED_BYTE";
|
|
4386
4443
|
/**
|
|
4387
4444
|
* Thrown by {@link composeAstmFrames} when the input cannot be framed into a
|
|
4388
4445
|
* spec-clean stream. Carries a stable code + positional context, never the
|
|
@@ -4404,6 +4461,16 @@ type AstmFrameEncodeErrorCode = "ASTM_FRAME_EMPTY_RECORD" | "ASTM_FRAME_UNENCODA
|
|
|
4404
4461
|
* The refusal removes no capability, it routes you to the parameter that already
|
|
4405
4462
|
* carried it.
|
|
4406
4463
|
*
|
|
4464
|
+
* **The reserved-byte case, and why it has no escape hatch.** `STX`, `ETB` and
|
|
4465
|
+
* `ETX` are what {@link decodeAstmFrames} reads as frame structure: `STX` opens
|
|
4466
|
+
* a frame, and the first `ETB`/`ETX` after it *is* the end of that frame's text.
|
|
4467
|
+
* A record carrying one of those bytes therefore cannot be framed and read back,
|
|
4468
|
+
* whichever form it arrives in, and this layer has no escape sequence to hide one
|
|
4469
|
+
* behind. Passing bytes instead of a string does **not** route around it, because
|
|
4470
|
+
* the byte is the problem rather than the encoding. Take the byte out of the value
|
|
4471
|
+
* before framing: which byte belongs in a clinical value is the sender's call, not
|
|
4472
|
+
* this library's, so it refuses rather than substituting or deleting one.
|
|
4473
|
+
*
|
|
4407
4474
|
* @example
|
|
4408
4475
|
* ```ts
|
|
4409
4476
|
* import { composeAstmFrames, AstmFrameEncodeError } from "@cosyte/astm";
|
|
@@ -4422,7 +4489,9 @@ declare class AstmFrameEncodeError extends Error {
|
|
|
4422
4489
|
/**
|
|
4423
4490
|
* Position of the offending character within that record, when applicable:
|
|
4424
4491
|
* its index in the string, never the character itself and never its code
|
|
4425
|
-
* point. Enough to find it in the caller's own data.
|
|
4492
|
+
* point. Enough to find it in the caller's own data. For a record supplied as
|
|
4493
|
+
* a `Uint8Array` this is the offending byte's index, which is the same
|
|
4494
|
+
* position: a record string is a byte string, so the two indices coincide.
|
|
4426
4495
|
*/
|
|
4427
4496
|
readonly characterIndex?: number;
|
|
4428
4497
|
/** @internal */
|
|
@@ -4456,20 +4525,32 @@ interface ComposeFramesOptions {
|
|
|
4456
4525
|
* resulting `Uint8Array`. Those two forms are the whole of what `records` accepts.
|
|
4457
4526
|
* A caller reaching this from JavaScript with some **other** typed array is outside
|
|
4458
4527
|
* the signature and gets no such refusal: each element is still written as its low
|
|
4459
|
-
* byte, the same substitution described above.
|
|
4460
|
-
*
|
|
4461
|
-
*
|
|
4462
|
-
*
|
|
4463
|
-
*
|
|
4464
|
-
*
|
|
4528
|
+
* byte, the same substitution described above. The reserved-byte check below does
|
|
4529
|
+
* not reach that route either, because it compares elements against the three byte
|
|
4530
|
+
* values and an element like `0x0103` is not one of them, though its low byte
|
|
4531
|
+
* becomes an `ETX` on the wire. Pass a `Uint8Array` or a string.
|
|
4532
|
+
*
|
|
4533
|
+
* **A record carrying a frame-structure byte is refused too, in either form.**
|
|
4534
|
+
* `STX`, `ETB` and `ETX` are what the decoder reads as the shape of a frame, and
|
|
4535
|
+
* framing has no escape sequence to hide one behind, so a record holding one is
|
|
4536
|
+
* `ASTM_FRAME_RESERVED_BYTE` rather than a frame truncated at that byte. It used
|
|
4537
|
+
* to be written as given, and the result was not reliably loud: with the two
|
|
4538
|
+
* following bytes matching, the truncated frame verifies and a whole record is
|
|
4539
|
+
* absorbed into the previous one with an empty warnings array at both layers.
|
|
4540
|
+
* Supplying the record as a `Uint8Array` does not route around this check.
|
|
4541
|
+
*
|
|
4542
|
+
* What is still **not** guaranteed: that an accepted record round-trips through
|
|
4543
|
+
* the *record* layer. A delimiter colliding with a record's type letter, for one,
|
|
4544
|
+
* frames and de-frames byte-exactly and still re-reads as a different record.
|
|
4465
4545
|
*
|
|
4466
4546
|
* @param records - The reassembled record byte-strings (`Uint8Array` or latin1
|
|
4467
4547
|
* `string`), one entry per complete record.
|
|
4468
4548
|
* @param options - Encode options.
|
|
4469
4549
|
* @returns The framed byte stream.
|
|
4470
4550
|
* @throws {@link AstmFrameEncodeError} when the list is empty, a record has no
|
|
4471
|
-
* bytes (`ASTM_FRAME_EMPTY_RECORD`),
|
|
4472
|
-
* `U+00FF` (`ASTM_FRAME_UNENCODABLE_CHARACTER`)
|
|
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`).
|
|
4473
4554
|
* @example
|
|
4474
4555
|
* ```ts
|
|
4475
4556
|
* import { composeAstmFrames, decodeAstmFrames } from "@cosyte/astm";
|
|
@@ -4548,16 +4629,26 @@ declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & Ast
|
|
|
4548
4629
|
*
|
|
4549
4630
|
* A value carrying a character above `U+00FF` is **refused** here rather than
|
|
4550
4631
|
* framed: the record layer is happy to hold one, but a frame carries bytes and
|
|
4551
|
-
* nothing in the message says which character encoding to turn it into.
|
|
4552
|
-
*
|
|
4632
|
+
* nothing in the message says which character encoding to turn it into.
|
|
4633
|
+
*
|
|
4634
|
+
* A value carrying a raw `STX`, `ETB` or `ETX` is refused too, and being Latin-1
|
|
4635
|
+
* is no exemption: those three are what {@link decodeAstmFrames} reads as the
|
|
4636
|
+
* shape of a frame, and framing has no escape sequence to hide one behind. The
|
|
4637
|
+
* record layer is happy to hold those as well, so this is a message that
|
|
4638
|
+
* serializes to records perfectly well and cannot be framed.
|
|
4553
4639
|
*
|
|
4554
4640
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
4555
4641
|
* @param options - Frame-encode options.
|
|
4556
4642
|
* @returns The framed byte stream.
|
|
4557
|
-
* @throws {@link AstmSerializeError} when a value contains an unencodable `CR`/`LF
|
|
4643
|
+
* @throws {@link AstmSerializeError} when a value contains an unencodable `CR`/`LF`
|
|
4644
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when a record's own type letter would not
|
|
4645
|
+
* survive being written in the canonical set this function serializes with
|
|
4646
|
+
* (`ASTM_EMIT_TYPE_LETTER_COLLISION`). The second reaches messages parsed under a
|
|
4647
|
+
* **different** delimiter set, since this function passes no set of its own.
|
|
4558
4648
|
* @throws {@link AstmFrameEncodeError} when there are no records to frame
|
|
4559
|
-
* (`ASTM_FRAME_EMPTY_RECORD`),
|
|
4560
|
-
* (`ASTM_FRAME_UNENCODABLE_CHARACTER`)
|
|
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`).
|
|
4561
4652
|
* @example
|
|
4562
4653
|
* ```ts
|
|
4563
4654
|
* import { parseAstmRecords, serializeFramedAstm, parseFramedAstm } from "@cosyte/astm";
|