@atomic-ehr/fhirpath 0.0.1-canary.0c6931e.20250727185306

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 (85) hide show
  1. package/README.md +473 -0
  2. package/dist/index.d.ts +462 -0
  3. package/dist/index.js +10307 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +58 -0
  6. package/src/analyzer/analyzer.ts +499 -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 +157 -0
  12. package/src/api/errors.ts +145 -0
  13. package/src/api/expression.ts +156 -0
  14. package/src/api/index.ts +122 -0
  15. package/src/api/inspect.ts +99 -0
  16. package/src/api/registry.ts +128 -0
  17. package/src/api/types.ts +210 -0
  18. package/src/compiler/compiler.ts +546 -0
  19. package/src/compiler/index.ts +2 -0
  20. package/src/compiler/prototype-context-adapter.ts +99 -0
  21. package/src/compiler/types.ts +24 -0
  22. package/src/index.ts +107 -0
  23. package/src/interpreter/README.md +78 -0
  24. package/src/interpreter/interpreter.ts +475 -0
  25. package/src/interpreter/types.ts +108 -0
  26. package/src/lexer/char-tables.ts +37 -0
  27. package/src/lexer/errors.ts +31 -0
  28. package/src/lexer/index.ts +5 -0
  29. package/src/lexer/lexer.ts +745 -0
  30. package/src/lexer/token.ts +104 -0
  31. package/src/lexer2/index.md +232 -0
  32. package/src/lexer2/index.perf.test.ts +68 -0
  33. package/src/lexer2/index.test.ts +549 -0
  34. package/src/lexer2/index.ts +1251 -0
  35. package/src/lexer2/notes.md +173 -0
  36. package/src/lexer2/optimization-summary.md +718 -0
  37. package/src/parser/ast-factory.ts +220 -0
  38. package/src/parser/ast.ts +144 -0
  39. package/src/parser/collection-parser.ts +89 -0
  40. package/src/parser/diagnostic-messages.ts +216 -0
  41. package/src/parser/diagnostics.ts +85 -0
  42. package/src/parser/error-reporter.ts +230 -0
  43. package/src/parser/index.ts +3 -0
  44. package/src/parser/literal-parser.ts +103 -0
  45. package/src/parser/parse-error.ts +16 -0
  46. package/src/parser/parser-error-factory.ts +141 -0
  47. package/src/parser/parser-state.ts +134 -0
  48. package/src/parser/parser.ts +1272 -0
  49. package/src/parser/pprint.ts +169 -0
  50. package/src/parser/precedence-manager.ts +64 -0
  51. package/src/parser/source-mapper.ts +248 -0
  52. package/src/parser/special-constructs.ts +142 -0
  53. package/src/parser/token-navigator.ts +110 -0
  54. package/src/parser/types.ts +60 -0
  55. package/src/parser2/index.md +177 -0
  56. package/src/parser2/index.perf.test.ts +184 -0
  57. package/src/parser2/index.test.ts +305 -0
  58. package/src/parser2/index.ts +578 -0
  59. package/src/parser2/optimization-summary.md +176 -0
  60. package/src/registry/default-analyzers.ts +257 -0
  61. package/src/registry/default-compilers.ts +31 -0
  62. package/src/registry/index.ts +96 -0
  63. package/src/registry/operations/arithmetic.ts +506 -0
  64. package/src/registry/operations/collection.ts +425 -0
  65. package/src/registry/operations/comparison.ts +432 -0
  66. package/src/registry/operations/existence.ts +703 -0
  67. package/src/registry/operations/filtering.ts +358 -0
  68. package/src/registry/operations/literals.ts +341 -0
  69. package/src/registry/operations/logical.ts +439 -0
  70. package/src/registry/operations/math.ts +128 -0
  71. package/src/registry/operations/membership.ts +132 -0
  72. package/src/registry/operations/navigation.ts +52 -0
  73. package/src/registry/operations/string.ts +507 -0
  74. package/src/registry/operations/subsetting.ts +174 -0
  75. package/src/registry/operations/type-checking.ts +162 -0
  76. package/src/registry/operations/type-conversion.ts +404 -0
  77. package/src/registry/operations/type-operators.ts +308 -0
  78. package/src/registry/operations/utility.ts +644 -0
  79. package/src/registry/registry.ts +146 -0
  80. package/src/registry/types.ts +161 -0
  81. package/src/registry/utils/evaluation-helpers.ts +93 -0
  82. package/src/registry/utils/index.ts +3 -0
  83. package/src/registry/utils/type-system.ts +173 -0
  84. package/src/runtime/context.ts +158 -0
  85. package/src/runtime/debug-context.ts +135 -0
