@atomic-ehr/fhirpath 0.0.2 → 0.0.3
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.
- package/README.md +716 -238
- package/dist/index.d.ts +225 -119
- package/dist/index.js +10911 -5600
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
- package/src/analyzer/augmentor.ts +242 -0
- package/src/analyzer/cursor-services.ts +75 -0
- package/src/analyzer/scope-manager.ts +57 -0
- package/src/analyzer/trivia-indexer.ts +58 -0
- package/src/analyzer/type-compat.ts +157 -0
- package/src/analyzer/utils.ts +132 -0
- package/src/analyzer.ts +921 -1208
- package/src/completion-provider.ts +209 -191
- package/src/{quantity-value.ts → complex-types/quantity-value.ts} +112 -22
- package/src/complex-types/temporal.ts +1737 -0
- package/src/errors.ts +25 -3
- package/src/index.ts +17 -104
- package/src/inspect.ts +4 -4
- package/src/{boxing.ts → interpreter/boxing.ts} +1 -1
- package/src/interpreter/navigator.ts +94 -0
- package/src/interpreter/runtime-context.ts +273 -0
- package/src/interpreter.ts +435 -469
- package/src/lexer.ts +188 -210
- package/src/model-provider.ts +71 -43
- package/src/operations/abs-function.ts +1 -1
- package/src/operations/aggregate-function.ts +84 -5
- package/src/operations/all-function.ts +4 -3
- package/src/operations/allFalse-function.ts +2 -1
- package/src/operations/allTrue-function.ts +2 -1
- package/src/operations/and-operator.ts +2 -1
- package/src/operations/anyFalse-function.ts +2 -1
- package/src/operations/anyTrue-function.ts +2 -1
- package/src/operations/as-function.ts +58 -0
- package/src/operations/as-operator.ts +57 -19
- package/src/operations/ceiling-function.ts +1 -1
- package/src/operations/children-function.ts +14 -5
- package/src/operations/combine-function.ts +6 -3
- package/src/operations/combine-operator.ts +6 -7
- package/src/operations/comparison.ts +692 -0
- package/src/operations/contains-function.ts +1 -1
- package/src/operations/contains-operator.ts +2 -1
- package/src/operations/convertsToBoolean-function.ts +78 -0
- package/src/operations/convertsToDecimal-function.ts +82 -0
- package/src/operations/convertsToInteger-function.ts +71 -0
- package/src/operations/convertsToLong-function.ts +89 -0
- package/src/operations/convertsToQuantity-function.ts +116 -0
- package/src/operations/convertsToString-function.ts +88 -0
- package/src/operations/count-function.ts +2 -1
- package/src/operations/dateOf-function.ts +69 -0
- package/src/operations/dayOf-function.ts +66 -0
- package/src/operations/decimal-boundaries.ts +133 -0
- package/src/operations/defineVariable-function.ts +130 -17
- package/src/operations/distinct-function.ts +1 -1
- package/src/operations/div-operator.ts +1 -1
- package/src/operations/divide-operator.ts +12 -7
- package/src/operations/dot-operator.ts +1 -1
- package/src/operations/empty-function.ts +30 -21
- package/src/operations/endsWith-function.ts +6 -1
- package/src/operations/equal-operator.ts +23 -32
- package/src/operations/equivalent-operator.ts +13 -53
- package/src/operations/exclude-function.ts +2 -1
- package/src/operations/exists-function.ts +4 -3
- package/src/operations/first-function.ts +1 -1
- package/src/operations/floor-function.ts +1 -1
- package/src/operations/greater-operator.ts +20 -3
- package/src/operations/greater-or-equal-operator.ts +20 -3
- package/src/operations/highBoundary-function.ts +120 -0
- package/src/operations/hourOf-function.ts +66 -0
- package/src/operations/iif-function.ts +186 -7
- package/src/operations/implies-operator.ts +1 -1
- package/src/operations/in-operator.ts +2 -1
- package/src/operations/index.ts +41 -0
- package/src/operations/indexOf-function.ts +1 -1
- package/src/operations/intersect-function.ts +1 -1
- package/src/operations/is-function.ts +59 -0
- package/src/operations/is-operator.ts +20 -9
- package/src/operations/isDistinct-function.ts +2 -1
- package/src/operations/join-function.ts +1 -1
- package/src/operations/last-function.ts +1 -1
- package/src/operations/lastIndexOf-function.ts +85 -0
- package/src/operations/length-function.ts +1 -1
- package/src/operations/less-operator.ts +20 -3
- package/src/operations/less-or-equal-operator.ts +20 -3
- package/src/operations/less-than.ts +2 -2
- package/src/operations/lowBoundary-function.ts +120 -0
- package/src/operations/lower-function.ts +1 -1
- package/src/operations/matches-function.ts +86 -0
- package/src/operations/matchesFull-function.ts +96 -0
- package/src/operations/millisecondOf-function.ts +66 -0
- package/src/operations/minus-operator.ts +69 -4
- package/src/operations/minuteOf-function.ts +66 -0
- package/src/operations/mod-operator.ts +1 -1
- package/src/operations/monthOf-function.ts +66 -0
- package/src/operations/multiply-operator.ts +27 -3
- package/src/operations/not-equal-operator.ts +24 -30
- package/src/operations/not-equivalent-operator.ts +13 -53
- package/src/operations/not-function.ts +1 -1
- package/src/operations/ofType-function.ts +8 -12
- package/src/operations/or-operator.ts +2 -1
- package/src/operations/plus-operator.ts +71 -7
- package/src/operations/power-function.ts +35 -10
- package/src/operations/repeat-function.ts +169 -0
- package/src/operations/replace-function.ts +1 -1
- package/src/operations/replaceMatches-function.ts +120 -0
- package/src/operations/round-function.ts +1 -1
- package/src/operations/secondOf-function.ts +66 -0
- package/src/operations/select-function.ts +66 -5
- package/src/operations/single-function.ts +1 -1
- package/src/operations/skip-function.ts +1 -1
- package/src/operations/split-function.ts +1 -1
- package/src/operations/sqrt-function.ts +15 -8
- package/src/operations/startsWith-function.ts +1 -1
- package/src/operations/subsetOf-function.ts +6 -2
- package/src/operations/substring-function.ts +1 -1
- package/src/operations/supersetOf-function.ts +6 -2
- package/src/operations/tail-function.ts +1 -1
- package/src/operations/take-function.ts +1 -1
- package/src/operations/temporal-functions.ts +555 -0
- package/src/operations/timeOf-function.ts +67 -0
- package/src/operations/timezoneOffsetOf-function.ts +69 -0
- package/src/operations/toBoolean-function.ts +27 -8
- package/src/operations/toChars-function.ts +56 -0
- package/src/operations/toDecimal-function.ts +27 -8
- package/src/operations/toInteger-function.ts +15 -3
- package/src/operations/toLong-function.ts +98 -0
- package/src/operations/toQuantity-function.ts +181 -0
- package/src/operations/toString-function.ts +45 -3
- package/src/operations/trace-function.ts +1 -1
- package/src/operations/trim-function.ts +1 -1
- package/src/operations/truncate-function.ts +1 -1
- package/src/operations/unary-minus-operator.ts +2 -2
- package/src/operations/unary-plus-operator.ts +1 -1
- package/src/operations/union-function.ts +1 -1
- package/src/operations/union-operator.ts +16 -26
- package/src/operations/upper-function.ts +1 -1
- package/src/operations/where-function.ts +3 -3
- package/src/operations/xor-operator.ts +1 -1
- package/src/operations/yearOf-function.ts +66 -0
- package/src/{cursor-nodes.ts → parser/cursor-nodes.ts} +10 -7
- package/src/parser.ts +248 -501
- package/src/registry.ts +53 -42
- package/src/types.ts +128 -16
- package/src/utils/pprint.ts +151 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// sqrt() takes no arguments
|
|
@@ -43,12 +43,19 @@ export const sqrtFunction: FunctionDefinition & { evaluate: FunctionEvaluator }
|
|
|
43
43
|
'81.sqrt()',
|
|
44
44
|
'(-1).sqrt()'
|
|
45
45
|
],
|
|
46
|
-
signatures: [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
signatures: [
|
|
47
|
+
{
|
|
48
|
+
name: 'sqrt-integer',
|
|
49
|
+
input: { type: 'Integer', singleton: true },
|
|
50
|
+
parameters: [],
|
|
51
|
+
result: { type: 'Decimal', singleton: true }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'sqrt-decimal',
|
|
55
|
+
input: { type: 'Decimal', singleton: true },
|
|
56
|
+
parameters: [],
|
|
57
|
+
result: { type: 'Decimal', singleton: true }
|
|
58
|
+
}
|
|
59
|
+
],
|
|
53
60
|
evaluate
|
|
54
61
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// startsWith() requires exactly 1 argument
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
if (args.length !== 1) {
|
|
@@ -19,7 +19,10 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
19
19
|
}
|
|
20
20
|
// Evaluate the argument with the root context ($this), not the current input
|
|
21
21
|
// This allows expressions like Patient.name.given to work correctly
|
|
22
|
-
|
|
22
|
+
// $this contains the original input to the FHIRPath expression
|
|
23
|
+
// context.input contains the original input passed to evaluate()
|
|
24
|
+
// We should use $this if available, otherwise fall back to context.input
|
|
25
|
+
const rootInput = context.variables?.['$this'] || context.input || [];
|
|
23
26
|
const otherResult = await evaluator(argNode, rootInput, context);
|
|
24
27
|
const other = otherResult.value;
|
|
25
28
|
|
|
@@ -52,6 +55,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
52
55
|
|
|
53
56
|
export const subsetOfFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
54
57
|
name: 'subsetOf',
|
|
58
|
+
doesNotPropagateEmpty: true, // Returns true if input is empty
|
|
55
59
|
category: ['existence'],
|
|
56
60
|
description: 'Returns true if all items in the input collection are members of the collection passed as the other argument. Membership is determined using the equals (=) operation.',
|
|
57
61
|
examples: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// Check single item in input
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
if (args.length !== 1) {
|
|
@@ -14,7 +14,10 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
14
14
|
}
|
|
15
15
|
// Evaluate the argument with the root context ($this), not the current input
|
|
16
16
|
// This allows expressions like Patient.name.given to work correctly
|
|
17
|
-
|
|
17
|
+
// $this contains the original input to the FHIRPath expression
|
|
18
|
+
// context.input contains the original input passed to evaluate()
|
|
19
|
+
// We should use $this if available, otherwise fall back to context.input
|
|
20
|
+
const rootInput = context.variables?.['$this'] || context.input || [];
|
|
18
21
|
const otherResult = await evaluator(argNode, rootInput, context);
|
|
19
22
|
const other = otherResult.value;
|
|
20
23
|
|
|
@@ -52,6 +55,7 @@ export const evaluate: FunctionEvaluator = async (input, context, args, evaluato
|
|
|
52
55
|
|
|
53
56
|
export const supersetOfFunction: FunctionDefinition & { evaluate: FunctionEvaluator } = {
|
|
54
57
|
name: 'supersetOf',
|
|
58
|
+
doesNotPropagateEmpty: true, // Returns false if input is empty (unless other is also empty)
|
|
55
59
|
category: ['existence'],
|
|
56
60
|
description: 'Returns true if all items in the collection passed as the other argument are members of the input collection. Membership is determined using the equals (=) operation.',
|
|
57
61
|
examples: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// tail() takes no arguments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionDefinition, FunctionEvaluator } from '../types';
|
|
2
2
|
import { Errors } from '../errors';
|
|
3
|
-
import { box, unbox } from '../boxing';
|
|
3
|
+
import { box, unbox } from '../interpreter/boxing';
|
|
4
4
|
|
|
5
5
|
export const evaluate: FunctionEvaluator = async (input, context, args, evaluator) => {
|
|
6
6
|
// Validate arguments
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
// Built-in temporal functions for FHIRPath
|
|
2
|
+
import type { FunctionDefinition } from '../types';
|
|
3
|
+
import type { FunctionEvaluator } from '../types';
|
|
4
|
+
import { box } from '../interpreter/boxing';
|
|
5
|
+
import { createDate, createDateTime, createTime, isFHIRDate, isFHIRDateTime, isFHIRTime } from '../complex-types/temporal';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// now() function - Returns current DateTime
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export const nowEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
12
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
13
|
+
const CACHE_KEY = '__fhirpath_now_cache__';
|
|
14
|
+
|
|
15
|
+
if (context.variables[CACHE_KEY]) {
|
|
16
|
+
return {
|
|
17
|
+
value: [context.variables[CACHE_KEY]],
|
|
18
|
+
context
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const dateTime = createDateTime(
|
|
25
|
+
now.getFullYear(),
|
|
26
|
+
now.getMonth() + 1,
|
|
27
|
+
now.getDate(),
|
|
28
|
+
now.getHours(),
|
|
29
|
+
now.getMinutes(),
|
|
30
|
+
now.getSeconds(),
|
|
31
|
+
now.getMilliseconds(),
|
|
32
|
+
-now.getTimezoneOffset() // Convert to minutes from UTC (JS gives minutes to subtract from UTC)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
37
|
+
context
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const nowFunction: FunctionDefinition & { evaluate: typeof nowEvaluator } = {
|
|
42
|
+
name: 'now',
|
|
43
|
+
category: ['temporal'],
|
|
44
|
+
description: 'Returns the current date and time as a DateTime value',
|
|
45
|
+
examples: ['now()', 'Patient.birthDate < now()'],
|
|
46
|
+
signatures: [
|
|
47
|
+
{
|
|
48
|
+
name: 'now',
|
|
49
|
+
input: { type: 'Any', singleton: false },
|
|
50
|
+
parameters: [],
|
|
51
|
+
result: { type: 'DateTime', singleton: true }
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
evaluate: nowEvaluator
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// today() function - Returns current Date
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
export const todayEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
62
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
63
|
+
const CACHE_KEY = '__fhirpath_today_cache__';
|
|
64
|
+
|
|
65
|
+
if (context.variables[CACHE_KEY]) {
|
|
66
|
+
return {
|
|
67
|
+
value: [context.variables[CACHE_KEY]],
|
|
68
|
+
context
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
73
|
+
const today = new Date();
|
|
74
|
+
const date = createDate(
|
|
75
|
+
today.getFullYear(),
|
|
76
|
+
today.getMonth() + 1,
|
|
77
|
+
today.getDate()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
value: [box(date, { type: 'Date', singleton: true })],
|
|
82
|
+
context
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const todayFunction: FunctionDefinition & { evaluate: typeof todayEvaluator } = {
|
|
87
|
+
name: 'today',
|
|
88
|
+
category: ['temporal'],
|
|
89
|
+
description: 'Returns the current date (no time component)',
|
|
90
|
+
examples: ['today()', 'Patient.birthDate < today()'],
|
|
91
|
+
signatures: [
|
|
92
|
+
{
|
|
93
|
+
name: 'today',
|
|
94
|
+
input: { type: 'Any', singleton: false },
|
|
95
|
+
parameters: [],
|
|
96
|
+
result: { type: 'Date', singleton: true }
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
evaluate: todayEvaluator
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// timeOfDay() function - Returns current Time
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export const timeOfDayEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
107
|
+
// Always use cached value if available (set at expression evaluation start)
|
|
108
|
+
const CACHE_KEY = '__fhirpath_timeOfDay_cache__';
|
|
109
|
+
|
|
110
|
+
if (context.variables[CACHE_KEY]) {
|
|
111
|
+
return {
|
|
112
|
+
value: [context.variables[CACHE_KEY]],
|
|
113
|
+
context
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fallback: create new value if not cached (shouldn't happen in normal evaluation)
|
|
118
|
+
const now = new Date();
|
|
119
|
+
const time = createTime(
|
|
120
|
+
now.getHours(),
|
|
121
|
+
now.getMinutes(),
|
|
122
|
+
now.getSeconds(),
|
|
123
|
+
now.getMilliseconds()
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
128
|
+
context
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const timeOfDayFunction: FunctionDefinition & { evaluate: typeof timeOfDayEvaluator } = {
|
|
133
|
+
name: 'timeOfDay',
|
|
134
|
+
category: ['temporal'],
|
|
135
|
+
description: 'Returns the current time of day',
|
|
136
|
+
examples: ['timeOfDay()', '@T09:00 < timeOfDay()'],
|
|
137
|
+
signatures: [
|
|
138
|
+
{
|
|
139
|
+
name: 'timeOfDay',
|
|
140
|
+
input: { type: 'Any', singleton: false },
|
|
141
|
+
parameters: [],
|
|
142
|
+
result: { type: 'Time', singleton: true }
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
evaluate: timeOfDayEvaluator
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// toDate() function - Convert to Date
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
export const toDateEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
153
|
+
if (input.length === 0) {
|
|
154
|
+
return { value: [], context };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const boxedItem = input[0];
|
|
158
|
+
if (!boxedItem) {
|
|
159
|
+
return { value: [], context };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const item = boxedItem.value;
|
|
163
|
+
const typeInfo = boxedItem.typeInfo;
|
|
164
|
+
|
|
165
|
+
// If it's already a Date, return as is
|
|
166
|
+
if (typeInfo?.type === 'Date') {
|
|
167
|
+
return { value: [boxedItem], context };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If it's a DateTime, extract the date portion
|
|
171
|
+
if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
|
|
172
|
+
const dateTime = item as any;
|
|
173
|
+
const date = createDate(dateTime.year, dateTime.month, dateTime.day);
|
|
174
|
+
return {
|
|
175
|
+
value: [box(date, { type: 'Date', singleton: true })],
|
|
176
|
+
context
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If it's a String, try to parse it
|
|
181
|
+
if (typeof item === 'string') {
|
|
182
|
+
try {
|
|
183
|
+
const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
|
|
184
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
185
|
+
if (isFHIRDate(temporal)) {
|
|
186
|
+
return {
|
|
187
|
+
value: [box(temporal, { type: 'Date', singleton: true })],
|
|
188
|
+
context
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Parsing failed, return empty
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// For any other type (including Boolean), return empty
|
|
197
|
+
// This allows expressions like ({}).empty().toDate() to return []
|
|
198
|
+
return { value: [], context };
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const toDateFunction: FunctionDefinition & { evaluate: typeof toDateEvaluator } = {
|
|
202
|
+
name: 'toDate',
|
|
203
|
+
category: ['conversion', 'temporal'],
|
|
204
|
+
description: 'Converts the input to a Date value',
|
|
205
|
+
examples: ["'2020-01-15'.toDate()", '@2020-01-15T10:30:00.toDate()'],
|
|
206
|
+
signatures: [
|
|
207
|
+
{
|
|
208
|
+
name: 'any-toDate',
|
|
209
|
+
parameters: [],
|
|
210
|
+
input: { type: 'Any', singleton: true },
|
|
211
|
+
result: { type: 'Date', singleton: true }
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
evaluate: toDateEvaluator
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// toDateTime() function - Convert to DateTime
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
export const toDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
222
|
+
if (input.length === 0) {
|
|
223
|
+
return { value: [], context };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const boxedItem = input[0];
|
|
227
|
+
if (!boxedItem) {
|
|
228
|
+
return { value: [], context };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const item = boxedItem.value;
|
|
232
|
+
const typeInfo = boxedItem.typeInfo;
|
|
233
|
+
|
|
234
|
+
// If it's already a DateTime, return as is
|
|
235
|
+
if (typeInfo?.type === 'DateTime') {
|
|
236
|
+
return { value: [boxedItem], context };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If it's a Date, convert to DateTime with time as 00:00:00
|
|
240
|
+
if (typeInfo?.type === 'Date' && item && typeof item === 'object') {
|
|
241
|
+
const date = item as any;
|
|
242
|
+
const dateTime = createDateTime(
|
|
243
|
+
date.year,
|
|
244
|
+
date.month,
|
|
245
|
+
date.day,
|
|
246
|
+
0, 0, 0, 0
|
|
247
|
+
);
|
|
248
|
+
return {
|
|
249
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
250
|
+
context
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// If it's a String, try to parse it
|
|
255
|
+
if (typeof item === 'string') {
|
|
256
|
+
try {
|
|
257
|
+
const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
|
|
258
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
259
|
+
if (isFHIRDateTime(temporal)) {
|
|
260
|
+
return {
|
|
261
|
+
value: [box(temporal, { type: 'DateTime', singleton: true })],
|
|
262
|
+
context
|
|
263
|
+
};
|
|
264
|
+
} else if (isFHIRDate(temporal)) {
|
|
265
|
+
// Convert Date to DateTime (with time as 00:00:00)
|
|
266
|
+
const dateTime = createDateTime(
|
|
267
|
+
temporal.year,
|
|
268
|
+
temporal.month,
|
|
269
|
+
temporal.day,
|
|
270
|
+
0, 0, 0, 0
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
value: [box(dateTime, { type: 'DateTime', singleton: true })],
|
|
274
|
+
context
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// Parsing failed, return empty
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// For any other type (including Boolean), return empty
|
|
283
|
+
// This allows expressions like ({}).empty().toDateTime() to return []
|
|
284
|
+
return { value: [], context };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const toDateTimeFunction: FunctionDefinition & { evaluate: typeof toDateTimeEvaluator } = {
|
|
288
|
+
name: 'toDateTime',
|
|
289
|
+
category: ['conversion', 'temporal'],
|
|
290
|
+
description: 'Converts the input to a DateTime value',
|
|
291
|
+
examples: ["'2020-01-15T10:30:00Z'.toDateTime()", '@2020-01-15.toDateTime()'],
|
|
292
|
+
signatures: [
|
|
293
|
+
{
|
|
294
|
+
name: 'any-toDateTime',
|
|
295
|
+
parameters: [],
|
|
296
|
+
input: { type: 'Any', singleton: true },
|
|
297
|
+
result: { type: 'DateTime', singleton: true }
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
evaluate: toDateTimeEvaluator
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// toTime() function - Convert to Time
|
|
305
|
+
// ============================================================================
|
|
306
|
+
|
|
307
|
+
export const toTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
308
|
+
if (input.length === 0) {
|
|
309
|
+
return { value: [], context };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const boxedItem = input[0];
|
|
313
|
+
if (!boxedItem) {
|
|
314
|
+
return { value: [], context };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const item = boxedItem.value;
|
|
318
|
+
const typeInfo = boxedItem.typeInfo;
|
|
319
|
+
|
|
320
|
+
// If it's already a Time, return as is
|
|
321
|
+
if (typeInfo?.type === 'Time') {
|
|
322
|
+
return { value: [boxedItem], context };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// If it's a DateTime, extract the time portion
|
|
326
|
+
if (typeInfo?.type === 'DateTime' && item && typeof item === 'object') {
|
|
327
|
+
const dateTime = item as any;
|
|
328
|
+
if (dateTime.hour !== undefined) {
|
|
329
|
+
const time = createTime(dateTime.hour, dateTime.minute, dateTime.second, dateTime.millisecond);
|
|
330
|
+
return {
|
|
331
|
+
value: [box(time, { type: 'Time', singleton: true })],
|
|
332
|
+
context
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// If it's a String, try to parse it
|
|
338
|
+
if (typeof item === 'string') {
|
|
339
|
+
try {
|
|
340
|
+
const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
|
|
341
|
+
// For time strings, prepend T if not present
|
|
342
|
+
const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
|
|
343
|
+
const temporal = parseTemporalLiteral(timeString);
|
|
344
|
+
if (isFHIRTime(temporal)) {
|
|
345
|
+
return {
|
|
346
|
+
value: [box(temporal, { type: 'Time', singleton: true })],
|
|
347
|
+
context
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
// Parsing failed, return empty
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// For any other type (including Boolean), return empty
|
|
356
|
+
// This allows expressions like ({}).empty().toTime() to return []
|
|
357
|
+
return { value: [], context };
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export const toTimeFunction: FunctionDefinition & { evaluate: typeof toTimeEvaluator } = {
|
|
361
|
+
name: 'toTime',
|
|
362
|
+
category: ['conversion', 'temporal'],
|
|
363
|
+
description: 'Converts the input to a Time value',
|
|
364
|
+
examples: ["'14:30:00'.toTime()", '@2020-01-15T10:30:00.toTime()'],
|
|
365
|
+
signatures: [
|
|
366
|
+
{
|
|
367
|
+
name: 'any-toTime',
|
|
368
|
+
parameters: [],
|
|
369
|
+
input: { type: 'Any', singleton: true },
|
|
370
|
+
result: { type: 'Time', singleton: true }
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
evaluate: toTimeEvaluator
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// convertsToDate() function - Check if convertible to Date
|
|
378
|
+
// ============================================================================
|
|
379
|
+
|
|
380
|
+
export const convertsToDateEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
381
|
+
if (input.length === 0) {
|
|
382
|
+
return { value: [], context };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const boxedItem = input[0];
|
|
386
|
+
if (!boxedItem) {
|
|
387
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const item = boxedItem.value;
|
|
391
|
+
const typeInfo = boxedItem.typeInfo;
|
|
392
|
+
|
|
393
|
+
// Already a Date
|
|
394
|
+
if (typeInfo?.type === 'Date') {
|
|
395
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// DateTime can be converted
|
|
399
|
+
if (typeInfo?.type === 'DateTime') {
|
|
400
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Try to parse string
|
|
404
|
+
if (typeof item === 'string') {
|
|
405
|
+
try {
|
|
406
|
+
const { parseTemporalLiteral, isFHIRDate } = await import('../complex-types/temporal');
|
|
407
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
408
|
+
return {
|
|
409
|
+
value: [box(isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
|
|
410
|
+
context
|
|
411
|
+
};
|
|
412
|
+
} catch {
|
|
413
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export const convertsToDateFunction: FunctionDefinition & { evaluate: typeof convertsToDateEvaluator } = {
|
|
421
|
+
name: 'convertsToDate',
|
|
422
|
+
category: ['conversion', 'temporal'],
|
|
423
|
+
description: 'Returns true if the input can be converted to a Date',
|
|
424
|
+
examples: ["'2020-01-15'.convertsToDate()", "'invalid'.convertsToDate()"],
|
|
425
|
+
signatures: [
|
|
426
|
+
{
|
|
427
|
+
name: 'convertsToDate',
|
|
428
|
+
parameters: [],
|
|
429
|
+
input: { type: 'Any', singleton: true },
|
|
430
|
+
result: { type: 'Boolean', singleton: true }
|
|
431
|
+
}
|
|
432
|
+
],
|
|
433
|
+
evaluate: convertsToDateEvaluator
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// convertsToDateTime() function - Check if convertible to DateTime
|
|
438
|
+
// ============================================================================
|
|
439
|
+
|
|
440
|
+
export const convertsToDateTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
441
|
+
if (input.length === 0) {
|
|
442
|
+
return { value: [], context };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const boxedItem = input[0];
|
|
446
|
+
if (!boxedItem) {
|
|
447
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const item = boxedItem.value;
|
|
451
|
+
const typeInfo = boxedItem.typeInfo;
|
|
452
|
+
|
|
453
|
+
// Already a DateTime
|
|
454
|
+
if (typeInfo?.type === 'DateTime') {
|
|
455
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Date can be converted
|
|
459
|
+
if (typeInfo?.type === 'Date') {
|
|
460
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Try to parse string
|
|
464
|
+
if (typeof item === 'string') {
|
|
465
|
+
try {
|
|
466
|
+
const { parseTemporalLiteral, isFHIRDate, isFHIRDateTime } = await import('../complex-types/temporal');
|
|
467
|
+
const temporal = parseTemporalLiteral('@' + item);
|
|
468
|
+
return {
|
|
469
|
+
value: [box(isFHIRDateTime(temporal) || isFHIRDate(temporal), { type: 'Boolean', singleton: true })],
|
|
470
|
+
context
|
|
471
|
+
};
|
|
472
|
+
} catch {
|
|
473
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
export const convertsToDateTimeFunction: FunctionDefinition & { evaluate: typeof convertsToDateTimeEvaluator } = {
|
|
481
|
+
name: 'convertsToDateTime',
|
|
482
|
+
category: ['conversion', 'temporal'],
|
|
483
|
+
description: 'Returns true if the input can be converted to a DateTime',
|
|
484
|
+
examples: ["'2020-01-15T10:30:00'.convertsToDateTime()", "'invalid'.convertsToDateTime()"],
|
|
485
|
+
signatures: [
|
|
486
|
+
{
|
|
487
|
+
name: 'convertsToDateTime',
|
|
488
|
+
parameters: [],
|
|
489
|
+
input: { type: 'Any', singleton: true },
|
|
490
|
+
result: { type: 'Boolean', singleton: true }
|
|
491
|
+
}
|
|
492
|
+
],
|
|
493
|
+
evaluate: convertsToDateTimeEvaluator
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// ============================================================================
|
|
497
|
+
// convertsToTime() function - Check if convertible to Time
|
|
498
|
+
// ============================================================================
|
|
499
|
+
|
|
500
|
+
export const convertsToTimeEvaluator: FunctionEvaluator = async (input, context, args) => {
|
|
501
|
+
if (input.length === 0) {
|
|
502
|
+
return { value: [], context };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const boxedItem = input[0];
|
|
506
|
+
if (!boxedItem) {
|
|
507
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const item = boxedItem.value;
|
|
511
|
+
const typeInfo = boxedItem.typeInfo;
|
|
512
|
+
|
|
513
|
+
// Already a Time
|
|
514
|
+
if (typeInfo?.type === 'Time') {
|
|
515
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// DateTime can be converted if it has time components
|
|
519
|
+
if (typeInfo?.type === 'DateTime') {
|
|
520
|
+
return { value: [box(true, { type: 'Boolean', singleton: true })], context };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Try to parse string
|
|
524
|
+
if (typeof item === 'string') {
|
|
525
|
+
try {
|
|
526
|
+
const { parseTemporalLiteral, isFHIRTime } = await import('../complex-types/temporal');
|
|
527
|
+
const timeString = item.startsWith('T') ? '@' + item : '@T' + item;
|
|
528
|
+
const temporal = parseTemporalLiteral(timeString);
|
|
529
|
+
return {
|
|
530
|
+
value: [box(isFHIRTime(temporal), { type: 'Boolean', singleton: true })],
|
|
531
|
+
context
|
|
532
|
+
};
|
|
533
|
+
} catch {
|
|
534
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return { value: [box(false, { type: 'Boolean', singleton: true })], context };
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
export const convertsToTimeFunction: FunctionDefinition & { evaluate: typeof convertsToTimeEvaluator } = {
|
|
542
|
+
name: 'convertsToTime',
|
|
543
|
+
category: ['conversion', 'temporal'],
|
|
544
|
+
description: 'Returns true if the input can be converted to a Time',
|
|
545
|
+
examples: ["'14:30:00'.convertsToTime()", "'invalid'.convertsToTime()"],
|
|
546
|
+
signatures: [
|
|
547
|
+
{
|
|
548
|
+
name: 'convertsToTime',
|
|
549
|
+
parameters: [],
|
|
550
|
+
input: { type: 'Any', singleton: true },
|
|
551
|
+
result: { type: 'Boolean', singleton: true }
|
|
552
|
+
}
|
|
553
|
+
],
|
|
554
|
+
evaluate: convertsToTimeEvaluator
|
|
555
|
+
};
|