@cosyte/hl7 0.0.2 → 0.0.4

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.cjs CHANGED
@@ -38,7 +38,7 @@ var ProfileDefinitionError = class extends Error {
38
38
  profileName;
39
39
  /**
40
40
  * Construct a new `ProfileDefinitionError`. `profileName` is optional so
41
- * Phase 6 callers may omit it when the offending profile cannot be named
41
+ * callers may omit it when the offending profile cannot be named
42
42
  * (e.g. during initial validation before a name is parsed).
43
43
  *
44
44
  * @internal
@@ -255,7 +255,104 @@ var KNOWN_SEGMENTS = /* @__PURE__ */ new Set([
255
255
  "CSS"
256
256
  ]);
257
257
 
258
+ // src/parser/charset.ts
259
+ function normalizeLabel(raw) {
260
+ return raw.trim().replace(/\s+/gu, " ").toUpperCase();
261
+ }
262
+ function decodeEntry(canonical, decoder) {
263
+ return { canonical, treatment: "decode", decoder };
264
+ }
265
+ function verbatimEntry(canonical) {
266
+ return { canonical, treatment: "verbatim", decoder: "" };
267
+ }
268
+ function buildTable() {
269
+ const table = /* @__PURE__ */ new Map();
270
+ const add = (aliases, entry) => {
271
+ for (const alias of aliases) table.set(normalizeLabel(alias), entry);
272
+ };
273
+ add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
274
+ add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
275
+ add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
276
+ for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
277
+ add(
278
+ [`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
279
+ decodeEntry(`8859/${n}`, `iso-8859-${n}`)
280
+ );
281
+ }
282
+ add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
283
+ add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
284
+ add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
285
+ add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
286
+ add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
287
+ add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
288
+ add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
289
+ add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
290
+ add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
291
+ add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
292
+ add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
293
+ return table;
294
+ }
295
+ var CHARSET_TABLE = buildTable();
296
+ var DEFAULT_RESOLUTION = Object.freeze({
297
+ canonical: "ASCII",
298
+ treatment: "decode",
299
+ decoder: "utf-8",
300
+ recognized: true
301
+ });
302
+ function resolveCharset(raw) {
303
+ if (raw === void 0) return DEFAULT_RESOLUTION;
304
+ const key = normalizeLabel(raw);
305
+ if (key === "") return DEFAULT_RESOLUTION;
306
+ const entry = CHARSET_TABLE.get(key);
307
+ if (entry !== void 0) {
308
+ return {
309
+ canonical: entry.canonical,
310
+ treatment: entry.treatment,
311
+ decoder: entry.decoder,
312
+ recognized: true
313
+ };
314
+ }
315
+ return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
316
+ }
317
+ function canonicalCharset(raw) {
318
+ return resolveCharset(raw).canonical;
319
+ }
320
+
321
+ // src/parser/tokens.ts
322
+ var DERIVED_TOKEN_SHAPES = {
323
+ /**
324
+ * HL7 v2 Ch. 2 §2.5 segment identifier: three characters, the first
325
+ * alphabetic.
326
+ *
327
+ * Two deliberate tightenings, each measured rather than assumed. The first
328
+ * version allowed four characters as "vendor tolerance", which was invented
329
+ * rather than grounded: all 91 `KNOWN_SEGMENTS` are exactly three, and
330
+ * `defineProfile` independently validates custom Z-segments to
331
+ * `/^Z[A-Z0-9]{2}$/`, so nothing legitimate is longer. The second requires a
332
+ * leading letter, which every known segment and every legal Z-segment
333
+ * satisfies, and which excludes a **bare numeric** residue. That matters out
334
+ * of proportion to its size: a forged line reading `120` is a glucose, a
335
+ * blood pressure or a dose, which is the most clinical meaning three
336
+ * characters can carry.
337
+ */
338
+ segmentId: /^[A-Za-z][A-Za-z0-9]{2}$/,
339
+ /** MSH-9 message type / trigger, e.g. `ADT^A01` or `A40`. */
340
+ messageType: /^[A-Za-z0-9]{1,8}(?:\^[A-Za-z0-9]{1,8}){0,2}$/,
341
+ /** MSH-12 version ID, e.g. `2.5` or `2.5.1`. */
342
+ version: /^[0-9]{1,2}(?:\.[0-9]{1,3}){0,3}$/
343
+ };
344
+ function safeDerivedToken(value, kind) {
345
+ return DERIVED_TOKEN_SHAPES[kind].test(value) ? value : WITHHELD;
346
+ }
347
+ var WITHHELD = "<withheld>";
348
+ function boundedIdentifier(value, kind) {
349
+ return value === "" ? "" : safeDerivedToken(value, kind);
350
+ }
351
+
258
352
  // src/parser/warnings.ts
353
+ function safeCharsetLabel(value) {
354
+ return resolveCharset(value).recognized ? value : WITHHELD;
355
+ }
259
356
  var WARNING_CODES = {
260
357
  MLLP_FRAMING_STRIPPED: "MLLP_FRAMING_STRIPPED",
261
358
  FIELD_WHITESPACE_TRIMMED: "FIELD_WHITESPACE_TRIMMED",
@@ -340,28 +437,28 @@ function timestampFallbackFormat(position, matchedFormat) {
340
437
  function segmentCase(position, observed) {
341
438
  return {
342
439
  code: WARNING_CODES.SEGMENT_CASE,
343
- message: `Segment identifier "${observed}" is not uppercase; normalized to "${observed.toUpperCase()}".`,
440
+ message: `Segment identifier "${safeDerivedToken(observed, "segmentId")}" is not uppercase; normalized to "${safeDerivedToken(observed.toUpperCase(), "segmentId")}".`,
344
441
  position
345
442
  };
346
443
  }
347
444
  function extraFields(position, segmentName2, extraCount) {
348
445
  return {
349
446
  code: WARNING_CODES.EXTRA_FIELDS,
350
- message: `Segment "${segmentName2}" has ${String(extraCount)} more field(s) than the profile declares.`,
447
+ message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" has ${String(extraCount)} more field(s) than the profile declares.`,
351
448
  position
352
449
  };
353
450
  }
354
451
  function unknownSegment(position, segmentName2) {
355
452
  return {
356
453
  code: WARNING_CODES.UNKNOWN_SEGMENT,
357
- message: `Unknown segment "${segmentName2}": not in HL7 spec and no profile claim.`,
454
+ message: `Unknown segment "${safeDerivedToken(segmentName2, "segmentId")}": not in HL7 spec and no profile claim.`,
358
455
  position
359
456
  };
360
457
  }
361
458
  function duplicateRequiredSegment(position, segmentName2) {
362
459
  return {
363
460
  code: WARNING_CODES.DUPLICATE_REQUIRED_SEGMENT,
364
- message: `Required singleton segment "${segmentName2}" appears more than once.`,
461
+ message: `Required singleton segment "${safeDerivedToken(segmentName2, "segmentId")}" appears more than once.`,
365
462
  position
366
463
  };
367
464
  }
@@ -375,42 +472,42 @@ function encodingMismatch(position, detail) {
375
472
  function missingRequiredField(position, segmentName2, fieldIndex) {
376
473
  return {
377
474
  code: WARNING_CODES.MISSING_REQUIRED_FIELD,
378
- message: `Required field ${segmentName2}-${String(fieldIndex)} is missing or empty.`,
475
+ message: `Required field ${safeDerivedToken(segmentName2, "segmentId")}-${String(fieldIndex)} is missing or empty.`,
379
476
  position
380
477
  };
381
478
  }
382
479
  function missingExpectedGroup(position, messageType, groupName, anchorSegments) {
383
480
  return {
384
481
  code: WARNING_CODES.MISSING_EXPECTED_GROUP,
385
- message: `Message type "${messageType}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
482
+ message: `Message type "${safeDerivedToken(messageType, "messageType")}" is missing its expected "${groupName}" segment group (no ${anchorSegments.join("/")}); message may be truncated or misrouted.`,
386
483
  position
387
484
  };
388
485
  }
389
486
  function outOfOrderSegment(position, segmentName2) {
390
487
  return {
391
488
  code: WARNING_CODES.OUT_OF_ORDER_SEGMENT,
392
- message: `Segment "${segmentName2}" appears out of the order declared by the active profile.`,
489
+ message: `Segment "${safeDerivedToken(segmentName2, "segmentId")}" appears out of the order declared by the active profile.`,
393
490
  position
394
491
  };
395
492
  }
396
493
  function versionMismatch(position, declared, expected) {
397
494
  return {
398
495
  code: WARNING_CODES.VERSION_MISMATCH,
399
- message: `HL7 version mismatch: message declares "${declared}", expected "${expected}".`,
496
+ message: `HL7 version mismatch: message declares "${safeDerivedToken(declared, "version")}", expected "${expected}".`,
400
497
  position
401
498
  };
402
499
  }
403
500
  function unknownCharset(position, requested) {
404
501
  return {
405
502
  code: WARNING_CODES.UNKNOWN_CHARSET,
406
- message: `Unrecognized character set "${requested}"; not decoded, bytes read as latin1.`,
503
+ message: `Unrecognized character set "${safeCharsetLabel(requested)}"; not decoded, bytes read as latin1.`,
407
504
  position
408
505
  };
409
506
  }
410
507
  function unsupportedCharset(position, code) {
411
508
  return {
412
509
  code: WARNING_CODES.UNSUPPORTED_CHARSET,
413
- message: `Character set "${code}" was not decoded; bytes read as latin1.`,
510
+ message: `Character set "${safeCharsetLabel(code)}" was not decoded; bytes read as latin1.`,
414
511
  position
415
512
  };
416
513
  }
@@ -425,7 +522,7 @@ function mergeMissingPriorOrSurvivor(position, eventType, missing) {
425
522
  const detail = missing === "prior" ? "no MRG segment carries a usable prior (non-surviving) identifier" : "no PID segment carries a usable surviving identifier";
426
523
  return {
427
524
  code: WARNING_CODES.MERGE_MISSING_PRIOR_OR_SURVIVOR,
428
- message: `Identity event "${eventType}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
525
+ message: `Identity event "${safeDerivedToken(eventType, "messageType")}" is missing its ${missing} role: ${detail}. Surfacing what is present; the MRG->PID merge direction is not guessed.`,
429
526
  position
430
527
  };
431
528
  }
@@ -471,69 +568,6 @@ function emitIfFramed(result, emit, position) {
471
568
  }
472
569
  }
473
570
 
474
- // src/parser/charset.ts
475
- function normalizeLabel(raw) {
476
- return raw.trim().replace(/\s+/gu, " ").toUpperCase();
477
- }
478
- function decodeEntry(canonical, decoder) {
479
- return { canonical, treatment: "decode", decoder };
480
- }
481
- function verbatimEntry(canonical) {
482
- return { canonical, treatment: "verbatim", decoder: "" };
483
- }
484
- function buildTable() {
485
- const table = /* @__PURE__ */ new Map();
486
- const add = (aliases, entry) => {
487
- for (const alias of aliases) table.set(normalizeLabel(alias), entry);
488
- };
489
- add(["ASCII", "US-ASCII", "ISO IR6"], decodeEntry("ASCII", "utf-8"));
490
- add(["UTF-8", "UTF8", "UNICODE UTF-8", "UNICODE"], decodeEntry("UTF-8", "utf-8"));
491
- add(["8859/1", "ISO-8859-1", "ISO 8859-1", "ISO8859-1"], decodeEntry("8859/1", "latin1"));
492
- for (const n of [2, 3, 4, 5, 6, 7, 8, 10, 13, 14, 15, 16]) {
493
- add(
494
- [`8859/${n}`, `ISO-8859-${n}`, `ISO 8859-${n}`, `ISO8859-${n}`],
495
- decodeEntry(`8859/${n}`, `iso-8859-${n}`)
496
- );
497
- }
498
- add(["8859/9", "ISO-8859-9", "ISO 8859-9", "ISO8859-9"], verbatimEntry("8859/9"));
499
- add(["8859/11", "ISO-8859-11", "ISO 8859-11", "ISO8859-11"], verbatimEntry("8859/11"));
500
- add(["ISO IR14", "JIS X 0201"], verbatimEntry("ISO IR14"));
501
- add(["ISO IR87", "JIS X 0208"], verbatimEntry("ISO IR87"));
502
- add(["ISO IR159", "JIS X 0212"], verbatimEntry("ISO IR159"));
503
- add(["GB 18030-2000", "GB18030", "GB 18030"], verbatimEntry("GB 18030-2000"));
504
- add(["KS X 1001", "KSX1001", "KS C 5601"], verbatimEntry("KS X 1001"));
505
- add(["CNS 11643-1992", "CNS 11643"], verbatimEntry("CNS 11643-1992"));
506
- add(["BIG-5", "BIG5"], verbatimEntry("BIG-5"));
507
- add(["UNICODE UTF-16", "UTF-16"], verbatimEntry("UNICODE UTF-16"));
508
- add(["UNICODE UTF-32", "UTF-32"], verbatimEntry("UNICODE UTF-32"));
509
- return table;
510
- }
511
- var CHARSET_TABLE = buildTable();
512
- var DEFAULT_RESOLUTION = Object.freeze({
513
- canonical: "ASCII",
514
- treatment: "decode",
515
- decoder: "utf-8",
516
- recognized: true
517
- });
518
- function resolveCharset(raw) {
519
- if (raw === void 0) return DEFAULT_RESOLUTION;
520
- const key = normalizeLabel(raw);
521
- if (key === "") return DEFAULT_RESOLUTION;
522
- const entry = CHARSET_TABLE.get(key);
523
- if (entry !== void 0) {
524
- return {
525
- canonical: entry.canonical,
526
- treatment: entry.treatment,
527
- decoder: entry.decoder,
528
- recognized: true
529
- };
530
- }
531
- return { canonical: key, treatment: "verbatim", decoder: "", recognized: false };
532
- }
533
- function canonicalCharset(raw) {
534
- return resolveCharset(raw).canonical;
535
- }
536
-
537
571
  // src/parser/normalize.ts
538
572
  var CHARSET_POSITION = { segmentIndex: 0, fieldIndex: 18 };
539
573
  function preserveVerbatim(input) {
@@ -928,8 +962,8 @@ function analyzeMessageStructure(messageCode, triggerEvent, presentSegmentNames)
928
962
  if (def === void 0) {
929
963
  return Object.freeze({
930
964
  recognized: false,
931
- messageCode,
932
- triggerEvent,
965
+ messageCode: boundedIdentifier(messageCode, "segmentId"),
966
+ triggerEvent: boundedIdentifier(triggerEvent, "segmentId"),
933
967
  expectedGroups: Object.freeze([]),
934
968
  missingGroups: Object.freeze([])
935
969
  });
@@ -1863,11 +1897,11 @@ var Field = class {
1863
1897
  isNull;
1864
1898
  /** Reference to the underlying `RawField.repetitions` (no defensive copy). */
1865
1899
  repetitions;
1866
- /** The 5 encoding characters for this message. Exposed for composite parsers (Plan 04). @internal */
1900
+ /** The 5 encoding characters for this message. Exposed for composite parsers. @internal */
1867
1901
  enc;
1868
1902
  /** Position of this field in the parent message: used for position-aware error messages. @internal */
1869
1903
  position;
1870
- /** The full `RawField` this wrapper wraps. Exposed for composite parsers (Plan 02/03/04). @internal */
1904
+ /** The full `RawField` this wrapper wraps. Exposed for composite parsers. @internal */
1871
1905
  raw;
1872
1906
  /**
1873
1907
  * Construct a new `Field`. Called internally by `Segment.field(n)`; user
@@ -1884,7 +1918,7 @@ var Field = class {
1884
1918
  /**
1885
1919
  * First-repetition, first-component, first-subcomponent value as a decoded
1886
1920
  * string: unescaped ONCE by the tokenizer on parse, returned verbatim here
1887
- * (never re-unescaped, HL7-VALUE-REDECODE). Returns `""` when the field is
1921
+ * (never re-unescaped). Returns `""` when the field is
1888
1922
  * empty or HL7 null. Equivalent to `msg.get('SEG.N')` for a top-level access.
1889
1923
  *
1890
1924
  * @example
@@ -1915,7 +1949,7 @@ var Field = class {
1915
1949
  * vendor-quirk control id containing an unescaped delimiter (`ID^X`) must
1916
1950
  * not be truncated to its first component.
1917
1951
  *
1918
- * **Byte-verbatim for parsed content (HL7-ESC).** The parse pipeline stores
1952
+ * **Byte-verbatim for parsed content.** The parse pipeline stores
1919
1953
  * *decoded* content, but also records the original wire bytes of any escape
1920
1954
  * whose decode is not byte-faithful (`RawComponent.rawSubcomponents`), so
1921
1955
  * re-serialization preserves the sender's exact escape bytes: hex escapes
@@ -2161,7 +2195,24 @@ var EMPTY_REP = Object.freeze({
2161
2195
 
2162
2196
  // src/model/segment.ts
2163
2197
  var Segment = class {
2164
- /** Segment name (3 chars, e.g. `"PID"`, `"OBX"`, `"ZPI"`). */
2198
+ /**
2199
+ * Segment identifier: three characters with a leading letter, e.g. `"PID"`,
2200
+ * `"OBX"`, `"ZPI"`.
2201
+ *
2202
+ * **Bounded, and it is `"<withheld>"` when the raw name is not that shape.**
2203
+ * A line with no field separator has its whole content read as a segment
2204
+ * name, so an unescaped line break inside a narrative field forges a
2205
+ * "segment" whose name is clinical text. This field presents itself as a
2206
+ * structural identifier and consumers interpolate it into labels, loci and
2207
+ * reports, so it never carries that text. `""` still means absent, which is
2208
+ * a different fact from withheld.
2209
+ *
2210
+ * Use {@link Segment.raw}`.name` when you need the verbatim text: it is the
2211
+ * unbounded value, and it is what serialization emits, so the byte-verbatim
2212
+ * round-trip is unaffected. One consequence worth knowing: a segment whose
2213
+ * raw name fails the shape (a 4-character vendor Z-segment, which HL7 v2
2214
+ * Ch. 2 §2.5 does not permit) is not matched by `msg.segments(name)`.
2215
+ */
2165
2216
  type;
2166
2217
  /** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
2167
2218
  fields;
@@ -2169,7 +2220,7 @@ var Segment = class {
2169
2220
  enc;
2170
2221
  /** Absolute index of this segment in `Hl7Message.rawSegments[]`. Used for position tracking. @internal */
2171
2222
  absoluteIndex;
2172
- /** The full `RawSegment` this wrapper wraps. Exposed for mutation methods (Plan 04). @internal */
2223
+ /** The full `RawSegment` this wrapper wraps. Exposed for mutation methods. @internal */
2173
2224
  raw;
2174
2225
  /**
2175
2226
  * Lookup map from profile-declared field name → 1-indexed HL7 position.
@@ -2186,7 +2237,7 @@ var Segment = class {
2186
2237
  * should obtain `Segment` instances via `msg.segments(type)` or
2187
2238
  * `msg.allSegments()`.
2188
2239
  *
2189
- * The optional `customFields` parameter is the per-segment slice of the
2240
+ * The optional `customFields` parameter is the per-segment portion of the
2190
2241
  * applied profile's merged `customSegments` map (PROF-07 / D-16). When
2191
2242
  * supplied, `get(name)` resolves names against it; otherwise `get(name)`
2192
2243
  * always returns `undefined`.
@@ -2194,7 +2245,7 @@ var Segment = class {
2194
2245
  */
2195
2246
  constructor(raw, enc, absoluteIndex, customFields) {
2196
2247
  this.raw = raw;
2197
- this.type = raw.name;
2248
+ this.type = boundedIdentifier(raw.name, "segmentId");
2198
2249
  this.fields = raw.fields;
2199
2250
  this.enc = enc;
2200
2251
  this.absoluteIndex = absoluteIndex;
@@ -3881,11 +3932,20 @@ var Hl7Message = class {
3881
3932
  * Raw positional tree produced by the parser. 1-indexed per HL7 convention
3882
3933
  * (`fields[0]` is the segment-name / MSH separator placeholder slot). Use
3883
3934
  * `segments(type)` / `allSegments()` for typed wrapper access: this field
3884
- * is exposed for advanced callers that need the raw tree directly (e.g.
3885
- * future serialization phase 5).
3935
+ * is exposed for advanced callers that need the raw tree directly.
3886
3936
  */
3887
3937
  rawSegments;
3888
3938
  encodingCharacters;
3939
+ /**
3940
+ * HL7 version the model asserts for this message, from MSH-12.1.1, e.g.
3941
+ * `"2.5"` or `"2.5.1"`.
3942
+ *
3943
+ * **Bounded, and it is `"<withheld>"` when MSH-12 does not hold a version.**
3944
+ * Same reasoning as {@link Segment.type}: it presents as an identifier and
3945
+ * consumers label with it. `meta.version` keeps MSH-12 exactly as it
3946
+ * arrived, and is what version-scoped field selection reads, so this bound
3947
+ * changes no parsing behaviour.
3948
+ */
3889
3949
  version;
3890
3950
  warnings;
3891
3951
  profile;
@@ -3898,7 +3958,7 @@ var Hl7Message = class {
3898
3958
  dateFormats;
3899
3959
  /**
3900
3960
  * Merged `customSegments` map from the applied profile, stored so
3901
- * `allSegments()` can hand per-segment slices to `Segment` constructors
3961
+ * `allSegments()` can hand per-segment portions to `Segment` constructors
3902
3962
  * (D-16). Undefined when no profile was applied.
3903
3963
  * @internal
3904
3964
  */
@@ -3907,40 +3967,40 @@ var Hl7Message = class {
3907
3967
  * Lazily built cache of Segment wrappers keyed by segment type. Built on
3908
3968
  * first `segments(type)` call and filtered from `_allSegments` so
3909
3969
  * individual Segment instances are identical across both caches (D-11).
3910
- * Plan 04 mutation methods drop this cache wholesale.
3970
+ * The mutation methods drop this cache wholesale.
3911
3971
  * @internal
3912
3972
  */
3913
3973
  _segmentsByType;
3914
3974
  /**
3915
3975
  * Lazily built cache of every Segment wrapper in document order. Built on
3916
- * first `segments(type)` / `allSegments()` call. Plan 04 mutation methods
3976
+ * first `segments(type)` / `allSegments()` call. The mutation methods
3917
3977
  * drop this cache wholesale.
3918
3978
  * @internal
3919
3979
  */
3920
3980
  _allSegments;
3921
3981
  /**
3922
- * Lazily built `Meta` view (Phase 4 D-02 memoization). Dropped wholesale
3982
+ * Lazily built `Meta` view (memoized). Dropped wholesale
3923
3983
  * by `invalidateCaches`. `Meta` is always defined (D-03: MSH absence
3924
3984
  * throws `NO_MSH_SEGMENT` at parse time) so no null-sentinel is needed.
3925
3985
  * @internal
3926
3986
  */
3927
3987
  _meta;
3928
3988
  /**
3929
- * Lazily built `MessageStructure` view (Phase G). Dropped wholesale by
3989
+ * Lazily built `MessageStructure` view. Dropped wholesale by
3930
3990
  * `invalidateCaches`. Always defined: `analyzeMessageStructure` returns an
3931
3991
  * `recognized: false` summary for unmodelled types rather than absence.
3932
3992
  * @internal
3933
3993
  */
3934
3994
  _structure;
3935
3995
  /**
3936
- * Lazily built `Patient | undefined` view (Phase 4 D-02 memoization). Uses
3996
+ * Lazily built `Patient | undefined` view (memoized). Uses
3937
3997
  * a null-sentinel cache because `undefined` means "not yet computed" while
3938
3998
  * `null` means "computed, absent". D-04: no PID → public value `undefined`.
3939
3999
  * @internal
3940
4000
  */
3941
4001
  _patient;
3942
4002
  /**
3943
- * Lazily built `Visit | undefined` view (Phase 4 D-02 memoization). Same
4003
+ * Lazily built `Visit | undefined` view (memoized). Same
3944
4004
  * null-sentinel convention as `_patient`. HELPERS-03: no PV1 → public
3945
4005
  * value `undefined`.
3946
4006
  * @internal
@@ -3956,7 +4016,7 @@ var Hl7Message = class {
3956
4016
  constructor(init) {
3957
4017
  this.rawSegments = init.segments;
3958
4018
  this.encodingCharacters = init.encodingCharacters;
3959
- this.version = init.version;
4019
+ this.version = boundedIdentifier(init.version, "version");
3960
4020
  this.warnings = Object.freeze(init.warnings.slice());
3961
4021
  this.profile = init.profile;
3962
4022
  if (init.customSegments !== void 0) this._customSegments = init.customSegments;
@@ -3964,8 +4024,8 @@ var Hl7Message = class {
3964
4024
  }
3965
4025
  /**
3966
4026
  * Resolve a dot-path (e.g. `PID.5.1`, `OBX[2].5`, `PID.3[0].1`) to its
3967
- * decoded leaf string (unescaped once at parse: never re-unescaped on read,
3968
- * HL7-VALUE-REDECODE). Returns `undefined` when the path doesn't
4027
+ * decoded leaf string (unescaped once at parse: never re-unescaped on
4028
+ * read). Returns `undefined` when the path doesn't
3969
4029
  * resolve: never throws on missing path (MODEL-05). Throws `TypeError`
3970
4030
  * on malformed path syntax (e.g. `"pid.5"`, empty string).
3971
4031
  *
@@ -4000,7 +4060,7 @@ var Hl7Message = class {
4000
4060
  * Return the cached array of `Segment` wrappers for `segmentType` in
4001
4061
  * document order. The returned array identity and the individual Segment
4002
4062
  * instances are both stable across calls (D-11). Invalidated wholesale
4003
- * by Plan 04 mutation methods.
4063
+ * by the mutation methods.
4004
4064
  *
4005
4065
  * @example
4006
4066
  * ```ts
@@ -4023,7 +4083,7 @@ var Hl7Message = class {
4023
4083
  * Iterate every `Segment` in document order (MSH first, then every
4024
4084
  * subsequent segment). Cached per-message; same array reference and same
4025
4085
  * Segment instances on repeat calls (D-11). Invalidated wholesale by
4026
- * Plan 04 mutation methods.
4086
+ * the mutation methods.
4027
4087
  *
4028
4088
  * @example
4029
4089
  * ```ts
@@ -4057,7 +4117,7 @@ var Hl7Message = class {
4057
4117
  * @example
4058
4118
  * ```ts
4059
4119
  * console.log(msg.meta.type); // "ADT^A01"
4060
- * console.log(msg.meta.timestamp?.raw); // fidelity TS (Phase N)
4120
+ * console.log(msg.meta.timestamp?.raw); // fidelity TS
4061
4121
  * console.log(msg.meta.controlId); // "MSG001"
4062
4122
  * ```
4063
4123
  */
@@ -4065,8 +4125,8 @@ var Hl7Message = class {
4065
4125
  return this._meta ??= buildMeta(this);
4066
4126
  }
4067
4127
  /**
4068
- * Structural-conformance summary for the common message types (roadmap
4069
- * Phase G): a misroute/truncation safety net, NOT a conformance validator.
4128
+ * Structural-conformance summary for the common message types: a
4129
+ * misroute/truncation safety net, NOT a conformance validator.
4070
4130
  * Reports, per the message's recognized (MSH-9.1, MSH-9.2) type, which
4071
4131
  * Required segment groups are present and which are entirely absent
4072
4132
  * (`missingGroups`: the same set the parser flags as
@@ -4134,7 +4194,7 @@ var Hl7Message = class {
4134
4194
  }
4135
4195
  /**
4136
4196
  * Every OBR as an Order with its OBX children grouped positionally (D-12) and
4137
- * its TQ1 / legacy embedded-TQ (ORC-7) `timings` (Phase M: the repeat pattern
4197
+ * its TQ1 / legacy embedded-TQ (ORC-7) `timings` (the repeat pattern is
4138
4198
  * surfaced verbatim, never resolved to a schedule). D-05: returns `[]` when no
4139
4199
  * OBR present. D-06: not memoized.
4140
4200
  *
@@ -4150,7 +4210,7 @@ var Hl7Message = class {
4150
4210
  return orders(this);
4151
4211
  }
4152
4212
  /**
4153
- * Message-level NTE notes (Phase P): every NTE segment with no recognized
4213
+ * Message-level NTE notes: every NTE segment with no recognized
4154
4214
  * preceding parent (not immediately following a `PID`, `ORC`, `OBR`, or
4155
4215
  * `OBX`), surfaced verbatim in document order so nothing is dropped. Notes
4156
4216
  * that DO attach to a specific patient / order / result are exposed on those
@@ -4167,7 +4227,7 @@ var Hl7Message = class {
4167
4227
  }
4168
4228
  /**
4169
4229
  * Every recognized ADT patient-identity event (merge / move / link /
4170
- * unlink / person add/update: roadmap Phase K), with the MRG-sourced
4230
+ * unlink / person add/update), with the MRG-sourced
4171
4231
  * `prior` and PID/PV1-sourced `surviving` parties labelled by role and the
4172
4232
  * spec-constant `direction: "MRG_TO_PID"` on merge/move events. Returns
4173
4233
  * `[]` when the trigger event is not in the identity family. D-06: not
@@ -4188,13 +4248,13 @@ var Hl7Message = class {
4188
4248
  }
4189
4249
  /**
4190
4250
  * Every RXO/RXE/RXD/RXA as a typed `Medication`, with RXR (route) and RXC
4191
- * (component) segments grouped positionally under their parent (Phase D,
4192
- * P0 safety). D-05: returns `[]` when no RX* parent present. D-06: not
4193
- * memoized. The give *amount* and give *strength* are surfaced separately
4194
- * and never reconciled (Phase D §4).
4251
+ * (component) segments grouped positionally under their parent. D-05:
4252
+ * returns `[]` when no RX* parent present. D-06: not memoized. The give
4253
+ * *amount* and give *strength* are surfaced separately and never
4254
+ * reconciled.
4195
4255
  *
4196
4256
  * Each medication also carries its TQ1 / legacy embedded-TQ (RXE-1) `timings`
4197
- * (Phase M: repeat pattern verbatim, never resolved to a schedule).
4257
+ * (repeat pattern verbatim, never resolved to a schedule).
4198
4258
  *
4199
4259
  * @example
4200
4260
  * ```ts
@@ -4211,7 +4271,7 @@ var Hl7Message = class {
4211
4271
  /**
4212
4272
  * Every RXA of a VXU^V04 as a typed `Immunization`, with RXR (route/site)
4213
4273
  * and OBX children grouped positionally under the RXA and `orderControl`
4214
- * from the preceding ORC of the VXU order group (Phase E, P0 safety). D-05:
4274
+ * from the preceding ORC of the VXU order group. D-05:
4215
4275
  * returns `[]` when no RXA present. D-06: not memoized. The vaccine code
4216
4276
  * carries its own provenance; the action code (RXA-21) is surfaced verbatim
4217
4277
  * and `recordOrigin` (administered vs historical) is derived only from the
@@ -4279,7 +4339,7 @@ var Hl7Message = class {
4279
4339
  return insurance(this);
4280
4340
  }
4281
4341
  /**
4282
- * Every SCH of an SIU message as a typed `Appointment` (roadmap Phase Q), with
4342
+ * Every SCH of an SIU message as a typed `Appointment`, with
4283
4343
  * the AIS/AIG/AIL/AIP resource segments that follow it grouped positionally
4284
4344
  * under that SCH. Surfaces the placer/filler appointment ids, SCH-25 filler
4285
4345
  * status (Table 0278), SCH-11 start/end timing, and the resource groups
@@ -4299,7 +4359,7 @@ var Hl7Message = class {
4299
4359
  return appointments(this);
4300
4360
  }
4301
4361
  /**
4302
- * Every TXA of an MDM message as a typed `ClinicalDocument` (roadmap Phase Q),
4362
+ * Every TXA of an MDM message as a typed `ClinicalDocument`,
4303
4363
  * with the OBX narrative body grouped positionally under that TXA. The
4304
4364
  * completion status (TXA-17) and availability status (TXA-19) are surfaced as
4305
4365
  * **distinct** fields and never conflated: a document can be available before
@@ -4319,7 +4379,7 @@ var Hl7Message = class {
4319
4379
  return documents(this);
4320
4380
  }
4321
4381
  /**
4322
- * Every FT1 of a DFT message as a typed `Charge` (roadmap Phase Q), one per
4382
+ * Every FT1 of a DFT message as a typed `Charge`, one per
4323
4383
  * FT1 in document order. Surfaces billing-critical fields (FT1-6 transaction
4324
4384
  * type, FT1-7 code, FT1-11/12 extended/unit amount, FT1-19 diagnosis linkage)
4325
4385
  * with **no billing logic and no money-as-float**: amounts are the verbatim
@@ -4413,7 +4473,7 @@ var Hl7Message = class {
4413
4473
  * `TypeError` with an actionable message otherwise).
4414
4474
  *
4415
4475
  * The value is accepted verbatim: unescaped delimiter characters are NOT
4416
- * rejected on input (D-18). Re-escaping on serialize is Phase 5's concern.
4476
+ * rejected on input (D-18). Re-escaping is the serializer's concern.
4417
4477
  *
4418
4478
  * MSH-1 / MSH-2 follow the user-facing HL7 convention: `setField("MSH.3", ...)`
4419
4479
  * targets MSH-3 (sending application), matching `msg.get("MSH.3")`.
@@ -4499,11 +4559,11 @@ var Hl7Message = class {
4499
4559
  return this;
4500
4560
  }
4501
4561
  /**
4502
- * Set a **typed composite** at a field (or field-repetition) dot-path,
4503
- * roadmap Phase T, the conservative-emit mirror of the typed read accessors
4562
+ * Set a **typed composite** at a field (or field-repetition) dot-path:
4563
+ * the conservative-emit mirror of the typed read accessors
4504
4564
  * (`asXpn`/`asCx`/…). The caller passes a structured value (an XPN name, a CX
4505
4565
  * identifier, a TS timestamp, …) by its {@link CompositeKind}, and the setter
4506
- * encodes it into a spec-clean field using the HL7-R encode-safe path: any
4566
+ * encodes it into a spec-clean field using the encode-safe path: any
4507
4567
  * delimiter embedded in a component value is **escaped, never injected**, so
4508
4568
  * a `familyName` of `"Smith^Jr"` re-parses to exactly that string rather than
4509
4569
  * forging a component boundary. No hand-assembly of `^`/`&`/`~`.
@@ -4694,8 +4754,8 @@ var Hl7Message = class {
4694
4754
  return this;
4695
4755
  }
4696
4756
  /**
4697
- * Drop every wrapper AND helper cache wholesale (Phase 3 D-17 + Phase 4
4698
- * D-02). Called by every mutation method so subsequent `segments(type)` /
4757
+ * Drop every wrapper AND helper cache wholesale (D-17 + D-02). Called by
4758
+ * every mutation method so subsequent `segments(type)` /
4699
4759
  * `allSegments()` / `meta` / `patient` / `visit` reads rebuild from the
4700
4760
  * mutated `rawSegments` tree.
4701
4761
  * @internal
@@ -4794,7 +4854,12 @@ function resolveBufferCharset(raw, options, emit) {
4794
4854
  emit(
4795
4855
  encodingMismatch(
4796
4856
  { segmentIndex: 0 },
4797
- `options.charset="${override}" disagrees with MSH-18="${declared}"`
4857
+ // `declared` is MSH-18 read off the wire. On a message with no
4858
+ // segment terminators it is not MSH-18 at all but the 18th
4859
+ // `|`-token of the flattened message, commonly a data field, so it
4860
+ // is shape-checked before it reaches a log surface. `override` is
4861
+ // the caller's own `options.charset`, not input, and is left alone.
4862
+ `options.charset="${override}" disagrees with MSH-18="${safeCharsetLabel(declared)}"`
4798
4863
  )
4799
4864
  );
4800
4865
  }
@@ -6809,7 +6874,7 @@ async function* parseStream(source, optionsOrProfile) {
6809
6874
  }
6810
6875
 
6811
6876
  // src/index.ts
6812
- var VERSION = "0.0.2";
6877
+ var VERSION = "0.0.4";
6813
6878
 
6814
6879
  exports.ACK_CODES = ACK_CODES;
6815
6880
  exports.ACK_CONDITIONS = ACK_CONDITIONS;