fhirpath-ts 0.0.0-bootstrap.0 → 0.2.0

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 (120) hide show
  1. package/README.md +50 -18
  2. package/dist/analyzer/analyze-dto.d.ts +4 -0
  3. package/dist/analyzer/analyze-dto.d.ts.map +1 -1
  4. package/dist/analyzer/analyze-dto.js +12 -14
  5. package/dist/analyzer/analyze-dto.js.map +1 -1
  6. package/dist/analyzer/analyze.d.ts +22 -6
  7. package/dist/analyzer/analyze.d.ts.map +1 -1
  8. package/dist/analyzer/analyze.js +242 -98
  9. package/dist/analyzer/analyze.js.map +1 -1
  10. package/dist/analyzer/declarations.d.ts +26 -0
  11. package/dist/analyzer/declarations.d.ts.map +1 -0
  12. package/dist/analyzer/declarations.js +68 -0
  13. package/dist/analyzer/declarations.js.map +1 -0
  14. package/dist/analyzer/operator-rules.d.ts +127 -0
  15. package/dist/analyzer/operator-rules.d.ts.map +1 -0
  16. package/dist/analyzer/operator-rules.js +70 -0
  17. package/dist/analyzer/operator-rules.js.map +1 -0
  18. package/dist/analyzer/signatures.d.ts +1097 -10
  19. package/dist/analyzer/signatures.d.ts.map +1 -1
  20. package/dist/analyzer/signatures.js +70 -29
  21. package/dist/analyzer/signatures.js.map +1 -1
  22. package/dist/api/compile.d.ts +32 -8
  23. package/dist/api/compile.d.ts.map +1 -1
  24. package/dist/api/compile.js +2 -0
  25. package/dist/api/compile.js.map +1 -1
  26. package/dist/api/engine.d.ts +42 -16
  27. package/dist/api/engine.d.ts.map +1 -1
  28. package/dist/api/engine.js +2 -1
  29. package/dist/api/engine.js.map +1 -1
  30. package/dist/api/project.d.ts +8 -8
  31. package/dist/api/project.d.ts.map +1 -1
  32. package/dist/api/project.js +31 -19
  33. package/dist/api/project.js.map +1 -1
  34. package/dist/api/strict.d.ts +6 -0
  35. package/dist/api/strict.d.ts.map +1 -0
  36. package/dist/api/strict.js +71 -0
  37. package/dist/api/strict.js.map +1 -0
  38. package/dist/api/tagged.d.ts +4 -4
  39. package/dist/api/tagged.d.ts.map +1 -1
  40. package/dist/api/tagged.js.map +1 -1
  41. package/dist/cli/dto-check.js +7 -3
  42. package/dist/cli/dto-check.js.map +1 -1
  43. package/dist/engine/context.d.ts +3 -1
  44. package/dist/engine/context.d.ts.map +1 -1
  45. package/dist/engine/context.js +5 -1
  46. package/dist/engine/context.js.map +1 -1
  47. package/dist/engine/navigation.d.ts.map +1 -1
  48. package/dist/engine/navigation.js +7 -48
  49. package/dist/engine/navigation.js.map +1 -1
  50. package/dist/engine/type-matching.d.ts.map +1 -1
  51. package/dist/engine/type-matching.js +6 -16
  52. package/dist/engine/type-matching.js.map +1 -1
  53. package/dist/index.d.ts +4 -3
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/parser/parser.d.ts.map +1 -1
  57. package/dist/parser/parser.js +102 -73
  58. package/dist/parser/parser.js.map +1 -1
  59. package/dist/parser/precedence.d.ts +368 -4
  60. package/dist/parser/precedence.d.ts.map +1 -1
  61. package/dist/parser/precedence.js +146 -32
  62. package/dist/parser/precedence.js.map +1 -1
  63. package/dist/r4/generated/type-maps.d.ts +1367 -0
  64. package/dist/r4/generated/type-maps.d.ts.map +1 -1
  65. package/dist/r4/index.d.ts +3 -1
  66. package/dist/r4/index.d.ts.map +1 -1
  67. package/dist/typed/context-maps.d.ts +23 -0
  68. package/dist/typed/context-maps.d.ts.map +1 -0
  69. package/dist/typed/context-maps.js +2 -0
  70. package/dist/typed/context-maps.js.map +1 -0
  71. package/dist/typed/infer.d.ts +99 -229
  72. package/dist/typed/infer.d.ts.map +1 -1
  73. package/dist/typed/infer.js +1 -84
  74. package/dist/typed/infer.js.map +1 -1
  75. package/dist/typed/inference-limits.d.ts +7 -0
  76. package/dist/typed/inference-limits.d.ts.map +1 -0
  77. package/dist/typed/inference-limits.js +6 -0
  78. package/dist/typed/inference-limits.js.map +1 -0
  79. package/dist/typed/metadata-compact.d.ts +189 -0
  80. package/dist/typed/metadata-compact.d.ts.map +1 -0
  81. package/dist/typed/metadata-compact.js +3 -0
  82. package/dist/typed/metadata-compact.js.map +1 -0
  83. package/dist/typed/parser.d.ts +515 -0
  84. package/dist/typed/parser.d.ts.map +1 -0
  85. package/dist/typed/parser.js +2 -0
  86. package/dist/typed/parser.js.map +1 -0
  87. package/dist/values/type-compat.d.ts +8 -0
  88. package/dist/values/type-compat.d.ts.map +1 -1
  89. package/dist/values/type-compat.js +44 -1
  90. package/dist/values/type-compat.js.map +1 -1
  91. package/package.json +20 -1
  92. package/src/analyzer/analyze-dto.ts +21 -17
  93. package/src/analyzer/analyze.ts +335 -109
  94. package/src/analyzer/declarations.ts +120 -0
  95. package/src/analyzer/operator-rules.ts +88 -0
  96. package/src/analyzer/signatures.ts +99 -41
  97. package/src/api/compile.ts +66 -11
  98. package/src/api/engine.ts +97 -33
  99. package/src/api/project.ts +54 -30
  100. package/src/api/strict.ts +112 -0
  101. package/src/api/tagged.ts +5 -5
  102. package/src/cli/dto-check.ts +7 -3
  103. package/src/engine/context.ts +7 -2
  104. package/src/engine/navigation.ts +7 -52
  105. package/src/engine/type-matching.ts +11 -17
  106. package/src/index.ts +25 -2
  107. package/src/parser/parser.ts +106 -74
  108. package/src/parser/precedence.ts +250 -33
  109. package/src/r4/generated/type-maps.ts +1557 -0
  110. package/src/typed/context-maps.ts +41 -0
  111. package/src/typed/infer.ts +157 -425
  112. package/src/typed/inference-limits.ts +9 -0
  113. package/src/typed/metadata-compact.ts +307 -0
  114. package/src/typed/parser.ts +2277 -0
  115. package/src/values/type-compat.ts +48 -1
  116. package/dist/typed/perf-fixture.types.d.ts +0 -136
  117. package/dist/typed/perf-fixture.types.d.ts.map +0 -1
  118. package/dist/typed/perf-fixture.types.js +0 -2
  119. package/dist/typed/perf-fixture.types.js.map +0 -1
  120. package/src/typed/perf-fixture.types.ts +0 -143
