@atomic-ehr/fhirpath 0.0.2 → 0.0.3

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 (143) hide show
  1. package/README.md +716 -238
  2. package/dist/index.d.ts +225 -119
  3. package/dist/index.js +10911 -5600
  4. package/dist/index.js.map +1 -1
  5. package/package.json +9 -4
  6. package/src/analyzer/augmentor.ts +242 -0
  7. package/src/analyzer/cursor-services.ts +75 -0
  8. package/src/analyzer/scope-manager.ts +57 -0
  9. package/src/analyzer/trivia-indexer.ts +58 -0
  10. package/src/analyzer/type-compat.ts +157 -0
  11. package/src/analyzer/utils.ts +132 -0
  12. package/src/analyzer.ts +921 -1208
  13. package/src/completion-provider.ts +209 -191
  14. package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
  15. package/src/complex-types/temporal.ts +1737 -0
  16. package/src/errors.ts +25 -3
  17. package/src/index.ts +17 -104
  18. package/src/inspect.ts +4 -4
  19. package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
  20. package/src/interpreter/navigator.ts +94 -0
  21. package/src/interpreter/runtime-context.ts +273 -0
  22. package/src/interpreter.ts +435 -469
  23. package/src/lexer.ts +188 -210
  24. package/src/model-provider.ts +71 -43
  25. package/src/operations/abs-function.ts +1 -1
  26. package/src/operations/aggregate-function.ts +84 -5
  27. package/src/operations/all-function.ts +4 -3
  28. package/src/operations/allFalse-function.ts +2 -1
  29. package/src/operations/allTrue-function.ts +2 -1
  30. package/src/operations/and-operator.ts +2 -1
  31. package/src/operations/anyFalse-function.ts +2 -1
  32. package/src/operations/anyTrue-function.ts +2 -1
  33. package/src/operations/as-function.ts +58 -0
  34. package/src/operations/as-operator.ts +57 -19
  35. package/src/operations/ceiling-function.ts +1 -1
  36. package/src/operations/children-function.ts +14 -5
  37. package/src/operations/combine-function.ts +6 -3
  38. package/src/operations/combine-operator.ts +6 -7
  39. package/src/operations/comparison.ts +692 -0
  40. package/src/operations/contains-function.ts +1 -1
  41. package/src/operations/contains-operator.ts +2 -1
  42. package/src/operations/convertsToBoolean-function.ts +78 -0
  43. package/src/operations/convertsToDecimal-function.ts +82 -0
  44. package/src/operations/convertsToInteger-function.ts +71 -0
  45. package/src/operations/convertsToLong-function.ts +89 -0
  46. package/src/operations/convertsToQuantity-function.ts +116 -0
  47. package/src/operations/convertsToString-function.ts +88 -0
  48. package/src/operations/count-function.ts +2 -1
  49. package/src/operations/dateOf-function.ts +69 -0
  50. package/src/operations/dayOf-function.ts +66 -0
  51. package/src/operations/decimal-boundaries.ts +133 -0
  52. package/src/operations/defineVariable-function.ts +130 -17
  53. package/src/operations/distinct-function.ts +1 -1
  54. package/src/operations/div-operator.ts +1 -1
  55. package/src/operations/divide-operator.ts +12 -7
  56. package/src/operations/dot-operator.ts +1 -1
  57. package/src/operations/empty-function.ts +30 -21
  58. package/src/operations/endsWith-function.ts +6 -1
  59. package/src/operations/equal-operator.ts +23 -32
  60. package/src/operations/equivalent-operator.ts +13 -53
  61. package/src/operations/exclude-function.ts +2 -1
  62. package/src/operations/exists-function.ts +4 -3
  63. package/src/operations/first-function.ts +1 -1
  64. package/src/operations/floor-function.ts +1 -1
  65. package/src/operations/greater-operator.ts +20 -3
  66. package/src/operations/greater-or-equal-operator.ts +20 -3
  67. package/src/operations/highBoundary-function.ts +120 -0
  68. package/src/operations/hourOf-function.ts +66 -0
  69. package/src/operations/iif-function.ts +186 -7
  70. package/src/operations/implies-operator.ts +1 -1
  71. package/src/operations/in-operator.ts +2 -1
  72. package/src/operations/index.ts +41 -0
  73. package/src/operations/indexOf-function.ts +1 -1
  74. package/src/operations/intersect-function.ts +1 -1
  75. package/src/operations/is-function.ts +59 -0
  76. package/src/operations/is-operator.ts +20 -9
  77. package/src/operations/isDistinct-function.ts +2 -1
  78. package/src/operations/join-function.ts +1 -1
  79. package/src/operations/last-function.ts +1 -1
  80. package/src/operations/lastIndexOf-function.ts +85 -0
  81. package/src/operations/length-function.ts +1 -1
  82. package/src/operations/less-operator.ts +20 -3
  83. package/src/operations/less-or-equal-operator.ts +20 -3
  84. package/src/operations/less-than.ts +2 -2
  85. package/src/operations/lowBoundary-function.ts +120 -0
  86. package/src/operations/lower-function.ts +1 -1
  87. package/src/operations/matches-function.ts +86 -0
  88. package/src/operations/matchesFull-function.ts +96 -0
  89. package/src/operations/millisecondOf-function.ts +66 -0
  90. package/src/operations/minus-operator.ts +69 -4
  91. package/src/operations/minuteOf-function.ts +66 -0
  92. package/src/operations/mod-operator.ts +1 -1
  93. package/src/operations/monthOf-function.ts +66 -0
  94. package/src/operations/multiply-operator.ts +27 -3
  95. package/src/operations/not-equal-operator.ts +24 -30
  96. package/src/operations/not-equivalent-operator.ts +13 -53
  97. package/src/operations/not-function.ts +1 -1
  98. package/src/operations/ofType-function.ts +8 -12
  99. package/src/operations/or-operator.ts +2 -1
  100. package/src/operations/plus-operator.ts +71 -7
  101. package/src/operations/power-function.ts +35 -10
  102. package/src/operations/repeat-function.ts +169 -0
  103. package/src/operations/replace-function.ts +1 -1
  104. package/src/operations/replaceMatches-function.ts +120 -0
  105. package/src/operations/round-function.ts +1 -1
  106. package/src/operations/secondOf-function.ts +66 -0
  107. package/src/operations/select-function.ts +66 -5
  108. package/src/operations/single-function.ts +1 -1
  109. package/src/operations/skip-function.ts +1 -1
  110. package/src/operations/split-function.ts +1 -1
  111. package/src/operations/sqrt-function.ts +15 -8
  112. package/src/operations/startsWith-function.ts +1 -1
  113. package/src/operations/subsetOf-function.ts +6 -2
  114. package/src/operations/substring-function.ts +1 -1
  115. package/src/operations/supersetOf-function.ts +6 -2
  116. package/src/operations/tail-function.ts +1 -1
  117. package/src/operations/take-function.ts +1 -1
  118. package/src/operations/temporal-functions.ts +555 -0
  119. package/src/operations/timeOf-function.ts +67 -0
  120. package/src/operations/timezoneOffsetOf-function.ts +69 -0
  121. package/src/operations/toBoolean-function.ts +27 -8
  122. package/src/operations/toChars-function.ts +56 -0
  123. package/src/operations/toDecimal-function.ts +27 -8
  124. package/src/operations/toInteger-function.ts +15 -3
  125. package/src/operations/toLong-function.ts +98 -0
  126. package/src/operations/toQuantity-function.ts +181 -0
  127. package/src/operations/toString-function.ts +45 -3
  128. package/src/operations/trace-function.ts +1 -1
  129. package/src/operations/trim-function.ts +1 -1
  130. package/src/operations/truncate-function.ts +1 -1
  131. package/src/operations/unary-minus-operator.ts +2 -2
  132. package/src/operations/unary-plus-operator.ts +1 -1
  133. package/src/operations/union-function.ts +1 -1
  134. package/src/operations/union-operator.ts +16 -26
  135. package/src/operations/upper-function.ts +1 -1
  136. package/src/operations/where-function.ts +3 -3
  137. package/src/operations/xor-operator.ts +1 -1
  138. package/src/operations/yearOf-function.ts +66 -0
  139. package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
  140. package/src/parser.ts +248 -501
  141. package/src/registry.ts +53 -42
  142. package/src/types.ts +128 -16
  143. package/src/utils/pprint.ts +151 -0
