@rethinkhealth/hl7v2-utils 0.3.0 → 0.3.1

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
@@ -54,126 +54,133 @@ report(file, requiredFieldRule, {
54
54
  });
55
55
  ```
56
56
 
57
- ### `Diagnostic`
57
+ ### `getByteLength(node)`
58
58
 
59
- Type definition for diagnostic rules. Diagnostics are used to report issues about HL7v2 messages.
59
+ Calculate the byte length of any HL7v2 AST node. This utility efficiently computes the total serialized length including all children and separators (assumed to be 1 byte each).
60
60
 
61
- #### Properties
62
-
63
- - `type` (`string`) - Tool/plugin category (e.g., "validator", "lint", "annotator", "transformer", "parser")
64
- - `namespace` (`string`) - Domain/concern (e.g., "order", "field", "segment", "conformance")
65
- - `code` (`string`) - Specific issue code (e.g., "transition", "required", "acceptance")
66
- - `title` (`string`) - Human-readable title
67
- - `description` (`string`) - Full description of the rule
68
- - `severity` (`"error" | "warning" | "info" | null | undefined`) - Default severity level
69
- - `message` (`(context: Record<string, unknown>) => string`) - Message formatter function
70
- - `helpUrl` (`string`, optional) - URL to documentation
61
+ #### Parameters
71
62
 
72
- #### Rule ID Format
63
+ - `node` (`Nodes | null | undefined`) - The AST node to measure
73
64
 
74
- The rule ID is automatically constructed as `type:namespace:code` (e.g., `lint:field:required`).
65
+ #### Returns
75
66
 
76
- #### Example
67
+ `number` - The total byte length when the node is serialized
77
68
 
78
- ```typescript
79
- const invalidTransitionRule: Diagnostic = {
80
- type: 'validator',
81
- namespace: 'order',
82
- code: 'transition',
83
- title: 'Invalid Segment Transition',
84
- description: 'A segment arrived that is not allowed in this position.',
85
- severity: 'error',
86
- message: (ctx) => {
87
- const expected = Array.isArray(ctx.expected)
88
- ? ctx.expected.join(', ')
89
- : ctx.expected;
90
- return `Expected ${expected}, got '${ctx.actual}'`;
91
- },
92
- helpUrl: 'https://example.com/docs/segment-order'
93
- };
94
- ```
69
+ #### Algorithm
95
70
 
96
- ### `DEFAULT_DELIMITERS`
71
+ - For literal nodes (Subcomponent, SegmentHeader): returns the byte length of the value using UTF-8 encoding (i.e., `Buffer.byteLength(value, 'utf8')`)
72
+ - For parent nodes: recursively sums the byte length of all children plus 1 byte per separator between children
73
+ - Handles all node types: Root, Segment, Group, Field, FieldRepetition, Component, Subcomponent
97
74
 
98
- Standard HL7v2 delimiters as defined in the specification.
75
+ #### Performance
99
76
 
100
- ```typescript
101
- export const DEFAULT_DELIMITERS = {
102
- field: "|",
103
- component: "^",
104
- repetition: "~",
105
- subcomponent: "&",
106
- escape: "\\",
107
- segment: "\r",
108
- };
109
- ```
77
+ The function is optimized for performance with O(n) time complexity where n is the total number of nodes in the tree. It uses a simple recursive approach with minimal overhead.
110
78
 
111
79
  #### Example
112
80
 
113
81
  ```typescript
114
- import { DEFAULT_DELIMITERS } from '@rethinkhealth/hl7v2-utils';
82
+ import { getByteLength } from '@rethinkhealth/hl7v2-utils';
83
+ import type { Field } from '@rethinkhealth/hl7v2-ast';
84
+
85
+ const field: Field = {
86
+ type: 'field',
87
+ children: [
88
+ {
89
+ type: 'field-repetition',
90
+ children: [
91
+ {
92
+ type: 'component',
93
+ children: [
94
+ { type: 'subcomponent', value: 'SMITH' },
95
+ { type: 'subcomponent', value: 'JOHN' }
96
+ ]
97
+ }
98
+ ]
99
+ }
100
+ ]
101
+ };
115
102
 