@@ -0,0 +1,41 @@
1
+ /** An empty host context that contributes no known keys. */
2
+ export type EmptyContextMap = Record<never, never>
3
+
4
+ /** Preserve host values for best-effort type inference without exposing them as declarations. */
5
+ export type HostValueDeclarations<Values> =
6
+ Values extends Readonly<Record<PropertyKey, unknown>>
7
+ ? { readonly [Name in keyof Values]: { readonly __value: Values[Name] } }
8
+ : EmptyContextMap
9
+
10
+ /** Environment names are stored without the optional leading `%`. */
11
+ export type BareContextName<Name extends PropertyKey> = Name extends string
12
+ ? Name extends `%${infer Bare}`
13
+ ? Bare
14
+ : Name
15
+ : Name
16
+
17
+ /** Normalize both accepted environment-key spellings into one type-level map. */
18
+ export type NormalizeContextMap<Map> =
19
+ Map extends Readonly<Record<PropertyKey, unknown>>
20
+ ? { readonly [Name in keyof Map as BareContextName<Name>]: Map[Name] }
21
+ : EmptyContextMap
22
+
23
+ /** Merge normalized maps by name, with the overlay taking precedence. */
24
+ export type MergeContextMaps<Base, Overlay> = Omit<NormalizeContextMap<Base>, keyof NormalizeContextMap<Overlay>> &
25
+ NormalizeContextMap<Overlay>
26
+
27
+ /** Infer undeclared host values while letting explicit declarations replace them. */
28
+ export type InferredHostValueDeclarations<Values, Declarations> =
29
+ keyof NormalizeContextMap<Values> extends keyof NormalizeContextMap<Declarations>
30
+ ? NormalizeContextMap<Declarations>
31
+ : MergeContextMaps<HostValueDeclarations<Values>, Declarations>
32
+
33
+ /** Read an optional context field without distributing undefined into its map. */
34
+ export type ContextProperty<Context, Name extends PropertyKey> = Name extends keyof Context
35
+ ? Exclude<Context[Name], undefined>
36
+ : EmptyContextMap
37
+
38
+ /** Look up one normalized name, returning never when it is undeclared. */
39
+ export type LookupContextMap<Map, Name extends string> = Name extends keyof NormalizeContextMap<Map>
40
+ ? NormalizeContextMap<Map>[Name]
41
+ : never
@@ -1,433 +1,165 @@
1
- import type { R4Bases, R4Elements, R4Resources, R4TypeOf } from '../r4/generated/type-maps.ts'
2
-
3
- /**
4
- * Type-level inference for common literal expressions: paths, indexes, choice
5
- * stems, selected collection functions, `select`, `ofType`, `as`, fixed-result
6
- * functions, unions, groups, and variable roots. Other expressions return
7
- * `unknown[]`. The runtime and analyzer support the full language.
8
- */
9
-
10
- /**
11
- * A type name the bound model knows — a resource or a datatype. What a DTO's
12
- * `fhirType` must be, and what `fhirpath`/`compile` accept as the type an
13
- * expression runs against. Lives here, with the rest of the inference vocabulary,
14
- * so every layer above spells it one way.
15
- */
1
+ import type { R4Resources, R4TypeOf } from '../r4/generated/type-maps.ts'
2
+ import type {
3
+ BareContextName,
4
+ ContextProperty,
5
+ EmptyContextMap,
6
+ InferredHostValueDeclarations,
7
+ MergeContextMaps,
8
+ NormalizeContextMap,
9
+ } from './context-maps.ts'
10
+ import type { InferTypeExpression } from './parser.ts'
11
+
12
+ /** A type name known by the generated R4 model. */
16
13
  export type FhirTypeName = keyof R4TypeOf & string