package/README.md CHANGED
@@ -15,7 +15,7 @@ bun add @atomic-ehr/fhirpath
15
15
  ## Quick Start
16
16
 
17
17
  ```typescript
18
- import fhirpath from '@atomic-ehr/fhirpath';
18
+ import { evaluate } from '@atomic-ehr/fhirpath';
19
19
 
20
20
  const patient = {
21
21
  name: [
@@ -26,14 +26,20 @@ const patient = {
26
26
  };
27
27
 
28
28
  // Simple evaluation (async)
29
- const givenNames = await fhirpath.evaluate('name.given', patient);
29
+ const givenNames = await evaluate('name.given', { input: patient });
30
30
  console.log(givenNames); // ['John', 'James', 'Johnny']
31
31
 
32
32
  // With filtering
33
- const officialName = await fhirpath.evaluate('name.where(use = \'official\').given', patient);
33
+ const officialName = await evaluate(
34
+ "name.where(use = 'official').given",
35
+ { input: patient }
36
+ );
34
37
 
35
- // Arithmetic
36
- const age = await fhirpath.evaluate('today().year() - birthDate.toDateTime().year()', patient);
38
+ // With variables
39
+ const result = await evaluate('%x + 5', {
40
+ variables: { x: 10 }
41
+ });
42
+ console.log(result); // [15]
37
43
  ```
38
44
 
39
45
  ## API Reference
