@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
@@ -1,9 +1,11 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
2
4
 
3
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
6
  // abs() takes no arguments
5
7
  if (args.length !== 0) {
6
- throw new Error('abs() takes no arguments');
8
+ throw Errors.wrongArgumentCount('abs', 0, args.length);
7
9
  }
8
10
 
9
11
  // If input is empty, return empty
@@ -13,29 +15,36 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
13
15
 
14
16
  // If input has multiple items, error
15
17
  if (input.length > 1) {
16
- throw new Error('abs() can only be applied to a single item');
18
+ throw Errors.singletonRequired('abs', input.length);
17
19
  }
18
20
 
19
- const value = input[0];
21
+ const boxedValue = input[0];
22
+ if (!boxedValue) return { value: [], context };
23
+ const value = unbox(boxedValue);
20
24
 
21
25
  // Handle different types
22
26
  if (typeof value === 'number') {
23
- return { value: [Math.abs(value)], context };
27
+ const result = Math.abs(value);
28
+ const typeInfo = Number.isInteger(result) ?
29
+ { type: 'Integer' as const, singleton: true } :
30
+ { type: 'Decimal' as const, singleton: true };
31
+ return { value: [box(result, typeInfo)], context };
24
32
  }
25
33
 
26
34
  // Handle Quantity type
27
35
  if (value && typeof value === 'object' && 'value' in value && 'unit' in value) {
36
+ const result = {
37
+ value: Math.abs(value.value),
38
+ unit: value.unit,
39
+ ...(value._ucumQuantity && { _ucumQuantity: { ...value._ucumQuantity, value: Math.abs(value._ucumQuantity.value) } })
40
+ };
28
41
  return {
29
- value: [{
30
- value: Math.abs(value.value),
31
- unit: value.unit,
32
- ...(value._ucumQuantity && { _ucumQuantity: { ...value._ucumQuantity, value: Math.abs(value._ucumQuantity.value) } })
33
- }],
42
+ value: [box(result, { type: 'Quantity', singleton: true })],
34
43
  context
35
44
  };
36
45
  }
37
46
 
38
- throw new Error(`Cannot apply abs() to ${typeof value}`);
47
+ throw Errors.invalidOperandType('abs', `${typeof value}`);
39
48
  };
40
49
 
41
50
  export const absFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,10 +1,12 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
2
3
  import { RuntimeContextManager } from '../interpreter';
4
+ import { box, unbox } from '../boxing';
3
5
 
4
6
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
5
7
  // aggregator expression is required
6
8
  if (args.length < 1) {
7
- throw new Error('aggregate() requires at least one argument (aggregator expression)');
9
+ throw Errors.invalidOperation('aggregate requires at least one argument (aggregator expression)');
8
10
  }
9
11
 
10
12
  const aggregatorExpr = args[0]!;
@@ -22,8 +24,14 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
22
24
  // For each item in the input collection, evaluate the aggregator expression
