@cosyte/hl7 0.0.1 → 0.0.2

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.mjs CHANGED
@@ -1,6 +1,12 @@
1
1
  import { randomBytes } from 'crypto';
2
2
  import { Buffer } from 'buffer';
3
3
 
4
+ var __defProp = Object.defineProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+
4
10
  // src/parser/errors.ts
5
11
  var FATAL_CODES = {
6
12
  NO_MSH_SEGMENT: "NO_MSH_SEGMENT",
@@ -66,7 +72,7 @@ function readDelimiters(firstSegment) {
66
72
  if (firstSegment.length < 3 || firstSegment.slice(0, 3) !== "MSH") {
67
73
  throw new Hl7ParseError(
68
74
  FATAL_CODES.NO_MSH_SEGMENT,
69
- "First segment is not MSH \u2014 HL7 v2 messages must begin with an MSH segment.",
75
+ "First segment is not MSH: HL7 v2 messages must begin with an MSH segment.",
70
76
  fatalPosition,
71
77
  snip
72
78
  );
@@ -74,7 +80,7 @@ function readDelimiters(firstSegment) {
74
80
  if (firstSegment.length < 8) {
75
81
  throw new Hl7ParseError(
76
82
  FATAL_CODES.MSH_TOO_SHORT,
77
- "MSH segment is truncated \u2014 cannot read encoding characters.",
83
+ "MSH segment is truncated: cannot read encoding characters.",
78
84
  fatalPosition,
79
85
  snip
80
86
  );
@@ -83,7 +89,7 @@ function readDelimiters(firstSegment) {
83
89
  if (/\s/u.test(field)) {
84
90
  throw new Hl7ParseError(
85
91
  FATAL_CODES.INVALID_ENCODING_CHARACTERS,
86
- "MSH-1 field separator is whitespace \u2014 refusing to parse.",
92
+ "MSH-1 field separator is whitespace: refusing to parse.",
87
93
  fatalPosition,
88
94
  snip
89
95
  );
@@ -267,7 +273,8 @@ var WARNING_CODES = {
267
273
  ACK_NO_CORRELATION_ID: "ACK_NO_CORRELATION_ID",
268
274
  MERGE_MISSING_PRIOR_OR_SURVIVOR: "MERGE_MISSING_PRIOR_OR_SURVIVOR",
269
275
  BATCH_COUNT_MISMATCH: "BATCH_COUNT_MISMATCH",
270
- BATCH_MISSING_TRAILER: "BATCH_MISSING_TRAILER"
276
+ BATCH_MISSING_TRAILER: "BATCH_MISSING_TRAILER",
277
+ UNTERMINATED_STREAM_MESSAGE: "UNTERMINATED_STREAM_MESSAGE"
271
278
  };
272
279
  function mllpFramingStripped(position) {
273
280
  return {
@@ -345,7 +352,7 @@ function extraFields(position, segmentName2, extraCount) {
345
352
  function unknownSegment(position, segmentName2) {
346
353
  return {
347
354
  code: WARNING_CODES.UNKNOWN_SEGMENT,
348
- message: `Unknown segment "${segmentName2}" \u2014 not in HL7 spec and no profile claim.`,
355
+ message: `Unknown segment "${segmentName2}": not in HL7 spec and no profile claim.`,
349
356
  position
350
357
  };
351
358
  }
@@ -430,7 +437,14 @@ function batchCountMismatch(position, unit, declared, actual) {
430
437
  function batchMissingTrailer(position, header, expectedTrailer) {
431
438
  return {
432
439
  code: WARNING_CODES.BATCH_MISSING_TRAILER,
433
- message: `Batch envelope header "${header}" has no matching "${expectedTrailer}" trailer; splitBatch surfaces this but does not reject \u2014 a profile that mandates the full envelope may.`,
440
+ message: `Batch envelope header "${header}" has no matching "${expectedTrailer}" trailer; splitBatch surfaces this but does not reject: a profile that mandates the full envelope may.`,
441
+ position
442
+ };
443
+ }
444
+ function unterminatedStreamMessage(position) {
445
+ return {
446
+ code: WARNING_CODES.UNTERMINATED_STREAM_MESSAGE,
447
+ message: "The final message in the stream ended without a segment terminator; it is still yielded in full. If the source terminates every segment, this may indicate a truncated feed.",
434
448
  position
435
449
  };
436
450
  }
@@ -586,7 +600,7 @@ var RECOGNIZED_PRESERVED_SET = /* @__PURE__ */ new Set([
586
600
  "H",
587
601
  // begin highlighting (§2.7.1)
588
602
  "N",
589
- // normal text — end highlighting (§2.7.1)
603
+ // normal text: end highlighting (§2.7.1)
590
604
  ".sp",
591
605
  // vertical skip (§2.7.6)
592
606
  ".in",
@@ -773,7 +787,7 @@ function tokenizeComponent(raw, enc, emit, position) {
773
787
  var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
774
788
  // ── ADT (Ch. 3): patient identification (PID) + visit (PV1) are Required
775
789
  // for these admit/transfer/discharge/register/update/cancel events.
776
- // EVN is intentionally excluded — real senders omit it freely, making it
790
+ // EVN is intentionally excluded: real senders omit it freely, making it
777
791
  // a weak (false-positive-prone) signal.
778
792
  Object.freeze({
779
793
  messageCode: "ADT",
@@ -785,7 +799,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
785
799
  }),
786
800
  // ── ORU^R01 (Ch. 7): the OBR/OBX result group is the Required payload.
787
801
  // PID is excluded (the patient-result group's PID is R within the group,
788
- // but a result-only relay can legitimately omit it at the top — keep the
802
+ // but a result-only relay can legitimately omit it at the top: keep the
789
803
  // anchor on the result segments themselves, the true truncation signal).
790
804
  Object.freeze({
791
805
  messageCode: "ORU",
@@ -795,7 +809,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
795
809
  ])
796
810
  }),
797
811
  // ── Orders (Ch. 4): ORC is the Required common-order segment across the
798
- // order message family. OBR is excluded — it is Optional in several of
812
+ // order message family. OBR is excluded: it is Optional in several of
799
813
  // these (OML/OMG/OMI carry it inside an optional observation-request
800
814
  // group), so anchoring only on ORC avoids a false positive.
801
815
  Object.freeze({
@@ -858,7 +872,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
858
872
  Object.freeze({ name: "schedule", anchorSegments: Object.freeze(["SCH"]) })
859
873
  ])
860
874
  }),
861
- // ── MDM (Ch. 9): document management — PID + the TXA document header are
875
+ // ── MDM (Ch. 9): document management: PID + the TXA document header are
862
876
  // Required for T02 (original document) and T06 (document addendum).
863
877
  Object.freeze({
864
878
  messageCode: "MDM",
@@ -868,7 +882,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
868
882
  Object.freeze({ name: "document", anchorSegments: Object.freeze(["TXA"]) })
869
883
  ])
870
884
  }),
871
- // ── DFT^P03 (Ch. 6): financial transaction — PID + at least one FT1
885
+ // ── DFT^P03 (Ch. 6): financial transaction: PID + at least one FT1
872
886
  // financial-transaction segment are Required.
873
887
  Object.freeze({
874
888
  messageCode: "DFT",
@@ -878,7 +892,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
878
892
  Object.freeze({ name: "financial", anchorSegments: Object.freeze(["FT1"]) })
879
893
  ])
880
894
  }),
881
- // ── VXU^V04 (CDC IG): immunization update — PID is Required. RXA is
895
+ // ── VXU^V04 (CDC IG): immunization update: PID is Required. RXA is
882
896
  // excluded: it lives in the Optional order group of the CDC IG, so a
883
897
  // query-shaped VXU with no administered vaccine must not warn.
884
898
  Object.freeze({
@@ -888,7 +902,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
888
902
  Object.freeze({ name: "patient", anchorSegments: Object.freeze(["PID"]) })
889
903
  ])
890
904
  }),
891
- // ── ACK (Ch. 2): a general acknowledgment — MSA (message acknowledgment) is
905
+ // ── ACK (Ch. 2): a general acknowledgment: MSA (message acknowledgment) is
892
906
  // Required. Matched on message code alone (no trigger event in MSH-9.2).
893
907
  Object.freeze({
894
908
  messageCode: "ACK",
@@ -1018,7 +1032,7 @@ function parsePath(path) {
1018
1032
  }
1019
1033
  if (i !== path.length) {
1020
1034
  throw new TypeError(
1021
- `Invalid HL7 dot-path: "${path}" (trailing content at position ${String(i)} \u2014 too deep or malformed).`
1035
+ `Invalid HL7 dot-path: "${path}" (trailing content at position ${String(i)}: too deep or malformed).`
1022
1036
  );
1023
1037
  }
1024
1038
  return freezeDotPath(out);
@@ -1144,6 +1158,127 @@ function emitSegment(seg, enc) {
1144
1158
  return parts.join(enc.field);
1145
1159
  }
1146
1160
 
1161
+ // src/text/render.ts
1162
+ var FORMATTING_RE = /^\.(br|sp|in|ti|fi|nf|ce|sk)[ +]?([+-]?\d+)?$/u;
1163
+ var DEFAULT_TRUNCATION_CHAR2 = "#";
1164
+ var MAX_FORMAT_REPEAT = 100;
1165
+ function normalizeBreaks(s, newline) {
1166
+ return s.replace(/\r\n|\r|\n/gu, newline);
1167
+ }
1168
+ function clampCount(arg) {
1169
+ if (arg === void 0) return 1;
1170
+ const n = Math.trunc(Number(arg));
1171
+ if (!Number.isFinite(n) || n < 1) return 1;
1172
+ return Math.min(n, MAX_FORMAT_REPEAT);
1173
+ }
1174
+ function renderText(input, enc = DEFAULT_ENCODING_CHARACTERS, opts) {
1175
+ const esc = enc.escape;
1176
+ const newline = opts?.newline ?? "\n";
1177
+ const runs = [];
1178
+ const unrendered = [];
1179
+ let highlighted = false;
1180
+ let current = "";
1181
+ const flush = () => {
1182
+ if (current !== "") {
1183
+ runs.push({ text: current, highlighted });
1184
+ current = "";
1185
+ }
1186
+ };
1187
+ let i = 0;
1188
+ const len = input.length;
1189
+ while (i < len) {
1190
+ const ch = input.charAt(i);
1191
+ if (ch === "\r" || ch === "\n") {
1192
+ current += newline;
1193
+ i += ch === "\r" && input.charAt(i + 1) === "\n" ? 2 : 1;
1194
+ continue;
1195
+ }
1196
+ if (ch !== esc) {
1197
+ current += ch;
1198
+ i++;
1199
+ continue;
1200
+ }
1201
+ const close = input.indexOf(esc, i + 1);
1202
+ if (close === -1) {
1203
+ const tail = input.slice(i);
1204
+ current += tail;
1205
+ unrendered.push(tail);
1206
+ break;
1207
+ }
1208
+ const body = input.slice(i + 1, close);
1209
+ const literal = esc + body + esc;
1210
+ if (body === "H") {
1211
+ flush();
1212
+ highlighted = true;
1213
+ } else if (body === "N") {
1214
+ flush();
1215
+ highlighted = false;
1216
+ } else {
1217
+ const decoded = decodeBody(body, enc, newline);
1218
+ if (decoded !== null) {
1219
+ current += decoded;
1220
+ } else {
1221
+ current += literal;
1222
+ unrendered.push(literal);
1223
+ }
1224
+ }
1225
+ i = close + 1;
1226
+ }
1227
+ flush();
1228
+ const text = runs.reduce((acc, r) => acc + r.text, "");
1229
+ return { text, runs, unrenderedSequences: unrendered };
1230
+ }
1231
+ function decodeBody(body, enc, newline) {
1232
+ switch (body) {
1233
+ case "F":
1234
+ return enc.field;
1235
+ case "S":
1236
+ return enc.component;
1237
+ case "T":
1238
+ return enc.subcomponent;
1239
+ case "R":
1240
+ return enc.repetition;
1241
+ case "E":
1242
+ return enc.escape;
1243
+ case "P":
1244
+ return enc.truncation ?? DEFAULT_TRUNCATION_CHAR2;
1245
+ }
1246
+ if (body.charAt(0) === ".") {
1247
+ const m = FORMATTING_RE.exec(body);
1248
+ if (m !== null) return renderFormatting(m[1] ?? "", m[2], newline);
1249
+ return null;
1250
+ }
1251
+ if (body.charAt(0) === "X") {
1252
+ const hex = body.slice(1);
1253
+ if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9A-Fa-f]+$/u.test(hex)) {
1254
+ return null;
1255
+ }
1256
+ let out = "";
1257
+ for (let j = 0; j < hex.length; j += 2) {
1258
+ out += String.fromCharCode(parseInt(hex.slice(j, j + 2), 16));
1259
+ }
1260
+ return normalizeBreaks(out, newline);
1261
+ }
1262
+ return null;
1263
+ }
1264
+ function renderFormatting(cmd, arg, newline) {
1265
+ switch (cmd) {
1266
+ case "br":
1267
+ case "ce":
1268
+ return newline;
1269
+ case "sp":
1270
+ return newline.repeat(clampCount(arg));
1271
+ case "sk":
1272
+ return " ".repeat(clampCount(arg));
1273
+ case "in":
1274
+ case "ti":
1275
+ case "fi":
1276
+ case "nf":
1277
+ default:
1278
+ return "";
1279
+ }
1280
+ }
1281
+
1147
1282
  // src/model/types/_shared.ts
1148
1283
  function readSubcomponent(component, index, _enc) {
1149
1284
  if (component === void 0) return void 0;
@@ -1722,13 +1857,13 @@ function parseXtn(rep, enc) {
1722
1857
  // src/model/field.ts
1723
1858
  var DEFAULT_POSITION = { segmentIndex: 0 };
1724
1859
  var Field = class {
1725
- /** HL7 null indicator — `true` iff the underlying field was the two-char literal `""`. */
1860
+ /** HL7 null indicator: `true` iff the underlying field was the two-char literal `""`. */
1726
1861
  isNull;
1727
1862
  /** Reference to the underlying `RawField.repetitions` (no defensive copy). */
1728
1863
  repetitions;
1729
1864
  /** The 5 encoding characters for this message. Exposed for composite parsers (Plan 04). @internal */
1730
1865
  enc;
1731
- /** Position of this field in the parent message — used for position-aware error messages. @internal */
1866
+ /** Position of this field in the parent message: used for position-aware error messages. @internal */
1732
1867
  position;
1733
1868
  /** The full `RawField` this wrapper wraps. Exposed for composite parsers (Plan 02/03/04). @internal */
1734
1869
  raw;
@@ -1746,7 +1881,7 @@ var Field = class {
1746
1881
  }
1747
1882
  /**
1748
1883
  * First-repetition, first-component, first-subcomponent value as a decoded
1749
- * string — unescaped ONCE by the tokenizer on parse, returned verbatim here
1884
+ * string: unescaped ONCE by the tokenizer on parse, returned verbatim here
1750
1885
  * (never re-unescaped, HL7-VALUE-REDECODE). Returns `""` when the field is
1751
1886
  * empty or HL7 null. Equivalent to `msg.get('SEG.N')` for a top-level access.
1752
1887
  *
@@ -1767,13 +1902,13 @@ var Field = class {
1767
1902
  return sub;
1768
1903
  }
1769
1904
  /**
1770
- * The field's **canonical wire text** — the full field re-serialized with
1905
+ * The field's **canonical wire text**: the full field re-serialized with
1771
1906
  * the active delimiters and re-escaped content (repetitions, components,
1772
1907
  * and subcomponents included). Contrast with {@link value}, which
1773
1908
  * returns only the first subcomponent of the first component of the first
1774
- * repetition (decoded once at parse — never re-unescaped).
1909
+ * repetition (decoded once at parse: never re-unescaped).
1775
1910
  *
1776
- * Use this when a field must be compared or echoed as a whole — e.g.
1911
+ * Use this when a field must be compared or echoed as a whole: e.g.
1777
1912
  * correlating an ACK's MSA-2 against the inbound MSH-10, where a
1778
1913
  * vendor-quirk control id containing an unescaped delimiter (`ID^X`) must
1779
1914
  * not be truncated to its first component.
@@ -1792,7 +1927,7 @@ var Field = class {
1792
1927
  *
1793
1928
  * **MSH-1/MSH-2 caveat.** Like {@link value}, calling this on MSH-1 or
1794
1929
  * MSH-2 (the delimiter-definition fields) re-escapes the encoding
1795
- * characters themselves and produces garbage — those two fields are only
1930
+ * characters themselves and produces garbage: those two fields are only
1796
1931
  * meaningful through `Hl7Message.encodingCharacters`.
1797
1932
  *
1798
1933
  * @example
@@ -1807,12 +1942,33 @@ var Field = class {
1807
1942
  return emitField(this.raw, this.enc);
1808
1943
  }
1809
1944
  /**
1810
- * Return a synthetic empty `Field` sentinel — used by `Segment.field(n)` to
1945
+ * Render this field's formatted text (HL7 v2 §2.7 highlight + formatting
1946
+ * escapes) into a normalized {@link RenderedText} display model: plain text
1947
+ * plus highlight-aware runs. A **read projection** over the field's
1948
+ * byte-verbatim wire {@link text}: it never mutates the raw value, and it
1949
+ * never fabricates (an unrenderable escape is preserved + flagged). Use this
1950
+ * to surface a clinical narrative (an NTE / OBX-5 note) to a human without
1951
+ * the raw `\.br\` / `\H\` sentinels.
1952
+ *
1953
+ * @param opts - see {@link RenderTextOptions} (e.g. a custom line-break string).
1954
+ * @returns the normalized display model.
1955
+ *
1956
+ * @example
1957
+ * ```ts
1958
+ * const note = msg.segments("OBX")[0]?.field(5);
1959
+ * note?.render().text; // "Specimen received.\nGross exam normal."
1960
+ * ```
1961
+ */
1962
+ render(opts) {
1963
+ return renderText(this.text, this.enc, opts);
1964
+ }
1965
+ /**
1966
+ * Return a synthetic empty `Field` sentinel: used by `Segment.field(n)` to
1811
1967
  * honor MODEL-05's "never throws on missing" contract. The returned Field
1812
1968
  * has `isNull === false`, `repetitions === []`, and `value === ""`.
1813
1969
  * Referentially stable across calls (same instance returned each time).
1814
1970
  *
1815
- * The `enc` argument is accepted for API symmetry but ignored — the
1971
+ * The `enc` argument is accepted for API symmetry but ignored: the
1816
1972
  * synthetic field carries no content, so unescape would be a no-op
1817
1973
  * regardless of the active encoding characters.
1818
1974
  *
@@ -1829,7 +1985,7 @@ var Field = class {
1829
1985
  /**
1830
1986
  * Coerce this field's first repetition to a typed `XPN` (Extended Person
1831
1987
  * Name). Absent components are OMITTED from the result
1832
- * (`exactOptionalPropertyTypes`). Not memoized in v1 — each call re-parses
1988
+ * (`exactOptionalPropertyTypes`). Not memoized in v1: each call re-parses
1833
1989
  * (D-09).
1834
1990
  *
1835
1991
  * @example
@@ -1919,7 +2075,7 @@ var Field = class {
1919
2075
  return parsePl(this.repetitions[0] ?? EMPTY_REP, this.enc);
1920
2076
  }
1921
2077
  /**
1922
- * Coerce this field's first repetition to a typed `TS` (Time Stamp) — the
2078
+ * Coerce this field's first repetition to a typed `TS` (Time Stamp): the
1923
2079
  * fidelity `DtmParts` (raw + parts + precision + timezone). `valid` is
1924
2080
  * `false` on unparseable input (no throw). Build an absolute instant only on
1925
2081
  * explicit request via `dtmToDate(ts)`.
@@ -1935,7 +2091,7 @@ var Field = class {
1935
2091
  }
1936
2092
  /**
1937
2093
  * Coerce this field's first repetition to a typed `NM` (Numeric).
1938
- * `{ raw, value }` — `value` is `undefined` on non-numeric input.
2094
+ * `{ raw, value }`: `value` is `undefined` on non-numeric input.
1939
2095
  *
1940
2096
  * @example
1941
2097
  * ```ts
@@ -2005,7 +2161,7 @@ var EMPTY_REP = Object.freeze({
2005
2161
  var Segment = class {
2006
2162
  /** Segment name (3 chars, e.g. `"PID"`, `"OBX"`, `"ZPI"`). */
2007
2163
  type;
2008
- /** Reference to the underlying `RawSegment.fields` — 1-indexed per HL7 convention. */
2164
+ /** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
2009
2165
  fields;
2010
2166
  /** The 5 encoding characters for this message. Exposed for composite parsers. @internal */
2011
2167
  enc;
@@ -2021,7 +2177,7 @@ var Segment = class {
2021
2177
  * @internal
2022
2178
  */
2023
2179
  customFields;
2024
- /** Lazy cache of Field wrappers — one per fields[] position. @internal */
2180
+ /** Lazy cache of Field wrappers: one per fields[] position. @internal */
2025
2181
  _fieldWrappers;
2026
2182
  /**
2027
2183
  * Construct a new `Segment`. Called internally by `Hl7Message`; user code
@@ -2044,16 +2200,16 @@ var Segment = class {
2044
2200
  }
2045
2201
  /**
2046
2202
  * Return the `Field` wrapper at HL7 position `n`. Indexing follows the HL7
2047
- * 1-indexed convention — `seg.field(5)` on a PID segment maps to PID-5.
2203
+ * 1-indexed convention: `seg.field(5)` on a PID segment maps to PID-5.
2048
2204
  * MSH segments use the same user-facing convention: `msh.field(1)` returns
2049
2205
  * the field-separator (MSH-1), `msh.field(2)` returns encoding chars
2050
- * (MSH-2), `msh.field(3)` returns MSH-3, and so on — the internal
2206
+ * (MSH-2), `msh.field(3)` returns MSH-3, and so on: the internal
2051
2207
  * `fields[N-1]` offset for MSH segments is applied here (mirrors the
2052
2208
  * dot-path resolver in `dot-path.ts`, keeping `msg.segments('MSH')[0].field(3)`
2053
2209
  * and `msg.get('MSH.3')` in agreement).
2054
2210
  *
2055
2211
  * Returns a synthetic empty `Field` (`.isNull === false`, `.value === ""`)
2056
- * when `n` is out of range — never throws (MODEL-05). Successive calls with
2212
+ * when `n` is out of range: never throws (MODEL-05). Successive calls with
2057
2213
  * the same `n` return the same `Field` instance (D-12).
2058
2214
  *
2059
2215
  * @example
@@ -2080,13 +2236,13 @@ var Segment = class {
2080
2236
  /**
2081
2237
  * Return the `Field` at the profile-declared position for `name`, or
2082
2238
  * `undefined` when no custom mapping exists (PROF-07). Unlike `field(n)`,
2083
- * missing names return `undefined` — NOT a synthetic empty Field — so
2239
+ * missing names return `undefined`, NOT a synthetic empty Field, so
2084
2240
  * typos surface instead of silently resolving to an empty string (D-14).
2085
2241
  *
2086
2242
  * For segments without a profile-declared customSegments slice (most
2087
2243
  * non-Z segments, and any Z-segment whose host message had no profile
2088
2244
  * applied), this method always returns `undefined` (D-15 defense-in-depth
2089
- * — D-05 already rejects standard-segment overlays at `defineProfile()`
2245
+ *: D-05 already rejects standard-segment overlays at `defineProfile()`
2090
2246
  * time).
2091
2247
  *
2092
2248
  * When the declared position is out of range for the underlying
@@ -3134,8 +3290,8 @@ function buildMeta(msg) {
3134
3290
  const out = {};
3135
3291
  const msh = msg.segments("MSH")[0];
3136
3292
  if (msh !== void 0) {
3137
- const typeField = msh.field(9);
3138
- const firstRep = typeField.repetitions[0];
3293
+ const typeField2 = msh.field(9);
3294
+ const firstRep = typeField2.repetitions[0];
3139
3295
  if (firstRep !== void 0) {
3140
3296
  const parts = [];
3141
3297
  for (let i = 0; i < firstRep.components.length; i++) {
@@ -3454,7 +3610,7 @@ function emitJson(msg) {
3454
3610
  name: seg.name,
3455
3611
  fields: seg.fields.map((field) => ({
3456
3612
  // D-17 + Claude's-Discretion: `repetitions` is ALWAYS `[]` when the
3457
- // field has zero repetitions — shape-stable, no conditional omission.
3613
+ // field has zero repetitions: shape-stable, no conditional omission.
3458
3614
  repetitions: field.repetitions.map((rep) => ({
3459
3615
  components: rep.components.map((comp) => ({
3460
3616
  subcomponents: comp.subcomponents.slice()
@@ -3473,7 +3629,7 @@ function emitJson(msg) {
3473
3629
  },
3474
3630
  segments,
3475
3631
  // D-19: ALWAYS present, even when empty. `msg.warnings` is already frozen
3476
- // at Hl7Message construction (Phase 2) — pass-through is safe.
3632
+ // at Hl7Message construction (Phase 2): pass-through is safe.
3477
3633
  warnings: msg.warnings
3478
3634
  };
3479
3635
  if (msg.profile !== void 0) {
@@ -3514,6 +3670,205 @@ function emitMshSegment(seg, enc) {
3514
3670
  return "MSH" + enc.field + msh2 + enc.field + tail;
3515
3671
  }
3516
3672
 
3673
+ // src/builder/encode-composite.ts
3674
+ function sc(value) {
3675
+ return { subcomponents: [value ?? ""] };
3676
+ }
3677
+ function hdComponent(hd) {
3678
+ if (hd === void 0) return { subcomponents: [] };
3679
+ const subs = [hd.namespaceId ?? "", hd.universalId ?? "", hd.universalIdType ?? ""];
3680
+ while (subs.length > 0 && subs[subs.length - 1] === "") subs.pop();
3681
+ return { subcomponents: subs };
3682
+ }
3683
+ function isEmptyComponent(c) {
3684
+ return c.subcomponents.every((s) => s === "");
3685
+ }
3686
+ function toField(components) {
3687
+ const trimmed = components.slice();
3688
+ while (trimmed.length > 0) {
3689
+ const last = trimmed[trimmed.length - 1];
3690
+ if (last !== void 0 && isEmptyComponent(last)) trimmed.pop();
3691
+ else break;
3692
+ }
3693
+ if (trimmed.length === 0) return { repetitions: [], isNull: false };
3694
+ return { repetitions: [{ components: trimmed }], isNull: false };
3695
+ }
3696
+ function encodeXpn(v) {
3697
+ return toField([
3698
+ sc(v.familyName),
3699
+ sc(v.givenName),
3700
+ sc(v.secondName),
3701
+ sc(v.suffix),
3702
+ sc(v.prefix),
3703
+ sc(v.degree),
3704
+ sc(v.nameTypeCode),
3705
+ sc(v.nameRepresentationCode),
3706
+ sc(v.nameContext),
3707
+ sc(v.nameValidityRange),
3708
+ sc(v.nameAssemblyOrder),
3709
+ sc(v.effectiveDate),
3710
+ sc(v.expirationDate),
3711
+ sc(v.professionalSuffix)
3712
+ ]);
3713
+ }
3714
+ function encodeXad(v) {
3715
+ return toField([
3716
+ sc(v.street),
3717
+ sc(v.otherDesignation),
3718
+ sc(v.city),
3719
+ sc(v.stateOrProvince),
3720
+ sc(v.zipOrPostalCode),
3721
+ sc(v.country),
3722
+ sc(v.addressType),
3723
+ sc(v.otherGeographicDesignation),
3724
+ sc(v.countyParishCode),
3725
+ sc(v.censusTract),
3726
+ sc(v.addressRepresentationCode),
3727
+ sc(v.addressValidityRange)
3728
+ ]);
3729
+ }
3730
+ function encodeCx(v) {
3731
+ return toField([
3732
+ sc(v.idNumber),
3733
+ sc(v.checkDigit),
3734
+ sc(v.checkDigitScheme),
3735
+ hdComponent(v.assigningAuthority),
3736
+ sc(v.identifierTypeCode),
3737
+ sc(v.assigningFacility),
3738
+ sc(v.effectiveDate),
3739
+ sc(v.expirationDate),
3740
+ sc(v.assigningJurisdiction),
3741
+ sc(v.assigningAgencyOrDepartment)
3742
+ ]);
3743
+ }
3744
+ function encodeCwe(v) {
3745
+ const components = [
3746
+ sc(v.identifier),
3747
+ sc(v.text),
3748
+ sc(v.nameOfCodingSystem),
3749
+ sc(v.alternateIdentifier),
3750
+ sc(v.alternateText),
3751
+ sc(v.nameOfAlternateCodingSystem),
3752
+ sc(v.codingSystemVersionId),
3753
+ sc(v.alternateCodingSystemVersionId),
3754
+ sc(v.originalText)
3755
+ ];
3756
+ for (const extra of v.extraComponents ?? []) components.push(sc(extra));
3757
+ return toField(components);
3758
+ }
3759
+ function encodeCe(v) {
3760
+ const components = [
3761
+ sc(v.identifier),
3762
+ sc(v.text),
3763
+ sc(v.nameOfCodingSystem),
3764
+ sc(v.alternateIdentifier),
3765
+ sc(v.alternateText),
3766
+ sc(v.nameOfAlternateCodingSystem)
3767
+ ];
3768
+ for (const extra of v.extraComponents ?? []) components.push(sc(extra));
3769
+ return toField(components);
3770
+ }
3771
+ function encodeXtn(v) {
3772
+ return toField([
3773
+ sc(v.telephoneNumber),
3774
+ sc(v.telecommunicationUseCode),
3775
+ sc(v.telecommunicationEquipmentType),
3776
+ sc(v.emailAddress),
3777
+ sc(v.countryCode),
3778
+ sc(v.areaCityCode),
3779
+ sc(v.localNumber),
3780
+ sc(v.extension),
3781
+ sc(v.anyText),
3782
+ sc(v.extensionPrefix),
3783
+ sc(v.speedDialCode),
3784
+ sc(v.unformattedTelephoneNumber)
3785
+ ]);
3786
+ }
3787
+ function encodePl(v) {
3788
+ return toField([
3789
+ sc(v.pointOfCare),
3790
+ sc(v.room),
3791
+ sc(v.bed),
3792
+ hdComponent(v.facility),
3793
+ sc(v.locationStatus),
3794
+ sc(v.personLocationType),
3795
+ sc(v.building),
3796
+ sc(v.floor),
3797
+ sc(v.locationDescription),
3798
+ sc(v.comprehensiveLocationId),
3799
+ sc(v.assigningAuthorityForLocation)
3800
+ ]);
3801
+ }
3802
+ function encodeHd(v) {
3803
+ return toField([sc(v.namespaceId), sc(v.universalId), sc(v.universalIdType)]);
3804
+ }
3805
+ function encodeTs(v) {
3806
+ const raw = typeof v === "string" ? v : v.raw;
3807
+ return toField([sc(raw)]);
3808
+ }
3809
+ function encodeNm(v) {
3810
+ let raw;
3811
+ if (typeof v === "number") raw = String(v);
3812
+ else if (typeof v === "string") raw = v;
3813
+ else raw = v.raw;
3814
+ return toField([sc(raw)]);
3815
+ }
3816
+ function encodeXcn(v) {
3817
+ return toField([
3818
+ sc(v.idNumber),
3819
+ sc(v.familyName),
3820
+ sc(v.givenName),
3821
+ sc(v.secondName),
3822
+ sc(v.suffix),
3823
+ sc(v.prefix),
3824
+ sc(v.degree),
3825
+ sc(v.sourceTable),
3826
+ hdComponent(v.assigningAuthority),
3827
+ sc(v.nameTypeCode),
3828
+ sc(v.identifierCheckDigit),
3829
+ sc(v.checkDigitScheme),
3830
+ sc(v.identifierTypeCode)
3831
+ ]);
3832
+ }
3833
+ function encodeComposite(kind, value) {
3834
+ switch (kind) {
3835
+ case "XPN":
3836
+ return encodeXpn(value);
3837
+ case "XAD":
3838
+ return encodeXad(value);
3839
+ case "CX":
3840
+ return encodeCx(value);
3841
+ case "CWE":
3842
+ return encodeCwe(value);
3843
+ case "CE":
3844
+ return encodeCe(value);
3845
+ case "XTN":
3846
+ return encodeXtn(value);
3847
+ case "PL":
3848
+ return encodePl(value);
3849
+ case "TS":
3850
+ return encodeTs(value);
3851
+ case "NM":
3852
+ return encodeNm(value);
3853
+ case "HD":
3854
+ return encodeHd(value);
3855
+ case "XCN":
3856
+ return encodeXcn(value);
3857
+ default: {
3858
+ const never = kind;
3859
+ throw new TypeError(`encodeComposite: unknown composite kind ${JSON.stringify(never)}.`);
3860
+ }
3861
+ }
3862
+ }
3863
+ function encodeCompositeReps(kind, values) {
3864
+ const repetitions = [];
3865
+ for (const value of values) {
3866
+ const encoded = encodeComposite(kind, value);
3867
+ for (const rep of encoded.repetitions) repetitions.push(rep);
3868
+ }
3869
+ return { repetitions, isNull: false };
3870
+ }
3871
+
3517
3872
  // src/model/message.ts
3518
3873
  var SEGMENT_NAME_RE2 = /^[A-Z][A-Z0-9]{2}$/u;
3519
3874
  function toMutableArray(arr) {
@@ -3523,7 +3878,7 @@ var Hl7Message = class {
3523
3878
  /**
3524
3879
  * Raw positional tree produced by the parser. 1-indexed per HL7 convention
3525
3880
  * (`fields[0]` is the segment-name / MSH separator placeholder slot). Use
3526
- * `segments(type)` / `allSegments()` for typed wrapper access — this field
3881
+ * `segments(type)` / `allSegments()` for typed wrapper access: this field
3527
3882
  * is exposed for advanced callers that need the raw tree directly (e.g.
3528
3883
  * future serialization phase 5).
3529
3884
  */
@@ -3533,7 +3888,7 @@ var Hl7Message = class {
3533
3888
  warnings;
3534
3889
  profile;
3535
3890
  /**
3536
- * Merged `dateFormats` list — `options.dateFormats ++ profile.dateFormats`
3891
+ * Merged `dateFormats` list: `options.dateFormats ++ profile.dateFormats`
3537
3892
  * deduped first-occurrence per D-21. Empty array when neither source
3538
3893
  * supplied any formats. Exposed publicly so helpers (`msg.meta.timestamp`)
3539
3894
  * and advanced callers can introspect the active cascade.
@@ -3563,14 +3918,14 @@ var Hl7Message = class {
3563
3918
  _allSegments;
3564
3919
  /**
3565
3920
  * Lazily built `Meta` view (Phase 4 D-02 memoization). Dropped wholesale
3566
- * by `invalidateCaches`. `Meta` is always defined (D-03 — MSH absence
3921
+ * by `invalidateCaches`. `Meta` is always defined (D-03: MSH absence
3567
3922
  * throws `NO_MSH_SEGMENT` at parse time) so no null-sentinel is needed.
3568
3923
  * @internal
3569
3924
  */
3570
3925
  _meta;
3571
3926
  /**
3572
3927
  * Lazily built `MessageStructure` view (Phase G). Dropped wholesale by
3573
- * `invalidateCaches`. Always defined — `analyzeMessageStructure` returns an
3928
+ * `invalidateCaches`. Always defined: `analyzeMessageStructure` returns an
3574
3929
  * `recognized: false` summary for unmodelled types rather than absence.
3575
3930
  * @internal
3576
3931
  */
@@ -3607,9 +3962,9 @@ var Hl7Message = class {
3607
3962
  }
3608
3963
  /**
3609
3964
  * Resolve a dot-path (e.g. `PID.5.1`, `OBX[2].5`, `PID.3[0].1`) to its
3610
- * decoded leaf string (unescaped once at parse — never re-unescaped on read,
3965
+ * decoded leaf string (unescaped once at parse: never re-unescaped on read,
3611
3966
  * HL7-VALUE-REDECODE). Returns `undefined` when the path doesn't
3612
- * resolve — never throws on missing path (MODEL-05). Throws `TypeError`
3967
+ * resolve: never throws on missing path (MODEL-05). Throws `TypeError`
3613
3968
  * on malformed path syntax (e.g. `"pid.5"`, empty string).
3614
3969
  *
3615
3970
  * @example
@@ -3618,7 +3973,7 @@ var Hl7Message = class {
3618
3973
  * msg.get("PID.5.1"); // "Smith"
3619
3974
  * msg.get("OBX[2].5"); // third OBX's 5th field
3620
3975
  * msg.get("NOT.9.9"); // undefined
3621
- * msg.get("MSH.12"); // "2.5" — HL7 version string
3976
+ * msg.get("MSH.12"); // "2.5": HL7 version string
3622
3977
  * ```
3623
3978
  */
3624
3979
  get(path) {
@@ -3627,7 +3982,7 @@ var Hl7Message = class {
3627
3982
  /**
3628
3983
  * Return every `Segment` of `segmentType` in document order. Returns `[]`
3629
3984
  * (empty array, NEVER `undefined`) when no segment of that type exists
3630
- * (MODEL-02). Alias for `segments(segmentType)` — shares the same cache.
3985
+ * (MODEL-02). Alias for `segments(segmentType)`: shares the same cache.
3631
3986
  *
3632
3987
  * @example
3633
3988
  * ```ts
@@ -3693,7 +4048,7 @@ var Hl7Message = class {
3693
4048
  }
3694
4049
  /**
3695
4050
  * MSH-derived message metadata (type, controlId, timestamp, version, etc.).
3696
- * D-01: plain object. D-02: memoized — `msg.meta === msg.meta` across
4051
+ * D-01: plain object. D-02: memoized: `msg.meta === msg.meta` across
3697
4052
  * reads until mutation invalidates. D-03: always defined (MSH absence
3698
4053
  * throws `NO_MSH_SEGMENT` at parse time).
3699
4054
  *
@@ -3709,10 +4064,10 @@ var Hl7Message = class {
3709
4064
  }
3710
4065
  /**
3711
4066
  * Structural-conformance summary for the common message types (roadmap
3712
- * Phase G) — a misroute/truncation safety net, NOT a conformance validator.
4067
+ * Phase G): a misroute/truncation safety net, NOT a conformance validator.
3713
4068
  * Reports, per the message's recognized (MSH-9.1, MSH-9.2) type, which
3714
4069
  * Required segment groups are present and which are entirely absent
3715
- * (`missingGroups` — the same set the parser flags as
4070
+ * (`missingGroups`: the same set the parser flags as
3716
4071
  * `MISSING_EXPECTED_GROUP` warnings). For an unmodelled type, `recognized`
3717
4072
  * is `false` and `missingGroups` is empty. D-02: memoized.
3718
4073
  *
@@ -3727,14 +4082,14 @@ var Hl7Message = class {
3727
4082
  }
3728
4083
  /**
3729
4084
  * PID-derived patient view, or `undefined` when no PID segment exists
3730
- * (D-04). D-02: memoized. HELPERS-07: never throws — absent fields
4085
+ * (D-04). D-02: memoized. HELPERS-07: never throws: absent fields
3731
4086
  * surface as `undefined` on the returned `Patient` object.
3732
4087
  *
3733
4088
  * @example
3734
4089
  * ```ts
3735
4090
  * console.log(msg.patient?.mrn);
3736
4091
  * console.log(msg.patient?.fullName);
3737
- * console.log(msg.patient?.dateOfBirth?.raw); // fidelity TS — e.g. "19800115"
4092
+ * console.log(msg.patient?.dateOfBirth?.raw); // fidelity TS: e.g. "19800115"
3738
4093
  * ```
3739
4094
  */
3740
4095
  get patient() {
@@ -3762,7 +4117,7 @@ var Hl7Message = class {
3762
4117
  }
3763
4118
  /**
3764
4119
  * Every OBX segment as a typed Observation in document order. D-05:
3765
- * returns `[]` when no OBX present. D-06: NOT memoized — each call
4120
+ * returns `[]` when no OBX present. D-06: NOT memoized: each call
3766
4121
  * re-walks `rawSegments`. Value type is discriminated per D-13.
3767
4122
  *
3768
4123
  * @example
@@ -3777,7 +4132,7 @@ var Hl7Message = class {
3777
4132
  }
3778
4133
  /**
3779
4134
  * Every OBR as an Order with its OBX children grouped positionally (D-12) and
3780
- * its TQ1 / legacy embedded-TQ (ORC-7) `timings` (Phase M — the repeat pattern
4135
+ * its TQ1 / legacy embedded-TQ (ORC-7) `timings` (Phase M: the repeat pattern
3781
4136
  * surfaced verbatim, never resolved to a schedule). D-05: returns `[]` when no
3782
4137
  * OBR present. D-06: not memoized.
3783
4138
  *
@@ -3785,7 +4140,7 @@ var Hl7Message = class {
3785
4140
  * ```ts
3786
4141
  * for (const order of msg.orders()) {
3787
4142
  * console.log(order.placerOrderNumber, order.observations.length);
3788
- * for (const t of order.timings) console.log(t.repeatPattern?.code); // e.g. "Q6H" — verbatim
4143
+ * for (const t of order.timings) console.log(t.repeatPattern?.code); // e.g. "Q6H": verbatim
3789
4144
  * }
3790
4145
  * ```
3791
4146
  */
@@ -3793,11 +4148,11 @@ var Hl7Message = class {
3793
4148
  return orders(this);
3794
4149
  }
3795
4150
  /**
3796
- * Message-level NTE notes (Phase P) — every NTE segment with no recognized
4151
+ * Message-level NTE notes (Phase P): every NTE segment with no recognized
3797
4152
  * preceding parent (not immediately following a `PID`, `ORC`, `OBR`, or
3798
4153
  * `OBX`), surfaced verbatim in document order so nothing is dropped. Notes
3799
4154
  * that DO attach to a specific patient / order / result are exposed on those
3800
- * helper outputs (`msg.patient?.notes`, `order.notes`, `observation.notes`) —
4155
+ * helper outputs (`msg.patient?.notes`, `order.notes`, `observation.notes`),
3801
4156
  * not here. D-05: returns `[]` when there are none. D-06: NOT memoized.
3802
4157
  *
3803
4158
  * @example
@@ -3810,7 +4165,7 @@ var Hl7Message = class {
3810
4165
  }
3811
4166
  /**
3812
4167
  * Every recognized ADT patient-identity event (merge / move / link /
3813
- * unlink / person add/update — roadmap Phase K), with the MRG-sourced
4168
+ * unlink / person add/update: roadmap Phase K), with the MRG-sourced
3814
4169
  * `prior` and PID/PV1-sourced `surviving` parties labelled by role and the
3815
4170
  * spec-constant `direction: "MRG_TO_PID"` on merge/move events. Returns
3816
4171
  * `[]` when the trigger event is not in the identity family. D-06: not
@@ -3837,7 +4192,7 @@ var Hl7Message = class {
3837
4192
  * and never reconciled (Phase D §4).
3838
4193
  *
3839
4194
  * Each medication also carries its TQ1 / legacy embedded-TQ (RXE-1) `timings`
3840
- * (Phase M — repeat pattern verbatim, never resolved to a schedule).
4195
+ * (Phase M: repeat pattern verbatim, never resolved to a schedule).
3841
4196
  *
3842
4197
  * @example
3843
4198
  * ```ts
@@ -3858,7 +4213,7 @@ var Hl7Message = class {
3858
4213
  * returns `[]` when no RXA present. D-06: not memoized. The vaccine code
3859
4214
  * carries its own provenance; the action code (RXA-21) is surfaced verbatim
3860
4215
  * and `recordOrigin` (administered vs historical) is derived only from the
3861
- * well-known NIP001 RXA-9.1 codes — never guessed.
4216
+ * well-known NIP001 RXA-9.1 codes: never guessed.
3862
4217
  *
3863
4218
  * @example
3864
4219
  * ```ts
@@ -3928,7 +4283,7 @@ var Hl7Message = class {
3928
4283
  * status (Table 0278), SCH-11 start/end timing, and the resource groups
3929
4284
  * (service / general / location / personnel). D-05: returns `[]` when no SCH
3930
4285
  * is present. D-06: not memoized. Never throws (HELPERS-07). Not a
3931
- * scheduling-workflow state machine — see the package known-limitations.
4286
+ * scheduling-workflow state machine: see the package known-limitations.
3932
4287
  *
3933
4288
  * @example
3934
4289
  * ```ts
@@ -3945,7 +4300,7 @@ var Hl7Message = class {
3945
4300
  * Every TXA of an MDM message as a typed `ClinicalDocument` (roadmap Phase Q),
3946
4301
  * with the OBX narrative body grouped positionally under that TXA. The
3947
4302
  * completion status (TXA-17) and availability status (TXA-19) are surfaced as
3948
- * **distinct** fields and never conflated — a document can be available before
4303
+ * **distinct** fields and never conflated: a document can be available before
3949
4304
  * it is authenticated, and reading a preliminary document as final is the
3950
4305
  * harm. D-05: returns `[]` when no TXA is present. D-06: not memoized. Never
3951
4306
  * throws (HELPERS-07).
@@ -3965,7 +4320,7 @@ var Hl7Message = class {
3965
4320
  * Every FT1 of a DFT message as a typed `Charge` (roadmap Phase Q), one per
3966
4321
  * FT1 in document order. Surfaces billing-critical fields (FT1-6 transaction
3967
4322
  * type, FT1-7 code, FT1-11/12 extended/unit amount, FT1-19 diagnosis linkage)
3968
- * with **no billing logic and no money-as-float** — amounts are the verbatim
4323
+ * with **no billing logic and no money-as-float**: amounts are the verbatim
3969
4324
  * CP wire text. D-05: returns `[]` when no FT1 is present. D-06: not memoized.
3970
4325
  * Never throws (HELPERS-07).
3971
4326
  *
@@ -3986,7 +4341,7 @@ var Hl7Message = class {
3986
4341
  * D-05; MSH-1 and MSH-2 are inlined verbatim from
3987
4342
  * `this.encodingCharacters` per D-06; every field string passes through
3988
4343
  * `reescape` per D-04. `RawField.isNull === true` is preserved as the
3989
- * HL7 literal `""` (D-02). Pure — never warns, never throws (D-07).
4344
+ * HL7 literal `""` (D-02). Pure: never warns, never throws (D-07).
3990
4345
  *
3991
4346
  * @example
3992
4347
  * ```ts
@@ -4004,7 +4359,7 @@ var Hl7Message = class {
4004
4359
  * Re-walks `rawSegments` on every call (D-30 no caching). Mirrors the
4005
4360
  * raw tree one-for-one, preserves `isNull`, always includes
4006
4361
  * `warnings: []`, and includes `profile: { name, lineage }` only when
4007
- * `this.profile` is truthy (D-19/D-20). Pure — never warns, never throws.
4362
+ * `this.profile` is truthy (D-19/D-20). Pure: never warns, never throws.
4008
4363
  *
4009
4364
  * @example
4010
4365
  * ```ts
@@ -4023,16 +4378,16 @@ var Hl7Message = class {
4023
4378
  * debugging (SER-04). Single opinionated format (D-22 no options):
4024
4379
  * header line with type / controlId / timestamp / segment count, then
4025
4380
  * one line per segment with labeled `[N]=value` fields (D-23). Composite
4026
- * values render as their raw HL7 string — depth stops at field level
4027
- * (D-24). Pure — never warns, never throws (D-26).
4381
+ * values render as their raw HL7 string: depth stops at field level
4382
+ * (D-24). Pure: never warns, never throws (D-26).
4028
4383
  *
4029
4384
  * **Field values render as their raw HL7 string representation.**
4030
- * Embedded delimiters in user data appear as escape sequences — e.g.
4385
+ * Embedded delimiters in user data appear as escape sequences: e.g.
4031
4386
  * a patient family name containing `|` renders as `Smith\F\Jones`
4032
4387
  * (NOT `Smith|Jones`). This preserves round-trip fidelity: copy-pasting
4033
4388
  * prettyPrint output into `parseHL7` yields a structurally equivalent
4034
4389
  * message. For un-escaped human display, parse the composite first via
4035
- * typed accessors (e.g. `msg.patient?.familyName`) — those return
4390
+ * typed accessors (e.g. `msg.patient?.familyName`): those return
4036
4391
  * already-decoded strings.
4037
4392
  *
4038
4393
  * @example
@@ -4052,13 +4407,13 @@ var Hl7Message = class {
4052
4407
  * Set the string value at a dot-path. Mutates the underlying tree and
4053
4408
  * returns `this` for chaining (D-15). Auto-creates missing repetitions,
4054
4409
  * components, and subcomponents WITHIN an existing field, but does NOT
4055
- * auto-create segments — callers must `addSegment` first (throws
4410
+ * auto-create segments: callers must `addSegment` first (throws
4056
4411
  * `TypeError` with an actionable message otherwise).
4057
4412
  *
4058
4413
  * The value is accepted verbatim: unescaped delimiter characters are NOT
4059
4414
  * rejected on input (D-18). Re-escaping on serialize is Phase 5's concern.
4060
4415
  *
4061
- * MSH-1 / MSH-2 follow the user-facing HL7 convention — `setField("MSH.3", ...)`
4416
+ * MSH-1 / MSH-2 follow the user-facing HL7 convention: `setField("MSH.3", ...)`
4062
4417
  * targets MSH-3 (sending application), matching `msg.get("MSH.3")`.
4063
4418
  *
4064
4419
  * Segment/Field wrapper caches are invalidated wholesale on success (D-17).
@@ -4095,7 +4450,7 @@ var Hl7Message = class {
4095
4450
  const mutSegments = toMutableArray(this.rawSegments);
4096
4451
  const seg = mutSegments[segIdx];
4097
4452
  if (seg === void 0) {
4098
- throw new Error("setField: internal invariant \u2014 segment disappeared after lookup.");
4453
+ throw new Error("setField: internal invariant, segment disappeared after lookup.");
4099
4454
  }
4100
4455
  const mutFields = toMutableArray(seg.fields);
4101
4456
  while (mutFields.length <= rawFieldIndex) {
@@ -4103,7 +4458,7 @@ var Hl7Message = class {
4103
4458
  }
4104
4459
  const field = mutFields[rawFieldIndex];
4105
4460
  if (field === void 0) {
4106
- throw new Error("setField: internal invariant \u2014 field missing after growth.");
4461
+ throw new Error("setField: internal invariant, field missing after growth.");
4107
4462
  }
4108
4463
  const mutReps = toMutableArray(field.repetitions);
4109
4464
  const repIdx = parsed.repetitionIndex ?? 0;
@@ -4112,7 +4467,7 @@ var Hl7Message = class {
4112
4467
  }
4113
4468
  const rep = mutReps[repIdx];
4114
4469
  if (rep === void 0) {
4115
- throw new Error("setField: internal invariant \u2014 repetition missing after growth.");
4470
+ throw new Error("setField: internal invariant, repetition missing after growth.");
4116
4471
  }
4117
4472
  const mutComps = toMutableArray(rep.components);
4118
4473
  const compIdx = (parsed.componentIndex ?? 1) - 1;
@@ -4121,7 +4476,7 @@ var Hl7Message = class {
4121
4476
  }
4122
4477
  const comp = mutComps[compIdx];
4123
4478
  if (comp === void 0) {
4124
- throw new Error("setField: internal invariant \u2014 component missing after growth.");
4479
+ throw new Error("setField: internal invariant, component missing after growth.");
4125
4480
  }
4126
4481
  const mutSubs = toMutableArray(comp.subcomponents);
4127
4482
  const subIdx = (parsed.subcomponentIndex ?? 1) - 1;
@@ -4141,9 +4496,95 @@ var Hl7Message = class {
4141
4496
  this.invalidateCaches();
4142
4497
  return this;
4143
4498
  }
4499
+ /**
4500
+ * Set a **typed composite** at a field (or field-repetition) dot-path,
4501
+ * roadmap Phase T, the conservative-emit mirror of the typed read accessors
4502
+ * (`asXpn`/`asCx`/…). The caller passes a structured value (an XPN name, a CX
4503
+ * identifier, a TS timestamp, …) by its {@link CompositeKind}, and the setter
4504
+ * encodes it into a spec-clean field using the HL7-R encode-safe path: any
4505
+ * delimiter embedded in a component value is **escaped, never injected**, so
4506
+ * a `familyName` of `"Smith^Jr"` re-parses to exactly that string rather than
4507
+ * forging a component boundary. No hand-assembly of `^`/`&`/`~`.
4508
+ *
4509
+ * The path must resolve to a **field** (`"PID.5"`) or a specific
4510
+ * **repetition** of a field (`"PID.11[1]"`). A component/subcomponent-level
4511
+ * path (`"PID.5.1"`) is rejected with `TypeError`: a composite occupies a
4512
+ * whole field, not a single component. Like {@link setField}, the target
4513
+ * segment must already exist (`addSegment` first); the repetition defaults to
4514
+ * index 0 and other repetitions of the field are preserved.
4515
+ *
4516
+ * Never fabricates: an omitted optional composite field encodes to an
4517
+ * empty/absent component, never a defaulted value; an all-empty composite
4518
+ * clears the field. Segment/helper caches are invalidated on success; the
4519
+ * frozen `warnings` array is untouched.
4520
+ *
4521
+ * @example
4522
+ * ```ts
4523
+ * const msg = buildMessage({ type: "ADT^A01" }).addSegment("PID", [""]);
4524
+ * msg.setComposite("PID.5", "XPN", { familyName: "Smith", givenName: "Ann" });
4525
+ * msg.setComposite("PID.3", "CX", { idNumber: "MRN001", identifierTypeCode: "MR" });
4526
+ * msg.setComposite("PID.7", "TS", "19880705");
4527
+ * msg.get("PID.5.1"); // "Smith"
4528
+ * ```
4529
+ */
4530
+ setComposite(path, kind, value) {
4531
+ const parsed = parsePath(path);
4532
+ if (parsed.componentIndex !== void 0 || parsed.subcomponentIndex !== void 0) {
4533
+ throw new TypeError(
4534
+ `setComposite: path "${path}" targets a component/subcomponent, but a typed composite occupies a whole field. Use a field path (e.g. "PID.5") or a field-repetition path (e.g. "PID.11[1]").`
4535
+ );
4536
+ }
4537
+ let seen = 0;
4538
+ let segIdx = -1;
4539
+ for (let i = 0; i < this.rawSegments.length; i++) {
4540
+ const s = this.rawSegments[i];
4541
+ if (s === void 0) continue;
4542
+ if (s.name === parsed.segmentType) {
4543
+ if (seen === parsed.segmentIndex) {
4544
+ segIdx = i;
4545
+ break;
4546
+ }
4547
+ seen++;
4548
+ }
4549
+ }
4550
+ if (segIdx === -1) {
4551
+ throw new TypeError(
4552
+ `setComposite: segment "${parsed.segmentType}" (occurrence ${String(parsed.segmentIndex)}) not found. Add it first with addSegment("${parsed.segmentType}", [...]).`
4553
+ );
4554
+ }
4555
+ const encoded = encodeComposite(kind, value);
4556
+ const encodedRep = encoded.repetitions[0] ?? { components: [] };
4557
+ const rawFieldIndex = parsed.segmentType === "MSH" ? parsed.fieldIndex - 1 : parsed.fieldIndex;
4558
+ const mutSegments = toMutableArray(this.rawSegments);
4559
+ const seg = mutSegments[segIdx];
4560
+ if (seg === void 0) {
4561
+ throw new Error("setComposite: internal invariant, segment disappeared after lookup.");
4562
+ }
4563
+ const mutFields = toMutableArray(seg.fields);
4564
+ while (mutFields.length <= rawFieldIndex) {
4565
+ mutFields.push({ repetitions: [], isNull: false });
4566
+ }
4567
+ const field = mutFields[rawFieldIndex];
4568
+ if (field === void 0) {
4569
+ throw new Error("setComposite: internal invariant, field missing after growth.");
4570
+ }
4571
+ const mutReps = toMutableArray(field.repetitions);
4572
+ const repIdx = parsed.repetitionIndex ?? 0;
4573
+ while (mutReps.length <= repIdx) {
4574
+ mutReps.push({ components: [] });
4575
+ }
4576
+ mutReps[repIdx] = encodedRep;
4577
+ const newField = { repetitions: mutReps, isNull: false };
4578
+ mutFields[rawFieldIndex] = newField;
4579
+ const newSeg = { name: seg.name, fields: mutFields };
4580
+ mutSegments[segIdx] = newSeg;
4581
+ this.rawSegments = mutSegments;
4582
+ this.invalidateCaches();
4583
+ return this;
4584
+ }
4144
4585
  /**
4145
4586
  * Append a new segment to the end of the message. `name` must match
4146
- * `/^(?:[A-Z]{3}|Z[A-Z0-9]{2})$/u` — throws `TypeError` otherwise (D-19).
4587
+ * `/^(?:[A-Z]{3}|Z[A-Z0-9]{2})$/u`: throws `TypeError` otherwise (D-19).
4147
4588
  *
4148
4589
  * `fields` is interpreted in HL7 1-indexed terms: `addSegment("NTE", [a, b, c])`
4149
4590
  * produces a segment whose `NTE-1 = a`, `NTE-2 = b`, `NTE-3 = c`. The
@@ -4193,14 +4634,14 @@ var Hl7Message = class {
4193
4634
  /**
4194
4635
  * Remove segments by type + occurrence or by type + all. Call shapes:
4195
4636
  *
4196
- * - `removeSegment("NTE")` — remove the FIRST NTE (occurrence 0).
4197
- * - `removeSegment("OBX", 1)` — remove the SECOND OBX (0-indexed per D-01).
4198
- * - `removeSegment("OBX", { all: true })` — remove ALL OBX segments.
4637
+ * - `removeSegment("NTE")`: remove the FIRST NTE (occurrence 0).
4638
+ * - `removeSegment("OBX", 1)`: remove the SECOND OBX (0-indexed per D-01).
4639
+ * - `removeSegment("OBX", { all: true })`: remove ALL OBX segments.
4199
4640
  *
4200
- * `MSH` is protected — `removeSegment("MSH")` throws `TypeError` (every
4641
+ * `MSH` is protected: `removeSegment("MSH")` throws `TypeError` (every
4201
4642
  * HL7 message must retain its MSH segment). Unknown segment types are a
4202
4643
  * no-op (idempotent; no throw). Segment name must match the D-19 shape
4203
- * regex — invalid shapes throw `TypeError` for symmetry with `addSegment`.
4644
+ * regex: invalid shapes throw `TypeError` for symmetry with `addSegment`.
4204
4645
  *
4205
4646
  * Invalidates caches on return; warnings untouched (D-16).
4206
4647
  *
@@ -4378,9 +4819,9 @@ function extractVersion(msh) {
4378
4819
  }
4379
4820
  function extractMessageType(msh) {
4380
4821
  if (msh === void 0 || msh.name !== "MSH") return { messageCode: "", triggerEvent: "" };
4381
- const typeField = msh.fields[8];
4382
- if (typeField === void 0) return { messageCode: "", triggerEvent: "" };
4383
- const firstRep = typeField.repetitions[0];
4822
+ const typeField2 = msh.fields[8];
4823
+ if (typeField2 === void 0) return { messageCode: "", triggerEvent: "" };
4824
+ const firstRep = typeField2.repetitions[0];
4384
4825
  if (firstRep === void 0) return { messageCode: "", triggerEvent: "" };
4385
4826
  const messageCode = firstRep.components[0]?.subcomponents[0] ?? "";
4386
4827
  const triggerEvent = firstRep.components[1]?.subcomponents[0] ?? "";
@@ -4512,6 +4953,24 @@ function parseHL7(raw, optionsOrProfile) {
4512
4953
  return new Hl7Message(init);
4513
4954
  }
4514
4955
 
4956
+ // src/text/codec.ts
4957
+ function ignore() {
4958
+ }
4959
+ function decodeText(input, enc = DEFAULT_ENCODING_CHARACTERS) {
4960
+ return unescape(input, enc, ignore, { segmentIndex: 0 });
4961
+ }
4962
+ function encodeText(input, enc = DEFAULT_ENCODING_CHARACTERS) {
4963
+ return reescape(input, enc);
4964
+ }
4965
+
4966
+ // src/text/index.ts
4967
+ var text_exports = {};
4968
+ __export(text_exports, {
4969
+ decode: () => decodeText,
4970
+ encode: () => encodeText,
4971
+ render: () => renderText
4972
+ });
4973
+
4515
4974
  // src/model/types/namespace.ts
4516
4975
  var namespace_exports = {};
4517
4976
  var ALNUM_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -4627,6 +5086,247 @@ function compositeField(typeString) {
4627
5086
  };
4628
5087
  }
4629
5088
 
5089
+ // src/builder/segment-fields.ts
5090
+ function scalarField2(value) {
5091
+ if (value === void 0 || value === "") return { repetitions: [], isNull: false };
5092
+ return { repetitions: [{ components: [{ subcomponents: [value] }] }], isNull: false };
5093
+ }
5094
+ function absentField2() {
5095
+ return { repetitions: [], isNull: false };
5096
+ }
5097
+ function typeField(typeString) {
5098
+ const components = typeString.split("^").map((p) => ({ subcomponents: [p] }));
5099
+ return { repetitions: [{ components }], isNull: false };
5100
+ }
5101
+ function resolveTimestamp2(ts) {
5102
+ if (ts === void 0) return formatHl7Timestamp(/* @__PURE__ */ new Date());
5103
+ if (typeof ts === "string") return ts;
5104
+ return formatHl7Timestamp(ts);
5105
+ }
5106
+ function buildMshSegment(type, envelope) {
5107
+ const enc = DEFAULT_ENCODING_CHARACTERS;
5108
+ const fields = [
5109
+ scalarField2(enc.field),
5110
+ // fields[0] MSH-1 field separator placeholder
5111
+ scalarField2(
5112
+ enc.component + enc.repetition + enc.escape + enc.subcomponent + (enc.truncation ?? "")
5113
+ ),
5114
+ // MSH-2 encoding characters
5115
+ scalarField2(envelope.sendingApp),
5116
+ // MSH-3
5117
+ scalarField2(envelope.sendingFacility),
5118
+ // MSH-4
5119
+ scalarField2(envelope.receivingApp),
5120
+ // MSH-5
5121
+ scalarField2(envelope.receivingFacility),
5122
+ // MSH-6
5123
+ scalarField2(resolveTimestamp2(envelope.timestamp)),
5124
+ // MSH-7
5125
+ absentField2(),
5126
+ // MSH-8
5127
+ typeField(type),
5128
+ // MSH-9
5129
+ scalarField2(envelope.controlId ?? generateControlId()),
5130
+ // MSH-10
5131
+ scalarField2(envelope.processingId ?? "P"),
5132
+ // MSH-11
5133
+ scalarField2(envelope.version ?? "2.5")
5134
+ // MSH-12
5135
+ ];
5136
+ return { name: "MSH", fields };
5137
+ }
5138
+ function assembleSegment(name, positions) {
5139
+ let max = 0;
5140
+ for (const [pos, field] of positions) {
5141
+ if (field !== void 0 && pos > max) max = pos;
5142
+ }
5143
+ const fields = [absentField2()];
5144
+ for (let i = 1; i <= max; i++) {
5145
+ fields.push(positions.get(i) ?? absentField2());
5146
+ }
5147
+ return { name, fields };
5148
+ }
5149
+
5150
+ // src/builder/build-adt.ts
5151
+ function repField(kind, value) {
5152
+ if (value === void 0) return { repetitions: [], isNull: false };
5153
+ if (Array.isArray(value)) {
5154
+ return encodeCompositeReps(kind, value);
5155
+ }
5156
+ return encodeComposite(kind, value);
5157
+ }
5158
+ function buildAdt(event, init) {
5159
+ if (typeof event !== "string" || event.trim().length === 0) {
5160
+ throw new TypeError(
5161
+ `buildAdt: \`event\` (the ADT trigger, e.g. "A01") is required and must be a non-empty string. Received: ${JSON.stringify(event)}.`
5162
+ );
5163
+ }
5164
+ if (init === null || init === void 0 || init.patient === void 0) {
5165
+ throw new TypeError(
5166
+ "buildAdt: `patient` is required. An ADT patient event is never emitted with a fabricated patient. Supply at least one identifier or a name."
5167
+ );
5168
+ }
5169
+ const enc = DEFAULT_ENCODING_CHARACTERS;
5170
+ const version = init.version ?? "2.5";
5171
+ const segments = [buildMshSegment(`ADT^${event}`, init)];
5172
+ const evn = init.event;
5173
+ segments.push(
5174
+ assembleSegment(
5175
+ "EVN",
5176
+ /* @__PURE__ */ new Map([
5177
+ [1, scalarField2(event)],
5178
+ [
5179
+ 2,
5180
+ evn?.recordedDateTime !== void 0 ? encodeComposite("TS", evn.recordedDateTime) : void 0
5181
+ ],
5182
+ [
5183
+ 6,
5184
+ evn?.eventOccurred !== void 0 ? encodeComposite("TS", evn.eventOccurred) : void 0
5185
+ ]
5186
+ ])
5187
+ )
5188
+ );
5189
+ const p = init.patient;
5190
+ segments.push(
5191
+ assembleSegment(
5192
+ "PID",
5193
+ /* @__PURE__ */ new Map([
5194
+ [1, p.setId !== void 0 ? scalarField2(p.setId) : void 0],
5195
+ [3, p.identifiers !== void 0 ? repField("CX", p.identifiers) : void 0],
5196
+ [5, p.name !== void 0 ? repField("XPN", p.name) : void 0],
5197
+ [
5198
+ 6,
5199
+ p.mothersMaidenName !== void 0 ? encodeComposite("XPN", p.mothersMaidenName) : void 0
5200
+ ],
5201
+ [7, p.birthDateTime !== void 0 ? encodeComposite("TS", p.birthDateTime) : void 0],
5202
+ [8, p.administrativeSex !== void 0 ? scalarField2(p.administrativeSex) : void 0],
5203
+ [11, p.address !== void 0 ? repField("XAD", p.address) : void 0],
5204
+ [13, p.phoneHome !== void 0 ? repField("XTN", p.phoneHome) : void 0],
5205
+ [18, p.accountNumber !== void 0 ? encodeComposite("CX", p.accountNumber) : void 0]
5206
+ ])
5207
+ )
5208
+ );
5209
+ const v = init.visit;
5210
+ segments.push(
5211
+ assembleSegment(
5212
+ "PV1",
5213
+ /* @__PURE__ */ new Map([
5214
+ [1, v?.setId !== void 0 ? scalarField2(v.setId) : void 0],
5215
+ [2, v?.patientClass !== void 0 ? scalarField2(v.patientClass) : void 0],
5216
+ [
5217
+ 3,
5218
+ v?.assignedLocation !== void 0 ? encodeComposite("PL", v.assignedLocation) : void 0
5219
+ ],
5220
+ [7, v?.attendingDoctor !== void 0 ? repField("XCN", v.attendingDoctor) : void 0],
5221
+ [8, v?.referringDoctor !== void 0 ? repField("XCN", v.referringDoctor) : void 0],
5222
+ [19, v?.visitNumber !== void 0 ? encodeComposite("CX", v.visitNumber) : void 0],
5223
+ [44, v?.admitDateTime !== void 0 ? encodeComposite("TS", v.admitDateTime) : void 0]
5224
+ ])
5225
+ )
5226
+ );
5227
+ return new Hl7Message({
5228
+ segments,
5229
+ encodingCharacters: enc,
5230
+ version,
5231
+ warnings: []
5232
+ });
5233
+ }
5234
+
5235
+ // src/builder/build-oru.ts
5236
+ function repField2(kind, value) {
5237
+ if (value === void 0) return { repetitions: [], isNull: false };
5238
+ if (Array.isArray(value)) {
5239
+ return encodeCompositeReps(kind, value);
5240
+ }
5241
+ return encodeComposite(kind, value);
5242
+ }
5243
+ function buildOru(init) {
5244
+ if (init === null || init === void 0 || init.patient === void 0) {
5245
+ throw new TypeError(
5246
+ "buildOru: `patient` is required. An ORU is never emitted with a fabricated subject."
5247
+ );
5248
+ }
5249
+ if (init.observations === void 0 || init.observations.length === 0) {
5250
+ throw new TypeError(
5251
+ "buildOru: at least one `observations` entry (OBX) is required. An ORU with no result is a typed error, never a fabricated observation."
5252
+ );
5253
+ }
5254
+ const enc = DEFAULT_ENCODING_CHARACTERS;
5255
+ const version = init.version ?? "2.5";
5256
+ const segments = [buildMshSegment("ORU^R01", init)];
5257
+ const p = init.patient;
5258
+ segments.push(
5259
+ assembleSegment(
5260
+ "PID",
5261
+ /* @__PURE__ */ new Map([
5262
+ [1, p.setId !== void 0 ? scalarField2(p.setId) : void 0],
5263
+ [3, p.identifiers !== void 0 ? repField2("CX", p.identifiers) : void 0],
5264
+ [5, p.name !== void 0 ? repField2("XPN", p.name) : void 0],
5265
+ [
5266
+ 6,
5267
+ p.mothersMaidenName !== void 0 ? encodeComposite("XPN", p.mothersMaidenName) : void 0
5268
+ ],
5269
+ [7, p.birthDateTime !== void 0 ? encodeComposite("TS", p.birthDateTime) : void 0],
5270
+ [8, p.administrativeSex !== void 0 ? scalarField2(p.administrativeSex) : void 0],
5271
+ [11, p.address !== void 0 ? repField2("XAD", p.address) : void 0],
5272
+ [13, p.phoneHome !== void 0 ? repField2("XTN", p.phoneHome) : void 0],
5273
+ [18, p.accountNumber !== void 0 ? encodeComposite("CX", p.accountNumber) : void 0]
5274
+ ])
5275
+ )
5276
+ );
5277
+ const o = init.order;
5278
+ segments.push(
5279
+ assembleSegment(
5280
+ "OBR",
5281
+ /* @__PURE__ */ new Map([
5282
+ [1, o?.setId !== void 0 ? scalarField2(o.setId) : void 0],
5283
+ [2, o?.placerOrderNumber !== void 0 ? scalarField2(o.placerOrderNumber) : void 0],
5284
+ [3, o?.fillerOrderNumber !== void 0 ? scalarField2(o.fillerOrderNumber) : void 0],
5285
+ [
5286
+ 4,
5287
+ o?.universalServiceId !== void 0 ? encodeComposite("CWE", o.universalServiceId) : void 0
5288
+ ],
5289
+ [
5290
+ 7,
5291
+ o?.observationDateTime !== void 0 ? encodeComposite("TS", o.observationDateTime) : void 0
5292
+ ],
5293
+ [16, o?.orderingProvider !== void 0 ? repField2("XCN", o.orderingProvider) : void 0],
5294
+ [25, o?.resultStatus !== void 0 ? scalarField2(o.resultStatus) : void 0]
5295
+ ])
5296
+ )
5297
+ );
5298
+ for (const obs of init.observations) {
5299
+ segments.push(
5300
+ assembleSegment(
5301
+ "OBX",
5302
+ /* @__PURE__ */ new Map([
5303
+ [1, obs.setId !== void 0 ? scalarField2(obs.setId) : void 0],
5304
+ [2, obs.valueType !== void 0 ? scalarField2(obs.valueType) : void 0],
5305
+ [3, obs.identifier !== void 0 ? encodeComposite("CWE", obs.identifier) : void 0],
5306
+ [5, obs.value !== void 0 ? scalarField2(obs.value) : void 0],
5307
+ [6, obs.units !== void 0 ? encodeComposite("CWE", obs.units) : void 0],
5308
+ [7, obs.referenceRange !== void 0 ? scalarField2(obs.referenceRange) : void 0],
5309
+ [8, obs.abnormalFlags !== void 0 ? scalarField2(obs.abnormalFlags) : void 0],
5310
+ [
5311
+ 11,
5312
+ obs.observationResultStatus !== void 0 ? scalarField2(obs.observationResultStatus) : void 0
5313
+ ],
5314
+ [
5315
+ 14,
5316
+ obs.observationDateTime !== void 0 ? encodeComposite("TS", obs.observationDateTime) : void 0
5317
+ ]
5318
+ ])
5319
+ )
5320
+ );
5321
+ }
5322
+ return new Hl7Message({
5323
+ segments,
5324
+ encodingCharacters: enc,
5325
+ version,
5326
+ warnings: []
5327
+ });
5328
+ }
5329
+
4630
5330
  // src/builder/ack-tables.ts
4631
5331
  var ACK_CODES = {
4632
5332
  AA: "AA",
@@ -4709,7 +5409,7 @@ function buildAck(inbound, options) {
4709
5409
  const enc = DEFAULT_ENCODING_CHARACTERS;
4710
5410
  const meta = inbound.meta;
4711
5411
  const msh = inbound.segments("MSH")[0];
4712
- const inboundMsh = (n) => msh?.field(n).raw ?? absentField2();
5412
+ const inboundMsh = (n) => msh?.field(n).raw ?? absentField3();
4713
5413
  const sameEnc = encodingsEqual(inbound.encodingCharacters, enc);
4714
5414
  const rawControlId = sameEnc ? inboundMsh(10) : stripEscapeOverlay(inboundMsh(10));
4715
5415
  const hasCorrelation = rawFieldHasContent(rawControlId);
@@ -4718,8 +5418,8 @@ function buildAck(inbound, options) {
4718
5418
  const version = meta.version ?? "2.5";
4719
5419
  const processingId = meta.processingId ?? "P";
4720
5420
  const mshFields = [
4721
- scalarField2(enc.field),
4722
- scalarField2(
5421
+ scalarField3(enc.field),
5422
+ scalarField3(
4723
5423
  enc.component + enc.repetition + enc.escape + enc.subcomponent + (enc.truncation ?? "")
4724
5424
  ),
4725
5425
  inboundMsh(5),
@@ -4730,26 +5430,26 @@ function buildAck(inbound, options) {
4730
5430
  // MSH-5 receivingApp = inbound MSH-3 (sendingApp)
4731
5431
  inboundMsh(4),
4732
5432
  // MSH-6 receivingFacility = inbound MSH-4 (sendingFacility)
4733
- scalarField2(formatHl7Timestamp(/* @__PURE__ */ new Date())),
5433
+ scalarField3(formatHl7Timestamp(/* @__PURE__ */ new Date())),
4734
5434
  // MSH-7
4735
- absentField2(),
5435
+ absentField3(),
4736
5436
  // MSH-8
4737
5437
  compositeField2(ackType),
4738
5438
  // MSH-9
4739
- scalarField2(generateControlId()),
5439
+ scalarField3(generateControlId()),
4740
5440
  // MSH-10
4741
- scalarField2(processingId),
5441
+ scalarField3(processingId),
4742
5442
  // MSH-11
4743
- scalarField2(version)
5443
+ scalarField3(version)
4744
5444
  // MSH-12
4745
5445
  ];
4746
5446
  const segments = [{ name: "MSH", fields: mshFields }];
4747
5447
  segments.push({
4748
5448
  name: "MSA",
4749
5449
  fields: [
4750
- absentField2(),
4751
- scalarField2(emittedCode),
4752
- hasCorrelation ? rawControlId : absentField2()
5450
+ absentField3(),
5451
+ scalarField3(emittedCode),
5452
+ hasCorrelation ? rawControlId : absentField3()
4753
5453
  ]
4754
5454
  });
4755
5455
  for (const detail of normalizeErrors(options.error)) {
@@ -4789,24 +5489,24 @@ function buildErrSegment(detail) {
4789
5489
  return {
4790
5490
  name: "ERR",
4791
5491
  fields: [
4792
- absentField2(),
4793
- // fields[0] — segment-name/separator placeholder slot
4794
- absentField2(),
5492
+ absentField3(),
5493
+ // fields[0]: segment-name/separator placeholder slot
5494
+ absentField3(),
4795
5495
  // ERR-1 (deprecated v2.4 / withdrawn v2.7)
4796
- detail.location !== void 0 ? compositeField2(detail.location) : absentField2(),
5496
+ detail.location !== void 0 ? compositeField2(detail.location) : absentField3(),
4797
5497
  // ERR-2 (ERL)
4798
5498
  err3,
4799
5499
  // ERR-3
4800
- scalarField2(severity)
5500
+ scalarField3(severity)
4801
5501
  // ERR-4
4802
5502
  ]
4803
5503
  };
4804
5504
  }
4805
- function scalarField2(value) {
5505
+ function scalarField3(value) {
4806
5506
  if (value === "") return { repetitions: [], isNull: false };
4807
5507
  return { repetitions: [{ components: [{ subcomponents: [value] }] }], isNull: false };
4808
5508
  }
4809
- function absentField2() {
5509
+ function absentField3() {
4810
5510
  return { repetitions: [], isNull: false };
4811
5511
  }
4812
5512
  function encodingsEqual(a, b) {
@@ -5066,7 +5766,7 @@ function validateCustomSegments(map, profileName) {
5066
5766
  for (const key of Object.keys(map)) {
5067
5767
  if (!Z_SEGMENT_RE.test(key)) {
5068
5768
  throw new ProfileDefinitionError(
5069
- `Profile '${profileName}' declares customSegments for '${key}' \u2014 only Z-segments (Z[A-Z0-9]{2}) are allowed in v1. Typed overlays are a v2 feature.`,
5769
+ `Profile '${profileName}' declares customSegments for '${key}': only Z-segments (Z[A-Z0-9]{2}) are allowed in v1. Typed overlays are a v2 feature.`,
5070
5770
  profileName
5071
5771
  );
5072
5772
  }
@@ -5093,7 +5793,7 @@ function validateDateFormats(formats, profileName) {
5093
5793
  const f = formats[i] ?? "";
5094
5794
  if (!TOKEN_MATCH_RE.test(f)) {
5095
5795
  throw new ProfileDefinitionError(
5096
- `Profile '${profileName}' dateFormats[${String(i)}] = ${JSON.stringify(f)} is malformed \u2014 must contain at least one of ${SUPPORTED_DATE_TOKENS.join("/")}.`,
5796
+ `Profile '${profileName}' dateFormats[${String(i)}] = ${JSON.stringify(f)} is malformed: must contain at least one of ${SUPPORTED_DATE_TOKENS.join("/")}.`,
5097
5797
  profileName
5098
5798
  );
5099
5799
  }
@@ -5151,7 +5851,7 @@ function defineProfile(opts) {
5151
5851
  // src/profiles/athena.ts
5152
5852
  var athena = defineProfile({
5153
5853
  name: "athena",
5154
- description: "athenahealth Interop \u2014 US-short-date format and care-team Z-segment",
5854
+ description: "athenahealth Interop: US-short-date format and care-team Z-segment",
5155
5855
  dateFormats: ["MM/DD/YYYY"],
5156
5856
  customSegments: {
5157
5857
  ZCA: { fields: { careTeamRole: 3, providerId: 5, providerName: 6 } }
@@ -5161,7 +5861,7 @@ var athena = defineProfile({
5161
5861
  // src/profiles/cerner.ts
5162
5862
  var cerner = defineProfile({
5163
5863
  name: "cerner",
5164
- description: "Cerner Millennium \u2014 ISO-style timestamps and common Z-segments",
5864
+ description: "Cerner Millennium: ISO-style timestamps and common Z-segments",
5165
5865
  dateFormats: ["YYYY-MM-DDTHH:mm:ss", "YYYY-MM-DD"],
5166
5866
  customSegments: {
5167
5867
  ZDS: { fields: { summaryText: 3 } },
@@ -5172,7 +5872,7 @@ var cerner = defineProfile({
5172
5872
  // src/profiles/epic.ts
5173
5873
  var epic = defineProfile({
5174
5874
  name: "epic",
5175
- description: "Epic Bridges Interconnect \u2014 ADT-style date formats and common Z-segments",
5875
+ description: "Epic Bridges Interconnect: ADT-style date formats and common Z-segments",
5176
5876
  dateFormats: ["MM/DD/YYYY HH:mm:ss", "MM/DD/YYYY"],
5177
5877
  customSegments: {
5178
5878
  ZDP: { fields: { departmentCode: 3, departmentName: 4 } },
@@ -5183,7 +5883,7 @@ var epic = defineProfile({
5183
5883
  // src/profiles/genericLab.ts
5184
5884
  var genericLab = defineProfile({
5185
5885
  name: "genericLab",
5186
- description: "Generic reference-laboratory \u2014 ASTM-era + ISO-date formats + lab Z-segments",
5886
+ description: "Generic reference-laboratory: ASTM-era + ISO-date formats + lab Z-segments",
5187
5887
  dateFormats: ["YYYYMMDD HHmm", "YYYY-MM-DD"],
5188
5888
  customSegments: {
5189
5889
  ZLB: { fields: { specimenOverride: 3, methodOverride: 5 } },
@@ -5194,10 +5894,122 @@ var genericLab = defineProfile({
5194
5894
  // src/profiles/meditech.ts
5195
5895
  var meditech = defineProfile({
5196
5896
  name: "meditech",
5197
- description: "Meditech MAGIC/Expanse \u2014 minute-precision timestamps and visit-info Z-segment",
5897
+ description: "MEDITECH MAGIC/6.x/Expanse: minute-precision timestamps and DFT charge Z-segments (ZF1/ZF2)",
5198
5898
  dateFormats: ["YYYYMMDDHHmm"],
5199
5899
  customSegments: {
5200
- ZVI: { fields: { visitReason: 3, admitSource: 5 } }
5900
+ ZF1: {
5901
+ fields: {
5902
+ providerEncounter: 1,
5903
+ misServiceGroup: 2,
5904
+ serviceGroupCopay: 3,
5905
+ visitCopay: 4,
5906
+ copayMinimum: 5,
5907
+ copayMaximum: 6
5908
+ }
5909
+ },
5910
+ ZF2: {
5911
+ fields: {
5912
+ setId: 1,
5913
+ providerEncounter: 2,
5914
+ encounterDate: 3,
5915
+ encounterProcedure: 4,
5916
+ encounterProcedureQuantity: 5,
5917
+ encounterProcedureCharge: 6,
5918
+ prvProcedureAmountPaid: 7,
5919
+ prvProcedureAmountDue: 8
5920
+ }
5921
+ }
5922
+ }
5923
+ });
5924
+
5925
+ // src/profiles/philips.ts
5926
+ var philips = defineProfile({
5927
+ name: "philips",
5928
+ description: "Philips Vue PACS (IS Link) imaging: ZDS/ZLK/ZAO/ZEB/ZAP/ZAV order/patient/visit-filler Z-segments",
5929
+ customSegments: {
5930
+ // §5.11 ZDS: Order Filler: DICOM Study Instance UID (RP, comp 1).
5931
+ ZDS: { fields: { studyInstanceUid: 1 } },
5932
+ // §5.12 ZLK: Order Filler: study/order linking ids.
5933
+ ZLK: { fields: { externalWorkitemId: 1, orderLinkId: 2 } },
5934
+ // §5.13 ZAO: Order Filler: order additional details (note: no field 7).
5935
+ ZAO: {
5936
+ fields: {
5937
+ orderAdditionalDetails: 1,
5938
+ orderWithNoImages: 2,
5939
+ modality: 3,
5940
+ bodyPart: 4,
5941
+ resultTransferStatus: 5,
5942
+ departmentId: 6,
5943
+ device: 8,
5944
+ section: 9,
5945
+ orderCustomString1: 10,
5946
+ orderCustomString2: 11,
5947
+ orderCustomString3: 12,
5948
+ orderCustomString4: 13,
5949
+ orderCustomString5: 14,
5950
+ orderCustomString6: 15,
5951
+ orderCustomString7: 16,
5952
+ orderCustomNumber1: 17,
5953
+ orderCustomNumber2: 18,
5954
+ orderCustomDate1: 19,
5955
+ orderCustomDate2: 20,
5956
+ orderCustomDate3: 21,
5957
+ technicianFamilyName: 22,
5958
+ technicianGivenName: 23,
5959
+ technicianMiddleName: 24,
5960
+ technicianId: 25,
5961
+ radiologistFamilyName: 26,
5962
+ radiologistGivenName: 27,
5963
+ radiologistMiddleName: 28,
5964
+ radiologistId: 29,
5965
+ orderCreatedBy: 30,
5966
+ orderUpdatedBy: 31,
5967
+ acquisitionStatus: 32
5968
+ }
5969
+ },
5970
+ // §5.14 ZEB: Patient Filler: encrypted patient-info blob (PHI-bearing).
5971
+ ZEB: { fields: { encryptedPatientInfo: 1 } },
5972
+ // §5.15 ZAP: Patient Filler: patient additional details (note: no field 2).
5973
+ ZAP: {
5974
+ fields: {
5975
+ patientAdditionalDetails: 1,
5976
+ patientCustomString1: 3,
5977
+ patientCustomString2: 4,
5978
+ patientCustomString3: 5,
5979
+ patientCustomString4: 6,
5980
+ patientCustomString5: 7,
5981
+ patientCustomString6: 8,
5982
+ patientCustomString7: 9,
5983
+ patientCustomNumber1: 10,
5984
+ patientCustomNumber2: 11,
5985
+ patientCustomDate1: 12,
5986
+ patientCustomDate2: 13,
5987
+ patientCustomDate3: 14
5988
+ }
5989
+ },
5990
+ // §5.16 ZAV: Visit Filler: visit additional details.
5991
+ ZAV: { fields: { visitAdditionalDetails: 1 } }
5992
+ }
5993
+ });
5994
+
5995
+ // src/profiles/va.ts
5996
+ var va = defineProfile({
5997
+ name: "va",
5998
+ description: "VA VistA Radiology/Nuclear Medicine imaging: ZDS DICOM Study Instance UID segment (ORM + ORU, IHE RAD)",
5999
+ customSegments: {
6000
+ // ZDS-1 = Study Instance UID (RP), UID in the first component (1.1 Pointer).
6001
+ // VA Radiology/Nuclear Medicine 5.0 HL7 IS v3.6 (June 2024): "ZDS Segment
6002
+ // Fields in ORU and ORM".
6003
+ ZDS: { fields: { studyInstanceUid: 1 } }
6004
+ }
6005
+ });
6006
+
6007
+ // src/profiles/visage.ts
6008
+ var visage = defineProfile({
6009
+ name: "visage",
6010
+ description: "Visage 7 imaging/PACS: ZDS DICOM Study Instance UID segment (IHE RAD)",
6011
+ customSegments: {
6012
+ ZDS: { fields: { studyInstanceUid: 1 } }
5201
6013
  }
5202
6014
  });
5203
6015
 
@@ -5207,7 +6019,10 @@ var profiles = Object.freeze({
5207
6019
  cerner,
5208
6020
  epic,
5209
6021
  genericLab,
5210
- meditech
6022
+ meditech,
6023
+ philips,
6024
+ va,
6025
+ visage
5211
6026
  });
5212
6027
 
5213
6028
  // src/model/coding-system.ts
@@ -5439,9 +6254,561 @@ function splitBatch(raw, optionsOrProfile) {
5439
6254
  return Object.freeze(result);
5440
6255
  }
5441
6256
 
6257
+ // src/conformance/types.ts
6258
+ var USAGE_CODES = Object.freeze(["R", "RE", "C", "CE", "O", "X"]);
6259
+ var FINDING_CODES = {
6260
+ /** A Required (`R`) segment or field is absent (or present-but-empty). */
6261
+ PROFILE_REQUIRED_ABSENT: "PROFILE_REQUIRED_ABSENT",
6262
+ /** A Not-permitted (`X`) segment or field is present. */
6263
+ PROFILE_NOT_PERMITTED: "PROFILE_NOT_PERMITTED",
6264
+ /** A segment-occurrence or field-repetition count is outside its cardinality. */
6265
+ PROFILE_CARDINALITY: "PROFILE_CARDINALITY",
6266
+ /** A checked component value exceeds the declared maximum length. */
6267
+ PROFILE_LENGTH: "PROFILE_LENGTH",
6268
+ /** A checked component value is not a member of the consumer-supplied value set. */
6269
+ PROFILE_VALUE_NOT_IN_SET: "PROFILE_VALUE_NOT_IN_SET",
6270
+ /** The profile ITSELF is structurally malformed (a diagnostic, not a message finding). */
6271
+ PROFILE_MALFORMED: "PROFILE_MALFORMED"
6272
+ };
6273
+
6274
+ // src/conformance/findings.ts
6275
+ function describeLocus(locus) {
6276
+ let s = locus.segment;
6277
+ if (locus.occurrence !== void 0 && locus.occurrence > 0) {
6278
+ s += `[occurrence ${String(locus.occurrence)}]`;
6279
+ }
6280
+ if (locus.field !== void 0) {
6281
+ s += `-${String(locus.field)}`;
6282
+ }
6283
+ if (locus.repetition !== void 0) {
6284
+ s += ` rep ${String(locus.repetition)}`;
6285
+ }
6286
+ if (locus.component !== void 0) {
6287
+ s += ` component ${String(locus.component)}`;
6288
+ }
6289
+ return s;
6290
+ }
6291
+ function requiredAbsent(locus, severity) {
6292
+ const kind = locus.field === void 0 ? "segment" : "field";
6293
+ return {
6294
+ code: FINDING_CODES.PROFILE_REQUIRED_ABSENT,
6295
+ severity,
6296
+ locus,
6297
+ message: `Required ${kind} ${describeLocus(locus)} is absent or empty (usage R).`
6298
+ };
6299
+ }
6300
+ function notPermitted(locus, severity) {
6301
+ const kind = locus.field === void 0 ? "segment" : "field";
6302
+ return {
6303
+ code: FINDING_CODES.PROFILE_NOT_PERMITTED,
6304
+ severity,
6305
+ locus,
6306
+ message: `${kind === "segment" ? "Segment" : "Field"} ${describeLocus(
6307
+ locus
6308
+ )} is present but the profile marks it not-permitted (usage X).`
6309
+ };
6310
+ }
6311
+ function cardinality(locus, actual, bound, unit, severity) {
6312
+ const parts = [];
6313
+ if (bound.min !== void 0) parts.push(`min ${String(bound.min)}`);
6314
+ if (bound.max !== void 0) parts.push(`max ${String(bound.max)}`);
6315
+ return {
6316
+ code: FINDING_CODES.PROFILE_CARDINALITY,
6317
+ severity,
6318
+ locus,
6319
+ message: `${describeLocus(locus)} has ${String(actual)} ${unit}(s), outside the declared cardinality (${parts.join(
6320
+ ", "
6321
+ )}).`
6322
+ };
6323
+ }
6324
+ function length(locus, actualLength, maxLength, severity) {
6325
+ return {
6326
+ code: FINDING_CODES.PROFILE_LENGTH,
6327
+ severity,
6328
+ locus,
6329
+ message: `${describeLocus(locus)} value length ${String(
6330
+ actualLength
6331
+ )} exceeds the declared maximum of ${String(maxLength)}.`
6332
+ };
6333
+ }
6334
+ function valueNotInSet(locus, valueSetSize, severity) {
6335
+ return {
6336
+ code: FINDING_CODES.PROFILE_VALUE_NOT_IN_SET,
6337
+ severity,
6338
+ locus,
6339
+ message: `${describeLocus(
6340
+ locus
6341
+ )} value is not in the profile value set (${String(valueSetSize)} permitted code(s)).`
6342
+ };
6343
+ }
6344
+ function malformed(locus, detail) {
6345
+ return {
6346
+ code: FINDING_CODES.PROFILE_MALFORMED,
6347
+ severity: "error",
6348
+ locus,
6349
+ message: `Malformed conformance profile: ${detail}`
6350
+ };
6351
+ }
6352
+
6353
+ // src/conformance/profile-shape.ts
6354
+ var SEGMENT_NAME_RE3 = /^[A-Z][A-Z0-9]{2}$/u;
6355
+ var SEVERITIES = ["error", "warning", "info"];
6356
+ function isObject(v) {
6357
+ return typeof v === "object" && v !== null && !Array.isArray(v);
6358
+ }
6359
+ function checkCardinality(card, locus, out) {
6360
+ if (card === void 0) return;
6361
+ if (!isObject(card)) {
6362
+ out.push({ locus, detail: `${describe(locus)} cardinality must be an object.` });
6363
+ return;
6364
+ }
6365
+ const min = card["min"];
6366
+ const max = card["max"];
6367
+ if (min !== void 0 && (typeof min !== "number" || !Number.isInteger(min) || min < 0)) {
6368
+ out.push({
6369
+ locus,
6370
+ detail: `${describe(locus)} cardinality.min must be a non-negative integer.`
6371
+ });
6372
+ }
6373
+ if (max !== void 0 && max !== "*" && (typeof max !== "number" || !Number.isInteger(max) || max < 0)) {
6374
+ out.push({
6375
+ locus,
6376
+ detail: `${describe(locus)} cardinality.max must be a non-negative integer or "*".`
6377
+ });
6378
+ }
6379
+ if (typeof min === "number" && typeof max === "number" && Number.isInteger(min) && Number.isInteger(max) && max < min) {
6380
+ out.push({
6381
+ locus,
6382
+ detail: `${describe(locus)} cardinality.max (${String(max)}) is less than min (${String(min)}).`
6383
+ });
6384
+ }
6385
+ }
6386
+ function describe(locus) {
6387
+ if (locus.segment === "(profile)") return "profile";
6388
+ let s = `segment "${locus.segment}"`;
6389
+ if (locus.field !== void 0) s += ` field ${String(locus.field)}`;
6390
+ return s;
6391
+ }
6392
+ function collectProfileDefects(profile) {
6393
+ const out = [];
6394
+ const root = { segment: "(profile)" };
6395
+ if (!isObject(profile)) {
6396
+ out.push({ locus: root, detail: "profile must be an object with `name` and `segments`." });
6397
+ return out;
6398
+ }
6399
+ if (typeof profile["name"] !== "string" || profile["name"].trim().length === 0) {
6400
+ out.push({ locus: root, detail: "profile.name must be a non-empty string." });
6401
+ }
6402
+ const segments = profile["segments"];
6403
+ if (!Array.isArray(segments)) {
6404
+ out.push({ locus: root, detail: "profile.segments must be an array of segment rules." });
6405
+ return out;
6406
+ }
6407
+ for (let i = 0; i < segments.length; i++) {
6408
+ const seg = segments[i];
6409
+ if (!isObject(seg)) {
6410
+ out.push({ locus: root, detail: `segments[${String(i)}] must be an object.` });
6411
+ continue;
6412
+ }
6413
+ const name = seg["segment"];
6414
+ if (typeof name !== "string" || !SEGMENT_NAME_RE3.test(name)) {
6415
+ out.push({
6416
+ locus: root,
6417
+ detail: `segments[${String(i)}].segment must be a valid segment name ([A-Z][A-Z0-9]{2}).`
6418
+ });
6419
+ continue;
6420
+ }
6421
+ const segLocus = { segment: name };
6422
+ checkUsage(seg["usage"], segLocus, out);
6423
+ checkSeverity(seg["severity"], segLocus, out);
6424
+ checkCardinality(seg["cardinality"], segLocus, out);
6425
+ const fields = seg["fields"];
6426
+ if (fields !== void 0) {
6427
+ if (!Array.isArray(fields)) {
6428
+ out.push({ locus: segLocus, detail: `segment "${name}" fields must be an array.` });
6429
+ continue;
6430
+ }
6431
+ for (let j = 0; j < fields.length; j++) {
6432
+ checkFieldRule(fields[j], name, j, out);
6433
+ }
6434
+ }
6435
+ }
6436
+ return out;
6437
+ }
6438
+ function checkUsage(usage, locus, out) {
6439
+ if (usage === void 0) return;
6440
+ if (typeof usage !== "string" || !USAGE_CODES.includes(usage)) {
6441
+ out.push({
6442
+ locus,
6443
+ detail: `${describe(locus)} usage must be one of ${USAGE_CODES.join("/")}.`
6444
+ });
6445
+ }
6446
+ }
6447
+ function checkSeverity(sev, locus, out) {
6448
+ if (sev === void 0) return;
6449
+ if (typeof sev !== "string" || !SEVERITIES.includes(sev)) {
6450
+ out.push({
6451
+ locus,
6452
+ detail: `${describe(locus)} severity must be one of ${SEVERITIES.join("/")}.`
6453
+ });
6454
+ }
6455
+ }
6456
+ function checkFieldRule(rule, segment, index, out) {
6457
+ const locus = { segment };
6458
+ if (!isObject(rule)) {
6459
+ out.push({ locus, detail: `segment "${segment}" fields[${String(index)}] must be an object.` });
6460
+ return;
6461
+ }
6462
+ const field = rule["field"];
6463
+ if (typeof field !== "number" || !Number.isInteger(field) || field < 1) {
6464
+ out.push({
6465
+ locus,
6466
+ detail: `segment "${segment}" fields[${String(index)}].field must be a positive (1-indexed) integer.`
6467
+ });
6468
+ return;
6469
+ }
6470
+ const fieldLocus = { segment, field };
6471
+ checkUsage(rule["usage"], fieldLocus, out);
6472
+ checkSeverity(rule["severity"], fieldLocus, out);
6473
+ checkCardinality(rule["cardinality"], fieldLocus, out);
6474
+ const len = rule["length"];
6475
+ if (len !== void 0 && (typeof len !== "number" || !Number.isInteger(len) || len < 0)) {
6476
+ out.push({
6477
+ locus: fieldLocus,
6478
+ detail: `${describe(fieldLocus)} length must be a non-negative integer.`
6479
+ });
6480
+ }
6481
+ const comp = rule["component"];
6482
+ if (comp !== void 0 && (typeof comp !== "number" || !Number.isInteger(comp) || comp < 1)) {
6483
+ out.push({
6484
+ locus: fieldLocus,
6485
+ detail: `${describe(fieldLocus)} component must be a positive (1-indexed) integer.`
6486
+ });
6487
+ }
6488
+ const vs = rule["valueSet"];
6489
+ if (vs !== void 0) {
6490
+ if (!Array.isArray(vs)) {
6491
+ out.push({
6492
+ locus: fieldLocus,
6493
+ detail: `${describe(fieldLocus)} valueSet must be an array of strings.`
6494
+ });
6495
+ } else if (!vs.every((c) => typeof c === "string")) {
6496
+ out.push({
6497
+ locus: fieldLocus,
6498
+ detail: `${describe(fieldLocus)} valueSet entries must all be strings.`
6499
+ });
6500
+ }
6501
+ }
6502
+ }
6503
+ function defineConformanceProfile(profile) {
6504
+ const defects = collectProfileDefects(profile);
6505
+ if (defects.length > 0) {
6506
+ const name = isObject(profile) && typeof profile["name"] === "string" ? profile["name"] : void 0;
6507
+ throw new ProfileDefinitionError(
6508
+ `Conformance profile has ${String(defects.length)} defect(s): ` + defects.map((d) => d.detail).join("; "),
6509
+ name
6510
+ );
6511
+ }
6512
+ return profile;
6513
+ }
6514
+
6515
+ // src/conformance/validate-against-profile.ts
6516
+ function safeName(profile) {
6517
+ if (typeof profile === "object" && profile !== null && !Array.isArray(profile)) {
6518
+ const n = profile["name"];
6519
+ if (typeof n === "string" && n.trim().length > 0) return n;
6520
+ }
6521
+ return "(unnamed profile)";
6522
+ }
6523
+ function componentValue(rep, component) {
6524
+ return rep.components[component - 1]?.subcomponents[0] ?? "";
6525
+ }
6526
+ function fieldHasValue(field) {
6527
+ return field.repetitions.some(
6528
+ (rep) => rep.components.some((comp) => comp.subcomponents.some((sub) => sub !== ""))
6529
+ );
6530
+ }
6531
+ function presenceVerdict(usage, present) {
6532
+ if (usage === "R" && !present) return "required-absent";
6533
+ if (usage === "X" && present) return "not-permitted";
6534
+ return null;
6535
+ }
6536
+ function checkCardinality2(count, card, locus, unit, severity, out) {
6537
+ if (card === void 0) return;
6538
+ const { min, max } = card;
6539
+ if (typeof max === "number" && count > max) {
6540
+ out.push(cardinality(locus, count, card, unit, severity));
6541
+ return;
6542
+ }
6543
+ if (typeof min === "number" && count > 0 && count < min) {
6544
+ out.push(cardinality(locus, count, card, unit, severity));
6545
+ }
6546
+ }
6547
+ function checkFields(seg, occurrence, fields, out) {
6548
+ for (const rule of fields) {
6549
+ const severity = rule.severity ?? "error";
6550
+ const field = seg.field(rule.field);
6551
+ const present = fieldHasValue(field);
6552
+ const baseLocus = { segment: seg.type, field: rule.field, occurrence };
6553
+ const verdict = presenceVerdict(rule.usage, present);
6554
+ if (verdict === "required-absent") {
6555
+ out.push(requiredAbsent(baseLocus, severity));
6556
+ continue;
6557
+ }
6558
+ if (verdict === "not-permitted") {
6559
+ out.push(notPermitted(baseLocus, severity));
6560
+ continue;
6561
+ }
6562
+ if (!present) continue;
6563
+ const reps = field.repetitions;
6564
+ checkCardinality2(reps.length, rule.cardinality, baseLocus, "repetition", severity, out);
6565
+ if (rule.length === void 0 && rule.valueSet === void 0) continue;
6566
+ const component = rule.component ?? 1;
6567
+ for (let r = 0; r < reps.length; r++) {
6568
+ const rep = reps[r];
6569
+ if (rep === void 0) continue;
6570
+ const value = componentValue(rep, component);
6571
+ const locus = {
6572
+ segment: seg.type,
6573
+ field: rule.field,
6574
+ component,
6575
+ repetition: r,
6576
+ occurrence
6577
+ };
6578
+ if (rule.length !== void 0 && value.length > rule.length) {
6579
+ out.push(length(locus, value.length, rule.length, severity));
6580
+ }
6581
+ if (rule.valueSet !== void 0 && value !== "" && !rule.valueSet.includes(value)) {
6582
+ out.push(valueNotInSet(locus, rule.valueSet.length, severity));
6583
+ }
6584
+ }
6585
+ }
6586
+ }
6587
+ function checkSegment(message, rule, out) {
6588
+ const severity = rule.severity ?? "error";
6589
+ const occurrences = message.segments(rule.segment);
6590
+ const count = occurrences.length;
6591
+ const present = count > 0;
6592
+ const segLocus = { segment: rule.segment };
6593
+ const verdict = presenceVerdict(rule.usage, present);
6594
+ if (verdict === "required-absent") {
6595
+ out.push(requiredAbsent(segLocus, severity));
6596
+ return;
6597
+ }
6598
+ if (verdict === "not-permitted") {
6599
+ out.push(notPermitted(segLocus, severity));
6600
+ return;
6601
+ }
6602
+ checkCardinality2(count, rule.cardinality, segLocus, "occurrence", severity, out);
6603
+ if (!present || rule.fields === void 0) return;
6604
+ for (let occ = 0; occ < occurrences.length; occ++) {
6605
+ const seg = occurrences[occ];
6606
+ if (seg === void 0) continue;
6607
+ checkFields(seg, occ, rule.fields, out);
6608
+ }
6609
+ }
6610
+ function validateAgainstProfile(message, profile) {
6611
+ const defects = collectProfileDefects(profile);
6612
+ if (defects.length > 0) {
6613
+ const findings = defects.map((d) => malformed(d.locus, d.detail));
6614
+ return Object.freeze({ profileName: safeName(profile), findings: Object.freeze(findings) });
6615
+ }
6616
+ const out = [];
6617
+ for (const rule of profile.segments) {
6618
+ checkSegment(message, rule, out);
6619
+ }
6620
+ return Object.freeze({ profileName: profile.name, findings: Object.freeze(out) });
6621
+ }
6622
+
6623
+ // src/conformance/index.ts
6624
+ var conformance_exports = {};
6625
+ __export(conformance_exports, {
6626
+ FINDING_CODES: () => FINDING_CODES,
6627
+ USAGE_CODES: () => USAGE_CODES,
6628
+ collectProfileDefects: () => collectProfileDefects,
6629
+ defineConformanceProfile: () => defineConformanceProfile,
6630
+ validateAgainstProfile: () => validateAgainstProfile
6631
+ });
6632
+ function isEnvelopeName2(name) {
6633
+ return name === "FHS" || name === "BHS" || name === "BTS" || name === "FTS";
6634
+ }
6635
+ function decodeChunk(chunk) {
6636
+ if (typeof chunk === "string") return { text: chunk, wasBuffer: false };
6637
+ if (chunk instanceof Uint8Array) {
6638
+ return { text: Buffer.from(chunk).toString("latin1"), wasBuffer: true };
6639
+ }
6640
+ throw new TypeError(
6641
+ `parseStream: each chunk must be a string, Buffer, or Uint8Array; received ${chunk === null ? "null" : typeof chunk}.`
6642
+ );
6643
+ }
6644
+ async function* iterateChunks(source) {
6645
+ if (source === null || source === void 0) {
6646
+ throw new TypeError("parseStream: source must be an async-iterable or iterable of chunks.");
6647
+ }
6648
+ const asAsync = source[Symbol.asyncIterator];
6649
+ if (typeof asAsync === "function") {
6650
+ for await (const chunk of source) yield chunk;
6651
+ return;
6652
+ }
6653
+ const asSync = source[Symbol.iterator];
6654
+ if (typeof asSync === "function") {
6655
+ for (const chunk of source) yield chunk;
6656
+ return;
6657
+ }
6658
+ throw new TypeError(
6659
+ "parseStream: source must be an async-iterable or iterable of chunks (e.g. a Node Readable)."
6660
+ );
6661
+ }
6662
+ function nextSegment(buf, cursor, final) {
6663
+ if (cursor >= buf.length) return null;
6664
+ const r = buf.indexOf("\r", cursor);
6665
+ const n = buf.indexOf("\n", cursor);
6666
+ let idx;
6667
+ let isCr;
6668
+ if (r === -1 && n === -1) return null;
6669
+ else if (r === -1) {
6670
+ idx = n;
6671
+ isCr = false;
6672
+ } else if (n === -1) {
6673
+ idx = r;
6674
+ isCr = true;
6675
+ } else if (r < n) {
6676
+ idx = r;
6677
+ isCr = true;
6678
+ } else {
6679
+ idx = n;
6680
+ isCr = false;
6681
+ }
6682
+ if (isCr && idx === buf.length - 1 && !final) return null;
6683
+ let termLen = 1;
6684
+ if (isCr && idx + 1 < buf.length && buf.charCodeAt(idx + 1) === 10) termLen = 2;
6685
+ return { seg: buf.slice(cursor, idx), next: idx + termLen };
6686
+ }
6687
+ function parseStreamEntry(rawMessage, position, wasBuffer, optionsOrProfile, streamWarnings) {
6688
+ const input = wasBuffer ? Buffer.from(rawMessage, "latin1") : rawMessage;
6689
+ try {
6690
+ const message = optionsOrProfile === void 0 ? parseHL7(input) : parseHL7(input, optionsOrProfile);
6691
+ return Object.freeze({ ok: true, message, raw: rawMessage, position, streamWarnings });
6692
+ } catch (error) {
6693
+ if (error instanceof Hl7ParseError) {
6694
+ return Object.freeze({ ok: false, error, raw: rawMessage, position, streamWarnings });
6695
+ }
6696
+ throw error;
6697
+ }
6698
+ }
6699
+ async function* parseStream(source, optionsOrProfile) {
6700
+ if (typeof source === "string" || source instanceof Uint8Array) {
6701
+ throw new TypeError(
6702
+ "parseStream expects a stream of chunks (a Node Readable, an async-iterable, or an iterable of string/Buffer chunks), not a single string or Buffer. For one message use parseHL7; to stream a whole buffer, wrap it: parseStream([buffer])."
6703
+ );
6704
+ }
6705
+ let pending = "";
6706
+ let currentLines = [];
6707
+ let messageStartIndex = -1;
6708
+ let globalSegmentIndex = 0;
6709
+ let wasBuffer = false;
6710
+ let firstChunkSeen = false;
6711
+ let bomStripped = false;
6712
+ const flush = (finalUnterminated2) => {
6713
+ while (currentLines.length > 0 && currentLines[currentLines.length - 1] === "") {
6714
+ currentLines.pop();
6715
+ }
6716
+ if (currentLines.length === 0) {
6717
+ messageStartIndex = -1;
6718
+ return null;
6719
+ }
6720
+ const position = Object.freeze({ segmentIndex: messageStartIndex });
6721
+ const streamWarnings = finalUnterminated2 ? Object.freeze([unterminatedStreamMessage(position)]) : Object.freeze([]);
6722
+ const entry = parseStreamEntry(
6723
+ currentLines.join("\r"),
6724
+ position,
6725
+ wasBuffer,
6726
+ optionsOrProfile,
6727
+ streamWarnings
6728
+ );
6729
+ currentLines = [];
6730
+ messageStartIndex = -1;
6731
+ return entry;
6732
+ };
6733
+ const processSegment = (seg) => {
6734
+ const thisIndex = globalSegmentIndex;
6735
+ globalSegmentIndex += 1;
6736
+ const name = seg.slice(0, 3);
6737
+ if (isEnvelopeName2(name)) {
6738
+ return flush(false);
6739
+ }
6740
+ if (name === "MSH") {
6741
+ const flushed = flush(false);
6742
+ currentLines = [seg];
6743
+ messageStartIndex = thisIndex;
6744
+ return flushed;
6745
+ }
6746
+ if (currentLines.length === 0) {
6747
+ if (seg.trim() === "") return null;
6748
+ messageStartIndex = thisIndex;
6749
+ }
6750
+ currentLines.push(seg);
6751
+ return null;
6752
+ };
6753
+ const tryStripBom = (final) => {
6754
+ if (bomStripped || pending.length === 0) return;
6755
+ if (pending.charCodeAt(0) === 65279) {
6756
+ pending = pending.slice(1);
6757
+ bomStripped = true;
6758
+ } else if (pending.startsWith("\xEF\xBB\xBF")) {
6759
+ pending = pending.slice(3);
6760
+ bomStripped = true;
6761
+ } else if (final || !"\xEF\xBB\xBF".startsWith(pending)) {
6762
+ bomStripped = true;
6763
+ }
6764
+ };
6765
+ for await (const rawChunk of iterateChunks(source)) {
6766
+ const { text, wasBuffer: chunkWasBuffer } = decodeChunk(rawChunk);
6767
+ if (!firstChunkSeen) {
6768
+ wasBuffer = chunkWasBuffer;
6769
+ firstChunkSeen = true;
6770
+ }
6771
+ if (text.length === 0) continue;
6772
+ pending += text;
6773
+ tryStripBom(false);
6774
+ if (!bomStripped) continue;
6775
+ let cursor = 0;
6776
+ for (; ; ) {
6777
+ const res = nextSegment(pending, cursor, false);
6778
+ if (res === null) break;
6779
+ cursor = res.next;
6780
+ const entry = processSegment(res.seg);
6781
+ if (entry !== null) yield entry;
6782
+ }
6783
+ pending = pending.slice(cursor);
6784
+ }
6785
+ tryStripBom(true);
6786
+ {
6787
+ let cursor = 0;
6788
+ for (; ; ) {
6789
+ const res = nextSegment(pending, cursor, true);
6790
+ if (res === null) break;
6791
+ cursor = res.next;
6792
+ const entry = processSegment(res.seg);
6793
+ if (entry !== null) yield entry;
6794
+ }
6795
+ pending = pending.slice(cursor);
6796
+ }
6797
+ const tail = pending;
6798
+ pending = "";
6799
+ let finalUnterminated = false;
6800
+ if (tail.length > 0) {
6801
+ const flushed = processSegment(tail);
6802
+ if (flushed !== null) yield flushed;
6803
+ finalUnterminated = currentLines.length > 0 && tail.trim() !== "";
6804
+ }
6805
+ const finalEntry = flush(finalUnterminated);
6806
+ if (finalEntry !== null) yield finalEntry;
6807
+ }
6808
+
5442
6809
  // src/index.ts
5443
- var VERSION = "0.0.1";
6810
+ var VERSION = "0.0.2";
5444
6811
 
5445
- export { ACK_CODES, ACK_CONDITIONS, BUILTIN_DATE_FALLBACKS, DEFAULT_ENCODING_CHARACTERS, ERR_CONDITION_CODES, ERR_CONDITION_CODE_SYSTEM, ERR_SEVERITIES, FATAL_CODES, Field, namespace_exports as HL7, Hl7Message, Hl7ParseError, KNOWN_CODING_SYSTEMS, KNOWN_SEGMENTS, MESSAGE_STRUCTURE_DEFINITIONS, ProfileDefinitionError, SUPPORTED_DATE_TOKENS, Segment, VERSION, WARNING_CODES, ackNoCorrelationId, alternateCodingSystemOf, analyzeMessageStructure, batchCountMismatch, batchMissingTrailer, buildAck, buildMessage, canonicalCharset, codingSystem, codingSystemOf, defineProfile, detectAckMode, downgradePositiveAck, dtmToDate, duplicateRequiredSegment, encodingMismatch, extraFields, fieldWhitespaceTrimmed, formatDtm, getDefaultProfile, interpretAck, isPositiveAck, mergeMissingPriorOrSurvivor, missingExpectedGroup, missingRequiredField, mllpFramingStripped, outOfOrderSegment, parseCe, parseCwe, parseCx, parseDtm, parseHL7, parseHd, parseNm, parsePath, parsePl, parseSn, parseTs, parseXad, parseXcn, parseXpn, parseXtn, pickMrn, profiles, reescape, resolveCharset, resolvePath, segmentCase, setDefaultProfile, splitBatch, timestampFallbackFormat, unescape, unknownCharset, unknownEscapeSequence, unknownSegment, unsupportedCharset, unterminatedEscapeSequence, versionMismatch };
6812
+ export { ACK_CODES, ACK_CONDITIONS, BUILTIN_DATE_FALLBACKS, DEFAULT_ENCODING_CHARACTERS, ERR_CONDITION_CODES, ERR_CONDITION_CODE_SYSTEM, ERR_SEVERITIES, FATAL_CODES, FINDING_CODES, Field, namespace_exports as HL7, Hl7Message, Hl7ParseError, KNOWN_CODING_SYSTEMS, KNOWN_SEGMENTS, MESSAGE_STRUCTURE_DEFINITIONS, ProfileDefinitionError, SUPPORTED_DATE_TOKENS, Segment, USAGE_CODES, VERSION, WARNING_CODES, ackNoCorrelationId, alternateCodingSystemOf, analyzeMessageStructure, batchCountMismatch, batchMissingTrailer, buildAck, buildAdt, buildMessage, buildOru, canonicalCharset, codingSystem, codingSystemOf, conformance_exports as conformance, decodeText, defineConformanceProfile, defineProfile, detectAckMode, downgradePositiveAck, dtmToDate, duplicateRequiredSegment, encodeCe, encodeComposite, encodeCompositeReps, encodeCwe, encodeCx, encodeHd, encodeNm, encodePl, encodeText, encodeTs, encodeXad, encodeXcn, encodeXpn, encodeXtn, encodingMismatch, extraFields, fieldWhitespaceTrimmed, formatDtm, getDefaultProfile, interpretAck, isPositiveAck, mergeMissingPriorOrSurvivor, missingExpectedGroup, missingRequiredField, mllpFramingStripped, outOfOrderSegment, parseCe, parseCwe, parseCx, parseDtm, parseHL7, parseHd, parseNm, parsePath, parsePl, parseSn, parseStream, parseTs, parseXad, parseXcn, parseXpn, parseXtn, pickMrn, profiles, reescape, renderText, resolveCharset, resolvePath, segmentCase, setDefaultProfile, splitBatch, text_exports as text, timestampFallbackFormat, unescape, unknownCharset, unknownEscapeSequence, unknownSegment, unsupportedCharset, unterminatedEscapeSequence, unterminatedStreamMessage, validateAgainstProfile, versionMismatch };
5446
6813
  //# sourceMappingURL=index.mjs.map
5447
6814
  //# sourceMappingURL=index.mjs.map