17
-
18
- /** Element lookup by name, walking base types. */
19
- type ElementInfo<T extends string, E extends string> = T extends keyof R4Elements
20
- ? E extends keyof R4Elements[T]
21
- ? R4Elements[T][E]
22
- : T extends keyof R4Bases
23
- ? R4Bases[T] extends string
24
- ? ElementInfo<R4Bases[T], E>
25
- : never
26
- : never
27
- : never
28
-
29
- /**
30
- * Characters that never appear in an element or variable name: their presence
31
- * in a segment means an operator or literal got glued on (`name and x`,
32
- * `gender=gender`), and the runtime evaluates something entirely different
33
- * from a navigation. Such segments must degrade to 'opaque' — the broad state
34
- * would let a later fixed-return call claim a concrete type for what is
35
- * really a comparison.
36
- */
37
- type GluedName<E extends string> = E extends
38
- | `${string} ${string}`
39
- | `${string}'${string}`
40
- | `${string}"${string}`
41
- | `${string}=${string}`
42
- | `${string}!${string}`
43
- | `${string}<${string}`
44
- | `${string}>${string}`
45
- | `${string}+${string}`
46
- | `${string}-${string}`
47
- | `${string}*${string}`
48
- | `${string}/${string}`
49
- | `${string}&${string}`
50
- | `${string}~${string}`
51
- | `${string},${string}`
52
- | `${string}|${string}`
53
- | `${string}%${string}`
54
- | `${string}$${string}`
55
- | `${string}@${string}`
56
- | `${string}(${string}`
57
- | `${string})${string}`
58
- | `${string}[${string}`
59
- | `${string}]${string}`
60
- | `${string}{${string}`
61
- | `${string}}${string}`
62
- | `${string}\\${string}`
63
- | `${string}\`${string}`
64
- ? true
65
- : false
66
-
67
- /**
68
- * Navigates one element. A missing normal name widens the state because the
69
- * runtime returns empty and a later fixed-result function still has a known
70
- * type. A segment containing expression syntax becomes `opaque` instead.
71
- */
72
- type Navigate<S extends string, E extends string> = string extends S
73
- ? MissedElement<E, S>
74
- : ElementInfo<S, E> extends { t: infer N extends string }
75
- ? string extends N
76
- ? // No element matched: `infer N` fell back to its `string` constraint
77
- // (a real element is always a literal name).
78
- MissedElement<E, string>
79
- : N
80
- : 'opaque'
81
-
82
- /**
83
- * An element lookup that found nothing: glued segments are misparses and go
84
- * 'opaque'; a sane-looking name widens to (or stays) the broad state. Only
85
- * misses pay the GluedName scan — real elements are never glued.
86
- */
87
- type MissedElement<E extends string, Broad extends string> = GluedName<E> extends true ? 'opaque' : Broad
88
-
89
- /**
90
- * Checks that a matched call ends at its final `)`. Quotes are removed before
91
- * balancing parentheses. Backslashes and backticks fall outside this subset.
92
- */
93
- type CleanArg<A extends string> = A extends `${string}\\${string}` | `${string}\`${string}`
94
- ? false
95
- : Balanced<StripQuoted<A>>
96
-
97
- /** Removes `'…'` spans so parens inside string literals don't disturb Balanced. */
98
- type StripQuoted<A extends string> = A extends `${infer L}'${string}'${infer R}` ? `${L}${StripQuoted<R>}` : A
99
-
100
- /** Every `(` opens before its `)` closes; one nesting level per pair. */
101
- type Balanced<A extends string> = A extends `${infer L}(${infer M})${infer R}`
102
- ? L extends `${string})${string}`
103
- ? false
104
- : M extends `${string}(${string}`
105
- ? false
106
- : Balanced<R>
107
- : A extends `${string})${string}`
108
- ? false
109
- : true
110
-
111
- /** Push one stack entry per `(` of A (quoted spans already stripped). */
112
- type PushOpens<A extends string, Acc extends unknown[]> = A extends `${string}(${infer R}`
113
- ? PushOpens<R, [...Acc, 0]>
114
- : Acc
115
-
116
- /** Checks full parenthesis balance after quoted spans have been removed. */
117
- type BalancedDeep<A extends string, Open extends unknown[] = []> = A extends `${infer Head})${infer Rest}`
118
- ? PushOpens<Head, Open> extends [unknown, ...infer Remaining]
119
- ? BalancedDeep<Rest, Remaining>
120
- : false
121
- : PushOpens<A, Open> extends []
122
- ? true
123
- : false
124
-
125
- /**
126
- * Whether a fragment is a complete sub-expression, so a `|` (or a group's `)`)
127
- * right after it is really at the top level: quotes pair up, and what remains
128
- * after stripping them balances with nothing left open. Backslashes and
129
- * backticks are declined outright, like CleanArg.
130
- */
131
- type CompleteFragment<A extends string> = A extends `${string}\\${string}` | `${string}\`${string}`
132
- ? false
133
- : StripQuoted<A> extends infer Q extends string
134
- ? Q extends `${string}'${string}`
135
- ? false
136
- : BalancedDeep<Q>
137
- : false
138
-
139
- /** Strip the leading/trailing spaces a union split leaves around its terms. */
140
- type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S extends `${infer L} ` ? Trim<L> : S
141
-
142
- /**
143
- * Functions with input-independent result types. A test compares this table
144
- * with analyzer signatures. Functions whose result depends on input stay out.
145
- */
146
- export const FIXED_RETURNS = {
147
- exists: 'boolean',
148
- empty: 'boolean',
149
- not: 'boolean',
150
- hasValue: 'boolean',
151
- count: 'integer',
152
- length: 'integer',
153
- toBoolean: 'boolean',
154
- convertsToBoolean: 'boolean',
155
- convertsToInteger: 'boolean',
156
- convertsToDecimal: 'boolean',
157
- convertsToString: 'boolean',
158
- convertsToDate: 'boolean',
159
- convertsToDateTime: 'boolean',
160
- convertsToTime: 'boolean',
161
- convertsToQuantity: 'boolean',
162
- toInteger: 'integer',
163
- toDecimal: 'decimal',
164
- toString: 'string',
165
- toDate: 'date',
166
- toDateTime: 'dateTime',
167
- toTime: 'time',
168
- toQuantity: 'Quantity',
169
- toChars: 'string',
170
- join: 'string',
171
- trim: 'string',
172
- upper: 'string',
173
- lower: 'string',
174
- replace: 'string',
175
- replaceMatches: 'string',
176
- substring: 'string',
177
- encode: 'string',
178
- decode: 'string',
179
- escape: 'string',
180
- unescape: 'string',
181
- split: 'string',
182
- matches: 'boolean',
183
- matchesFull: 'boolean',
184
- startsWith: 'boolean',
185
- endsWith: 'boolean',
186
- contains: 'boolean',
187
- subsetOf: 'boolean',
188
- supersetOf: 'boolean',
189
- isDistinct: 'boolean',
190
- allTrue: 'boolean',
191
- anyTrue: 'boolean',
192
- allFalse: 'boolean',
193
- anyFalse: 'boolean',
194
- all: 'boolean',
195
- indexOf: 'integer',
196
- lastIndexOf: 'integer',
197
- ceiling: 'integer',
198
- floor: 'integer',
199
- truncate: 'integer',
200
- round: 'decimal',
201
- sqrt: 'decimal',
202
- exp: 'decimal',
203
- ln: 'decimal',
204
- log: 'decimal',
205
- } as const satisfies Record<string, FhirTypeName>
206
-
207
- type FixedReturns = typeof FIXED_RETURNS
208
-
209
- /**
210
- * Functions that yield a subset or reordering of their input, so the input's
211
- * type carries through. Cross-checked against the analyzer the same way as
212
- * FIXED_RETURNS (their signatures preserve the input's types). `abs` is
213
- * excluded deliberately: it preserves the numeric kind at runtime, but the
214
- * analyzer declares its result unknown, and the table must not outrun it.
215
- */
216
- export const IDENTITY_RETURNS = [
217
- 'where',
218
- 'first',
219
- 'last',
220
- 'single',
221
- 'distinct',
222
- 'tail',
223
- 'skip',
224
- 'take',
225
- 'exclude',
226
- 'intersect',
227
- 'trace',
228
- ] as const
229
-
230
- type IdentityFn = (typeof IDENTITY_RETURNS)[number]
231
-
232
- /** One call segment, dispatched on the function name. */
233
- type Call<S extends string, Fn extends string, Arg extends string> = Fn extends IdentityFn
234
- ? S
235
- : Fn extends 'ofType' | 'as'
236
- ? Arg extends FhirTypeName
237
- ? Arg
238
- : 'opaque'
239
- : Fn extends keyof FixedReturns
240
- ? FixedReturns[Fn]
241
- : 'opaque'
242
-
243
- /** One `.`-separated segment: a call the subset knows, an indexer, or an element. */
244
- type Step<S extends string, Seg extends string> = [S] extends ['opaque']
245
- ? 'opaque'
246
- : Seg extends `${infer Fn}(${infer Arg})`
247
- ? Fn extends 'select'
248
- ? // A select argument is a sub-expression, so parse it before inference.
249
- ParseExpr<Arg, S>
250
- : CleanArg<Arg> extends true
251
- ? Call<S, Fn, Arg>
252
- : 'opaque'
253
- : Seg extends `${infer N}[${infer I}]`
254
- ? I extends `${string}]${string}` | `${string}[${string}`
255
- ? // A ']' or '[' inside the index means this "one indexer" spans an
256
- // operator (`family[0] | active[0]`): outside the subset.
257
- 'opaque'
258
- : Navigate<S, N>
259
- : Navigate<S, Seg>
260
-
261
- /** Walk the remaining `.`-separated segments. */
262
- type ParseSegments<Expr extends string, S extends string> = Expr extends ''
263
- ? S
264
- : Expr extends `${infer Head}.${infer Rest}`
265
- ? Head extends `${string}(` | `${string}(${string}`
266
- ? // A '.' inside parentheses split the segment: rejoin before stepping.
267
- StepAcrossParen<Expr, S>
268
- : ParseSegments<Rest, Step<S, Head>>
269
- : Step<S, Expr>
270
-
271
- /**
272
- * A segment whose parentheses contain dots (e.g. `select(name.given)`) runs
273
- * to the `)` that completes its argument; the `.` after that starts the next
274
- * segment. Scanned `).` by `).`: a candidate close inside a string literal,
275
- * or with parens still open, is not the close — CompleteFragment decides.
276
- * With no valid `).` split, the whole rest is one segment ending at its
277
- * final `)`.
278
- */
279
- type StepAcrossParen<Expr extends string, S extends string> = ScanSegmentEnd<Expr, '', S>
280
-
281
- type ScanSegmentEnd<
282
- Expr extends string,
283
- Acc extends string,
284
- S extends string,
285
- > = Expr extends `${infer Head}).${infer Rest}`
286
- ? SegmentComplete<`${Acc}${Head}`> extends true
287
- ? ParseSegments<Rest, Step<S, `${Acc}${Head})`>>
288
- : ScanSegmentEnd<Rest, `${Acc}${Head}).`, S>
289
- : WholeParenSegment<`${Acc}${Expr}`, S>
290
-
291
- /** Whether `Body)` is one complete call segment: its argument closes exactly there. */
292
- type SegmentComplete<Body extends string> = `${Body})` extends `${infer _Fn}(${infer Arg})`
293
- ? CompleteFragment<Arg>
294
- : false
295
-
296
- /** The rest of the expression is a single paren segment ending at its final `)`. */
297
- type WholeParenSegment<Expr extends string, S extends string> = Expr extends `${infer Head})${''}`
298
- ? Step<S, `${Head})`>
299
- : 'opaque'
300
-
301
- /**
302
- * One expression in a context state `S`: a `|`-union of terms, or a single
303
- * term. Plain dotted paths take two cheap checks: one skips the union scanner,
304
- * and the next skips trimming and group or `%var` handling.
305
- */
306
- type ParseExpr<Expr extends string, S extends string> = Expr extends `${string}|${string}` | `(${string}` | `%${string}`
307
- ? ParseUnion<Expr, S>
308
- : Expr extends `${infer Root}.${infer Rest}`
309
- ? Root extends keyof R4Resources & string
310
- ? ParseSegments<Rest, Root>
311
- : ParseSegments<Expr, S>
312
- : Expr extends keyof R4Resources & string
313
- ? Expr
314
- : ParseSegments<Expr, S>
315
-
316
- /**
317
- * Split off the leftmost top-level `|`. A candidate fragment whose quotes or
318
- * parens are cut mid-way (the `|` sat inside a literal or a group) is not a
319
- * split point: the fragment absorbs the `|` and the scan continues. With no
320
- * top-level `|` at all, the whole expression is one term — its own parse is
321
- * the validation, so this branch needs no separate completeness check.
322
- */
323
- type SplitUnion<Expr extends string, Acc extends string = ''> = Expr extends `${infer L}|${infer R}`
324
- ? CompleteFragment<`${Acc}${L}`> extends true
325
- ? { term: `${Acc}${L}`; rest: R }
326
- : SplitUnion<R, `${Acc}${L}|`>
327
- : { whole: `${Acc}${Expr}` }
328
-
329
- /** Fold the split terms into one union state. */
330
- type ParseUnion<Expr extends string, S extends string> =
331
- SplitUnion<Expr> extends { term: infer T extends string; rest: infer R extends string }
332
- ? UnionStates<ParseTerm<Trim<T>, S>, ParseUnion<R, S>>
333
- : SplitUnion<Expr> extends { whole: infer W extends string }
334
- ? ParseTerm<Trim<W>, S>
335
- : 'opaque'
336
-
337
- /**
338
- * The union of two term states. If EITHER side failed to parse, the whole
339
- * union is 'opaque' — a known name must never absorb a failed parse. A broad
340
- * member needs no special case: `string` swallows the other names, and
341
- * unknowable-joined-with-anything is unknowable.
342
- */
343
- type UnionStates<A extends string, B extends string> = [A] extends ['opaque']
344
- ? 'opaque'
345
- : [B] extends ['opaque']
346
- ? 'opaque'
347
- : A | B
348
-
349
- /**
350
- * One union term (or a whole single-term expression): a parenthesized group,
351
- * a `%var` root, a resource-rooted path, or a path relative to the context
352
- * state `S`. The top level passes S = 'opaque', so relative terms degrade
353
- * there; select() passes its input state, so its sub-paths resolve.
354
- */
355
- type ParseTerm<Term extends string, S extends string> = Term extends `(${infer Body}`
356
- ? ExtractGroup<Body, '', S>
357
- : Term extends `%${infer Var}`
358
- ? ParseVarTerm<Var>
359
- : Term extends `${infer Root}.${infer Rest}`
360
- ? Root extends keyof R4Resources & string
361
- ? ParseSegments<Rest, Root>
362
- : ParseSegments<Term, S>
363
- : Term extends keyof R4Resources & string
364
- ? Term
365
- : ParseSegments<Term, S>
366
-
367
- /**
368
- * A `%var` root enters the broad state: the variable's value is unknowable
369
- * here, and broad is exactly "unknowable" — plain navigation stays
370
- * `unknown[]`, while a fixed-return call (`%rowIndex.toString()`) keeps its
371
- * input-independent type. The name must look like a name: a glued operator
372
- * (`%a = b`) would make the runtime evaluate a comparison, not a variable.
373
- */
374
- type ParseVarTerm<Var extends string> = Var extends `${infer Name}.${infer Rest}`
375
- ? GluedName<Name> extends true
376
- ? 'opaque'
377
- : ParseSegments<Rest, string>
378
- : GluedName<Var> extends true
379
- ? 'opaque'
380
- : string
381
-
382
- /**
383
- * Find the `)` closing a group's opening paren: scan `)` by `)`, and the
384
- * first one whose interior is a complete fragment closes the group. What
385
- * follows must be nothing or a `.`-chain — anything else (`(A | B) = (C)`)
386
- * is a glued operator.
387
- */
388
- type ExtractGroup<Body extends string, Acc extends string, S extends string> = Body extends `${infer L})${infer R}`
389
- ? CompleteFragment<`${Acc}${L}`> extends true
390
- ? GroupTail<ParseExpr<`${Acc}${L}`, S>, R>
391
- : ExtractGroup<R, `${Acc}${L})`, S>
392
- : 'opaque'
393
-
394
- /** Continue after a group: `(…)` ends the term, `(…).rest` walks on from the group's state. */
395
- type GroupTail<G extends string, Tail extends string> = Tail extends ''
396
- ? G
397
- : Tail extends `.${infer Rest}`
398
- ? ParseSegments<Rest, G>
14
+ export type EmptyFhirpathTypeContext = EmptyContextMap
15
+
16
+ /** A host declaration that the type-level evaluator can use without reading a runtime value. */
17
+ export interface FhirpathTypeDeclaration<
18
+ Type extends FhirTypeName = FhirTypeName,
19
+ Collection extends boolean = boolean,
20
+ > {
21
+ /** One candidate type, or every candidate a value may hold. */
22
+ type: Type | readonly Type[]
23
+ /** Omitted means at most one item; true means the value may contain many. */
24
+ collection?: Collection
25
+ /** Resource targets when `type` includes Reference. */
26
+ targets?: FhirTypeName | readonly FhirTypeName[]
27
+ }
28
+
29
+ export type FhirpathTypeDeclarations = Readonly<Record<string, FhirpathTypeDeclaration>>
30
+
31
+ /** The static fields of a native, expression-defined, or overloaded custom function. */
32
+ export type FhirpathFunctionDeclaration =
33
+ | {
34
+ readonly signature?: {
35
+ readonly input?: { readonly types?: readonly string[] }
36
+ readonly args?: readonly string[]
37
+ readonly result?: { readonly types?: readonly string[]; readonly single?: boolean }
38
+ }
39
+ readonly expression?: string | { readonly source: string }
40
+ readonly criteria?: boolean
41
+ readonly env?: Readonly<Record<string, unknown>>
42
+ readonly envTypes?: FhirpathTypeDeclarations
43
+ }
44
+ | { readonly overloads: readonly FhirpathFunctionDeclaration[] }
45
+
46
+ /** Type information supplied by a host around a literal FHIRPath expression. */
47
+ export interface FhirpathTypeContext {
48
+ env?: FhirpathTypeDeclarations
49
+ vars?: FhirpathTypeDeclarations
50
+ /** The same declarations accepted by `EvaluateOptions.functions`. */
51
+ functions?: Readonly<Record<string, FhirpathFunctionDeclaration>>
52
+ }
53
+
54
+ /** The inferred result of evaluating a literal FHIRPath expression. */
55
+ export type FhirpathResult<
56
+ Expression extends string,
57
+ Context extends FhirpathTypeContext = EmptyFhirpathTypeContext,
58
+ > = FhirpathResultIn<Expression, 'opaque', Context>
59
+
60
+ /**
61
+ * The inferred result with an explicit FHIR input type. Non-literal,
62
+ * malformed, over-budget, or unsupported expressions safely become
63
+ * `unknown[]`.
64
+ */
65
+ export type FhirpathResultIn<
66
+ Expression extends string,
67
+ Input extends string,
68
+ Context extends FhirpathTypeContext = EmptyFhirpathTypeContext,
69
+ > = FhirpathResultForContext<Expression, Input, Context>
70
+
71
+ /** Internal inference entry point for contexts assembled from generic API options. */
72
+ export type FhirpathResultForContext<
73
+ Expression extends string,
74
+ Input extends string,
75
+ Context extends object = EmptyFhirpathTypeContext,
76
+ > = string extends Expression ? unknown[] : InferTypeExpression<Expression, Input, Context>
77
+
78
+ /** Merge contexts by normalized name. The later context wins, matching per-call runtime options. */
79
+ export type MergeFhirpathTypeContexts<Base extends object, Overlay extends object> = {
80
+ env: MergeContextMaps<ContextProperty<Base, 'env'>, ContextProperty<Overlay, 'env'>>
81
+ vars: MergeContextMaps<ContextProperty<Base, 'vars'>, ContextProperty<Overlay, 'vars'>>
82
+ functions: MergeContextMaps<ContextProperty<Base, 'functions'>, ContextProperty<Overlay, 'functions'>>
83
+ }
84
+
85
+ type LiteralVarDeclaration<Value> = Value extends string
86
+ ? string extends Value
87
+ ? FhirpathTypeDeclaration
88
+ : FhirpathTypeDeclaration & { readonly __expression: Value }
89
+ : Value extends { readonly source: infer Source extends string }
90
+ ? string extends Source
91
+ ? FhirpathTypeDeclaration
92
+ : FhirpathTypeDeclaration & { readonly __expression: Source }
93
+ : FhirpathTypeDeclaration
94
+
95
+ type LiteralVarDeclarations<Values> =
96
+ Values extends Readonly<Record<PropertyKey, unknown>>
97
+ ? { readonly [Name in keyof Values]: LiteralVarDeclaration<Values[Name]> }
98
+ : EmptyFhirpathTypeContext
99
+
100
+ type OptionEnvironmentContext<Options> = Options extends { readonly env?: infer Env }
101
+ ? InferredHostValueDeclarations<Exclude<Env, undefined>, ContextProperty<Options, 'envTypes'>>
102
+ : NormalizeContextMap<ContextProperty<Options, 'envTypes'>>
103
+
104
+ /** The inference context retained from one literal engine or per-call options object. */
105
+ export type FhirpathTypeContextOf<Options> = {
106
+ env: OptionEnvironmentContext<Options>
107
+ vars: MergeContextMaps<LiteralVarDeclarations<ContextProperty<Options, 'vars'>>, ContextProperty<Options, 'varTypes'>>
108
+ functions: NormalizeContextMap<ContextProperty<Options, 'functions'>>
109
+ }
110
+
111
+ type DeclarationElement<Declaration> = Declaration extends { readonly type: infer Type }
112
+ ? Type extends readonly FhirTypeName[]
113
+ ? R4TypeOf[Type[number]]
114
+ : Type extends FhirTypeName
115
+ ? R4TypeOf[Type]
116
+ : unknown
117
+ : unknown
118
+
119
+ type DeclaredHostValue<Declaration> = Declaration extends { readonly collection: true }
120
+ ? DeclarationElement<Declaration> | readonly DeclarationElement<Declaration>[] | undefined
121
+ : DeclarationElement<Declaration> | readonly [] | readonly [DeclarationElement<Declaration>] | undefined
122
+
123
+ type ConstrainedDeclaredValues<Values, Declarations> =
124
+ Values extends Readonly<Record<PropertyKey, unknown>>
125
+ ? {
126
+ [Name in keyof Values]: LookupNormalizedDeclaration<Declarations, Name> extends infer Declaration
127
+ ? [Declaration] extends [never]
128
+ ? Values[Name]
129
+ : Values[Name] extends DeclaredHostValue<Declaration>
130
+ ? Values[Name]
131
+ : never
132
+ : Values[Name]
133
+ }
134
+ : Values
135
+
136
+ type LookupNormalizedDeclaration<Declarations, Name extends PropertyKey> =
137
+ BareContextName<Name> extends keyof NormalizeContextMap<Declarations>
138
+ ? NormalizeContextMap<Declarations>[BareContextName<Name>]
139
+ : never
140
+
141
+ /** Cross-check declarations and values when both remain visible in one literal options object. */
142
+ export type CheckedFhirpathOptionValues<Options> = Options extends {
143
+ readonly env: infer Env
144
+ readonly envTypes: infer EnvTypes
145
+ }
146
+ ? { readonly env: ConstrainedDeclaredValues<Env, EnvTypes> }
147
+ : unknown
148
+
149
+ /** A model root known from a resource-shaped input; ambiguous and structural inputs stay opaque. */
150
+ export type FhirpathRootOf<Input> = Input extends readonly (infer Item)[]
151
+ ? FhirpathRootOf<Item>
152
+ : Input extends { readonly resourceType: infer Root extends FhirTypeName }
153
+ ? Root
399
154
  : 'opaque'