116
- const message = segments.join(DEFAULT_DELIMITERS.segment);
103
+ // Calculate: SMITH&JOHN = 5 + 1 + 4 = 10 bytes
104
+ const length = getByteLength(field); // Returns: 10
117
105
  ```
118
106
 
119
- ### `isEmptyNode(node)`
107
+ #### Use Cases
108
+
109
+ - Validating field or message size constraints
110
+ - Memory allocation planning
111
+ - Message size reporting and analytics
112
+ - Performance optimization by avoiding full serialization
113
+
114
+ ### `getLength(node)`
120
115
 
121
- Check if an AST node is semantically empty. This is useful for validation and transformation logic.
116
+ Calculate the string length of any HL7v2 AST node. This utility efficiently computes the total serialized character length including all children and separators (assumed to be 1 character each).
122
117
 
123
118
  #### Parameters
124
119
 
125
- - `node` (`Nodes | null | undefined`) - The AST node to check
120
+ - `node` (`Nodes | null | undefined`) - The AST node to measure
126
121
 
127
122
  #### Returns
128
123
 
129
- `boolean` - `true` if the node is empty, `false` otherwise
124
+ `number` - The total string length when the node is serialized
125
+
126
+ #### Algorithm
127
+
128
+ - For literal nodes (Subcomponent, SegmentHeader): returns `value.length` (JavaScript string length)
129
+ - For parent nodes: recursively sums the string length of all children plus 1 character per separator between children
130
+ - Handles all node types: Root, Segment, Group, Field, FieldRepetition, Component, Subcomponent
131
+
132
+ #### Important Note
133
+
134
+ Returns JavaScript string length (UTF-16 code units). For UTF-8 byte length (e.g., for wire protocol or size constraints), use `getByteLength` instead. These values differ for characters outside the ASCII range.
135
+
136
+ #### Performance
137
+
138
+ The function is optimized for performance with O(n) time complexity where n is the total number of nodes in the tree. It uses a simple recursive approach with minimal overhead.
130
139
 
131
140
  #### Example
132
141
 
133
142
  ```typescript
