@atomic-ehr/fhirpath 0.0.1-canary.69eb286.20250724163205

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 (56) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8256 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +148 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +149 -0
  14. package/src/api/index.ts +57 -0
  15. package/src/api/registry.ts +128 -0
  16. package/src/api/types.ts +154 -0
  17. package/src/compiler/compiler.ts +589 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/types.ts +23 -0
  20. package/src/index.ts +52 -0
  21. package/src/interpreter/README.md +78 -0
  22. package/src/interpreter/context.ts +181 -0
  23. package/src/interpreter/interpreter.ts +484 -0
  24. package/src/interpreter/types.ts +132 -0
  25. package/src/lexer/char-tables.ts +37 -0
  26. package/src/lexer/errors.ts +31 -0
  27. package/src/lexer/index.ts +5 -0
  28. package/src/lexer/lexer.ts +745 -0
  29. package/src/lexer/token.ts +104 -0
  30. package/src/parser/ast.ts +123 -0
  31. package/src/parser/index.ts +3 -0
  32. package/src/parser/parser.ts +701 -0
  33. package/src/parser/pprint.ts +169 -0
  34. package/src/registry/default-analyzers.ts +257 -0
  35. package/src/registry/default-compilers.ts +31 -0
  36. package/src/registry/index.ts +93 -0
  37. package/src/registry/operations/arithmetic.ts +506 -0
  38. package/src/registry/operations/collection.ts +422 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +719 -0
  41. package/src/registry/operations/filtering.ts +374 -0
  42. package/src/registry/operations/literals.ts +341 -0
  43. package/src/registry/operations/logical.ts +402 -0
  44. package/src/registry/operations/math.ts +128 -0
  45. package/src/registry/operations/membership.ts +132 -0
  46. package/src/registry/operations/string.ts +507 -0
  47. package/src/registry/operations/subsetting.ts +174 -0
  48. package/src/registry/operations/type-checking.ts +162 -0
  49. package/src/registry/operations/type-conversion.ts +404 -0
  50. package/src/registry/operations/type-operators.ts +307 -0
  51. package/src/registry/operations/utility.ts +553 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +162 -0
  54. package/src/registry/utils/evaluation-helpers.ts +93 -0
  55. package/src/registry/utils/index.ts +3 -0
  56. package/src/registry/utils/type-system.ts +173 -0
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # @atomic-ehr/fhirpath
2
+
3
+ A TypeScript implementation of FHIRPath, the path-based navigation and extraction language for FHIR (Fast Healthcare Interoperability Resources).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @atomic-ehr/fhirpath
9
+ # or
10
+ bun add @atomic-ehr/fhirpath
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import fhirpath from '@atomic-ehr/fhirpath';
17
+
18
+ const patient = {
19
+ name: [
20
+ { given: ['John', 'James'], family: 'Doe' },
21
+ { given: ['Johnny'], family: 'Doe' }
22
+ ],
23
+ birthDate: '1990-01-01'
24
+ };
25
+
26
+ // Simple evaluation
27
+ const givenNames = fhirpath.evaluate('name.given', patient);
28
+ console.log(givenNames); // ['John', 'James', 'Johnny']
29
+
30
+ // With filtering
31
+ const officialName = fhirpath.evaluate('name.where(use = \'official\').given', patient);
32
+
33
+ // Arithmetic
34
+ const age = fhirpath.evaluate('today().year() - birthDate.toDateTime().year()', patient);
35
+ ```
36
+
37
+ ## API Reference
38
+
39
+ ### Core Functions
40
+
41
+ #### `parse(expression: string): FHIRPathExpression`
42
+
43
+ Parses a FHIRPath expression string into an AST (Abstract Syntax Tree).
44
+
45
+ ```typescript
46
+ const expr = fhirpath.parse('Patient.name.given');
47
+ // Use the parsed expression multiple times
48
+ const result1 = fhirpath.evaluate(expr, patient1);
49
+ const result2 = fhirpath.evaluate(expr, patient2);
50
+ ```
51
+
52
+ #### `evaluate(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext): any[]`
53
+
54
+ Evaluates a FHIRPath expression against input data.
55
+
56
+ ```typescript
57
+ // Evaluate with string expression
58
+ const names = fhirpath.evaluate('name.family', patient);
59
+
60
+ // Evaluate with parsed expression
61
+ const expr = fhirpath.parse('name.family');
62
+ const names = fhirpath.evaluate(expr, patient);
63
+
64
+ // With context variables
65
+ const result = fhirpath.evaluate('%myVar + 5', null, {
66
+ variables: { myVar: 10 }
67
+ }); // [15]
68
+ ```
69
+
70
+ #### `compile(expression: string | FHIRPathExpression, options?: CompileOptions): CompiledExpression`
71
+
72
+ Compiles an expression into an optimized JavaScript function for better performance.
73
+
74
+ ```typescript
75
+ const compiled = fhirpath.compile('name.given');
76
+
77
+ // Use compiled function multiple times
78
+ const names1 = compiled(patient1);
79
+ const names2 = compiled(patient2);
80
+ ```
81
+
82
+ #### `analyze(expression: string | FHIRPathExpression, options?: AnalyzeOptions): AnalysisResult`
83
+
84
+ Performs static type analysis on an expression.
85
+
86
+ ```typescript
87
+ const analysis = fhirpath.analyze('name.given');
88
+ console.log(analysis.type); // Type information
89
+ console.log(analysis.errors); // Any type errors
90
+ ```
91
+
92
+ ### Registry API
93
+
94
+ The registry provides introspection capabilities for available operations.
95
+
96
+ ```typescript
97
+ // List all available functions
98
+ const functions = fhirpath.registry.listFunctions();
99
+ console.log(functions.map(f => f.name)); // ['where', 'select', 'first', ...]
100
+
101
+ // Check if operation exists
102
+ fhirpath.registry.hasFunction('where'); // true
103
+ fhirpath.registry.hasOperator('+'); // true
104
+
105
+ // Get operation details
106
+ const whereInfo = fhirpath.registry.getOperationInfo('where');
107
+ console.log(whereInfo.syntax.notation); // "where(expression)"
108
+
109
+ // Validate custom function names
110
+ fhirpath.registry.canRegisterFunction('myFunc'); // true
111
+ fhirpath.registry.canRegisterFunction('where'); // false (built-in)
112
+ ```
113
+
114
+ ### Builder Pattern
115
+
116
+ For advanced configurations, use the builder pattern:
117
+
118
+ ```typescript
119
+ import { FHIRPath } from '@atomic-ehr/fhirpath';
120
+
121
+ const fp = FHIRPath.builder()
122
+ // Add custom functions
123
+ .withCustomFunction('double', (context, input) => {
124
+ return input.map(x => x * 2);
125
+ })
126
+
127
+ // Set default variables
128
+ .withVariable('defaultStatus', 'active')
129
+
130
+ // Add model provider for type information
131
+ .withModelProvider({
132
+ resolveType: (typeName) => { /* ... */ },
133
+ getTypeHierarchy: (typeName) => { /* ... */ },
134
+ getProperties: (typeName) => { /* ... */ }
135
+ })
136
+
137
+ .build();
138
+
139
+ // Use the configured instance
140
+ const result = fp.evaluate('value.double()', { value: [5] }); // [10]
141
+ const status = fp.evaluate('%defaultStatus'); // ['active']
142
+ ```
143
+
144
+ ### Custom Functions
145
+
146
+ Custom functions extend FHIRPath with domain-specific operations:
147
+
148
+ ```typescript
149
+ const fp = FHIRPath.builder()
150
+ .withCustomFunction('age', (context, input) => {
151
+ // Calculate age from birthDate
152
+ return input.map(birthDate => {
153
+ const today = new Date();
154
+ const birth = new Date(birthDate);
155
+ let age = today.getFullYear() - birth.getFullYear();
156
+ const monthDiff = today.getMonth() - birth.getMonth();
157
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
158
+ age--;
159
+ }
160
+ return age;
161
+ });
162
+ })
163
+ .withCustomFunction('fullName', (context, input) => {
164
+ return input.map(name => {
165
+ if (name && typeof name === 'object') {
166
+ const given = Array.isArray(name.given) ? name.given.join(' ') : '';
167
+ const family = name.family || '';
168
+ return `${given} ${family}`.trim();
169
+ }
170
+ return '';
171
+ });
172
+ })
173
+ .build();
174
+
175
+ // Use custom functions
176
+ const age = fp.evaluate('birthDate.age()', patient);
177
+ const fullNames = fp.evaluate('name.fullName()', patient);
178
+ ```
179
+
180
+ ### Error Handling
181
+
182
+ All API functions throw `FHIRPathError` for invalid expressions or runtime errors:
183
+
184
+ ```typescript
185
+ import { FHIRPathError, ErrorCode } from '@atomic-ehr/fhirpath';
186
+
187
+ try {
188
+ fhirpath.parse('invalid..expression');
189
+ } catch (error) {
190
+ if (error instanceof FHIRPathError) {
191
+ console.error(`Error: ${error.message}`);
192
+ console.error(`Code: ${error.code}`);
193
+ console.error(`Location: Line ${error.location?.line}, Column ${error.location?.column}`);
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Type Definitions
199
+
200
+ The library is fully typed with TypeScript:
201
+
202
+ ```typescript
203
+ import type {
204
+ FHIRPathExpression,
205
+ CompiledExpression,
206
+ EvaluationContext,
207
+ ModelProvider,
208
+ CustomFunction,
209
+ OperationInfo,
210
+ AnalysisResult
211
+ } from '@atomic-ehr/fhirpath';
212
+ ```
213
+
214
+ ## Common Use Cases
215
+
216
+ ### Working with FHIR Resources
217
+
218
+ ```typescript
219
+ const bundle = {
220
+ resourceType: 'Bundle',
221
+ entry: [
222
+ { resource: { resourceType: 'Patient', id: '1', active: true } },
223
+ { resource: { resourceType: 'Patient', id: '2', active: false } },
224
+ { resource: { resourceType: 'Observation', status: 'final' } }
225
+ ]
226
+ };
227
+
228
+ // Get all patients
229
+ const patients = fhirpath.evaluate(
230
+ 'entry.resource.where(resourceType = \'Patient\')',
231
+ bundle
232
+ );
233
+
234
+ // Get active patients
235
+ const activePatients = fhirpath.evaluate(
236
+ 'entry.resource.where(resourceType = \'Patient\' and active = true)',
237
+ bundle
238
+ );
239
+
240
+ // Count resources by type
241
+ const patientCount = fhirpath.evaluate(
242
+ 'entry.resource.where(resourceType = \'Patient\').count()',
243
+ bundle
244
+ ); // [2]
245
+ ```
246
+
247
+ ### Complex Filtering
248
+
249
+ ```typescript
250
+ const observations = [
251
+ { code: { coding: [{ system: 'loinc', code: '1234' }] }, value: 140 },
252
+ { code: { coding: [{ system: 'loinc', code: '1234' }] }, value: 120 },
253
+ { code: { coding: [{ system: 'loinc', code: '5678' }] }, value: 98.6 }
254
+ ];
255
+
256
+ // Find high blood pressure readings
257
+ const highBP = fhirpath.evaluate(
258
+ 'where(code.coding.exists(system = \'loinc\' and code = \'1234\') and value > 130)',
259
+ observations
260
+ );
261
+ ```
262
+
263
+ ### Date Manipulation
264
+
265
+ ```typescript
266
+ const patient = {
267
+ birthDate: '1990-05-15'
268
+ };
269
+
270
+ // Check if patient is adult (>= 18 years)
271
+ const isAdult = fhirpath.evaluate(
272
+ 'today() - birthDate.toDateTime() >= 18 years',
273
+ patient
274
+ );
275
+ ```
276
+
277
+ ## Performance Tips
278
+
279
+ 1. **Parse Once, Evaluate Many**: Parse expressions once and reuse the parsed AST:
280
+ ```typescript
281
+ const expr = fhirpath.parse('name.given');
282
+ for (const patient of patients) {
283
+ const names = fhirpath.evaluate(expr, patient);
284
+ }
285
+ ```
286
+
287
+ 2. **Use Compiled Functions**: For expressions evaluated frequently, use compilation:
288
+ ```typescript
289
+ const getName = fhirpath.compile('name.given');
290
+ const results = patients.map(p => getName(p));
291
+ ```
292
+
293
+ 3. **Builder Instance**: Create a configured instance once and reuse:
294
+ ```typescript
295
+ const fp = FHIRPath.builder()
296
+ .withCustomFunction('myFunc', /* ... */)
297
+ .build();
298
+ // Use fp instance throughout your application
299
+ ```
300
+
301
+ ## Contributing
302
+
303
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,225 @@
1
+ interface ASTNode {
2
+ type: NodeType;
3
+ position: Position;
4
+ resultType?: unknown;
5
+ isSingleton?: boolean;
6
+ }
7
+ interface Position {
8
+ line: number;
9
+ column: number;
10
+ offset: number;
11
+ }
12
+ declare enum NodeType {
13
+ Identifier = 0,
14
+ TypeOrIdentifier = 1,// Uppercase identifiers that could be types (Patient, Observation)
15
+ Binary = 2,// All binary operators including dot
16
+ Unary = 3,// unary +, -, not
17
+ Union = 4,// | operator (special handling for multiple operands)
18
+ Function = 5,// Function calls
19
+ Literal = 6,// numbers, strings, booleans, dates, null
20
+ Variable = 7,// $this, $index, $total, %var
21
+ Collection = 8,// {} empty collection or {expr1, expr2, ...}
22
+ MembershipTest = 9,// 'is' operator
23
+ TypeCast = 10,// 'as' operator
24
+ TypeReference = 11,// Type name in ofType()
25
+ Index = 12
26
+ }
27
+
28
+ /**
29
+ * Type system interfaces for FHIRPath type analysis
30
+ *
31
+ * Uses opaque type references to allow flexible implementation
32
+ */
33
+ type TypeRef = unknown;
34
+
35
+ /**
36
+ * Context carries variables and environment data parallel to the data stream.
37
+ * It flows through expressions and can be modified by certain operations.
38
+ *
39
+ * Uses JavaScript prototype chain for efficient inheritance.
40
+ */
41
+ interface Context {
42
+ variables: Record<string, any[]>;
43
+ env: {
44
+ $this?: any[];
45
+ $index?: number;
46
+ $total?: any[];
47
+ };
48
+ $context?: any[];
49
+ $resource?: any[];
50
+ $rootResource?: any[];
51
+ customFunctions?: Record<string, (context: Context, input: any[], ...args: any[]) => any[]>;
52
+ }
53
+
54
+ interface FHIRPathExpression {
55
+ readonly ast: ASTNode;
56
+ evaluate(input?: any, context?: EvaluationContext): any[];
57
+ compile(options?: CompileOptions): CompiledExpression;
58
+ analyze(options?: AnalyzeOptions): AnalysisResult;
59
+ toString(): string;
60
+ }
61
+ interface CompiledExpression {
62
+ (input?: any, context?: EvaluationContext): any[];
63
+ readonly source: string;
64
+ }
65
+ interface EvaluationContext {
66
+ variables?: Record<string, any>;
67
+ environment?: Record<string, any>;
68
+ modelProvider?: ModelProvider;
69
+ customFunctions?: CustomFunctionMap;
70
+ }
71
+ type CustomFunction = (context: Context, input: any[], ...args: any[]) => any[];
72
+ type CustomFunctionMap = Record<string, CustomFunction>;
73
+ interface ModelProvider {
74
+ resolveType(typeName: string): TypeRef | undefined;
75
+ getTypeHierarchy(typeName: string): string[];
76
+ getProperties(typeName: string): PropertyDefinition[];
77
+ getTypeName?(type: TypeRef): string;
78
+ }
79
+ interface PropertyDefinition {
80
+ name: string;
81
+ type: string;
82
+ isCollection: boolean;
83
+ isRequired: boolean;
84
+ }
85
+ interface CompileOptions {
86
+ optimize?: boolean;
87
+ sourceMap?: boolean;
88
+ }
89
+ interface AnalyzeOptions {
90
+ modelProvider?: ModelProvider;
91
+ strict?: boolean;
92
+ }
93
+ interface AnalysisResult {
94
+ type: TypeRef;
95
+ isSingleton: boolean;
96
+ errors: AnalysisError[];
97
+ warnings: AnalysisWarning[];
98
+ }
99
+ interface AnalysisError {
100
+ message: string;
101
+ location?: Location;
102
+ code: string;
103
+ }
104
+ interface AnalysisWarning {
105
+ message: string;
106
+ location?: Location;
107
+ code: string;
108
+ }
109
+ interface Location {
110
+ line: number;
111
+ column: number;
112
+ offset: number;
113
+ length: number;
114
+ }
115
+ interface RegistryAPI {
116
+ listFunctions(): OperationMetadata[];
117
+ listOperators(): OperationMetadata[];
118
+ listAllOperations(): OperationMetadata[];
119
+ hasOperation(name: string): boolean;
120
+ hasFunction(name: string): boolean;
121
+ hasOperator(symbol: string): boolean;
122
+ getOperationInfo(name: string): OperationInfo | undefined;
123
+ canRegisterFunction(name: string): boolean;
124
+ }
125
+ interface OperationMetadata {
126
+ name: string;
127
+ kind: 'function' | 'operator' | 'literal';
128
+ syntax: {
129
+ notation: string;
130
+ };
131
+ }
132
+ interface OperationInfo extends OperationMetadata {
133
+ signature: {
134
+ input?: {
135
+ types?: string[];
136
+ cardinality?: 'singleton' | 'collection' | 'any';
137
+ };
138
+ parameters?: Array<{
139
+ name: string;
140
+ types?: string[];
141
+ cardinality?: 'singleton' | 'collection' | 'any';
142
+ optional?: boolean;
143
+ }>;
144
+ output?: {
145
+ type?: string | 'dynamic';
146
+ cardinality?: 'singleton' | 'collection' | 'preserve-input';
147
+ };
148
+ };
149
+ description?: string;
150
+ examples?: string[];
151
+ }
152
+ interface FHIRPathBuilder {
153
+ withModelProvider(provider: ModelProvider): this;
154
+ withCustomFunction(name: string, fn: CustomFunction): this;
155
+ withVariable(name: string, value: any): this;
156
+ build(): FHIRPathAPI;
157
+ }
158
+ interface FHIRPathAPI {
159
+ parse(expression: string): FHIRPathExpression;
160
+ evaluate(expression: string | FHIRPathExpression, input?: any): any[];
161
+ compile(expression: string | FHIRPathExpression): CompiledExpression;
162
+ analyze(expression: string | FHIRPathExpression): AnalysisResult;
163
+ registry: RegistryAPI;
164
+ }
165
+
166
+ declare class PublicRegistryAPI implements RegistryAPI {
167
+ listFunctions(): OperationMetadata[];
168
+ listOperators(): OperationMetadata[];
169
+ listAllOperations(): OperationMetadata[];
170
+ hasOperation(name: string): boolean;
171
+ hasFunction(name: string): boolean;
172
+ hasOperator(symbol: string): boolean;
173
+ getOperationInfo(name: string): OperationInfo | undefined;
174
+ canRegisterFunction(name: string): boolean;
175
+ private toMetadata;
176
+ private toOperationInfo;
177
+ private extractTypes;
178
+ }
179
+
180
+ declare function parse(expression: string): FHIRPathExpression;
181
+ declare function evaluate(expression: string | FHIRPathExpression, input?: any, context?: EvaluationContext): any[];
182
+ declare function compile(expression: string | FHIRPathExpression, options?: CompileOptions): CompiledExpression;
183
+ declare function analyze(expression: string | FHIRPathExpression, options?: AnalyzeOptions): AnalysisResult;
184
+ declare const registry: PublicRegistryAPI;
185
+
186
+ declare class FHIRPath {
187
+ static builder(): FHIRPathBuilder;
188
+ }
189
+
190
+ declare enum ErrorCode {
191
+ PARSE_ERROR = "PARSE_ERROR",
192
+ SYNTAX_ERROR = "SYNTAX_ERROR",
193
+ UNEXPECTED_TOKEN = "UNEXPECTED_TOKEN",
194
+ UNTERMINATED_STRING = "UNTERMINATED_STRING",
195
+ INVALID_ESCAPE = "INVALID_ESCAPE",
196
+ TYPE_ERROR = "TYPE_ERROR",
197
+ TYPE_MISMATCH = "TYPE_MISMATCH",
198
+ UNKNOWN_TYPE = "UNKNOWN_TYPE",
199
+ CARDINALITY_ERROR = "CARDINALITY_ERROR",
200
+ RUNTIME_ERROR = "RUNTIME_ERROR",
201
+ UNDEFINED_VARIABLE = "UNDEFINED_VARIABLE",
202
+ UNDEFINED_FUNCTION = "UNDEFINED_FUNCTION",
203
+ INVALID_ARGUMENT = "INVALID_ARGUMENT",
204
+ DIVISION_BY_ZERO = "DIVISION_BY_ZERO",
205
+ ANALYSIS_ERROR = "ANALYSIS_ERROR",
206
+ UNREACHABLE_CODE = "UNREACHABLE_CODE",
207
+ AMBIGUOUS_TYPE = "AMBIGUOUS_TYPE"
208
+ }
209
+ declare class FHIRPathError extends Error {
210
+ code: ErrorCode;
211
+ location?: Location | undefined;
212
+ expression?: string | undefined;
213
+ constructor(message: string, code: ErrorCode, location?: Location | undefined, expression?: string | undefined);
214
+ toString(): string;
215
+ }
216
+
217
+ declare const _default: {
218
+ parse: typeof parse;
219
+ evaluate: typeof evaluate;
220
+ compile: typeof compile;
221
+ analyze: typeof analyze;
222
+ registry: PublicRegistryAPI;
223
+ };
224
+
225
+ export { type AnalysisError, type AnalysisResult, type AnalysisWarning, type AnalyzeOptions, type CompileOptions, type CompiledExpression, type CustomFunction, type CustomFunctionMap, ErrorCode, type EvaluationContext, FHIRPath, type FHIRPathAPI, type FHIRPathBuilder, FHIRPathError, type FHIRPathExpression, type Location, type ModelProvider, type OperationInfo, type OperationMetadata, type PropertyDefinition, type RegistryAPI, analyze, compile, _default as default, evaluate, parse, registry };