23
25
  input.forEach((item, index) => {
24
26
  // Create a new context with $this, $index, and $total
25
- let aggregatorContext = RuntimeContextManager.withIterator(context, item, index);
26
- aggregatorContext = RuntimeContextManager.setVariable(aggregatorContext, '$total', total);
27
+ // Note: $this needs unboxed value, but we pass boxed item to evaluator
28
+ const unboxedItem = unbox(item);
29
+ let aggregatorContext = RuntimeContextManager.withIterator(context, unboxedItem, index);
30
+
31
+ // For the first iteration without init, $total should be empty (not undefined)
32
+ // $total needs to be unboxed values for variable access
33
+ const unboxedTotal = total.map(v => unbox(v));
34
+ aggregatorContext = RuntimeContextManager.setVariable(aggregatorContext, '$total', unboxedTotal);
27
35
 
28
36
  // Evaluate the aggregator expression
29
37
  const result = evaluator(aggregatorExpr, [item], aggregatorContext);
@@ -1,42 +1,52 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
2
3
  import { RuntimeContextManager } from '../interpreter';
4
+ import { box, unbox } from '../boxing';
3
5
 
4
6
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
5
7
  // all() requires exactly one argument (the criteria expression)
6
8
  if (!args || args.length !== 1) {
7
- throw new Error('all() requires exactly one argument');
9
+ throw Errors.invalidOperation('all requires exactly one argument');
8
10
  }
9
11
 
10
12
  // If the input collection is empty, the result is true per spec
11
13
  if (input.length === 0) {
12
- return { value: [true], context };
14
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
13
15
  }
14
16
 
15
17
  const criteriaExpression = args[0];
16
18
  if (!criteriaExpression) {
17
- throw new Error('all() requires a criteria expression');
19
+ throw Errors.invalidOperation('all requires a criteria expression');
18
20
  }
19
21
 
20
22
  // Evaluate the criteria for each element in the input collection
21
23
  for (let i = 0; i < input.length; i++) {
22
- const item = input[i];
24
+ const boxedItem = input[i];
25
+ if (!boxedItem) continue;
26
+
27
+ const item = unbox(boxedItem);
23
28
 
24
29
  // Create iterator context with $this and $index
25
30
  let tempContext = RuntimeContextManager.withIterator(context, item, i);
26
31
  tempContext = RuntimeContextManager.setVariable(tempContext, '$total', input.length);
27
32
 
28
33
  // Evaluate the criteria expression with the current item as context
29
- const result = evaluator(criteriaExpression, [item], tempContext);
34
+ const result = evaluator(criteriaExpression, [boxedItem], tempContext);
30
35
 
31
36
  // Check if the result is truthy
32
37
  // If the result is empty or contains a falsy value, return false
33
- if (result.value.length === 0 || result.value[0] !== true) {
34
- return { value: [false], context };
38
+ if (result.value.length === 0) {
39
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
40
+ }
41
+
42
+ const resultValue = unbox(result.value[0]!);
43
+ if (resultValue !== true) {
44
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
35
45
  }
36
46
  }
37
47
 
38
48
  // All criteria evaluations were true
39
- return { value: [true], context };
49
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
40
50
  };
41
51
 
42
52
  export const allFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,22 +1,25 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
2
4
 
3
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
6
  // If the input is empty, the result is true
5
7
  if (input.length === 0) {
6
- return { value: [true], context };
8
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
7
9
  }
8
10
 
9
- // Verify all inputs are booleans
11
+ // Verify all inputs are booleans (unbox first)
10
12
  for (let i = 0; i < input.length; i++) {
11
- if (typeof input[i] !== 'boolean') {
12
- throw new Error(`allFalse() expects all items to be Boolean values, but item at index ${i} is ${typeof input[i]}`);
13
+ const unboxedValue = unbox(input[i]!);
14
+ if (typeof unboxedValue !== 'boolean') {
15
+ throw Errors.booleanOperationOnNonBoolean('allFalse', i, `${typeof unboxedValue}`);
13
16
  }
14
17
  }
15
18
 
16
19
  // Return true if all items are false, false if any item is true
17
- const result = input.every(item => item === false);
20
+ const result = input.every(item => unbox(item) === false);
18
21
 
19
- return { value: [result], context };
22
+ return { value: [box(result, { type: 'Boolean', singleton: true })], context };
20
23
  };
21
24
 
22
25
  export const allFalseFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,22 +1,25 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
2
4
 
3
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
6
  // If the input is empty, the result is true
5
7
  if (input.length === 0) {
6
- return { value: [true], context };
8
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
7
9
  }
8
10
 
9
- // Verify all inputs are booleans
11
+ // Verify all inputs are booleans (unbox first)
10
12
  for (let i = 0; i < input.length; i++) {
11
- if (typeof input[i] !== 'boolean') {
12
- throw new Error(`allTrue() expects all items to be Boolean values, but item at index ${i} is ${typeof input[i]}`);
13
+ const unboxedValue = unbox(input[i]!);
14
+ if (typeof unboxedValue !== 'boolean') {
15
+ throw Errors.booleanOperationOnNonBoolean('allTrue', i, `${typeof unboxedValue}`);
13
16
  }
14
17
  }
15
18
 
16
19
  // Return true if all items are true, false if any item is false
17
- const result = input.every(item => item === true);
20
+ const result = input.every(item => unbox(item) === true);
18
21
 
19
- return { value: [result], context };
22
+ return { value: [box(result, { type: 'Boolean', singleton: true })], context };
20
23
  };
21
24
 
22
25
  export const allTrueFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,21 +1,30 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
+ import { box, unbox } from '../boxing';
4
5
 
5
6
  export const evaluate: OperationEvaluator = (input, context, left, right) => {
6
7
  // Three-valued logic implementation
7
- if (left.length === 0 || right.length === 0) {
8
- // If either operand is false, result is false
9
- if (left.length > 0 && left[0] === false) {
10
- return { value: [false], context };
11
- }
12
- if (right.length > 0 && right[0] === false) {
13
- return { value: [false], context };
14
- }
15
- // Otherwise unknown
16
- return { value: [], context };
8
+
9
+ // Get values safely
10
+ const leftBoxed = left.length > 0 ? left[0] : null;
11
+ const rightBoxed = right.length > 0 ? right[0] : null;
12
+
13
+ const leftValue = leftBoxed ? unbox(leftBoxed) : null;
14
+ const rightValue = rightBoxed ? unbox(rightBoxed) : null;
15
+
16
+ // If either operand is false, result is false
17
+ if (leftValue === false || rightValue === false) {
18
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
19
+ }
20
+
21
+ // If both operands are true, result is true
22
+ if (leftValue === true && rightValue === true) {
23
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
17
24
  }
18
- return { value: [left[0] && right[0]], context };
25
+
26
+ // Otherwise (empty or non-boolean), result is empty
27
+ return { value: [], context };
19
28
  };
20
29
 
21
30
  export const andOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -1,23 +1,25 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { box, unbox } from '../boxing';
2
3
 
3
4
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
5
  // Empty input returns false per spec
5
6
  if (input.length === 0) {
6
- return { value: [false], context };
7
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
7
8
  }
8
9
 
9
10
  // Check if any item in the collection is false
10
- for (const item of input) {
11
+ for (const boxedItem of input) {
12
+ const item = unbox(boxedItem);
11
13
  if (typeof item !== 'boolean') {
12
14
  continue; // Skip non-boolean values
13
15
  }
14
16
  if (item === false) {
15
- return { value: [true], context };
17
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
16
18
  }
17
19
  }
18
20
 
19
21
  // All items are true or non-boolean
20
- return { value: [false], context };
22
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
21
23
  };
22
24
 
23
25
  export const anyFalseFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,23 +1,25 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { box, unbox } from '../boxing';
2
3
 
3
4
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
5
  // Empty input returns false per spec
5
6
  if (input.length === 0) {
6
- return { value: [false], context };
7
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
7
8
  }
8
9
 
9
10
  // Check if any item in the collection is true
10
- for (const item of input) {
11
+ for (const boxedItem of input) {
12
+ const item = unbox(boxedItem);
11
13
  if (typeof item !== 'boolean') {
12
14
  continue; // Skip non-boolean values
13
15
  }
14
16
  if (item === true) {
15
- return { value: [true], context };
17
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
16
18
  }
17
19
  }
18
20
 
19
21
  // All items are false or non-boolean
20
- return { value: [false], context };
22
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
21
23
  };
22
24
 
23
25
  export const anyTrueFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,6 +1,7 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
+ import { box, unbox } from '../boxing';
4
5
 
5
6
  export const evaluate: OperationEvaluator = (input, context, left, right) => {
6
7
  // 'as' operator performs type casting/filtering
@@ -1,9 +1,11 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
2
4
 
3
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
6
  // ceiling() takes no arguments
5
7
  if (args.length !== 0) {
6
- throw new Error('ceiling() takes no arguments');
8
+ throw Errors.wrongArgumentCount('ceiling', 0, args.length);
7
9
  }
8
10
 
9
11
  // If input is empty, return empty
@@ -13,19 +15,21 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
13
15
 
14
16
  // If input has multiple items, error
15
17
  if (input.length > 1) {
16
- throw new Error('ceiling() can only be applied to a single item');
18
+ throw Errors.singletonRequired('ceiling', input.length);
17
19
  }
18
20
 
19
- const value = input[0];
21
+ const boxedValue = input[0];
22
+ if (!boxedValue) return { value: [], context };
23
+ const value = unbox(boxedValue);
20
24
 
21
25
  // Must be a number
22
26
  if (typeof value !== 'number') {
23
- throw new Error(`Cannot apply ceiling() to ${typeof value}`);
27
+ throw Errors.invalidOperandType('ceiling', `${typeof value}`);
24
28
  }
25
29
 
26
30
  // Math.ceil can return -0, normalize it to 0
27
31
  const result = Math.ceil(value);
28
- return { value: [Object.is(result, -0) ? 0 : result], context };
32
+ return { value: [box(Object.is(result, -0) ? 0 : result, { type: 'Integer', singleton: true })], context };
29
33
  };
30
34
 
31
35
  export const ceilingFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -0,0 +1,94 @@
1
+ import type { FunctionDefinition, FunctionEvaluator, TypeInfo } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
4
+
5
+ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
6
+ if (args.length !== 0) {
7
+ throw Errors.wrongArgumentCount('children', 0, args.length);
8
+ }
9
+
10
+ const results: any[] = [];
11
+ const modelProvider = context.modelProvider;
12
+
13
+ // Process each item in the input collection
14
+ // input is already an array, not an object with value property
15
+ for (const boxedItem of input) {
16
+ const item = unbox(boxedItem);
17
+
18
+ if (item && typeof item === 'object') {
19
+ // Get the parent type info if available
20
+ let parentTypeInfo: TypeInfo | undefined;
21
+ if (modelProvider && boxedItem.typeInfo) {
22
+ parentTypeInfo = boxedItem.typeInfo;
23
+ } else if (modelProvider && 'resourceType' in item && typeof item.resourceType === 'string') {
24
+ // Try to get type info from resourceType
25
+ parentTypeInfo = modelProvider.getType(item.resourceType);
26
+ }
27
+
28
+ // Collect all child properties
29
+ for (const propertyName in item) {
30
+ // Skip resourceType as it's not a child element in FHIRPath
31
+ if (propertyName === 'resourceType') {
32
+ continue;
33
+ }
34
+
35
+ // Skip primitive element properties (those starting with _)
36
+ if (propertyName.startsWith('_')) {
37
+ continue;
38
+ }
39
+
40
+ const value = item[propertyName];
41
+
42
+ // Skip null/undefined values
43
+ if (value === null || value === undefined) {
44
+ continue;
45
+ }
46
+
47
+ // Get type info for this element from model provider
48
+ let elementTypeInfo: TypeInfo | undefined;
49
+ if (modelProvider && parentTypeInfo) {
50
+ elementTypeInfo = modelProvider.getElementType(parentTypeInfo, propertyName);
51
+ }
52
+
53
+ // Get primitive element if it exists
54
+ const primitiveElementName = `_${propertyName}`;
55
+ const primitiveElement = (primitiveElementName in item) ? item[primitiveElementName] : undefined;
56
+
57
+ if (Array.isArray(value)) {
58
+ // Add each array element
59
+ for (let i = 0; i < value.length; i++) {
60
+ const elementValue = value[i];
61
+ if (elementValue !== null && elementValue !== undefined) {
62
+ const elementPrimitive = primitiveElement?.[i];
63
+ // Make the type info singleton since it's a single element
64
+ const singletonTypeInfo = elementTypeInfo ? { ...elementTypeInfo, singleton: true } : undefined;
65
+ results.push(box(elementValue, singletonTypeInfo, elementPrimitive));
66
+ }
67
+ }
68
+ } else {
69
+ // Add single value - ensure it's marked as singleton
70
+ const singletonTypeInfo = elementTypeInfo ? { ...elementTypeInfo, singleton: true } : undefined;
71
+ results.push(box(value, singletonTypeInfo, primitiveElement));
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ return { value: results, context };
78
+ };
79
+
80
+ export const childrenFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
81
+ name: 'children',
82
+ category: ['navigation'],
83
+ description: 'Returns all immediate child nodes of all items in the input collection',
84
+ examples: [
85
+ 'Patient.children()',
86
+ 'Observation.children().ofType(CodeableConcept)'
87
+ ],
88
+ signature: {
89
+ input: { type: 'Any', singleton: false },
90
+ parameters: [],
91
+ result: { type: 'Any', singleton: false }
92
+ },
93
+ evaluate
94
+ };
@@ -1,15 +1,17 @@
1
1
  import type { FunctionDefinition, FunctionEvaluator } from '../types';
2
+ import { Errors } from '../errors';
3
+ import { box, unbox } from '../boxing';
2
4
 
3
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
4
6
  // combine() requires exactly one argument (the other collection)
5
7
  if (!args || args.length !== 1) {
6
- throw new Error('combine() requires exactly one argument');
8
+ throw Errors.invalidOperation('combine requires exactly one argument');
7
9
  }
8
10
 
9
11
  // Evaluate the argument to get the other collection
10
12
  const otherArg = args[0];
11
13
  if (!otherArg) {
12
- throw new Error('combine() requires a collection argument');
14
+ throw Errors.argumentRequired('combine', 'collection argument');
13
15
  }
14
16
 
15
17
  // Evaluate the argument expression
@@ -1,17 +1,18 @@
1
1
  import type { OperatorDefinition } from '../types';
2
2
  import { PRECEDENCE } from '../types';
3
3
  import type { OperationEvaluator } from '../types';
4
+ import { box, unbox } from '../boxing';
4
5
 
5
6
  export const evaluate: OperationEvaluator = (input, context, left, right) => {
6
7
  // Combine operator concatenates all values as strings
7
- const leftStr = left.map(v => String(v)).join('');
8
- const rightStr = right.map(v => String(v)).join('');
8
+ const leftStr = left.map(v => String(unbox(v))).join('');
9
+ const rightStr = right.map(v => String(unbox(v))).join('');
9
10
 
10
11
  if (leftStr === '' && rightStr === '') {
11
12
  return { value: [], context };
12
13
  }
13
14
 
14
- return { value: [leftStr + rightStr], context };
15
+ return { value: [box(leftStr + rightStr, { type: 'String', singleton: true })], context };
15
16
  };
16
17
 
17
18
  export const combineOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -22,6 +23,19 @@ export const combineOperator: OperatorDefinition & { evaluate: OperationEvaluato
22
23
  associativity: 'left',
23
24
  description: 'String concatenation operator',
24
25
  examples: ['first & " " & last'],
25
- signatures: [],
26
+ signatures: [
27
+ {
28
+ name: 'string-combine',
29
+ left: { type: 'String', singleton: true },
30
+ right: { type: 'String', singleton: true },
31
+ result: { type: 'String', singleton: true },
32
+ },
33
+ {
34
+ name: 'any-combine',
35
+ left: { type: 'Any' },
36
+ right: { type: 'Any' },
37
+ result: { type: 'String', singleton: true },
38
+ }
39
+ ],
26
40
  evaluate
27
41
  };
@@ -1,15 +1,17 @@
1
1
  import type { FunctionDefinition } from '../types';
2
+ import { Errors } from '../errors';
2
3
  import type { FunctionEvaluator } from '../types';
4
+ import { box, unbox } from '../boxing';
3
5
 
4
6
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
5
7
  // contains() requires exactly 1 argument
6
8
  if (args.length !== 1) {
7
- throw new Error('contains() requires exactly 1 argument');
9
+ throw Errors.wrongArgumentCount('contains', 1, args.length);
8
10
  }
9
11
 
10
12
  const substringExpr = args[0];
11
13
  if (!substringExpr) {
12
- throw new Error('contains() requires a substring argument');
14
+ throw Errors.argumentRequired('contains', 'substring argument');
13
15
  }
14
16
 
15
17
  // If input collection is empty, result is empty
@@ -19,11 +21,16 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
19
21
 
20
22
  // If input collection contains multiple items, signal an error
21
23
  if (input.length > 1) {
22
- throw new Error('contains() can only be applied to a single string');
24
+ throw Errors.invalidOperation('contains can only be applied to a single string');
23
25
  }
24
26
 
25
27
  // Input must be a string
26
- const inputValue = input[0];
28
+ const boxedInputValue = input[0];
29
+ if (!boxedInputValue) {
30
+ return { value: [], context };
31
+ }
32
+
33
+ const inputValue = unbox(boxedInputValue);
27
34
  if (typeof inputValue !== 'string') {
28
35
  // Non-string input returns empty
29
36
  return { value: [], context };
@@ -39,10 +46,15 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
39
46
 
40
47
  // Substring must be a single string
41
48
  if (substringResult.value.length > 1) {
42
- throw new Error('contains() substring argument must evaluate to a single value');
49
+ throw Errors.invalidOperation('contains substring argument must evaluate to a single value');
50
+ }
51
+
52
+ const boxedSubstring = substringResult.value[0];
53
+ if (!boxedSubstring) {
54
+ return { value: [], context };
43
55
  }
44
56
 
45
- const substring = substringResult.value[0];
57
+ const substring = unbox(boxedSubstring);
46
58
  if (typeof substring !== 'string') {
47
59
  // Non-string substring returns empty
48
60
  return { value: [], context };
@@ -50,12 +62,12 @@ export const evaluate: FunctionEvaluator = (input, context, args, evaluator) =>
50
62
 
51
63
  // If substring is empty string, result is true
52
64
  if (substring === '') {
53
- return { value: [true], context };
65
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
54
66
  }
55
67
 
56
68
  // Check if input string contains the substring
57
69
  const result = inputValue.includes(substring);
58
- return { value: [result], context };
70
+ return { value: [box(result, { type: 'Boolean', singleton: true })], context };
59
71
  };
60
72
 
61
73
  export const containsFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
@@ -1,6 +1,8 @@
1
1
  import type { OperatorDefinition } from '../types';
2
+ import { Errors } from '../errors';
2
3
  import { PRECEDENCE } from '../types';
3
4
  import type { OperationEvaluator } from '../types';
5
+ import { box, unbox } from '../boxing';
4
6
 
5
7
  export const evaluate: OperationEvaluator = (input, context, left, right) => {
6
8
  // If right is empty, result is empty
@@ -10,23 +12,29 @@ export const evaluate: OperationEvaluator = (input, context, left, right) => {
10
12
 
11
13
  // Right must have single item
12
14
  if (right.length > 1) {
13
- throw new Error('contains operator: right operand must be a single item');
15
+ throw Errors.invalidOperation('contains operator: right operand must be a single item');
14
16
  }
15
17
 
16
18
  // If left is empty, result is false
17
19
  if (left.length === 0) {
18
- return { value: [false], context };
20
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
19
21
  }
20
22
 
21
23
  // Check if the single right item is in left using equality
22
- const rightItem = right[0];
23
- for (const leftItem of left) {
24
+ const boxedRightItem = right[0];
25
+ if (!boxedRightItem) {
26
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
27
+ }
28
+ const rightItem = unbox(boxedRightItem);
29
+
30
+ for (const boxedLeftItem of left) {
31
+ const leftItem = unbox(boxedLeftItem);
24
32
  if (leftItem === rightItem) {
25
- return { value: [true], context };
33
+ return { value: [box(true, { type: 'Boolean', singleton: true })], context };
26
34
  }
27
35
  }
28
36
 
29
- return { value: [false], context };
37
+ return { value: [box(false, { type: 'Boolean', singleton: true })], context };
30
38
  };
31
39
 
32
40
  export const containsOperator: OperatorDefinition & { evaluate: OperationEvaluator } = {
@@ -1,9 +1,10 @@
1
1
  import type { FunctionDefinition } from '../types';
2
2
  import type { FunctionEvaluator } from '../types';
3
+ import { box, unbox } from '../boxing';
3
4
 
4
5
  export const evaluate: FunctionEvaluator = (input, context, args, evaluator) => {
5
6
  return {
6
- value: [input.length],
7
+ value: [box(input.length, { type: 'Integer', singleton: true })],
7
8
  context
8
9
  };
9
10
  };