@atomic-ehr/fhirpath 0.0.1 → 0.0.2-canary.261bb9b.20250811212042

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.
Files changed (104) hide show
  1. package/README.md +62 -56
  2. package/dist/index.d.ts +368 -99
  3. package/dist/index.js +3623 -1253
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/analyzer.ts +779 -120
  7. package/src/boxing.ts +124 -0
  8. package/src/completion-provider.ts +636 -0
  9. package/src/cursor-nodes.ts +111 -0
  10. package/src/errors.ts +247 -0
  11. package/src/index.ts +97 -14
  12. package/src/inspect.ts +309 -176
  13. package/src/interpreter.ts +380 -71
  14. package/src/lexer.ts +1 -0
  15. package/src/model-provider.ts +282 -39
  16. package/src/operations/abs-function.ts +40 -16
  17. package/src/operations/aggregate-function.ts +21 -10
  18. package/src/operations/all-function.ts +23 -11
  19. package/src/operations/allFalse-function.ts +14 -9
  20. package/src/operations/allTrue-function.ts +14 -9
  21. package/src/operations/and-operator.ts +21 -12
  22. package/src/operations/anyFalse-function.ts +11 -7
  23. package/src/operations/anyTrue-function.ts +11 -7
  24. package/src/operations/as-operator.ts +2 -1
  25. package/src/operations/ceiling-function.ts +14 -8
  26. package/src/operations/children-function.ts +98 -0
  27. package/src/operations/combine-function.ts +10 -6
  28. package/src/operations/combine-operator.ts +19 -5
  29. package/src/operations/contains-function.ts +26 -12
  30. package/src/operations/contains-operator.ts +15 -7
  31. package/src/operations/count-function.ts +7 -4
  32. package/src/operations/defineVariable-function.ts +11 -7
  33. package/src/operations/descendants-function.ts +64 -0
  34. package/src/operations/distinct-function.ts +21 -7
  35. package/src/operations/div-operator.ts +16 -3
  36. package/src/operations/divide-operator.ts +11 -6
  37. package/src/operations/dot-operator.ts +4 -2
  38. package/src/operations/empty-function.ts +7 -4
  39. package/src/operations/endsWith-function.ts +28 -14
  40. package/src/operations/equal-operator.ts +19 -7
  41. package/src/operations/equivalent-operator.ts +16 -11
  42. package/src/operations/exclude-function.ts +18 -14
  43. package/src/operations/exists-function.ts +19 -10
  44. package/src/operations/first-function.ts +12 -7
  45. package/src/operations/floor-function.ts +14 -8
  46. package/src/operations/greater-operator.ts +10 -5
  47. package/src/operations/greater-or-equal-operator.ts +10 -5
  48. package/src/operations/iif-function.ts +27 -23
  49. package/src/operations/implies-operator.ts +23 -8
  50. package/src/operations/in-operator.ts +18 -8
  51. package/src/operations/index.ts +3 -0
  52. package/src/operations/indexOf-function.ts +28 -14
  53. package/src/operations/intersect-function.ts +23 -24
  54. package/src/operations/is-operator.ts +67 -15
  55. package/src/operations/isDistinct-function.ts +17 -9
  56. package/src/operations/join-function.ts +22 -9
  57. package/src/operations/last-function.ts +12 -7
  58. package/src/operations/length-function.ts +17 -8
  59. package/src/operations/less-operator.ts +10 -5
  60. package/src/operations/less-or-equal-operator.ts +10 -5
  61. package/src/operations/less-than.ts +26 -2
  62. package/src/operations/lower-function.ts +17 -8
  63. package/src/operations/minus-operator.ts +10 -5
  64. package/src/operations/mod-operator.ts +20 -3
  65. package/src/operations/multiply-operator.ts +12 -7
  66. package/src/operations/not-equal-operator.ts +16 -7
  67. package/src/operations/not-equivalent-operator.ts +16 -11
  68. package/src/operations/not-function.ts +17 -7
  69. package/src/operations/ofType-function.ts +139 -0
  70. package/src/operations/or-operator.ts +21 -12
  71. package/src/operations/plus-operator.ts +19 -7
  72. package/src/operations/power-function.ts +27 -13
  73. package/src/operations/replace-function.ts +33 -14
  74. package/src/operations/round-function.ts +29 -12
  75. package/src/operations/select-function.ts +17 -9
  76. package/src/operations/single-function.ts +10 -6
  77. package/src/operations/skip-function.ts +18 -9
  78. package/src/operations/split-function.ts +30 -13
  79. package/src/operations/sqrt-function.ts +14 -8
  80. package/src/operations/startsWith-function.ts +26 -12
  81. package/src/operations/subsetOf-function.ts +20 -15
  82. package/src/operations/substring-function.ts +43 -24
  83. package/src/operations/supersetOf-function.ts +20 -15
  84. package/src/operations/tail-function.ts +8 -4
  85. package/src/operations/take-function.ts +18 -9
  86. package/src/operations/toBoolean-function.ts +23 -14
  87. package/src/operations/toDecimal-function.ts +18 -9
  88. package/src/operations/toInteger-function.ts +22 -12
  89. package/src/operations/toString-function.ts +26 -16
  90. package/src/operations/trace-function.ts +19 -10
  91. package/src/operations/trim-function.ts +16 -7
  92. package/src/operations/truncate-function.ts +14 -8
  93. package/src/operations/unary-minus-operator.ts +23 -13
  94. package/src/operations/unary-plus-operator.ts +2 -1
  95. package/src/operations/union-function.ts +25 -28
  96. package/src/operations/union-operator.ts +2 -1
  97. package/src/operations/upper-function.ts +17 -8
  98. package/src/operations/where-function.ts +20 -11
  99. package/src/operations/xor-operator.ts +9 -4
  100. package/src/parser.ts +527 -8
  101. package/src/quantity-value.ts +4 -8
  102. package/src/registry.ts +147 -5
  103. package/src/types.ts +42 -19
  104. package/src/parser-base.ts +0 -400
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @atomic-ehr/fhirpath
2
2
 