400
155
 
401
- /** The unwrapped result element type for a state; `[…]` keeps unions whole. */
402
- type ResultOf<S extends string> = [S] extends [keyof R4TypeOf] ? R4TypeOf[S][] : unknown[]
403
-
404
- /**
405
- * The inferred result of evaluating `Expr` against its root resource.
406
- * `string` (a non-literal expression) and anything outside the subset give
407
- * `unknown[]`. The context is 'opaque': terms must be resource-rooted,
408
- * `%var`-rooted, or parenthesized groups of those — a relative term
409
- * (`id | …`) has no root to resolve against here and degrades.
410
- */
411
- export type FhirpathResult<Expr extends string> = FhirpathResultIn<Expr, 'opaque'>
412
-
413
- /**
414
- * The inferred result of evaluating `Expr` with `Input` as the context type —
415
- * what a DTO's `fhirType` gives its columns, so their paths can stay relative
416
- * (`clinicalStatus.coding.first().code`). A resource-rooted term still resolves
417
- * against its own root, and a type name outside the model degrades like any
418
- * other out-of-subset expression.
419
- */
420
- export type FhirpathResultIn<Expr extends string, Input extends string> = string extends Expr
421
- ? unknown[]
422
- : ResultOf<ParseExpr<Expr, Input>>
423
-
424
- /** The expected input resource for `Expr` (`Patient.name` wants a Patient). */
425
- export type FhirpathInput<Expr extends string> = string extends Expr
156
+ /** The expected input resource for a resource-rooted literal expression. */
157
+ export type FhirpathInput<Expression extends string> = string extends Expression
426
158
  ? unknown
427
- : Expr extends `${infer Root}.${string}`
159
+ : Expression extends `${infer Root}.${string}`
428
160
  ? Root extends keyof R4Resources
429
161
  ? R4Resources[Root]
430
162
  : unknown
431
- : Expr extends keyof R4Resources
432
- ? R4Resources[Expr]
163
+ : Expression extends keyof R4Resources
164
+ ? R4Resources[Expression]
433
165
  : unknown
@@ -0,0 +1,9 @@
1
+ /* v8 ignore file -- build-time constants consumed by the type parser and generation scripts */
2
+ /** Maximum tokens the type-level scanner emits for one literal expression. */
3
+ export const INFERENCE_TOKEN_LIMIT = 64
4
+
5
+ /** Maximum source characters the type-level scanner visits for one literal expression. */
6
+ export const INFERENCE_SOURCE_STEP_LIMIT = 256
7
+
8
+ export type InferenceTokenLimit = typeof INFERENCE_TOKEN_LIMIT
9
+ export type InferenceSourceStepLimit = typeof INFERENCE_SOURCE_STEP_LIMIT