@@ -0,0 +1,146 @@
1
+ import { TokenType } from '../lexer/token';
2
+ import type { Operation, Operator, Function, Literal } from './types';
3
+
4
+ export class Registry {
5
+ private static operations = new Map<string, Operation>();
6
+ private static tokenToOperation = new Map<TokenType, Operation>();
7
+ private static prefixOperators = new Map<TokenType, Operator>();
8
+ private static infixOperators = new Map<TokenType, Operator>();
9
+ private static postfixOperators = new Map<TokenType, Operator>();
10
+ private static precedenceTable = new Map<TokenType, number>();
11
+ private static literals: Literal[] = [];
12
+ private static keywords = new Set<string>();
13
+
14
+ static register(op: Operation) {
15
+ this.operations.set(op.name, op);
16
+
17
+ // Type-based registration
18
+ switch (op.kind) {
19
+ case 'operator':
20
+ // Register by form to handle operators with same token but different forms
21
+ if (op.syntax.form === 'prefix') {
22
+ this.prefixOperators.set(op.syntax.token, op);
23
+ } else if (op.syntax.form === 'infix') {
24
+ this.infixOperators.set(op.syntax.token, op);
25
+ } else if (op.syntax.form === 'postfix') {
26
+ this.postfixOperators.set(op.syntax.token, op);
27
+ }
28
+
29
+ // For backward compatibility, store in tokenToOperation (prioritize infix)
30
+ if (op.syntax.form === 'infix' || !this.tokenToOperation.has(op.syntax.token)) {
31
+ this.tokenToOperation.set(op.syntax.token, op);
32
+ }
33
+
34
+ // Only set precedence for infix operators in the precedence table
35
+ // Prefix and postfix operators have their own precedence but don't affect the infix precedence table
36
+ if (op.syntax.form === 'infix') {
37
+ this.precedenceTable.set(op.syntax.token, op.syntax.precedence);
38
+ }
39
+ // Register keyword operators (and, or, not, etc.)
40
+ if (/^[a-z]+$/.test(op.name)) {
41
+ this.keywords.add(op.name);
42
+ }
43
+ break;
44
+
45
+ case 'literal':
46
+ this.literals.push(op);
47
+ // Register keyword literals
48
+ if (op.syntax.keywords) {
49
+ op.syntax.keywords.forEach(kw => this.keywords.add(kw));
50
+ }
51
+ break;
52
+
53
+ case 'function':
54
+ // Functions don't need special registration
55
+ break;
56
+ }
57
+ }
58
+
59
+ static get(name: string): Operation | undefined {
60
+ return this.operations.get(name);
61
+ }
62
+
63
+ static getByToken(token: TokenType, form?: 'prefix' | 'infix' | 'postfix'): Operation | undefined {
64
+ if (form === 'prefix') {
65
+ return this.prefixOperators.get(token);
66
+ } else if (form === 'infix') {
67
+ return this.infixOperators.get(token);
68
+ } else if (form === 'postfix') {
69
+ return this.postfixOperators.get(token);
70
+ }
71
+ // Default fallback
72
+ return this.tokenToOperation.get(token);
73
+ }
74
+
75
+ static getPrecedence(token: TokenType): number {
76
+ return this.precedenceTable.get(token) ?? 0;
77
+ }
78
+
79
+ static isKeyword(word: string): boolean {
80
+ return this.keywords.has(word);
81
+ }
82
+
83
+ static getLiterals(): Literal[] {
84
+ return this.literals;
85
+ }
86
+
87
+ static matchLiteral(text: string): { operation: Literal; value: any } | null {
88
+ for (const literal of this.literals) {
89
+ if (literal.syntax.pattern && literal.syntax.pattern.test(text)) {
90
+ return {
91
+ operation: literal,
92
+ value: literal.parse(text)
93
+ };
94
+ }
95
+ if (literal.syntax.keywords && literal.syntax.keywords.includes(text)) {
96
+ return {
97
+ operation: literal,
98
+ value: literal.parse(text)
99
+ };
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+
105
+ // For special forms
106
+ static getSpecialForms(): Operator[] {
107
+ return Array.from(this.operations.values())
108
+ .filter((op): op is Operator => op.kind === 'operator' && op.syntax.special === true);
109
+ }
110
+
111
+ // Check if token starts a composite operator
112
+ static isCompositeOperatorStart(token: TokenType): boolean {
113
+ // Used by lexer to know when to look ahead
114
+ return [TokenType.LT, TokenType.GT, TokenType.NEQ, TokenType.NEQUIV]
115
+ .includes(token);
116
+ }
117
+
118
+ // Clear registry (useful for testing)
119
+ static clear() {
120
+ this.operations.clear();
121
+ this.tokenToOperation.clear();
122
+ this.prefixOperators.clear();
123
+ this.infixOperators.clear();
124
+ this.postfixOperators.clear();
125
+ this.precedenceTable.clear();
126
+ this.literals = [];
127
+ this.keywords.clear();
128
+ }
129
+
130
+ // Get all registered operations
131
+ static getAllOperations(): Operation[] {
132
+ return Array.from(this.operations.values());
133
+ }
134
+
135
+ // Get operators by form
136
+ static getOperatorsByForm(form: 'prefix' | 'infix' | 'postfix'): Operator[] {
137
+ return Array.from(this.operations.values())
138
+ .filter((op): op is Operator => op.kind === 'operator' && op.syntax.form === form);
139
+ }
140
+
141
+ // Get all functions
142
+ static getAllFunctions(): Function[] {
143
+ return Array.from(this.operations.values())
144
+ .filter((op): op is Function => op.kind === 'function');
145
+ }
146
+ }
@@ -0,0 +1,161 @@
1
+ import type { TokenType } from '../lexer/token';
2
+ import type { TypeRef, ModelProvider } from '../analyzer/types';
3
+ import type { EvaluationResult } from '../interpreter/types';
4
+ import type { RuntimeContext } from '../runtime/context';
5
+
6
+ // Type information returned by analyze
7
+ export interface TypeInfo {
8
+ type: TypeRef;
9
+ isSingleton: boolean;
10
+ }
11
+
12
+ // Base interface for all operations
13
+ export interface BaseOperation {
14
+ name: string;
15
+
16
+ // Common lifecycle methods
17
+ analyze: (analyzer: Analyzer, input: TypeInfo, args: TypeInfo[]) => TypeInfo;
18
+ evaluate: (interpreter: Interpreter, context: RuntimeContext, input: any[], ...args: any[]) => EvaluationResult;
19
+ compile: (compiler: Compiler, input: CompiledExpression, args: CompiledExpression[]) => CompiledExpression;
20
+ }
21
+
22
+ // Operator-specific interface
23
+ export interface Operator extends BaseOperation {
24
+ kind: 'operator';
25
+
26
+ syntax: {
27
+ form: 'prefix' | 'infix' | 'postfix';
28
+ token: TokenType;
29
+ precedence: number;
30
+ associativity?: 'left' | 'right'; // For infix operators
31
+ notation: string; // e.g., "a + b", "not a"
32
+ special?: boolean; // For special forms like . and []
33
+ endToken?: TokenType; // For bracketed operators like []
34
+ };
35
+
36
+ signature: {
37
+ parameters: [OperatorParameter] | [OperatorParameter, OperatorParameter]; // Unary or binary
38
+ output: {
39
+ type: TypeInferenceRule;
40
+ cardinality: CardinalityInferenceRule;
41
+ };
42
+ propagatesEmpty?: boolean;
43
+ };
44
+ }
45
+
46
+ // Function-specific interface
47
+ export interface Function extends BaseOperation {
48
+ kind: 'function';
49
+
50
+ syntax: {
51
+ notation: string; // e.g., "substring(start [, length])"
52
+ };
53
+
54
+ signature: {
55
+ input?: {
56
+ types?: TypeConstraint;
57
+ cardinality?: 'singleton' | 'collection' | 'any';
58
+ };
59
+ parameters: FunctionParameter[];
60
+ output: {
61
+ type: TypeInferenceRule;
62
+ cardinality: CardinalityInferenceRule;
63
+ };
64
+ propagatesEmpty?: boolean;
65
+ deterministic?: boolean;
66
+ };
67
+ }
68
+
69
+ // Literal-specific interface
70
+ export interface Literal extends BaseOperation {
71
+ kind: 'literal';
72
+
73
+ syntax: {
74
+ pattern?: RegExp; // For complex literals like dates
75
+ keywords?: string[]; // For keyword literals like 'true', 'false'
76
+ notation: string; // Example: "123", "@2023-01-01"
77
+ };
78
+
79
+ // Literals have fixed output type
80
+ signature: {
81
+ output: {
82
+ type: string; // Always a specific type
83
+ cardinality: 'singleton'; // Literals are always singleton
84
+ };
85
+ };
86
+
87
+ // Parse the literal value from source text
88
+ parse: (value: string) => any;
89
+ }
90
+
91
+ // Union type for registry
92
+ export type Operation = Operator | Function | Literal;
93
+
94
+ // Specialized parameter types
95
+ export interface OperatorParameter {
96
+ name: 'left' | 'right' | 'operand';
97
+ types?: TypeConstraint;
98
+ cardinality?: 'singleton' | 'collection' | 'any'; // Required cardinality for this parameter
99
+ }
100
+
101
+ export interface FunctionParameter {
102
+ name: string;
103
+ kind: 'value' | 'expression' | 'type-specifier';
104
+ types?: TypeConstraint;
105
+ cardinality?: 'singleton' | 'collection' | 'any';
106
+ optional?: boolean;
107
+ default?: any;
108
+ }
109
+
110
+ export interface TypeConstraint {
111
+ kind: 'primitive' | 'class' | 'union' | 'any';
112
+ types?: string[]; // ['Integer', 'Decimal'] for numeric, ['Resource'] for FHIR types
113
+ }
114
+
115
+ export type TypeInferenceRule =
116
+ | string // Fixed type like 'Boolean'
117
+ | 'preserve-input' // Returns input type
118
+ | 'promote-numeric' // Integer + Decimal = Decimal
119
+ | ((input: TypeRef, args: TypeRef[], provider: ModelProvider) => TypeRef);
120
+
121
+ export type CardinalityInferenceRule =
122
+ | 'singleton' | 'collection'
123
+ | 'preserve-input' // Same as input
124
+ | 'all-singleton' // Singleton only if all inputs are singleton
125
+ | ((input: boolean, args: boolean[]) => boolean);
126
+
127
+ // Closure-based compiled expression
128
+ export interface CompiledExpression {
129
+ // The compiled function
130
+ fn: (context: RuntimeContext) => any[];
131
+
132
+ // Type information for optimization
133
+ type: TypeRef;
134
+ isSingleton: boolean;
135
+
136
+ // For debugging/tracing
137
+ source?: string;
138
+ }
139
+
140
+
141
+ // Interfaces for components that use the registry
142
+ export interface Analyzer {
143
+ error(message: string): void;
144
+ warning(message: string): void;
145
+ resolveType(typeName: string): TypeRef;
146
+ }
147
+
148
+ export interface Interpreter {
149
+ evaluate(node: any, input: any[], context: RuntimeContext): EvaluationResult;
150
+ }
151
+
152
+ export interface Compiler {
153
+ compile(node: any, input: CompiledExpression): CompiledExpression;
154
+ resolveType(typeName: string): TypeRef;
155
+ }
156
+
157
+ // Re-export TypeRef from analyzer
158
+ export type { TypeRef } from '../analyzer/types';
159
+
160
+ // Re-export RuntimeContext for use in other modules
161
+ export type { RuntimeContext } from '../runtime/context';
@@ -0,0 +1,93 @@
1
+ import { CollectionUtils } from '../../interpreter/types';
2
+
3
+ /**
4
+ * Convert collection to singleton value
5
+ */
6
+ export function toSingleton(collection: any[]): any {
7
+ return CollectionUtils.toSingleton(collection);
8
+ }
9
+
10
+ /**
11
+ * Convert value to boolean according to FHIRPath rules
12
+ */
13
+ export function toBoolean(value: any): boolean {
14
+ // Handle direct boolean
15
+ if (typeof value === 'boolean') {
16
+ return value;
17
+ }
18
+
19
+ // Handle null/undefined as false
20
+ if (value == null) {
21
+ return false;
22
+ }
23
+
24
+ // Handle empty string as false
25
+ if (value === '') {
26
+ return false;
27
+ }
28
+
29
+ // Handle zero as false
30
+ if (value === 0) {
31
+ return false;
32
+ }
33
+
34
+ // Everything else is true
35
+ return true;
36
+ }
37
+
38
+ /**
39
+ * Determines if a value is truthy according to FHIRPath rules
40
+ */
41
+ export function isTruthy(value: any[]): boolean {
42
+ if (value.length === 0) {
43
+ return false;
44
+ }
45
+
46
+ // Convert to singleton
47
+ const singleton = toSingleton(value);
48
+
49
+ // Rule: true if singleton is true, false if singleton is false
50
+ if (typeof singleton === 'boolean') {
51
+ return singleton;
52
+ }
53
+
54
+ // Rule: singleton exists and is not false = true
55
+ return singleton !== undefined;
56
+ }
57
+
58
+ /**
59
+ * Checks if two values are equivalent for FHIRPath comparison
60
+ */
61
+ export function isEquivalent(a: any, b: any): boolean {
62
+ // Handle null/undefined
63
+ if (a === b) return true;
64
+ if (a == null || b == null) return false;
65
+
66
+ // Handle different types
67
+ if (typeof a !== typeof b) return false;
68
+
69
+ // Handle primitives
70
+ if (typeof a !== 'object') return a === b;
71
+
72
+ // Handle arrays
73
+ if (Array.isArray(a) && Array.isArray(b)) {
74
+ if (a.length !== b.length) return false;
75
+ for (let i = 0; i < a.length; i++) {
76
+ if (!isEquivalent(a[i], b[i])) return false;
77
+ }
78
+ return true;
79
+ }
80
+
81
+ // Handle objects (deep comparison)
82
+ const keysA = Object.keys(a);
83
+ const keysB = Object.keys(b);
84
+
85
+ if (keysA.length !== keysB.length) return false;
86
+
87
+ for (const key of keysA) {
88
+ if (!keysB.includes(key)) return false;
89
+ if (!isEquivalent(a[key], b[key])) return false;
90
+ }
91
+
92
+ return true;
93
+ }
@@ -0,0 +1,3 @@
1
+ export { TypeSystem } from './type-system';
2
+ export { isTruthy, isEquivalent, toSingleton, toBoolean } from './evaluation-helpers';
3
+ export type { TypeInfo, PrimitiveType } from './type-system';
@@ -0,0 +1,173 @@
1
+ /**
2
+ * FHIRPath Type System
3
+ *
4
+ * Handles type checking and casting for FHIRPath expressions.
5
+ * Supports primitive types and FHIR resource types.
6
+ */
7
+
8
+ export enum PrimitiveType {
9
+ Boolean = 'Boolean',
10
+ String = 'String',
11
+ Integer = 'Integer',
12
+ Decimal = 'Decimal',
13
+ Date = 'Date',
14
+ DateTime = 'DateTime',
15
+ Time = 'Time',
16
+ Quantity = 'Quantity'
17
+ }
18
+
19
+ /**
20
+ * Type information for a value
21
+ */
22
+ export interface TypeInfo {
23
+ name: string;
24
+ isPrimitive: boolean;
25
+ isResource?: boolean;
26
+ isList?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Type registry for checking and casting
31
+ */
32
+ export class TypeSystem {
33
+ private static primitiveTypes = new Set<string>(Object.values(PrimitiveType));
34
+
35
+ /**
36
+ * Check if a value is of a specific type
37
+ */
38
+ static isType(value: any, typeName: string): boolean {
39
+ // Handle primitive types
40
+ if (this.primitiveTypes.has(typeName)) {
41
+ return this.isPrimitiveType(value, typeName as PrimitiveType);
42
+ }
43
+
44
+ // Handle FHIR resource types
45
+ // For now, check if the object has a resourceType property matching the type name
46
+ if (typeof value === 'object' && value !== null) {
47
+ return value.resourceType === typeName;
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Check if a value is a primitive type
55
+ */
56
+ private static isPrimitiveType(value: any, type: PrimitiveType): boolean {
57
+ switch (type) {
58
+ case PrimitiveType.Boolean:
59
+ return typeof value === 'boolean';
60
+
61
+ case PrimitiveType.String:
62
+ return typeof value === 'string';
63
+
64
+ case PrimitiveType.Integer:
65
+ return typeof value === 'number' && Number.isInteger(value);
66
+
67
+ case PrimitiveType.Decimal:
68
+ return typeof value === 'number';
69
+
70
+ case PrimitiveType.Date:
71
+ // Check if it's a date string in FHIR format (YYYY-MM-DD)
72
+ if (typeof value === 'string') {
73
+ return /^\d{4}(-\d{2}(-\d{2})?)?$/.test(value);
74
+ }
75
+ return false;
76
+
77
+ case PrimitiveType.DateTime:
78
+ // Check if it's a datetime string in FHIR format
79
+ if (typeof value === 'string') {
80
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|[+-]\d{2}:\d{2})?$/.test(value);
81
+ }
82
+ return false;
83
+
84
+ case PrimitiveType.Time:
85
+ // Check if it's a time string in FHIR format (HH:MM:SS)
86
+ if (typeof value === 'string') {
87
+ return /^\d{2}:\d{2}:\d{2}(\.\d{3})?$/.test(value);
88
+ }
89
+ return false;
90
+
91
+ case PrimitiveType.Quantity:
92
+ // Check if it's a quantity object with value and optional unit
93
+ if (typeof value === 'object' && value !== null) {
94
+ return typeof value.value === 'number' &&
95
+ (value.unit === undefined || typeof value.unit === 'string');
96
+ }
97
+ return false;
98
+
99
+ default:
100
+ return false;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Cast a value to a specific type (returns empty if cast fails)
106
+ */
107
+ static cast(value: any, typeName: string): any | null {
108
+ // If already the correct type, return as-is
109
+ if (this.isType(value, typeName)) {
110
+ return value;
111
+ }
112
+
113
+ // Handle FHIR resource casting
114
+ if (!this.primitiveTypes.has(typeName)) {
115
+ // Can't cast between different resource types
116
+ return null;
117
+ }
118
+
119
+ // Handle primitive type casting
120
+ return this.castToPrimitive(value, typeName as PrimitiveType);
121
+ }
122
+
123
+ /**
124
+ * Cast to primitive type
125
+ */
126
+ private static castToPrimitive(value: any, type: PrimitiveType): any | null {
127
+ // For now, return null for failed casts
128
+ // Later we can implement actual conversions (e.g., string to number)
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Get type information for a value
134
+ */
135
+ static getType(value: any): TypeInfo {
136
+ // Check primitive types
137
+ if (typeof value === 'boolean') {
138
+ return { name: PrimitiveType.Boolean, isPrimitive: true };
139
+ }
140
+ if (typeof value === 'string') {
141
+ // Check for specific string formats
142
+ if (/^\d{4}(-\d{2}(-\d{2})?)?$/.test(value)) {
143
+ return { name: PrimitiveType.Date, isPrimitive: true };
144
+ }
145
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value)) {
146
+ return { name: PrimitiveType.DateTime, isPrimitive: true };
147
+ }
148
+ if (/^\d{2}:\d{2}:\d{2}/.test(value)) {
149
+ return { name: PrimitiveType.Time, isPrimitive: true };
150
+ }
151
+ return { name: PrimitiveType.String, isPrimitive: true };
152
+ }
153
+ if (typeof value === 'number') {
154
+ return {
155
+ name: Number.isInteger(value) ? PrimitiveType.Integer : PrimitiveType.Decimal,
156
+ isPrimitive: true
157
+ };
158
+ }
159
+
160
+ // Check for FHIR resources
161
+ if (typeof value === 'object' && value !== null) {
162
+ if (value.resourceType) {
163
+ return { name: value.resourceType, isPrimitive: false, isResource: true };
164
+ }
165
+ if (typeof value.value === 'number' && (value.unit || value.code)) {
166
+ return { name: PrimitiveType.Quantity, isPrimitive: true };
167
+ }
168
+ }
169
+
170
+ // Unknown type
171
+ return { name: 'Unknown', isPrimitive: false };
172
+ }
173
+ }