@atomic-ehr/fhirpath 0.0.1 → 0.0.2-canary.2fee5d9.20250808110507

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 (101) hide show
  1. package/README.md +2 -0
  2. package/dist/index.d.ts +195 -77
  3. package/dist/index.js +2541 -981
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/analyzer.ts +536 -64
  7. package/src/boxing.ts +124 -0
  8. package/src/errors.ts +246 -0
  9. package/src/index.ts +57 -9
  10. package/src/inspect.ts +307 -174
  11. package/src/interpreter.ts +299 -48
  12. package/src/model-provider.ts +110 -8
  13. package/src/operations/abs-function.ts +19 -10
  14. package/src/operations/aggregate-function.ts +11 -3
  15. package/src/operations/all-function.ts +18 -8
  16. package/src/operations/allFalse-function.ts +9 -6
  17. package/src/operations/allTrue-function.ts +9 -6
  18. package/src/operations/and-operator.ts +20 -11
  19. package/src/operations/anyFalse-function.ts +6 -4
  20. package/src/operations/anyTrue-function.ts +6 -4
  21. package/src/operations/as-operator.ts +1 -0
  22. package/src/operations/ceiling-function.ts +9 -5
  23. package/src/operations/children-function.ts +94 -0
  24. package/src/operations/combine-function.ts +4 -2
  25. package/src/operations/combine-operator.ts +18 -4
  26. package/src/operations/contains-function.ts +20 -8
  27. package/src/operations/contains-operator.ts +14 -6
  28. package/src/operations/count-function.ts +2 -1
  29. package/src/operations/defineVariable-function.ts +5 -3
  30. package/src/operations/descendants-function.ts +62 -0
  31. package/src/operations/distinct-function.ts +16 -4
  32. package/src/operations/div-operator.ts +15 -2
  33. package/src/operations/divide-operator.ts +10 -5
  34. package/src/operations/dot-operator.ts +3 -1
  35. package/src/operations/empty-function.ts +2 -1
  36. package/src/operations/endsWith-function.ts +22 -10
  37. package/src/operations/equal-operator.ts +18 -6
  38. package/src/operations/equivalent-operator.ts +15 -10
  39. package/src/operations/exclude-function.ts +12 -10
  40. package/src/operations/exists-function.ts +14 -7
  41. package/src/operations/first-function.ts +7 -4
  42. package/src/operations/floor-function.ts +9 -5
  43. package/src/operations/greater-operator.ts +9 -4
  44. package/src/operations/greater-or-equal-operator.ts +9 -4
  45. package/src/operations/iif-function.ts +18 -16
  46. package/src/operations/implies-operator.ts +22 -7
  47. package/src/operations/in-operator.ts +17 -7
  48. package/src/operations/index.ts +3 -0
  49. package/src/operations/indexOf-function.ts +22 -10
  50. package/src/operations/intersect-function.ts +17 -20
  51. package/src/operations/is-operator.ts +63 -14
  52. package/src/operations/isDistinct-function.ts +12 -6
  53. package/src/operations/join-function.ts +15 -4
  54. package/src/operations/last-function.ts +7 -4
  55. package/src/operations/length-function.ts +12 -5
  56. package/src/operations/less-operator.ts +9 -4
  57. package/src/operations/less-or-equal-operator.ts +9 -4
  58. package/src/operations/less-than.ts +25 -1
  59. package/src/operations/lower-function.ts +12 -5
  60. package/src/operations/minus-operator.ts +9 -4
  61. package/src/operations/mod-operator.ts +19 -2
  62. package/src/operations/multiply-operator.ts +11 -6
  63. package/src/operations/not-equal-operator.ts +15 -6
  64. package/src/operations/not-equivalent-operator.ts +15 -10
  65. package/src/operations/not-function.ts +12 -4
  66. package/src/operations/ofType-function.ts +135 -0
  67. package/src/operations/or-operator.ts +20 -11
  68. package/src/operations/plus-operator.ts +18 -6
  69. package/src/operations/power-function.ts +21 -9
  70. package/src/operations/replace-function.ts +26 -9
  71. package/src/operations/round-function.ts +23 -8
  72. package/src/operations/select-function.ts +12 -6
  73. package/src/operations/single-function.ts +5 -3
  74. package/src/operations/skip-function.ts +12 -5
  75. package/src/operations/split-function.ts +24 -9
  76. package/src/operations/sqrt-function.ts +9 -5
  77. package/src/operations/startsWith-function.ts +20 -8
  78. package/src/operations/subsetOf-function.ts +14 -11
  79. package/src/operations/substring-function.ts +36 -19
  80. package/src/operations/supersetOf-function.ts +14 -11
  81. package/src/operations/tail-function.ts +3 -1
  82. package/src/operations/take-function.ts +12 -5
  83. package/src/operations/toBoolean-function.ts +18 -11
  84. package/src/operations/toDecimal-function.ts +13 -6
  85. package/src/operations/toInteger-function.ts +13 -6
  86. package/src/operations/toString-function.ts +17 -10
  87. package/src/operations/trace-function.ts +12 -5
  88. package/src/operations/trim-function.ts +11 -4
  89. package/src/operations/truncate-function.ts +9 -5
  90. package/src/operations/unary-minus-operator.ts +22 -12
  91. package/src/operations/unary-plus-operator.ts +1 -0
  92. package/src/operations/union-function.ts +19 -24
  93. package/src/operations/union-operator.ts +1 -0
  94. package/src/operations/upper-function.ts +12 -5
  95. package/src/operations/where-function.ts +15 -8
  96. package/src/operations/xor-operator.ts +8 -3
  97. package/src/parser.ts +391 -8
  98. package/src/quantity-value.ts +4 -8
  99. package/src/registry.ts +3 -3
  100. package/src/types.ts +10 -6
  101. package/src/parser-base.ts +0 -400
