@glion/util-visit 0.14.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/LICENSE +21 -0
- package/README.md +486 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Rethink Health
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# @glion/util-visit
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
This package provides a lightweight, type-safe visitor pattern for traversing [HL7v2 AST][hl7v2-ast] trees. Built on top of the battle-tested [unist-util-visit-parents][], it adds HL7v2-specific context while delegating core traversal to a proven implementation.
|
|
6
|
+
|
|
7
|
+
### What is this?
|
|
8
|
+
|
|
9
|
+
`hl7v2-util-visit` enables you to walk through any HL7v2 AST tree — from the root message down to individual subcomponents — with full context about where you are in the hierarchy. The visitor pattern:
|
|
10
|
+
|
|
11
|
+
- **Works from any starting node** (Root, Segment, Field, Component, etc.)
|
|
12
|
+
- **Tracks ancestors** from traversal root to current node
|
|
13
|
+
- **Provides visit info** (index, sequence, depth, metadata)
|
|
14
|
+
- **Efficient O(n) traversal** — Pre-computed index map for O(1) lookups
|
|
15
|
+
- **Battle-tested core** — Delegates traversal to `unist-util-visit-parents`
|
|
16
|
+
|
|
17
|
+
### When should I use this?
|
|
18
|
+
|
|
19
|
+
Use `hl7v2-util-visit` when you need to:
|
|
20
|
+
|
|
21
|
+
- Validate HL7v2 message structure
|
|
22
|
+
- Transform or annotate AST nodes
|
|
23
|
+
- Extract specific fields with context
|
|
24
|
+
- Analyze message patterns across the tree
|
|
25
|
+
- Implement custom processing rules that need parent/ancestor awareness
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install @glion/util-visit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Use
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { visit, EXIT, SKIP } from "@glion/util-visit";
|
|
37
|
+
import { parse } from "@glion/parser";
|
|
38
|
+
|
|
39
|
+
const message = parse("MSH|^~\\&|...\rPID|...");
|
|
40
|
+
|
|
41
|
+
// Visit all segments
|
|
42
|
+
visit(message, "segment", (node, ancestors, info) => {
|
|
43
|
+
console.log(`Segment: ${info.metadata?.header} at depth ${info.depth}`);
|
|
44
|
+
});
|
|
45
|
+
// => Segment: MSH at depth 2
|
|
46
|
+
// => Segment: PID at depth 2
|
|
47
|
+
|
|
48
|
+
// Find fields with parent context
|
|
49
|
+
visit(message, "field", (node, ancestors, info) => {
|
|
50
|
+
const segment = ancestors.find((n) => n.type === "segment");
|
|
51
|
+
console.log(`Field ${info.sequence} in segment`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Skip processing of sensitive segments
|
|
55
|
+
visit(message, (node, ancestors, info) => {
|
|
56
|
+
if (node.type === "segment" && info.metadata?.header === "NTE") {
|
|
57
|
+
return SKIP; // Skip NTE segment children
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `visit(tree, visitor)`
|
|
65
|
+
|
|
66
|
+
### `visit(tree, test, visitor)`
|
|
67
|
+
|
|
68
|
+
Visit nodes in an HL7v2 AST tree.
|
|
69
|
+
|
|
70
|
+
#### Parameters
|
|
71
|
+
|
|
72
|
+
- **`tree`** (`Nodes`) — Tree to traverse (can be any node type, not just Root)
|
|
73
|
+
- **`test`** (`string | Partial<Nodes> | Test`) — Optional filter:
|
|
74
|
+
- `string`: Match nodes by type (e.g., `'segment'`)
|
|
75
|
+
- `Partial<Nodes>`: Match nodes with matching properties (e.g., `{ name: 'PATIENT_GROUP' }`)
|
|
76
|
+
- `Test`: Custom function `(node, ancestors) => boolean`
|
|
77
|
+
- **`visitor`** (`Visitor`) — Function called for each matching node
|
|
78
|
+
|
|
79
|
+
#### Returns
|
|
80
|
+
|
|
81
|
+
`void`
|
|
82
|
+
|
|
83
|
+
#### Important: Test vs Visitor Functions
|
|
84
|
+
|
|
85
|
+
**If you pass a function as the second argument, it is always treated as a Visitor, never as a Test.**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// WRONG - testFn will be treated as a visitor, not a test
|
|
89
|
+
visit(ast, (node) => node.type === 'segment', ...); // Missing visitor!
|
|
90
|
+
|
|
91
|
+
// CORRECT - Explicit 3-argument form
|
|
92
|
+
visit(ast, (node) => node.type === 'segment', (node, ancestors, info) => {
|
|
93
|
+
console.log('Visiting segment');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// CORRECT - Use string or object for simple tests
|
|
97
|
+
visit(ast, 'segment', (node, ancestors, info) => {
|
|
98
|
+
console.log('Visiting segment');
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Visitor Function
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
type Visitor<T extends Nodes = Nodes> = (
|
|
106
|
+
node: T,
|
|
107
|
+
ancestors: Nodes[],
|
|
108
|
+
info: VisitInfo
|
|
109
|
+
) => VisitorResult;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The visitor receives:
|
|
113
|
+
|
|
114
|
+
- **`node`** — Current AST node
|
|
115
|
+
- **`ancestors`** — Array of ancestor nodes from root to parent (not including current node)
|
|
116
|
+
- **`info`** — Visit information with index, sequence, depth, and metadata
|
|
117
|
+
|
|
118
|
+
The visitor can return:
|
|
119
|
+
|
|
120
|
+
- `undefined` or `void` — Continue traversal normally
|
|
121
|
+
- `SKIP` — Skip children of current node
|
|
122
|
+
- `EXIT` — Stop traversal immediately
|
|
123
|
+
|
|
124
|
+
### VisitInfo
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface VisitInfo {
|
|
128
|
+
/** 0-based index among siblings */
|
|
129
|
+
index: number;
|
|
130
|
+
|
|
131
|
+
/** 1-based sequence (HL7v2 convention). For segment-header: 0 */
|
|
132
|
+
sequence: number;
|
|
133
|
+
|
|
134
|
+
/** 1-based depth in tree (root = 1) */
|
|
135
|
+
depth: number;
|
|
136
|
+
|
|
137
|
+
/** Metadata (e.g., { header: "MSH" } or { name: "PATIENT" }) */
|
|
138
|
+
metadata: Record<string, unknown> | undefined;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Important**: `index` and `sequence` represent the node's **position in the tree**, not its position among filtered results. For example:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Structure: MSH segment with fields at positions 1, 2, 3, 4
|
|
146
|
+
// Filter matches only field 3
|
|
147
|
+
|
|
148
|
+
visit(
|
|
149
|
+
ast,
|
|
150
|
+
(n) => n.type === "field" && hasContent(n),
|
|
151
|
+
(node, ancestors, info) => {
|
|
152
|
+
console.log(info.sequence); // => 3 (position in segment, not "1st match")
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This is correct because HL7v2 paths like `PID.3` refer to tree positions, not filtered positions.
|
|
158
|
+
|
|
159
|
+
### Automatic Metadata Extraction
|
|
160
|
+
|
|
161
|
+
The `metadata` field is populated automatically with common metadata:
|
|
162
|
+
|
|
163
|
+
| Node Type | Metadata Key | Description |
|
|
164
|
+
| --------- | ------------ | ------------------------------------------- |
|
|
165
|
+
| `segment` | `header` | Segment identifier (e.g., `"MSH"`, `"PID"`) |
|
|
166
|
+
| `group` | `name` | Group name (e.g., `"PATIENT_GROUP"`) |
|
|
167
|
+
|
|
168
|
+
### Exports
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import {
|
|
172
|
+
visit, // Main traversal function
|
|
173
|
+
EXIT, // Return to stop traversal
|
|
174
|
+
SKIP, // Return to skip children
|
|
175
|
+
} from "@glion/util-visit";
|
|
176
|
+
|
|
177
|
+
import type {
|
|
178
|
+
VisitInfo, // { index, sequence, depth, metadata }
|
|
179
|
+
Visitor, // (node, ancestors, info) => VisitorResult
|
|
180
|
+
VisitorResult, // Return type from visitor
|
|
181
|
+
Test, // Filter predicate
|
|
182
|
+
Predicate, // (node, ancestors) => boolean
|
|
183
|
+
} from "@glion/util-visit";
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Examples
|
|
187
|
+
|
|
188
|
+
### Filter by Node Type
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
visit(ast, "segment", (node, ancestors, info) => {
|
|
192
|
+
console.log(`Found segment: ${info.metadata?.header}`);
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Filter by Properties
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
visit(ast, { name: "PATIENT_GROUP" }, (node, ancestors, info) => {
|
|
200
|
+
console.log("Inside PATIENT_GROUP");
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Custom Test Function
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Visit fields in MSH segment only
|
|
208
|
+
visit(
|
|
209
|
+
ast,
|
|
210
|
+
(node, ancestors) => {
|
|
211
|
+
const parent = ancestors.at(-1);
|
|
212
|
+
return node.type === "field" && parent?.type === "segment";
|
|
213
|
+
},
|
|
214
|
+
(node, ancestors, info) => {
|
|
215
|
+
console.log(`Field at sequence ${info.sequence}`);
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Access Parent and Ancestors
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
visit(ast, "component", (node, ancestors, info) => {
|
|
224
|
+
// Get immediate parent
|
|
225
|
+
const parent = ancestors.at(-1);
|
|
226
|
+
|
|
227
|
+
// Find closest segment ancestor
|
|
228
|
+
const segment = ancestors.findLast((n) => n.type === "segment");
|
|
229
|
+
|
|
230
|
+
console.log(`Component at depth ${info.depth}`);
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Control Flow: Skip Children
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
visit(ast, (node, ancestors, info) => {
|
|
238
|
+
if (node.type === "segment" && info.metadata?.header === "OBX") {
|
|
239
|
+
return SKIP; // Don't process OBX segment children
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Control Flow: Exit Early
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { EXIT } from '@glion/util-visit';
|
|
248
|
+
|
|
249
|
+
let found = false;
|
|
250
|
+
visit(ast, 'field', (node, ancestors, info) => {
|
|
251
|
+
if (/* some condition */) {
|
|
252
|
+
found = true;
|
|
253
|
+
return EXIT; // Stop traversal completely
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Start from Any Node
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { s, f, c } from "@glion/builder";
|
|
262
|
+
|
|
263
|
+
// Create a standalone segment
|
|
264
|
+
const segment = s("PID", f(c("value1")), f(c("value2")));
|
|
265
|
+
|
|
266
|
+
// Traverse from segment (not root)
|
|
267
|
+
visit(segment, "field", (node, ancestors, info) => {
|
|
268
|
+
console.log(`Field at sequence ${info.sequence}`);
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Track Nesting Levels
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
visit(ast, (node, ancestors, info) => {
|
|
276
|
+
const indent = " ".repeat(info.depth - 1);
|
|
277
|
+
console.log(`${indent}${node.type} [${info.sequence}]`);
|
|
278
|
+
});
|
|
279
|
+
// Output:
|
|
280
|
+
// root [1]
|
|
281
|
+
// segment [1]
|
|
282
|
+
// segment-header [0]
|
|
283
|
+
// field [1]
|
|
284
|
+
// field-repetition [1]
|
|
285
|
+
// component [1]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Group Hierarchy Navigation
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
visit(ast, "segment", (node, ancestors, info) => {
|
|
292
|
+
// Get all parent groups
|
|
293
|
+
const groups = ancestors
|
|
294
|
+
.filter((n) => n.type === "group")
|
|
295
|
+
.map((n) => (n as any).name)
|
|
296
|
+
.filter((name): name is string => typeof name === "string");
|
|
297
|
+
|
|
298
|
+
console.log(`${info.metadata?.header} is in groups: ${groups.join(" > ")}`);
|
|
299
|
+
});
|
|
300
|
+
// => PID is in groups: PATIENT_GROUP
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Real-World Use Cases
|
|
304
|
+
|
|
305
|
+
### Validate Required Fields
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
function validateRequiredFields(ast: Root): string[] {
|
|
309
|
+
const errors: string[] = [];
|
|
310
|
+
|
|
311
|
+
visit(ast, "segment", (node, ancestors, info) => {
|
|
312
|
+
const segment = node as Segment;
|
|
313
|
+
const header = info.metadata?.header;
|
|
314
|
+
|
|
315
|
+
// MSH segment must have at least 12 fields
|
|
316
|
+
if (header === "MSH" && segment.children.length < 12) {
|
|
317
|
+
errors.push(`MSH segment missing required fields`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// PID segment must have patient ID (PID.3)
|
|
321
|
+
if (header === "PID") {
|
|
322
|
+
const patientId = segment.children[3];
|
|
323
|
+
if (!patientId || patientId.children.length === 0) {
|
|
324
|
+
errors.push("PID segment missing required Patient ID (PID.3)");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return errors;
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Extract Specific Data with Context
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
interface PatientName {
|
|
337
|
+
name: string;
|
|
338
|
+
sequence: number;
|
|
339
|
+
inGroup?: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function extractPatientNames(ast: Root): PatientName[] {
|
|
343
|
+
const names: PatientName[] = [];
|
|
344
|
+
|
|
345
|
+
visit(ast, "segment", (node, ancestors, info) => {
|
|
346
|
+
if (info.metadata?.header !== "PID") return;
|
|
347
|
+
|
|
348
|
+
const segment = node as Segment;
|
|
349
|
+
const nameField = segment.children[5];
|
|
350
|
+
if (nameField?.children[0]?.children[0]) {
|
|
351
|
+
const nameComponent = nameField.children[0].children[0];
|
|
352
|
+
const name = (nameComponent.children[0] as Subcomponent)?.value || "";
|
|
353
|
+
|
|
354
|
+
const groupAncestor = ancestors.find((n) => n.type === "group");
|
|
355
|
+
|
|
356
|
+
names.push({
|
|
357
|
+
name,
|
|
358
|
+
sequence: info.sequence,
|
|
359
|
+
inGroup: groupAncestor ? (groupAncestor as any).name : undefined,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return names;
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Message Structure Analysis
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
interface MessageStructure {
|
|
372
|
+
segmentCount: number;
|
|
373
|
+
groupCount: number;
|
|
374
|
+
maxDepth: number;
|
|
375
|
+
segmentTypes: Record<string, number>;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function analyzeStructure(ast: Root): MessageStructure {
|
|
379
|
+
const structure: MessageStructure = {
|
|
380
|
+
segmentCount: 0,
|
|
381
|
+
groupCount: 0,
|
|
382
|
+
maxDepth: 0,
|
|
383
|
+
segmentTypes: {},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
visit(ast, (node, ancestors, info) => {
|
|
387
|
+
structure.maxDepth = Math.max(structure.maxDepth, info.depth);
|
|
388
|
+
|
|
389
|
+
if (node.type === "segment") {
|
|
390
|
+
structure.segmentCount++;
|
|
391
|
+
const header = info.metadata?.header as string;
|
|
392
|
+
if (header) {
|
|
393
|
+
structure.segmentTypes[header] =
|
|
394
|
+
(structure.segmentTypes[header] || 0) + 1;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (node.type === "group") {
|
|
399
|
+
structure.groupCount++;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return structure;
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Find First Match and Exit
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
function findFirstObservation(ast: Root, targetCode: string): string | null {
|
|
411
|
+
let result: string | null = null;
|
|
412
|
+
|
|
413
|
+
visit(ast, "segment", (node, ancestors, info) => {
|
|
414
|
+
if (info.metadata?.header !== "OBX") return;
|
|
415
|
+
|
|
416
|
+
const segment = node as Segment;
|
|
417
|
+
const identifierField = segment.children[3];
|
|
418
|
+
const code = identifierField?.children[0]?.children[0]?.children[0]?.value;
|
|
419
|
+
|
|
420
|
+
if (code === targetCode) {
|
|
421
|
+
const valueField = segment.children[5];
|
|
422
|
+
result = valueField?.children[0]?.children[0]?.children[0]?.value || null;
|
|
423
|
+
return EXIT; // Stop traversal
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Architecture
|
|
432
|
+
|
|
433
|
+
This library is a thin wrapper around [unist-util-visit-parents][] that adds HL7v2-specific features:
|
|
434
|
+
|
|
435
|
+
1. **Core Traversal**: Delegates to `unist-util-visit-parents` (battle-tested with 50M+ weekly downloads)
|
|
436
|
+
2. **Index Optimization**: Pre-computes node indices for O(1) lookups (avoids O(n²) `indexOf` calls)
|
|
437
|
+
3. **HL7v2 Context**: Adds `VisitInfo` with sequence numbers, depth tracking, and metadata extraction
|
|
438
|
+
4. **Domain Conventions**: Implements HL7v2-specific indexing (segment-header sequence = 0, fields = 1,2,3...)
|
|
439
|
+
|
|
440
|
+
This design gives us:
|
|
441
|
+
|
|
442
|
+
- ✅ Proven traversal logic (EXIT/SKIP handling, edge cases)
|
|
443
|
+
- ✅ Mutation support (inherited from unist)
|
|
444
|
+
- ✅ Reverse traversal support (pass `reverse: true` to underlying library)
|
|
445
|
+
- ✅ Small codebase (~120 lines vs previous ~180 lines)
|
|
446
|
+
- ✅ Ecosystem compatibility (works with other unist utilities)
|
|
447
|
+
|
|
448
|
+
## Performance Characteristics
|
|
449
|
+
|
|
450
|
+
- **O(n) traversal** — Single pass through all nodes
|
|
451
|
+
- **O(n) index pre-computation** — One-time upfront cost
|
|
452
|
+
- **O(1) index lookups** — Avoids O(n²) `indexOf()` anti-pattern
|
|
453
|
+
- **O(d) ancestor construction** where d = depth (typically < 10 for HL7v2)
|
|
454
|
+
- **Minimal allocations** — Metadata extracted once per node
|
|
455
|
+
|
|
456
|
+
## Types
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
export type {
|
|
460
|
+
VisitInfo, // { index, sequence, depth, metadata }
|
|
461
|
+
Visitor, // (node, ancestors, info) => VisitorResult
|
|
462
|
+
VisitorResult, // void | false | 'skip' | ActionTuple
|
|
463
|
+
Test, // string | Partial | predicate | null
|
|
464
|
+
Predicate, // (node, ancestors) => boolean
|
|
465
|
+
} from "@glion/util-visit";
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Contributing
|
|
469
|
+
|
|
470
|
+
We welcome contributions! Please see our [Contributing Guide][github-contributing] for more details.
|
|
471
|
+
|
|
472
|
+
## Code of Conduct
|
|
473
|
+
|
|
474
|
+
To ensure a welcoming and positive environment, we have a [Code of Conduct][github-code-of-conduct] that all contributors and participants are expected to adhere to.
|
|
475
|
+
|
|
476
|
+
## License
|
|
477
|
+
|
|
478
|
+
Copyright 2025 Rethink Health, SUARL. All rights reserved.
|
|
479
|
+
|
|
480
|
+
This program is licensed to you under the terms of the [MIT License](https://opensource.org/licenses/MIT). See the [LICENSE][github-license] file for details.
|
|
481
|
+
|
|
482
|
+
[github-code-of-conduct]: https://github.com/rethinkhealth/glion/blob/main/CODE_OF_CONDUCT.md
|
|
483
|
+
[github-license]: https://github.com/rethinkhealth/glion/blob/main/LICENSE
|
|
484
|
+
[github-contributing]: https://github.com/rethinkhealth/glion/blob/main/CONTRIBUTING.md
|
|
485
|
+
[hl7v2-ast]: https://github.com/rethinkhealth/glion/tree/main/packages/hl7v2-ast
|
|
486
|
+
[unist-util-visit-parents]: https://github.com/syntax-tree/unist-util-visit-parents
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HL7v2 AST Visitor
|
|
3
|
+
*
|
|
4
|
+
* Wraps unist-util-visit-parents to add HL7v2-specific context
|
|
5
|
+
* (index, sequence, depth, metadata) while delegating core traversal.
|
|
6
|
+
*/
|
|
7
|
+
import type { Nodes } from "@glion/ast";
|
|
8
|
+
import type { Test, Visitor } from "./types";
|
|
9
|
+
export type { VisitorResult } from "unist-util-visit-parents";
|
|
10
|
+
export { EXIT, SKIP } from "unist-util-visit-parents";
|
|
11
|
+
export type { Predicate, Test, VisitInfo, Visitor } from "./types";
|
|
12
|
+
export declare function visit(tree: Nodes, visitor: Visitor): void;
|
|
13
|
+
export declare function visit<Type extends Nodes["type"]>(tree: Nodes, test: Type, visitor: Visitor<Extract<Nodes, {
|
|
14
|
+
type: Type;
|
|
15
|
+
}>>): void;
|
|
16
|
+
export declare function visit<T extends Nodes>(tree: Nodes, test: Test<T>, visitor: Visitor<T>): void;
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,OAAO,KAAK,EAAE,IAAI,EAAa,OAAO,EAAE,MAAM,SAAS,CAAC;AAGxD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEtD,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AA8BnE,wBAAgB,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;AAC3D,wBAAgB,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,MAAM,CAAC,EAC9C,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,GAC/C,IAAI,CAAC;AACR,wBAAgB,KAAK,CAAC,CAAC,SAAS,KAAK,EACnC,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACb,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,IAAI,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { EXIT, SKIP, visitParents } from "unist-util-visit-parents";
|
|
2
|
+
//#region src/utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create test predicate from various input types.
|
|
5
|
+
*
|
|
6
|
+
* Assumptions:
|
|
7
|
+
*
|
|
8
|
+
* - Null test matches all nodes
|
|
9
|
+
* - String test matches by node.type property
|
|
10
|
+
* - Object test uses strict equality (===) for property matching
|
|
11
|
+
* - Explicit undefined values in test object check for property absence
|
|
12
|
+
* - Dangerous keys (**proto**, constructor, prototype) are filtered for security
|
|
13
|
+
*
|
|
14
|
+
* @param test - Filter criteria: null (all), string (type), object
|
|
15
|
+
* (properties), or function
|
|
16
|
+
* @returns Predicate function that returns true if node matches test criteria
|
|
17
|
+
*/
|
|
18
|
+
function createTest(test) {
|
|
19
|
+
if (test === null) return () => true;
|
|
20
|
+
if (typeof test === "string") return (node) => node.type === test;
|
|
21
|
+
if (typeof test === "function") return test;
|
|
22
|
+
return (node) => {
|
|
23
|
+
for (const key of Object.keys(test)) {
|
|
24
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
25
|
+
const testValue = test[key];
|
|
26
|
+
const nodeValue = node[key];
|
|
27
|
+
if (testValue === void 0) {
|
|
28
|
+
if (Object.hasOwn(node, key) && nodeValue !== void 0) return false;
|
|
29
|
+
} else if (nodeValue !== testValue) return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/index.ts
|
|
36
|
+
/**
|
|
37
|
+
* Build index map for O(1) child index lookups.
|
|
38
|
+
* Pre-computes the index of each node within its parent's children array.
|
|
39
|
+
*
|
|
40
|
+
* @param tree - Root of tree to index
|
|
41
|
+
* @returns WeakMap mapping each node to its index in parent.children
|
|
42
|
+
*/
|
|
43
|
+
function buildIndexMap(tree) {
|
|
44
|
+
const map = /* @__PURE__ */ new WeakMap();
|
|
45
|
+
function traverse(node) {
|
|
46
|
+
if ("children" in node && Array.isArray(node.children)) for (let i = 0; i < node.children.length; i++) {
|
|
47
|
+
const child = node.children[i];
|
|
48
|
+
if (child) {
|
|
49
|
+
map.set(child, i);
|
|
50
|
+
traverse(child);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
traverse(tree);
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Visit nodes in an HL7 AST tree.
|
|
59
|
+
* Wraps unist-util-visit-parents to add HL7v2-specific context.
|
|
60
|
+
*
|
|
61
|
+
* @param tree - The tree to traverse (can be any node type, not just Root)
|
|
62
|
+
* @param test - Optional test to filter nodes (type string, partial match, or
|
|
63
|
+
* Test function)
|
|
64
|
+
* @param visitor - Function called for each matching node
|
|
65
|
+
*
|
|
66
|
+
* **Important**: If you pass a function as the second argument, it is always
|
|
67
|
+
* treated as a Visitor, never as a Test. To use a Test function, you MUST
|
|
68
|
+
* provide both the test and visitor parameters: `visit(tree, testFn,
|
|
69
|
+
* visitorFn)`.
|
|
70
|
+
*/
|
|
71
|
+
function visit(tree, arg2, arg3) {
|
|
72
|
+
let test = null;
|
|
73
|
+
let visitor;
|
|
74
|
+
if (arg3 === void 0) visitor = arg2;
|
|
75
|
+
else {
|
|
76
|
+
test = arg2;
|
|
77
|
+
visitor = arg3;
|
|
78
|
+
}
|
|
79
|
+
const predicate = createTest(test);
|
|
80
|
+
const indexMap = buildIndexMap(tree);
|
|
81
|
+
visitParents(tree, (node, ancestors) => {
|
|
82
|
+
if (!predicate(node, ancestors)) return;
|
|
83
|
+
const parent = ancestors.at(-1);
|
|
84
|
+
const childIndex = parent ? indexMap.get(node) ?? 0 : 0;
|
|
85
|
+
const info = {
|
|
86
|
+
depth: ancestors.length + 1,
|
|
87
|
+
index: parent ? childIndex : 0,
|
|
88
|
+
metadata: "name" in node && typeof node.name === "string" ? { name: node.name } : void 0,
|
|
89
|
+
sequence: parent ? childIndex + 1 : 1
|
|
90
|
+
};
|
|
91
|
+
return visitor(node, ancestors, info);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { EXIT, SKIP, visit };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import type { Nodes } from \"@glion/ast\";\n\nimport type { Predicate, Test } from \"./types\";\n\n/**\n * Create test predicate from various input types.\n *\n * Assumptions:\n *\n * - Null test matches all nodes\n * - String test matches by node.type property\n * - Object test uses strict equality (===) for property matching\n * - Explicit undefined values in test object check for property absence\n * - Dangerous keys (**proto**, constructor, prototype) are filtered for security\n *\n * @param test - Filter criteria: null (all), string (type), object\n * (properties), or function\n * @returns Predicate function that returns true if node matches test criteria\n */\nexport function createTest(test: Test<Nodes>): Predicate {\n if (test === null) {\n return () => true;\n }\n if (typeof test === \"string\") {\n return (node) => node.type === test;\n }\n if (typeof test === \"function\") {\n return test;\n }\n // Object property matching\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Property matching requires checking multiple conditions\n return (node) => {\n for (const key of Object.keys(test)) {\n // Guard against prototype pollution\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n continue;\n }\n\n const testValue = test[key as keyof typeof test];\n // oxlint-disable-next-line typescript/no-explicit-any\n const nodeValue = (node as any)[key];\n\n // If test has explicit undefined, check property doesn't exist or is undefined\n if (testValue === undefined) {\n if (Object.hasOwn(node, key) && nodeValue !== undefined) {\n return false;\n }\n } else if (nodeValue !== testValue) {\n // For non-undefined values, strict equality check\n return false;\n }\n }\n return true;\n };\n}\n","/**\n * HL7v2 AST Visitor\n *\n * Wraps unist-util-visit-parents to add HL7v2-specific context\n * (index, sequence, depth, metadata) while delegating core traversal.\n */\n\nimport type { Nodes } from \"@glion/ast\";\nimport { visitParents } from \"unist-util-visit-parents\";\n\nimport type { Test, VisitInfo, Visitor } from \"./types\";\nimport { createTest } from \"./utils\";\n\nexport type { VisitorResult } from \"unist-util-visit-parents\";\n\n// biome-ignore lint/performance/noBarrelFile: fine\nexport { EXIT, SKIP } from \"unist-util-visit-parents\";\n\nexport type { Predicate, Test, VisitInfo, Visitor } from \"./types\";\n\n/**\n * Build index map for O(1) child index lookups.\n * Pre-computes the index of each node within its parent's children array.\n *\n * @param tree - Root of tree to index\n * @returns WeakMap mapping each node to its index in parent.children\n */\nfunction buildIndexMap(tree: Nodes): WeakMap<Nodes, number> {\n const map = new WeakMap<Nodes, number>();\n\n function traverse(node: Nodes): void {\n if (\"children\" in node && Array.isArray(node.children)) {\n // oxlint-disable-next-line no-plusplus\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n if (child) {\n map.set(child, i);\n traverse(child);\n }\n }\n }\n }\n\n traverse(tree);\n return map;\n}\n\n// Overload signatures\nexport function visit(tree: Nodes, visitor: Visitor): void;\nexport function visit<Type extends Nodes[\"type\"]>(\n tree: Nodes,\n test: Type,\n visitor: Visitor<Extract<Nodes, { type: Type }>>\n): void;\nexport function visit<T extends Nodes>(\n tree: Nodes,\n test: Test<T>,\n visitor: Visitor<T>\n): void;\n\n/**\n * Visit nodes in an HL7 AST tree.\n * Wraps unist-util-visit-parents to add HL7v2-specific context.\n *\n * @param tree - The tree to traverse (can be any node type, not just Root)\n * @param test - Optional test to filter nodes (type string, partial match, or\n * Test function)\n * @param visitor - Function called for each matching node\n *\n * **Important**: If you pass a function as the second argument, it is always\n * treated as a Visitor, never as a Test. To use a Test function, you MUST\n * provide both the test and visitor parameters: `visit(tree, testFn,\n * visitorFn)`.\n */\nexport function visit<T extends Nodes>(\n tree: Nodes,\n arg2: Visitor<T> | Test<T>,\n arg3?: Visitor<T>\n): void {\n let test: Test<T> = null;\n let visitor: Visitor<T>;\n\n if (arg3 === undefined) {\n visitor = arg2 as Visitor<T>;\n } else {\n test = arg2 as Test<T>;\n visitor = arg3;\n }\n\n const predicate = createTest(test as Test<Nodes>);\n\n // Pre-compute index map for O(1) lookups\n const indexMap = buildIndexMap(tree);\n\n // Delegate traversal to unist-util-visit-parents\n visitParents(tree, (node, ancestors) => {\n // Only call visitor if node matches test\n if (!predicate(node, ancestors)) {\n return; // Continue traversal but skip visitor\n }\n\n // Compute HL7v2-specific context\n const parent = ancestors.at(-1);\n\n // For root node (no parent), use defaults\n // For children, look up their index in the parent's children array\n const childIndex = parent ? (indexMap.get(node) ?? 0) : 0;\n\n const info: VisitInfo = {\n depth: ancestors.length + 1,\n index: parent ? childIndex : 0,\n metadata:\n \"name\" in node && typeof node.name === \"string\"\n ? { name: node.name }\n : undefined,\n sequence: parent ? childIndex + 1 : 1,\n };\n\n // Call user visitor with augmented signature\n return visitor(node as T, ancestors, info);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,SAAgB,WAAW,MAA8B;AACvD,KAAI,SAAS,KACX,cAAa;AAEf,KAAI,OAAO,SAAS,SAClB,SAAQ,SAAS,KAAK,SAAS;AAEjC,KAAI,OAAO,SAAS,WAClB,QAAO;AAIT,SAAQ,SAAS;AACf,OAAK,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AAEnC,OAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAC1D;GAGF,MAAM,YAAY,KAAK;GAEvB,MAAM,YAAa,KAAa;AAGhC,OAAI,cAAc,KAAA;QACZ,OAAO,OAAO,MAAM,IAAI,IAAI,cAAc,KAAA,EAC5C,QAAO;cAEA,cAAc,UAEvB,QAAO;;AAGX,SAAO;;;;;;;;;;;;ACzBX,SAAS,cAAc,MAAqC;CAC1D,MAAM,sBAAM,IAAI,SAAwB;CAExC,SAAS,SAAS,MAAmB;AACnC,MAAI,cAAc,QAAQ,MAAM,QAAQ,KAAK,SAAS,CAEpD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;GAC7C,MAAM,QAAQ,KAAK,SAAS;AAC5B,OAAI,OAAO;AACT,QAAI,IAAI,OAAO,EAAE;AACjB,aAAS,MAAM;;;;AAMvB,UAAS,KAAK;AACd,QAAO;;;;;;;;;;;;;;;;AA8BT,SAAgB,MACd,MACA,MACA,MACM;CACN,IAAI,OAAgB;CACpB,IAAI;AAEJ,KAAI,SAAS,KAAA,EACX,WAAU;MACL;AACL,SAAO;AACP,YAAU;;CAGZ,MAAM,YAAY,WAAW,KAAoB;CAGjD,MAAM,WAAW,cAAc,KAAK;AAGpC,cAAa,OAAO,MAAM,cAAc;AAEtC,MAAI,CAAC,UAAU,MAAM,UAAU,CAC7B;EAIF,MAAM,SAAS,UAAU,GAAG,GAAG;EAI/B,MAAM,aAAa,SAAU,SAAS,IAAI,KAAK,IAAI,IAAK;EAExD,MAAM,OAAkB;GACtB,OAAO,UAAU,SAAS;GAC1B,OAAO,SAAS,aAAa;GAC7B,UACE,UAAU,QAAQ,OAAO,KAAK,SAAS,WACnC,EAAE,MAAM,KAAK,MAAM,GACnB,KAAA;GACN,UAAU,SAAS,aAAa,IAAI;GACrC;AAGD,SAAO,QAAQ,MAAW,WAAW,KAAK;GAC1C"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Nodes } from "@glion/ast";
|
|
2
|
+
import type { VisitorResult } from "unist-util-visit-parents";
|
|
3
|
+
/**
|
|
4
|
+
* Visit information computed for each node.
|
|
5
|
+
* This is HL7v2-specific context not provided by unist-util-visit-parents.
|
|
6
|
+
*/
|
|
7
|
+
export interface VisitInfo {
|
|
8
|
+
/** 0-based index among siblings */
|
|
9
|
+
index: number;
|
|
10
|
+
/** 1-based sequence (HL7v2 convention) */
|
|
11
|
+
sequence: number;
|
|
12
|
+
/** 1-based depth in tree (root = 1) */
|
|
13
|
+
depth: number;
|
|
14
|
+
/** Metadata extracted from node (e.g., { name: "MSH" } or { name: "PATIENT" }) */
|
|
15
|
+
metadata: Record<string, unknown> | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A function called for each node during traversal.
|
|
19
|
+
* Extends unist's visitor signature with HL7v2-specific context.
|
|
20
|
+
*
|
|
21
|
+
* @param node - Current node being visited
|
|
22
|
+
* @param ancestors - Array of ancestor nodes from root to parent
|
|
23
|
+
* @param info - HL7v2-specific visit info (index, sequence, depth, metadata)
|
|
24
|
+
* @returns VisitorResult to control traversal (EXIT, SKIP, etc.)
|
|
25
|
+
*/
|
|
26
|
+
export type Visitor<T extends Nodes = Nodes> = (node: T, ancestors: Nodes[], info: VisitInfo) => VisitorResult;
|
|
27
|
+
/**
|
|
28
|
+
* Predicate function to test if a node matches filter criteria.
|
|
29
|
+
* Compatible with unist-util-is predicates but uses our Nodes type.
|
|
30
|
+
*/
|
|
31
|
+
export type Predicate = (node: Nodes, ancestors: Nodes[]) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Filter criteria to determine which nodes to visit.
|
|
34
|
+
* Based on unist-util-is Test but specialized for HL7v2 AST.
|
|
35
|
+
*
|
|
36
|
+
* Can be:
|
|
37
|
+
* - string: matches `node.type`
|
|
38
|
+
* - object: matches properties (partial match)
|
|
39
|
+
* - function: predicate returning boolean or type guard
|
|
40
|
+
* - null: matches everything
|
|
41
|
+
*/
|
|
42
|
+
export type Test<T extends Nodes = Nodes> = string | Partial<T> | ((node: Nodes, ancestors: Nodes[]) => node is T) | ((node: Nodes, ancestors: Nodes[]) => boolean) | null;
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IAEd,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,IAAI,CAC7C,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,KAAK,EAAE,EAClB,IAAI,EAAE,SAAS,KACZ,aAAa,CAAC;AAEnB;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;AAErE;;;;;;;;;GASG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,IACpC,MAAM,GACN,OAAO,CAAC,CAAC,CAAC,GACV,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,GAChD,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAC9C,IAAI,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Nodes } from "@glion/ast";
|
|
2
|
+
import type { Predicate, Test } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Create test predicate from various input types.
|
|
5
|
+
*
|
|
6
|
+
* Assumptions:
|
|
7
|
+
*
|
|
8
|
+
* - Null test matches all nodes
|
|
9
|
+
* - String test matches by node.type property
|
|
10
|
+
* - Object test uses strict equality (===) for property matching
|
|
11
|
+
* - Explicit undefined values in test object check for property absence
|
|
12
|
+
* - Dangerous keys (**proto**, constructor, prototype) are filtered for security
|
|
13
|
+
*
|
|
14
|
+
* @param test - Filter criteria: null (all), string (type), object
|
|
15
|
+
* (properties), or function
|
|
16
|
+
* @returns Predicate function that returns true if node matches test criteria
|
|
17
|
+
*/
|
|
18
|
+
export declare function createTest(test: Test<Nodes>): Predicate;
|
|
19
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,CAmCvD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glion/util-visit",
|
|
3
|
+
"version": "0.14.1",
|
|
4
|
+
"description": "An AST visitor for HL7v2 messages",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"health",
|
|
7
|
+
"healthcare",
|
|
8
|
+
"hl7",
|
|
9
|
+
"hl7v2",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://glion.dev",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Melek Somai",
|
|
17
|
+
"email": "melek@rethinkhealth.io"
|
|
18
|
+
},
|
|
19
|
+
"repository": "rethinkhealth/glion.git",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"unist-util-visit-parents": "6.0.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.5.0",
|
|
37
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
38
|
+
"tsdown": "0.21.7",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vitest": "4.1.2",
|
|
41
|
+
"@glion/ast": "0.14.1",
|
|
42
|
+
"@glion/parser": "0.14.1",
|
|
43
|
+
"@glion/builder": "0.14.1",
|
|
44
|
+
"@glion/testing": "0.0.2",
|
|
45
|
+
"@glion/config": "0.14.1",
|
|
46
|
+
"@glion/tsconfig": "0.0.1"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
53
|
+
"check-types": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:coverage": "vitest run --coverage",
|
|
56
|
+
"test:watch": "vitest"
|
|
57
|
+
}
|
|
58
|
+
}
|