@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
@@ -0,0 +1,111 @@
1
+ export enum CursorContext {
2
+ Operator = 'operator',
3
+ Identifier = 'identifier',
4
+ Argument = 'argument',
5
+ Index = 'index',
6
+ Type = 'type',
7
+ }
8
+
9
+ export interface CursorNode {
10
+ type: 'CursorNode';
11
+ context: CursorContext;
12
+ position: number;
13
+ range: {
14
+ start: { line: number; character: number; offset: number };
15
+ end: { line: number; character: number; offset: number };
16
+ };
17
+ }
18
+
19
+ export interface CursorOperatorNode extends CursorNode {
20
+ context: CursorContext.Operator;
21
+ }
22
+
23
+ export interface CursorIdentifierNode extends CursorNode {
24
+ context: CursorContext.Identifier;
25
+ }
26
+
27
+ export interface CursorArgumentNode extends CursorNode {
28
+ context: CursorContext.Argument;
29
+ functionName: string;
30
+ argumentIndex: number;
31
+ }
32
+
33
+ export interface CursorIndexNode extends CursorNode {
34
+ context: CursorContext.Index;
35
+ }
36
+
37
+ export interface CursorTypeNode extends CursorNode {
38
+ context: CursorContext.Type;
39
+ typeOperator: 'is' | 'as' | 'ofType';
40
+ }
41
+
42
+ export type AnyCursorNode =
43
+ | CursorOperatorNode
44
+ | CursorIdentifierNode
45
+ | CursorArgumentNode
46
+ | CursorIndexNode
47
+ | CursorTypeNode;
48
+
49
+ export function isCursorNode(node: any): node is CursorNode {
50
+ return node && node.type === 'CursorNode';
51
+ }
52
+
53
+ export function createCursorOperatorNode(position: number): CursorOperatorNode {
54
+ const point = { line: 0, character: position, offset: position };
55
+ return {
56
+ type: 'CursorNode',
57
+ context: CursorContext.Operator,
58
+ position,
59
+ range: { start: point, end: point },
60
+ };
61
+ }
62
+
63
+ export function createCursorIdentifierNode(position: number): CursorIdentifierNode {
64
+ const point = { line: 0, character: position, offset: position };
65
+ return {
66
+ type: 'CursorNode',
67
+ context: CursorContext.Identifier,
68
+ position,
69
+ range: { start: point, end: point },
70
+ };
71
+ }
72
+
73
+ export function createCursorArgumentNode(
74
+ position: number,
75
+ functionName: string,
76
+ argumentIndex: number
77
+ ): CursorArgumentNode {
78
+ const point = { line: 0, character: position, offset: position };
79
+ return {
80
+ type: 'CursorNode',
81
+ context: CursorContext.Argument,
82
+ position,
83
+ functionName,
84
+ argumentIndex,
85
+ range: { start: point, end: point },
86
+ };
87
+ }
88
+
89
+ export function createCursorIndexNode(position: number): CursorIndexNode {
90
+ const point = { line: 0, character: position, offset: position };
91
+ return {
92
+ type: 'CursorNode',
93
+ context: CursorContext.Index,
94
+ position,
95
+ range: { start: point, end: point },
96
+ };
97
+ }
98
+
99
+ export function createCursorTypeNode(
100
+ position: number,
101
+ typeOperator: 'is' | 'as' | 'ofType'
102
+ ): CursorTypeNode {
103
+ const point = { line: 0, character: position, offset: position };
104
+ return {
105
+ type: 'CursorNode',
106
+ context: CursorContext.Type,
107
+ position,
108
+ typeOperator,
109
+ range: { start: point, end: point },
110
+ };
111
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,247 @@
1
+ import type { Range, Diagnostic } from './types';
2
+ import { DiagnosticSeverity } from './types';
3
+
4
+ /**
5
+ * Base error class for all FHIRPath errors
6
+ */
7
+ export class FHIRPathError extends Error {
8
+ constructor(
9
+ public code: string,
10
+ message: string,
11
+ public location?: Range
12
+ ) {
13
+ super(message);
14
+ this.name = 'FHIRPathError';
15
+
16
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
17
+ if (Error.captureStackTrace) {
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Convert FHIRPathError to Diagnostic for analyzer
25
+ */
26
+ export function toDiagnostic(error: FHIRPathError, severity: DiagnosticSeverity = DiagnosticSeverity.Error): Diagnostic {
27
+ return {
28
+ code: error.code,
29
+ message: error.message,
30
+ severity,
31
+ range: error.location!,
32
+ source: 'fhirpath'
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Error factory with specialized constructors
38
+ */
39
+ export const Errors = {
40
+ // Resolution errors (1000-1999)
41
+ unknownOperator(operator: string, location?: Range): FHIRPathError {
42
+ return new FHIRPathError(ErrorCodes.UNKNOWN_OPERATOR, `Unknown operator: ${operator}`, location);
43
+ },
44
+
45
+ unknownFunction(name: string, location?: Range): FHIRPathError {
46
+ return new FHIRPathError(ErrorCodes.UNKNOWN_FUNCTION, `Unknown function: ${name}`, location);
47
+ },
48
+
49
+ unknownVariable(name: string, location?: Range): FHIRPathError {
50
+ return new FHIRPathError(ErrorCodes.UNKNOWN_VARIABLE, `Unknown variable: ${name}`, location);
51
+ },
52
+
53
+ unknownUserVariable(name: string, location?: Range): FHIRPathError {
54
+ return new FHIRPathError(ErrorCodes.UNKNOWN_USER_VARIABLE, `Unknown user variable: ${name}`, location);
55
+ },
56
+
57
+ unknownProperty(property: string, type: string, location?: Range): FHIRPathError {
58
+ return new FHIRPathError(ErrorCodes.UNKNOWN_PROPERTY, `Unknown property '${property}' on type ${type}`, location);
59
+ },
60
+
61
+ unknownNodeType(nodeType: string, location?: Range): FHIRPathError {
62
+ return new FHIRPathError(ErrorCodes.UNKNOWN_NODE_TYPE, `Unknown node type: ${nodeType}`, location);
63
+ },
64
+
65
+ noEvaluatorFound(evaluatorType: string, name: string, location?: Range): FHIRPathError {
66
+ return new FHIRPathError(ErrorCodes.NO_EVALUATOR_FOUND, `No evaluator found for ${evaluatorType}: ${name}`, location);
67
+ },
68
+
69
+ variableNotDefined(name: string, location?: Range): FHIRPathError {
70
+ return new FHIRPathError(ErrorCodes.VARIABLE_NOT_DEFINED, `Variable '${name}' is not defined in the current scope`, location);
71
+ },
72
+
73
+ // Arity errors (2000-2999)
74
+ wrongArgumentCount(funcName: string, expected: number, actual: number, location?: Range): FHIRPathError {
75
+ return new FHIRPathError(ErrorCodes.WRONG_ARGUMENT_COUNT, `${funcName} expects ${expected} arguments, got ${actual}`, location);
76
+ },
77
+
78
+ wrongArgumentCountRange(funcName: string, min: number, max: number, actual: number, location?: Range): FHIRPathError {
79
+ return new FHIRPathError(ErrorCodes.WRONG_ARGUMENT_COUNT_RANGE, `${funcName} expects ${min} to ${max} arguments, got ${actual}`, location);
80
+ },
81
+
82
+ singletonRequired(funcName: string, actualCount: number, location?: Range): FHIRPathError {
83
+ return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} requires a single item, but collection has ${actualCount} items`, location);
84
+ },
85
+
86
+ stringSingletonRequired(funcName: string, actualCount: number, location?: Range): FHIRPathError {
87
+ return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} can only be used on a single string, but collection has ${actualCount} items`, location);
88
+ },
89
+
90
+ emptyNotAllowed(funcName: string, location?: Range): FHIRPathError {
91
+ return new FHIRPathError(ErrorCodes.EMPTY_NOT_ALLOWED, `${funcName} cannot operate on empty collection`, location);
92
+ },
93
+
94
+ argumentRequired(funcName: string, argumentName: string, location?: Range): FHIRPathError {
95
+ return new FHIRPathError(ErrorCodes.ARGUMENT_REQUIRED, `${funcName} requires ${argumentName}`, location);
96
+ },
97
+
98
+ singletonTypeRequired(funcName: string, actualType: string, location?: Range): FHIRPathError {
99
+ return new FHIRPathError(ErrorCodes.SINGLETON_REQUIRED, `${funcName} expects a singleton value, but received collection type ${actualType}`, location);
100
+ },
101
+
102
+ // Type errors (3000-3999)
103
+
104
+ operatorTypeMismatch(operator: string, leftType: string, rightType: string, location?: Range): FHIRPathError {
105
+ return new FHIRPathError(ErrorCodes.OPERATOR_TYPE_MISMATCH, `Operator '${operator}' cannot be applied to types ${leftType} and ${rightType}`, location);
106
+ },
107
+
108
+ argumentTypeMismatch(argIndex: number, funcName: string, expected: string, actual: string, location?: Range): FHIRPathError {
109
+ return new FHIRPathError(ErrorCodes.ARGUMENT_TYPE_MISMATCH, `Argument ${argIndex} of ${funcName}: expected ${expected}, got ${actual}`, location);
110
+ },
111
+
112
+ conversionFailed(value: string, targetType: string, location?: Range): FHIRPathError {
113
+ return new FHIRPathError(ErrorCodes.CONVERSION_FAILED, `Cannot convert '${value}' to ${targetType}`, location);
114
+ },
115
+
116
+ invalidValueType(expected: string, actual: string, location?: Range): FHIRPathError {
117
+ return new FHIRPathError(ErrorCodes.INVALID_VALUE_TYPE, `${expected} expected, got ${actual}`, location);
118
+ },
119
+
120
+ invalidOperandType(operation: string, type: string, location?: Range): FHIRPathError {
121
+ return new FHIRPathError(ErrorCodes.INVALID_OPERAND_TYPE, `Cannot apply ${operation} to ${type}`, location);
122
+ },
123
+
124
+ stringOperationOnNonString(operation: string, location?: Range): FHIRPathError {
125
+ return new FHIRPathError(ErrorCodes.STRING_OPERATION_ON_NON_STRING, `${operation} can only be used on string values`, location);
126
+ },
127
+
128
+ numericOperationOnNonNumeric(operation: string, location?: Range): FHIRPathError {
129
+ return new FHIRPathError(ErrorCodes.NUMERIC_OPERATION_ON_NON_NUMERIC, `${operation} can only be applied to numeric values`, location);
130
+ },
131
+
132
+ booleanOperationOnNonBoolean(operation: string, index: number, actualType: string, location?: Range): FHIRPathError {
133
+ return new FHIRPathError(ErrorCodes.BOOLEAN_OPERATION_ON_NON_BOOLEAN, `${operation} expects all items to be Boolean values, but item at index ${index} is ${actualType}`, location);
134
+ },
135
+
136
+ // Configuration errors (4000-4999)
137
+ modelProviderRequired(operation: string, location?: Range): FHIRPathError {
138
+ return new FHIRPathError(ErrorCodes.MODEL_PROVIDER_REQUIRED,
139
+ `ModelProvider required for '${operation}' operation. Even primitive types like Boolean can fail due to choice types (e.g., Patient.deceased is actually deceasedBoolean in FHIR data)`,
140
+ location);
141
+ },
142
+
143
+ // Syntax errors (5000-5999)
144
+ unexpectedToken(token: string, location?: Range): FHIRPathError {
145
+ return new FHIRPathError(ErrorCodes.UNEXPECTED_TOKEN, `Unexpected token: ${token}`, location);
146
+ },
147
+
148
+ expectedToken(expected: string, actual: string, location?: Range): FHIRPathError {
149
+ return new FHIRPathError(ErrorCodes.EXPECTED_TOKEN, `Expected ${expected}, got ${actual}`, location);
150
+ },
151
+
152
+ invalidSyntax(details: string, location?: Range): FHIRPathError {
153
+ return new FHIRPathError(ErrorCodes.INVALID_SYNTAX, `Invalid syntax: ${details}`, location);
154
+ },
155
+
156
+ expectedIdentifier(after: string, actual: string, location?: Range): FHIRPathError {
157
+ return new FHIRPathError(ErrorCodes.EXPECTED_IDENTIFIER, `Expected identifier after '${after}', got: ${actual}`, location);
158
+ },
159
+
160
+ expectedTypeName(actual: string, location?: Range): FHIRPathError {
161
+ return new FHIRPathError(ErrorCodes.EXPECTED_TYPE_NAME, `Expected type name, got: ${actual}`, location);
162
+ },
163
+
164
+ // Domain errors (6000-6999)
165
+ divisionByZero(location?: Range): FHIRPathError {
166
+ return new FHIRPathError(ErrorCodes.DIVISION_BY_ZERO, 'Division by zero', location);
167
+ },
168
+
169
+ invalidDateTimeFormat(format: string, location?: Range): FHIRPathError {
170
+ return new FHIRPathError(ErrorCodes.INVALID_DATE_TIME_FORMAT, `Invalid date/time format: '${format}'`, location);
171
+ },
172
+
173
+ incompatibleUnits(unit1: string, unit2: string, location?: Range): FHIRPathError {
174
+ return new FHIRPathError(ErrorCodes.INCOMPATIBLE_UNITS, `Cannot perform operation on incompatible units: ${unit1} and ${unit2}`, location);
175
+ },
176
+
177
+ indexOutOfBounds(index: number, size: number, location?: Range): FHIRPathError {
178
+ return new FHIRPathError(ErrorCodes.INDEX_OUT_OF_BOUNDS, `Index ${index} out of bounds for collection of size ${size}`, location);
179
+ },
180
+
181
+ invalidOperation(details: string, location?: Range): FHIRPathError {
182
+ return new FHIRPathError(ErrorCodes.INVALID_OPERATION, `Invalid operation: ${details}`, location);
183
+ },
184
+
185
+ invalidPrecision(operation: string, location?: Range): FHIRPathError {
186
+ return new FHIRPathError(ErrorCodes.INVALID_PRECISION, `${operation} precision must be a non-negative integer`, location);
187
+ },
188
+
189
+ invalidStringOperation(operation: string, paramName: string, location?: Range): FHIRPathError {
190
+ return new FHIRPathError(ErrorCodes.INVALID_STRING_OPERATION, `${operation} ${paramName} must be a string`, location);
191
+ },
192
+
193
+ invalidNumericOperation(operation: string, paramName: string, expectedType: string, location?: Range): FHIRPathError {
194
+ return new FHIRPathError(ErrorCodes.INVALID_NUMERIC_OPERATION, `${operation} ${paramName} must be ${expectedType}`, location);
195
+ }
196
+ };
197
+
198
+ // Export error codes as enum for external use
199
+ export enum ErrorCodes {
200
+ // Resolution errors (1000-1999)
201
+ UNKNOWN_OPERATOR = 'FP1001',
202
+ UNKNOWN_FUNCTION = 'FP1002',
203
+ UNKNOWN_VARIABLE = 'FP1003',
204
+ UNKNOWN_USER_VARIABLE = 'FP1004',
205
+ UNKNOWN_PROPERTY = 'FP1005',
206
+ UNKNOWN_NODE_TYPE = 'FP1006',
207
+ NO_EVALUATOR_FOUND = 'FP1007',
208
+ VARIABLE_NOT_DEFINED = 'FP1008',
209
+
210
+ // Arity errors (2000-2999)
211
+ WRONG_ARGUMENT_COUNT = 'FP2001',
212
+ WRONG_ARGUMENT_COUNT_RANGE = 'FP2002',
213
+ SINGLETON_REQUIRED = 'FP2003',
214
+ EMPTY_NOT_ALLOWED = 'FP2004',
215
+ ARGUMENT_REQUIRED = 'FP2005',
216
+
217
+ // Type errors (3000-3999)
218
+ // FP3001 - removed (unified with FP3006)
219
+ OPERATOR_TYPE_MISMATCH = 'FP3002',
220
+ ARGUMENT_TYPE_MISMATCH = 'FP3003',
221
+ CONVERSION_FAILED = 'FP3004',
222
+ INVALID_VALUE_TYPE = 'FP3005',
223
+ INVALID_OPERAND_TYPE = 'FP3006',
224
+ STRING_OPERATION_ON_NON_STRING = 'FP3007',
225
+ NUMERIC_OPERATION_ON_NON_NUMERIC = 'FP3008',
226
+ BOOLEAN_OPERATION_ON_NON_BOOLEAN = 'FP3009',
227
+
228
+ // Configuration errors (4000-4999)
229
+ MODEL_PROVIDER_REQUIRED = 'FP4001',
230
+
231
+ // Syntax errors (5000-5999)
232
+ UNEXPECTED_TOKEN = 'FP5001',
233
+ EXPECTED_TOKEN = 'FP5002',
234
+ INVALID_SYNTAX = 'FP5003',
235
+ EXPECTED_IDENTIFIER = 'FP5004',
236
+ EXPECTED_TYPE_NAME = 'FP5005',
237
+
238
+ // Domain errors (6000-6999)
239
+ DIVISION_BY_ZERO = 'FP6001',
240
+ INVALID_DATE_TIME_FORMAT = 'FP6002',
241
+ INCOMPATIBLE_UNITS = 'FP6003',
242
+ INDEX_OUT_OF_BOUNDS = 'FP6004',
243
+ INVALID_OPERATION = 'FP6005',
244
+ INVALID_PRECISION = 'FP6006',
245
+ INVALID_STRING_OPERATION = 'FP6007',
246
+ INVALID_NUMERIC_OPERATION = 'FP6008'
247
+ }
package/src/index.ts CHANGED
@@ -2,16 +2,20 @@ import { Parser } from './parser';
2
2
  import { Interpreter, RuntimeContextManager } from './interpreter';
3
3
  import { Analyzer } from './analyzer';
4
4
  import type { AnalysisResult } from './types';
5
+ import { box, unbox } from './boxing';
6
+ import { FHIRPathError, Errors } from './errors';
5
7
 
6
8
  export interface EvaluateOptions {
7
9
  input?: unknown;
8
10
  variables?: Record<string, unknown>;
11
+ modelProvider?: import('./types').ModelProvider;
12
+ inputType?: import('./types').TypeInfo;
9
13
  }
10
14
 
11
- export function evaluate(
15
+ export async function evaluate(
12
16
  expression: string,
13
17
  options: EvaluateOptions = {}
14
- ): any[] {
18
+ ): Promise<any[]> {
15
19
  const parser = new Parser(expression);
16
20
  const parseResult = parser.parse();
17
21
 
@@ -19,19 +23,60 @@ export function evaluate(
19
23
  if (parseResult.errors.length > 0) {
20
24
  // For backward compatibility, throw the first error
21
25
  const firstError = parseResult.errors[0]!;
22
- throw new Error(firstError.message);
26
+ throw Errors.invalidSyntax(firstError.message);
23
27
  }
24
28
 
25
- const ast = parseResult.ast;
29
+ // ALWAYS analyze the AST
30
+ const analyzer = new Analyzer(options.modelProvider);
31
+ const analysisResult = await analyzer.analyze(
32
+ parseResult.ast,
33
+ options.variables,
34
+ options.inputType
35
+ );
26
36
 
27
- const interpreter = new Interpreter();
37
+ // Check for analysis errors
38
+ const errors = analysisResult.diagnostics.filter(d => d.severity === 1); // DiagnosticSeverity.Error
39
+ if (errors.length > 0) {
40
+ // Throw the first error
41
+ const firstError = errors[0]!;
42
+ if (firstError.code) {
43
+ // Always throw as FHIRPathError if we have a code
44
+ throw new FHIRPathError(firstError.code, firstError.message, firstError.range);
45
+ } else {
46
+ // Otherwise throw a generic error
47
+ throw new Error(firstError.message);
48
+ }
49
+ }
50
+
51
+ // Use the analyzed AST with type information
52
+ const interpreter = new Interpreter(undefined, options.modelProvider);
28
53
  const input = options.input === undefined ? [] : Array.isArray(options.input) ? options.input : [options.input];
29
54
 
55
+ // Box input with typeInfo if we have a modelProvider and the input is a FHIR resource
56
+ let boxedInput = input;
57
+ if (options.modelProvider) {
58
+ boxedInput = await Promise.all(input.map(async item => {
59
+ if (item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') {
60
+ // Get type info asynchronously
61
+ const typeInfo = await options.modelProvider!.getType(item.resourceType);
62
+ if (typeInfo) {
63
+ return box(item, typeInfo);
64
+ }
65
+ }
66
+ return item;
67
+ }));
68
+ }
69
+
30
70
  // Create context with variables if provided
31
71
  let context = RuntimeContextManager.create(input);
32
72
 
33
- // Set $this to the input (required for expressions like $this.where(...))
34
- context = RuntimeContextManager.setVariable(context, '$this', input);
73
+ // Set $this to the boxed input (required for expressions like $this.where(...))
74
+ context = RuntimeContextManager.setVariable(context, '$this', boxedInput);
75
+
76
+ // Add model provider to context if available
77
+ if (options.modelProvider) {
78
+ context.modelProvider = options.modelProvider;
79
+ }
35
80
 
36
81
  if (options.variables) {
37
82
  for (const [key, value] of Object.entries(options.variables)) {
@@ -40,12 +85,13 @@ export function evaluate(
40
85
  }
41
86
  }
42
87
 
43
- const result = interpreter.evaluate(ast, input, context);
88
+ const result = await interpreter.evaluate(analysisResult.ast, input, context);
44
89
 
45
- return result.value;
90
+ // Unbox the results before returning
91
+ return result.value.map(unbox);
46
92
  }
47
93
 
48
- export function analyze(
94
+ export async function analyze(
49
95
  expression: string,
50
96
  options: {
51
97
  variables?: Record<string, unknown>;
@@ -53,7 +99,7 @@ export function analyze(
53
99
  inputType?: import('./types').TypeInfo;
54
100
  errorRecovery?: boolean;
55
101
  } = {}
56
- ): AnalysisResult {
102
+ ): Promise<AnalysisResult> {
57
103
  // Use LSP mode with error recovery if requested
58
104
  const parserOptions = options.errorRecovery
59
105
  ? { mode: 'lsp' as const, errorRecovery: true }
@@ -66,14 +112,14 @@ export function analyze(
66
112
  if (!options.errorRecovery && parseResult.errors.length > 0) {
67
113
  // For backward compatibility, throw the first error
68
114
  const firstError = parseResult.errors[0]!;
69
- throw new Error(firstError.message);
115
+ throw Errors.invalidSyntax(firstError.message);
70
116
  }
71
117
 
72
118
  const ast = parseResult.ast;
73
119
 
74
120
  // Create analyzer with optional model provider
75
121
  const analyzer = new Analyzer(options.modelProvider);
76
- const analysisResult = analyzer.analyze(ast, options.variables, options.inputType);
122
+ const analysisResult = await analyzer.analyze(ast, options.variables, options.inputType);
77
123
 
78
124
  // If error recovery is enabled, merge parse errors into diagnostics
79
125
  if (options.errorRecovery && parseResult.errors.length > 0) {
@@ -109,4 +155,41 @@ export type { FHIRModelContext, FHIRModelProviderConfig } from './model-provider
109
155
 
110
156
  // Export inspect API
111
157
  export { inspect } from './inspect';
112
- export type { InspectOptions, InspectResult, TraceEntry } from './inspect';
158
+ export type { InspectOptions, InspectResult, ASTMetadata } from './inspect';
159
+
160
+ // Export error system
161
+ export { FHIRPathError, Errors, ErrorCodes } from './errors';
162
+
163
+ // Export LSP support - completion provider and cursor nodes
164
+ /**
165
+ * Provides context-aware code completions for FHIRPath expressions.
166
+ * @param expression - The FHIRPath expression being edited
167
+ * @param cursorPosition - The cursor position (0-based offset)
168
+ * @param options - Optional configuration including modelProvider and variables
169
+ * @returns Array of completion items with labels, kinds, and documentation
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * import { provideCompletions } from 'fhirpath';
174
+ *
175
+ * const completions = provideCompletions('Patient.', 8);
176
+ * // Returns completions for properties and functions after 'Patient.'
177
+ * ```
178
+ */
179
+ export { provideCompletions, CompletionKind } from './completion-provider';
180
+ export type {
181
+ CompletionItem,
182
+ CompletionOptions
183
+ } from './completion-provider';
184
+
185
+ // Export cursor node types for LSP integration
186
+ export { CursorContext, isCursorNode } from './cursor-nodes';
187
+ export type {
188
+ CursorNode,
189
+ CursorOperatorNode,
190
+ CursorIdentifierNode,
191
+ CursorArgumentNode,
192
+ CursorIndexNode,
193
+ CursorTypeNode,
194
+ AnyCursorNode
195
+ } from './cursor-nodes';