@rethinkhealth/hl7v2-parser 0.3.3 → 0.4.0

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/README.md CHANGED
@@ -16,7 +16,7 @@ If you want to handle syntax trees manually, use this. For an easier time proces
16
16
  - **Composable**: works directly as a unified parser plugin or as a standalone function.
17
17
  - **Streaming-friendly**: pull-based design is ready for streaming use cases.
18
18
 
19
- ## Install
19
+ ## Install
20
20
 
21
21
  This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). In Node.js (version 16+), install with npm:
22
22
 
@@ -71,6 +71,7 @@ const customTree = unified()
71
71
  ```
72
72
 
73
73
  The default delimiters are:
74
+
74
75
  - `field`: `|`
75
76
  - `component`: `^`
76
77
  - `repetition`: `~`
@@ -78,6 +79,57 @@ The default delimiters are:
78
79
  - `escape`: `\`
79
80
  - `segment`: `\r`
80
81
 
82
+ ### Experimental Features
83
+
84
+ The parser supports experimental features through the `experimental` option. These features are subject to change but provide opt-in access to upcoming behaviors.
85
+
86
+ #### Empty Array Mode
87
+
88
+ By default, the parser represents empty fields with full scaffolding (Field → FieldRepetition → Component → Subcomponent with `value: ""`). The `emptyMode: 'empty'` option changes this behavior to use empty children arrays instead, making the AST more compact and easier to work with.
89
+
90
+ ```typescript
91
+ import { unified } from 'unified';
92
+ import { hl7v2Parser } from '@rethinkhealth/hl7v2-parser';
93
+
94
+ // With empty-array mode (new behavior)
95
+ const tree = unified()
96
+ .use(hl7v2Parser, {
97
+ experimental: {
98
+ emptyMode: 'empty-array',
99
+ }
100
+ })
101
+ .parse('PID|1||');
102
+
103
+ // PID.2 (empty field) will have: { type: 'field', children: [] }
104
+ // Instead of: Field → Rep → Comp → Sub with value: ""
105
+ ```
106
+
107
+ **Spec Rules:**
108
+
109
+ - **Leaf nodes** (Subcomponent, SegmentHeader) with no value: `value: ""`
110
+ - **Parent nodes** (Field, FieldRepetition, Component) with no content: `children: []`
111
+ - **Presence vs Value**:
112
+ - Node exists in parent's children array = position exists
113
+ - Node has empty children array = no value at that position
114
+
115
+ **Examples:**
116
+
117
+ | Wire Format | Legacy Mode | Empty-Array Mode |
118
+ |--------------|------------------------------------------------|-----------------------------------------|
119
+ | `PID\|1\|\|` | Field → Rep → Comp → Sub("") | Field(children: []) |
120
+ | `PID\|1\|^\|` | Field → Rep → [Comp → Sub(""), Comp → Sub("")] | Field → Rep → [Comp[], Comp[]] |
121
+ | `PID\|1\|~\|` | Field → [Rep → Comp → Sub(""), Rep → ...] | Field → [Rep[], Rep[]] |
122
+ | `PID\|1\|ABC\|`| Field → Rep → Comp → Sub("ABC") | Field → Rep → Comp → Sub("ABC") (same) |
123
+
124
+ **Benefits:**
125
+
126
+ - 37-63% reduction in node count for messages with empty fields
127
+ - ~11% performance improvement for sparse messages
128
+ - Cleaner AST structure for visitor patterns and transformations
129
+ - Easier to distinguish "field exists but is empty" from "field has content"
130
+
131
+ **Note:** This feature will become the default behavior in future versions. The legacy mode will be deprecated.
132
+
81
133
  ## Contributing
82
134
 
83
135
  We welcome contributions! Please see our [Contributing Guide][github-contributing] for more details.
@@ -100,4 +152,4 @@ This program is licensed to you under the terms of the [MIT License](https://ope
100
152
 
101
153
  [github-code-of-conduct]: https://github.com/rethinkhealth/hl7v2/blob/main/CODE_OF_CONDUCT.md
102
154
  [github-license]: https://github.com/rethinkhealth/hl7v2/blob/main/LICENSE
103
- [github-contributing]: https://github.com/rethinkhealth/hl7v2/blob/main/CONTRIBUTING.md
155
+ [github-contributing]: https://github.com/rethinkhealth/hl7v2/blob/main/CONTRIBUTING.md
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { default as hl7v2Parser } from "./parser";
1
+ export { default as hl7v2Parser, parseHL7v2 } from "./parser";
2
2
  export * from "./preprocessor";
3
3
  export { parseHL7v2FromIterator } from "./processor";
4
4
  export * from "./tokenizer";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/parser.ts
2
+ import { loadConfig } from "@rethinkhealth/hl7v2-config";
2
3
  import { DEFAULT_DELIMITERS as DEFAULT_DELIMITERS2 } from "@rethinkhealth/hl7v2-utils";
3
4
 
4
5
  // src/preprocessor.ts
@@ -54,28 +55,29 @@ function createSubcomponent(start) {
54
55
  position: { start, end: start }
55
56
  };
56
57
  }
57
- function createComponent(start) {
58
+ function createComponent(start, mode) {
58
59
  return {
59
60
  type: "component",
60
- children: [createSubcomponent(start)],
61
+ children: mode === "legacy" ? [createSubcomponent(start)] : [],
61
62
  position: { start, end: start }
62
63
  };
63
64
  }
64
- function createFieldRepetition(start) {
65
+ function createFieldRepetition(start, mode) {
65
66
  return {
66
67
  type: "field-repetition",
67
- children: [createComponent(start)],
68
+ children: mode === "legacy" ? [createComponent(start, mode)] : [],
68
69
  position: { start, end: start }
69
70
  };
70
71
  }
71
- function createField(start) {
72
+ function createField(start, mode) {
72
73
  return {
73
74
  type: "field",
74
- children: [createFieldRepetition(start)],
75
+ children: mode === "legacy" ? [createFieldRepetition(start, mode)] : [],
75
76
  position: { start, end: start }
76
77
  };
77
78
  }
78
79
  function createParserCore(ctx) {
80
+ const mode = ctx.settings.experimental.emptyMode;
79
81
  const root = {
80
82
  type: "root",
81
83
  children: [],
@@ -130,50 +132,108 @@ function createParserCore(ctx) {
130
132
  "Cannot open field without an active segment. TEXT token with segment name must precede field content."
131
133
  );
132
134
  }
133
- field = createField(start);
135
+ field = createField(start, mode);
134
136
  seg.children.push(field);
135
- rep = field.children[0];
136
- comp = rep.children[0];
137
- currentSub = comp.children[0];
137
+ if (mode === "legacy") {
138
+ rep = field.children[0] ?? null;
139
+ comp = rep?.children[0] ?? null;
140
+ currentSub = comp?.children[0] ?? null;
141
+ } else {
142
+ rep = null;
143
+ comp = null;
144
+ currentSub = null;
145
+ }
138
146
  segmentHasContent = true;
139
147
  };
140
148
  const openRepetition = (start) => {
141
149
  if (!field) {
142
150
  openField(start);
143
- return;
151
+ if (mode === "empty") {
152
+ const emptyRep = createFieldRepetition(start, mode);
153
+ field.children.push(emptyRep);
154
+ }
155
+ }
156
+ if (mode === "empty" && field.children.length === 0) {
157
+ const emptyRep = createFieldRepetition(start, mode);
158
+ field.children.push(emptyRep);
144
159
  }
145
- rep = createFieldRepetition(start);
160
+ rep = createFieldRepetition(start, mode);
146
161
  field.children.push(rep);
147
- comp = rep.children[0] ?? null;
148
- currentSub = comp?.children[0] ?? null;
162
+ if (mode === "legacy") {
163
+ comp = rep.children[0] ?? null;
164
+ currentSub = comp?.children[0] ?? null;
165
+ } else {
166
+ comp = null;
167
+ currentSub = null;
168
+ }
169
+ segmentHasContent = true;
170
+ };
171
+ const ensureComponent = (start) => {
172
+ if (!field) {
173
+ openField(start);
174
+ }
175
+ if (!rep) {
176
+ rep = createFieldRepetition(start, mode);
177
+ field.children.push(rep);
178
+ }
179
+ if (!comp) {
180
+ comp = createComponent(start, mode);
181
+ rep.children.push(comp);
182
+ }
149
183
  segmentHasContent = true;
150
184
  };
151
185
  const openComponent = (start) => {
152
186
  if (!field) {
153
187
  openField(start);
154
- return;
188
+ if (mode === "empty") {
189
+ rep = createFieldRepetition(start, mode);
190
+ field.children.push(rep);
191
+ const emptyComp = createComponent(start, mode);
192
+ rep.children.push(emptyComp);
193
+ }
155
194
  }
156
195
  if (!rep) {
157
- rep = createFieldRepetition(start);
196
+ rep = createFieldRepetition(start, mode);
158
197
  field.children.push(rep);
198
+ if (mode === "empty") {
199
+ const emptyComp = createComponent(start, mode);
200
+ rep.children.push(emptyComp);
201
+ }
202
+ } else if (mode === "empty" && rep.children.length === 0) {
203
+ const emptyComp = createComponent(start, mode);
204
+ rep.children.push(emptyComp);
159
205
  }
160
- comp = createComponent(start);
206
+ comp = createComponent(start, mode);
161
207
  rep.children.push(comp);
162
- currentSub = comp.children[0] ?? null;
208
+ if (mode === "legacy") {
209
+ currentSub = comp.children[0] ?? null;
210
+ } else {
211
+ currentSub = null;
212
+ }
163
213
  segmentHasContent = true;
164
214
  };
165
215
  const ensureForText = (start) => {
166
216
  if (!field) {
167
217
  openField(start);
168
- return;
218
+ if (mode === "legacy") {
219
+ return;
220
+ }
169
221
  }
170
222
  if (!rep) {
171
- openRepetition(start);
172
- return;
223
+ if (mode === "legacy") {
224
+ openRepetition(start);
225
+ return;
226
+ }
227
+ rep = createFieldRepetition(start, mode);
228
+ field.children.push(rep);
173
229
  }
174
230
  if (!comp) {
175
- openComponent(start);
176
- return;
231
+ if (mode === "legacy") {
232
+ openComponent(start);
233
+ return;
234
+ }
235
+ comp = createComponent(start, mode);
236
+ rep.children.push(comp);
177
237
  }
178
238
  if (!currentSub) {
179
239
  currentSub = createSubcomponent(start);
@@ -238,8 +298,10 @@ function createParserCore(ctx) {
238
298
  case "SUBCOMP_DELIM": {
239
299
  lastContentEnd = tok.position.end;
240
300
  documentEnd = tok.position.end;
241
- if (!comp) {
242
- openComponent(tok.position.start);
301
+ ensureComponent(tok.position.start);
302
+ if (mode === "empty" && comp.children.length === 0) {
303
+ const emptySub = createSubcomponent(tok.position.start);
304
+ comp.children.push(emptySub);
243
305
  }
244
306
  currentSub = createSubcomponent(tok.position.end);
245
307
  comp?.children.push(currentSub);
@@ -262,7 +324,9 @@ function createParserCore(ctx) {
262
324
  return;
263
325
  }
264
326
  ensureForText(tok.position.start);
265
- currentSub.value += val;
327
+ if (currentSub) {
328
+ currentSub.value += val;
329
+ }
266
330
  updatePositionsToEnd(tok.position.end);
267
331
  lastContentEnd = tok.position.end;
268
332
  documentEnd = tok.position.end;
@@ -467,17 +531,22 @@ var HL7v2Tokenizer = class {
467
531
  };
468
532
 
469
533
  // src/parser.ts
534
+ var { settings: DEFAULT_SETTINGS } = loadConfig();
470
535
  function* iterateTokenizerSync(t) {
471
536
  for (let tok = t.next(); tok; tok = t.next()) {
472
537
  yield tok;
473
538
  }
474
539
  }
475
- function parseHL7v2(input, opts) {
540
+ function parseHL7v2(input, opts = {}) {
476
541
  let ctx = {
477
542
  input,
478
543
  delimiters: {
479
544
  ...DEFAULT_DELIMITERS2,
480
545
  ...opts.delimiters
546
+ },
547
+ settings: {
548
+ ...DEFAULT_SETTINGS,
549
+ ...opts.settings
481
550
  }
482
551
  };
483
552
  ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);
@@ -486,7 +555,13 @@ function parseHL7v2(input, opts) {
486
555
  return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);
487
556
  }
488
557
  var hl7v2Parser = function(options = {}) {
489
- this.parser = (document) => parseHL7v2(document, options);
558
+ this.parser = (document) => {
559
+ const settings = this.data("settings");
560
+ return parseHL7v2(document, {
561
+ settings,
562
+ ...options
563
+ });
564
+ };
490
565
  };
491
566
  var parser_default = hl7v2Parser;
492
567
  export {
@@ -495,6 +570,7 @@ export {
495
570
  detectDelimiters,
496
571
  parser_default as hl7v2Parser,
497
572
  normalizeNewlines,
573
+ parseHL7v2,
498
574
  parseHL7v2FromIterator,
499
575
  runPreprocessors,
500
576
  stripBOM
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parser.ts","../src/preprocessor.ts","../src/processor.ts","../src/tokenizer.ts"],"sourcesContent":["import type { Root } from \"@rethinkhealth/hl7v2-ast\";\nimport { DEFAULT_DELIMITERS } from \"@rethinkhealth/hl7v2-utils\";\nimport type { Plugin } from \"unified\";\nimport { defaultPreprocessors, runPreprocessors } from \"./preprocessor\";\nimport { parseHL7v2FromIterator } from \"./processor\";\nimport { HL7v2Tokenizer } from \"./tokenizer\";\nimport type { ParseOptions, ParserContext, Token, Tokenizer } from \"./types\";\n\nfunction* iterateTokenizerSync(t: Tokenizer): Iterable<Token> {\n for (let tok = t.next(); tok; tok = t.next()) {\n yield tok;\n }\n}\n\nexport function parseHL7v2(input: string, opts: ParseOptions): Root {\n let ctx: ParserContext = {\n input,\n delimiters: {\n ...DEFAULT_DELIMITERS,\n ...opts.delimiters,\n },\n };\n // Run preprocessing\n ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);\n // Run tokenizer\n const tokenizer = new HL7v2Tokenizer();\n // Reset tokenizer.\n tokenizer.reset(ctx);\n // Parse\n return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);\n}\n\nconst hl7v2Parser: Plugin<[ParseOptions?], string, Root> = function (\n options = {}\n) {\n this.parser = (document: string) => parseHL7v2(document, options);\n};\n\nexport default hl7v2Parser;\n","import { DEFAULT_DELIMITERS } from \"@rethinkhealth/hl7v2-utils\";\nimport type { ParserContext, PreprocessorStep } from \"./types\";\n\n// PreprocessorStep is declared in `types.ts` to avoid circular imports\n\n/**\n * Step: strip UTF-8 BOM.\n */\nexport const stripBOM: PreprocessorStep = (ctx) => {\n if (ctx.input.charCodeAt(0) === 0xfe_ff) {\n ctx.input = ctx.input.slice(1);\n }\n return ctx;\n};\n\n/**\n * Step: normalize newlines to the default delimiters.\n */\nexport const normalizeNewlines: PreprocessorStep = (ctx) => {\n ctx.input = ctx.input.replace(/\\r?\\n/g, ctx.delimiters.segment);\n return ctx;\n};\n\n/**\n * Step: auto-detect delimiters from MSH.\n */\nexport const detectDelimiters: PreprocessorStep = (ctx) => {\n if (ctx.input.startsWith(\"MSH\")) {\n const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;\n const enc = ctx.input.slice(4, 8);\n const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;\n const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;\n const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;\n const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;\n const segment = DEFAULT_DELIMITERS.segment;\n\n ctx.delimiters = {\n field,\n component,\n repetition,\n escape: _escape,\n subcomponent,\n segment,\n };\n }\n return ctx;\n};\n\n/**\n * Default preprocessing pipeline.\n */\nexport const defaultPreprocessors: PreprocessorStep[] = [\n stripBOM,\n normalizeNewlines,\n detectDelimiters,\n];\n\n/**\n * Run the pipeline to initialize ParserContext.\n */\nexport function runPreprocessors(\n ctx: ParserContext,\n steps: PreprocessorStep[]\n): ParserContext {\n for (const step of steps) {\n // biome-ignore lint/style/noParameterAssign: this is necessary to keep no-copy\n ctx = step(ctx);\n }\n\n return ctx;\n}\n","// src/parser.ts\n\nimport type {\n Component,\n Field,\n FieldRepetition,\n Root,\n Segment,\n SegmentHeader,\n Subcomponent,\n} from \"@rethinkhealth/hl7v2-ast\";\nimport { isEmptyNode } from \"@rethinkhealth/hl7v2-utils\";\nimport type { ParserContext, Position, Token } from \"./types\";\n\n// Helper to create an empty subcomponent at a given position\nfunction createSubcomponent(start: Position[\"start\"]): Subcomponent {\n return {\n type: \"subcomponent\",\n value: \"\",\n position: { start, end: start },\n };\n}\n\n// Helper to create a component with an initial empty subcomponent\nfunction createComponent(start: Position[\"start\"]): Component {\n return {\n type: \"component\",\n children: [createSubcomponent(start)],\n position: { start, end: start },\n };\n}\n\n// Helper to create a field repetition with an initial component\nfunction createFieldRepetition(start: Position[\"start\"]): FieldRepetition {\n return {\n type: \"field-repetition\",\n children: [createComponent(start)],\n position: { start, end: start },\n };\n}\n\n// Helper to create a field with an initial repetition\nfunction createField(start: Position[\"start\"]): Field {\n return {\n type: \"field\",\n children: [createFieldRepetition(start)],\n position: { start, end: start },\n };\n}\n\n// Shared core: process a single token into mutable parse state\nfunction createParserCore(ctx: ParserContext) {\n const root: Root = {\n type: \"root\",\n children: [],\n data: {\n delimiters: ctx.delimiters,\n },\n };\n\n let seg: Segment | null = null;\n let field: Field | null = null;\n let rep: FieldRepetition | null = null;\n let comp: Component | null = null;\n let currentSub: Subcomponent | null = null;\n let segmentHasContent = false;\n let lastContentEnd: Position[\"end\"] | null = null;\n let documentEnd: Position[\"end\"] | null = null; // Track document-level end position\n let expectingSegmentName = true; // Start expecting a segment name\n let justSetSegmentName = false; // Track if we just set the segment name\n let rootStartSet = false; // Track if we've set the root start position\n\n const resetState = () => {\n seg = null;\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n lastContentEnd = null;\n expectingSegmentName = true;\n };\n\n const openSegment = (name: string, position: Position) => {\n const header: SegmentHeader = {\n type: \"segment-header\",\n value: name,\n position: { ...position },\n };\n seg = {\n type: \"segment\",\n children: [header],\n position: { start: position.start, end: position.end },\n };\n root.children.push(seg);\n // Reset field-level state\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n justSetSegmentName = true;\n expectingSegmentName = false;\n };\n\n const openField = (start: Position[\"start\"]) => {\n if (!seg) {\n throw new Error(\n \"Cannot open field without an active segment. TEXT token with segment name must precede field content.\"\n );\n }\n field = createField(start);\n seg.children.push(field);\n // biome-ignore lint/style/noNonNullAssertion: createField guarantees at least one child at each level\n rep = field.children[0]!;\n // biome-ignore lint/style/noNonNullAssertion: createFieldRepetition guarantees at least one child\n comp = rep.children[0]!;\n // biome-ignore lint/style/noNonNullAssertion: createComponent guarantees at least one child\n currentSub = comp.children[0]!;\n segmentHasContent = true;\n };\n\n const openRepetition = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n return; // openField already created everything including subcomponent\n }\n rep = createFieldRepetition(start);\n field.children.push(rep);\n comp = rep.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n segmentHasContent = true;\n };\n\n const openComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n return; // openField already created everything including subcomponent\n }\n if (!rep) {\n rep = createFieldRepetition(start);\n field.children.push(rep);\n }\n comp = createComponent(start);\n rep.children.push(comp);\n currentSub = comp.children[0] ?? null;\n segmentHasContent = true;\n };\n\n const ensureForText = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n return; // Everything is already set up by openField\n }\n if (!rep) {\n openRepetition(start);\n return; // Everything is already set up by openRepetition\n }\n if (!comp) {\n openComponent(start);\n return; // Everything is already set up by openComponent\n }\n if (!currentSub) {\n currentSub = createSubcomponent(start);\n comp.children.push(currentSub);\n segmentHasContent = true;\n }\n };\n\n const updatePositionsToEnd = (endPos: Position[\"end\"]) => {\n if (currentSub?.position) {\n currentSub.position.end = endPos;\n }\n if (comp?.position) {\n comp.position.end = endPos;\n }\n if (rep?.position) {\n rep.position.end = endPos;\n }\n if (field?.position) {\n field.position.end = endPos;\n }\n if (seg?.position) {\n seg.position.end = endPos;\n }\n };\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: State machine requires handling all token types\n const processToken = (tok: Token) => {\n switch (tok.type) {\n case \"SEGMENT_END\": {\n // Use the last content position instead of the segment delimiter position\n const endPos = lastContentEnd || tok.position.start;\n updatePositionsToEnd(endPos);\n dropTrailingEmptyFieldIfPresent();\n resetState();\n return;\n }\n case \"FIELD_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Skip the first field delimiter after setting segment name (it separates segment name from fields)\n if (justSetSegmentName) {\n justSetSegmentName = false;\n return;\n }\n // Leading field delimiter implies an empty first field\n if (!field) {\n // Open an empty first field (already has empty subcomponent from openField)\n openField(tok.position.start);\n }\n openField(tok.position.end);\n return;\n }\n case \"REPETITION_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!field) {\n openField(tok.position.start);\n }\n openRepetition(tok.position.end);\n return;\n }\n case \"COMPONENT_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n openComponent(tok.position.end);\n return;\n }\n case \"SUBCOMP_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!comp) {\n openComponent(tok.position.start);\n }\n currentSub = createSubcomponent(tok.position.end);\n comp?.children.push(currentSub);\n segmentHasContent = true;\n return;\n }\n case \"TEXT\": {\n const val = tok.value ?? \"\";\n\n // Set root start position on first token\n if (!rootStartSet) {\n root.position = {\n start: tok.position.start,\n end: tok.position.start,\n };\n rootStartSet = true;\n }\n\n // If we're expecting a segment name, this TEXT is the segment name\n if (expectingSegmentName) {\n // `tok.position` is always available for TEXT tokens\n openSegment(val, tok.position);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n return;\n }\n\n // Otherwise, it's regular field content\n ensureForText(tok.position.start);\n // biome-ignore lint/style/noNonNullAssertion: ensured above\n currentSub!.value += val;\n updatePositionsToEnd(tok.position.end);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n segmentHasContent = true;\n return;\n }\n default: {\n throw new Error(`Unexpected token type: ${tok.type}`);\n }\n }\n };\n\n // Do not pre-open a segment; lazily open upon first non-SEGMENT_END token.\n\n const finalize = () => {\n // Handle input that ends without an explicit segment delimiter as above.\n if (lastContentEnd && seg) {\n updatePositionsToEnd(lastContentEnd);\n }\n dropTrailingEmptyFieldIfPresent();\n if (seg && !segmentHasContent) {\n // Drop trailing empty segment if no content was added\n root.children.pop();\n }\n\n // Set root end position once at finalization using document-level end\n if (root.position && documentEnd) {\n root.position.end = documentEnd;\n } else if (!root.position) {\n // Empty document - set default position\n root.position = {\n start: { line: 1, column: 1, offset: 0 },\n end: { line: 1, column: 1, offset: 0 },\n };\n }\n\n return root;\n };\n\n function dropTrailingEmptyFieldIfPresent() {\n if (!seg || seg.children.length <= 1) {\n return;\n }\n // Drop only the final trailing empty field (created by the last delimiter),\n // preserving any intentional empty fields immediately before it.\n const lastChild = seg.children.at(-1);\n if (lastChild?.type !== \"field\") {\n return;\n }\n const lastField = lastChild as Field;\n if (isEmptyNode(lastField)) {\n seg.children.pop();\n }\n }\n\n return { processToken, finalize, root };\n}\n\n// Sync convenience wrapper over a sync Iterable token source\nexport function parseHL7v2FromIterator(\n tokens: Iterable<Token>,\n ctx: ParserContext\n): Root {\n const core = createParserCore(ctx);\n for (const tok of tokens) {\n core.processToken(tok);\n }\n return core.finalize();\n}\n","// src/tokenizer.ts\nimport type { Delimiters } from \"@rethinkhealth/hl7v2-ast\";\nimport type {\n ParserContext,\n Position,\n Token,\n Tokenizer,\n TokenType,\n} from \"./types\";\n\nconst MSH_SEGMENT_START = 0;\nconst MSH_SEGMENT_END = 3;\nconst MSH_FIELD_SEPERATOR_START = 3;\nconst MSH_FIELD_SEPERATOR_END = 4;\nconst MSH_FIELD_DELIMITER_START = 4;\nconst MSH_FIELD_DELIMITER_END = 8;\n\nexport class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {\n private input = \"\";\n private i = 0;\n private line = 1;\n private col = 1;\n private delims!: Delimiters;\n\n // Only-run-once MSH bootstrap at the start of the file\n private didMshBootstrap = false;\n private pendingBootstrap: null | Array<\n | { kind: \"TEXT\"; value: string; advance: number }\n | { kind: \"FIELD_DELIM\"; advance: number }\n > = null; // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\\\&')\n\n reset(ctx: ParserContext) {\n this.input = ctx.input;\n this.delims = ctx.delimiters;\n this.i = 0;\n this.line = 1;\n this.col = 1;\n this.didMshBootstrap = false;\n this.pendingBootstrap = null;\n\n // Prepare a one-time bootstrap if file starts with MSH\n if (this.input.startsWith(\"MSH\")) {\n // Precompute MSH and MSH.2; MSH.1 is the field delimiter char at index 3\n const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);\n const msh1 = this._slice(\n MSH_FIELD_SEPERATOR_START,\n MSH_FIELD_SEPERATOR_END\n ); // the field delimiter char at index 3\n const msh2 = this._slice(\n MSH_FIELD_DELIMITER_START,\n MSH_FIELD_DELIMITER_END\n ); // may be shorter than 4 if truncated\n this.pendingBootstrap = [\n { kind: \"TEXT\", value: msh, advance: msh.length },\n { kind: \"FIELD_DELIM\", advance: 0 },\n { kind: \"TEXT\", value: msh1, advance: msh1.length },\n { kind: \"FIELD_DELIM\", advance: 0 },\n { kind: \"TEXT\", value: msh2, advance: msh2.length },\n ];\n // NOTE: we have not advanced indices yet; we will as we emit tokens\n }\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: The cognitive complexity of this method is justified because it must handle the HL7v2 MSH segment bootstrap logic and multiple tokenization cases in a single method for performance and maintainability.\n next(): Token | null {\n const s = this.input;\n const n = s.length;\n if (this.i >= n && !this.pendingBootstrap?.length) {\n return null;\n }\n\n const start = { offset: this.i, line: this.line, column: this.col };\n\n // ---- One-time MSH bootstrap at file start ----\n if (!this.didMshBootstrap && this.pendingBootstrap) {\n const step = this.pendingBootstrap.shift();\n if (step) {\n if (step.kind === \"TEXT\") {\n const take = Math.min(step.advance, n - this.i);\n const out = step.value.slice(0, take);\n this._fastAdvance(out);\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"TEXT\", out, start);\n }\n // FIELD_DELIM: advance exactly one char (the delimiter) and emit a FIELD_DELIM token\n this._advance(Math.min(step.advance, n - this.i));\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n }\n\n // ---- Normal tokenization (delimiters + text) ----\n if (this.i >= n) {\n return null;\n }\n const ch = s.charAt(this.i);\n\n if (ch === this.delims.segment) {\n this._advance(1, true);\n return this._tok(\"SEGMENT_END\", undefined, start);\n }\n if (ch === this.delims.field) {\n this._advance(1);\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n if (ch === this.delims.repetition) {\n this._advance(1);\n return this._tok(\"REPETITION_DELIM\", undefined, start);\n }\n if (ch === this.delims.component) {\n this._advance(1);\n return this._tok(\"COMPONENT_DELIM\", undefined, start);\n }\n if (ch === this.delims.subcomponent) {\n this._advance(1);\n return this._tok(\"SUBCOMP_DELIM\", undefined, start);\n }\n\n // TEXT until next delimiter or end\n let j = this.i;\n const seg = this.delims.segment,\n fld = this.delims.field,\n rep = this.delims.repetition,\n cmp = this.delims.component,\n sub = this.delims.subcomponent;\n\n while (j < s.length) {\n const c = s.charAt(j);\n if (c === seg || c === fld || c === rep || c === cmp || c === sub) {\n break;\n }\n j++;\n }\n\n const val = s.slice(this.i, j);\n this._fastAdvance(val);\n return this._tok(\"TEXT\", val, start);\n }\n\n // ---- helpers ----\n private _slice(start: number, end: number): string {\n return this.input.slice(start, Math.min(end, this.input.length));\n }\n\n private _advance(n: number, isNewline = false) {\n this.i += n;\n if (isNewline) {\n this.line += 1;\n this.col = 1;\n } else {\n this.col += n;\n }\n }\n\n private _fastAdvance(chunk: string) {\n const parts = chunk.split(this.delims.segment);\n if (parts.length > 1) {\n this.line += parts.length - 1;\n this.col = (parts.at(-1)?.length ?? 0) + 1;\n } else {\n this.col += chunk.length;\n }\n this.i += chunk.length;\n }\n\n private _tok(\n type: TokenType,\n value: string | undefined,\n start: Position[\"start\"]\n ): Token {\n const end = { offset: this.i, line: this.line, column: this.col };\n return { type, value, position: { start, end } };\n }\n\n // Iterable protocol (sync)\n [Symbol.iterator](): Iterator<Token> {\n return {\n next: () => {\n const t = this.next();\n if (t == null) {\n return { done: true, value: undefined as unknown as Token };\n }\n return { done: false, value: t };\n },\n };\n }\n}\n"],"mappings":"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","names":["DEFAULT_DELIMITERS","DEFAULT_DELIMITERS"]}
1
+ {"version":3,"sources":["../src/parser.ts","../src/preprocessor.ts","../src/processor.ts","../src/tokenizer.ts"],"sourcesContent":["import type { Root } from \"@rethinkhealth/hl7v2-ast\";\nimport { loadConfig } from \"@rethinkhealth/hl7v2-config\";\nimport { DEFAULT_DELIMITERS } from \"@rethinkhealth/hl7v2-utils\";\nimport type { Plugin, Processor } from \"unified\";\nimport { defaultPreprocessors, runPreprocessors } from \"./preprocessor\";\nimport { parseHL7v2FromIterator } from \"./processor\";\nimport { HL7v2Tokenizer } from \"./tokenizer\";\nimport type { ParseOptions, ParserContext, Token, Tokenizer } from \"./types\";\n\nconst { settings: DEFAULT_SETTINGS } = loadConfig();\n\nfunction* iterateTokenizerSync(t: Tokenizer): Iterable<Token> {\n for (let tok = t.next(); tok; tok = t.next()) {\n yield tok;\n }\n}\n\nexport function parseHL7v2(input: string, opts: ParseOptions = {}): Root {\n let ctx: ParserContext = {\n input,\n delimiters: {\n ...DEFAULT_DELIMITERS,\n ...opts.delimiters,\n },\n settings: {\n ...DEFAULT_SETTINGS,\n ...opts.settings,\n },\n };\n // Run preprocessing\n ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);\n // Run tokenizer\n const tokenizer = new HL7v2Tokenizer();\n // Reset tokenizer.\n tokenizer.reset(ctx);\n // Parse\n return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);\n}\n\nconst hl7v2Parser: Plugin<[ParseOptions?], string, Root> = function (\n this: Processor,\n options = {}\n) {\n this.parser = (document: string) => {\n // Get settings from processor (populated by unified-args from config)\n const settings = this.data(\"settings\");\n\n return parseHL7v2(document, {\n settings,\n ...options,\n });\n };\n};\n\nexport default hl7v2Parser;\n","import { DEFAULT_DELIMITERS } from \"@rethinkhealth/hl7v2-utils\";\nimport type { ParserContext, PreprocessorStep } from \"./types\";\n\n// PreprocessorStep is declared in `types.ts` to avoid circular imports\n\n/**\n * Step: strip UTF-8 BOM.\n */\nexport const stripBOM: PreprocessorStep = (ctx) => {\n if (ctx.input.charCodeAt(0) === 0xfe_ff) {\n ctx.input = ctx.input.slice(1);\n }\n return ctx;\n};\n\n/**\n * Step: normalize newlines to the default delimiters.\n */\nexport const normalizeNewlines: PreprocessorStep = (ctx) => {\n ctx.input = ctx.input.replace(/\\r?\\n/g, ctx.delimiters.segment);\n return ctx;\n};\n\n/**\n * Step: auto-detect delimiters from MSH.\n */\nexport const detectDelimiters: PreprocessorStep = (ctx) => {\n if (ctx.input.startsWith(\"MSH\")) {\n const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;\n const enc = ctx.input.slice(4, 8);\n const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;\n const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;\n const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;\n const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;\n const segment = DEFAULT_DELIMITERS.segment;\n\n ctx.delimiters = {\n field,\n component,\n repetition,\n escape: _escape,\n subcomponent,\n segment,\n };\n }\n return ctx;\n};\n\n/**\n * Default preprocessing pipeline.\n */\nexport const defaultPreprocessors: PreprocessorStep[] = [\n stripBOM,\n normalizeNewlines,\n detectDelimiters,\n];\n\n/**\n * Run the pipeline to initialize ParserContext.\n */\nexport function runPreprocessors(\n ctx: ParserContext,\n steps: PreprocessorStep[]\n): ParserContext {\n for (const step of steps) {\n // biome-ignore lint/style/noParameterAssign: this is necessary to keep no-copy\n ctx = step(ctx);\n }\n\n return ctx;\n}\n","// src/parser.ts\n/** biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: there are complex logic */\n/** biome-ignore-all lint/style/noNonNullAssertion: the processor uses non-null assertions */\n\nimport type {\n Component,\n Field,\n FieldRepetition,\n Root,\n Segment,\n SegmentHeader,\n Subcomponent,\n} from \"@rethinkhealth/hl7v2-ast\";\nimport { isEmptyNode } from \"@rethinkhealth/hl7v2-utils\";\nimport type { ParserContext, Position, Token } from \"./types\";\n\n// Helper to create an empty subcomponent at a given position\nfunction createSubcomponent(start: Position[\"start\"]): Subcomponent {\n return {\n type: \"subcomponent\",\n value: \"\",\n position: { start, end: start },\n };\n}\n\n// Helper to create a component (with or without children based on mode)\nfunction createComponent(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\"\n): Component {\n return {\n type: \"component\",\n children: mode === \"legacy\" ? [createSubcomponent(start)] : [],\n position: { start, end: start },\n };\n}\n\n// Helper to create a field repetition (with or without children based on mode)\nfunction createFieldRepetition(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\"\n): FieldRepetition {\n return {\n type: \"field-repetition\",\n children: mode === \"legacy\" ? [createComponent(start, mode)] : [],\n position: { start, end: start },\n };\n}\n\n// Helper to create a field (with or without children based on mode)\nfunction createField(\n start: Position[\"start\"],\n mode: \"legacy\" | \"empty\"\n): Field {\n return {\n type: \"field\",\n children: mode === \"legacy\" ? [createFieldRepetition(start, mode)] : [],\n position: { start, end: start },\n };\n}\n\n// Shared core: process a single token into mutable parse state\nfunction createParserCore(ctx: ParserContext) {\n const mode = ctx.settings.experimental.emptyMode;\n const root: Root = {\n type: \"root\",\n children: [],\n data: {\n delimiters: ctx.delimiters,\n },\n };\n\n let seg: Segment | null = null;\n let field: Field | null = null;\n let rep: FieldRepetition | null = null;\n let comp: Component | null = null;\n let currentSub: Subcomponent | null = null;\n let segmentHasContent = false;\n let lastContentEnd: Position[\"end\"] | null = null;\n let documentEnd: Position[\"end\"] | null = null; // Track document-level end position\n let expectingSegmentName = true; // Start expecting a segment name\n let justSetSegmentName = false; // Track if we just set the segment name\n let rootStartSet = false; // Track if we've set the root start position\n\n const resetState = () => {\n seg = null;\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n lastContentEnd = null;\n expectingSegmentName = true;\n };\n\n const openSegment = (name: string, position: Position) => {\n const header: SegmentHeader = {\n type: \"segment-header\",\n value: name,\n position: { ...position },\n };\n seg = {\n type: \"segment\",\n children: [header],\n position: { start: position.start, end: position.end },\n };\n root.children.push(seg);\n // Reset field-level state\n field = null;\n rep = null;\n comp = null;\n currentSub = null;\n segmentHasContent = false;\n justSetSegmentName = true;\n expectingSegmentName = false;\n };\n\n const openField = (start: Position[\"start\"]) => {\n if (!seg) {\n throw new Error(\n \"Cannot open field without an active segment. TEXT token with segment name must precede field content.\"\n );\n }\n field = createField(start, mode);\n seg.children.push(field);\n if (mode === \"legacy\") {\n rep = field.children[0] ?? null;\n comp = rep?.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n } else {\n rep = null;\n comp = null;\n currentSub = null;\n }\n segmentHasContent = true;\n };\n\n const openRepetition = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to add an empty first repetition before the delimiter\n if (mode === \"empty\") {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n // Fall through to create the repetition after the delimiter\n }\n // In empty-array mode, if field has no children yet, add empty first rep\n if (mode === \"empty\" && field!.children.length === 0) {\n const emptyRep = createFieldRepetition(start, mode);\n field!.children.push(emptyRep);\n }\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n if (mode === \"legacy\") {\n comp = rep.children[0] ?? null;\n currentSub = comp?.children[0] ?? null;\n } else {\n comp = null;\n currentSub = null;\n }\n segmentHasContent = true;\n };\n\n // Ensure there's a component to add subcomponents to (for SUBCOMP_DELIM)\n // Does NOT add empty sibling components\n const ensureComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n comp = createComponent(start, mode);\n rep.children.push(comp);\n }\n segmentHasContent = true;\n };\n\n // Handle COMPONENT_DELIM - adds empty component before delimiter if needed\n const openComponent = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n // In empty-array mode, openField creates empty field\n // We need to create rep and add empty first component\n if (mode === \"empty\") {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n // Fall through to create the component after the delimiter\n }\n if (!rep) {\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n // In empty-array mode, need to add empty first component before the delimiter\n if (mode === \"empty\") {\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n } else if (mode === \"empty\" && rep.children.length === 0) {\n // If rep exists but has no children, add empty first component\n const emptyComp = createComponent(start, mode);\n rep.children.push(emptyComp);\n }\n comp = createComponent(start, mode);\n rep.children.push(comp);\n if (mode === \"legacy\") {\n currentSub = comp.children[0] ?? null;\n } else {\n currentSub = null;\n }\n segmentHasContent = true;\n };\n\n const ensureForText = (start: Position[\"start\"]) => {\n if (!field) {\n openField(start);\n if (mode === \"legacy\") {\n return; // Everything is already set up by openField in legacy mode\n }\n // In empty-array mode, openField created empty field, need to build structure\n }\n if (!rep) {\n if (mode === \"legacy\") {\n openRepetition(start);\n return; // Everything is already set up by openRepetition in legacy mode\n }\n // In empty-array mode, need to create repetition\n rep = createFieldRepetition(start, mode);\n field!.children.push(rep);\n }\n if (!comp) {\n if (mode === \"legacy\") {\n openComponent(start);\n return; // Everything is already set up by openComponent in legacy mode\n }\n // In empty-array mode, need to create component\n comp = createComponent(start, mode);\n rep!.children.push(comp);\n }\n if (!currentSub) {\n currentSub = createSubcomponent(start);\n comp!.children.push(currentSub);\n segmentHasContent = true;\n }\n };\n\n const updatePositionsToEnd = (endPos: Position[\"end\"]) => {\n if (currentSub?.position) {\n currentSub.position.end = endPos;\n }\n if (comp?.position) {\n comp.position.end = endPos;\n }\n if (rep?.position) {\n rep.position.end = endPos;\n }\n if (field?.position) {\n field.position.end = endPos;\n }\n if (seg?.position) {\n seg.position.end = endPos;\n }\n };\n\n const processToken = (tok: Token) => {\n switch (tok.type) {\n case \"SEGMENT_END\": {\n // Use the last content position instead of the segment delimiter position\n const endPos = lastContentEnd || tok.position.start;\n updatePositionsToEnd(endPos);\n dropTrailingEmptyFieldIfPresent();\n resetState();\n return;\n }\n case \"FIELD_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Skip the first field delimiter after setting segment name (it separates segment name from fields)\n if (justSetSegmentName) {\n justSetSegmentName = false;\n return;\n }\n // Leading field delimiter implies an empty first field\n if (!field) {\n // Open an empty first field (already has empty subcomponent from openField)\n openField(tok.position.start);\n }\n openField(tok.position.end);\n return;\n }\n case \"REPETITION_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n if (!field) {\n openField(tok.position.start);\n }\n openRepetition(tok.position.end);\n return;\n }\n case \"COMPONENT_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n openComponent(tok.position.end);\n return;\n }\n case \"SUBCOMP_DELIM\": {\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n // Ensure there's a component to add subcomponents to\n ensureComponent(tok.position.start);\n // In empty-array mode, if comp has no children yet, add empty first subcomponent\n if (mode === \"empty\" && comp!.children.length === 0) {\n const emptySub = createSubcomponent(tok.position.start);\n comp!.children.push(emptySub);\n }\n currentSub = createSubcomponent(tok.position.end);\n comp?.children.push(currentSub);\n segmentHasContent = true;\n return;\n }\n case \"TEXT\": {\n const val = tok.value ?? \"\";\n\n // Set root start position on first token\n if (!rootStartSet) {\n root.position = {\n start: tok.position.start,\n end: tok.position.start,\n };\n rootStartSet = true;\n }\n\n // If we're expecting a segment name, this TEXT is the segment name\n if (expectingSegmentName) {\n // `tok.position` is always available for TEXT tokens\n openSegment(val, tok.position);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n return;\n }\n\n // Otherwise, it's regular field content\n ensureForText(tok.position.start);\n if (currentSub) {\n currentSub.value += val;\n }\n updatePositionsToEnd(tok.position.end);\n lastContentEnd = tok.position.end;\n documentEnd = tok.position.end;\n segmentHasContent = true;\n return;\n }\n default: {\n throw new Error(`Unexpected token type: ${tok.type}`);\n }\n }\n };\n\n // Do not pre-open a segment; lazily open upon first non-SEGMENT_END token.\n\n const finalize = () => {\n // Handle input that ends without an explicit segment delimiter as above.\n if (lastContentEnd && seg) {\n updatePositionsToEnd(lastContentEnd);\n }\n dropTrailingEmptyFieldIfPresent();\n if (seg && !segmentHasContent) {\n // Drop trailing empty segment if no content was added\n root.children.pop();\n }\n\n // Set root end position once at finalization using document-level end\n if (root.position && documentEnd) {\n root.position.end = documentEnd;\n } else if (!root.position) {\n // Empty document - set default position\n root.position = {\n start: { line: 1, column: 1, offset: 0 },\n end: { line: 1, column: 1, offset: 0 },\n };\n }\n\n return root;\n };\n\n function dropTrailingEmptyFieldIfPresent() {\n if (!seg || seg.children.length <= 1) {\n return;\n }\n // Drop only the final trailing empty field (created by the last delimiter),\n // preserving any intentional empty fields immediately before it.\n const lastChild = seg.children.at(-1);\n if (lastChild?.type !== \"field\") {\n return;\n }\n const lastField = lastChild as Field;\n if (isEmptyNode(lastField)) {\n seg.children.pop();\n }\n }\n\n return { processToken, finalize, root };\n}\n\n// Sync convenience wrapper over a sync Iterable token source\nexport function parseHL7v2FromIterator(\n tokens: Iterable<Token>,\n ctx: ParserContext\n): Root {\n const core = createParserCore(ctx);\n for (const tok of tokens) {\n core.processToken(tok);\n }\n return core.finalize();\n}\n","// src/tokenizer.ts\nimport type { Delimiters } from \"@rethinkhealth/hl7v2-ast\";\nimport type {\n ParserContext,\n Position,\n Token,\n Tokenizer,\n TokenType,\n} from \"./types\";\n\nconst MSH_SEGMENT_START = 0;\nconst MSH_SEGMENT_END = 3;\nconst MSH_FIELD_SEPERATOR_START = 3;\nconst MSH_FIELD_SEPERATOR_END = 4;\nconst MSH_FIELD_DELIMITER_START = 4;\nconst MSH_FIELD_DELIMITER_END = 8;\n\nexport class HL7v2Tokenizer implements Tokenizer, Iterable<Token> {\n private input = \"\";\n private i = 0;\n private line = 1;\n private col = 1;\n private delims!: Delimiters;\n\n // Only-run-once MSH bootstrap at the start of the file\n private didMshBootstrap = false;\n private pendingBootstrap: null | Array<\n | { kind: \"TEXT\"; value: string; advance: number }\n | { kind: \"FIELD_DELIM\"; advance: number }\n > = null; // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\\\&')\n\n reset(ctx: ParserContext) {\n this.input = ctx.input;\n this.delims = ctx.delimiters;\n this.i = 0;\n this.line = 1;\n this.col = 1;\n this.didMshBootstrap = false;\n this.pendingBootstrap = null;\n\n // Prepare a one-time bootstrap if file starts with MSH\n if (this.input.startsWith(\"MSH\")) {\n // Precompute MSH and MSH.2; MSH.1 is the field delimiter char at index 3\n const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);\n const msh1 = this._slice(\n MSH_FIELD_SEPERATOR_START,\n MSH_FIELD_SEPERATOR_END\n ); // the field delimiter char at index 3\n const msh2 = this._slice(\n MSH_FIELD_DELIMITER_START,\n MSH_FIELD_DELIMITER_END\n ); // may be shorter than 4 if truncated\n this.pendingBootstrap = [\n { kind: \"TEXT\", value: msh, advance: msh.length },\n { kind: \"FIELD_DELIM\", advance: 0 },\n { kind: \"TEXT\", value: msh1, advance: msh1.length },\n { kind: \"FIELD_DELIM\", advance: 0 },\n { kind: \"TEXT\", value: msh2, advance: msh2.length },\n ];\n // NOTE: we have not advanced indices yet; we will as we emit tokens\n }\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: The cognitive complexity of this method is justified because it must handle the HL7v2 MSH segment bootstrap logic and multiple tokenization cases in a single method for performance and maintainability.\n next(): Token | null {\n const s = this.input;\n const n = s.length;\n if (this.i >= n && !this.pendingBootstrap?.length) {\n return null;\n }\n\n const start = { offset: this.i, line: this.line, column: this.col };\n\n // ---- One-time MSH bootstrap at file start ----\n if (!this.didMshBootstrap && this.pendingBootstrap) {\n const step = this.pendingBootstrap.shift();\n if (step) {\n if (step.kind === \"TEXT\") {\n const take = Math.min(step.advance, n - this.i);\n const out = step.value.slice(0, take);\n this._fastAdvance(out);\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"TEXT\", out, start);\n }\n // FIELD_DELIM: advance exactly one char (the delimiter) and emit a FIELD_DELIM token\n this._advance(Math.min(step.advance, n - this.i));\n if (this.pendingBootstrap.length === 0) {\n this.pendingBootstrap = null;\n this.didMshBootstrap = true;\n }\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n }\n\n // ---- Normal tokenization (delimiters + text) ----\n if (this.i >= n) {\n return null;\n }\n const ch = s.charAt(this.i);\n\n if (ch === this.delims.segment) {\n this._advance(1, true);\n return this._tok(\"SEGMENT_END\", undefined, start);\n }\n if (ch === this.delims.field) {\n this._advance(1);\n return this._tok(\"FIELD_DELIM\", undefined, start);\n }\n if (ch === this.delims.repetition) {\n this._advance(1);\n return this._tok(\"REPETITION_DELIM\", undefined, start);\n }\n if (ch === this.delims.component) {\n this._advance(1);\n return this._tok(\"COMPONENT_DELIM\", undefined, start);\n }\n if (ch === this.delims.subcomponent) {\n this._advance(1);\n return this._tok(\"SUBCOMP_DELIM\", undefined, start);\n }\n\n // TEXT until next delimiter or end\n let j = this.i;\n const seg = this.delims.segment,\n fld = this.delims.field,\n rep = this.delims.repetition,\n cmp = this.delims.component,\n sub = this.delims.subcomponent;\n\n while (j < s.length) {\n const c = s.charAt(j);\n if (c === seg || c === fld || c === rep || c === cmp || c === sub) {\n break;\n }\n j++;\n }\n\n const val = s.slice(this.i, j);\n this._fastAdvance(val);\n return this._tok(\"TEXT\", val, start);\n }\n\n // ---- helpers ----\n private _slice(start: number, end: number): string {\n return this.input.slice(start, Math.min(end, this.input.length));\n }\n\n private _advance(n: number, isNewline = false) {\n this.i += n;\n if (isNewline) {\n this.line += 1;\n this.col = 1;\n } else {\n this.col += n;\n }\n }\n\n private _fastAdvance(chunk: string) {\n const parts = chunk.split(this.delims.segment);\n if (parts.length > 1) {\n this.line += parts.length - 1;\n this.col = (parts.at(-1)?.length ?? 0) + 1;\n } else {\n this.col += chunk.length;\n }\n this.i += chunk.length;\n }\n\n private _tok(\n type: TokenType,\n value: string | undefined,\n start: Position[\"start\"]\n ): Token {\n const end = { offset: this.i, line: this.line, column: this.col };\n return { type, value, position: { start, end } };\n }\n\n // Iterable protocol (sync)\n [Symbol.iterator](): Iterator<Token> {\n return {\n next: () => {\n const t = this.next();\n if (t == null) {\n return { done: true, value: undefined as unknown as Token };\n }\n return { done: false, value: t };\n },\n };\n }\n}\n"],"mappings":"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","names":["DEFAULT_DELIMITERS","DEFAULT_DELIMITERS"]}
package/dist/parser.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Root } from "@rethinkhealth/hl7v2-ast";
2
2
  import type { Plugin } from "unified";
3
3
  import type { ParseOptions } from "./types";
4
- export declare function parseHL7v2(input: string, opts: ParseOptions): Root;
4
+ export declare function parseHL7v2(input: string, opts?: ParseOptions): Root;
5
5
  declare const hl7v2Parser: Plugin<[ParseOptions?], string, Root>;
6
6
  export default hl7v2Parser;
@@ -1,3 +1,5 @@
1
+ /** biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: there are complex logic */
2
+ /** biome-ignore-all lint/style/noNonNullAssertion: the processor uses non-null assertions */
1
3
  import type { Root } from "@rethinkhealth/hl7v2-ast";
2
4
  import type { ParserContext, Token } from "./types";
3
5
  export declare function parseHL7v2FromIterator(tokens: Iterable<Token>, ctx: ParserContext): Root;
package/dist/types.d.ts CHANGED
@@ -1,13 +1,27 @@
1
1
  import type { Delimiters } from "@rethinkhealth/hl7v2-ast";
2
+ import type { HL7v2Settings } from "@rethinkhealth/hl7v2-config";
2
3
  export type PreprocessorStep = (ctx: ParserContext) => ParserContext;
3
4
  export type ParseOptions = {
5
+ /**
6
+ * Custom delimiters to use instead of the HL7 standard defaults.
7
+ */
4
8
  delimiters?: Partial<Delimiters>;
9
+ /**
10
+ * Optional preprocessing steps to apply to the input before parsing.
11
+ */
5
12
  preprocess?: PreprocessorStep[];
13
+ /**
14
+ * Feature flags for experimental parser behaviors.
15
+ *
16
+ * This API is experimental and may change in future releases.
17
+ */
18
+ settings?: HL7v2Settings;
6
19
  };
7
20
  export type ParserContext = {
8
21
  input: string;
9
22
  delimiters: Delimiters;
10
23
  metadata?: Record<string, unknown>;
24
+ settings: HL7v2Settings;
11
25
  };
12
26
  export type Position = {
13
27
  start: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rethinkhealth/hl7v2-parser",
3
3
  "description": "hl7v2 plugin to parse hl7v2 messages",
4
- "version": "0.3.3",
4
+ "version": "0.4.0",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Melek Somai",
@@ -17,18 +17,22 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "unified": "11.0.5",
20
- "@rethinkhealth/hl7v2-utils": "0.3.3"
20
+ "@rethinkhealth/hl7v2-utils": "0.4.0",
21
+ "@rethinkhealth/hl7v2-config": "0.4.0"
21
22
  },
22
23
  "devDependencies": {
23
- "@types/node": "24.10.0",
24
+ "@types/node": "24.10.1",
24
25
  "@types/unist": "^3.0.3",
25
- "@vitest/coverage-v8": "^4.0.5",
26
- "tsup": "8.5.0",
26
+ "@vitest/coverage-v8": "4.0.14",
27
+ "tsup": "8.5.1",
27
28
  "typescript": "^5.9.3",
28
- "vitest": "^4.0.6",
29
- "@rethinkhealth/tsconfig": "0.0.1",
30
- "@rethinkhealth/hl7v2-ast": "0.3.3",
31
- "@rethinkhealth/testing": "0.0.2"
29
+ "vitest": "4.0.14",
30
+ "@rethinkhealth/hl7v2-ast": "0.4.0",
31
+ "@rethinkhealth/testing": "0.0.2",
32
+ "@rethinkhealth/tsconfig": "0.0.1"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
32
36
  },
33
37
  "repository": "rethinkhealth/hl7v2.git",
34
38
  "homepage": "https://www.rethinkhealth.io/hl7v2/docs",
@@ -40,14 +44,12 @@
40
44
  "nodejs",
41
45
  "typescript"
42
46
  ],
43
- "engines": {
44
- "node": ">=18"
45
- },
46
47
  "packageManager": "pnpm@10.14.0",
47
48
  "publishConfig": {
48
49
  "access": "public"
49
50
  },
50
51
  "scripts": {
52
+ "bench": "vitest bench --run",
51
53
  "build": "tsup && tsc --emitDeclarationOnly",
52
54
  "check-types": "tsc --noEmit",
53
55
  "test": "vitest run",