UNPKG

@atomic-ehr/fhirpath

Version:

A TypeScript implementation of FHIRPath

196 lines (169 loc) 6.52 kB
import { Parser } from './parser'; import { Interpreter, RuntimeContextManager } from './interpreter'; import { Analyzer } from './analyzer'; import type { AnalysisResult } from './types'; import { box, unbox } from './boxing'; import { FHIRPathError, Errors } from './errors'; export interface EvaluateOptions { input?: unknown; variables?: Record<string, unknown>; modelProvider?: import('./types').ModelProvider; inputType?: import('./types').TypeInfo; } export async function evaluate( expression: string, options: EvaluateOptions = {} ): Promise<any[]> { const parser = new Parser(expression); const parseResult = parser.parse(); // Check for parse errors if (parseResult.errors.length > 0) { // For backward compatibility, throw the first error const firstError = parseResult.errors[0]!; throw Errors.invalidSyntax(firstError.message); } // ALWAYS analyze the AST const analyzer = new Analyzer(options.modelProvider); const analysisResult = await analyzer.analyze( parseResult.ast, options.variables, options.inputType ); // Check for analysis errors const errors = analysisResult.diagnostics.filter(d => d.severity === 1); // DiagnosticSeverity.Error if (errors.length > 0) { // Throw the first error const firstError = errors[0]!; if (firstError.code) { // Always throw as FHIRPathError if we have a code throw new FHIRPathError(firstError.code, firstError.message, firstError.range); } else { // Otherwise throw a generic error throw new Error(firstError.message); } } // Use the analyzed AST with type information const interpreter = new Interpreter(undefined, options.modelProvider); const input = options.input === undefined ? [] : Array.isArray(options.input) ? options.input : [options.input]; // Box input with typeInfo if we have a modelProvider and the input is a FHIR resource let boxedInput = input; if (options.modelProvider) { boxedInput = await Promise.all(input.map(async item => { if (item && typeof item === 'object' && 'resourceType' in item && typeof item.resourceType === 'string') { // Get type info asynchronously const typeInfo = await options.modelProvider!.getType(item.resourceType); if (typeInfo) { return box(item, typeInfo); } } return item; })); } // Create context with variables if provided let context = RuntimeContextManager.create(input); // Set $this to the boxed input (required for expressions like $this.where(...)) context = RuntimeContextManager.setVariable(context, '$this', boxedInput); // Add model provider to context if available if (options.modelProvider) { context.modelProvider = options.modelProvider; } if (options.variables) { for (const [key, value] of Object.entries(options.variables)) { const varValue = Array.isArray(value) ? value : [value]; context = RuntimeContextManager.setVariable(context, key, varValue); } } const result = await interpreter.evaluate(analysisResult.ast, input, context); // Unbox the results before returning return result.value.map(unbox); } export async function analyze( expression: string, options: { variables?: Record<string, unknown>; modelProvider?: import('./types').ModelProvider; inputType?: import('./types').TypeInfo; errorRecovery?: boolean; } = {} ): Promise<AnalysisResult> { // Use LSP mode with error recovery if requested const parserOptions = options.errorRecovery ? { mode: 'lsp' as const, errorRecovery: true } : undefined; const parser = new Parser(expression, parserOptions); const parseResult = parser.parse(); // Check for parse errors only if error recovery is disabled if (!options.errorRecovery && parseResult.errors.length > 0) { // For backward compatibility, throw the first error const firstError = parseResult.errors[0]!; throw Errors.invalidSyntax(firstError.message); } const ast = parseResult.ast; // Create analyzer with optional model provider const analyzer = new Analyzer(options.modelProvider); const analysisResult = await analyzer.analyze(ast, options.variables, options.inputType); // If error recovery is enabled, merge parse errors into diagnostics if (options.errorRecovery && parseResult.errors.length > 0) { // Parse errors are already converted to diagnostics by the analyzer // when it encounters Error nodes in the AST } return analysisResult; } // Export key types and classes export { Parser } from './parser'; export { Interpreter } from './interpreter'; export { Analyzer } from './analyzer'; export { parse } from './parser'; export { DiagnosticSeverity } from './types'; export { Registry, registry } from './registry'; export type { ParseResult, Diagnostic, AnalysisResult, ASTNode, TypeInfo, TypeName, ModelProvider as ModelTypeProvider, OperatorDefinition, FunctionDefinition } from './types'; // Export FHIR ModelProvider export { FHIRModelProvider } from './model-provider'; export type { FHIRModelContext, FHIRModelProviderConfig } from './model-provider'; // Export inspect API export { inspect } from './inspect'; export type { InspectOptions, InspectResult, ASTMetadata } from './inspect'; // Export error system export { FHIRPathError, Errors, ErrorCodes } from './errors'; // Export LSP support - completion provider and cursor nodes /** * Provides context-aware code completions for FHIRPath expressions. * @param expression - The FHIRPath expression being edited * @param cursorPosition - The cursor position (0-based offset) * @param options - Optional configuration including modelProvider and variables * @returns Array of completion items with labels, kinds, and documentation * * @example * ```typescript * import { provideCompletions } from 'fhirpath'; * * const completions = provideCompletions('Patient.', 8); * // Returns completions for properties and functions after 'Patient.' * ``` */ export { provideCompletions, CompletionKind } from './completion-provider'; export type { CompletionItem, CompletionOptions } from './completion-provider'; // Export cursor node types for LSP integration export { CursorContext, isCursorNode } from './cursor-nodes'; export type { CursorNode, CursorOperatorNode, CursorIdentifierNode, CursorArgumentNode, CursorIndexNode, CursorTypeNode, AnyCursorNode } from './cursor-nodes';