@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/CHANGELOG.md +69 -0
- package/README.md +7 -1
- package/dist/index.cjs +13 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -16
- package/dist/index.d.ts +86 -16
- package/dist/index.mjs +13 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,75 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
13
|
|
|
14
|
+
- **`composeAstmFrames` wrote an out-of-range `startFrameNumber` straight into the frame-number
|
|
15
|
+
position, so a value that was not a frame number produced a stream whose records the decoder
|
|
16
|
+
refused to emit** (`ASTM-FRAME-RESIDUALS`, defect 13). `PRE-EXISTING`, byte-identical on `64c2fd5`.
|
|
17
|
+
The option is now checked before `composeAstmFrames` reads a record, and refused with the new code
|
|
18
|
+
`ASTM_FRAME_INVALID_START_FRAME_NUMBER` (a fourth member of `AstmFrameEncodeErrorCode`) unless it is
|
|
19
|
+
a whole number from `0` to `7`.
|
|
20
|
+
|
|
21
|
+
**What went out instead, measured on this package's own round trip.** A frame's number is one ASCII
|
|
22
|
+
digit, written as `FN_ZERO + n`, and nothing checked `n`. `-1` put a `/` in the frame-number
|
|
23
|
+
position, and `NaN`, `Infinity` and `-Infinity` each put a `NUL` there in **every** frame of the
|
|
24
|
+
stream, after which `decodeAstmFrames` recognised no frame number at all, warned
|
|
25
|
+
`ASTM_FRAME_SEQUENCE_GAP` on every frame, and emitted **none** of the records: a whole four-record
|
|
26
|
+
message in, zero out, and `parseFramedAstm` throwing on empty input. **The recorded defect said an
|
|
27
|
+
out-of-range value writes a non-digit byte and named a space for `NaN`. It is a `NUL`**, which is
|
|
28
|
+
the difference between an ordinary inter-frame byte the decoder skips and a byte that costs the
|
|
29
|
+
stream every one of its records.
|
|
30
|
+
|
|
31
|
+
**The quieter half is why the check covers the whole domain rather than the ends.** Values that
|
|
32
|
+
truncated back onto a digit were accepted silently and behaved as some other start value: `1.5` and
|
|
33
|
+
`257` each produced the byte-for-byte stream a `startFrameNumber` of `1` produces. The option
|
|
34
|
+
documented `0`-`7` and enforced neither bound nor integrality in either direction.
|
|
35
|
+
|
|
36
|
+
**The domain is derived from what a frame can carry, and the refusal is not a clamp.** The writable
|
|
37
|
+
numbers are exactly the whole numbers `0`-`7`, the modulo-8 sequence `decodeAstmFrames` reads and
|
|
38
|
+
rolls over. Clamping or taking a modulo would pick a frame number the caller did not ask for, and
|
|
39
|
+
the frame number is the decoder's only evidence that no frame was dropped, so a stream numbered from
|
|
40
|
+
a value nobody chose is a stream whose sequence check certifies the wrong thing. The refusal is
|
|
41
|
+
raised before `composeAstmFrames` reads a record, so on that entry point it never depends on the
|
|
42
|
+
caller's data (`serializeFramedAstm` serializes every record first, so a record that cannot be
|
|
43
|
+
serialized at all is refused ahead of it on that route). Its message names the value received: that
|
|
44
|
+
value is the caller's own option, not stream content, and nothing from the records reaches it.
|
|
45
|
+
|
|
46
|
+
**The whole `0`-`7` range is still accepted, because a non-default start has a real use that was
|
|
47
|
+
measured rather than assumed.** It composes a **continuation** of a transfer already in progress:
|
|
48
|
+
`composeAstmFrames(head)` joined with `composeAstmFrames(tail, { startFrameNumber: n })`, where `n`
|
|
49
|
+
is the number after the last frame `head` used, is **byte-identical** to composing the whole list in
|
|
50
|
+
one call and decodes with an empty warnings array, across the `7 → 0` rollover included. Narrowing
|
|
51
|
+
the option to `1` would have removed that.
|
|
52
|
+
|
|
53
|
+
**What a continuation is not, now stated on the option rather than guarded against.** Read on its
|
|
54
|
+
own, a stream that starts anywhere but `1` opens on a sequence gap; the decoder never bridges a gap
|
|
55
|
+
silently, so it warns and does not emit that first record. **What `parseFramedAstm` does after that
|
|
56
|
+
varies with the message shape, and no rule is offered for it:** it may throw, under more than one
|
|
57
|
+
code (`ASTM_RECORD_NO_HEADER`, `EMPTY_INPUT` and `ASTM_RECORD_UNDECLARED_DELIMITERS` were each
|
|
58
|
+
measured), and it may return a message one record short. **The one statement that generalizes is
|
|
59
|
+
that the record layer never reports the loss**, because `parseFramedAstm` hands the record parser
|
|
60
|
+
only the frames the codec vouched for, so `message.warnings` carries what the surviving records
|
|
61
|
+
warrant and nothing about the record that did not survive. Read `frameWarnings`. Nothing returns the number to continue a sequence from either, and the frame count
|
|
62
|
+
is not it once a record splits, so the supported computation is named on the option. The
|
|
63
|
+
documented-valid `0` has always behaved that way. It is the cost of the option, not a defect in the
|
|
64
|
+
caller's records, and it is why `1` is the default.
|
|
65
|
+
|
|
66
|
+
**This is a narrowing on a published package, in a small population.** `1.5` and `257` used to
|
|
67
|
+
produce a working stream, the byte-for-byte start-at-`1` one, and now throw. Every value the check
|
|
68
|
+
turns away was already outside what the option documented: a frame number, a whole number `0`-`7`.
|
|
69
|
+
|
|
70
|
+
Both the old bytes and the new refusal are pinned in `test/frames/start-frame-number.test.ts`,
|
|
71
|
+
asserted on what the old encoder produced (rebuilt with the test-only `frame()` builder, so nothing
|
|
72
|
+
is transcribed twice), with a biconditional property: a value is refused **if and only if** it is
|
|
73
|
+
not a whole number in `0`-`7`. 14 of the 32 new tests are red against `64c2fd5`.
|
|
74
|
+
|
|
75
|
+
- **Three `{@link}` targets in the published `.d.ts` did not name a symbol declared there**
|
|
76
|
+
(`ASTM-FRAME-RESIDUALS`, the sibling minors). `QuirkTolerance` is `AstmQuirkTolerance` (twice, in
|
|
77
|
+
`src/profiles/types.ts`); the `startFrameNumber` doc linked `FIRST_FRAME_NUMBER`, which this
|
|
78
|
+
package does not export, and that sentence was rewritten and no longer links it; and
|
|
79
|
+
`AstmMessage.profile` linked a bare `warnings`, meaning its sibling member, which resolves only for
|
|
80
|
+
a tool that resolves against the enclosing declaration. Every `{@link}` target in
|
|
81
|
+
`dist/index.d.ts` now names a symbol declared in that file.
|
|
82
|
+
|
|
14
83
|
- **A delimiter set colliding with a record's type letter emitted a stream that read back as
|
|
15
84
|
DIFFERENT records, and in its silent branch it fabricated a final lab result out of patient
|
|
16
85
|
identifiers** (`ASTM-FRAME-RESIDUALS`, defect 5). `PRE-EXISTING`, byte-identical on `7253098`,
|
package/README.md
CHANGED
|
@@ -53,7 +53,13 @@ reference parser, [`@cosyte/hl7`](https://github.com/cosyte/hl7).
|
|
|
53
53
|
of a frame, framing has no escape sequence for them, and writing one through truncated the frame at
|
|
54
54
|
that byte, silently losing a whole record whenever the two bytes after it happened to be the short
|
|
55
55
|
frame's checksum. The **record** layer still carries them, because a returned string is not yet on a
|
|
56
|
-
wire and a raw-transport consumer round-trips such a value exactly.
|
|
56
|
+
wire and a raw-transport consumer round-trips such a value exactly. `startFrameNumber` lets you
|
|
57
|
+
compose one transfer across several calls, and `composeAstmFrames` checks it before it reads a
|
|
58
|
+
record: a frame's number is a single ASCII digit, so a value that is not a whole number from `0` to
|
|
59
|
+
`7` is a typed error rather than whatever byte the arithmetic truncated to. The round trip above is
|
|
60
|
+
the **default** start; a non-default one writes a continuation of a sequence already in progress,
|
|
61
|
+
and a continuation read on its own opens on a frame-sequence gap, so its first record is warned
|
|
62
|
+
about and not emitted.
|
|
57
63
|
- **Vendor profiles.** `defineAstmProfile()` builds a provenance-backed profile whose tolerances
|
|
58
64
|
downgrade _expected_, non-safety-critical deviations to a `PROFILE_QUIRK_APPLIED` warning without
|
|
59
65
|
ever altering a value, behind a safety gate that refuses to tolerate any result value, flag,
|
package/dist/index.cjs
CHANGED
|
@@ -2290,6 +2290,15 @@ function assertFramable(bytes, recordIndex) {
|
|
|
2290
2290
|
}
|
|
2291
2291
|
}
|
|
2292
2292
|
}
|
|
2293
|
+
function assertStartFrameNumber(value) {
|
|
2294
|
+
if (!Number.isInteger(value) || value < 0 || value > FRAME_NUMBER_MODULUS - 1) {
|
|
2295
|
+
throw new AstmFrameEncodeError(
|
|
2296
|
+
`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)}.`,
|
|
2297
|
+
void 0,
|
|
2298
|
+
"ASTM_FRAME_INVALID_START_FRAME_NUMBER"
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2293
2302
|
function encodeFrame(text, frameNumber, isFinal) {
|
|
2294
2303
|
const term = isFinal ? ETX : ETB;
|
|
2295
2304
|
const bytes = [STX, FN_ZERO + frameNumber, ...text, term];
|
|
@@ -2298,11 +2307,13 @@ function encodeFrame(text, frameNumber, isFinal) {
|
|
|
2298
2307
|
return [...bytes, hex.charCodeAt(0), hex.charCodeAt(1), CR, LF];
|
|
2299
2308
|
}
|
|
2300
2309
|
function composeAstmFrames(records, options = {}) {
|
|
2310
|
+
const start = options.startFrameNumber ?? FIRST_FRAME_NUMBER;
|
|
2311
|
+
assertStartFrameNumber(start);
|
|
2301
2312
|
if (records.length === 0) {
|
|
2302
2313
|
throw new AstmFrameEncodeError("Cannot frame an empty record list: nothing to transmit.");
|
|
2303
2314
|
}
|
|
2304
2315
|
const out = [];
|
|
2305
|
-
let frameNumber =
|
|
2316
|
+
let frameNumber = start;
|
|
2306
2317
|
records.forEach((record, recordIndex) => {
|
|
2307
2318
|
const bytes = toBytes(record, recordIndex);
|
|
2308
2319
|
if (bytes.length === 0) {
|
|
@@ -2347,7 +2358,7 @@ function concatBytes3(chunks) {
|
|
|
2347
2358
|
}
|
|
2348
2359
|
|
|
2349
2360
|
// src/index.ts
|
|
2350
|
-
var VERSION = "0.0.
|
|
2361
|
+
var VERSION = "0.0.14";
|
|
2351
2362
|
|
|
2352
2363
|
exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
|
|
2353
2364
|
exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;
|