@@ -47,68 +53,63 @@ const age = await fhirpath.evaluate('today().year() - birthDate.toDateTime().yea
47
53
  Parses a FHIRPath expression string into an AST (Abstract Syntax Tree) with optional parser features.
48
54
 
49
55
  ```typescript
50
- // Basic parsing (default - collects diagnostics)
51
- const result = fhirpath.parse('Patient.name.given');
52
- console.log(result.ast); // The parsed AST
53
- console.log(result.diagnostics); // Array of any syntax issues
54
- console.log(result.hasErrors); // Boolean indicating if there are errors
56
+ import { parse } from '@atomic-ehr/fhirpath';
55
57
 
56
- // Parse with error on first syntax error (fastest)
57
- const ast = fhirpath.parseForEvaluation('Patient.name.given');
58
- // Throws immediately on syntax errors - best for production use
58
+ // Basic parsing (collects diagnostics)
59
+ const result = parse('Patient.name.given');
60
+ console.log(result.ast); // The parsed AST
61
+ console.log(result.errors); // Array of any syntax errors
59
62
 
60
- // Parse with advanced features for development tools
61
- const result = fhirpath.parse('Patient..name', {
62
- errorRecovery: true, // Continue parsing after errors
63
- trackRanges: true // Track source location for each AST node
63
+ // Parse with error recovery for IDE tools
64
+ const result = parse('Patient..name', {
65
+ mode: 'lsp', // Enable LSP mode
66
+ errorRecovery: true // Continue parsing after errors
64
67
  });
65
- console.log(result.isPartial); // true - indicates incomplete AST due to errors
66
- console.log(result.ranges); // Map of AST nodes to source locations
68
+ console.log(result.errors); // Contains parse errors
69
+ console.log(result.ast); // Partial AST with error nodes
67
70
  ```
68
71
 
69
72
  **Parser Options:**
70
- - `throwOnError?: boolean` - Throw on first error instead of collecting diagnostics (performance mode)
71
- - `trackRanges?: boolean` - Enable source range tracking for each AST node (useful for IDEs)
72
- - `errorRecovery?: boolean` - Enable error recovery to continue parsing after errors (useful for IDEs)
73
- - `maxErrors?: number` - Maximum number of errors to collect before stopping
73
+ - `mode?: 'simple' | 'lsp'` - Parser mode (simple for fast parsing, lsp for IDE features)
74
+ - `errorRecovery?: boolean` - Enable error recovery to continue parsing after errors (LSP mode only)
75
+ - `cursorPosition?: number` - Cursor position for completion support (LSP mode only)
74
76
 
75
- #### `evaluate(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext): Promise<any[]>`
77
+ #### `evaluate(expression: string, options?: EvaluateOptions): Promise<any[]>`
76
78
 
77
- Evaluates a FHIRPath expression against input data. **Note: This function is now async.**
79
+ Evaluates a FHIRPath expression against input data. **Note: This function is async.**
78
80
 
79
81
  ```typescript
80
- // Evaluate with string expression
81
- const names = await fhirpath.evaluate('name.family', patient);
82
+ import { evaluate } from '@atomic-ehr/fhirpath';
82
83
 
83
- // Evaluate with parsed expression
84
- const expr = fhirpath.parse('name.family');
85
- const names = await fhirpath.evaluate(expr, patient);
84
+ // Simple evaluation
85
+ const names = await evaluate('name.family', { input: patient });
86
86
 
87
- // With context variables
88
- const result = await fhirpath.evaluate('%myVar + 5', null, {
87
+ // With variables
88
+ const result = await evaluate('%myVar + 5', {
89
89
  variables: { myVar: 10 }
90
90
  }); // [15]
91
- ```
92
91
 
93
- #### `compile(expression: string | FHIRPathExpression, options?: CompileOptions): Promise<CompiledExpression>`
94
-
95
- Compiles an expression into an optimized JavaScript function for better performance. **Note: Both compilation and execution are now async.**
96
-
97
- ```typescript
98
- const compiled = await fhirpath.compile('name.given');
92
+ // With model provider for type checking
93
+ const modelProvider = new FHIRModelProvider({
94
+ packages: [{ name: 'hl7.fhir.r4.core', version: '4.0.1' }]
95
+ });
96
+ await modelProvider.initialize();
99
97
 
100
- // Use compiled function multiple times (async)
101
- const names1 = await compiled(patient1);
102
- const names2 = await compiled(patient2);
98
+ const typed = await evaluate('Patient.name.given', {
99
+ input: patient,
100
+ modelProvider,
101
+ inputType: { type: 'Patient', singleton: true }
102
+ });
103
103
  ```
104
104
 
105
+
105
106
  #### `analyze(expression: string, options?: AnalyzeOptions): Promise<AnalysisResult>`
106
107
 
107
108
  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.**
108
109
 
109
110
  ```typescript
110
111
  // Basic analysis
111
- const analysis = await fhirpath.analyze('name.given');
112
+ const analysis = await analyze('name.given');
112
113
  console.log(analysis.diagnostics); // Array of any issues found
113
114
  console.log(analysis.ast); // The analyzed AST with type information
114
115
 
@@ -120,13 +121,13 @@ const modelProvider = new FHIRModelProvider({
120
121
  });
121
122
  await modelProvider.initialize();
122
123
 
123
- const analysis = await fhirpath.analyze('Patient.birthDate.substring(0, 4)', {
124
+ const analysis = await analyze('Patient.birthDate.substring(0, 4)', {
124
125
  modelProvider
125
126
  });
126
127
  // Will report type error: substring() expects String but birthDate is date
127
128
 
128
129
  // With error recovery for IDE/tooling scenarios
129
- const analysis = await fhirpath.analyze('Patient.name.', {
130
+ const analysis = await analyze('Patient.name.', {
130
131
  errorRecovery: true, // Won't throw on syntax errors
131
132
  modelProvider
132
133
  });
@@ -134,11 +135,10 @@ console.log(analysis.diagnostics); // Contains parse error: "Expected identifier
134
135
  console.log(analysis.ast); // Partial AST with error nodes
135
136
 
136
137
  // With input type context
137
- const analysis = await fhirpath.analyze('name.given', {
138
+ const analysis = await analyze('name.given', {
138
139
  modelProvider,
139
140
  inputType: {
140
141
  type: 'HumanName',
141
- namespace: 'FHIR',
142
142
  singleton: false
143
143
  }
144
144
  });
@@ -150,15 +150,17 @@ const analysis = await fhirpath.analyze('name.given', {
150
150
  - `variables?: Record<string, unknown>` - Variables available in the expression context
151
151
  - `inputType?: TypeInfo` - Type information for the input context
152
152
 
153
- #### `inspect(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext, options?: InspectOptions): Promise<InspectResult>`
153
+ #### `inspect(expression: string, options?: InspectOptions): Promise<InspectResult>`
154
154
 
155
- Evaluates an expression while capturing rich debugging information including traces, execution time, and AST. **Note: This function is now async.**
155
+ Evaluates an expression while capturing rich debugging information including traces, execution time, and AST. **Note: This function is async.**
156
156
 
157
157
  ```typescript
158
+ import { inspect } from '@atomic-ehr/fhirpath';
159
+
158
160
  // Basic usage - capture trace output
159
- const result = await fhirpath.inspect(
161
+ const result = await inspect(
160
162
  'name.trace("names").given.trace("given names")',
161
- patient
163
+ { input: patient }
162
164
  );
163
165
 
164
166
  console.log(result.result); // ['John', 'James', 'Johnny']
@@ -173,18 +175,16 @@ result.traces.forEach(trace => {
173
175
  });
174
176
 
175
177
  // With options
176
- const detailedResult = await fhirpath.inspect(
178
+ const detailedResult = await inspect(
177
179
  'Patient.name.where(use = "official")',
178
- bundle,
179
- undefined,
180
180
  {
181
- maxTraces: 100, // Limit number of traces collected
182
- recordSteps: true // Future: enable step-by-step recording
181
+ input: bundle,
182
+ maxTraces: 100 // Limit number of traces collected
183
183
  }
184
184
  );
185
185
 
186
186
  // Error handling
187
- const errorResult = await fhirpath.inspect('invalid.expression()');
187
+ const errorResult = await inspect('invalid.expression()', {});
188
188
  if (errorResult.errors) {
189
189
  console.log('Errors:', errorResult.errors);
190
190
  }
@@ -200,52 +200,29 @@ The `InspectResult` contains:
200
200
  - `warnings`: Any warnings (optional)
201
201
  - `evaluationSteps`: Step-by-step evaluation details (when enabled)
202
202
 
203
- ### Convenience Functions
204
-
205
- #### `parseForEvaluation(expression: string): ASTNode`
206
-
207
- Parses an expression and returns just the AST, throwing on any syntax errors. This is the most performant option for production use.
208
-
209
- ```typescript
210
- try {
211
- const ast = fhirpath.parseForEvaluation('Patient.name.given');
212
- // Use ast for evaluation
213
- } catch (error) {
214
- console.error('Parse error:', error.message);
215
- }
216
- ```
217
-
218
- #### `validate(expression: string): { valid: boolean; diagnostics: ParseDiagnostic[] }`
219
-
220
- Validates a FHIRPath expression syntax without throwing errors.
221
-
222
- ```typescript
223
- const validation = fhirpath.validate('Patient..name');
224
- if (!validation.valid) {
225
- console.log('Syntax errors:', validation.diagnostics);
226
- }
227
- ```
228
203
 
229
204
  ### Registry API
230
205
 
231
206
  The registry provides introspection capabilities for available operations.
232
207
 
233
208
  ```typescript
209
+ import { registry } from '@atomic-ehr/fhirpath';
210
+
234
211
  // List all available functions
235
- const functions = fhirpath.registry.listFunctions();
212
+ const functions = registry.listFunctions();
236
213
  console.log(functions.map(f => f.name)); // ['where', 'select', 'first', ...]
237
214
 
238
- // Check if operation exists
239
- fhirpath.registry.hasFunction('where'); // true
240
- fhirpath.registry.hasOperator('+'); // true
215
+ // List all available operators
216
+ const operators = registry.listOperators();
217
+ console.log(operators.map(o => o.name)); // ['+', '-', '*', ...]
241
218
 
242
- // Get operation details
243
- const whereInfo = fhirpath.registry.getOperationInfo('where');
244
- console.log(whereInfo.syntax.notation); // "where(expression)"
219
+ // Get function metadata
220
+ const whereFunction = registry.getFunction('where');
221
+ console.log(whereFunction?.signatures); // Function signatures
245
222
 
246
- // Validate custom function names
247
- fhirpath.registry.canRegisterFunction('myFunc'); // true
248
- fhirpath.registry.canRegisterFunction('where'); // false (built-in)
223
+ // Get operator metadata
224
+ const plusOperator = registry.getOperator('+');
225
+ console.log(plusOperator?.precedence); // Operator precedence
249
226
  ```
250
227
 
251
228
  ### FHIR Model Provider
@@ -290,73 +267,6 @@ The model provider supports:
290
267
  - Extension navigation
291
268
  - Full FHIR R4 type system
292
269
 
293
- ### Builder Pattern
294
-
295
- For advanced configurations, use the builder pattern:
296
-
297
- ```typescript
298
- import { FHIRPath } from '@atomic-ehr/fhirpath';
299
-
300
- const fp = FHIRPath.builder()
301
- // Add custom functions
302
- .withCustomFunction('double', async (context, input) => {
303
- return input.map(x => x * 2);
304
- })
305
-
306
- // Set default variables
307
- .withVariable('defaultStatus', 'active')
308
-
309
- // Add model provider for type information
310
- .withModelProvider({
311
- getType: async (typeName) => { /* ... */ },
312
- getElementType: async (parentType, propertyName) => { /* ... */ },
313
- ofType: (type, typeName) => { /* ... */ },
314
- getElementNames: (parentType) => { /* ... */ },
315
- getChildrenType: async (parentType) => { /* ... */ }
316
- })
317
-
318
- .build();
319
-
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']
323
- ```
324
-
325
- ### Custom Functions
326
-
327
- Custom functions extend FHIRPath with domain-specific operations:
328
-
329
- ```typescript
330
- const fp = FHIRPath.builder()
331
- .withCustomFunction('age', async (context, input) => {
332
- // Calculate age from birthDate
333
- return input.map(birthDate => {
334
- const today = new Date();
335
- const birth = new Date(birthDate);
336
- let age = today.getFullYear() - birth.getFullYear();
337
- const monthDiff = today.getMonth() - birth.getMonth();
338
- if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
339
- age--;
340
- }
341
- return age;
342
- });
343
- })
344
- .withCustomFunction('fullName', async (context, input) => {
345
- return input.map(name => {
346
- if (name && typeof name === 'object') {
347
- const given = Array.isArray(name.given) ? name.given.join(' ') : '';
348
- const family = name.family || '';
349
- return `${given} ${family}`.trim();
350
- }
351
- return '';
352
- });
353
- })
354
- .build();
355
-
356
- // Use custom functions (async)
357
- const age = await fp.evaluate('birthDate.age()', patient);
358
- const fullNames = await fp.evaluate('name.fullName()', patient);
359
- ```
360
270
 
361
271
  ### Error Handling
362
272
 
@@ -382,29 +292,26 @@ The library is fully typed with TypeScript:
382
292
 
383
293
  ```typescript
384
294
  import type {
385
- FHIRPathExpression,
386
- CompiledExpression,
387
- EvaluationContext,
388
- ModelProvider,
389
- CustomFunction,
390
- OperationInfo,
391
- AnalysisResult,
392
- InspectResult,
393
- InspectOptions,
394
- // Parser types
395
- ParserOptions,
396
- ParseResult,
397
- ParseDiagnostic,
398
- DiagnosticSeverity,
399
- TextRange,
400
295
  ASTNode,
401
- // Analyzer types
402
- AnalyzeOptions,
296
+ AnalysisResult,
403
297
  Diagnostic,
298
+ DiagnosticSeverity,
404
299
  TypeInfo,
300
+ ModelProvider,
301
+ OperatorDefinition,
302
+ FunctionDefinition,
303
+ // Parser types
304
+ ParseResult,
405
305
  // FHIR Model Provider
406
306
  FHIRModelProvider,
407
- FHIRModelProviderConfig
307
+ FHIRModelProviderConfig,
308
+ // Inspect types
309
+ InspectResult,
310
+ InspectOptions,
311
+ ASTMetadata,
312
+ // Completion types
313
+ CompletionItem,
314
+ CompletionOptions
408
315
  } from '@atomic-ehr/fhirpath';
409
316
  ```
410
317
 
@@ -415,13 +322,13 @@ The analyzer supports error recovery mode for IDE and tooling scenarios, allowin
415
322
  ```typescript
416
323
  // Normal mode - throws on syntax errors
417
324
  try {
418
- const result = await fhirpath.analyze('Patient.name.');
325
+ const result = await analyze('Patient.name.');
419
326
  } catch (error) {
420
327
  console.error('Syntax error:', error.message);
421
328
  }
422
329
 
423
330
  // Error recovery mode - continues analysis despite errors
424
- const result = await fhirpath.analyze('Patient.name.', {
331
+ const result = await analyze('Patient.name.', {
425
332
  errorRecovery: true
426
333
  });
427
334
 
@@ -437,14 +344,14 @@ console.log(result.diagnostics);
437
344
  console.log(result.ast); // Partial AST with error nodes
438
345
 
439
346
  // Complex broken expression - multiple errors reported
440
- const result2 = await fhirpath.analyze('Patient.name.where(use = ).given.', {
347
+ const result2 = await analyze('Patient.name.where(use = ).given.', {
441
348
  errorRecovery: true,
442
349
  modelProvider
443
350
  });
444
351
  console.log(result2.diagnostics.length); // 2 errors
445
352
 
446
353
  // Type information is preserved for valid parts
447
- const result3 = await fhirpath.analyze('5 + 3 * ', {
354
+ const result3 = await analyze('5 + 3 * ', {
448
355
  errorRecovery: true
449
356
  });
450
357
  // Even though expression is incomplete, literals 5 and 3 have type info
@@ -456,6 +363,107 @@ This is particularly useful for:
456
363
  - **Code completion**: Analyze partial expressions for context
457
364
  - **Linting tools**: Report all issues in a single pass
458
365
 
366
+ ## Supported Features
367
+
368
+ ### Complete FHIRPath Function Coverage
369
+
370
+ This implementation supports **100% of FHIRPath functions** as defined in the specification:
371
+
372
+ #### Arithmetic Functions (13)
373
+ - **Basic**: `+`, `-`, `*`, `/`, `div`, `mod`, `power`
374
+ - **Math**: `abs`, `ceiling`, `floor`, `round`, `sqrt`, `truncate`
375
+
376
+ #### String Functions (19)
377
+ - **Properties**: `length`
378
+ - **Manipulation**: `substring`, `upper`, `lower`, `trim`, `toChars`, `repeat`
379
+ - **Testing**: `startsWith`, `endsWith`, `contains`, `matches`, `matchesFull`
380
+ - **Search**: `indexOf`, `lastIndexOf`
381
+ - **Transform**: `replace`, `replaceMatches`, `split`, `join`
382
+
383
+ #### Collection Functions (23)
384
+ - **Filtering**: `where`, `select`, `ofType`
385
+ - **Subsetting**: `first`, `last`, `tail`, `skip`, `take`, `single`
386
+ - **Existence**: `exists`, `empty`, `count`, `distinct`, `isDistinct`
387
+ - **Aggregation**: `all`, `allTrue`, `anyTrue`, `allFalse`, `anyFalse`
388
+ - **Combining**: `combine`, `union`, `intersect`, `exclude`
389
+ - **Membership**: `subsetOf`, `supersetOf`
390
+
391
+ #### Type Functions (16)
392
+ - **Conversion**: `toBoolean`, `toInteger`, `toLong`, `toDecimal`, `toString`, `toQuantity`
393
+ - **Testing**: `convertsToBoolean`, `convertsToInteger`, `convertsToLong`, `convertsToDecimal`, `convertsToString`, `convertsToQuantity`
394
+ - **Operators**: `is`, `as`
395
+ - **Boundaries**: `highBoundary`, `lowBoundary`
396
+
397
+ #### Temporal Functions (15)
398
+ - **Current**: `now`, `today`, `timeOfDay`
399
+ - **Extraction**: `dateOf`, `timeOf`, `yearOf`, `monthOf`, `dayOf`, `hourOf`, `minuteOf`, `secondOf`, `millisecondOf`, `timezoneOffsetOf`
400
+ - **Arithmetic**: Full support for date/time arithmetic with durations
401
+
402
+ #### Logical Operators (6)
403
+ - **Boolean**: `and`, `or`, `xor`, `not`, `implies`
404
+ - **Membership**: `in`, `contains`
405
+
406
+ #### Comparison Operators (8)
407
+ - **Equality**: `=`, `!=`, `~`, `!~`
408
+ - **Ordering**: `<`, `>`, `<=`, `>=`
409
+
410
+ #### Utility Functions (7)
411
+ - **Control**: `iif`, `defineVariable`
412
+ - **Navigation**: `children`, `descendants`
413
+ - **Debugging**: `trace`
414
+ - **Advanced**: `aggregate`
415
+ - **Indexing**: Array indexing with `[n]`
416
+
417
+ ### Advanced Features
418
+
419
+ #### FHIR Model Integration
420
+ - Full R4, R5, STU3, DSTU2 support via `@atomic-ehr/fhir-canonical-manager`
421
+ - Type-aware property navigation
422
+ - Polymorphic type resolution
423
+ - Choice type handling (e.g., `value[x]`)
424
+ - Extension navigation
425
+
426
+ #### UCUM Quantity Support
427
+ - Complete UCUM unit system
428
+ - Unit conversion and normalization
429
+ - Quantity arithmetic with unit compatibility checking
430
+ - Temporal quantity support (years, months, days, etc.)
431
+
432
+ #### Temporal Types
433
+ - FHIR Date, DateTime, and Time types
434
+ - Timezone-aware operations
435
+ - Partial date support (e.g., "2023", "2023-05")
436
+ - Date/time boundary calculations
437
+ - Duration arithmetic
438
+
439
+ #### Variables and Context
440
+ - User-defined variables with `%` prefix
441
+ - Built-in variables: `$this`, `$index`, `$total`
442
+ - System variables: `%context`, `%resource`, `%rootResource`, `%ucum`
443
+ - Scoped variable definitions with `defineVariable()`
444
+
445
+ #### Type System
446
+ - Complete type inference
447
+ - Union type support
448
+ - Polymorphic function signatures
449
+ - Type coercion rules
450
+ - Singleton vs collection tracking
451
+
452
+ #### Performance Optimizations
453
+ - Compiled expression mode for repeated evaluations
454
+ - Prototype-based context for minimal allocation
455
+ - Schema and type hierarchy caching
456
+ - Optimized operator dispatch
457
+ - Lazy evaluation where applicable
458
+
459
+ #### IDE and Tooling Support
460
+ - LSP (Language Server Protocol) integration
461
+ - Context-aware code completions
462
+ - Error recovery for partial expressions
463
+ - Source range tracking for diagnostics
464
+ - Trivia preservation (comments, whitespace)
465
+ - Step-by-step debugging with `inspect()`
466
+
459
467
  ## Common Use Cases
460
468
 
461
469
  ### Working with FHIR Resources
@@ -471,54 +479,542 @@ const bundle = {
471
479
  };
472
480
 
473
481
  // Get all patients
474
- const patients = await fhirpath.evaluate(
475
- 'entry.resource.where(resourceType = \'Patient\')',
476
- bundle
482
+ const patients = await evaluate(
483
+ "entry.resource.where(resourceType = 'Patient')",
484
+ { input: bundle }
477
485
  );
478
486
 
479
487
  // Get active patients
480
- const activePatients = await fhirpath.evaluate(
481
- 'entry.resource.where(resourceType = \'Patient\' and active = true)',
482
- bundle
488
+ const activePatients = await evaluate(
489
+ "entry.resource.where(resourceType = 'Patient' and active = true)",
490
+ { input: bundle }
483
491
  );
484
492
 
485
493
  // Count resources by type
486
- const patientCount = await fhirpath.evaluate(
487
- 'entry.resource.where(resourceType = \'Patient\').count()',
488
- bundle
494
+ const patientCount = await evaluate(
495
+ "entry.resource.where(resourceType = 'Patient').count()",
496
+ { input: bundle }
489
497
  ); // [2]
490
498
  ```
491
499
 
492
- ### Complex Filtering
500
+ ### Complex Filtering and Navigation
501
+
502
+ **Important Note on Polymorphic Properties**: In FHIR JSON, polymorphic properties like `value[x]` are stored with their type suffix (e.g., `valueQuantity`, `valueString`). However, in FHIRPath expressions, you access them without the suffix and use `ofType()` to filter by type:
503
+ - JSON: `observation.valueQuantity.value`
504
+ - FHIRPath: `observation.value.ofType(Quantity).value`
493
505
 
494
506
  ```typescript
507
+ // Blood pressure observations (FHIR Observation resources)
495
508
  const observations = [
496
- { code: { coding: [{ system: 'loinc', code: '1234' }] }, value: 140 },
497
- { code: { coding: [{ system: 'loinc', code: '1234' }] }, value: 120 },
498
- { code: { coding: [{ system: 'loinc', code: '5678' }] }, value: 98.6 }
509
+ {
510
+ resourceType: 'Observation',
511
+ code: { coding: [{ system: 'loinc', code: '85354-9' }] },
512
+ valueQuantity: { value: 140, unit: 'mm[Hg]' },
513
+ effectiveDateTime: '2024-01-15T10:30:00Z' // In FHIR JSON, stored as effectiveDateTime
514
+ },
515
+ {
516
+ resourceType: 'Observation',
517
+ code: { coding: [{ system: 'loinc', code: '85354-9' }] },
518
+ valueQuantity: { value: 120, unit: 'mm[Hg]' },
519
+ effectiveDateTime: '2024-01-14T09:00:00Z' // In FHIR JSON, stored as effectiveDateTime
520
+ },
521
+ {
522
+ resourceType: 'Observation',
523
+ code: { coding: [{ system: 'loinc', code: '8310-5' }] },
524
+ valueQuantity: { value: 98.6, unit: '[degF]' },
525
+ effectiveDateTime: '2024-01-15T10:30:00Z' // In FHIR JSON, stored as effectiveDateTime
526
+ }
499
527
  ];
500
528
 
501
- // Find high blood pressure readings
502
- const highBP = await fhirpath.evaluate(
503
- 'where(code.coding.exists(system = \'loinc\' and code = \'1234\') and value > 130)',
504
- observations
529
+ // Find high blood pressure readings (systolic > 130)
530
+ // Note: In FHIRPath, polymorphic properties like value[x] are accessed directly as 'value'
531
+ const highBP = await evaluate(
532
+ `where(code.coding.exists(system = 'loinc' and code = '85354-9')
533
+ and value.ofType(Quantity).value > 130)`,
534
+ { input: observations }
535
+ );
536
+
537
+ // Get all observation values with units (for Quantity types)
538
+ const valuesWithUnits = await evaluate(
539
+ "value.ofType(Quantity).select(value.toString() + ' ' + unit)",
540
+ { input: observations }
541
+ ); // ['140 mm[Hg]', '120 mm[Hg]', '98.6 [degF]']
542
+
543
+ // Find most recent observation
544
+ // Note: effective[x] can be DateTime, Period, Timing, or Instant
545
+ const mostRecent = await evaluate(
546
+ 'where(effective.ofType(dateTime) = effective.ofType(dateTime).max()).first()',
547
+ { input: observations }
505
548
  );
506
549
  ```
507
550
 
508
- ### Date Manipulation
551
+ ### Patient Demographics and Identifiers
509
552
 
510
553
  ```typescript
511
554
  const patient = {
512
- birthDate: '1990-05-15'
555
+ resourceType: 'Patient',
556
+ identifier: [
557
+ { system: 'http://hospital.org/mrn', value: 'MRN-12345' },
558
+ { system: 'http://hl7.org/fhir/sid/us-ssn', value: '123-45-6789' }
559
+ ],
560
+ name: [
561
+ {
562
+ use: 'official',
563
+ family: 'Smith',
564
+ given: ['John', 'Robert'],
565
+ prefix: ['Dr']
566
+ },
567
+ {
568
+ use: 'nickname',
569
+ given: ['Johnny']
570
+ }
571
+ ],
572
+ telecom: [
573
+ { system: 'phone', value: '555-0123', use: 'home' },
574
+ { system: 'email', value: 'john@example.com', use: 'work' }
575
+ ],
576
+ birthDate: '1980-05-15',
577
+ address: [
578
+ {
579
+ use: 'home',
580
+ line: ['123 Main St', 'Apt 4B'],
581
+ city: 'Boston',
582
+ state: 'MA',
583
+ postalCode: '02101'
584
+ }
585
+ ]
513
586
  };
514
587
 
515
- // Check if patient is adult (>= 18 years)
516
- const isAdult = await fhirpath.evaluate(
517
- 'today() - birthDate.toDateTime() >= 18 years',
518
- patient
588
+ // Get patient's MRN
589
+ const mrn = await evaluate(
590
+ "identifier.where(system = 'http://hospital.org/mrn').value",
591
+ { input: patient }
592
+ ); // ['MRN-12345']
593
+
594
+ // Get full official name
595
+ const fullName = await evaluate(
596
+ "name.where(use = 'official').select((prefix | {}).first() + ' ' + given.join(' ') + ' ' + family)",
597
+ { input: patient }
598
+ ); // ['Dr John Robert Smith']
599
+
600
+ // Get all phone numbers
601
+ const phones = await evaluate(
602
+ "telecom.where(system = 'phone').value",
603
+ { input: patient }
604
+ ); // ['555-0123']
605
+
606
+ // Get home address as single line
607
+ const address = await evaluate(
608
+ "address.where(use = 'home').select(line.join(', ') + ', ' + city + ', ' + state + ' ' + postalCode)",
609
+ { input: patient }
610
+ ); // ['123 Main St, Apt 4B, Boston, MA 02101']
611
+
612
+ // Calculate age
613
+ const age = await evaluate(
614
+ "today().toString().substring(0, 4).toInteger() - birthDate.substring(0, 4).toInteger()",
615
+ { input: patient }
616
+ ); // [44] (as of 2024)
617
+ ```
618
+
619
+ ### Medication and Dosage Calculations
620
+
621
+ ```typescript
622
+ const medicationRequest = {
623
+ resourceType: 'MedicationRequest',
624
+ medication: {
625
+ coding: [{
626
+ system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
627
+ code: '1049502',
628
+ display: 'Acetaminophen 325 MG Oral Tablet'
629
+ }]
630
+ },
631
+ dosageInstruction: [
632
+ {
633
+ timing: {
634
+ repeat: {
635
+ frequency: 2,
636
+ period: 1,
637
+ periodUnit: 'd'
638
+ }
639
+ },
640
+ doseAndRate: [{
641
+ doseQuantity: {
642
+ value: 650,
643
+ unit: 'mg',
644
+ system: 'http://unitsofmeasure.org'
645
+ }
646
+ }]
647
+ }
648
+ ],
649
+ dispenseRequest: {
650
+ quantity: {
651
+ value: 60,
652
+ unit: 'TAB'
653
+ },
654
+ expectedSupplyDuration: {
655
+ value: 30,
656
+ unit: 'd'
657
+ }
658
+ }
659
+ };
660
+
661
+ // Get medication name (medication[x] can be CodeableConcept or Reference)
662
+ const medName = await evaluate(
663
+ 'medication.ofType(CodeableConcept).coding.display.first()',
664
+ { input: medicationRequest }
665
+ ); // ['Acetaminophen 325 MG Oral Tablet']
666
+
667
+ // Calculate daily dose
668
+ const dailyDose = await evaluate(
669
+ 'dosageInstruction.first().doseAndRate.doseQuantity.value * dosageInstruction.first().timing.repeat.frequency',
670
+ { input: medicationRequest }
671
+ ); // [1300] (650mg * 2 times per day)
672
+
673
+ // Get supply duration
674
+ const duration = await evaluate(
675
+ "dispenseRequest.expectedSupplyDuration.value.toString() + ' ' + dispenseRequest.expectedSupplyDuration.unit",
676
+ { input: medicationRequest }
677
+ ); // ['30 d']
678
+ ```
679
+
680
+ ### Date and Time Operations
681
+
682
+ ```typescript
683
+ const appointment = {
684
+ resourceType: 'Appointment',
685
+ start: '2024-03-15T14:30:00Z',
686
+ end: '2024-03-15T15:00:00Z',
687
+ created: '2024-03-01T10:00:00Z',
688
+ participant: [
689
+ {
690
+ actor: { reference: 'Patient/123' },
691
+ required: 'required',
692
+ status: 'accepted'
693
+ },
694
+ {
695
+ actor: { reference: 'Practitioner/456' },
696
+ required: 'required',
697
+ status: 'accepted'
698
+ }
699
+ ]
700
+ };
701
+
702
+ // Calculate appointment duration in minutes
703
+ const duration = await evaluate(
704
+ '(end - start) / 1 minute',
705
+ { input: appointment }
706
+ ); // [30]
707
+
708
+ // Check if appointment is in the future
709
+ const isFuture = await evaluate(
710
+ 'start > now()',
711
+ { input: appointment }
712
+ );
713
+
714
+ // Get appointment date only
715
+ const appointmentDate = await evaluate(
716
+ 'start.toString().substring(0, 10)',
717
+ { input: appointment }
718
+ ); // ['2024-03-15']
719
+
720
+ // Check if all participants accepted
721
+ const allAccepted = await evaluate(
722
+ "participant.all(status = 'accepted')",
723
+ { input: appointment }
724
+ ); // [true]
725
+
726
+ // Days until appointment
727
+ const daysUntil = await evaluate(
728
+ '(start - now()) / 1 day',
729
+ { input: appointment }
519
730
  );
520
731
  ```
521
732
 
733
+ ### Laboratory Results Analysis
734
+
735
+ ```typescript
736
+ const labResults = {
737
+ resourceType: 'Bundle',
738
+ entry: [
739
+ {
740
+ resource: {
741
+ resourceType: 'Observation',
742
+ code: {
743
+ coding: [{
744
+ system: 'http://loinc.org',
745
+ code: '2951-2',
746
+ display: 'Sodium'
747
+ }]
748
+ },
749
+ valueQuantity: { value: 145, unit: 'mmol/L' }, // In actual FHIR, this is stored as valueQuantity
750
+ referenceRange: [{
751
+ low: { value: 136, unit: 'mmol/L' },
752
+ high: { value: 145, unit: 'mmol/L' }
753
+ }],
754
+ status: 'final',
755
+ effectiveDateTime: '2024-01-15T08:00:00Z' // In FHIR JSON, stored as effectiveDateTime
756
+ }
757
+ },
758
+ {
759
+ resource: {
760
+ resourceType: 'Observation',
761
+ code: {
762
+ coding: [{
763
+ system: 'http://loinc.org',
764
+ code: '2823-3',
765
+ display: 'Potassium'
766
+ }]
767
+ },
768
+ valueQuantity: { value: 5.5, unit: 'mmol/L' }, // In actual FHIR, this is stored as valueQuantity
769
+ referenceRange: [{
770
+ low: { value: 3.5, unit: 'mmol/L' },
771
+ high: { value: 5.0, unit: 'mmol/L' }
772
+ }],
773
+ status: 'final',
774
+ effectiveDateTime: '2024-01-15T08:00:00Z' // In FHIR JSON, stored as effectiveDateTime
775
+ }
776
+ }
777
+ ]
778
+ };
779
+
780
+ // Find all abnormal results (outside reference range)
781
+ const abnormalResults = await evaluate(
782
+ `entry.resource.where(
783
+ value.ofType(Quantity).value < referenceRange.low.value or
784
+ value.ofType(Quantity).value > referenceRange.high.value
785
+ )`,
786
+ { input: labResults }
787
+ );
788
+
789
+ // Get test names and values
790
+ const testSummary = await evaluate(
791
+ `entry.resource.select(
792
+ code.coding.display.first() + ': ' +
793
+ value.ofType(Quantity).value.toString() + ' ' +
794
+ value.ofType(Quantity).unit
795
+ )`,
796
+ { input: labResults }
797
+ ); // ['Sodium: 145 mmol/L', 'Potassium: 5.5 mmol/L']
798
+
799
+ // Find critical values (e.g., potassium > 5.0)
800
+ const criticalValues = await evaluate(
801
+ `entry.resource.where(
802
+ code.coding.exists(code = '2823-3') and
803
+ value.ofType(Quantity).value > 5.0
804
+ )`,
805
+ { input: labResults }
806
+ );
807
+ ```
808
+
809
+ ### Allergy and Intolerance Checking
810
+
811
+ ```typescript
812
+ const allergyIntolerance = {
813
+ resourceType: 'AllergyIntolerance',
814
+ clinicalStatus: {
815
+ coding: [{
816
+ system: 'http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical',
817
+ code: 'active'
818
+ }]
819
+ },
820
+ verificationStatus: {
821
+ coding: [{
822
+ system: 'http://terminology.hl7.org/CodeSystem/allergyintolerance-verification',
823
+ code: 'confirmed'
824
+ }]
825
+ },
826
+ type: 'allergy',
827
+ category: ['medication'],
828
+ criticality: 'high',
829
+ code: {
830
+ coding: [{
831
+ system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
832
+ code: '7980',
833
+ display: 'Penicillin'
834
+ }]
835
+ },
836
+ reaction: [
837
+ {
838
+ substance: {
839
+ coding: [{
840
+ system: 'http://www.nlm.nih.gov/research/umls/rxnorm',
841
+ code: '7980',
842
+ display: 'Penicillin'
843
+ }]
844
+ },
845
+ manifestation: [
846
+ {
847
+ coding: [{
848
+ system: 'http://snomed.info/sct',
849
+ code: '39579001',
850
+ display: 'Anaphylaxis'
851
+ }]
852
+ },
853
+ {
854
+ coding: [{
855
+ system: 'http://snomed.info/sct',
856
+ code: '271807003',
857
+ display: 'Rash'
858
+ }]
859
+ }
860
+ ],
861
+ severity: 'severe'
862
+ }
863
+ ]
864
+ };
865
+
866
+ // Check if allergy is active and confirmed
867
+ const isActiveConfirmed = await evaluate(
868
+ "clinicalStatus.coding.code = 'active' and verificationStatus.coding.code = 'confirmed'",
869
+ { input: allergyIntolerance }
870
+ ); // [true]
871
+
872
+ // Get all reaction manifestations
873
+ const manifestations = await evaluate(
874
+ 'reaction.manifestation.coding.display',
875
+ { input: allergyIntolerance }
876
+ ); // ['Anaphylaxis', 'Rash']
877
+
878
+ // Check for severe reactions
879
+ const hasSevereReaction = await evaluate(
880
+ "reaction.exists(severity = 'severe')",
881
+ { input: allergyIntolerance }
882
+ ); // [true]
883
+
884
+ // Get allergen name
885
+ const allergen = await evaluate(
886
+ 'code.coding.display.first()',
887
+ { input: allergyIntolerance }
888
+ ); // ['Penicillin']
889
+ ```
890
+
891
+ ### Bundle Processing and Resource Extraction
892
+
893
+ ```typescript
894
+ const bundle = {
895
+ resourceType: 'Bundle',
896
+ type: 'searchset',
897
+ total: 3,
898
+ entry: [
899
+ {
900
+ fullUrl: 'http://example.org/Patient/123',
901
+ resource: {
902
+ resourceType: 'Patient',
903
+ id: '123',
904
+ name: [{ family: 'Smith', given: ['John'] }],
905
+ birthDate: '1980-01-01'
906
+ }
907
+ },
908
+ {
909
+ fullUrl: 'http://example.org/Encounter/456',
910
+ resource: {
911
+ resourceType: 'Encounter',
912
+ id: '456',
913
+ status: 'finished',
914
+ class: { code: 'IMP' },
915
+ subject: { reference: 'Patient/123' }
916
+ }
917
+ },
918
+ {
919
+ fullUrl: 'http://example.org/Condition/789',
920
+ resource: {
921
+ resourceType: 'Condition',
922
+ id: '789',
923
+ clinicalStatus: {
924
+ coding: [{ code: 'active' }]
925
+ },
926
+ code: {
927
+ coding: [{
928
+ system: 'http://snomed.info/sct',
929
+ code: '44054006',
930
+ display: 'Diabetes mellitus type 2'
931
+ }]
932
+ },
933
+ subject: { reference: 'Patient/123' },
934
+ onsetDateTime: '2015-06-15' // In FHIR JSON, stored as onsetDateTime
935
+ }
936
+ }
937
+ ]
938
+ };
939
+
940
+ // Extract all patients
941
+ const patients = await evaluate(
942
+ "entry.resource.where(resourceType = 'Patient')",
943
+ { input: bundle }
944
+ );
945
+
946
+ // Get all active conditions
947
+ const activeConditions = await evaluate(
948
+ "entry.resource.where(resourceType = 'Condition' and clinicalStatus.coding.code = 'active')",
949
+ { input: bundle }
950
+ );
951
+
952
+ // Count resources by type
953
+ const patientCount = await evaluate(
954
+ "entry.resource.where(resourceType = 'Patient').count()",
955
+ { input: bundle }
956
+ ); // [1]
957
+
958
+ // Get all resources for a specific patient
959
+ const patientResources = await evaluate(
960
+ "entry.resource.where(subject.reference = 'Patient/123')",
961
+ { input: bundle }
962
+ );
963
+
964
+ // Extract condition names
965
+ const conditions = await evaluate(
966
+ "entry.resource.where(resourceType = 'Condition').code.coding.display",
967
+ { input: bundle }
968
+ ); // ['Diabetes mellitus type 2']
969
+ ```
970
+
971
+ ### Using Variables and Advanced Control Flow
972
+
973
+ ```typescript
974
+ // Using defineVariable for complex calculations
975
+ const result = await evaluate(
976
+ `name
977
+ .defineVariable('fullName', given.first() + ' ' + family)
978
+ .select(%fullName + ' (' + use + ')')`,
979
+ {
980
+ input: {
981
+ name: [
982
+ { use: 'official', given: ['John'], family: 'Doe' },
983
+ { use: 'nickname', given: ['Johnny'], family: 'D' }
984
+ ]
985
+ }
986
+ }
987
+ ); // ['John Doe (official)', 'Johnny D (nickname)']
988
+
989
+ // Using aggregate for running calculations
990
+ const sum = await evaluate(
991
+ 'aggregate(0, $total + $this)',
992
+ { input: [1, 2, 3, 4, 5] }
993
+ ); // [15]
994
+
995
+ // Using iif for conditional logic
996
+ const ageGroup = await evaluate(
997
+ "iif(age < 18, 'child', iif(age < 65, 'adult', 'senior'))",
998
+ { input: { age: 70 } }
999
+ ); // ['senior']
1000
+
1001
+ // Complex aggregation with variables
1002
+ const stats = await evaluate(
1003
+ `defineVariable('values', value)
1004
+ .defineVariable('sum', %values.aggregate(0, $total + $this))
1005
+ .defineVariable('count', %values.count())
1006
+ .defineVariable('avg', %sum / %count)
1007
+ .select('Average: ' + %avg.toString())`,
1008
+ {
1009
+ input: [
1010
+ { value: 10 },
1011
+ { value: 20 },
1012
+ { value: 30 }
1013
+ ]
1014
+ }
1015
+ ); // ['Average: 20']
1016
+ ```
1017
+
522
1018
  ### Debugging Expressions
523
1019
 
524
1020
  Use the `inspect()` function to debug complex FHIRPath expressions:
@@ -532,14 +1028,14 @@ const bundle = {
532
1028
  };
533
1029
 
534
1030
  // Debug a complex expression with traces
535
- const result = await fhirpath.inspect(
1031
+ const result = await inspect(
536
1032
  `entry.resource
537
1033
  .trace('all resources')
538
1034
  .where(resourceType = 'Patient')
539
1035
  .trace('patients only')
540
1036
  .name.given
541
1037
  .trace('all given names')`,
542
- bundle
1038
+ { input: bundle }
543
1039
  );
544
1040
 
545
1041
  // Analyze the execution
@@ -562,48 +1058,30 @@ result.traces.forEach(trace => {
562
1058
 
563
1059
  ## Performance Tips
564
1060
 
565
- 1. **Use Fast Parsing for Production**: When parsing expressions in production, use `parseForEvaluation()` or enable `throwOnError`:
566
- ```typescript
567
- // Fastest - throws on first error
568
- const ast = fhirpath.parseForEvaluation('name.given');
569
-
570
- // Or use throwOnError option
571
- const result = fhirpath.parse('name.given', { throwOnError: true });
572
- ```
573
-
574
- 2. **Parse Once, Evaluate Many**: Parse expressions once and reuse the parsed AST:
1061
+ 1. **Reuse Parsed Expressions**: Parse expressions once when possible:
575
1062
  ```typescript
576
- const expr = fhirpath.parse('name.given');
577
- for (const patient of patients) {
578
- const names = await fhirpath.evaluate(expr, patient);
579
- }
1063
+ const parsed = parse('name.given');
1064
+ // Use parsed.ast for multiple evaluations
580
1065
  ```
581
1066
 
582
- 3. **Use Compiled Functions**: For expressions evaluated frequently, use compilation:
1067
+ 2. **Cache Analysis Results**: For repeated type checking, cache the analyzed AST:
583
1068
  ```typescript
584
- const getName = await fhirpath.compile('name.given');
585
- const results = await Promise.all(patients.map(p => getName(p)));
1069
+ const analysis = await analyze('name.given', { modelProvider });
1070
+ // Reuse analysis.ast which includes type information
586
1071
  ```
587
1072
 
588
- 4. **Disable Unnecessary Parser Features**: Only enable parser features you need:
1073
+ 3. **Use Simple Parser Mode**: For production, use simple parsing:
589
1074
  ```typescript
590
- // For development tools (IDEs, linters)
591
- const result = fhirpath.parse(expr, {
592
- errorRecovery: true, // Only if you need partial ASTs
593
- trackRanges: true // Only if you need source mapping
594
- });
1075
+ // Simple mode (default) - fastest
1076
+ const result = parse('name.given');
595
1077
 
596
- // For production (fastest)
597
- const ast = fhirpath.parseForEvaluation(expr);
1078
+ // LSP mode - only for IDE tools
1079
+ const result = parse('name.given', {
1080
+ mode: 'lsp',
1081
+ errorRecovery: true
1082
+ });
598
1083
  ```
599
1084
 
600
- 5. **Builder Instance**: Create a configured instance once and reuse:
601
- ```typescript
602
- const fp = FHIRPath.builder()
603
- .withCustomFunction('myFunc', /* ... */)
604
- .build();
605
- // Use fp instance throughout your application
606
- ```
607
1085
 
608
1086
  ## Contributing
609
1087