fhirsmith 0.17.0 → 0.18.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 (121) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +80 -13
  3. package/config-template.json +106 -1
  4. package/ips/ai-client.js +263 -0
  5. package/ips/composer.js +805 -0
  6. package/ips/editor-schema.js +289 -0
  7. package/ips/editor.js +290 -0
  8. package/ips/fhirpath-ips.js +397 -0
  9. package/ips/ips-codeable-concepts.md +1742 -0
  10. package/ips/ips-template.html +125 -0
  11. package/ips/ips-valuesets.js +159 -0
  12. package/ips/ips.js +1736 -0
  13. package/ips/membership.js +179 -0
  14. package/ips/pipeline.js +1012 -0
  15. package/ips/presenter.js +974 -0
  16. package/ips/profile-editor.js +578 -0
  17. package/ips/profiles.js +506 -0
  18. package/ips/readme.md +460 -0
  19. package/ips/render.js +1482 -0
  20. package/ips/search-params.js +83 -0
  21. package/ips/search.js +667 -0
  22. package/ips/share.js +178 -0
  23. package/ips/store.js +650 -0
  24. package/ips/terminology.js +395 -0
  25. package/ips/tx-client.js +202 -0
  26. package/ips/valuesets/CodeSystem-absent-unknown-uv-ips.json +206 -0
  27. package/ips/valuesets/ValueSet-absent-or-unknown-immunizations-uv-ips.json +59 -0
  28. package/ips/valuesets/ValueSet-ips-allergy-code.json +59 -0
  29. package/ips/valuesets/ValueSet-ips-allergy-manifestation.json +356 -0
  30. package/ips/valuesets/ValueSet-ips-allergy-substance.json +37 -0
  31. package/ips/valuesets/ValueSet-ips-body-site.json +26 -0
  32. package/ips/valuesets/ValueSet-ips-clinical-finding-or-none.json +35 -0
  33. package/ips/valuesets/ValueSet-ips-clinical-finding.json +26 -0
  34. package/ips/valuesets/ValueSet-ips-condition-code.json +72 -0
  35. package/ips/valuesets/ValueSet-ips-condition-severity.json +30 -0
  36. package/ips/valuesets/ValueSet-ips-condition-stage.json +26 -0
  37. package/ips/valuesets/ValueSet-ips-exposure-route.json +26 -0
  38. package/ips/valuesets/ValueSet-ips-imaging-procedure.json +25 -0
  39. package/ips/valuesets/ValueSet-ips-immunization-status-reason.json +43 -0
  40. package/ips/valuesets/ValueSet-ips-international-codes.json +25 -0
  41. package/ips/valuesets/ValueSet-ips-medication-ingredient.json +26 -0
  42. package/ips/valuesets/ValueSet-ips-medication-status-reason.json +37 -0
  43. package/ips/valuesets/ValueSet-ips-medication.json +50 -0
  44. package/ips/valuesets/ValueSet-ips-observation-code.json +18 -0
  45. package/ips/valuesets/ValueSet-ips-observation-interpretation.json +18 -0
  46. package/ips/valuesets/ValueSet-ips-procedure-followup.json +51 -0
  47. package/ips/valuesets/ValueSet-ips-procedure-outcome.json +30 -0
  48. package/ips/valuesets/ValueSet-ips-procedure-reason.json +37 -0
  49. package/ips/valuesets/ValueSet-ips-procedure.json +116 -0
  50. package/ips/valuesets/ValueSet-ips-route.json +46 -0
  51. package/ips/valuesets/ValueSet-ips-target-disease.json +171 -0
  52. package/ips/valuesets/ValueSet-ips-vaccine.json +31 -0
  53. package/ips/valuesets/elements.json +44 -0
  54. package/library/daily-report.js +422 -0
  55. package/library/fhir-search-values.js +138 -0
  56. package/library/html-server.js +32 -23
  57. package/library/mailer.js +77 -0
  58. package/library/narrative-check.js +21 -2
  59. package/library/npm-audit.js +25 -0
  60. package/library/package-manager.js +4 -2
  61. package/library/sqlite-session-store.js +29 -1
  62. package/package.json +7 -2
  63. package/packages/packages.js +34 -3
  64. package/publisher/publisher.js +29 -0
  65. package/registry/auth-store.js +366 -0
  66. package/registry/auth.js +872 -0
  67. package/registry/crawler.js +41 -1
  68. package/registry/documentation/api.md +162 -0
  69. package/registry/documentation/configuration.md +93 -0
  70. package/registry/documentation/documents.md +74 -0
  71. package/registry/documentation/internals.md +151 -0
  72. package/registry/documentation/readme.md +100 -0
  73. package/registry/documentation/security.md +199 -0
  74. package/registry/documents.js +309 -0
  75. package/registry/openapi.yaml +30 -0
  76. package/registry/registry-template.html +1 -1
  77. package/registry/registry.js +230 -44
  78. package/registry/uptime.js +154 -0
  79. package/server.js +105 -44
  80. package/shl/shl.js +64 -9
  81. package/stats-db.js +93 -0
  82. package/stats.js +158 -1
  83. package/testing/search.js +2 -108
  84. package/testing/store.js +5 -0
  85. package/testing/testing.js +20 -0
  86. package/token/token-template.html +5 -4
  87. package/token/token.js +17 -10
  88. package/translations/Messages.properties +9 -0
  89. package/tx/README.md +52 -0
  90. package/tx/cs/cs-api.js +90 -14
  91. package/tx/cs/cs-cs.js +56 -1
  92. package/tx/cs/cs-icd11.js +366 -112
  93. package/tx/cs/cs-icf.js +418 -0
  94. package/tx/cs/cs-loinc.js +47 -25
  95. package/tx/cs/cs-omop.js +10 -3
  96. package/tx/cs/cs-snomed.js +918 -55
  97. package/tx/cs/cs-ucum.js +6 -0
  98. package/tx/cs/icd11-expressions.js +191 -78
  99. package/tx/importers/export-icd11-codesystem.js +58 -12
  100. package/tx/importers/icd11-schema.md +9 -5
  101. package/tx/library.js +98 -4
  102. package/tx/openapi-operations.js +4 -0
  103. package/tx/params.js +8 -1
  104. package/tx/provider.js +16 -4
  105. package/tx/sct/documentation.md +251 -0
  106. package/tx/sct/ecl.js +758 -46
  107. package/tx/sct/expressions.js +113 -52
  108. package/tx/syndication/syndication-feed.js +218 -0
  109. package/tx/syndication/syndication-store.js +415 -0
  110. package/tx/tx-html.js +15 -1
  111. package/tx/tx.fhir.org.yml +2 -3
  112. package/tx/tx.js +144 -2
  113. package/tx/vs/vs-vsac.js +25 -6
  114. package/tx/workers/expand.js +33 -6
  115. package/tx/workers/lookup.js +61 -6
  116. package/tx/workers/subsumes.js +1 -1
  117. package/tx/workers/translate.js +166 -15
  118. package/tx/workers/validate.js +128 -6
  119. package/utilities/make-sct-test-extension.js +264 -0
  120. package/xig/xig.js +32 -0
  121. package/registry/readme.md +0 -124