134
- import { isEmptyNode } from '@rethinkhealth/hl7v2-utils';
135
-
136
- if (isEmptyNode(field)) {
137
- report(file, requiredFieldRule, {
138
- context: { fieldPath: 'PID-5' },
139
- node: field
140
- });
141
- }
143
+ import { getLength } from '@rethinkhealth/hl7v2-utils';
144
+ import type { Field } from '@rethinkhealth/hl7v2-ast';
145
+
146
+ const field: Field = {
147
+ type: 'field',
148
+ children: [
149
+ {
150
+ type: 'field-repetition',
151
+ children: [
152
+ {
153
+ type: 'component',
154
+ children: [
155
+ { type: 'subcomponent', value: 'SMITH' },
156
+ { type: 'subcomponent', value: 'JOHN' }
157
+ ]
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ };
163
+
164
+ // Calculate: SMITH&JOHN = 5 + 1 + 4 = 10 characters
165
+ const length = getLength(field); // Returns: 10
142
166
  ```
143
167
 
144
- ## Usage
168
+ #### Use Cases
169
+
170
+ - UI display and text formatting
171
+ - Character-based validation rules
172
+ - String manipulation operations
173
+ - Quick length checks where byte-level precision is not required
145
174
 
146
- ### Creating a Custom Linter
175
+ #### Comparison with `getByteLength`
147
176
 
148
177
  ```typescript
149
- import { visit } from 'unist-util-visit';
150
- import { report } from '@rethinkhealth/hl7v2-utils';
151
- import type { Diagnostic } from '@rethinkhealth/hl7v2-utils';
152
- import type { Root } from '@rethinkhealth/hl7v2-ast';
153
- import type { VFile } from 'vfile';
178
+ import { getLength, getByteLength } from '@rethinkhealth/hl7v2-utils';
154
179
 
155
- const noEmptySegmentRule: Diagnostic = {
156
- type: 'lint',
157
- namespace: 'segment',
158
- code: 'no-empty',
159
- title: 'Empty Segment',
160
- description: 'Segments should not be empty.',
161
- severity: 'warning',
162
- message: (ctx) => `Segment '${ctx.segmentId}' is empty`
163
- };
180
+ const subcomponent = { type: 'subcomponent', value: 'café' };
164
181
 
165
- function lintNoEmptySegment() {
166
- return (tree: Root, file: VFile) => {
167
- visit(tree, 'segment', (node) => {
168
- if (isEmptyNode(node)) {
169
- report(file, noEmptySegmentRule, {
170
- node,
171
- context: { segmentId: node.segmentId }
172
- });
173
- }
174
- });
175
- };
176
- }
182
+ getLength(subcomponent); // Returns: 4 (4 characters)
183
+ getByteLength(subcomponent); // Returns: 5 (5 bytes in UTF-8: c-a-f-C3-A9)
177
184
  ```
178
185
 
179
186
  ## Contributing
package/dist/index.d.ts CHANGED
@@ -11,4 +11,41 @@ export declare const DEFAULT_DELIMITERS: {
11
11
  * Utility: check if a node is semantically empty
12
12
  */
13
13
  export declare function isEmptyNode(node: Nodes | null | undefined): boolean;
14
+ /**
15
+ * Calculate the byte length of any HL7v2 AST node.
16
+ *
17
+ * For literal nodes (Subcomponent, SegmentHeader), returns the UTF-8 byte length of the value.
18
+ * For parent nodes, recursively calculates the length of all children plus 1 byte
19
+ * per separator (assumed to be single-byte delimiters).
20
+ *
21
+ * @param node - The HL7v2 AST node to measure
22
+ * @returns The total byte length of the node when serialized
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const field: Field = { type: "field", children: [...] };
27
+ * const length = getByteLength(field); // e.g., 42
28
+ * ```
29
+ */
30
+ export declare function getByteLength(node: Nodes | null | undefined): number;
31
+ /**
32
+ * Calculate the string length of any HL7v2 AST node.
33
+ *
34
+ * For literal nodes (Subcomponent, SegmentHeader), returns `value.length`.
35
+ * For parent nodes, recursively calculates the length of all children plus 1
36
+ * per separator (assumed to be single-character delimiters).
37
+ *
38
+ * Note: Returns JavaScript string length (UTF-16 code units). For UTF-8 byte
39
+ * length (e.g., for wire protocol), use `getByteLength` instead.
40
+ *
41
+ * @param node - The HL7v2 AST node to measure
42
+ * @returns The total string length of the node when serialized
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const field: Field = { type: "field", children: [...] };
47
+ * const length = getLength(field); // e.g., 42
48
+ * ```
49
+ */
50
+ export declare function getLength(node: Nodes | null | undefined): number;
14
51
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAMtD,eAAO,MAAM,kBAAkB;;;;;;;CAO9B,CAAC;AAMF;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CA2BnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAMtD,eAAO,MAAM,kBAAkB;;;;;;;CAO9B,CAAC;AAMF;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CA2BnE;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAcpE;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAchE"}
package/dist/index.js CHANGED
@@ -25,8 +25,34 @@ function isEmptyNode(node) {
25
25
  }
26
26
  return false;
27
27
  }
28
+ function getByteLength(node) {
29
+ if (!node) {
30
+ return 0;
31
+ }
32
+ if ("value" in node) {
33
+ return Buffer.byteLength(node.value, "utf8");
34
+ }
35
+ return node.children.reduce(
36
+ (total, child, i) => total + getByteLength(child) + (i < node.children.length - 1 ? 1 : 0),
37
+ 0
38
+ );
39
+ }
40
+ function getLength(node) {
41
+ if (!node) {
42
+ return 0;
43
+ }
44
+ if ("value" in node) {
45
+ return node.value.length;
46
+ }
47
+ return node.children.reduce(
48
+ (total, child, i) => total + getLength(child) + (i < node.children.length - 1 ? 1 : 0),
49
+ 0
50
+ );
51
+ }
28
52
  export {
29
53
  DEFAULT_DELIMITERS,
54
+ getByteLength,
55
+ getLength,
30
56
  isEmptyNode
31
57
  };
32
58
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Nodes } from \"@rethinkhealth/hl7v2-ast\";\n\n// -------------\n// Delimiters\n// -------------\n\nexport const DEFAULT_DELIMITERS = {\n field: \"|\",\n component: \"^\",\n repetition: \"~\",\n subcomponent: \"&\",\n escape: \"\\\\\",\n segment: \"\\r\",\n};\n\n// -------------\n// General\n// -------------\n\n/**\n * Utility: check if a node is semantically empty\n */\nexport function isEmptyNode(node: Nodes | null | undefined): boolean {\n if (!node) {\n return true;\n }\n\n // If node has a \"value\" property (Subcomponent, maybe Component)\n if (\"value\" in node) {\n return !node.value || node.value.trim() === \"\";\n }\n\n // If node has children (Field, Component, Repetition, Segment, Root, etc.)\n if (\"children\" in node) {\n if (!node.children || node.children.length === 0) {\n return true;\n }\n\n // If node has more than one child, then it is considered non-empty\n if (node.children.length > 1) {\n return false;\n }\n\n // If node has only one child, then it is considered empty if the child is also empty\n return isEmptyNode(node.children[0]);\n }\n\n // Fallback: consider unknown node as non-empty\n return false;\n}\n"],"mappings":";AAMO,IAAM,qBAAqB;AAAA,EAChC,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AACX;AASO,SAAS,YAAY,MAAyC;AACnE,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,MAAM;AACnB,WAAO,CAAC,KAAK,SAAS,KAAK,MAAM,KAAK,MAAM;AAAA,EAC9C;AAGA,MAAI,cAAc,MAAM;AACtB,QAAI,CAAC,KAAK,YAAY,KAAK,SAAS,WAAW,GAAG;AAChD,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,aAAO;AAAA,IACT;AAGA,WAAO,YAAY,KAAK,SAAS,CAAC,CAAC;AAAA,EACrC;AAGA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Nodes } from \"@rethinkhealth/hl7v2-ast\";\n\n// -------------\n// Delimiters\n// -------------\n\nexport const DEFAULT_DELIMITERS = {\n field: \"|\",\n component: \"^\",\n repetition: \"~\",\n subcomponent: \"&\",\n escape: \"\\\\\",\n segment: \"\\r\",\n};\n\n// -------------\n// General\n// -------------\n\n/**\n * Utility: check if a node is semantically empty\n */\nexport function isEmptyNode(node: Nodes | null | undefined): boolean {\n if (!node) {\n return true;\n }\n\n // If node has a \"value\" property (Subcomponent, maybe Component)\n if (\"value\" in node) {\n return !node.value || node.value.trim() === \"\";\n }\n\n // If node has children (Field, Component, Repetition, Segment, Root, etc.)\n if (\"children\" in node) {\n if (!node.children || node.children.length === 0) {\n return true;\n }\n\n // If node has more than one child, then it is considered non-empty\n if (node.children.length > 1) {\n return false;\n }\n\n // If node has only one child, then it is considered empty if the child is also empty\n return isEmptyNode(node.children[0]);\n }\n\n // Fallback: consider unknown node as non-empty\n return false;\n}\n\n// -------------\n// Byte Length\n// -------------\n\n/**\n * Calculate the byte length of any HL7v2 AST node.\n *\n * For literal nodes (Subcomponent, SegmentHeader), returns the UTF-8 byte length of the value.\n * For parent nodes, recursively calculates the length of all children plus 1 byte\n * per separator (assumed to be single-byte delimiters).\n *\n * @param node - The HL7v2 AST node to measure\n * @returns The total byte length of the node when serialized\n *\n * @example\n * ```ts\n * const field: Field = { type: \"field\", children: [...] };\n * const length = getByteLength(field); // e.g., 42\n * ```\n */\nexport function getByteLength(node: Nodes | null | undefined): number {\n if (!node) {\n return 0;\n }\n\n if (\"value\" in node) {\n return Buffer.byteLength(node.value, \"utf8\");\n }\n\n return node.children.reduce(\n (total, child, i) =>\n total + getByteLength(child) + (i < node.children.length - 1 ? 1 : 0),\n 0\n );\n}\n\n// -------------\n// Length\n// -------------\n\n/**\n * Calculate the string length of any HL7v2 AST node.\n *\n * For literal nodes (Subcomponent, SegmentHeader), returns `value.length`.\n * For parent nodes, recursively calculates the length of all children plus 1\n * per separator (assumed to be single-character delimiters).\n *\n * Note: Returns JavaScript string length (UTF-16 code units). For UTF-8 byte\n * length (e.g., for wire protocol), use `getByteLength` instead.\n *\n * @param node - The HL7v2 AST node to measure\n * @returns The total string length of the node when serialized\n *\n * @example\n * ```ts\n * const field: Field = { type: \"field\", children: [...] };\n * const length = getLength(field); // e.g., 42\n * ```\n */\nexport function getLength(node: Nodes | null | undefined): number {\n if (!node) {\n return 0;\n }\n\n if (\"value\" in node) {\n return node.value.length;\n }\n\n return node.children.reduce(\n (total, child, i) =>\n total + getLength(child) + (i < node.children.length - 1 ? 1 : 0),\n 0\n );\n}\n"],"mappings":";AAMO,IAAM,qBAAqB;AAAA,EAChC,OAAO;AAAA,EACP,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AACX;AASO,SAAS,YAAY,MAAyC;AACnE,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,MAAM;AACnB,WAAO,CAAC,KAAK,SAAS,KAAK,MAAM,KAAK,MAAM;AAAA,EAC9C;AAGA,MAAI,cAAc,MAAM;AACtB,QAAI,CAAC,KAAK,YAAY,KAAK,SAAS,WAAW,GAAG;AAChD,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,aAAO;AAAA,IACT;AAGA,WAAO,YAAY,KAAK,SAAS,CAAC,CAAC;AAAA,EACrC;AAGA,SAAO;AACT;AAsBO,SAAS,cAAc,MAAwC;AACpE,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,MAAM;AACnB,WAAO,OAAO,WAAW,KAAK,OAAO,MAAM;AAAA,EAC7C;AAEA,SAAO,KAAK,SAAS;AAAA,IACnB,CAAC,OAAO,OAAO,MACb,QAAQ,cAAc,KAAK,KAAK,IAAI,KAAK,SAAS,SAAS,IAAI,IAAI;AAAA,IACrE;AAAA,EACF;AACF;AAyBO,SAAS,UAAU,MAAwC;AAChE,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,MAAM;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO,KAAK,SAAS;AAAA,IACnB,CAAC,OAAO,OAAO,MACb,QAAQ,UAAU,KAAK,KAAK,IAAI,KAAK,SAAS,SAAS,IAAI,IAAI;AAAA,IACjE;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rethinkhealth/hl7v2-utils",
3
3
  "description": "hl7v2 utilities",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Melek Somai",
@@ -24,9 +24,9 @@
24
24
  "unist-builder": "^4.0.0",
25
25
  "vfile": "^6.0.3",
26
26
  "vitest": "^4.0.6",
27
- "@rethinkhealth/hl7v2-ast": "0.3.0",
28
- "@rethinkhealth/testing": "0.0.2",
29
- "@rethinkhealth/tsconfig": "0.0.1"
27
+ "@rethinkhealth/hl7v2-ast": "0.3.1",
28
+ "@rethinkhealth/tsconfig": "0.0.1",
29
+ "@rethinkhealth/testing": "0.0.2"
30
30
  },
31
31
  "repository": "rethinkhealth/hl7v2.git",
32
32
  "homepage": "https://www.rethinkhealth.io/hl7v2/docs",