fhirpath-ts 0.0.0-bootstrap.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 (414) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +391 -0
  3. package/THIRD-PARTY-NOTICES.md +296 -0
  4. package/dist/analyzer/analyze-dto.d.ts +45 -0
  5. package/dist/analyzer/analyze-dto.d.ts.map +1 -0
  6. package/dist/analyzer/analyze-dto.js +147 -0
  7. package/dist/analyzer/analyze-dto.js.map +1 -0
  8. package/dist/analyzer/analyze.d.ts +98 -0
  9. package/dist/analyzer/analyze.d.ts.map +1 -0
  10. package/dist/analyzer/analyze.js +925 -0
  11. package/dist/analyzer/analyze.js.map +1 -0
  12. package/dist/analyzer/expression-policy.d.ts +210 -0
  13. package/dist/analyzer/expression-policy.d.ts.map +1 -0
  14. package/dist/analyzer/expression-policy.js +254 -0
  15. package/dist/analyzer/expression-policy.js.map +1 -0
  16. package/dist/analyzer/index.d.ts +3 -0
  17. package/dist/analyzer/index.d.ts.map +1 -0
  18. package/dist/analyzer/index.js +6 -0
  19. package/dist/analyzer/index.js.map +1 -0
  20. package/dist/analyzer/regex-safety.d.ts +10 -0
  21. package/dist/analyzer/regex-safety.d.ts.map +1 -0
  22. package/dist/analyzer/regex-safety.js +84 -0
  23. package/dist/analyzer/regex-safety.js.map +1 -0
  24. package/dist/analyzer/signatures.d.ts +72 -0
  25. package/dist/analyzer/signatures.d.ts.map +1 -0
  26. package/dist/analyzer/signatures.js +195 -0
  27. package/dist/analyzer/signatures.js.map +1 -0
  28. package/dist/api/bundle.d.ts +29 -0
  29. package/dist/api/bundle.d.ts.map +1 -0
  30. package/dist/api/bundle.js +108 -0
  31. package/dist/api/bundle.js.map +1 -0
  32. package/dist/api/cache.d.ts +10 -0
  33. package/dist/api/cache.d.ts.map +1 -0
  34. package/dist/api/cache.js +38 -0
  35. package/dist/api/cache.js.map +1 -0
  36. package/dist/api/column-signature.d.ts +53 -0
  37. package/dist/api/column-signature.d.ts.map +1 -0
  38. package/dist/api/column-signature.js +43 -0
  39. package/dist/api/column-signature.js.map +1 -0
  40. package/dist/api/compile.d.ts +120 -0
  41. package/dist/api/compile.d.ts.map +1 -0
  42. package/dist/api/compile.js +167 -0
  43. package/dist/api/compile.js.map +1 -0
  44. package/dist/api/constraints.d.ts +48 -0
  45. package/dist/api/constraints.d.ts.map +1 -0
  46. package/dist/api/constraints.js +52 -0
  47. package/dist/api/constraints.js.map +1 -0
  48. package/dist/api/dto.d.ts +134 -0
  49. package/dist/api/dto.d.ts.map +1 -0
  50. package/dist/api/dto.js +295 -0
  51. package/dist/api/dto.js.map +1 -0
  52. package/dist/api/engine.d.ts +136 -0
  53. package/dist/api/engine.d.ts.map +1 -0
  54. package/dist/api/engine.js +183 -0
  55. package/dist/api/engine.js.map +1 -0
  56. package/dist/api/evaluate.d.ts +7 -0
  57. package/dist/api/evaluate.d.ts.map +1 -0
  58. package/dist/api/evaluate.js +10 -0
  59. package/dist/api/evaluate.js.map +1 -0
  60. package/dist/api/project.d.ts +89 -0
  61. package/dist/api/project.d.ts.map +1 -0
  62. package/dist/api/project.js +150 -0
  63. package/dist/api/project.js.map +1 -0
  64. package/dist/api/tagged.d.ts +14 -0
  65. package/dist/api/tagged.d.ts.map +1 -0
  66. package/dist/api/tagged.js +13 -0
  67. package/dist/api/tagged.js.map +1 -0
  68. package/dist/cli/dto-check.d.ts +25 -0
  69. package/dist/cli/dto-check.d.ts.map +1 -0
  70. package/dist/cli/dto-check.js +77 -0
  71. package/dist/cli/dto-check.js.map +1 -0
  72. package/dist/cli/fhirpath-check.d.ts +3 -0
  73. package/dist/cli/fhirpath-check.d.ts.map +1 -0
  74. package/dist/cli/fhirpath-check.js +149 -0
  75. package/dist/cli/fhirpath-check.js.map +1 -0
  76. package/dist/cli/ts-loader.d.mts +3 -0
  77. package/dist/cli/ts-loader.d.mts.map +1 -0
  78. package/dist/cli/ts-loader.mjs +48 -0
  79. package/dist/cli/ts-loader.mjs.map +1 -0
  80. package/dist/engine/context.d.ts +173 -0
  81. package/dist/engine/context.d.ts.map +1 -0
  82. package/dist/engine/context.js +118 -0
  83. package/dist/engine/context.js.map +1 -0
  84. package/dist/engine/evaluator.d.ts +7 -0
  85. package/dist/engine/evaluator.d.ts.map +1 -0
  86. package/dist/engine/evaluator.js +196 -0
  87. package/dist/engine/evaluator.js.map +1 -0
  88. package/dist/engine/navigation.d.ts +11 -0
  89. package/dist/engine/navigation.d.ts.map +1 -0
  90. package/dist/engine/navigation.js +102 -0
  91. package/dist/engine/navigation.js.map +1 -0
  92. package/dist/engine/operators/collections.d.ts +12 -0
  93. package/dist/engine/operators/collections.d.ts.map +1 -0
  94. package/dist/engine/operators/collections.js +31 -0
  95. package/dist/engine/operators/collections.js.map +1 -0
  96. package/dist/engine/operators/comparison.d.ts +13 -0
  97. package/dist/engine/operators/comparison.d.ts.map +1 -0
  98. package/dist/engine/operators/comparison.js +77 -0
  99. package/dist/engine/operators/comparison.js.map +1 -0
  100. package/dist/engine/operators/equality.d.ts +19 -0
  101. package/dist/engine/operators/equality.d.ts.map +1 -0
  102. package/dist/engine/operators/equality.js +203 -0
  103. package/dist/engine/operators/equality.js.map +1 -0
  104. package/dist/engine/operators/index.d.ts +18 -0
  105. package/dist/engine/operators/index.d.ts.map +1 -0
  106. package/dist/engine/operators/index.js +30 -0
  107. package/dist/engine/operators/index.js.map +1 -0
  108. package/dist/engine/operators/logic.d.ts +8 -0
  109. package/dist/engine/operators/logic.d.ts.map +1 -0
  110. package/dist/engine/operators/logic.js +38 -0
  111. package/dist/engine/operators/logic.js.map +1 -0
  112. package/dist/engine/operators/math.d.ts +13 -0
  113. package/dist/engine/operators/math.d.ts.map +1 -0
  114. package/dist/engine/operators/math.js +198 -0
  115. package/dist/engine/operators/math.js.map +1 -0
  116. package/dist/engine/operators/strings.d.ts +8 -0
  117. package/dist/engine/operators/strings.d.ts.map +1 -0
  118. package/dist/engine/operators/strings.js +22 -0
  119. package/dist/engine/operators/strings.js.map +1 -0
  120. package/dist/engine/operators/types.d.ts +3 -0
  121. package/dist/engine/operators/types.d.ts.map +1 -0
  122. package/dist/engine/operators/types.js +15 -0
  123. package/dist/engine/operators/types.js.map +1 -0
  124. package/dist/engine/type-matching.d.ts +20 -0
  125. package/dist/engine/type-matching.d.ts.map +1 -0
  126. package/dist/engine/type-matching.js +122 -0
  127. package/dist/engine/type-matching.js.map +1 -0
  128. package/dist/errors.d.ts +25 -0
  129. package/dist/errors.d.ts.map +1 -0
  130. package/dist/errors.js +21 -0
  131. package/dist/errors.js.map +1 -0
  132. package/dist/eslint/index.d.ts +12 -0
  133. package/dist/eslint/index.d.ts.map +1 -0
  134. package/dist/eslint/index.js +316 -0
  135. package/dist/eslint/index.js.map +1 -0
  136. package/dist/fhir/html-checks.d.ts +7 -0
  137. package/dist/fhir/html-checks.d.ts.map +1 -0
  138. package/dist/fhir/html-checks.js +256 -0
  139. package/dist/fhir/html-checks.js.map +1 -0
  140. package/dist/fhir/model-navigation.d.ts +11 -0
  141. package/dist/fhir/model-navigation.d.ts.map +1 -0
  142. package/dist/fhir/model-navigation.js +142 -0
  143. package/dist/fhir/model-navigation.js.map +1 -0
  144. package/dist/functions/aggregate.d.ts +2 -0
  145. package/dist/functions/aggregate.d.ts.map +1 -0
  146. package/dist/functions/aggregate.js +127 -0
  147. package/dist/functions/aggregate.js.map +1 -0
  148. package/dist/functions/boundary.d.ts +2 -0
  149. package/dist/functions/boundary.d.ts.map +1 -0
  150. package/dist/functions/boundary.js +189 -0
  151. package/dist/functions/boundary.js.map +1 -0
  152. package/dist/functions/combining.d.ts +2 -0
  153. package/dist/functions/combining.d.ts.map +1 -0
  154. package/dist/functions/combining.js +13 -0
  155. package/dist/functions/combining.js.map +1 -0
  156. package/dist/functions/control.d.ts +2 -0
  157. package/dist/functions/control.d.ts.map +1 -0
  158. package/dist/functions/control.js +25 -0
  159. package/dist/functions/control.js.map +1 -0
  160. package/dist/functions/conversion.d.ts +3 -0
  161. package/dist/functions/conversion.d.ts.map +1 -0
  162. package/dist/functions/conversion.js +316 -0
  163. package/dist/functions/conversion.js.map +1 -0
  164. package/dist/functions/date-components.d.ts +2 -0
  165. package/dist/functions/date-components.d.ts.map +1 -0
  166. package/dist/functions/date-components.js +81 -0
  167. package/dist/functions/date-components.js.map +1 -0
  168. package/dist/functions/existence.d.ts +4 -0
  169. package/dist/functions/existence.d.ts.map +1 -0
  170. package/dist/functions/existence.js +97 -0
  171. package/dist/functions/existence.js.map +1 -0
  172. package/dist/functions/fhir.d.ts +2 -0
  173. package/dist/functions/fhir.d.ts.map +1 -0
  174. package/dist/functions/fhir.js +222 -0
  175. package/dist/functions/fhir.js.map +1 -0
  176. package/dist/functions/filtering.d.ts +2 -0
  177. package/dist/functions/filtering.d.ts.map +1 -0
  178. package/dist/functions/filtering.js +112 -0
  179. package/dist/functions/filtering.js.map +1 -0
  180. package/dist/functions/install.d.ts +18 -0
  181. package/dist/functions/install.d.ts.map +1 -0
  182. package/dist/functions/install.js +20 -0
  183. package/dist/functions/install.js.map +1 -0
  184. package/dist/functions/iteration.d.ts +7 -0
  185. package/dist/functions/iteration.d.ts.map +1 -0
  186. package/dist/functions/iteration.js +9 -0
  187. package/dist/functions/iteration.js.map +1 -0
  188. package/dist/functions/math.d.ts +2 -0
  189. package/dist/functions/math.d.ts.map +1 -0
  190. package/dist/functions/math.js +152 -0
  191. package/dist/functions/math.js.map +1 -0
  192. package/dist/functions/reflection.d.ts +2 -0
  193. package/dist/functions/reflection.d.ts.map +1 -0
  194. package/dist/functions/reflection.js +23 -0
  195. package/dist/functions/reflection.js.map +1 -0
  196. package/dist/functions/registry.d.ts +28 -0
  197. package/dist/functions/registry.d.ts.map +1 -0
  198. package/dist/functions/registry.js +46 -0
  199. package/dist/functions/registry.js.map +1 -0
  200. package/dist/functions/sort.d.ts +2 -0
  201. package/dist/functions/sort.d.ts.map +1 -0
  202. package/dist/functions/sort.js +48 -0
  203. package/dist/functions/sort.js.map +1 -0
  204. package/dist/functions/string.d.ts +2 -0
  205. package/dist/functions/string.d.ts.map +1 -0
  206. package/dist/functions/string.js +353 -0
  207. package/dist/functions/string.js.map +1 -0
  208. package/dist/functions/subsetting.d.ts +2 -0
  209. package/dist/functions/subsetting.d.ts.map +1 -0
  210. package/dist/functions/subsetting.js +72 -0
  211. package/dist/functions/subsetting.js.map +1 -0
  212. package/dist/functions/tree.d.ts +8 -0
  213. package/dist/functions/tree.d.ts.map +1 -0
  214. package/dist/functions/tree.js +109 -0
  215. package/dist/functions/tree.js.map +1 -0
  216. package/dist/functions/type-specifier.d.ts +8 -0
  217. package/dist/functions/type-specifier.d.ts.map +1 -0
  218. package/dist/functions/type-specifier.js +27 -0
  219. package/dist/functions/type-specifier.js.map +1 -0
  220. package/dist/functions/utility.d.ts +2 -0
  221. package/dist/functions/utility.d.ts.map +1 -0
  222. package/dist/functions/utility.js +76 -0
  223. package/dist/functions/utility.js.map +1 -0
  224. package/dist/functions/variables.d.ts +2 -0
  225. package/dist/functions/variables.d.ts.map +1 -0
  226. package/dist/functions/variables.js +35 -0
  227. package/dist/functions/variables.js.map +1 -0
  228. package/dist/index.d.ts +25 -0
  229. package/dist/index.d.ts.map +1 -0
  230. package/dist/index.js +11 -0
  231. package/dist/index.js.map +1 -0
  232. package/dist/lexer/lexer.d.ts +4 -0
  233. package/dist/lexer/lexer.d.ts.map +1 -0
  234. package/dist/lexer/lexer.js +318 -0
  235. package/dist/lexer/lexer.js.map +1 -0
  236. package/dist/lexer/tokens.d.ts +23 -0
  237. package/dist/lexer/tokens.d.ts.map +1 -0
  238. package/dist/lexer/tokens.js +40 -0
  239. package/dist/lexer/tokens.js.map +1 -0
  240. package/dist/model/provider.d.ts +36 -0
  241. package/dist/model/provider.d.ts.map +1 -0
  242. package/dist/model/provider.js +2 -0
  243. package/dist/model/provider.js.map +1 -0
  244. package/dist/parser/ast.d.ts +104 -0
  245. package/dist/parser/ast.d.ts.map +1 -0
  246. package/dist/parser/ast.js +2 -0
  247. package/dist/parser/ast.js.map +1 -0
  248. package/dist/parser/parser.d.ts +4 -0
  249. package/dist/parser/parser.d.ts.map +1 -0
  250. package/dist/parser/parser.js +299 -0
  251. package/dist/parser/parser.js.map +1 -0
  252. package/dist/parser/precedence.d.ts +24 -0
  253. package/dist/parser/precedence.d.ts.map +1 -0
  254. package/dist/parser/precedence.js +52 -0
  255. package/dist/parser/precedence.js.map +1 -0
  256. package/dist/parser/printer.d.ts +4 -0
  257. package/dist/parser/printer.d.ts.map +1 -0
  258. package/dist/parser/printer.js +112 -0
  259. package/dist/parser/printer.js.map +1 -0
  260. package/dist/r4/generated/resources-data.d.ts +3 -0
  261. package/dist/r4/generated/resources-data.d.ts.map +1 -0
  262. package/dist/r4/generated/resources-data.js +28628 -0
  263. package/dist/r4/generated/resources-data.js.map +1 -0
  264. package/dist/r4/generated/type-maps.d.ts +43153 -0
  265. package/dist/r4/generated/type-maps.d.ts.map +1 -0
  266. package/dist/r4/generated/type-maps.js +4 -0
  267. package/dist/r4/generated/type-maps.js.map +1 -0
  268. package/dist/r4/generated/types-data.d.ts +3 -0
  269. package/dist/r4/generated/types-data.d.ts.map +1 -0
  270. package/dist/r4/generated/types-data.js +2205 -0
  271. package/dist/r4/generated/types-data.js.map +1 -0
  272. package/dist/r4/index.d.ts +9 -0
  273. package/dist/r4/index.d.ts.map +1 -0
  274. package/dist/r4/index.js +94 -0
  275. package/dist/r4/index.js.map +1 -0
  276. package/dist/r4/model-data.d.ts +17 -0
  277. package/dist/r4/model-data.d.ts.map +1 -0
  278. package/dist/r4/model-data.js +2 -0
  279. package/dist/r4/model-data.js.map +1 -0
  280. package/dist/sites/index.d.ts +31 -0
  281. package/dist/sites/index.d.ts.map +1 -0
  282. package/dist/sites/index.js +205 -0
  283. package/dist/sites/index.js.map +1 -0
  284. package/dist/typed/infer.d.ts +234 -0
  285. package/dist/typed/infer.d.ts.map +1 -0
  286. package/dist/typed/infer.js +85 -0
  287. package/dist/typed/infer.js.map +1 -0
  288. package/dist/typed/perf-fixture.types.d.ts +136 -0
  289. package/dist/typed/perf-fixture.types.d.ts.map +1 -0
  290. package/dist/typed/perf-fixture.types.js +2 -0
  291. package/dist/typed/perf-fixture.types.js.map +1 -0
  292. package/dist/values/collection.d.ts +24 -0
  293. package/dist/values/collection.d.ts.map +1 -0
  294. package/dist/values/collection.js +51 -0
  295. package/dist/values/collection.js.map +1 -0
  296. package/dist/values/datetime.d.ts +54 -0
  297. package/dist/values/datetime.d.ts.map +1 -0
  298. package/dist/values/datetime.js +226 -0
  299. package/dist/values/datetime.js.map +1 -0
  300. package/dist/values/decimal.d.ts +48 -0
  301. package/dist/values/decimal.d.ts.map +1 -0
  302. package/dist/values/decimal.js +188 -0
  303. package/dist/values/decimal.js.map +1 -0
  304. package/dist/values/numeric.d.ts +24 -0
  305. package/dist/values/numeric.d.ts.map +1 -0
  306. package/dist/values/numeric.js +53 -0
  307. package/dist/values/numeric.js.map +1 -0
  308. package/dist/values/quantity.d.ts +49 -0
  309. package/dist/values/quantity.d.ts.map +1 -0
  310. package/dist/values/quantity.js +297 -0
  311. package/dist/values/quantity.js.map +1 -0
  312. package/dist/values/temporal-arithmetic.d.ts +17 -0
  313. package/dist/values/temporal-arithmetic.d.ts.map +1 -0
  314. package/dist/values/temporal-arithmetic.js +183 -0
  315. package/dist/values/temporal-arithmetic.js.map +1 -0
  316. package/dist/values/temporal-compare.d.ts +9 -0
  317. package/dist/values/temporal-compare.d.ts.map +1 -0
  318. package/dist/values/temporal-compare.js +59 -0
  319. package/dist/values/temporal-compare.js.map +1 -0
  320. package/dist/values/type-compat.d.ts +47 -0
  321. package/dist/values/type-compat.d.ts.map +1 -0
  322. package/dist/values/type-compat.js +100 -0
  323. package/dist/values/type-compat.js.map +1 -0
  324. package/dist/values/typed-value.d.ts +48 -0
  325. package/dist/values/typed-value.d.ts.map +1 -0
  326. package/dist/values/typed-value.js +117 -0
  327. package/dist/values/typed-value.js.map +1 -0
  328. package/dist/values/ucum.d.ts +14 -0
  329. package/dist/values/ucum.d.ts.map +1 -0
  330. package/dist/values/ucum.js +146 -0
  331. package/dist/values/ucum.js.map +1 -0
  332. package/package.json +137 -0
  333. package/src/analyzer/analyze-dto.ts +195 -0
  334. package/src/analyzer/analyze.ts +1181 -0
  335. package/src/analyzer/expression-policy.ts +434 -0
  336. package/src/analyzer/index.ts +23 -0
  337. package/src/analyzer/regex-safety.ts +85 -0
  338. package/src/analyzer/signatures.ts +260 -0
  339. package/src/api/bundle.ts +132 -0
  340. package/src/api/cache.ts +40 -0
  341. package/src/api/column-signature.ts +71 -0
  342. package/src/api/compile.ts +308 -0
  343. package/src/api/constraints.ts +104 -0
  344. package/src/api/dto.ts +445 -0
  345. package/src/api/engine.ts +328 -0
  346. package/src/api/evaluate.ts +11 -0
  347. package/src/api/project.ts +255 -0
  348. package/src/api/tagged.ts +35 -0
  349. package/src/cli/dto-check.ts +94 -0
  350. package/src/cli/fhirpath-check.ts +168 -0
  351. package/src/cli/ts-loader.mjs +49 -0
  352. package/src/engine/context.ts +282 -0
  353. package/src/engine/evaluator.ts +246 -0
  354. package/src/engine/navigation.ts +106 -0
  355. package/src/engine/operators/collections.ts +34 -0
  356. package/src/engine/operators/comparison.ts +82 -0
  357. package/src/engine/operators/equality.ts +232 -0
  358. package/src/engine/operators/index.ts +65 -0
  359. package/src/engine/operators/logic.ts +53 -0
  360. package/src/engine/operators/math.ts +225 -0
  361. package/src/engine/operators/strings.ts +25 -0
  362. package/src/engine/operators/types.ts +16 -0
  363. package/src/engine/type-matching.ts +148 -0
  364. package/src/errors.ts +33 -0
  365. package/src/eslint/index.ts +388 -0
  366. package/src/fhir/html-checks.ts +266 -0
  367. package/src/fhir/model-navigation.ts +148 -0
  368. package/src/functions/aggregate.ts +136 -0
  369. package/src/functions/boundary.ts +209 -0
  370. package/src/functions/combining.ts +15 -0
  371. package/src/functions/control.ts +25 -0
  372. package/src/functions/conversion.ts +349 -0
  373. package/src/functions/date-components.ts +88 -0
  374. package/src/functions/existence.ts +107 -0
  375. package/src/functions/fhir.ts +247 -0
  376. package/src/functions/filtering.ts +120 -0
  377. package/src/functions/install.ts +19 -0
  378. package/src/functions/iteration.ts +21 -0
  379. package/src/functions/math.ts +167 -0
  380. package/src/functions/reflection.ts +24 -0
  381. package/src/functions/registry.ts +72 -0
  382. package/src/functions/sort.ts +68 -0
  383. package/src/functions/string.ts +413 -0
  384. package/src/functions/subsetting.ts +89 -0
  385. package/src/functions/tree.ts +112 -0
  386. package/src/functions/type-specifier.ts +29 -0
  387. package/src/functions/utility.ts +82 -0
  388. package/src/functions/variables.ts +36 -0
  389. package/src/index.ts +30 -0
  390. package/src/lexer/lexer.ts +341 -0
  391. package/src/lexer/tokens.ts +69 -0
  392. package/src/model/provider.ts +36 -0
  393. package/src/parser/ast.ts +164 -0
  394. package/src/parser/parser.ts +321 -0
  395. package/src/parser/precedence.ts +52 -0
  396. package/src/parser/printer.ts +114 -0
  397. package/src/r4/generated/resources-data.ts +28631 -0
  398. package/src/r4/generated/type-maps.ts +22145 -0
  399. package/src/r4/generated/types-data.ts +2208 -0
  400. package/src/r4/index.ts +107 -0
  401. package/src/r4/model-data.ts +17 -0
  402. package/src/sites/index.ts +285 -0
  403. package/src/typed/infer.ts +433 -0
  404. package/src/typed/perf-fixture.types.ts +143 -0
  405. package/src/values/collection.ts +57 -0
  406. package/src/values/datetime.ts +285 -0
  407. package/src/values/decimal.ts +213 -0
  408. package/src/values/numeric.ts +63 -0
  409. package/src/values/quantity.ts +358 -0
  410. package/src/values/temporal-arithmetic.ts +231 -0
  411. package/src/values/temporal-compare.ts +65 -0
  412. package/src/values/type-compat.ts +133 -0
  413. package/src/values/typed-value.ts +151 -0
  414. package/src/values/ucum.ts +181 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Imports DTO modules, records the engines they create, and checks each exported