@@ -0,0 +1,397 @@
1
+ //
2
+ // Copyright 2026, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
3
+ //
4
+ // Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
5
+ //
6
+
7
+ /**
8
+ * Two FHIRPath functions for IPS documents, for fhirpath.js (a user invocation table):
9
+ *
10
+ * section(system, code [, min, max])
11
+ * The input: documents (Bundles) or Compositions. On a document, section() moves into
12
+ * its Composition (the Bundle's first Composition entry) by itself, so the rules of a
13
+ * clinical profile, which are on the document, can say Bundle.section(...). For each
14
+ * input, the Composition's sections, nested ones too, whose
15
+ * code has a coding with that system and code. When the number found is within
16
+ * min..max, they are all in the output; when it is not, none of that item's are.
17
+ *
18
+ * entry(system, code [, min, max])
19
+ * The input: sections (usually from section(), but any section will do). For each,
20
+ * its entries, resolved, whose code (code, medicationCodeableConcept or vaccineCode)
21
+ * has a coding with that system and code. Within min..max, they are all in the
22
+ * output; otherwise none of that section's are.
23
+ *
24
+ * resources(system, code [, type])
25
+ * The input: documents (Bundles), or anything in one (a Composition, say). Every
26
+ * resource in the Bundle whose code (as entry() matches it) has a coding with that
27
+ * system and code - and, when a type is given, that is of that type (Condition...).
28
+ *
29
+ * entryIn(valueSet [, min, max])
30
+ * resourcesIn(valueSet [, type])
31
+ * As entry() and resources(), with the code in a value set (a canonical, with |version
32
+ * or not) rather than a given code: a resource is in when any coding of its code is.
33
+ * Whether a code is in a value set is the caller's membership() (ipsFunctions), which
34
+ * usually asks a terminology server - asynchronously, so the evaluation must be
35
+ * (evaluateAsync); answers are best cached (membership.js).
36
+ *
37
+ * min and max are optional (together): with neither, there is no constraint; max may
38
+ * be '*'. Being outside the bounds is not an error - the item adds nothing.
39
+ *
40
+ * References are resolved in the Bundle the section is in: found by walking up from the
41
+ * section, or else the Bundle the expression was evaluated on (the evaluation's
42
+ * context). A reference is matched to an entry by fullUrl - absolute (urn:uuid:, http..)
43
+ * as is, relative (Type/id) against the base of the Composition's fullUrl, or by type
44
+ * and id when the entries have no usable fullUrl - and '#id' is a contained resource of
45
+ * the Composition. When there is no Bundle, or it doesn't have the resource, the
46
+ * caller's resolve(reference, composition) is asked, when there is one. A medication
47
+ * given by reference (medicationReference) is not followed.
48
+ *
49
+ * @module ips/fhirpath-ips
50
+ */
51
+
52
+ const fhirpath = require('fhirpath');
53
+ const r4Model = require('fhirpath/fhir-context/r4');
54
+ const { ResourceNode } = require('fhirpath/src/types.js');
55
+
56
+ const valData = fhirpath.util.valData;
57
+ // the elements of a resource that hold the code entry() matches
58
+ const CODE_ELEMENTS = ['code', 'medicationCodeableConcept', 'vaccineCode'];
59
+
60
+ function isObject(v) {
61
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
62
+ }
63
+
64
+ /** A number as fhirpath.js gives it (an integer literal is one of its decimals), as a JS number. */
65
+ function num(v) {
66
+ return v && typeof v === 'object' && typeof v.toNumber === 'function' ? v.toNumber() : v;
67
+ }
68
+
69
+ /** min and max from the arguments: min an Integer, max an Integer or '*'. */
70
+ function bounds(fnName, min, max) {
71
+ if (min === undefined) {
72
+ return { min: 0, max: Infinity };
73
+ }
74
+ min = num(min);
75
+ if (!Number.isInteger(min) || min < 0) {
76
+ throw new Error(`${fnName}(): min must be a non-negative integer`);
77
+ }
78
+ const m = Array.isArray(max) ? max.map(valData) : [max];
79
+ if (m.length !== 1) {
80
+ throw new Error(`${fnName}(): max must be an integer or '*'`);
81
+ }
82
+ const v = num(m[0]);
83
+ if (v === '*') {
84
+ return { min, max: Infinity };
85
+ }
86
+ if (!Number.isInteger(v)) {
87
+ throw new Error(`${fnName}(): max must be an integer or '*'`);
88
+ }
89
+ if (v < min) {
90
+ throw new Error(`${fnName}(): max must be an integer no less than min, or '*'`);
91
+ }
92
+ return { min, max: v };
93
+ }
94
+
95
+ /** Whether a CodeableConcept has a coding with the system and code. */
96
+ function hasCoding(concept, system, code) {
97
+ return isObject(concept) && Array.isArray(concept.coding) &&
98
+ concept.coding.some(c => isObject(c) && c.system === system && c.code === code);
99
+ }
100
+
101
+ /** The nearest node above (or at) a node whose data is a resource of the type. */
102
+ function ancestor(node, resourceType) {
103
+ for (let n = node; n; n = n.parentResNode) {
104
+ if (n instanceof ResourceNode && valData(n)?.resourceType === resourceType) {
105
+ return n;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
111
+ /** The Bundle node the evaluation was started on, if it was. */
112
+ function rootBundle(ctx) {
113
+ const root = Array.isArray(ctx.dataRoot) ? ctx.dataRoot : [ctx.dataRoot];
114
+ return root.find(n => valData(n)?.resourceType === 'Bundle') || null;
115
+ }
116
+
117
+ /**
118
+ * The Composition for an input item, as a node: the item itself, or a document's first
119
+ * Composition entry (a node under the Bundle's, so the Bundle can be found from it).
120
+ */
121
+ function compositionNode(ctx, item) {
122
+ const data = valData(item);
123
+ if (!isObject(data)) {
124
+ return null;
125
+ }
126
+ const node = item instanceof ResourceNode ? item : ResourceNode.makeResNode(ctx, data, null, null, null, null);
127
+ if (data.resourceType === 'Composition') {
128
+ return node;
129
+ }
130
+ if (data.resourceType !== 'Bundle' || !Array.isArray(data.entry)) {
131
+ return null;
132
+ }
133
+ const i = data.entry.findIndex(e => e?.resource?.resourceType === 'Composition');
134
+ if (i < 0) {
135
+ return null;
136
+ }
137
+ const entry = ResourceNode.makeResNode(ctx, data.entry[i], node, 'Bundle.entry', null, 'BackboneElement', 'entry', i);
138
+ return ResourceNode.makeResNode(ctx, data.entry[i].resource, entry, 'Bundle.entry.resource', null, null, 'resource', null);
139
+ }
140
+
141
+ /** All of a node's sections, nested ones after their parent, as nodes. */
142
+ function allSections(ctx, parent, out = []) {
143
+ const list = valData(parent)?.section;
144
+ if (!Array.isArray(list)) {
145
+ return out;
146
+ }
147
+ list.forEach((s, i) => {
148
+ if (isObject(s)) {
149
+ const node = ResourceNode.makeResNode(ctx, s, parent, 'Composition.section', null, 'BackboneElement', 'section', i);
150
+ out.push(node);
151
+ allSections(ctx, node, out);
152
+ }
153
+ });
154
+ return out;
155
+ }
156
+
157
+ /** The base of an absolute RESTful fullUrl ([base]/Type/id), or null. */
158
+ function baseOf(fullUrl) {
159
+ const m = typeof fullUrl === 'string' ? /^(https?:\/\/.+?\/)[A-Z][A-Za-z]+\/[A-Za-z0-9\-.]{1,64}(\/_history\/[A-Za-z0-9\-.]{1,64})?$/.exec(fullUrl) : null;
160
+ return m ? m[1] : null;
161
+ }
162
+
163
+ /**
164
+ * A reference resolved in a Bundle: the entry index and resource, or null.
165
+ * @param {Object} bundle
166
+ * @param {string} reference
167
+ * @param {string|undefined} fromFullUrl - the fullUrl of the referring resource's entry
168
+ */
169
+ function inBundle(bundle, reference, fromFullUrl) {
170
+ const entries = Array.isArray(bundle?.entry) ? bundle.entry : [];
171
+ const ref = reference.replace(/\/_history\/[^/]+$/, '');
172
+ const at = (pred) => {
173
+ const i = entries.findIndex(e => isObject(e) && isObject(e.resource) && pred(e));
174
+ return i < 0 ? null : { index: i, resource: entries[i].resource };
175
+ };
176
+ if (/^[a-z][a-z0-9+.-]*:/i.test(ref)) {
177
+ return at(e => e.fullUrl === ref);
178
+ }
179
+ const m = /^([A-Z][A-Za-z]+)\/([A-Za-z0-9\-.]{1,64})$/.exec(ref);
180
+ if (!m) {
181
+ return null;
182
+ }
183
+ const base = baseOf(fromFullUrl);
184
+ if (base) {
185
+ const found = at(e => e.fullUrl === base + ref);
186
+ if (found) {
187
+ return found;
188
+ }
189
+ }
190
+ // no usable base: an entry with that type and id, whose fullUrl (if any) ends with it
191
+ return at(e => e.resource.resourceType === m[1] && e.resource.id === m[2] &&
192
+ (typeof e.fullUrl !== 'string' || e.fullUrl.endsWith('/' + ref) || !baseOf(e.fullUrl)));
193
+ }
194
+
195
+ /**
196
+ * A section's entries, resolved, as nodes: in the Bundle the section is in (or the one the
197
+ * evaluation was started on), a resource contained in the Composition, or what the
198
+ * caller's resolve() gives. Unresolvable references are left out.
199
+ */
200
+ function sectionEntryNodes(ctx, item, resolve) {
201
+ const s = valData(item);
202
+ if (!isObject(s) || !Array.isArray(s.entry)) {
203
+ return null;
204
+ }
205
+ const sectionNode = item instanceof ResourceNode ? item : null;
206
+ const compNode = ancestor(sectionNode, 'Composition');
207
+ const comp = compNode ? valData(compNode) : null;
208
+ const bundleNode = ancestor(sectionNode, 'Bundle') || rootBundle(ctx);
209
+ const bundle = bundleNode ? valData(bundleNode) : null;
210
+ const compEntry = compNode?.parentResNode && valData(compNode.parentResNode);
211
+ const fromFullUrl = isObject(compEntry) && typeof compEntry.fullUrl === 'string' ? compEntry.fullUrl
212
+ : (bundle && comp ? bundle.entry?.find(e => e?.resource === comp)?.fullUrl : undefined);
213
+ const nodes = [];
214
+ for (const ref of s.entry) {
215
+ const reference = isObject(ref) && typeof ref.reference === 'string' ? ref.reference : null;
216
+ if (!reference) {
217
+ continue;
218
+ }
219
+ let node = null;
220
+ if (reference.startsWith('#')) {
221
+ const c = Array.isArray(comp?.contained) ? comp.contained.findIndex(r => isObject(r) && r.id === reference.substring(1)) : -1;
222
+ if (c >= 0) {
223
+ node = ResourceNode.makeResNode(ctx, comp.contained[c], compNode, 'Composition.contained', null, null, 'contained', c);
224
+ }
225
+ } else if (bundle) {
226
+ const r = inBundle(bundle, reference, fromFullUrl);
227
+ if (r) {
228
+ const e = ResourceNode.makeResNode(ctx, bundle.entry[r.index], bundleNode, 'Bundle.entry', null, 'BackboneElement', 'entry', r.index);
229
+ node = ResourceNode.makeResNode(ctx, r.resource, e, 'Bundle.entry.resource', null, null, 'resource', null);
230
+ }
231
+ }
232
+ if (!node && resolve) {
233
+ const r = resolve(reference, comp);
234
+ if (isObject(r) && r.resourceType) {
235
+ node = ResourceNode.makeResNode(ctx, r, null, null, null, null);
236
+ }
237
+ }
238
+ if (node) {
239
+ nodes.push(node);
240
+ }
241
+ }
242
+ return nodes;
243
+ }
244
+
245
+ /** The resources of the documents the inputs are (or are in), as nodes; each document once. */
246
+ function documentResourceNodes(ctx, inputs, fnName, type) {
247
+ if (type !== undefined && (typeof type !== 'string' || !/^[A-Z][A-Za-z]{1,63}$/.test(type))) {
248
+ throw new Error(`${fnName}(): type must be a resource type`);
249
+ }
250
+ const out = [];
251
+ const seen = new Set();
252
+ for (const item of inputs) {
253
+ const data = valData(item);
254
+ const node = item instanceof ResourceNode ? item : (isObject(data) ? ResourceNode.makeResNode(ctx, data, null, null, null, null) : null);
255
+ const bundleNode = data?.resourceType === 'Bundle' ? node : (ancestor(node, 'Bundle') || rootBundle(ctx));
256
+ const bundle = bundleNode ? valData(bundleNode) : null;
257
+ if (!bundle || seen.has(bundle) || !Array.isArray(bundle.entry)) {
258
+ continue;
259
+ }
260
+ seen.add(bundle);
261
+ bundle.entry.forEach((e, i) => {
262
+ const r = isObject(e) ? e.resource : null;
263
+ if (!isObject(r) || (type && r.resourceType !== type)) {
264
+ return;
265
+ }
266
+ const en = ResourceNode.makeResNode(ctx, e, bundleNode, 'Bundle.entry', null, 'BackboneElement', 'entry', i);
267
+ out.push(ResourceNode.makeResNode(ctx, r, en, 'Bundle.entry.resource', null, null, 'resource', null));
268
+ });
269
+ }
270
+ return out;
271
+ }
272
+
273
+ /** A value set's canonical, as entryIn() and resourcesIn() take it: a URL, with |version or not. */
274
+ function checkCanonical(fnName, url) {
275
+ if (typeof url !== 'string' || !/^[a-z][a-z0-9+.-]*:[^\s|]{1,250}(\|[^\s|]{1,64})?$/i.test(url)) {
276
+ throw new Error(`${fnName}(): the value set must be a canonical URL (optionally with |version)`);
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Of the nodes, those with a code (code, medicationCodeableConcept or vaccineCode) in the
282
+ * value set, as membership() says. membership(url, concept) answers true or false, or null
283
+ * when it can't tell (which is not a member), at once or as a Promise. Synchronous answers
284
+ * give a synchronous result; any Promise makes the result one, which fhirpath.js accepts
285
+ * only when the evaluation is asynchronous.
286
+ */
287
+ function membersOf(ctx, fnName, nodes, url, membership) {
288
+ const asks = nodes.map(n => {
289
+ const r = valData(n);
290
+ const concepts = CODE_ELEMENTS.map(el => r[el]).filter(c => isObject(c) && Array.isArray(c.coding) && c.coding.length);
291
+ return concepts.map(c => membership(url, c));
292
+ });
293
+ const isMember = (answers) => answers.some(a => a === true);
294
+ if (!asks.some(list => list.some(a => a && typeof a.then === 'function'))) {
295
+ return nodes.filter((n, i) => isMember(asks[i]));
296
+ }
297
+ fhirpath.util.checkAllowAsync(ctx, fnName);
298
+ return Promise.all(asks.map(list => Promise.all(list))).then(answers => nodes.filter((n, i) => isMember(answers[i])));
299
+ }
300
+
301
+ /**
302
+ * The fhirpath.js user invocation table with section(), entry(), resources(), entryIn()
303
+ * and resourcesIn().
304
+ *
305
+ * @param {{resolve?: function(string, Object): (Object|null), membership?: function(string, Object): (boolean|null|Promise<boolean|null>)}} [options]
306
+ * resolve: for references that aren't in a Bundle; given the reference and the
307
+ * Composition, it returns the resource or null. membership: whether a CodeableConcept is
308
+ * in a value set (a canonical) - without it, entryIn() and resourcesIn() find nothing
309
+ */
310
+ function ipsFunctions(options = {}) {
311
+ const resolve = typeof options.resolve === 'function' ? options.resolve : null;
312
+ const membership = typeof options.membership === 'function' ? options.membership : () => false;
313
+
314
+ function section(inputs, system, code, min, max) {
315
+ const ctx = this;
316
+ const b = bounds('section', min, max);
317
+ const out = [];
318
+ for (const item of inputs) {
319
+ const comp = compositionNode(ctx, item);
320
+ if (!comp) {
321
+ continue;
322
+ }
323
+ const found = allSections(ctx, comp).filter(s => hasCoding(valData(s).code, system, code));
324
+ if (found.length >= b.min && found.length <= b.max) {
325
+ out.push(...found);
326
+ }
327
+ }
328
+ return out;
329
+ }
330
+
331
+ const hasCode = (system, code) => (node) => CODE_ELEMENTS.some(el => hasCoding(valData(node)[el], system, code));
332
+ const within = (b, found) => (found.length >= b.min && found.length <= b.max ? found : []);
333
+
334
+ function entry(inputs, system, code, min, max) {
335
+ const ctx = this;
336
+ const b = bounds('entry', min, max);
337
+ const out = [];
338
+ for (const item of inputs) {
339
+ const nodes = sectionEntryNodes(ctx, item, resolve);
340
+ if (nodes) {
341
+ out.push(...within(b, nodes.filter(hasCode(system, code))));
342
+ }
343
+ }
344
+ return out;
345
+ }
346
+
347
+ function entryIn(inputs, valueSet, min, max) {
348
+ const ctx = this;
349
+ checkCanonical('entryIn', valueSet);
350
+ const b = bounds('entryIn', min, max);
351
+ const found = inputs.map(item => sectionEntryNodes(ctx, item, resolve)).filter(Boolean).map(nodes => membersOf(ctx, 'entryIn', nodes, valueSet, membership));
352
+ if (!found.some(f => typeof f.then === 'function')) {
353
+ return found.flatMap(f => within(b, f));
354
+ }
355
+ return Promise.all(found).then(all => all.flatMap(f => within(b, f)));
356
+ }
357
+
358
+ function resources(inputs, system, code, type) {
359
+ return documentResourceNodes(this, inputs, 'resources', type).filter(hasCode(system, code));
360
+ }
361
+
362
+ function resourcesIn(inputs, valueSet, type) {
363
+ checkCanonical('resourcesIn', valueSet);
364
+ return membersOf(this, 'resourcesIn', documentResourceNodes(this, inputs, 'resourcesIn', type), valueSet, membership);
365
+ }
366
+
367
+ const arity = { 2: ['String', 'String'], 4: ['String', 'String', 'Integer', 'Any'] };
368
+ // internalStructures: the input and output are nodes, so the paths (and the Bundle a
369
+ // section is in) are known; and fhirpath.js calls the function with the context as this
370
+ return {
371
+ section: { fn: section, arity, internalStructures: true },
372
+ entry: { fn: entry, arity, internalStructures: true },
373
+ resources: { fn: resources, arity: { 2: ['String', 'String'], 3: ['String', 'String', 'String'] }, internalStructures: true },
374
+ entryIn: { fn: entryIn, arity: { 1: ['String'], 3: ['String', 'Integer', 'Any'] }, internalStructures: true },
375
+ resourcesIn: { fn: resourcesIn, arity: { 1: ['String'], 2: ['String', 'String'] }, internalStructures: true }
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Evaluate an expression on a document (or anything else) with the module's functions.
381
+ * Synchronous: a membership() that answers with Promises needs evaluateAsync().
382
+ *
383
+ * @param {Object} resource
384
+ * @param {string} expression
385
+ * @param {{vars?: Object, resolve?: Function, membership?: Function}} [options]
386
+ * @returns {Array} the result, as plain values
387
+ */
388
+ function evaluate(resource, expression, options = {}) {
389
+ return fhirpath.evaluate(resource, expression, options.vars || null, r4Model, { userInvocationTable: ipsFunctions(options) });
390
+ }
391
+
392
+ /** As evaluate(), always asynchronous: what a membership() that asks a server needs. */
393
+ function evaluateAsync(resource, expression, options = {}) {
394
+ return fhirpath.evaluate(resource, expression, options.vars || null, r4Model, { userInvocationTable: ipsFunctions(options), async: 'always' });
395
+ }
396
+
397
+ module.exports = { ipsFunctions, evaluate, evaluateAsync, hasCoding, inBundle, CODE_ELEMENTS };