package/src/boxing.ts ADDED
@@ -0,0 +1,124 @@
1
+ import type {TypeInfo} from './types';
2
+ export type {TypeInfo};
3
+
4
+ /**
5
+ * Symbol to mark boxed values
6
+ */
7
+ const BOXED_SYMBOL = Symbol('FHIRPathBoxedValue');
8
+
9
+ /**
10
+ * Extension structure for FHIR primitive elements
11
+ */
12
+ export interface Extension {
13
+ url: string;
14
+ [key: string]: any;
15
+ }
16
+
17
+ /**
18
+ * Metadata for FHIR primitive elements (e.g., _gender for gender property)
19
+ */
20
+ export interface PrimitiveElement {
21
+ id?: string;
22
+ extension?: Extension[];
23
+ }
24
+
25
+ /**
26
+ * Boxed FHIRPath value with metadata
27
+ */
28
+ export interface FHIRPathValue<T = any> {
29
+ /** The actual value */
30
+ value: T;
31
+
32
+ /** Type information from ModelProvider */
33
+ typeInfo?: TypeInfo;
34
+
35
+ /** Primitive element extension (for primitives only) */
36
+ primitiveElement?: PrimitiveElement;
37
+
38
+ /** Internal marker to identify boxed values */
39
+ [BOXED_SYMBOL]: true;
40
+ }
41
+
42
+ /**
43
+ * Box a value with optional metadata
44
+ */
45
+ export function box<T>(
46
+ value: T,
47
+ typeInfo?: TypeInfo,
48
+ primitiveElement?: PrimitiveElement
49
+ ): FHIRPathValue<T> {
50
+ return {
51
+ value,
52
+ typeInfo,
53
+ primitiveElement,
54
+ [BOXED_SYMBOL]: true
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Unbox a value to get the raw value
60
+ */
61
+ export function unbox<T>(boxedValue: FHIRPathValue<T>): T {
62
+ return boxedValue.value;
63
+ }
64
+
65
+ /**
66
+ * Check if a value is already boxed
67
+ */
68
+ export function isBoxed(value: any): value is FHIRPathValue {
69
+ return value !== null &&
70
+ value !== undefined &&
71
+ typeof value === 'object' &&
72
+ BOXED_SYMBOL in value &&
73
+ value[BOXED_SYMBOL] === true;
74
+ }
75
+
76
+ /**
77
+ * Ensure a value is boxed, boxing it if necessary
78
+ */
79
+ export function ensureBoxed(value: any, typeInfo?: TypeInfo): FHIRPathValue {
80
+ if (isBoxed(value)) {
81
+ return value;
82
+ }
83
+ return box(value, typeInfo);
84
+ }
85
+
86
+ /**
87
+ * Map over an array of boxed values, preserving boxing
88
+ */
89
+ export function mapBoxed<T, U>(
90
+ values: FHIRPathValue<T>[],
91
+ fn: (value: T, index: number) => U,
92
+ typeInfo?: TypeInfo
93
+ ): FHIRPathValue<U>[] {
94
+ return values.map((boxedValue, index) =>
95
+ box(fn(boxedValue.value, index), typeInfo)
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Filter an array of boxed values, preserving boxing
101
+ */
102
+ export function filterBoxed<T>(
103
+ values: FHIRPathValue<T>[],
104
+ predicate: (value: T, index: number) => boolean
105
+ ): FHIRPathValue<T>[] {
106
+ return values.filter((boxedValue, index) =>
107
+ predicate(boxedValue.value, index)
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Flatten an array of boxed arrays into a single array of boxed values
113
+ */
114
+ export function flattenBoxed<T>(values: FHIRPathValue<T[]>[]): FHIRPathValue<T>[] {
115
+ const result: FHIRPathValue<T>[] = [];
116
+ for (const boxedArray of values) {
117
+ if (Array.isArray(boxedArray.value)) {
118
+ for (const item of boxedArray.value) {
119
+ result.push(box(item, boxedArray.typeInfo));
120
+ }
121
+ }
122
+ }
123
+ return result;
124
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,246 @@
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
+ // Type errors (3000-3999)
99
+ typeNotAssignable(sourceType: string, targetType: string, location?: Range): FHIRPathError {
100
+ return new FHIRPathError(ErrorCodes.TYPE_NOT_ASSIGNABLE, `Type ${sourceType} is not assignable to type ${targetType}`, location);
101
+ },
102
+
103
+ operatorTypeMismatch(operator: string, leftType: string, rightType: string, location?: Range): FHIRPathError {
104
+ return new FHIRPathError(ErrorCodes.OPERATOR_TYPE_MISMATCH, `Operator '${operator}' cannot be applied to types ${leftType} and ${rightType}`, location);
105
+ },
106
+
107
+ argumentTypeMismatch(argIndex: number, funcName: string, expected: string, actual: string, location?: Range): FHIRPathError {
108
+ return new FHIRPathError(ErrorCodes.ARGUMENT_TYPE_MISMATCH, `Argument ${argIndex} of ${funcName}: expected ${expected}, got ${actual}`, location);
109
+ },
110
+
111
+ conversionFailed(value: string, targetType: string, location?: Range): FHIRPathError {
112
+ return new FHIRPathError(ErrorCodes.CONVERSION_FAILED, `Cannot convert '${value}' to ${targetType}`, location);
113
+ },
114
+
115
+ invalidValueType(expected: string, actual: string, location?: Range): FHIRPathError {
116
+ return new FHIRPathError(ErrorCodes.INVALID_VALUE_TYPE, `${expected} expected, got ${actual}`, location);
117
+ },
118
+
119
+ invalidOperandType(operation: string, type: string, location?: Range): FHIRPathError {
120
+ return new FHIRPathError(ErrorCodes.INVALID_OPERAND_TYPE, `Cannot apply ${operation} to ${type}`, location);
121
+ },
122
+
123
+ stringOperationOnNonString(operation: string, location?: Range): FHIRPathError {
124
+ return new FHIRPathError(ErrorCodes.STRING_OPERATION_ON_NON_STRING, `${operation} can only be used on string values`, location);
125
+ },
126
+
127
+ numericOperationOnNonNumeric(operation: string, location?: Range): FHIRPathError {
128
+ return new FHIRPathError(ErrorCodes.NUMERIC_OPERATION_ON_NON_NUMERIC, `${operation} can only be applied to numeric values`, location);
129
+ },
130
+
131
+ booleanOperationOnNonBoolean(operation: string, index: number, actualType: string, location?: Range): FHIRPathError {
132
+ 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);
133
+ },
134
+
135
+ // Configuration errors (4000-4999)
136
+ modelProviderRequired(operation: string, location?: Range): FHIRPathError {
137
+ return new FHIRPathError(ErrorCodes.MODEL_PROVIDER_REQUIRED,
138
+ `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)`,
139
+ location);
140
+ },
141
+
142
+ // Syntax errors (5000-5999)
143
+ unexpectedToken(token: string, location?: Range): FHIRPathError {
144
+ return new FHIRPathError(ErrorCodes.UNEXPECTED_TOKEN, `Unexpected token: ${token}`, location);
145
+ },
146
+
147
+ expectedToken(expected: string, actual: string, location?: Range): FHIRPathError {
148
+ return new FHIRPathError(ErrorCodes.EXPECTED_TOKEN, `Expected ${expected}, got ${actual}`, location);
149
+ },
150
+
151
+ invalidSyntax(details: string, location?: Range): FHIRPathError {
152
+ return new FHIRPathError(ErrorCodes.INVALID_SYNTAX, `Invalid syntax: ${details}`, location);
153
+ },
154
+
155
+ expectedIdentifier(after: string, actual: string, location?: Range): FHIRPathError {
156
+ return new FHIRPathError(ErrorCodes.EXPECTED_IDENTIFIER, `Expected identifier after '${after}', got: ${actual}`, location);
157
+ },
158
+
159
+ expectedTypeName(actual: string, location?: Range): FHIRPathError {
160
+ return new FHIRPathError(ErrorCodes.EXPECTED_TYPE_NAME, `Expected type name, got: ${actual}`, location);
161
+ },
162
+
163
+ // Domain errors (6000-6999)
164
+ divisionByZero(location?: Range): FHIRPathError {
165
+ return new FHIRPathError(ErrorCodes.DIVISION_BY_ZERO, 'Division by zero', location);
166
+ },
167
+
168
+ invalidDateTimeFormat(format: string, location?: Range): FHIRPathError {
169
+ return new FHIRPathError(ErrorCodes.INVALID_DATE_TIME_FORMAT, `Invalid date/time format: '${format}'`, location);
170
+ },
171
+
172
+ incompatibleUnits(unit1: string, unit2: string, location?: Range): FHIRPathError {
173
+ return new FHIRPathError(ErrorCodes.INCOMPATIBLE_UNITS, `Cannot perform operation on incompatible units: ${unit1} and ${unit2}`, location);
174
+ },
175
+
176
+ indexOutOfBounds(index: number, size: number, location?: Range): FHIRPathError {
177
+ return new FHIRPathError(ErrorCodes.INDEX_OUT_OF_BOUNDS, `Index ${index} out of bounds for collection of size ${size}`, location);
178
+ },
179
+
180
+ invalidOperation(details: string, location?: Range): FHIRPathError {
181
+ return new FHIRPathError(ErrorCodes.INVALID_OPERATION, `Invalid operation: ${details}`, location);
182
+ },
183
+
184
+ invalidPrecision(operation: string, location?: Range): FHIRPathError {
185
+ return new FHIRPathError(ErrorCodes.INVALID_PRECISION, `${operation} precision must be a non-negative integer`, location);
186
+ },
187
+
188
+ invalidStringOperation(operation: string, paramName: string, location?: Range): FHIRPathError {
189
+ return new FHIRPathError(ErrorCodes.INVALID_STRING_OPERATION, `${operation} ${paramName} must be a string`, location);
190
+ },
191
+
192
+ invalidNumericOperation(operation: string, paramName: string, expectedType: string, location?: Range): FHIRPathError {
193
+ return new FHIRPathError(ErrorCodes.INVALID_NUMERIC_OPERATION, `${operation} ${paramName} must be ${expectedType}`, location);
194
+ }
195
+ };
196
+
197
+ // Export error codes as enum for external use
198
+ export enum ErrorCodes {
199
+ // Resolution errors (1000-1999)
200
+ UNKNOWN_OPERATOR = 'FP1001',
201
+ UNKNOWN_FUNCTION = 'FP1002',
202
+ UNKNOWN_VARIABLE = 'FP1003',
203
+ UNKNOWN_USER_VARIABLE = 'FP1004',
204
+ UNKNOWN_PROPERTY = 'FP1005',
205
+ UNKNOWN_NODE_TYPE = 'FP1006',
206
+ NO_EVALUATOR_FOUND = 'FP1007',
207
+ VARIABLE_NOT_DEFINED = 'FP1008',
208
+
209
+ // Arity errors (2000-2999)
210
+ WRONG_ARGUMENT_COUNT = 'FP2001',
211
+ WRONG_ARGUMENT_COUNT_RANGE = 'FP2002',
212
+ SINGLETON_REQUIRED = 'FP2003',
213
+ EMPTY_NOT_ALLOWED = 'FP2004',
214
+ ARGUMENT_REQUIRED = 'FP2005',
215
+
216
+ // Type errors (3000-3999)
217
+ TYPE_NOT_ASSIGNABLE = 'FP3001',
218
+ OPERATOR_TYPE_MISMATCH = 'FP3002',
219
+ ARGUMENT_TYPE_MISMATCH = 'FP3003',
220
+ CONVERSION_FAILED = 'FP3004',
221
+ INVALID_VALUE_TYPE = 'FP3005',
222
+ INVALID_OPERAND_TYPE = 'FP3006',
223
+ STRING_OPERATION_ON_NON_STRING = 'FP3007',
224
+ NUMERIC_OPERATION_ON_NON_NUMERIC = 'FP3008',
225
+ BOOLEAN_OPERATION_ON_NON_BOOLEAN = 'FP3009',
226
+
227
+ // Configuration errors (4000-4999)
228
+ MODEL_PROVIDER_REQUIRED = 'FP4001',
229
+
230
+ // Syntax errors (5000-5999)
231
+ UNEXPECTED_TOKEN = 'FP5001',
232
+ EXPECTED_TOKEN = 'FP5002',
233
+ INVALID_SYNTAX = 'FP5003',
234
+ EXPECTED_IDENTIFIER = 'FP5004',
235
+ EXPECTED_TYPE_NAME = 'FP5005',
236
+
237
+ // Domain errors (6000-6999)
238
+ DIVISION_BY_ZERO = 'FP6001',
239
+ INVALID_DATE_TIME_FORMAT = 'FP6002',
240
+ INCOMPATIBLE_UNITS = 'FP6003',
241
+ INDEX_OUT_OF_BOUNDS = 'FP6004',
242
+ INVALID_OPERATION = 'FP6005',
243
+ INVALID_PRECISION = 'FP6006',
244
+ INVALID_STRING_OPERATION = 'FP6007',
245
+ INVALID_NUMERIC_OPERATION = 'FP6008'
246
+ }
package/src/index.ts CHANGED
@@ -2,10 +2,14 @@ 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
15
  export function evaluate(
@@ -19,19 +23,59 @@ 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 = analyzer.analyze(
32
+ parseResult.ast,
33
+ options.variables,
34
+ options.inputType
35
+ );
36
+
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
+ }
26
50
 
27
- const interpreter = new Interpreter();
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 = input.map(item => {
59
+ if (item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') {
60
+ const typeInfo = options.modelProvider!.getType(item.resourceType);
61
+ if (typeInfo) {
62
+ return box(item, typeInfo);
63
+ }
64
+ }
65
+ return item;
66
+ });
67
+ }
68
+
30
69
  // Create context with variables if provided
31
70
  let context = RuntimeContextManager.create(input);
32
71
 
33
- // Set $this to the input (required for expressions like $this.where(...))
34
- context = RuntimeContextManager.setVariable(context, '$this', input);
72
+ // Set $this to the boxed input (required for expressions like $this.where(...))
73
+ context = RuntimeContextManager.setVariable(context, '$this', boxedInput);
74
+
75
+ // Add model provider to context if available
76
+ if (options.modelProvider) {
77
+ context.modelProvider = options.modelProvider;
78
+ }
35
79
 
36
80
  if (options.variables) {
37
81
  for (const [key, value] of Object.entries(options.variables)) {
@@ -40,9 +84,10 @@ export function evaluate(
40
84
  }
41
85
  }
42
86
 
43
- const result = interpreter.evaluate(ast, input, context);
87
+ const result = interpreter.evaluate(analysisResult.ast, input, context);
44
88
 
45
- return result.value;
89
+ // Unbox the results before returning
90
+ return result.value.map(unbox);
46
91
  }
47
92
 
48
93
  export function analyze(
@@ -66,7 +111,7 @@ export function analyze(
66
111
  if (!options.errorRecovery && parseResult.errors.length > 0) {
67
112
  // For backward compatibility, throw the first error
68
113
  const firstError = parseResult.errors[0]!;
69
- throw new Error(firstError.message);
114
+ throw Errors.invalidSyntax(firstError.message);
70
115
  }
71
116
 
72
117
  const ast = parseResult.ast;
@@ -109,4 +154,7 @@ export type { FHIRModelContext, FHIRModelProviderConfig } from './model-provider
109
154
 
110
155
  // Export inspect API
111
156
  export { inspect } from './inspect';
112
- export type { InspectOptions, InspectResult, TraceEntry } from './inspect';
157
+ export type { InspectOptions, InspectResult, ASTMetadata } from './inspect';
158
+
159
+ // Export error system
160
+ export { FHIRPathError, Errors, ErrorCodes } from './errors';