3
+ * DTO with loaded runtime context. The default module pattern is `*.dto.ts`.
4
+ * Importing runs module initialization; `--no-import` skips this pass.
5
+ */
6
+ /* v8 ignore file -- covered end-to-end as a subprocess in fhirpath-check.test.ts, which is the only way to exercise a module loader and engine discovery in a fresh process */
7
+ import { glob } from 'node:fs/promises'
8
+ import { register } from 'node:module'
9
+ import { relative } from 'node:path'
10
+ import { pathToFileURL } from 'node:url'
11
+
12
+ import { type AnalyzedContext, analyzeDto, type DtoDiagnostic } from '../analyzer/analyze-dto.ts'
13
+ import { type DtoClass, isDtoClass } from '../api/dto.ts'
14
+ import { type FhirPathEngine, recordEngines } from '../api/engine.ts'
15
+
16
+ /** Where DTO classes live unless `--dtos` says otherwise. */
17
+ export const DEFAULT_DTO_GLOB = '**/*.dto.ts'
18
+
19
+ const IGNORED = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**']
20
+
21
+ /** One finding, with the DTO and file it belongs to and the expression that produced it. */
22
+ export interface DtoFinding extends DtoDiagnostic {
23
+ /** The DTO class name. */
24
+ dto: string
25
+ /** The module the DTO was imported from, relative to the working directory. */
26
+ file: string
27
+ }
28
+
29
+ export interface DtoCheckResult {
30
+ findings: DtoFinding[]
31
+ /** Modules that matched and were imported, relative to the working directory. */
32
+ files: string[]
33
+ /** How many engines the imported modules constructed. */
34
+ engines: number
35
+ /** Every DTO analyzed, by module. */
36
+ dtos: { file: string; dto: string }[]
37
+ }
38
+
39
+ /** Imports matching modules and checks their exported DTOs against recorded engines. */
40
+ export async function checkDtoModules(patterns: readonly string[], cwd: string): Promise<DtoCheckResult> {
41
+ const recorded = recordEngines()
42
+ register(new URL('ts-loader.mjs', import.meta.url))
43
+ const files: string[] = []
44
+ const dtos: { file: string; dto: string; cls: DtoClass }[] = []
45
+ for await (const match of glob(patterns.length > 0 ? [...patterns] : [DEFAULT_DTO_GLOB], { cwd, exclude: IGNORED })) {
46
+ const file = relative(cwd, new URL(match, pathToFileURL(`${cwd}/`)).pathname)
47
+ files.push(file)
48
+ const module: Record<string, unknown> = await import(pathToFileURL(`${cwd}/${match}`).href)
49
+ for (const [name, value] of Object.entries(module)) {
50
+ if (isDtoClass(value)) {
51
+ dtos.push({ file, dto: value.name || name, cls: value })
52
+ }
53
+ }
54
+ }
55
+ const engines = recorded()
56
+ const findings = dtos.flatMap(({ file, dto, cls }) =>
57
+ analyzeFor(cls, engines).map(finding => ({ ...finding, dto, file }))
58
+ )
59
+ return { findings, files, engines: engines.length, dtos: dtos.map(({ file, dto }) => ({ file, dto })) }
60
+ }
61
+
62
+ /**
63
+ * A DTO against the engine that registered it, or — when no engine claims it, as
64
+ * for a row shape that is only ever projected — against everything the project's
65
+ * engines make available. A DTO checked against one engine of several would
66
+ * report calls that resolve perfectly well in the engine it actually runs on, and
67
+ * "some engine in this project declares this" is the most that can be said
68
+ * without being told which. Merging says exactly that, once, in engine order.
69
+ */
70
+ function analyzeFor(dto: DtoClass, engines: readonly FhirPathEngine[]): DtoDiagnostic[] {
71
+ const owner = engines.find(engine => engine.dtos.includes(dto))
72
+ if (owner !== undefined) {
73
+ return analyzeDto(dto, { engine: owner })
74
+ }
75
+ if (engines.length === 0) {
76
+ return analyzeDto(dto)
77
+ }
78
+ return analyzeDto(dto, { engine: merged(engines) })
79
+ }
80
+
81
+ /**
82
+ * Every engine's context as one: the union of their registered functions and env
83
+ * names. The first engine's `model` stands for all of them — a project binds one
84
+ * FHIR version, and an unregistered DTO names no engine that could pick another.
85
+ */
86
+ function merged(engines: readonly FhirPathEngine[]): AnalyzedContext {
87
+ return {
88
+ defaults: {
89
+ ...engines[0]?.defaults,
90
+ functions: Object.assign({}, ...engines.map(engine => engine.defaults.functions)),
91
+ env: Object.assign({}, ...engines.map(engine => engine.defaults.env)),
92
+ },
93
+ }
94
+ }
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Checks source literals with the TypeScript site finder, then imports DTO
4
+ * modules for checks that need their engine context. Errors produce a non-zero
5
+ * exit status. Use `--no-import` for source only and `--dtos` to change the DTO
6
+ * module glob.
7
+ *
8
+ * Usage: fhirpath-check [--dtos <glob>]... [--no-import] <file...>
9
+ */
10
+ /* v8 ignore file -- covered end-to-end as a subprocess in fhirpath-check.test.ts */
11
+ import { readFileSync } from 'node:fs'
12
+
13
+ import ts from 'typescript'
14
+
15
+ import { analyzeSite } from '../analyzer/analyze.ts'
16
+ import { r4Model } from '../r4/index.ts'
17
+ import { createSiteFinder, type ExpressionSite } from '../sites/index.ts'
18
+ import { checkDtoModules, DEFAULT_DTO_GLOB, type DtoFinding } from './dto-check.ts'
19
+
20
+ const findExpressionSites = createSiteFinder(ts)
21
+
22
+ interface Args {
23
+ files: string[]
24
+ dtoGlobs: string[]
25
+ imports: boolean
26
+ }
27
+
28
+ function parseArgs(argv: readonly string[]): Args {
29
+ const args: Args = { files: [], dtoGlobs: [], imports: true }
30
+ for (let index = 0; index < argv.length; index++) {
31
+ const arg = argv[index]!
32
+ if (arg === '--no-import') {
33
+ args.imports = false
34
+ } else if (arg === '--dtos') {
35
+ const glob = argv[++index]
36
+ if (glob === undefined) {
37
+ console.error('fhirpath-check: --dtos needs a glob')
38
+ process.exit(2)
39
+ }
40
+ args.dtoGlobs.push(glob)
41
+ } else {
42
+ args.files.push(arg)
43
+ }
44
+ }
45
+ return args
46
+ }
47
+
48
+ const args = parseArgs(process.argv.slice(2))
49
+ if (args.files.length === 0 && !args.imports) {
50
+ console.error('usage: fhirpath-check [--dtos <glob>]... [--no-import] <file...>')
51
+ process.exit(2)
52
+ }
53
+
54
+ let failures = 0
55
+
56
+ function report(location: string, diagnostic: { severity: string; code: string; message: string }): void {
57
+ if (diagnostic.severity === 'error') {
58
+ failures += 1
59
+ }
60
+ const prefix = diagnostic.severity === 'warning' ? 'warning:' : ''
61
+ console.error(`${location} [${prefix}${diagnostic.code}] ${diagnostic.message}`)
62
+ }
63
+
64
+ /**
65
+ * Where a diagnostic falls in the file, given the site its expression came from.
66
+ * A span's own line/column are relative to the expression text, so only a
67
+ * first-line column is an offset from the site's; a later line starts at its own
68
+ * column 1.
69
+ */
70
+ function positionIn(site: { line: number; column: number }, span: { line: number; column: number }): string {
71
+ const line = site.line + (span.line - 1)
72
+ const column = span.line === 1 ? site.column + span.column - 1 : span.column
73
+ return `${line}:${column}`
74
+ }
75
+
76
+ /** Expression sites cached so each source file is read once. */
77
+ const sitesByFile = new Map<string, ExpressionSite[]>()
78
+
79
+ function sitesOf(file: string): ExpressionSite[] {
80
+ const cached = sitesByFile.get(file)
81
+ if (cached !== undefined) {
82
+ return cached
83
+ }
84
+ const sites = findExpressionSites(readFileSync(file, 'utf8'), file)
85
+ sitesByFile.set(file, sites)
86
+ return sites
87
+ }
88
+
89
+ // Check source literals first.
90
+ for (const file of args.files) {
91
+ let sites: ExpressionSite[]
92
+ try {
93
+ sites = sitesOf(file)
94
+ } catch (error) {
95
+ console.error(`fhirpath-check: cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`)
96
+ process.exit(2)
97
+ }
98
+ for (const site of sites) {
99
+ for (const diagnostic of analyzeSite(site, { model: r4Model })) {
100
+ report(`${file}:${positionIn(site, diagnostic.span)}`, diagnostic)
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * A DTO finding's position in source: `analyzeDto` knows the member and the
107
+ * expression, and the site finder knows where each expression literal sits, so
108
+ * the expression text joins the two. Identical expressions in one file share the
109
+ * first match — a cosmetic tie, not a wrong finding. A file that cannot be read,
110
+ * or an expression with no literal to point at (one built at runtime), degrades
111
+ * to naming the member.
112
+ */
113
+ function locate(finding: DtoFinding): string {
114
+ const member = `${finding.dto}.${finding.member}`
115
+ let site: ExpressionSite | undefined
116
+ try {
117
+ site = sitesOf(finding.file).find(candidate => candidate.expression === finding.expression)
118
+ } catch {
119
+ return `${finding.file} ${member}`
120
+ }
121
+ return site === undefined
122
+ ? `${finding.file} ${member}`
123
+ : `${finding.file}:${positionIn(site, finding.span)} ${member}`
124
+ }
125
+
126
+ // --- 2. the project's DTO modules, imported ---
127
+ if (args.imports) {
128
+ const globs = args.dtoGlobs.length > 0 ? args.dtoGlobs : [DEFAULT_DTO_GLOB]
129
+ let result
130
+ try {
131
+ result = await checkDtoModules(globs, process.cwd())
132
+ } catch (error) {
133
+ console.error(
134
+ `fhirpath-check: cannot import DTO modules (${globs.join(', ')}): ${error instanceof Error ? error.message : String(error)}`
135
+ )
136
+ process.exit(2)
137
+ }
138
+ for (const finding of result.findings) {
139
+ // With no engine in reach, an unresolved column call is unresolvable rather
140
+ // than wrong, so it is reported without failing the run.
141
+ const unresolvable = result.engines === 0 && finding.code === 'unknown-function'
142
+ report(locate(finding), unresolvable ? { ...finding, severity: 'warning' } : finding)
143
+ }
144
+ if (result.files.length === 0) {
145
+ console.error(
146
+ `fhirpath-check: no DTO modules matched ${globs.join(', ')} — DTOs live in *.dto.ts, or pass --dtos <glob>`
147
+ )
148
+ } else if (result.dtos.length === 0) {
149
+ console.error(`fhirpath-check: ${result.files.length} DTO module(s) matched but export no DTO class`)
150
+ } else if (result.engines === 0) {
151
+ // Without an engine, a call into another DTO's column cannot resolve, so the
152
+ // DTO half would report valid code. Say so instead of failing the run.
153
+ console.error(
154
+ 'fhirpath-check: no engine was constructed by the imported modules — column function calls cannot be resolved; ' +
155
+ 'point --dtos at the module that builds your FhirPathEngine'
156
+ )
157
+ } else {
158
+ console.log(
159
+ `fhirpath-check: analyzed ${result.dtos.length} DTO(s) from ${result.files.length} module(s) against ${result.engines} engine(s)`
160
+ )
161
+ }
162
+ }
163
+
164
+ if (failures > 0) {
165
+ console.error(`fhirpath-check: ${failures} problem(s) found`)
166
+ process.exit(1)
167
+ }
168
+ console.log('fhirpath-check: no problems found')
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Minimal Node loader for DTO checks. It compiles standard decorators to ES2022
3
+ * before importing TypeScript modules. It does not read tsconfig paths or run
4
+ * type checks; projects that need custom module resolution should compile first.
5
+ */
6
+ import { readFile } from 'node:fs/promises'
7
+ import { fileURLToPath } from 'node:url'
8
+
9
+ import ts from 'typescript'
10
+
11
+ const COMPILER_OPTIONS = {
12
+ target: ts.ScriptTarget.ES2022,
13
+ module: ts.ModuleKind.ESNext,
14
+ useDefineForClassFields: true,
15
+ experimentalDecorators: false,
16
+ inlineSourceMap: true,
17
+ inlineSources: true,
18
+ }
19
+
20
+ export async function resolve(specifier, context, nextResolve) {
21
+ try {
22
+ return await nextResolve(specifier, context)
23
+ } catch (error) {
24
+ // A relative import written without an extension: try the TypeScript file
25
+ // the project meant, then its directory index.
26
+ if (specifier.startsWith('.') && !/\.[cm]?[jt]sx?$/.test(specifier)) {
27
+ for (const candidate of [`${specifier}.ts`, `${specifier}/index.ts`]) {
28
+ try {
29
+ return await nextResolve(candidate, context)
30
+ } catch {
31
+ continue
32
+ }
33
+ }
34
+ }
35
+ throw error
36
+ }
37
+ }
38
+
39
+ export async function load(url, context, nextLoad) {
40
+ if (!url.startsWith('file:') || !/\.[cm]?ts$/.test(url)) {
41
+ return nextLoad(url, context)
42
+ }
43
+ const fileName = fileURLToPath(url)
44
+ const { outputText } = ts.transpileModule(await readFile(fileName, 'utf8'), {
45
+ fileName,
46
+ compilerOptions: COMPILER_OPTIONS,
47
+ })
48
+ return { format: 'module', shortCircuit: true, source: outputText }
49
+ }
@@ -0,0 +1,282 @@
1
+ import { FhirPathTypeError } from '../errors.ts'
2
+ import { functions as builtinFunctions } from '../functions/registry.ts'
3
+ import type { ModelProvider } from '../model/provider.ts'
4
+ import type { AstNode } from '../parser/ast.ts'
5
+ import { SYSTEM_STRING, toCollection, type TypedValue } from '../values/typed-value.ts'
6
+
7
+ /**
8
+ * The runtime side of a native host-supplied function: arity and implementation.
9
+ * The API layer's CustomFunction (api/compile.ts) adds the optional analyzer
10
+ * signature; this module only needs what evaluation uses.
11
+ */
12
+ export interface HostNativeFunction {
13
+ /** Inclusive argument count range, checked before invocation. Defaults: 0 to unlimited. */
14
+ minArity?: number
15
+ maxArity?: number
16
+ /**
17
+ * Model type names the call's focus must be able to hold, spelled as the
18
+ * caller wrote them. Either a local name or a canonical one works, because
19
+ * `unsatisfiedInput` (values/type-compat.ts) canonicalizes the names it
20
+ * compares. Leave this out to accept any focus, which is what every function
21
+ * that does not declare `signature.input.types` does.
22
+ */
23
+ inputTypes?: readonly string[]
24
+ /**
25
+ * The implementation. `input` is the unwrapped input collection; each
26
+ * argument is eagerly evaluated against `$this` — the enclosing context
27
+ * item, like built-in value arguments — and arrives as an unwrapped
28
+ * collection. Return a plain value, an array of plain values, or undefined
29
+ * for empty — the engine converts back to its typed representation.
30
+ */
31
+ fn: (input: unknown[], ...args: unknown[][]) => unknown
32
+ }
33
+
34
+ /**
35
+ * The runtime side of an expression-defined function (CustomFunction's
36
+ * `expression` form, pre-parsed by the API layer). Zero arguments; the body
37
+ * evaluates as if spliced at the call site, with the call's input as focus.
38
+ */
39
+ export interface HostExpressionFunction {
40
+ ast: AstNode
41
+ /** See HostNativeFunction.inputTypes. Same meaning, checked the same way. */
42
+ inputTypes?: readonly string[]
43
+ /**
44
+ * Environment variables the body reads that the caller does not supply, laid
45
+ * over the caller's env for the length of the call and gone again after it
46
+ * (see `withEnvOverlay`). This is how a DTO's `env` reaches its own column
47
+ * bodies without being published to every expression the engine evaluates.
48
+ * Already in collection form, since the same overlay serves every call.
49
+ */
50
+ env?: ReadonlyMap<string, TypedValue[]>
51
+ /**
52
+ * Apply the criteria rule to the body's result, so the function always returns
53
+ * exactly one Boolean. That rule is `criteriaBoolean`: §4.5 singleton
54
+ * evaluation, with an empty result read as false. It is what makes a
55
+ * `@criteria` mean the same thing whether it is projected as a column or
56
+ * called from an expression.
57
+ */
58
+ criteria?: boolean
59
+ }
60
+
61
+ /** One host-supplied function, in either form. */
62
+ export type HostSingleFunction = HostNativeFunction | HostExpressionFunction
63
+
64
+ /**
65
+ * Several functions registered under one name, told apart by the focus each was
66
+ * written for. A call runs the first whose `inputTypes` the focus satisfies (see
67
+ * `resolveHostCall`), so two DTOs may both declare a `displayText` column as
68
+ * long as a CodeableConcept can never be a Coding. Every member must declare
69
+ * `inputTypes` for that to mean anything, which is what `withDtos` (api/dto.ts)
70
+ * checks before it builds one.
71
+ */
72
+ export interface HostOverloadedFunction {
73
+ overloads: readonly HostSingleFunction[]
74
+ }
75
+
76
+ export type HostFunction = HostSingleFunction | HostOverloadedFunction
77
+
78
+ /**
79
+ * A pluggable regular-expression engine for matches()/matchesFull()/
80
+ * replaceMatches(). The default is the built-in RegExp, which backtracks and
81
+ * cannot be timed out synchronously — hosts evaluating untrusted expressions
82
+ * can supply a linear-time engine (e.g. an RE2 binding) here instead.
83
+ */
84
+ export interface RegexEngine {
85
+ /**
86
+ * Compile `pattern` with `flags` (a subset of 's' and 'g'; matchesFull
87
+ * wraps the pattern in `^(?:...)$` before compiling). Throw on invalid
88
+ * patterns — the engine converts that to the spec's type error.
89
+ */
90
+ compile(
91
+ pattern: string,
92
+ flags: string
93
+ ): {
94
+ test(subject: string): boolean
95
+ /** Replace every match (the 'g' flag is passed for replaceMatches). */
96
+ replace(subject: string, substitution: string): string
97
+ }
98
+ }
99
+
100
+ /** `$this` / `$index` / `$total` bindings; iteration functions push one frame per element. */
101
+ export interface Frame {
102
+ parent: Frame | undefined
103
+ thisValue: TypedValue[]
104
+ index: number | undefined
105
+ total: TypedValue[] | undefined
106
+ }
107
+
108
+ export interface EvaluationContext {
109
+ /** The original input node: `%context`. */
110
+ root: TypedValue[]
111
+ /** Environment variables by name (without the `%`). Values are collections. */
112
+ env: ReadonlyMap<string, TypedValue[]>
113
+ model: ModelProvider | undefined
114
+ /** Fixed evaluation clock so now()/today()/timeOfDay() are stable within one evaluation. */
115
+ now: Date
116
+ /**
117
+ * Sink for trace(). Default is a no-op: traced values may contain patient data,
118
+ * so writing them to console or log files is the caller's deliberate choice.
119
+ */
120
+ trace: (name: string, values: TypedValue[]) => void
121
+ /**
122
+ * Variables from defineVariable(), seeded with the call's `vars` bindings.
123
+ * The map is local to one expression chain: dots thread it along, while
124
+ * operator operands, function arguments, and iteration frames evaluate
125
+ * against a copy (see forkVariables).
126
+ */
127
+ variables: Map<string, TypedValue[]>
128
+ /** Host-supplied functions by name; never contains a built-in name (createContext rejects overrides). */
129
+ functions: ReadonlyMap<string, HostFunction>
130
+ /**
131
+ * Names of expression-defined functions currently on the call stack. One
132
+ * shared Set per evaluation (context copies keep the reference), so a
133
+ * definition that reaches itself — directly or through another definition —
134
+ * fails as recursion instead of overflowing the stack.
135
+ */
136
+ activeExpressionFunctions: Set<string>
137
+ /** Regex engine for the matches() family; undefined means the built-in RegExp. */
138
+ regex: RegexEngine | undefined
139
+ frame: Frame
140
+ }
141
+
142
+ const BUILTIN_CONSTANTS: ReadonlyMap<string, string> = new Map([
143
+ ['ucum', 'http://unitsofmeasure.org'],
144
+ ['sct', 'http://snomed.info/sct'],
145
+ ['loinc', 'http://loinc.org'],
146
+ ])
147
+
148
+ /**
149
+ * Environment variables every evaluation defines. The static analyzer resolves
150
+ * and protects exactly these names, so keep this list next to the code that
151
+ * seeds them (createContext) — a builtin added here is known there for free.
152
+ */
153
+ export const BUILTIN_ENV_VARIABLE_NAMES: ReadonlySet<string> = new Set([
154
+ ...BUILTIN_CONSTANTS.keys(),
155
+ 'context',
156
+ 'resource',
157
+ 'rootResource',
158
+ ])
159
+
160
+ /**
161
+ * Env records accept variable names with or without the leading `%`
162
+ * (`{ '%loinc': … }` or `{ loinc: … }`). Normalize to bare names — the form the
163
+ * context binds — so code that merges or overrides env records treats both
164
+ * spellings as one namespace. Later entries win on the same bare name.
165
+ */
166
+ export function normalizeEnvKeys<T>(env: Record<string, T> | undefined): Record<string, T> {
167
+ const normalized: Record<string, T> = {}
168
+ for (const [name, value] of Object.entries(env ?? {})) {
169
+ normalized[name.startsWith('%') ? name.slice(1) : name] = value
170
+ }
171
+ return normalized
172
+ }
173
+
174
+ /**
175
+ * An env record as the collections a context binds: names normalized, values
176
+ * wrapped. The one conversion from the host's env shape into the engine's, used
177
+ * both to seed a context and to build a function's own overlay.
178
+ */
179
+ export function envCollections(env: Record<string, unknown> | undefined): Map<string, TypedValue[]> {
180
+ const collections = new Map<string, TypedValue[]>()
181
+ for (const [name, value] of Object.entries(normalizeEnvKeys(env))) {
182
+ collections.set(name, toCollection(value))
183
+ }
184
+ return collections
185
+ }
186
+
187
+ /** Merge two env-shaped records per name: both key spellings normalize first, and `override` wins. */
188
+ export function mergeEnvKeys<T>(
189
+ base: Record<string, T> | undefined,
190
+ override: Record<string, T> | undefined
191
+ ): Record<string, T> {
192
+ return { ...normalizeEnvKeys(base), ...normalizeEnvKeys(override) }
193
+ }
194
+
195
+ export function createContext(options: {
196
+ root: TypedValue[]
197
+ env?: Record<string, unknown> | undefined
198
+ model?: ModelProvider | undefined
199
+ now?: Date | undefined
200
+ trace?: ((name: string, values: TypedValue[]) => void) | undefined
201
+ functions?: Record<string, HostFunction> | undefined
202
+ regex?: RegexEngine | undefined
203
+ }): EvaluationContext {
204
+ const env = new Map<string, TypedValue[]>()
205
+ for (const [name, url] of BUILTIN_CONSTANTS) {
206
+ env.set(name, [{ type: SYSTEM_STRING, value: url }])
207
+ }
208
+ env.set('context', options.root)
209
+ // FHIR-defined variables; contained-resource re-rooting is a later refinement.
210
+ env.set('resource', options.root)
211
+ env.set('rootResource', options.root)
212
+ for (const [name, value] of envCollections(options.env)) {
213
+ env.set(name, value)
214
+ }
215
+ const hostFunctions = new Map<string, HostFunction>()
216
+ for (const [name, fn] of Object.entries(options.functions ?? {})) {
217
+ // Overriding a built-in would silently change spec behavior — fail loudly.
218
+ if (builtinFunctions.has(name)) {
219
+ throw new FhirPathTypeError(`Cannot override the built-in function '${name}'`)
220
+ }
221
+ hostFunctions.set(name, fn)
222
+ }
223
+ return {
224
+ root: options.root,
225
+ env,
226
+ model: options.model,
227
+ now: options.now ?? new Date(),
228
+ trace: options.trace ?? (() => {}),
229
+ variables: new Map(),
230
+ functions: hostFunctions,
231
+ activeExpressionFunctions: new Set(),
232
+ regex: options.regex,
233
+ frame: { parent: undefined, thisValue: options.root, index: undefined, total: undefined },
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Adds environment values for one expression function body. Existing variables
239
+ * still take priority because variable lookup happens before environment lookup.
240
+ */
241
+ export function withEnvOverlay(
242
+ context: EvaluationContext,
243
+ overlay: ReadonlyMap<string, TypedValue[]>
244
+ ): EvaluationContext {
245
+ return { ...context, env: new Map([...context.env, ...overlay]) }
246
+ }
247
+
248
+ /** A context whose defineVariable() scope is detached from the parent chain. */
249
+ export function forkVariables(context: EvaluationContext): EvaluationContext {
250
+ return { ...context, variables: new Map(context.variables) }
251
+ }
252
+
253
+ /** Resolve `%name`; referencing an undefined environment variable is an error (spec §9). */
254
+ export function resolveEnvironmentVariable(context: EvaluationContext, name: string): TypedValue[] {
255
+ const value = context.variables.get(name) ?? context.env.get(name)
256
+ if (value !== undefined) {
257
+ return value
258
+ }
259
+ // FHIR-defined families: %`vs-[name]` and %`ext-[name]` expand to HL7 urls.
260
+ if (name.startsWith('vs-')) {
261
+ return [{ type: SYSTEM_STRING, value: `http://hl7.org/fhir/ValueSet/${name.slice(3)}` }]
262
+ }
263
+ if (name.startsWith('ext-')) {
264
+ return [{ type: SYSTEM_STRING, value: `http://hl7.org/fhir/StructureDefinition/${name.slice(4)}` }]
265
+ }
266
+ throw new FhirPathTypeError(`Undefined environment variable %${name}`)
267
+ }
268
+
269
+ /** Run `body` with a frame binding `$this` (and optionally `$index` / `$total`). */
270
+ export function withFrame<T>(
271
+ context: EvaluationContext,
272
+ frame: { thisValue: TypedValue[]; index?: number; total?: TypedValue[] },
273
+ body: (context: EvaluationContext) => T
274
+ ): T {
275
+ const child: Frame = {
276
+ parent: context.frame,
277
+ thisValue: frame.thisValue,
278
+ index: frame.index,
279
+ total: frame.total,
280
+ }
281
+ return body({ ...context, frame: child, variables: new Map(context.variables) })
282
+ }