3
+ [![npm version](https://badge.fury.io/js/@atomic-ehr%2Ffhirpath.svg)](https://www.npmjs.com/package/@atomic-ehr/fhirpath)
4
+
3
5
  A TypeScript implementation of FHIRPath, the path-based navigation and extraction language for FHIR (Fast Healthcare Interoperability Resources).
4
6
 
5
7
  ## Installation
@@ -23,19 +25,21 @@ const patient = {
23
25
  birthDate: '1990-01-01'
24
26
  };
25
27
 
26
- // Simple evaluation
27
- const givenNames = fhirpath.evaluate('name.given', patient);
28
+ // Simple evaluation (async)
29
+ const givenNames = await fhirpath.evaluate('name.given', patient);
28
30
  console.log(givenNames); // ['John', 'James', 'Johnny']
29
31
 
30
32
  // With filtering
31
- const officialName = fhirpath.evaluate('name.where(use = \'official\').given', patient);
33
+ const officialName = await fhirpath.evaluate('name.where(use = \'official\').given', patient);
32
34
 
33
35
  // Arithmetic
34
- const age = fhirpath.evaluate('today().year() - birthDate.toDateTime().year()', patient);
36
+ const age = await fhirpath.evaluate('today().year() - birthDate.toDateTime().year()', patient);
35
37
  ```
36
38
 
37
39
  ## API Reference
38
40
 
41
+ **Important Note**: As of version 2.0.0, all evaluation and analysis functions are async to support lazy loading of FHIR schemas and improved performance. Make sure to use `await` when calling `evaluate()`, `compile()`, `analyze()`, and `inspect()`.
42
+
39
43
  ### Core Functions
40
44
 
41
45
  #### `parse(expression: string, options?: ParserOptions): ParseResult`
@@ -68,43 +72,43 @@ console.log(result.ranges); // Map of AST nodes to source locations
68
72
  - `errorRecovery?: boolean` - Enable error recovery to continue parsing after errors (useful for IDEs)
69
73
  - `maxErrors?: number` - Maximum number of errors to collect before stopping
70
74
 
71
- #### `evaluate(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext): any[]`
75
+ #### `evaluate(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext): Promise<any[]>`
72
76
 
73
- Evaluates a FHIRPath expression against input data.
77
+ Evaluates a FHIRPath expression against input data. **Note: This function is now async.**
74
78
 
75
79
  ```typescript
76
80
  // Evaluate with string expression
77
- const names = fhirpath.evaluate('name.family', patient);
81
+ const names = await fhirpath.evaluate('name.family', patient);
78
82
 
79
83
  // Evaluate with parsed expression
80
84
  const expr = fhirpath.parse('name.family');
81
- const names = fhirpath.evaluate(expr, patient);
85
+ const names = await fhirpath.evaluate(expr, patient);
82
86
 
83
87
  // With context variables
84
- const result = fhirpath.evaluate('%myVar + 5', null, {
88
+ const result = await fhirpath.evaluate('%myVar + 5', null, {
85
89
  variables: { myVar: 10 }
86
90
  }); // [15]
87
91
  ```
88
92
 
89
- #### `compile(expression: string | FHIRPathExpression, options?: CompileOptions): CompiledExpression`
93
+ #### `compile(expression: string | FHIRPathExpression, options?: CompileOptions): Promise<CompiledExpression>`
90
94
 
91
- Compiles an expression into an optimized JavaScript function for better performance.
95
+ Compiles an expression into an optimized JavaScript function for better performance. **Note: Both compilation and execution are now async.**
92
96
 
93
97
  ```typescript
94
- const compiled = fhirpath.compile('name.given');
98
+ const compiled = await fhirpath.compile('name.given');
95
99
 
96
- // Use compiled function multiple times
97
- const names1 = compiled(patient1);
98
- const names2 = compiled(patient2);
100
+ // Use compiled function multiple times (async)
101
+ const names1 = await compiled(patient1);
102
+ const names2 = await compiled(patient2);
99
103
  ```
100
104
 
101
- #### `analyze(expression: string, options?: AnalyzeOptions): AnalysisResult`
105
+ #### `analyze(expression: string, options?: AnalyzeOptions): Promise<AnalysisResult>`
102
106
 
103
- Performs static type analysis on a FHIRPath expression, with optional type checking using a FHIR model provider and error recovery for broken expressions.
107
+ Performs static type analysis on a FHIRPath expression, with optional type checking using a FHIR model provider and error recovery for broken expressions. **Note: This function is now async.**
104
108
 
105
109
  ```typescript
106
110
  // Basic analysis
107
- const analysis = fhirpath.analyze('name.given');
111
+ const analysis = await fhirpath.analyze('name.given');
108
112
  console.log(analysis.diagnostics); // Array of any issues found
109
113
  console.log(analysis.ast); // The analyzed AST with type information
110
114
 
@@ -116,13 +120,13 @@ const modelProvider = new FHIRModelProvider({
116
120
  });
117
121
  await modelProvider.initialize();
118
122
 
119
- const analysis = fhirpath.analyze('Patient.birthDate.substring(0, 4)', {
123
+ const analysis = await fhirpath.analyze('Patient.birthDate.substring(0, 4)', {
120
124
  modelProvider
121
125
  });
122
126
  // Will report type error: substring() expects String but birthDate is date
123
127
 
124
128
  // With error recovery for IDE/tooling scenarios
125
- const analysis = fhirpath.analyze('Patient.name.', {
129
+ const analysis = await fhirpath.analyze('Patient.name.', {
126
130
  errorRecovery: true, // Won't throw on syntax errors
127
131
  modelProvider
128
132
  });
@@ -130,7 +134,7 @@ console.log(analysis.diagnostics); // Contains parse error: "Expected identifier
130
134
  console.log(analysis.ast); // Partial AST with error nodes
131
135
 
132
136
  // With input type context
133
- const analysis = fhirpath.analyze('name.given', {
137
+ const analysis = await fhirpath.analyze('name.given', {
134
138
  modelProvider,
135
139
  inputType: {
136
140
  type: 'HumanName',
@@ -146,13 +150,13 @@ const analysis = fhirpath.analyze('name.given', {
146
150
  - `variables?: Record<string, unknown>` - Variables available in the expression context
147
151
  - `inputType?: TypeInfo` - Type information for the input context
148
152
 
149
- #### `inspect(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext, options?: InspectOptions): InspectResult`
153
+ #### `inspect(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext, options?: InspectOptions): Promise<InspectResult>`
150
154
 
151
- Evaluates an expression while capturing rich debugging information including traces, execution time, and AST.
155
+ Evaluates an expression while capturing rich debugging information including traces, execution time, and AST. **Note: This function is now async.**
152
156
 
153
157
  ```typescript
154
158
  // Basic usage - capture trace output
155
- const result = fhirpath.inspect(
159
+ const result = await fhirpath.inspect(
156
160
  'name.trace("names").given.trace("given names")',
157
161
  patient
158
162
  );
@@ -169,7 +173,7 @@ result.traces.forEach(trace => {
169
173
  });
170
174
 
171
175
  // With options
172
- const detailedResult = fhirpath.inspect(
176
+ const detailedResult = await fhirpath.inspect(
173
177
  'Patient.name.where(use = "official")',
174
178
  bundle,
175
179
  undefined,
@@ -180,7 +184,7 @@ const detailedResult = fhirpath.inspect(
180
184
  );
181
185
 
182
186
  // Error handling
183
- const errorResult = fhirpath.inspect('invalid.expression()');
187
+ const errorResult = await fhirpath.inspect('invalid.expression()');
184
188
  if (errorResult.errors) {
185
189
  console.log('Errors:', errorResult.errors);
186
190
  }
@@ -259,20 +263,20 @@ const modelProvider = new FHIRModelProvider({
259
263
  // Initialize before use (loads FHIR type definitions)
260
264
  await modelProvider.initialize();
261
265
 
262
- // Use with analyze for type checking
263
- const result = analyze('Patient.active.substring(0, 1)', { modelProvider });
266
+ // Use with analyze for type checking (async)
267
+ const result = await analyze('Patient.active.substring(0, 1)', { modelProvider });
264
268
  // Diagnostics: "Type mismatch: function 'substring' expects input type String but got Boolean"
265
269
 
266
270
  // Type-aware property navigation
267
- const result2 = analyze('Patient.birthDate.year()', { modelProvider });
271
+ const result2 = await analyze('Patient.birthDate.year()', { modelProvider });
268
272
  // Works correctly - birthDate is recognized as a date type
269
273
 
270
274
  // Detect invalid property access
271
- const result3 = analyze('Patient.invalidProperty', { modelProvider });
275
+ const result3 = await analyze('Patient.invalidProperty', { modelProvider });
272
276
  // Diagnostics: "Unknown property 'invalidProperty' on type FHIR.Patient"
273
277
 
274
278
  // Works with complex paths
275
- const result4 = analyze(
279
+ const result4 = await analyze(
276
280
  'Bundle.entry.resource.where(resourceType = "Patient").name.given',
277
281
  { modelProvider }
278
282
  );
@@ -295,7 +299,7 @@ import { FHIRPath } from '@atomic-ehr/fhirpath';
295
299
 
296
300
  const fp = FHIRPath.builder()
297
301
  // Add custom functions
298
- .withCustomFunction('double', (context, input) => {
302
+ .withCustomFunction('double', async (context, input) => {
299
303
  return input.map(x => x * 2);
300
304
  })
301
305
 
@@ -304,16 +308,18 @@ const fp = FHIRPath.builder()
304
308
 
305
309
  // Add model provider for type information
306
310
  .withModelProvider({
307
- resolveType: (typeName) => { /* ... */ },
308
- getTypeHierarchy: (typeName) => { /* ... */ },
309
- getProperties: (typeName) => { /* ... */ }
311
+ getType: async (typeName) => { /* ... */ },
312
+ getElementType: async (parentType, propertyName) => { /* ... */ },
313
+ ofType: (type, typeName) => { /* ... */ },
314
+ getElementNames: (parentType) => { /* ... */ },
315
+ getChildrenType: async (parentType) => { /* ... */ }
310
316
  })
311
317
 
312
318
  .build();
313
319
 
314
- // Use the configured instance
315
- const result = fp.evaluate('value.double()', { value: [5] }); // [10]
316
- const status = fp.evaluate('%defaultStatus'); // ['active']
320
+ // Use the configured instance (async)
321
+ const result = await fp.evaluate('value.double()', { value: [5] }); // [10]
322
+ const status = await fp.evaluate('%defaultStatus'); // ['active']
317
323
  ```
318
324
 
319
325
  ### Custom Functions
@@ -322,7 +328,7 @@ Custom functions extend FHIRPath with domain-specific operations:
322
328
 
323
329
  ```typescript
324
330
  const fp = FHIRPath.builder()
325
- .withCustomFunction('age', (context, input) => {
331
+ .withCustomFunction('age', async (context, input) => {
326
332
  // Calculate age from birthDate
327
333
  return input.map(birthDate => {
328
334
  const today = new Date();
@@ -335,7 +341,7 @@ const fp = FHIRPath.builder()
335
341
  return age;
336
342
  });
337
343
  })
338
- .withCustomFunction('fullName', (context, input) => {
344
+ .withCustomFunction('fullName', async (context, input) => {
339
345
  return input.map(name => {
340
346
  if (name && typeof name === 'object') {
341
347
  const given = Array.isArray(name.given) ? name.given.join(' ') : '';
@@ -347,9 +353,9 @@ const fp = FHIRPath.builder()
347
353
  })
348
354
  .build();
349
355
 
350
- // Use custom functions
351
- const age = fp.evaluate('birthDate.age()', patient);
352
- const fullNames = fp.evaluate('name.fullName()', patient);
356
+ // Use custom functions (async)
357
+ const age = await fp.evaluate('birthDate.age()', patient);
358
+ const fullNames = await fp.evaluate('name.fullName()', patient);
353
359
  ```
354
360
 
355
361
  ### Error Handling
@@ -409,13 +415,13 @@ The analyzer supports error recovery mode for IDE and tooling scenarios, allowin
409
415
  ```typescript
410
416
  // Normal mode - throws on syntax errors
411
417
  try {
412
- const result = fhirpath.analyze('Patient.name.');
418
+ const result = await fhirpath.analyze('Patient.name.');
413
419
  } catch (error) {
414
420
  console.error('Syntax error:', error.message);
415
421
  }
416
422
 
417
423
  // Error recovery mode - continues analysis despite errors
418
- const result = fhirpath.analyze('Patient.name.', {
424
+ const result = await fhirpath.analyze('Patient.name.', {
419
425
  errorRecovery: true
420
426
  });
421
427
 
@@ -431,14 +437,14 @@ console.log(result.diagnostics);
431
437
  console.log(result.ast); // Partial AST with error nodes
432
438
 
433
439
  // Complex broken expression - multiple errors reported
434
- const result2 = fhirpath.analyze('Patient.name.where(use = ).given.', {
440
+ const result2 = await fhirpath.analyze('Patient.name.where(use = ).given.', {
435
441
  errorRecovery: true,
436
442
  modelProvider
437
443
  });
438
444
  console.log(result2.diagnostics.length); // 2 errors
439
445
 
440
446
  // Type information is preserved for valid parts
441
- const result3 = fhirpath.analyze('5 + 3 * ', {
447
+ const result3 = await fhirpath.analyze('5 + 3 * ', {
442
448
  errorRecovery: true
443
449
  });
444
450
  // Even though expression is incomplete, literals 5 and 3 have type info
@@ -465,19 +471,19 @@ const bundle = {
465
471
  };
466
472
 
467
473
  // Get all patients
468
- const patients = fhirpath.evaluate(
474
+ const patients = await fhirpath.evaluate(
469
475
  'entry.resource.where(resourceType = \'Patient\')',
470
476
  bundle
471
477
  );
472
478
 
473
479
  // Get active patients
474
- const activePatients = fhirpath.evaluate(
480
+ const activePatients = await fhirpath.evaluate(
475
481
  'entry.resource.where(resourceType = \'Patient\' and active = true)',
476
482
  bundle
477
483
  );
478
484
 
479
485
  // Count resources by type
480
- const patientCount = fhirpath.evaluate(
486
+ const patientCount = await fhirpath.evaluate(
481
487
  'entry.resource.where(resourceType = \'Patient\').count()',
482
488
  bundle
483
489
  ); // [2]
@@ -493,7 +499,7 @@ const observations = [
493
499
  ];
494
500
 
495
501
  // Find high blood pressure readings
496
- const highBP = fhirpath.evaluate(
502
+ const highBP = await fhirpath.evaluate(
497
503
  'where(code.coding.exists(system = \'loinc\' and code = \'1234\') and value > 130)',
498
504
  observations
499
505
  );
@@ -507,7 +513,7 @@ const patient = {
507
513
  };
508
514
 
509
515
  // Check if patient is adult (>= 18 years)
510
- const isAdult = fhirpath.evaluate(
516
+ const isAdult = await fhirpath.evaluate(
511
517
  'today() - birthDate.toDateTime() >= 18 years',
512
518
  patient
513
519
  );
@@ -526,7 +532,7 @@ const bundle = {
526
532
  };
527
533
 
528
534
  // Debug a complex expression with traces
529
- const result = fhirpath.inspect(
535
+ const result = await fhirpath.inspect(
530
536
  `entry.resource
531
537
  .trace('all resources')
532
538
  .where(resourceType = 'Patient')
@@ -569,14 +575,14 @@ result.traces.forEach(trace => {
569
575
  ```typescript
570
576
  const expr = fhirpath.parse('name.given');
571
577
  for (const patient of patients) {
572
- const names = fhirpath.evaluate(expr, patient);
578
+ const names = await fhirpath.evaluate(expr, patient);
573
579
  }
574
580
  ```
575
581
 
576
582
  3. **Use Compiled Functions**: For expressions evaluated frequently, use compilation:
577
583
  ```typescript
578
- const getName = fhirpath.compile('name.given');
579
- const results = patients.map(p => getName(p));
584
+ const getName = await fhirpath.compile('name.given');
585
+ const results = await Promise.all(patients.map(p => getName(p)));
580
586
  ```
581
587
 
582
588
  4. **Disable Unnecessary Parser Features**: Only enable parser features you need: