@cosyte/fhir 0.0.10 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -218,6 +218,12 @@ function markNonObjectMeta(node, source) {
218
218
  function markDroppedText(node) {
219
219
  return { ...node, droppedText: true };
220
220
  }
221
+ function isForeignRoot(node) {
222
+ return node.kind === "complex" && node.foreignRoot === true;
223
+ }
224
+ function markForeignRoot(node) {
225
+ return { ...node, foreignRoot: true };
226
+ }
221
227
  function primitive(value, meta = {}) {
222
228
  const node = { kind: "primitive", value };
223
229
  if (meta.id !== void 0) node.id = meta.id;
@@ -1235,1139 +1241,1798 @@ function typesOf(resource) {
1235
1241
  return getAllProperties(resource, "resourceType").flatMap((node) => [...primitiveStrings(node)]);
1236
1242
  }
1237
1243
 
1238
- // src/safety/modifier-elements.ts
1239
- var PATIENT = "Patient";
1240
- var PRACTITIONER = "Practitioner";
1241
- var IDENTIFIER = "identifier";
1242
- var UNGATED_ELEMENTS = ["implicitRules", "comparator"];
1243
- var MODIFIER_ELEMENT_ROOT_TYPES = /* @__PURE__ */ new Set([
1244
- ...SAFETY_RESOURCE_TYPES,
1245
- PATIENT,
1246
- PRACTITIONER,
1247
- "Bundle"
1248
- ]);
1249
- function carries(node, name) {
1250
- return getAllProperties(node, name).length > 0 || getAllProperties(node, `_${name}`).length > 0;
1251
- }
1252
- function eachEntry(value, path, visit2) {
1253
- if (isList(value)) {
1254
- value.items.forEach((item, index) => {
1255
- if (isComplex(item)) visit2(item, `${path}[${String(index)}]`);
1256
- });
1257
- return;
1258
- }
1259
- if (isComplex(value)) visit2(value, path);
1260
- }
1261
- function collectModifierElements(node, path, out) {
1262
- for (const element of UNGATED_ELEMENTS) {
1263
- if (carries(node, element)) out.push({ element, location: childPath(path, element) });
1264
- }
1265
- const types = typesOf(node);
1266
- if (types.includes(PATIENT) && carries(node, "active")) {
1267
- out.push({ element: "active", location: childPath(path, "active") });
1268
- }
1269
- if (types.includes(PRACTITIONER)) {
1270
- const at = childPath(path, IDENTIFIER);
1271
- for (const written of getAllProperties(node, IDENTIFIER)) {
1272
- eachEntry(written, at, (entry, entryPath) => {
1273
- if (carries(entry, "use")) {
1274
- out.push({ element: "use", location: childPath(entryPath, "use") });
1275
- }
1276
- });
1277
- }
1278
- }
1279
- }
1280
- function dedupeModifierElements(reports) {
1281
- const seen = /* @__PURE__ */ new Set();
1282
- const out = [];
1283
- for (const report of reports) {
1284
- const key = `${report.element}\0${report.location}`;
1285
- if (seen.has(key)) continue;
1286
- seen.add(key);
1287
- out.push(report);
1288
- }
1289
- return out;
1290
- }
1291
- function rebaseModifierElements(reports, prefix, types) {
1292
- const declared = types[0];
1293
- if (declared === void 0) return [...reports];
1294
- const root = MODIFIER_ELEMENT_ROOT_TYPES.has(declared) ? rootPath(declared) : WITHHELD;
1295
- if (root === prefix) return [...reports];
1296
- return reports.map((report) => ({
1297
- element: report.element,
1298
- location: report.location.startsWith(prefix) ? `${root}${report.location.slice(prefix.length)}` : report.location
1299
- }));
1300
- }
1301
-
1302
- // src/safety/absence.ts
1303
- var DATA_ABSENT_REASON_URL = "http://hl7.org/fhir/StructureDefinition/data-absent-reason";
1304
- var ABSENCE_CODES = [
1305
- "unknown",
1306
- "asked-unknown",
1307
- "temp-unknown",
1308
- "not-asked",
1309
- "asked-declined",
1310
- "masked",
1311
- "not-applicable",
1312
- "unsupported",
1313
- "as-text",
1314
- "error",
1315
- "not-a-number",
1316
- "negative-infinity",
1317
- "positive-infinity",
1318
- "not-performed",
1319
- "not-permitted"
1320
- ];
1321
- function isAbsenceCode(value) {
1322
- return ABSENCE_CODES.includes(value);
1323
- }
1324
- var STRUCTURAL_MEMBERS = /* @__PURE__ */ new Set([
1325
- "id",
1326
- "url",
1327
- "extension",
1328
- "modifierExtension",
1329
- "resourceType"
1330
- ]);
1331
- function extensionsOf(node) {
1332
- if (isPrimitive(node)) return node.extension ?? [];
1333
- if (!isComplex(node)) return [];
1334
- return getAllProperties(node, "extension").flatMap(
1335
- (written) => isList(written) ? written.items : [written]
1336
- );
1337
- }
1338
- function isAbsenceMarker(extension) {
1339
- if (!isComplex(extension)) return false;
1340
- return getAllProperties(extension, "url").some(
1341
- (written) => primitiveString(written) === DATA_ABSENT_REASON_URL
1342
- );
1343
- }
1344
- function reasonOf(marker) {
1345
- const written = getAllProperties(marker, "valueCode");
1346
- if (written.length !== 1) return void 0;
1347
- const spelled = primitiveString(written[0]);
1348
- if (spelled === void 0 || !isAbsenceCode(spelled)) return void 0;
1349
- return spelled;
1350
- }
1351
- function carriesValue(node) {
1352
- if (isPrimitive(node)) return node.value !== void 0;
1353
- if (!isComplex(node)) return false;
1354
- return [...node.properties, ...node.duplicates ?? []].some(
1355
- (property) => !STRUCTURAL_MEMBERS.has(property.name)
1356
- );
1357
- }
1358
- function pushOnce(out, location) {
1359
- if (!out.includes(location)) out.push(location);
1360
- }
1361
- function visit(node, path, out) {
1362
- const markers = extensionsOf(node).filter(isAbsenceMarker);
1363
- if (markers.length === 0) return;
1364
- if (carriesValue(node)) pushOnce(out.conflicting, path);
1365
- for (const marker of markers) {
1366
- const code = reasonOf(marker);
1367
- if (code === void 0) {
1368
- pushOnce(out.unreadable, path);
1369
- continue;
1370
- }
1371
- if (!out.markers.some((seen) => seen.code === code && seen.location === path)) {
1372
- out.markers.push({ code, location: path });
1373
- }
1374
- }
1375
- }
1376
- function walk(node, path, out) {
1377
- visit(node, path, out);
1378
- if (isList(node)) {
1379
- node.items.forEach((item, index) => {
1380
- walk(item, `${path}[${String(index)}]`, out);
1381
- });
1382
- return;
1383
- }
1384
- if (isPrimitive(node)) {
1385
- (node.extension ?? []).forEach((extension, index) => {
1386
- walk(extension, `${path}.extension[${String(index)}]`, out);
1387
- });
1388
- return;
1389
- }
1390
- for (const property of node.properties) walk(property.value, childPath(path, property.name), out);
1391
- for (const property of node.duplicates ?? []) {
1392
- walk(property.value, childPath(path, property.name), out);
1393
- }
1394
- }
1395
- function collectAbsence(resource, path) {
1396
- const out = { markers: [], unreadable: [], conflicting: [] };
1397
- walk(resource, path, out);
1398
- return out;
1399
- }
1400
-
1401
- // src/safety/status.ts
1402
- function readDoNotPerform(resource) {
1403
- const values = getAllProperties(resource, "doNotPerform");
1404
- if (values.some((node) => primitiveBooleans(node).includes(true))) return true;
1405
- return primitiveBooleans(values[0])[0];
1406
- }
1407
- function clinicalSystemFor(rt) {
1408
- if (rt === "AllergyIntolerance") return ALLERGY_CLINICAL_SYSTEM;
1409
- if (rt === "Condition") return CONDITION_CLINICAL_SYSTEM;
1410
- return void 0;
1411
- }
1412
- function verificationSystemFor(rt) {
1413
- if (rt === "AllergyIntolerance") return ALLERGY_VERIFICATION_SYSTEM;
1414
- if (rt === "Condition") return CONDITION_VERIFICATION_SYSTEM;
1415
- return void 0;
1416
- }
1417
- var NEGATION_ORDER = [
1418
- "entered-in-error",
1419
- "refuted",
1420
- "no-known-allergy",
1421
- "do-not-perform",
1422
- "not-taken",
1423
- "not-done"
1424
- ];
1425
- function unhandledModifierExtensions(resource, path) {
1426
- return walkSafety(resource, path).modifiers;
1427
- }
1428
- function modifierElements(resource) {
1429
- const types = typesOf(resource);
1430
- const prefix = safetyPrefix(types);
1431
- return rebaseModifierElements(walkSafety(resource, prefix).modifierElements, prefix, types);
1432
- }
1433
- function safetyPrefix(types) {
1434
- const rt = types[0];
1435
- return rt === void 0 ? "$this" : rootPath(rt);
1436
- }
1437
- function shadowedProperties(resource, path) {
1438
- return walkSafety(resource, path).shadowed;
1439
- }
1440
- function arrayWrappedScalars(resource, path) {
1441
- return walkSafety(resource, path).arrayWrapped;
1442
- }
1443
- function unspellableXmlWrappers(resource, path) {
1444
- return walkSafety(resource, path).unspellableInXml;
1445
- }
1446
- function nestedArrays(resource, path) {
1447
- return collectMarked(resource, path, isNestedArray);
1448
- }
1449
- function droppedText(resource, path) {
1450
- return collectMarked(resource, path, isDroppedText);
1451
- }
1452
- function unreadableBooleans(resource, path) {
1453
- return walkSafety(resource, path).unreadableBoolean;
1454
- }
1455
- function nearMissNegationCodes(resource, path) {
1456
- return walkSafety(resource, path).nearMissCode;
1457
- }
1458
- function unreadableNegationCodes(resource, path) {
1459
- return walkSafety(resource, path).unreadableCode;
1460
- }
1461
- function absenceMarkers(resource, path) {
1462
- return [...collectAbsence(resource, path).markers];
1463
- }
1464
- function unreadableAbsenceMarkers(resource, path) {
1465
- return [...collectAbsence(resource, path).unreadable];
1466
- }
1467
- function conflictingAbsenceMarkers(resource, path) {
1468
- return [...collectAbsence(resource, path).conflicting];
1469
- }
1470
- function collectMarked(resource, path, marked) {
1471
- const out = [];
1472
- collectNested(resource, path, out, marked);
1473
- return [...new Set(out)];
1474
- }
1475
- function collectNested(node, path, out, marked) {
1476
- if (marked(node)) out.push(path);
1477
- if (isList(node)) {
1478
- node.items.forEach(
1479
- (item, index) => collectNested(item, `${path}[${String(index)}]`, out, marked)
1480
- );
1481
- return;
1482
- }
1483
- if (isPrimitive(node)) {
1484
- (node.extension ?? []).forEach(
1485
- (ext, index) => collectNested(ext, `${path}.extension[${String(index)}]`, out, marked)
1486
- );
1487
- return;
1488
- }
1489
- for (const property of node.properties)
1490
- collectNested(property.value, childPath(path, property.name), out, marked);
1491
- for (const property of node.duplicates ?? []) {
1492
- collectNested(property.value, childPath(path, property.name), out, marked);
1493
- }
1494
- }
1495
- function walkSafety(resource, path) {
1496
- const out = {
1497
- modifiers: [],
1498
- modifierElements: [],
1499
- shadowed: [],
1500
- arrayWrapped: [],
1501
- unspellableInXml: [],
1502
- negations: /* @__PURE__ */ new Set(),
1503
- unreadableBoolean: [],
1504
- nearMissCode: [],
1505
- unreadableCode: []
1244
+ // src/validate/issues.ts
1245
+ var ISSUE_SEVERITIES = {
1246
+ FATAL: "fatal",
1247
+ ERROR: "error",
1248
+ WARNING: "warning",
1249
+ INFORMATION: "information"
1250
+ };
1251
+ var ISSUE_TYPES = {
1252
+ /** Structural issue, an element that is not allowed here, or a cardinality-max violation. */
1253
+ STRUCTURE: "structure",
1254
+ /** A required element (min cardinality ≥ 1) is missing. */
1255
+ REQUIRED: "required",
1256
+ /** An element value is invalid against its datatype value-domain (a primitive-regex failure). */
1257
+ VALUE: "value",
1258
+ /** A code is not a member of a required-strength value set binding. */
1259
+ CODE_INVALID: "code-invalid",
1260
+ /** A content-validation rule (a resource `constraint` / invariant) failed. */
1261
+ INVARIANT: "invariant",
1262
+ /** The content uses a modifier the processor does not support and cannot safely ignore. */
1263
+ NOT_SUPPORTED: "not-supported",
1264
+ /** Informational only, carries no defect (e.g. "this resource type has no schema yet"). */
1265
+ INFORMATIONAL: "informational",
1266
+ /** A business rule / profile-level assertion failed (e.g. a declared profile version is unknown). */
1267
+ BUSINESS_RULE: "business-rule",
1268
+ /** A referenced resource could not be found within the resolution closure (a Bundle reference). */
1269
+ NOT_FOUND: "not-found"
1270
+ };
1271
+ var VALIDATION_CODES = {
1272
+ /** Layer 1, an element the resource's structure does not define at this location. */
1273
+ UNKNOWN_ELEMENT: "UNKNOWN_ELEMENT",
1274
+ /** Layer 1, the resource carries no `resourceType`, so it cannot be structurally validated. */
1275
+ RESOURCE_TYPE_UNKNOWN: "RESOURCE_TYPE_UNKNOWN",
1276
+ /** Layer 1, no schema is available for this resource type; structural layers were skipped. */
1277
+ RESOURCE_NOT_MODELED: "RESOURCE_NOT_MODELED",
1278
+ /** Layer 1, an element's node shape (primitive / complex) is not what its datatype expects. */
1279
+ TYPE_MISMATCH: "TYPE_MISMATCH",
1280
+ /** Layer 1, more than one variant of a `choice[x]` element is present. */
1281
+ CHOICE_AMBIGUOUS: "CHOICE_AMBIGUOUS",
1282
+ /** Layer 2, a required element (min ≥ 1) is absent. */
1283
+ CARDINALITY_MIN: "CARDINALITY_MIN",
1284
+ /** Layer 2, an element appears more times than its maximum cardinality allows. */
1285
+ CARDINALITY_MAX: "CARDINALITY_MAX",
1286
+ /** Layer 3, a primitive value does not match its datatype's lexical form. */
1287
+ PRIMITIVE_INVALID: "PRIMITIVE_INVALID",
1288
+ /** Layer 3, a `code` value is outside a required-strength enumerated binding. */
1289
+ CODE_INVALID: "CODE_INVALID",
1290
+ /**
1291
+ * Safety, an element carries a `modifierExtension` this library does not understand.
1292
+ * FHIR's `?!` rule forbids ignoring an unknown modifier, so this **fails closed** (an `error`): the
1293
+ * element cannot be safely processed. See {@link ./safety.js}.
1294
+ */
1295
+ UNHANDLED_MODIFIER_EXTENSION: "UNHANDLED_MODIFIER_EXTENSION",
1296
+ /**
1297
+ * Safety, the document wrote a property name more than once, so an element holds several values
1298
+ * and nothing says which the sender meant. FHIR JSON requires unique property names (json.html §2.6.2:
1299
+ * "Property names SHALL be unique") and expresses repetition with an array, so this is a violated
1300
+ * `SHALL` and an `error`. The reader keeps every value (see {@link ../model/node.js} `duplicates`),
1301
+ * so this reports an ambiguity, never a loss.
1302
+ */
1303
+ DUPLICATE_PROPERTY: "DUPLICATE_PROPERTY",
1304
+ /**
1305
+ * Safety, a single-valued (`0..1`) safety element, or `resourceType`, arrived wrapped in a JSON
1306
+ * array. FHIR JSON writes a single-valued element as a name/value pair and uses an array only for a
1307
+ * repeating element (json.html §2.6.2.2), so this is a non-conformant encoding and an `error`. It is the
1308
+ * shape a **generic XML-to-JSON converter** produces for every element, which is how a C-CDA or v2
1309
+ * feed commonly reaches a FHIR surface, and left unreported it reaches the same harm as a repeated
1310
+ * property name: a single-value read finds no code in the array, so a retraction or a negation the
1311
+ * sender wrote goes unreported and the record reads live. Nothing is lost, the wrapper is preserved
1312
+ * and the safety layer reads through it; this reports that the encoding was ambiguous.
1313
+ */
1314
+ ARRAY_WRAPPED_SCALAR: "ARRAY_WRAPPED_SCALAR",
1315
+ /**
1316
+ * Safety, an `Observation.value[x]` choice element arrived wrapped in a JSON array. The same
1317
+ * non-conformant encoding {@link VALIDATION_CODES.ARRAY_WRAPPED_SCALAR} reports (json.html
1318
+ * §2.6.2.2 reserves the array for a repeating element), at the one position whose cardinality is
1319
+ * decided by a choice rather than by a per-resource element table, so it needs a code of its own
1320
+ * rather than a wider window on that one. An `error`.
1321
+ *
1322
+ * **Why the value readout needs its own channel.** `readObservationValue` fails safe on this
1323
+ * shape, it reports the variant that is present and no `quantity`, so no wrong number is handed
1324
+ * out, but "the variant is present and the magnitude is absent" reads identically to an
1325
+ * `Observation` whose `valueQuantity` carried no `value`. A dose the sender wrote is then
1326
+ * indistinguishable from one it did not, and this is the record that the position held content
1327
+ * nothing here would read.
1328
+ *
1329
+ * **The window is `Observation.value[x]` and `Observation.component.value[x]`, at every
1330
+ * Observation resource root the model holds**, which is the window the value readout itself reads
1331
+ * (`../quantity/value.js`). Both are `0..1` in R4 (observation.html), so this needs no
1332
+ * per-resource cardinality table and cannot fire on a conformant document. It is deliberately not
1333
+ * every R4 `0..1` element: that is the per-resource model this library does not have.
1334
+ *
1335
+ * Value-free, the location of the choice variant, never the magnitude, unit or code inside the
1336
+ * wrapper.
1337
+ */
1338
+ ARRAY_WRAPPED_CHOICE: "ARRAY_WRAPPED_CHOICE",
1339
+ /**
1340
+ * Safety, the document wrote a JSON array **inside another array**. FHIR JSON uses an array for a
1341
+ * repeating element and for nothing else (json.html §2.6.2.2), so a list of lists has no meaning at
1342
+ * any position and this is a non-conformant encoding wherever it appears, which is why it needs no
1343
+ * cardinality rule and cannot fire on a conformant document. Reported at every position the model
1344
+ * has a node for; a `_`-sibling the reader discards whole is the stated exception, and draws a
1345
+ * reader warning instead: `UNKNOWN_PROPERTY` for an unrecognised member of a `_`-sibling object,
1346
+ * `MISPLACED_PRIMITIVE_EXTENSION` for a sibling on an object or on a non-primitive array, not one
1347
+ * code for all three. An `error`, and one of the two on this list
1348
+ * where the reader could **not model** what the sender wrote (the other is
1349
+ * {@link VALIDATION_CODES.DROPPED_ELEMENT_TEXT}): the codec does not model an inner array, so this
1350
+ * reports a loss of *structure* rather than an ambiguity, though the array's JSON text is kept and
1351
+ * readable (see {@link ../model/node.js} `nestedArrayContent`). Left unreported it is among the worst of the
1352
+ * set, because the model then looks exactly like an element that was legitimately absent, and a
1353
+ * refuted allergy, a resolved condition, or an entire resource inside a Bundle entry reads back as
1354
+ * a clean document. Value-free, the position the inner array occupied, never its contents.
1355
+ */
1356
+ NESTED_ARRAY: "NESTED_ARRAY",
1357
+ /**
1358
+ * Safety, an XML document wrote **character data directly on a FHIR element**. FHIR XML carries a
1359
+ * primitive's value in the `value` attribute (xml.html §2.6.1), so text written as element content
1360
+ * has no slot on the model and the reader drops it: `<status>entered-in-error</status>` yields a
1361
+ * `status` with no value. An `error`, and the only code on this list where the content is neither
1362
+ * modeled **nor kept**: unlike {@link VALIDATION_CODES.NESTED_ARRAY}, which preserves the array's
1363
+ * JSON text, the character data is discarded outright, because reading it back would be a
1364
+ * tolerance for a non-conformant encoding rather than a report of one. Left unreported it reaches the same harm as
1365
+ * {@link VALIDATION_CODES.NESTED_ARRAY} by the other wire format, because the model is again
1366
+ * indistinguishable from an element that was legitimately absent: a retraction, a `refuted`
1367
+ * verification status, or a dose *number* beside a surviving unit and UCUM code all read back as a
1368
+ * clean document. Value-free, the position the text occupied, never its contents.
1369
+ */
1370
+ DROPPED_ELEMENT_TEXT: "DROPPED_ELEMENT_TEXT",
1371
+ /**
1372
+ * Safety, an element carries a DataAbsentReason extension **and a value of its own**, so the
1373
+ * document asserts both that the element holds that value and that it holds none. An `error`
1374
+ * (`structure`): the two cannot both be true, nothing here ranks them, and a consumer that
1375
+ * happened to read one of the two would report the record as though the other had not been
1376
+ * written. Both survive on the model and on the safety readout; this reports that they disagree.
1377
+ * Value-free, the location of the element, never the value and never the reason.
1378
+ *
1379
+ * Cannot fire on a conformant document: an element the sender has data for is written with the
1380
+ * data and no marker. It is **not** the `Observation.dataAbsentReason` ELEMENT beside a `value[x]`,
1381
+ * which is the `obs-6` invariant and reports as {@link VALIDATION_CODES.INVARIANT_VIOLATED}; the
1382
+ * element is not the extension, and the two never report about one another's shape.
1383
+ */
1384
+ ABSENCE_MARKER_CONFLICT: "ABSENCE_MARKER_CONFLICT",
1385
+ /**
1386
+ * Safety, an element carries a DataAbsentReason extension whose reason this library could not
1387
+ * read: no `valueCode`, one holding no readable string, an empty one, one written twice, or a code
1388
+ * outside the closed fifteen-concept value set the extension's `value[x]` binds to at **required**
1389
+ * strength. An `error` (`code-invalid`), on the same footing as any other required-binding miss:
1390
+ * the set is closed and published, so membership is decided from the set itself with no
1391
+ * terminology service involved and no value-set expansion.
1392
+ *
1393
+ * **Nothing is coerced.** The code is not trimmed, case-folded or substituted, and the element is
1394
+ * not read as `unknown` and not read as populated: doing either would author a reason the sender
1395
+ * did not spell, or erase a declaration the sender did make. The element stays value-absent and
1396
+ * this is the record that a declaration was made and could not be honoured. Value-free, the
1397
+ * location of the element, never the code that failed to match.
1398
+ */
1399
+ ABSENCE_MARKER_UNREADABLE: "ABSENCE_MARKER_UNREADABLE",
1400
+ /**
1401
+ * Safety, the resource is marked `entered-in-error` and is therefore **retracted, not
1402
+ * data**. Surfaced as `information` (it is not itself a defect) so a consumer cannot miss it.
1403
+ */
1404
+ RETRACTED_RESOURCE: "RETRACTED_RESOURCE",
1405
+ /**
1406
+ * Invariant, a resource constraint failed. Three sources raise it, each with or without the
1407
+ * others: the safety layer's named invariants (`ait-1`/`ait-2`, `con-3`/`con-4`/`con-5`,
1408
+ * `obs-6`/`obs-7`); the base-constraint layer, which evaluates the other error-severity
1409
+ * constraints R4 declares on the eight modeled types and their `DomainResource` / `Element` /
1410
+ * `Extension` base (`pat-1`, `obs-3`, `con-1`, `con-2`, `imm-1`, `dom-2` to `dom-5`, `ele-1`,
1411
+ * `ext-1`) with no profile supplied; and a supplied profile's own `constraint`s. The specific
1412
+ * constraint key travels in {@link ValidationIssue.constraint}, and the severity mirrors the
1413
+ * constraint's own (`error`, except a `warning` constraint such as the best-practice `con-3`).
1414
+ */
1415
+ INVARIANT_VIOLATED: "INVARIANT_VIOLATED",
1416
+ /**
1417
+ * Invariant, a `constraint`'s FHIRPath `expression`, from a supplied profile or from the R4 base
1418
+ * constraints the base-constraint layer evaluates, is **outside the bounded
1419
+ * engine's subset** and could not be evaluated. Always `information` (`informational`):
1420
+ * the constraint is reported **unchecked, never assumed to pass** (fail-safe), the
1421
+ * library does not claim conformance to an invariant it could not test. The constraint `key` travels
1422
+ * in {@link ValidationIssue.constraint}. Value-free (the location + key, never an instance value).
1423
+ * A profile's verbatim copy of an R4 base constraint that the base-constraint layer decided at the
1424
+ * same occurrence is not reported this way, because that constraint was evaluated: R4's `dom-3`
1425
+ * on a resource with nothing contained draws nothing, with or without a profile.
1426
+ */
1427
+ INVARIANT_UNCHECKED: "INVARIANT_UNCHECKED",
1428
+ /**
1429
+ * Quantity/UCUM, a `Quantity` claims the UCUM `system` but its `code` is absent or not a
1430
+ * shape-valid UCUM expression, so the unit cannot be trusted for machine use. A `warning`
1431
+ * (`value`): the value is **preserved verbatim and never converted**, the library does not bundle
1432
+ * UCUM content, so it cannot assert the code *is* a real unit, only that it is present and well-shaped.
1433
+ */
1434
+ UCUM_UNIT_UNRECOGNIZED: "UCUM_UNIT_UNRECOGNIZED",
1435
+ /**
1436
+ * Quantity/UCUM, a vital-signs Observation's measured value carries a unit the FHIR
1437
+ * vital-signs profile forbids for that LOINC code (wrong UCUM `code`, or a non-UCUM `system`). An
1438
+ * `error` (`code-invalid`): the vital-signs profile *requires* the unit, so a nonconformant one is a
1439
+ * profile violation, compared on the UCUM `code` (case- and bracket-sensitive), never the `unit` string.
1440
+ */
1441
+ VITAL_SIGN_UNIT_NONCONFORMANT: "VITAL_SIGN_UNIT_NONCONFORMANT",
1442
+ /**
1443
+ * Quantity/UCUM, an Observation whose profile expects a numeric `Quantity` value carries
1444
+ * a different `value[x]` variant instead (e.g. `valueString`). A `warning` (`value`): the value is
1445
+ * preserved and surfaced by its real type, a caller must not read it as a number.
1446
+ */
1447
+ VALUE_TYPE_UNEXPECTED: "VALUE_TYPE_UNEXPECTED",
1448
+ /**
1449
+ * Terminology, a bound coding's `system` URI is not in the frozen known-systems registry
1450
+ * (and not one the binding's value set draws from). Always `information` (`code-invalid`): an
1451
+ * unknown system may be a legitimate local/proprietary one, so it is never a defect, it only means
1452
+ * the library cannot validate codes drawn from it. Content-free, so it can never flip validity.
1453
+ */
1454
+ CODE_SYSTEM_UNKNOWN: "CODE_SYSTEM_UNKNOWN",
1455
+ /**
1456
+ * Terminology, a bound coding uses a **known** code `system` that is not one the
1457
+ * binding's value set draws from (e.g. an ICD-10-CM code where the binding expects RxNorm + SNOMED).
1458
+ * This is the content-free "wrong system for this binding" check, decided from the `system` alone,
1459
+ * with no value-set content. Severity follows the binding strength (`required` → `error`;
1460
+ * `extensible`/`preferred` → `warning`, since a different system may be a legitimate extension;
1461
+ * `example` → none). Compared on the `system` URI, never a code value.
1462
+ */
1463
+ CODE_SYSTEM_UNEXPECTED: "CODE_SYSTEM_UNEXPECTED",
1464
+ /**
1465
+ * Terminology, a configured terminology service reported that a bound coding's
1466
+ * `(system, code)` is **not a member** of the binding's value set. Severity follows the binding
1467
+ * strength (`required`/`extensible` → `error`; `preferred` → `warning`; `example` → `information`,
1468
+ * never an error). Emitted **only** when a service definitively answers `not-in`; with no service,
1469
+ * or an `unknown` answer, the library degrades to the content-free system checks and never
1470
+ * false-errors (fail-safe). Value-free, the coding location, never the code itself.
1471
+ *
1472
+ * The **only** finding that carries a {@link ValidationIssue.codeSystemVersion} record, because it
1473
+ * is the only one a terminology service produces: the release the service declared its answer was
1474
+ * made against, or the explicit mark that it declared none. Severity, emission and the fail-safe
1475
+ * degrade are all independent of it.
1476
+ */
1477
+ CODE_NOT_IN_VALUESET: "CODE_NOT_IN_VALUESET",
1478
+ /**
1479
+ * Profile, an instance element is present under a **closed** slicing whose discriminators
1480
+ * matched none of the profile's defined slices. A `structure` `error`: `closed` slicing forbids
1481
+ * content outside the named slices. (Under `open` slicing an unmatched element is allowed and draws
1482
+ * nothing; under `openAtEnd` it is allowed only in the trailing position, this library flags a
1483
+ * closed-slicing miss and leaves the ordering nuance unenforced.)
1484
+ */
1485
+ PROFILE_SLICE_UNMATCHED: "PROFILE_SLICE_UNMATCHED",
1486
+ /**
1487
+ * Profile, a slicing whose discriminator this library cannot evaluate (a `profile`
1488
+ * discriminator, which needs recursive profile resolution, or the R5-only `position`). Emitted as
1489
+ * `information` so slice membership is reported **unchecked, never silently passed** (the
1490
+ * fail-safe): the library does not guess a slice assignment it cannot justify.
1491
+ */
1492
+ PROFILE_SLICE_UNCHECKED: "PROFILE_SLICE_UNCHECKED",
1493
+ /**
1494
+ * Profile, an element the profile marks **must-support** is absent from the instance.
1495
+ * **Always `information`, never an error** (the fail-safe, and the single most important
1496
+ * must-support rule): must-support is a *system obligation* on the sender to be able to populate the
1497
+ * element and on the receiver to tolerate its absence, it is **not** an instance-presence
1498
+ * requirement. A strict client that errors on an absent must-support element is the classic bug this
1499
+ * code exists to avoid.
1500
+ */
1501
+ MUST_SUPPORT_ABSENT: "MUST_SUPPORT_ABSENT",
1502
+ /**
1503
+ * Profile, the instance's `meta.profile` declares a profile at a version the supplied
1504
+ * profile set does not carry (`canonical|version` with a different version, or an unresolvable
1505
+ * canonical). A `warning` (`business-rule`): an unknown profile
1506
+ * version is flagged rather than silently best-effort-validating against a different one.
1507
+ */
1508
+ PROFILE_VERSION_MISMATCH: "PROFILE_VERSION_MISMATCH",
1509
+ /**
1510
+ * Profile, an element carries a value that is not **exactly** the profile's `fixed[x]`.
1511
+ * A `value` `error`: `fixed[x]` is an equality constraint (the element SHALL match the fixed value
1512
+ * exactly, including every nested property). Compared structurally and precision-exactly (decimals
1513
+ * via {@link ../model/decimal.js}), never by echoing the value.
1514
+ */
1515
+ PROFILE_FIXED_MISMATCH: "PROFILE_FIXED_MISMATCH",
1516
+ /**
1517
+ * Profile, an element does not match the profile's `pattern[x]`. A `value` `error`:
1518
+ * `pattern[x]` is a **subset** constraint (the element SHALL contain *at least* the pattern's
1519
+ * properties and values, but may carry more), the weaker sibling of `fixed[x]`. Value-free.
1520
+ */
1521
+ PROFILE_PATTERN_MISMATCH: "PROFILE_PATTERN_MISMATCH",
1522
+ /**
1523
+ * Bundle, a `Reference` inside a Bundle entry (or a `#fragment` inside a resource's
1524
+ * `contained`) that could not be resolved within the resolution closure: a fragment whose target
1525
+ * contained resource is absent, or a relative `Type/id` reference naming no entry in the Bundle. A
1526
+ * `warning` (`not-found`) and **never fatal**, the target may legitimately live outside the
1527
+ * supplied closure (a partial Bundle, an external server), so the reference is **preserved**, only
1528
+ * flagged. An absolute/logical reference that is simply external to the Bundle draws **no** finding.
1529
+ * Value-free, the FHIRPath location of the reference, never the reference string itself.
1530
+ */
1531
+ REFERENCE_UNRESOLVED: "REFERENCE_UNRESOLVED",
1532
+ /**
1533
+ * Bundle, the `#fragment` references among a resource's `contained` resources form a
1534
+ * **cycle** (a → b → a, or a self-reference). An `error` (`structure`): a containment cycle is
1535
+ * malformed and, to a naive transitive resolver, a denial-of-service (an unbounded loop / stack
1536
+ * blow-up). The bounded, iterative cycle guard detects it and reports it here rather than looping,
1537
+ * DoS-safe by construction. Value-free, the location of the `contained` element, never a value.
1538
+ */
1539
+ CONTAINED_CYCLE: "CONTAINED_CYCLE",
1540
+ /**
1541
+ * Bundle, a Bundle entry's `fullUrl` is a RESTful URL (relative `Type/id` or an absolute
1542
+ * URL ending in `Type/id`) whose id disagrees with the entry `resource.id`. An `error`
1543
+ * (`business-rule`): FHIR requires a RESTful `fullUrl` to be consistent with the resource it wraps,
1544
+ * and a disagreement can cause a reference to resolve to the wrong resource. A `urn:uuid:` (logical)
1545
+ * `fullUrl` places no constraint on `resource.id`, so it never triggers this. Value-free, the
1546
+ * location of the `fullUrl`, never either id.
1547
+ */
1548
+ FULLURL_ID_MISMATCH: "FULLURL_ID_MISMATCH"
1549
+ };
1550
+ var CODE_SYSTEM_VERSION_RECORD_SYSTEM = "https://cosyte.com/fhir/CodeSystem/code-system-version-record";
1551
+ var CODE_SYSTEM_VERSION_RECORD_CODES = {
1552
+ /** The service declared a release; the string it declared travels beside this marker. */
1553
+ DECLARED: "declared",
1554
+ /**
1555
+ * The service was consulted, answered definitively, and declared **no** release. It is an
1556
+ * applicable, unanswered question, **not** "current" and not "not applicable": the marker is
1557
+ * emitted precisely so the silence cannot be read as currency.
1558
+ */
1559
+ UNDECLARED: "undeclared"
1560
+ };
1561
+ var ISSUE_TYPE_OF = {
1562
+ UNKNOWN_ELEMENT: ISSUE_TYPES.STRUCTURE,
1563
+ RESOURCE_TYPE_UNKNOWN: ISSUE_TYPES.STRUCTURE,
1564
+ RESOURCE_NOT_MODELED: ISSUE_TYPES.INFORMATIONAL,
1565
+ TYPE_MISMATCH: ISSUE_TYPES.STRUCTURE,
1566
+ CHOICE_AMBIGUOUS: ISSUE_TYPES.STRUCTURE,
1567
+ CARDINALITY_MIN: ISSUE_TYPES.REQUIRED,
1568
+ CARDINALITY_MAX: ISSUE_TYPES.STRUCTURE,
1569
+ PRIMITIVE_INVALID: ISSUE_TYPES.VALUE,
1570
+ CODE_INVALID: ISSUE_TYPES.CODE_INVALID,
1571
+ UNHANDLED_MODIFIER_EXTENSION: ISSUE_TYPES.NOT_SUPPORTED,
1572
+ DUPLICATE_PROPERTY: ISSUE_TYPES.STRUCTURE,
1573
+ ARRAY_WRAPPED_SCALAR: ISSUE_TYPES.STRUCTURE,
1574
+ ARRAY_WRAPPED_CHOICE: ISSUE_TYPES.STRUCTURE,
1575
+ NESTED_ARRAY: ISSUE_TYPES.STRUCTURE,
1576
+ DROPPED_ELEMENT_TEXT: ISSUE_TYPES.STRUCTURE,
1577
+ ABSENCE_MARKER_CONFLICT: ISSUE_TYPES.STRUCTURE,
1578
+ ABSENCE_MARKER_UNREADABLE: ISSUE_TYPES.CODE_INVALID,
1579
+ RETRACTED_RESOURCE: ISSUE_TYPES.INFORMATIONAL,
1580
+ INVARIANT_VIOLATED: ISSUE_TYPES.INVARIANT,
1581
+ INVARIANT_UNCHECKED: ISSUE_TYPES.INFORMATIONAL,
1582
+ UCUM_UNIT_UNRECOGNIZED: ISSUE_TYPES.VALUE,
1583
+ VITAL_SIGN_UNIT_NONCONFORMANT: ISSUE_TYPES.CODE_INVALID,
1584
+ VALUE_TYPE_UNEXPECTED: ISSUE_TYPES.VALUE,
1585
+ CODE_SYSTEM_UNKNOWN: ISSUE_TYPES.CODE_INVALID,
1586
+ CODE_SYSTEM_UNEXPECTED: ISSUE_TYPES.CODE_INVALID,
1587
+ CODE_NOT_IN_VALUESET: ISSUE_TYPES.CODE_INVALID,
1588
+ PROFILE_SLICE_UNMATCHED: ISSUE_TYPES.STRUCTURE,
1589
+ PROFILE_SLICE_UNCHECKED: ISSUE_TYPES.INFORMATIONAL,
1590
+ MUST_SUPPORT_ABSENT: ISSUE_TYPES.INFORMATIONAL,
1591
+ PROFILE_VERSION_MISMATCH: ISSUE_TYPES.BUSINESS_RULE,
1592
+ PROFILE_FIXED_MISMATCH: ISSUE_TYPES.VALUE,
1593
+ PROFILE_PATTERN_MISMATCH: ISSUE_TYPES.VALUE,
1594
+ REFERENCE_UNRESOLVED: ISSUE_TYPES.NOT_FOUND,
1595
+ CONTAINED_CYCLE: ISSUE_TYPES.STRUCTURE,
1596
+ FULLURL_ID_MISMATCH: ISSUE_TYPES.BUSINESS_RULE
1597
+ };
1598
+ var DIAGNOSTIC_OF = {
1599
+ UNKNOWN_ELEMENT: "Element is not defined at this location in the resource structure.",
1600
+ RESOURCE_TYPE_UNKNOWN: "Resource is missing a resourceType and cannot be structurally validated.",
1601
+ RESOURCE_NOT_MODELED: "No structural schema is available for this resource type; structural validation was skipped.",
1602
+ TYPE_MISMATCH: "Element value is not of the shape its datatype requires.",
1603
+ CHOICE_AMBIGUOUS: "More than one variant of a choice element is present.",
1604
+ CARDINALITY_MIN: "Required element is missing.",
1605
+ CARDINALITY_MAX: "Element appears more times than its maximum cardinality allows.",
1606
+ PRIMITIVE_INVALID: "Primitive value does not match the required lexical form for its datatype.",
1607
+ CODE_INVALID: "Code is not in the required value set for this element.",
1608
+ UNHANDLED_MODIFIER_EXTENSION: "Element carries a modifierExtension this processor does not understand; it cannot be safely processed and is rejected (fail-closed).",
1609
+ DUPLICATE_PROPERTY: "Property name appears more than once on this object; FHIR requires unique property names and uses an array for a repeating element, so the element's value is ambiguous.",
1610
+ ARRAY_WRAPPED_SCALAR: "Single-valued element is wrapped in an array; FHIR JSON writes a 0..1 element as a name/value pair and uses an array only for a repeating element, so the element's encoding is ambiguous.",
1611
+ ARRAY_WRAPPED_CHOICE: "Observation value[x] choice is wrapped in an array; FHIR JSON writes a 0..1 choice as a name/value pair and uses an array only for a repeating element, so the value's encoding is ambiguous and no magnitude, unit or code was read from inside the wrapper.",
1612
+ NESTED_ARRAY: "A JSON array appears inside another array; FHIR JSON uses an array only for a repeating element, so this shape has no meaning and its contents were not read. Content the sender wrote is missing from the model at this position.",
1613
+ DROPPED_ELEMENT_TEXT: "Character data was written directly on this element; FHIR XML carries a primitive's value in the value attribute, so an element has no slot for text and it was not read. Content the sender wrote is missing from the model at this position.",
1614
+ ABSENCE_MARKER_CONFLICT: "Element carries a data-absent-reason extension and a value of its own; the document asserts both that the element holds that value and that it holds none, and neither is preferred here. Both are preserved.",
1615
+ ABSENCE_MARKER_UNREADABLE: "Element carries a data-absent-reason extension whose reason could not be read: it is absent, empty, written more than once, or outside the value set the extension requires. The reason is not guessed and the element is not read as populated.",
1616
+ RETRACTED_RESOURCE: "Resource is marked entered-in-error; it is retracted and must not be treated as active data.",
1617
+ INVARIANT_VIOLATED: "A resource invariant (content-validation constraint) was violated.",
1618
+ INVARIANT_UNCHECKED: "A resource invariant could not be evaluated by the bounded FHIRPath engine; it is reported unchecked rather than assumed to pass.",
1619
+ UCUM_UNIT_UNRECOGNIZED: "Quantity declares the UCUM system but its unit code is absent or not a well-formed UCUM expression; the unit is preserved verbatim and never converted.",
1620
+ VITAL_SIGN_UNIT_NONCONFORMANT: "Vital-signs measurement carries a unit the vital-signs profile does not allow for this code.",
1621
+ VALUE_TYPE_UNEXPECTED: "Observation value is present but not the expected type for this profile.",
1622
+ CODE_SYSTEM_UNKNOWN: "Coding uses a code system that is not in the known-systems registry; its codes cannot be validated (an unrecognized system is not itself an error).",
1623
+ CODE_SYSTEM_UNEXPECTED: "Coding uses a code system that the bound value set does not draw from.",
1624
+ CODE_NOT_IN_VALUESET: "Coding is not a member of the value set required by this element's binding.",
1625
+ PROFILE_SLICE_UNMATCHED: "Element is present under closed slicing but matches none of the profile's defined slices.",
1626
+ PROFILE_SLICE_UNCHECKED: "Slice membership could not be evaluated (an unsupported discriminator); it is reported unchecked rather than assumed to pass.",
1627
+ MUST_SUPPORT_ABSENT: "Profile marks this element must-support; it is absent. Must-support is a system obligation, not an instance-presence requirement, this is informational, never an error.",
1628
+ PROFILE_VERSION_MISMATCH: "Instance declares a profile at a version the supplied profile set does not carry; it was not validated against that exact version.",
1629
+ PROFILE_FIXED_MISMATCH: "Element value does not exactly match the value the profile fixes for it.",
1630
+ PROFILE_PATTERN_MISMATCH: "Element does not match the pattern the profile requires for it.",
1631
+ REFERENCE_UNRESOLVED: "Reference could not be resolved within the supplied closure; it is preserved and flagged, never dropped (the target may live outside this Bundle).",
1632
+ CONTAINED_CYCLE: "Contained resources reference each other in a cycle; the containment graph is malformed and was reported by the bounded cycle guard rather than followed into an unbounded loop.",
1633
+ FULLURL_ID_MISMATCH: "Bundle entry fullUrl is a RESTful URL whose id disagrees with the wrapped resource's id."
1634
+ };
1635
+ function diagnosticFor(code) {
1636
+ return DIAGNOSTIC_OF[code];
1637
+ }
1638
+ function validationIssue(code, severity, expression, constraint, codeSystemVersion) {
1639
+ const issue = { code, severity, type: ISSUE_TYPE_OF[code], expression };
1640
+ const withConstraint = constraint === void 0 ? issue : { ...issue, constraint };
1641
+ return codeSystemVersion === void 0 ? withConstraint : { ...withConstraint, codeSystemVersion };
1642
+ }
1643
+
1644
+ // src/quantity/ucum.ts
1645
+ var UCUM_SYSTEM = "http://unitsofmeasure.org";
1646
+ var LOINC_SYSTEM = "http://loinc.org";
1647
+ var OBSERVATION_CATEGORY_SYSTEM = "http://terminology.hl7.org/CodeSystem/observation-category";
1648
+ var VITAL_SIGNS_CATEGORY = "vital-signs";
1649
+ var VITAL_SIGNS_PROFILE = "http://hl7.org/fhir/StructureDefinition/vitalsigns";
1650
+ var VITAL_SIGN_UNITS = /* @__PURE__ */ new Map([
1651
+ ["29463-7", ["g", "kg", "[lb_av]"]],
1652
+ // Body weight
1653
+ ["8302-2", ["cm", "[in_i]"]],
1654
+ // Body height
1655
+ ["8306-3", ["cm", "[in_i]"]],
1656
+ // Body height (lying) / body length
1657
+ ["8287-5", ["cm", "[in_i]"]],
1658
+ // Head circumference (Occipital-frontal, by tape)
1659
+ ["9843-4", ["cm", "[in_i]"]],
1660
+ // Head circumference
1661
+ ["8310-5", ["Cel", "[degF]"]],
1662
+ // Body temperature
1663
+ ["8867-4", ["/min"]],
1664
+ // Heart rate
1665
+ ["9279-1", ["/min"]],
1666
+ // Respiratory rate
1667
+ ["8480-6", ["mm[Hg]"]],
1668
+ // Systolic blood pressure
1669
+ ["8462-4", ["mm[Hg]"]],
1670
+ // Diastolic blood pressure
1671
+ ["2708-6", ["%"]],
1672
+ // Oxygen saturation in Arterial blood
1673
+ ["59408-5", ["%"]],
1674
+ // Oxygen saturation (SpO2) by pulse oximetry
1675
+ ["39156-5", ["kg/m2"]]
1676
+ // Body mass index
1677
+ ]);
1678
+ function requiredVitalSignUnits(loincCode) {
1679
+ return VITAL_SIGN_UNITS.get(loincCode);
1680
+ }
1681
+ function decimalValue(node) {
1682
+ if (node === void 0 || !isPrimitive(node)) return void 0;
1683
+ const { value } = node;
1684
+ if (typeof value === "object") return value;
1685
+ return typeof value === "string" ? decimalFromLexical(value) : void 0;
1686
+ }
1687
+ function readQuantity(node) {
1688
+ if (node === void 0 || node.kind !== "complex") return void 0;
1689
+ const q = node;
1690
+ return {
1691
+ value: decimalValue(getProperty(q, "value")),
1692
+ comparator: primitiveString(getProperty(q, "comparator")),
1693
+ unit: primitiveString(getProperty(q, "unit")),
1694
+ system: primitiveString(getProperty(q, "system")),
1695
+ code: primitiveString(getProperty(q, "code"))
1506
1696
  };
1507
- walkComplex(resource, path, out, true);
1508
- return { ...out, modifierElements: dedupeModifierElements(out.modifierElements) };
1509
1697
  }
1510
- function unspellableInXml(name, value) {
1511
- if (!isList(value)) return false;
1512
- return name === "resourceType" || value.items.length < 2;
1698
+ function validateUcumShape(code) {
1699
+ if (code.length === 0) return "invalid";
1700
+ const skeleton = code.replace(/\{[^{}]*\}/g, "");
1701
+ if (skeleton.includes("{") || skeleton.includes("}")) return "invalid";
1702
+ if (/\s/.test(skeleton)) return "invalid";
1703
+ const pairs = { "]": "[", ")": "(" };
1704
+ const stack = [];
1705
+ for (const ch of skeleton) {
1706
+ if (ch === "[" || ch === "(") stack.push(ch);
1707
+ else if (ch === "]" || ch === ")") {
1708
+ if (stack.pop() !== pairs[ch]) return "invalid";
1709
+ }
1710
+ }
1711
+ return stack.length === 0 ? "ok" : "invalid";
1712
+ }
1713
+
1714
+ // src/quantity/value.ts
1715
+ var OBSERVATION_VALUE_TYPES = [
1716
+ "Quantity",
1717
+ "CodeableConcept",
1718
+ "String",
1719
+ "Boolean",
1720
+ "Integer",
1721
+ "Range",
1722
+ "Ratio",
1723
+ "SampledData",
1724
+ "Time",
1725
+ "DateTime",
1726
+ "Period"
1727
+ ];
1728
+ function presentValues(node) {
1729
+ const byName = new Map(node.properties.map((p) => [p.name, p.value]));
1730
+ const out = [];
1731
+ for (const type of OBSERVATION_VALUE_TYPES) {
1732
+ const value = byName.get(`value${type}`);
1733
+ if (value !== void 0) out.push({ type, node: value });
1734
+ }
1735
+ return out;
1736
+ }
1737
+ function readObservationValue(node) {
1738
+ const [first, ...rest] = presentValues(node);
1739
+ if (first === void 0) return void 0;
1740
+ const wrapped = isList(first.node);
1741
+ return {
1742
+ type: first.type,
1743
+ property: `value${first.type}`,
1744
+ node: first.node,
1745
+ // Never from inside a wrapper, and the guard is written here rather than left to
1746
+ // {@link readQuantity} returning `undefined` for a list. Picking the member would author a
1747
+ // magnitude at a position the sender spelled ambiguously, which is the one thing Postel's Law
1748
+ // does not license: tolerate the form, never invent the content.
1749
+ quantity: first.type === "Quantity" && !wrapped ? readQuantity(first.node) : void 0,
1750
+ ambiguous: rest.map((r) => r.type),
1751
+ encodingIssue: wrapped ? VALIDATION_CODES.ARRAY_WRAPPED_CHOICE : void 0
1752
+ };
1753
+ }
1754
+ var OBSERVATION_VALUE_PROPERTIES = new Set(
1755
+ OBSERVATION_VALUE_TYPES.map((type) => `value${type}`)
1756
+ );
1757
+ function collectChoiceWrappers(element, path, keep, out) {
1758
+ for (const property of [...element.properties, ...element.duplicates ?? []]) {
1759
+ if (!OBSERVATION_VALUE_PROPERTIES.has(property.name)) continue;
1760
+ if (!isList(property.value)) continue;
1761
+ if (!keep(property.value.items)) continue;
1762
+ const at = childPath(path, property.name);
1763
+ if (!out.includes(at)) out.push(at);
1764
+ }
1765
+ }
1766
+ function walkForObservationRoots(node, path, keep, out) {
1767
+ if (isList(node)) {
1768
+ node.items.forEach(
1769
+ (item, index) => walkForObservationRoots(item, `${path}[${String(index)}]`, keep, out)
1770
+ );
1771
+ return;
1772
+ }
1773
+ if (isPrimitive(node)) return;
1774
+ if (typesOf(node).includes("Observation")) {
1775
+ collectChoiceWrappers(node, path, keep, out);
1776
+ for (const member of getAllProperties(node, "component")) {
1777
+ const items = isList(member) ? member.items : [member];
1778
+ items.forEach((item, index) => {
1779
+ if (!isComplex(item)) return;
1780
+ const at = isList(member) ? `${path}.component[${String(index)}]` : `${path}.component`;
1781
+ collectChoiceWrappers(item, at, keep, out);
1782
+ });
1783
+ }
1784
+ }
1785
+ for (const property of [...node.properties, ...node.duplicates ?? []]) {
1786
+ walkForObservationRoots(property.value, childPath(path, property.name), keep, out);
1787
+ }
1788
+ }
1789
+ var ANY_ARITY = () => true;
1790
+ function arrayWrappedValueChoices(resource, path) {
1791
+ const out = [];
1792
+ walkForObservationRoots(resource, path, ANY_ARITY, out);
1793
+ return out;
1794
+ }
1795
+ function unspellableXmlValueChoices(resource, path) {
1796
+ const out = [];
1797
+ walkForObservationRoots(resource, path, (items) => items.length < 2, out);
1798
+ return out;
1799
+ }
1800
+ function readReferenceRanges(observation) {
1801
+ const node = getProperty(observation, "referenceRange");
1802
+ if (node === void 0) return [];
1803
+ const items = isList(node) ? node.items : [node];
1804
+ const ranges = [];
1805
+ for (const item of items) {
1806
+ if (item.kind !== "complex") continue;
1807
+ ranges.push({
1808
+ low: readQuantity(getProperty(item, "low")),
1809
+ high: readQuantity(getProperty(item, "high")),
1810
+ type: codingsOf(getProperty(item, "type")),
1811
+ text: primitiveString(getProperty(item, "text"))
1812
+ });
1813
+ }
1814
+ return ranges;
1815
+ }
1816
+ function readInterpretations(observation) {
1817
+ return codingsOf(getProperty(observation, "interpretation"));
1818
+ }
1819
+
1820
+ // src/safety/modifier-elements.ts
1821
+ var PATIENT = "Patient";
1822
+ var PRACTITIONER = "Practitioner";
1823
+ var IMMUNIZATION = "Immunization";
1824
+ var MEDICATION_REQUEST = "MedicationRequest";
1825
+ var IDENTIFIER = "identifier";
1826
+ var DECEASED = "deceased";
1827
+ var INTENT = "intent";
1828
+ var INTENT_CODES = /* @__PURE__ */ new Set([
1829
+ "proposal",
1830
+ "plan",
1831
+ "order",
1832
+ "original-order",
1833
+ "reflex-order",
1834
+ "filler-order",
1835
+ "instance-order",
1836
+ "option"
1837
+ ]);
1838
+ var USE = "use";
1839
+ var USE_ROOT_TYPES = /* @__PURE__ */ new Set([
1840
+ "AllergyIntolerance",
1841
+ "Condition",
1842
+ "DiagnosticReport",
1843
+ "Immunization",
1844
+ "MedicationRequest",
1845
+ "MedicationStatement",
1846
+ "Observation",
1847
+ PATIENT
1848
+ ]);
1849
+ var IDENTIFIER_USE = /* @__PURE__ */ new Set([
1850
+ "usual",
1851
+ "official",
1852
+ "temp",
1853
+ "secondary",
1854
+ "old"
1855
+ ]);
1856
+ var NAME_USE = /* @__PURE__ */ new Set([
1857
+ "usual",
1858
+ "official",
1859
+ "temp",
1860
+ "nickname",
1861
+ "anonymous",
1862
+ "old",
1863
+ "maiden"
1864
+ ]);
1865
+ var ADDRESS_USE = /* @__PURE__ */ new Set([
1866
+ "home",
1867
+ "work",
1868
+ "temp",
1869
+ "old",
1870
+ "billing"
1871
+ ]);
1872
+ var CONTACT_POINT_USE = /* @__PURE__ */ new Set([
1873
+ "home",
1874
+ "work",
1875
+ "temp",
1876
+ "old",
1877
+ "mobile"
1878
+ ]);
1879
+ var IDENTIFIER_MEMBERS = /* @__PURE__ */ new Set(["identifier", "groupIdentifier"]);
1880
+ var PATIENT_MEMBERS = /* @__PURE__ */ new Map([
1881
+ ["name", NAME_USE],
1882
+ ["telecom", CONTACT_POINT_USE],
1883
+ ["address", ADDRESS_USE]
1884
+ ]);
1885
+ var CONTACT = "contact";
1886
+ var UNGATED_ELEMENTS = ["implicitRules", "comparator"];
1887
+ var MODIFIER_ELEMENT_ROOT_TYPES = /* @__PURE__ */ new Set([
1888
+ ...SAFETY_RESOURCE_TYPES,
1889
+ PATIENT,
1890
+ PRACTITIONER,
1891
+ "Bundle"
1892
+ ]);
1893
+ function carries(node, name) {
1894
+ return getAllProperties(node, name).length > 0 || getAllProperties(node, `_${name}`).length > 0;
1895
+ }
1896
+ function eachEntry(value, path, visit2) {
1897
+ if (isList(value)) {
1898
+ value.items.forEach((item, index) => {
1899
+ if (isComplex(item)) visit2(item, `${path}[${String(index)}]`);
1900
+ });
1901
+ return;
1902
+ }
1903
+ if (isComplex(value)) visit2(value, path);
1513
1904
  }
1514
- function checkArrayWrapping(node, path, report) {
1905
+ function collectModifierElements(node, path, out) {
1906
+ for (const element of UNGATED_ELEMENTS) {
1907
+ if (carries(node, element)) out.push({ element, location: childPath(path, element) });
1908
+ }
1515
1909
  const types = typesOf(node);
1516
- const isSafetyType = types.some((type) => SAFETY_RESOURCE_TYPES.has(type));
1517
- for (const property of [...node.properties, ...node.duplicates ?? []]) {
1518
- const clinical = SAFETY_SCALAR_ELEMENTS.has(property.name);
1519
- if (property.name !== "resourceType" && !(clinical && isSafetyType)) continue;
1520
- const at = childPath(path, property.name);
1521
- if (isList(property.value)) report(at, unspellableInXml(property.name, property.value));
1522
- if (clinical && isSafetyType && SAFETY_CODEABLE_ELEMENTS.has(property.name)) {
1523
- checkCodingWrapping(property.value, at, report);
1910
+ if (types.includes(PATIENT)) {
1911
+ if (carries(node, "active")) {
1912
+ out.push({ element: "active", location: childPath(path, "active") });
1524
1913
  }
1914
+ for (const member of deceasedMembers(node)) {
1915
+ out.push({ element: "deceased", location: childPath(path, member) });
1916
+ }
1917
+ if (carries(node, "link")) out.push({ element: "link", location: childPath(path, "link") });
1525
1918
  }
1526
- }
1527
- function checkCodingWrapping(value, path, report) {
1528
- const concepts = isList(value) ? value.items : [value];
1529
- concepts.forEach((concept, conceptIndex) => {
1530
- if (!isComplex(concept)) return;
1531
- const base = isList(value) ? `${path}[${String(conceptIndex)}]` : path;
1532
- for (const member of getAllProperties(concept, "coding")) {
1533
- const codings = isList(member) ? member.items : [member];
1534
- codings.forEach((coding, index) => {
1535
- if (!isComplex(coding)) return;
1536
- const at = isList(member) ? `${base}.coding[${String(index)}]` : `${base}.coding`;
1537
- for (const name of CODING_SCALAR_ELEMENTS) {
1538
- const members = getAllProperties(coding, name);
1539
- if (members.some((scalar) => isList(scalar))) {
1540
- report(
1541
- `${at}.${name}`,
1542
- members.some((scalar) => unspellableInXml(name, scalar))
1543
- );
1544
- }
1919
+ if (types.includes(IMMUNIZATION) && carries(node, "isSubpotent")) {
1920
+ out.push({ element: "isSubpotent", location: childPath(path, "isSubpotent") });
1921
+ }
1922
+ if (types.includes(PRACTITIONER)) {
1923
+ const at = childPath(path, IDENTIFIER);
1924
+ for (const written of getAllProperties(node, IDENTIFIER)) {
1925
+ eachEntry(written, at, (entry, entryPath) => {
1926
+ if (carries(entry, "use")) {
1927
+ out.push({ element: "use", location: childPath(entryPath, "use") });
1545
1928
  }
1546
1929
  });
1547
1930
  }
1548
- });
1931
+ }
1549
1932
  }
1550
- var CODING_SCALAR_ELEMENTS = ["system", "code"];
1551
- function checkNegations(node, path, report, out) {
1552
- const written = getAllProperties(node, "doNotPerform");
1553
- if (written.length > 0) {
1554
- const at = childPath(path, "doNotPerform");
1555
- if (written.some((value) => primitiveBooleans(value).includes(true))) {
1556
- out.negations.add("do-not-perform");
1557
- }
1558
- if (written.some(hasUnreadableBoolean)) out.unreadableBoolean.push(at);
1933
+ function dedupeModifierElements(reports) {
1934
+ const seen = /* @__PURE__ */ new Set();
1935
+ const out = [];
1936
+ for (const report of reports) {
1937
+ const key = `${report.element}\0${report.location}`;
1938
+ if (seen.has(key)) continue;
1939
+ seen.add(key);
1940
+ out.push(report);
1559
1941
  }
1560
- if (isRetracted(node)) out.negations.add(ENTERED_IN_ERROR);
1561
- if (getAllProperties(node, "verificationStatus").some((v) => safetyHasCodeAnySystem(v, REFUTED))) {
1562
- out.negations.add(REFUTED);
1942
+ return out;
1943
+ }
1944
+ function deceasedMembers(node) {
1945
+ const members = /* @__PURE__ */ new Set();
1946
+ for (const property of [...node.properties, ...node.duplicates ?? []]) {
1947
+ const name = property.name.startsWith("_") ? property.name.slice(1) : property.name;
1948
+ if (name.startsWith(DECEASED)) members.add(name);
1949
+ }
1950
+ return [...members];
1951
+ }
1952
+ function collectIntent(node, path, intents, unreadable) {
1953
+ if (!typesOf(node).includes(MEDICATION_REQUEST)) return;
1954
+ const written = getAllProperties(node, INTENT);
1955
+ if (written.length === 0 && getAllProperties(node, `_${INTENT}`).length === 0) return;
1956
+ const at = childPath(path, INTENT);
1957
+ const only = written.length === 1 ? written[0] : void 0;
1958
+ const value = only !== void 0 && isPrimitive(only) ? only.value : void 0;
1959
+ if (typeof value === "string" && isIntentCode(value)) {
1960
+ intents.push({ code: value, location: at });
1961
+ } else {
1962
+ unreadable.push(at);
1563
1963
  }
1564
- if (statusSpells(node, NOT_TAKEN)) out.negations.add(NOT_TAKEN);
1565
- if (statusSpells(node, NOT_DONE)) out.negations.add(NOT_DONE);
1566
- for (const read of NEGATION_CODE_READS) {
1567
- const written2 = getAllProperties(node, read.element);
1568
- if (written2.length === 0) continue;
1569
- if (read.codings === true) {
1570
- const at = childPath(path, read.element);
1571
- for (const value of written2) checkCodingWrapping(value, at, report);
1572
- }
1573
- const spelled = written2.flatMap((value) => [...read.values(value)]);
1574
- const near = read.codes.some(
1575
- (code) => !spelled.includes(code) && spelled.some((value) => isNearMissCode(value, code))
1576
- );
1577
- if (near) out.nearMissCode.push(childPath(path, read.element));
1578
- const unread = read.unread;
1579
- if (unread !== void 0 && written2.some((value) => unread(value))) {
1580
- out.unreadableCode.push(childPath(path, read.element));
1964
+ }
1965
+ function isIntentCode(value) {
1966
+ return INTENT_CODES.has(value);
1967
+ }
1968
+ function collectDatatypeUses(node, path, out) {
1969
+ const types = typesOf(node);
1970
+ if (types.includes(PRACTITIONER)) return;
1971
+ if (!types.some((type) => USE_ROOT_TYPES.has(type))) return;
1972
+ readUsesUnder(node, path, types.includes(PATIENT) ? "patient-root" : "other", out);
1973
+ }
1974
+ function readUsesUnder(node, path, place, out) {
1975
+ for (const property of [...node.properties, ...node.duplicates ?? []]) {
1976
+ const at = childPath(path, property.name);
1977
+ const codes = coveredCodes(property.name, place);
1978
+ if (codes !== void 0) {
1979
+ eachEntry(property.value, at, (entry, entryPath) => {
1980
+ readUse(entry, entryPath, codes, out);
1981
+ });
1581
1982
  }
1983
+ const next = place === "patient-root" && property.name === CONTACT ? "patient-contact" : "other";
1984
+ descendForUses(property.value, at, next, out);
1582
1985
  }
1583
1986
  }
1584
- function checkResourceRoot(node, path, out) {
1585
- const reported = /* @__PURE__ */ new Set();
1586
- const unspellable = /* @__PURE__ */ new Set();
1587
- const report = (location, unwritable) => {
1588
- if (!reported.has(location)) {
1589
- reported.add(location);
1590
- out.arrayWrapped.push(location);
1987
+ function descendForUses(value, path, place, out) {
1988
+ if (isList(value)) {
1989
+ value.items.forEach((item, index) => {
1990
+ descendForUses(item, `${path}[${String(index)}]`, place, out);
1991
+ });
1992
+ return;
1993
+ }
1994
+ if (isPrimitive(value)) {
1995
+ (value.extension ?? []).forEach((extension, index) => {
1996
+ descendForUses(extension, `${path}.extension[${String(index)}]`, "other", out);
1997
+ });
1998
+ return;
1999
+ }
2000
+ if (getAllProperties(value, "resourceType").length > 0) return;
2001
+ readUsesUnder(value, path, place, out);
2002
+ }
2003
+ function coveredCodes(name, place) {
2004
+ if (IDENTIFIER_MEMBERS.has(name)) return IDENTIFIER_USE;
2005
+ if (place === "other") return void 0;
2006
+ return PATIENT_MEMBERS.get(name);
2007
+ }
2008
+ function readUse(entry, at, codes, out) {
2009
+ const written = getAllProperties(entry, USE);
2010
+ if (written.length === 0 && getAllProperties(entry, `_${USE}`).length === 0) return;
2011
+ const only = written.length === 1 ? written[0] : void 0;
2012
+ const value = only !== void 0 && isPrimitive(only) ? only.value : void 0;
2013
+ const code = typeof value === "string" && isUseCode(value, codes) ? value : void 0;
2014
+ out.push({ location: childPath(at, USE), code });
2015
+ }
2016
+ function isUseCode(value, codes) {
2017
+ return codes.has(value);
2018
+ }
2019
+ function resolveDatatypeUses(readings) {
2020
+ const byLocation = /* @__PURE__ */ new Map();
2021
+ for (const reading of readings) {
2022
+ const codes = byLocation.get(reading.location) ?? /* @__PURE__ */ new Set();
2023
+ codes.add(reading.code);
2024
+ byLocation.set(reading.location, codes);
2025
+ }
2026
+ const uses = [];
2027
+ const unreadable = [];
2028
+ for (const [location, codes] of byLocation) {
2029
+ const [only] = [...codes];
2030
+ if (codes.size === 1 && only !== void 0) uses.push({ code: only, location });
2031
+ else unreadable.push(location);
2032
+ }
2033
+ return { uses, unreadable };
2034
+ }
2035
+ function modifierLocationRebaser(prefix, types) {
2036
+ const declared = types[0];
2037
+ if (declared === void 0) return (location) => location;
2038
+ const root = MODIFIER_ELEMENT_ROOT_TYPES.has(declared) ? rootPath(declared) : WITHHELD;
2039
+ if (root === prefix) return (location) => location;
2040
+ return (location) => location.startsWith(prefix) ? `${root}${location.slice(prefix.length)}` : location;
2041
+ }
2042
+ function rebaseModifierElements(reports, prefix, types) {
2043
+ const rebase = modifierLocationRebaser(prefix, types);
2044
+ return reports.map((report) => ({ element: report.element, location: rebase(report.location) }));
2045
+ }
2046
+
2047
+ // src/safety/absence.ts
2048
+ var DATA_ABSENT_REASON_URL = "http://hl7.org/fhir/StructureDefinition/data-absent-reason";
2049
+ var ABSENCE_CODES = [
2050
+ "unknown",
2051
+ "asked-unknown",
2052
+ "temp-unknown",
2053
+ "not-asked",
2054
+ "asked-declined",
2055
+ "masked",
2056
+ "not-applicable",
2057
+ "unsupported",
2058
+ "as-text",
2059
+ "error",
2060
+ "not-a-number",
2061
+ "negative-infinity",
2062
+ "positive-infinity",
2063
+ "not-performed",
2064
+ "not-permitted"
2065
+ ];
2066
+ function isAbsenceCode(value) {
2067
+ return ABSENCE_CODES.includes(value);
2068
+ }
2069
+ var STRUCTURAL_MEMBERS = /* @__PURE__ */ new Set([
2070
+ "id",
2071
+ "url",
2072
+ "extension",
2073
+ "modifierExtension",
2074
+ "resourceType"
2075
+ ]);
2076
+ function extensionsOf(node) {
2077
+ if (isPrimitive(node)) return node.extension ?? [];
2078
+ if (!isComplex(node)) return [];
2079
+ return getAllProperties(node, "extension").flatMap(
2080
+ (written) => isList(written) ? written.items : [written]
2081
+ );
2082
+ }
2083
+ function isAbsenceMarker(extension) {
2084
+ if (!isComplex(extension)) return false;
2085
+ return getAllProperties(extension, "url").some(
2086
+ (written) => primitiveString(written) === DATA_ABSENT_REASON_URL
2087
+ );
2088
+ }
2089
+ function reasonOf(marker) {
2090
+ const written = getAllProperties(marker, "valueCode");
2091
+ if (written.length !== 1) return void 0;
2092
+ const spelled = primitiveString(written[0]);
2093
+ if (spelled === void 0 || !isAbsenceCode(spelled)) return void 0;
2094
+ return spelled;
2095
+ }
2096
+ function carriesValue(node) {
2097
+ if (isPrimitive(node)) return node.value !== void 0;
2098
+ if (!isComplex(node)) return false;
2099
+ return [...node.properties, ...node.duplicates ?? []].some(
2100
+ (property) => !STRUCTURAL_MEMBERS.has(property.name)
2101
+ );
2102
+ }
2103
+ function pushOnce(out, location) {
2104
+ if (!out.includes(location)) out.push(location);
2105
+ }
2106
+ function visit(node, path, out) {
2107
+ const markers = extensionsOf(node).filter(isAbsenceMarker);
2108
+ if (markers.length === 0) return;
2109
+ if (carriesValue(node)) pushOnce(out.conflicting, path);
2110
+ for (const marker of markers) {
2111
+ const code = reasonOf(marker);
2112
+ if (code === void 0) {
2113
+ pushOnce(out.unreadable, path);
2114
+ continue;
1591
2115
  }
1592
- if (unwritable && !unspellable.has(location)) {
1593
- unspellable.add(location);
1594
- out.unspellableInXml.push(location);
2116
+ if (!out.markers.some((seen) => seen.code === code && seen.location === path)) {
2117
+ out.markers.push({ code, location: path });
1595
2118
  }
1596
- };
1597
- checkArrayWrapping(node, path, report);
1598
- checkNegations(node, path, report, out);
2119
+ }
1599
2120
  }
1600
- function walkComplex(node, path, out, isRoot = false) {
1601
- if (isRoot || getAllProperties(node, "resourceType").length > 0) {
1602
- checkResourceRoot(node, path, out);
2121
+ function walk(node, path, out) {
2122
+ visit(node, path, out);
2123
+ if (isList(node)) {
2124
+ node.items.forEach((item, index) => {
2125
+ walk(item, `${path}[${String(index)}]`, out);
2126
+ });
2127
+ return;
1603
2128
  }
1604
- collectModifierElements(node, path, out.modifierElements);
1605
- for (const property of node.properties) visitProperty(property, path, out);
1606
- const reported = /* @__PURE__ */ new Set();
2129
+ if (isPrimitive(node)) {
2130
+ (node.extension ?? []).forEach((extension, index) => {
2131
+ walk(extension, `${path}.extension[${String(index)}]`, out);
2132
+ });
2133
+ return;
2134
+ }
2135
+ for (const property of node.properties) walk(property.value, childPath(path, property.name), out);
1607
2136
  for (const property of node.duplicates ?? []) {
1608
- if (!reported.has(property.name)) {
1609
- reported.add(property.name);
1610
- out.shadowed.push(childPath(path, property.name));
1611
- }
1612
- visitProperty(property, path, out);
2137
+ walk(property.value, childPath(path, property.name), out);
1613
2138
  }
1614
2139
  }
1615
- function visitProperty(property, path, out) {
1616
- if (property.name === "modifierExtension") {
1617
- checkModifierExtension(property.value, path, out.modifiers);
1618
- }
1619
- descend(property.value, childPath(path, property.name), out);
2140
+ function collectAbsence(resource, path) {
2141
+ const out = { markers: [], unreadable: [], conflicting: [] };
2142
+ walk(resource, path, out);
2143
+ return out;
2144
+ }
2145
+
2146
+ // src/safety/status.ts
2147
+ function readDoNotPerform(resource) {
2148
+ const values = getAllProperties(resource, "doNotPerform");
2149
+ if (values.some((node) => primitiveBooleans(node).includes(true))) return true;
2150
+ return primitiveBooleans(values[0])[0];
2151
+ }
2152
+ function clinicalSystemFor(rt) {
2153
+ if (rt === "AllergyIntolerance") return ALLERGY_CLINICAL_SYSTEM;
2154
+ if (rt === "Condition") return CONDITION_CLINICAL_SYSTEM;
2155
+ return void 0;
1620
2156
  }
1621
- function checkModifierExtension(value, path, out) {
1622
- const items = isList(value) ? value.items : [value];
1623
- items.forEach((ext, index) => {
1624
- const url = isComplex(ext) ? primitiveString(getProperty(ext, "url")) : void 0;
1625
- if (url === void 0 || !KNOWN_MODIFIER_EXTENSION_URLS.has(url)) {
1626
- out.push(
1627
- isList(value) ? `${path}.modifierExtension[${String(index)}]` : `${path}.modifierExtension`
1628
- );
1629
- }
1630
- });
2157
+ function verificationSystemFor(rt) {
2158
+ if (rt === "AllergyIntolerance") return ALLERGY_VERIFICATION_SYSTEM;
2159
+ if (rt === "Condition") return CONDITION_VERIFICATION_SYSTEM;
2160
+ return void 0;
1631
2161
  }
1632
- function descend(node, path, out) {
1633
- if (isComplex(node)) walkComplex(node, path, out);
1634
- else if (isList(node))
1635
- node.items.forEach((item, index) => descend(item, `${path}[${String(index)}]`, out));
2162
+ var NEGATION_ORDER = [
2163
+ "entered-in-error",
2164
+ "refuted",
2165
+ "no-known-allergy",
2166
+ "do-not-perform",
2167
+ "not-taken",
2168
+ "not-done"
2169
+ ];
2170
+ function unhandledModifierExtensions(resource, path) {
2171
+ return walkSafety(resource, path).modifiers;
1636
2172
  }
1637
- function readSafety(resource) {
2173
+ function modifierElements(resource) {
1638
2174
  const types = typesOf(resource);
1639
- const rt = types[0];
1640
- const isType = (type) => types.includes(type);
1641
- const status = primitiveStrings(getProperty(resource, "status"))[0];
1642
- const clinicalStatus = safetyCodeOf(
1643
- getProperty(resource, "clinicalStatus"),
1644
- clinicalSystemFor(rt)
1645
- );
1646
- const verificationStatus = safetyCodeOf(
1647
- getProperty(resource, "verificationStatus"),
1648
- verificationSystemFor(rt)
1649
- );
1650
2175
  const prefix = safetyPrefix(types);
1651
- const walk2 = walkSafety(resource, prefix);
1652
- const doNotPerform = readDoNotPerform(resource);
1653
- const retracted = isRetracted(resource);
1654
- const noKnownAllergy = isType("AllergyIntolerance") && getAllProperties(resource, "code").some(
1655
- (node) => safetyHasCoding(node, SNOMED_SCT, NO_KNOWN_ALLERGY)
1656
- );
1657
- const found = new Set(walk2.negations);
1658
- if (noKnownAllergy) found.add("no-known-allergy");
1659
- const negations = NEGATION_ORDER.filter((kind) => found.has(kind));
1660
- const { modifiers, shadowed, arrayWrapped, unreadableBoolean, nearMissCode, unreadableCode } = walk2;
1661
- const modifierElementReports = rebaseModifierElements(walk2.modifierElements, prefix, types);
1662
- const nested = nestedArrays(resource, prefix);
1663
- const dropped = droppedText(resource, prefix);
1664
- const absence = collectAbsence(resource, prefix);
1665
- return {
1666
- resourceType: rt === void 0 ? void 0 : rootPath(rt),
1667
- status,
1668
- clinicalStatus,
1669
- verificationStatus,
1670
- doNotPerform,
1671
- retracted,
1672
- noKnownAllergy,
1673
- negations,
1674
- unhandledModifierExtensions: modifiers,
1675
- modifierElements: modifierElementReports,
1676
- shadowedProperties: shadowed,
1677
- arrayWrappedScalars: arrayWrapped,
1678
- nestedArrays: nested,
1679
- droppedText: dropped,
1680
- unreadableBooleans: unreadableBoolean,
1681
- nearMissNegationCodes: nearMissCode,
1682
- unreadableNegationCodes: unreadableCode,
1683
- absenceMarkers: absence.markers,
1684
- unreadableAbsenceMarkers: absence.unreadable,
1685
- conflictingAbsenceMarkers: absence.conflicting,
1686
- safeToSummarize: modifiers.length === 0 && modifierElementReports.length === 0 && shadowed.length === 0 && arrayWrapped.length === 0 && nested.length === 0 && dropped.length === 0 && unreadableBoolean.length === 0 && nearMissCode.length === 0 && unreadableCode.length === 0 && absence.unreadable.length === 0 && absence.conflicting.length === 0
1687
- };
1688
- }
1689
- var FhirSafetyError = class extends Error {
1690
- /** FHIRPath locations that forced the refusal (value-free). */
1691
- locations;
1692
- /**
1693
- * @param locations - The FHIRPath locations that forced the refusal (value-free).
1694
- */
1695
- constructor(locations) {
1696
- super(
1697
- `Resource cannot be safely summarized: an unhandled modifierExtension, a modifier element, a repeated property name, an array-wrapped single-valued element, an array inside an array, dropped XML element text, a boolean value this library cannot read, a code that spells a negation bar its case or its surrounding whitespace, content where a code belongs, a declared absence whose reason this library cannot read, or a declared absence beside a value leaves an element this library must not flatten (${String(locations.length)} location(s)).`
1698
- );
1699
- this.name = "FhirSafetyError";
1700
- this.locations = locations;
1701
- }
1702
- };
1703
- function assertSafeToSummarize(resource) {
1704
- const readout = "unhandledModifierExtensions" in resource ? resource : readSafety(resource);
1705
- const locations = [
1706
- ...readout.unhandledModifierExtensions,
1707
- ...readout.modifierElements.map((report) => report.location),
1708
- ...readout.shadowedProperties,
1709
- ...readout.arrayWrappedScalars,
1710
- ...readout.nestedArrays,
1711
- ...readout.droppedText,
1712
- ...readout.unreadableBooleans,
1713
- ...readout.nearMissNegationCodes,
1714
- ...readout.unreadableNegationCodes,
1715
- // The two absence channels that withdraw the affirmation. `absenceMarkers` is deliberately
1716
- // absent from this list, exactly as it is absent from `safeToSummarize`'s conjunction: a
1717
- // declaration the caller can read is a disclosure, not a loss.
1718
- ...readout.unreadableAbsenceMarkers,
1719
- ...readout.conflictingAbsenceMarkers
1720
- ];
1721
- if (locations.length > 0) throw new FhirSafetyError(locations);
1722
- }
1723
-
1724
- // src/codec/serialize-guard.ts
1725
- var SERIALIZE_ERROR_CODES = {
1726
- /**
1727
- * The model carries character data the XML reader dropped, at one or more locations. There is no
1728
- * conformant encoding of it in either wire format, and emitting the element as unfilled would
1729
- * launder the `DROPPED_ELEMENT_TEXT` finding across a round trip.
1730
- */
1731
- DROPPED_ELEMENT_TEXT: "DROPPED_ELEMENT_TEXT",
1732
- /**
1733
- * The model carries a name that cannot occupy the `Name` slot of an XML start tag, so writing it
1734
- * would emit markup that does not re-read as the element the model holds. **XML only**: JSON
1735
- * escapes a member name, so this refusal never reaches it and that route stays open. **Narrowed
1736
- * 2026-08-07 from "encodes every one of these correctly", which was false and shipped**: a model
1737
- * refused here can carry one of `serializeResource`'s own declared exceptions and emit that.
1738
- *
1739
- * See {@link breaksTag} for the exact predicate and for what is deliberately NOT refused.
1740
- */
1741
- UNSERIALIZABLE_ELEMENT_NAME: "UNSERIALIZABLE_ELEMENT_NAME",
1742
- /**
1743
- * A `div` property carries a string the XML writer would emit as raw markup, and that string does
1744
- * not contribute exactly one element named `div` to the document. **XML only**: `serializeResource`
1745
- * carries the string as a string, so this refusal never reaches it and that route stays open.
1746
- *
1747
- * **That is a statement about the `div` string, not about the whole model.** `serializeResource`
1748
- * has its own declared non-spec-clean exceptions, so a model refused here can still route through
1749
- * it and emit one of those: `{"text":{"div":""},"name":[[{"family":"X"}]]}` is refused here and
1750
- * `serializeResource` emits the array inside an array unchanged.
1751
- *
1752
- * See `emitsOneDivElement` in `../xml/write.js` for the exact predicate and what it does not cover.
1753
- */
1754
- UNSERIALIZABLE_DIV_MARKUP: "UNSERIALIZABLE_DIV_MARKUP",
1755
- /**
1756
- * The model carries, at one or more locations, a shape the JSON reader marked because FHIR JSON
1757
- * gives that position no meaning: an array inside an array, a scalar or `null` where FHIR JSON has
1758
- * an object (a complex element's own position or a primitive's `_`-sibling), or a `null` in a
1759
- * primitive's value channel that padded nothing. **XML only**: this refusal does not reach
1760
- * `serializeResource`, which writes these back from the text the reader preserved at every position
1761
- * that writer walks. **It does not walk a member a repeated property name shadowed, and this
1762
- * refusal does reach one** -- `serializeResource` refuses that model on
1763
- * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_SHADOWED_PROPERTY} rather than emitting it, so it is
1764
- * still not a route the shape survives. The XML writer keeps reporting THIS code on such a model,
1765
- * because this one is raised first. See {@link assertXmlSerializable}.
1766
- *
1767
- * XML has no array-of-arrays, no `_`-sibling and no `null`, so the XML writer has nowhere to put
1768
- * any of it and emits the element the reader was left holding: an empty one, or none. That output
1769
- * re-reads with an empty issue list, so the finding the reader raised is gone after one trip.
1770
- *
1771
- * See {@link assertXmlSerializable} for the exact set of markers and for what it does NOT cover.
1772
- */
1773
- UNSERIALIZABLE_JSON_ONLY_SHAPE: "UNSERIALIZABLE_JSON_ONLY_SHAPE",
1774
- /**
1775
- * The model carries, at a location this library already reports as an array-wrapped `0..1` safety
1776
- * element, a wrapper FHIR XML has no repetition to spell back: one holding fewer than two items,
1777
- * or any wrapper at all on `resourceType`. **XML only**: `serializeResource` writes the list back
1778
- * and the re-read reports the same location, so that route stays open.
1779
- *
1780
- * XML spells a repeat by repeating the element and has no other mark for one, so a wrapper of
1781
- * fewer than two items emits at most one element and re-reads as an ordinary single-valued
1782
- * element. The encoding complaint the reader raised is then gone, and with it an `error`-severity
1783
- * `ARRAY_WRAPPED_SCALAR` and a `safeToSummarize: false`.
1784
- *
1785
- * See {@link assertXmlArrayWrapper} for the exact predicate and for what it deliberately leaves.
1786
- */
1787
- UNSERIALIZABLE_ARRAY_WRAPPER: "UNSERIALIZABLE_ARRAY_WRAPPER",
1788
- /**
1789
- * The model carries, at one or more object elements, a member a repeated property name shadowed
1790
- * ({@link ../model/node.js} `duplicates`). **Both writers**, unlike the four refusals above it:
1791
- * each walks `properties` only, so each wrote one member per name and the second value left the
1792
- * document with no diagnostic on it.
1793
- *
1794
- * See {@link assertNoShadowedProperty} for the window, the two routes weighed, and what it leaves.
1795
- */
1796
- UNSERIALIZABLE_SHADOWED_PROPERTY: "UNSERIALIZABLE_SHADOWED_PROPERTY",
1797
- /**
1798
- * The model carries, at one or more elements, a first `resourceType` that is not a string. FHIR
1799
- * XML has no `resourceType` element at all -- the type IS the tag -- so the XML writer skipped the
1800
- * property, and at the root, with no string to name the tag, it wrote `Resource` instead: the
1801
- * member deleted and the element named a type nobody wrote. **XML only**: `serializeResource` emits a
1802
- * non-string `resourceType` through its ordinary path, so this refusal never reaches it and that
1803
- * route stays open.
1804
- *
1805
- * See {@link assertXmlResourceType} for the window and for what it deliberately leaves.
1806
- */
1807
- UNSERIALIZABLE_RESOURCE_TYPE: "UNSERIALIZABLE_RESOURCE_TYPE"
1808
- };
1809
- var FhirSerializeError = class extends Error {
1810
- /** Which refusal this is. */
1811
- code;
1812
- /** The bounded FHIRPath locations the refusal is about, in walk order. Never document content. */
1813
- locations;
1814
- /**
1815
- * @param message - A value-free description of the refusal.
1816
- * @param code - Which refusal this is.
1817
- * @param locations - The bounded FHIRPath locations it is about.
1818
- */
1819
- constructor(message, code, locations) {
1820
- super(message);
1821
- this.name = "FhirSerializeError";
1822
- this.code = code;
1823
- this.locations = locations;
1824
- }
1825
- };
1826
- function assertSerializable(node) {
1827
- const locations = droppedText(node, rootPath(typeOf(node) ?? "Resource"));
1828
- if (locations.length === 0) return;
1829
- throw new FhirSerializeError(
1830
- `cannot serialize: the reader dropped character data at ${String(locations.length)} location(s), which this model cannot encode`,
1831
- SERIALIZE_ERROR_CODES.DROPPED_ELEMENT_TEXT,
1832
- locations
1833
- );
1834
- }
1835
- function carriesJsonOnlyShape(node) {
1836
- if (node.kind === "list") return false;
1837
- if (node.nestedArray === true || node.nestedArraySource !== void 0) return true;
1838
- if (node.kind === "complex") return node.nonObjectSource !== void 0;
1839
- return node.undefinedNull === true || node.nonObjectMetaSource !== void 0 || node.nestedArrayMetaSource !== void 0;
2176
+ return rebaseModifierElements(walkSafety(resource, prefix).modifierElements, prefix, types);
1840
2177
  }
1841
- function assertXmlSerializable(node) {
1842
- const locations = collectMarked(node, rootPath(typeOf(node) ?? "Resource"), carriesJsonOnlyShape);
1843
- if (locations.length === 0) return;
1844
- throw new FhirSerializeError(
1845
- // Says only what this refusal does NOT reach, which is the wording the two refusals beside it
1846
- // were narrowed to. It deliberately does not promise that `serializeResource` writes the shape
1847
- // back: it does for every position that writer walks, and a marked node inside a member a
1848
- // repeated property name shadowed is dropped by it as well.
1849
- `cannot serialize to XML: ${String(locations.length)} location(s) carry a shape only FHIR JSON can spell, which XML has no channel for; this refusal does not reach serializeResource`,
1850
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_JSON_ONLY_SHAPE,
1851
- locations
1852
- );
2178
+ function safetyPrefix(types) {
2179
+ const rt = types[0];
2180
+ return rt === void 0 ? "$this" : rootPath(rt);
1853
2181
  }
1854
- function assertXmlArrayWrapper(node) {
1855
- const locations = unspellableXmlWrappers(node, rootPath(typeOf(node) ?? "Resource"));
1856
- if (locations.length === 0) return;
1857
- throw new FhirSerializeError(
1858
- // Says what this refusal does not reach, in the wording the three refusals beside it were
1859
- // narrowed to. `serializeResource` does write these wrappers back -- pinned by the JSON half of
1860
- // the same test -- with the shadowed-member limit stated in the docblock rather than here.
1861
- `cannot serialize to XML: ${String(locations.length)} location(s) carry an array wrapper around a 0..1 element that XML has no repeated element to spell back; this refusal does not reach serializeResource`,
1862
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ARRAY_WRAPPER,
1863
- locations
1864
- );
2182
+ function shadowedProperties(resource, path) {
2183
+ return walkSafety(resource, path).shadowed;
1865
2184
  }
1866
- function assertNoShadowedProperty(node) {
1867
- const locations = shadowedProperties(node, rootPath(typeOf(node) ?? "Resource"));
1868
- if (locations.length === 0) return;
1869
- throw new FhirSerializeError(
1870
- // Names both writers, because both drop it: there is no route here that keeps the member, and a
1871
- // message pointing at one would point an operator at a silent drop. That is the wording the four
1872
- // refusals above were narrowed to, at the one refusal where the answer is "neither".
1873
- `cannot serialize: ${String(locations.length)} location(s) carry a member a repeated property name shadowed, which neither serializeResource nor serializeResourceXml can write`,
1874
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_SHADOWED_PROPERTY,
1875
- locations
1876
- );
2185
+ function arrayWrappedScalars(resource, path) {
2186
+ return walkSafety(resource, path).arrayWrapped;
1877
2187
  }
1878
- function lacksTaggableResourceType(node) {
1879
- if (node.kind !== "complex") return false;
1880
- const written = node.properties.find((property) => property.name === "resourceType");
1881
- if (written === void 0) return false;
1882
- return !(isPrimitive(written.value) && typeof written.value.value === "string");
2188
+ function unspellableXmlWrappers(resource, path) {
2189
+ return walkSafety(resource, path).unspellableInXml;
1883
2190
  }
1884
- function assertXmlResourceType(node) {
1885
- const elements = collectMarked(
1886
- node,
1887
- rootPath(typeOf(node) ?? "Resource"),
1888
- lacksTaggableResourceType
1889
- );
1890
- if (elements.length === 0) return;
1891
- const locations = elements.map((element) => childPath(element, "resourceType"));
1892
- throw new FhirSerializeError(
1893
- // Says what this refusal does not reach, in the wording the refusals beside it were narrowed to.
1894
- // `serializeResource` emits a non-string `resourceType` through its ordinary path -- pinned by
1895
- // the JSON half of the same test -- which is a statement about this shape, not about the rest of
1896
- // the model: that writer's own declared exceptions still apply to everything else it carries.
1897
- // It does NOT say "while naming the element Resource": that substitution happens at the root,
1898
- // and this refusal reports depths where the tag comes from the property name instead.
1899
- `cannot serialize to XML: ${String(locations.length)} location(s) carry a resourceType with no string to name a tag with, which this writer would delete; this refusal does not reach serializeResource`,
1900
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_RESOURCE_TYPE,
1901
- locations
1902
- );
2191
+ function nestedArrays(resource, path) {
2192
+ return collectMarked(resource, path, isNestedArray);
1903
2193
  }
1904
- var TAG_BREAKING = /[ \t\n\r/>=<]/u;
1905
- var TAG_OPENER_STEALING = /* @__PURE__ */ new Set(["!", "?"]);
1906
- function breaksTag(name) {
1907
- return name === "" || TAG_BREAKING.test(name) || TAG_OPENER_STEALING.has(name.slice(0, 1));
2194
+ function droppedText(resource, path) {
2195
+ return collectMarked(resource, path, isDroppedText);
1908
2196
  }
1909
- function refuseUnserializableNames(locations) {
1910
- throw new FhirSerializeError(
1911
- `cannot serialize to XML: ${String(locations.length)} location(s) carry a name that cannot be written as an XML tag without changing which elements the document holds; serializeResource escapes a member name, so this refusal never reaches it`,
1912
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ELEMENT_NAME,
1913
- locations
1914
- );
2197
+ function unreadableBooleans(resource, path) {
2198
+ return walkSafety(resource, path).unreadableBoolean;
1915
2199
  }
1916
- function refuseUnserializableDivMarkup(locations) {
1917
- throw new FhirSerializeError(
1918
- `cannot serialize to XML: ${String(locations.length)} div location(s) carry markup that would not be written as the one div element the model names; serializeResource carries the string as a string`,
1919
- SERIALIZE_ERROR_CODES.UNSERIALIZABLE_DIV_MARKUP,
1920
- locations
1921
- );
2200
+ function nearMissNegationCodes(resource, path) {
2201
+ return walkSafety(resource, path).nearMissCode;
1922
2202
  }
1923
-
1924
- // src/codec/write.ts
1925
- function emitScalar(value) {
1926
- if (value === void 0) return "null";
1927
- if (typeof value === "string") return JSON.stringify(value);
1928
- if (typeof value === "boolean") return value ? "true" : "false";
1929
- return value.raw;
2203
+ function unreadableNegationCodes(resource, path) {
2204
+ return walkSafety(resource, path).unreadableCode;
1930
2205
  }
1931
- function emitPrimitiveValue(node) {
1932
- return node.nestedArraySource ?? emitScalar(node.value);
2206
+ function absenceMarkers(resource, path) {
2207
+ return [...collectAbsence(resource, path).markers];
1933
2208
  }
1934
- function hasValue(node) {
1935
- return node.value !== void 0 || node.nestedArraySource !== void 0 || node.undefinedNull === true;
2209
+ function unreadableAbsenceMarkers(resource, path) {
2210
+ return [...collectAbsence(resource, path).unreadable];
1936
2211
  }
1937
- function hasMeta(node) {
1938
- return node.id !== void 0 || node.extension !== void 0 && node.extension.length > 0 || node.nestedArrayMetaSource !== void 0 || node.nonObjectMetaSource !== void 0;
2212
+ function conflictingAbsenceMarkers(resource, path) {
2213
+ return [...collectAbsence(resource, path).conflicting];
1939
2214
  }
1940
- function emitMeta(node) {
1941
- if (node.nestedArrayMetaSource !== void 0) return node.nestedArrayMetaSource;
1942
- if (node.nonObjectMetaSource !== void 0) return node.nonObjectMetaSource;
1943
- const parts = [];
1944
- if (node.id !== void 0) parts.push(`"id":${JSON.stringify(node.id)}`);
1945
- if (node.extension !== void 0 && node.extension.length > 0) {
1946
- parts.push(`"extension":[${node.extension.map(emitComplex).join(",")}]`);
1947
- }
1948
- return `{${parts.join(",")}}`;
2215
+ function collectMarked(resource, path, marked) {
2216
+ const out = [];
2217
+ collectNested(resource, path, out, marked);
2218
+ return [...new Set(out)];
1949
2219
  }
1950
- function emitPrimitiveProperty(name, node) {
1951
- const entries = [];
1952
- if (hasValue(node)) {
1953
- entries.push(`${JSON.stringify(name)}:${emitPrimitiveValue(node)}`);
2220
+ function collectNested(node, path, out, marked) {
2221
+ if (marked(node)) out.push(path);
2222
+ if (isList(node)) {
2223
+ node.items.forEach(
2224
+ (item, index) => collectNested(item, `${path}[${String(index)}]`, out, marked)
2225
+ );
2226
+ return;
1954
2227
  }
1955
- if (hasMeta(node)) {
1956
- entries.push(`${JSON.stringify(`_${name}`)}:${emitMeta(node)}`);
2228
+ if (isPrimitive(node)) {
2229
+ (node.extension ?? []).forEach(
2230
+ (ext, index) => collectNested(ext, `${path}.extension[${String(index)}]`, out, marked)
2231
+ );
2232
+ return;
2233
+ }
2234
+ for (const property of node.properties)
2235
+ collectNested(property.value, childPath(path, property.name), out, marked);
2236
+ for (const property of node.duplicates ?? []) {
2237
+ collectNested(property.value, childPath(path, property.name), out, marked);
1957
2238
  }
1958
- return entries;
1959
2239
  }
1960
- function emitListProperty(name, node) {
1961
- if (node.items.length === 0) return [];
1962
- const allPrimitive = node.items.every(isPrimitive);
1963
- if (!allPrimitive) {
1964
- return [`${JSON.stringify(name)}:[${node.items.map(emitNode).join(",")}]`];
2240
+ function walkSafety(resource, path) {
2241
+ const out = {
2242
+ modifiers: [],
2243
+ modifierElements: [],
2244
+ intents: [],
2245
+ unreadableIntent: [],
2246
+ useReadings: [],
2247
+ datatypeUses: [],
2248
+ unreadableDatatypeUse: [],
2249
+ shadowed: [],
2250
+ arrayWrapped: [],
2251
+ unspellableInXml: [],
2252
+ negations: /* @__PURE__ */ new Set(),
2253
+ unreadableBoolean: [],
2254
+ nearMissCode: [],
2255
+ unreadableCode: []
2256
+ };
2257
+ walkComplex(resource, path, out, true);
2258
+ const uses = resolveDatatypeUses(out.useReadings);
2259
+ const intentKeys = /* @__PURE__ */ new Set();
2260
+ return {
2261
+ ...out,
2262
+ modifierElements: dedupeModifierElements(out.modifierElements),
2263
+ intents: out.intents.filter((read) => {
2264
+ const key = `${read.code} ${read.location}`;
2265
+ if (intentKeys.has(key)) return false;
2266
+ intentKeys.add(key);
2267
+ return true;
2268
+ }),
2269
+ unreadableIntent: [...new Set(out.unreadableIntent)],
2270
+ datatypeUses: uses.uses,
2271
+ unreadableDatatypeUse: uses.unreadable
2272
+ };
2273
+ }
2274
+ function unspellableInXml(name, value) {
2275
+ if (!isList(value)) return false;
2276
+ return name === "resourceType" || value.items.length < 2;
2277
+ }
2278
+ function checkArrayWrapping(node, path, report) {
2279
+ const types = typesOf(node);
2280
+ const isSafetyType = types.some((type) => SAFETY_RESOURCE_TYPES.has(type));
2281
+ for (const property of [...node.properties, ...node.duplicates ?? []]) {
2282
+ const clinical = SAFETY_SCALAR_ELEMENTS.has(property.name);
2283
+ if (property.name !== "resourceType" && !(clinical && isSafetyType)) continue;
2284
+ const at = childPath(path, property.name);
2285
+ if (isList(property.value)) report(at, unspellableInXml(property.name, property.value));
2286
+ if (clinical && isSafetyType && SAFETY_CODEABLE_ELEMENTS.has(property.name)) {
2287
+ checkCodingWrapping(property.value, at, report);
2288
+ }
1965
2289
  }
1966
- const primitives = node.items.filter(isPrimitive);
1967
- const anyValue = primitives.some(hasValue);
1968
- const anyMeta = primitives.some(hasMeta);
1969
- const entries = [];
1970
- if (anyValue) {
1971
- const values = primitives.map(emitPrimitiveValue).join(",");
1972
- entries.push(`${JSON.stringify(name)}:[${values}]`);
2290
+ }
2291
+ function checkCodingWrapping(value, path, report) {
2292
+ const concepts = isList(value) ? value.items : [value];
2293
+ concepts.forEach((concept, conceptIndex) => {
2294
+ if (!isComplex(concept)) return;
2295
+ const base = isList(value) ? `${path}[${String(conceptIndex)}]` : path;
2296
+ for (const member of getAllProperties(concept, "coding")) {
2297
+ const codings = isList(member) ? member.items : [member];
2298
+ codings.forEach((coding, index) => {
2299
+ if (!isComplex(coding)) return;
2300
+ const at = isList(member) ? `${base}.coding[${String(index)}]` : `${base}.coding`;
2301
+ for (const name of CODING_SCALAR_ELEMENTS) {
2302
+ const members = getAllProperties(coding, name);
2303
+ if (members.some((scalar) => isList(scalar))) {
2304
+ report(
2305
+ `${at}.${name}`,
2306
+ members.some((scalar) => unspellableInXml(name, scalar))
2307
+ );
2308
+ }
2309
+ }
2310
+ });
2311
+ }
2312
+ });
2313
+ }
2314
+ var CODING_SCALAR_ELEMENTS = ["system", "code"];
2315
+ function checkNegations(node, path, report, out) {
2316
+ const written = getAllProperties(node, "doNotPerform");
2317
+ if (written.length > 0) {
2318
+ const at = childPath(path, "doNotPerform");
2319
+ if (written.some((value) => primitiveBooleans(value).includes(true))) {
2320
+ out.negations.add("do-not-perform");
2321
+ }
2322
+ if (written.some(hasUnreadableBoolean)) out.unreadableBoolean.push(at);
2323
+ }
2324
+ if (isRetracted(node)) out.negations.add(ENTERED_IN_ERROR);
2325
+ if (getAllProperties(node, "verificationStatus").some((v) => safetyHasCodeAnySystem(v, REFUTED))) {
2326
+ out.negations.add(REFUTED);
2327
+ }
2328
+ if (statusSpells(node, NOT_TAKEN)) out.negations.add(NOT_TAKEN);
2329
+ if (statusSpells(node, NOT_DONE)) out.negations.add(NOT_DONE);
2330
+ for (const read of NEGATION_CODE_READS) {
2331
+ const written2 = getAllProperties(node, read.element);
2332
+ if (written2.length === 0) continue;
2333
+ if (read.codings === true) {
2334
+ const at = childPath(path, read.element);
2335
+ for (const value of written2) checkCodingWrapping(value, at, report);
2336
+ }
2337
+ const spelled = written2.flatMap((value) => [...read.values(value)]);
2338
+ const near = read.codes.some(
2339
+ (code) => !spelled.includes(code) && spelled.some((value) => isNearMissCode(value, code))
2340
+ );
2341
+ if (near) out.nearMissCode.push(childPath(path, read.element));
2342
+ const unread = read.unread;
2343
+ if (unread !== void 0 && written2.some((value) => unread(value))) {
2344
+ out.unreadableCode.push(childPath(path, read.element));
2345
+ }
2346
+ }
2347
+ }
2348
+ function checkResourceRoot(node, path, out) {
2349
+ const reported = /* @__PURE__ */ new Set();
2350
+ const unspellable = /* @__PURE__ */ new Set();
2351
+ const report = (location, unwritable) => {
2352
+ if (!reported.has(location)) {
2353
+ reported.add(location);
2354
+ out.arrayWrapped.push(location);
2355
+ }
2356
+ if (unwritable && !unspellable.has(location)) {
2357
+ unspellable.add(location);
2358
+ out.unspellableInXml.push(location);
2359
+ }
2360
+ };
2361
+ checkArrayWrapping(node, path, report);
2362
+ checkNegations(node, path, report, out);
2363
+ }
2364
+ function walkComplex(node, path, out, isRoot = false) {
2365
+ if (isRoot || getAllProperties(node, "resourceType").length > 0) {
2366
+ checkResourceRoot(node, path, out);
1973
2367
  }
1974
- if (anyMeta) {
1975
- const metas = primitives.map((item) => hasMeta(item) ? emitMeta(item) : "null").join(",");
1976
- entries.push(`${JSON.stringify(`_${name}`)}:[${metas}]`);
2368
+ collectModifierElements(node, path, out.modifierElements);
2369
+ collectIntent(node, path, out.intents, out.unreadableIntent);
2370
+ collectDatatypeUses(node, path, out.useReadings);
2371
+ for (const property of node.properties) visitProperty(property, path, out);
2372
+ const reported = /* @__PURE__ */ new Set();
2373
+ for (const property of node.duplicates ?? []) {
2374
+ if (!reported.has(property.name)) {
2375
+ reported.add(property.name);
2376
+ out.shadowed.push(childPath(path, property.name));
2377
+ }
2378
+ visitProperty(property, path, out);
1977
2379
  }
1978
- return entries;
1979
2380
  }
1980
- function emitNode(node) {
1981
- switch (node.kind) {
1982
- case "complex":
1983
- return emitComplex(node);
1984
- case "list":
1985
- return `[${node.items.map(emitNode).join(",")}]`;
1986
- case "primitive":
1987
- return emitPrimitiveValue(node);
2381
+ function visitProperty(property, path, out) {
2382
+ if (property.name === "modifierExtension") {
2383
+ checkModifierExtension(property.value, path, out.modifiers);
1988
2384
  }
2385
+ descend(property.value, childPath(path, property.name), out);
1989
2386
  }
1990
- function emitComplex(node) {
1991
- if (node.nestedArraySource !== void 0) return node.nestedArraySource;
1992
- if (node.nonObjectSource !== void 0) return node.nonObjectSource;
1993
- const parts = [];
1994
- const rt = node.properties.find((p) => p.name === "resourceType");
1995
- const hoisted = rt !== void 0 && isPrimitive(rt.value) && typeof rt.value.value === "string";
1996
- if (hoisted) parts.push(`"resourceType":${JSON.stringify(rt.value.value)}`);
1997
- for (const property of node.properties) {
1998
- if (property.name === "resourceType" && hoisted) {
1999
- if (isPrimitive(property.value) && hasMeta(property.value)) {
2000
- parts.push(`"_resourceType":${emitMeta(property.value)}`);
2001
- }
2002
- continue;
2003
- }
2004
- const value = property.value;
2005
- switch (value.kind) {
2006
- case "primitive":
2007
- parts.push(...emitPrimitiveProperty(property.name, value));
2008
- break;
2009
- case "list":
2010
- parts.push(...emitListProperty(property.name, value));
2011
- break;
2012
- case "complex":
2013
- parts.push(`${JSON.stringify(property.name)}:${emitComplex(value)}`);
2014
- break;
2387
+ function checkModifierExtension(value, path, out) {
2388
+ const items = isList(value) ? value.items : [value];
2389
+ items.forEach((ext, index) => {
2390
+ const url = isComplex(ext) ? primitiveString(getProperty(ext, "url")) : void 0;
2391
+ if (url === void 0 || !KNOWN_MODIFIER_EXTENSION_URLS.has(url)) {
2392
+ out.push(
2393
+ isList(value) ? `${path}.modifierExtension[${String(index)}]` : `${path}.modifierExtension`
2394
+ );
2015
2395
  }
2016
- }
2017
- return `{${parts.join(",")}}`;
2396
+ });
2018
2397
  }
2019
- function serializeResource(node) {
2020
- assertSerializable(node);
2021
- assertNoShadowedProperty(node);
2022
- return emitComplex(node);
2398
+ function descend(node, path, out) {
2399
+ if (isComplex(node)) walkComplex(node, path, out);
2400
+ else if (isList(node))
2401
+ node.items.forEach((item, index) => descend(item, `${path}[${String(index)}]`, out));
2023
2402
  }
2024
-
2025
- // src/validate/issues.ts
2026
- var ISSUE_SEVERITIES = {
2027
- FATAL: "fatal",
2028
- ERROR: "error",
2029
- WARNING: "warning",
2030
- INFORMATION: "information"
2031
- };
2032
- var ISSUE_TYPES = {
2033
- /** Structural issue, an element that is not allowed here, or a cardinality-max violation. */
2034
- STRUCTURE: "structure",
2035
- /** A required element (min cardinality ≥ 1) is missing. */
2036
- REQUIRED: "required",
2037
- /** An element value is invalid against its datatype value-domain (a primitive-regex failure). */
2038
- VALUE: "value",
2039
- /** A code is not a member of a required-strength value set binding. */
2040
- CODE_INVALID: "code-invalid",
2041
- /** A content-validation rule (a resource `constraint` / invariant) failed. */
2042
- INVARIANT: "invariant",
2043
- /** The content uses a modifier the processor does not support and cannot safely ignore. */
2044
- NOT_SUPPORTED: "not-supported",
2045
- /** Informational only, carries no defect (e.g. "this resource type has no schema yet"). */
2046
- INFORMATIONAL: "informational",
2047
- /** A business rule / profile-level assertion failed (e.g. a declared profile version is unknown). */
2048
- BUSINESS_RULE: "business-rule",
2049
- /** A referenced resource could not be found within the resolution closure (a Bundle reference). */
2050
- NOT_FOUND: "not-found"
2051
- };
2052
- var VALIDATION_CODES = {
2053
- /** Layer 1, an element the resource's structure does not define at this location. */
2054
- UNKNOWN_ELEMENT: "UNKNOWN_ELEMENT",
2055
- /** Layer 1, the resource carries no `resourceType`, so it cannot be structurally validated. */
2056
- RESOURCE_TYPE_UNKNOWN: "RESOURCE_TYPE_UNKNOWN",
2057
- /** Layer 1, no schema is available for this resource type; structural layers were skipped. */
2058
- RESOURCE_NOT_MODELED: "RESOURCE_NOT_MODELED",
2059
- /** Layer 1, an element's node shape (primitive / complex) is not what its datatype expects. */
2060
- TYPE_MISMATCH: "TYPE_MISMATCH",
2061
- /** Layer 1, more than one variant of a `choice[x]` element is present. */
2062
- CHOICE_AMBIGUOUS: "CHOICE_AMBIGUOUS",
2063
- /** Layer 2, a required element (min ≥ 1) is absent. */
2064
- CARDINALITY_MIN: "CARDINALITY_MIN",
2065
- /** Layer 2, an element appears more times than its maximum cardinality allows. */
2066
- CARDINALITY_MAX: "CARDINALITY_MAX",
2067
- /** Layer 3, a primitive value does not match its datatype's lexical form. */
2068
- PRIMITIVE_INVALID: "PRIMITIVE_INVALID",
2069
- /** Layer 3, a `code` value is outside a required-strength enumerated binding. */
2070
- CODE_INVALID: "CODE_INVALID",
2071
- /**
2072
- * Safety, an element carries a `modifierExtension` this library does not understand.
2073
- * FHIR's `?!` rule forbids ignoring an unknown modifier, so this **fails closed** (an `error`): the
2074
- * element cannot be safely processed. See {@link ./safety.js}.
2075
- */
2076
- UNHANDLED_MODIFIER_EXTENSION: "UNHANDLED_MODIFIER_EXTENSION",
2077
- /**
2078
- * Safety, the document wrote a property name more than once, so an element holds several values
2079
- * and nothing says which the sender meant. FHIR JSON requires unique property names (json.html §2.6.2:
2080
- * "Property names SHALL be unique") and expresses repetition with an array, so this is a violated
2081
- * `SHALL` and an `error`. The reader keeps every value (see {@link ../model/node.js} `duplicates`),
2082
- * so this reports an ambiguity, never a loss.
2083
- */
2084
- DUPLICATE_PROPERTY: "DUPLICATE_PROPERTY",
2085
- /**
2086
- * Safety, a single-valued (`0..1`) safety element, or `resourceType`, arrived wrapped in a JSON
2087
- * array. FHIR JSON writes a single-valued element as a name/value pair and uses an array only for a
2088
- * repeating element (json.html §2.6.2.2), so this is a non-conformant encoding and an `error`. It is the
2089
- * shape a **generic XML-to-JSON converter** produces for every element, which is how a C-CDA or v2
2090
- * feed commonly reaches a FHIR surface, and left unreported it reaches the same harm as a repeated
2091
- * property name: a single-value read finds no code in the array, so a retraction or a negation the
2092
- * sender wrote goes unreported and the record reads live. Nothing is lost, the wrapper is preserved
2093
- * and the safety layer reads through it; this reports that the encoding was ambiguous.
2094
- */
2095
- ARRAY_WRAPPED_SCALAR: "ARRAY_WRAPPED_SCALAR",
2096
- /**
2097
- * Safety, the document wrote a JSON array **inside another array**. FHIR JSON uses an array for a
2098
- * repeating element and for nothing else (json.html §2.6.2.2), so a list of lists has no meaning at
2099
- * any position and this is a non-conformant encoding wherever it appears, which is why it needs no
2100
- * cardinality rule and cannot fire on a conformant document. Reported at every position the model
2101
- * has a node for; a `_`-sibling the reader discards whole is the stated exception, and draws a
2102
- * reader warning instead: `UNKNOWN_PROPERTY` for an unrecognised member of a `_`-sibling object,
2103
- * `MISPLACED_PRIMITIVE_EXTENSION` for a sibling on an object or on a non-primitive array, not one
2104
- * code for all three. An `error`, and one of the two on this list
2105
- * where the reader could **not model** what the sender wrote (the other is
2106
- * {@link VALIDATION_CODES.DROPPED_ELEMENT_TEXT}): the codec does not model an inner array, so this
2107
- * reports a loss of *structure* rather than an ambiguity, though the array's JSON text is kept and
2108
- * readable (see {@link ../model/node.js} `nestedArrayContent`). Left unreported it is among the worst of the
2109
- * set, because the model then looks exactly like an element that was legitimately absent, and a
2110
- * refuted allergy, a resolved condition, or an entire resource inside a Bundle entry reads back as
2111
- * a clean document. Value-free, the position the inner array occupied, never its contents.
2112
- */
2113
- NESTED_ARRAY: "NESTED_ARRAY",
2114
- /**
2115
- * Safety, an XML document wrote **character data directly on a FHIR element**. FHIR XML carries a
2116
- * primitive's value in the `value` attribute (xml.html §2.6.1), so text written as element content
2117
- * has no slot on the model and the reader drops it: `<status>entered-in-error</status>` yields a
2118
- * `status` with no value. An `error`, and the only code on this list where the content is neither
2119
- * modeled **nor kept**: unlike {@link VALIDATION_CODES.NESTED_ARRAY}, which preserves the array's
2120
- * JSON text, the character data is discarded outright, because reading it back would be a
2121
- * tolerance for a non-conformant encoding rather than a report of one. Left unreported it reaches the same harm as
2122
- * {@link VALIDATION_CODES.NESTED_ARRAY} by the other wire format, because the model is again
2123
- * indistinguishable from an element that was legitimately absent: a retraction, a `refuted`
2124
- * verification status, or a dose *number* beside a surviving unit and UCUM code all read back as a
2125
- * clean document. Value-free, the position the text occupied, never its contents.
2126
- */
2127
- DROPPED_ELEMENT_TEXT: "DROPPED_ELEMENT_TEXT",
2128
- /**
2129
- * Safety, an element carries a DataAbsentReason extension **and a value of its own**, so the
2130
- * document asserts both that the element holds that value and that it holds none. An `error`
2131
- * (`structure`): the two cannot both be true, nothing here ranks them, and a consumer that
2132
- * happened to read one of the two would report the record as though the other had not been
2133
- * written. Both survive on the model and on the safety readout; this reports that they disagree.
2134
- * Value-free, the location of the element, never the value and never the reason.
2135
- *
2136
- * Cannot fire on a conformant document: an element the sender has data for is written with the
2137
- * data and no marker. It is **not** the `Observation.dataAbsentReason` ELEMENT beside a `value[x]`,
2138
- * which is the `obs-6` invariant and reports as {@link VALIDATION_CODES.INVARIANT_VIOLATED}; the
2139
- * element is not the extension, and the two never report about one another's shape.
2140
- */
2141
- ABSENCE_MARKER_CONFLICT: "ABSENCE_MARKER_CONFLICT",
2142
- /**
2143
- * Safety, an element carries a DataAbsentReason extension whose reason this library could not
2144
- * read: no `valueCode`, one holding no readable string, an empty one, one written twice, or a code
2145
- * outside the closed fifteen-concept value set the extension's `value[x]` binds to at **required**
2146
- * strength. An `error` (`code-invalid`), on the same footing as any other required-binding miss:
2147
- * the set is closed and published, so membership is decided from the set itself with no
2148
- * terminology service involved and no value-set expansion.
2149
- *
2150
- * **Nothing is coerced.** The code is not trimmed, case-folded or substituted, and the element is
2151
- * not read as `unknown` and not read as populated: doing either would author a reason the sender
2152
- * did not spell, or erase a declaration the sender did make. The element stays value-absent and
2153
- * this is the record that a declaration was made and could not be honoured. Value-free, the
2154
- * location of the element, never the code that failed to match.
2155
- */
2156
- ABSENCE_MARKER_UNREADABLE: "ABSENCE_MARKER_UNREADABLE",
2157
- /**
2158
- * Safety, the resource is marked `entered-in-error` and is therefore **retracted, not
2159
- * data**. Surfaced as `information` (it is not itself a defect) so a consumer cannot miss it.
2160
- */
2161
- RETRACTED_RESOURCE: "RETRACTED_RESOURCE",
2162
- /**
2163
- * Safety, a named resource invariant failed (`ait-1`/`ait-2`, `con-3`/`con-4`/`con-5`,
2164
- * `obs-6`/`obs-7`). The specific constraint key travels in {@link ValidationIssue.constraint}, and
2165
- * the severity mirrors the constraint's own (`error`, except the best-practice `con-3` → `warning`).
2166
- */
2167
- INVARIANT_VIOLATED: "INVARIANT_VIOLATED",
2168
- /**
2169
- * Invariant, a profile `constraint`'s FHIRPath `expression` is **outside the bounded
2170
- * engine's subset** and could not be evaluated. Always `information` (`informational`):
2171
- * the constraint is reported **unchecked, never assumed to pass** (fail-safe), the
2172
- * library does not claim conformance to an invariant it could not test. The constraint `key` travels
2173
- * in {@link ValidationIssue.constraint}. Value-free (the location + key, never an instance value).
2174
- */
2175
- INVARIANT_UNCHECKED: "INVARIANT_UNCHECKED",
2176
- /**
2177
- * Quantity/UCUM, a `Quantity` claims the UCUM `system` but its `code` is absent or not a
2178
- * shape-valid UCUM expression, so the unit cannot be trusted for machine use. A `warning`
2179
- * (`value`): the value is **preserved verbatim and never converted**, the library does not bundle
2180
- * UCUM content, so it cannot assert the code *is* a real unit, only that it is present and well-shaped.
2181
- */
2182
- UCUM_UNIT_UNRECOGNIZED: "UCUM_UNIT_UNRECOGNIZED",
2183
- /**
2184
- * Quantity/UCUM, a vital-signs Observation's measured value carries a unit the FHIR
2185
- * vital-signs profile forbids for that LOINC code (wrong UCUM `code`, or a non-UCUM `system`). An
2186
- * `error` (`code-invalid`): the vital-signs profile *requires* the unit, so a nonconformant one is a
2187
- * profile violation, compared on the UCUM `code` (case- and bracket-sensitive), never the `unit` string.
2188
- */
2189
- VITAL_SIGN_UNIT_NONCONFORMANT: "VITAL_SIGN_UNIT_NONCONFORMANT",
2190
- /**
2191
- * Quantity/UCUM, an Observation whose profile expects a numeric `Quantity` value carries
2192
- * a different `value[x]` variant instead (e.g. `valueString`). A `warning` (`value`): the value is
2193
- * preserved and surfaced by its real type, a caller must not read it as a number.
2194
- */
2195
- VALUE_TYPE_UNEXPECTED: "VALUE_TYPE_UNEXPECTED",
2403
+ function readSafety(resource) {
2404
+ const types = typesOf(resource);
2405
+ const rt = types[0];
2406
+ const isType = (type) => types.includes(type);
2407
+ const status = primitiveStrings(getProperty(resource, "status"))[0];
2408
+ const clinicalStatus = safetyCodeOf(
2409
+ getProperty(resource, "clinicalStatus"),
2410
+ clinicalSystemFor(rt)
2411
+ );
2412
+ const verificationStatus = safetyCodeOf(
2413
+ getProperty(resource, "verificationStatus"),
2414
+ verificationSystemFor(rt)
2415
+ );
2416
+ const prefix = safetyPrefix(types);
2417
+ const walk2 = walkSafety(resource, prefix);
2418
+ const doNotPerform = readDoNotPerform(resource);
2419
+ const retracted = isRetracted(resource);
2420
+ const noKnownAllergy = isType("AllergyIntolerance") && getAllProperties(resource, "code").some(
2421
+ (node) => safetyHasCoding(node, SNOMED_SCT, NO_KNOWN_ALLERGY)
2422
+ );
2423
+ const found = new Set(walk2.negations);
2424
+ if (noKnownAllergy) found.add("no-known-allergy");
2425
+ const negations = NEGATION_ORDER.filter((kind) => found.has(kind));
2426
+ const { modifiers, shadowed, arrayWrapped, unreadableBoolean, nearMissCode, unreadableCode } = walk2;
2427
+ const modifierElementReports = rebaseModifierElements(walk2.modifierElements, prefix, types);
2428
+ const rebase = modifierLocationRebaser(prefix, types);
2429
+ const intents = walk2.intents.map((read) => ({
2430
+ code: read.code,
2431
+ location: rebase(read.location)
2432
+ }));
2433
+ const unreadableIntents = walk2.unreadableIntent.map(rebase);
2434
+ const datatypeUses = walk2.datatypeUses.map((read) => ({
2435
+ code: read.code,
2436
+ location: rebase(read.location)
2437
+ }));
2438
+ const unreadableDatatypeUses = walk2.unreadableDatatypeUse.map(rebase);
2439
+ const nested = nestedArrays(resource, prefix);
2440
+ const dropped = droppedText(resource, prefix);
2441
+ const absence = collectAbsence(resource, prefix);
2442
+ return {
2443
+ resourceType: rt === void 0 ? void 0 : rootPath(rt),
2444
+ status,
2445
+ clinicalStatus,
2446
+ verificationStatus,
2447
+ doNotPerform,
2448
+ retracted,
2449
+ noKnownAllergy,
2450
+ negations,
2451
+ unhandledModifierExtensions: modifiers,
2452
+ modifierElements: modifierElementReports,
2453
+ intents,
2454
+ datatypeUses,
2455
+ shadowedProperties: shadowed,
2456
+ arrayWrappedScalars: arrayWrapped,
2457
+ nestedArrays: nested,
2458
+ droppedText: dropped,
2459
+ unreadableBooleans: unreadableBoolean,
2460
+ nearMissNegationCodes: nearMissCode,
2461
+ unreadableNegationCodes: unreadableCode,
2462
+ unreadableIntents,
2463
+ unreadableDatatypeUses,
2464
+ absenceMarkers: absence.markers,
2465
+ unreadableAbsenceMarkers: absence.unreadable,
2466
+ conflictingAbsenceMarkers: absence.conflicting,
2467
+ safeToSummarize: modifiers.length === 0 && modifierElementReports.length === 0 && shadowed.length === 0 && arrayWrapped.length === 0 && nested.length === 0 && dropped.length === 0 && unreadableBoolean.length === 0 && nearMissCode.length === 0 && unreadableCode.length === 0 && unreadableIntents.length === 0 && unreadableDatatypeUses.length === 0 && absence.unreadable.length === 0 && absence.conflicting.length === 0
2468
+ };
2469
+ }
2470
+ var FhirSafetyError = class extends Error {
2471
+ /** FHIRPath locations that forced the refusal (value-free). */
2472
+ locations;
2196
2473
  /**
2197
- * Terminology, a bound coding's `system` URI is not in the frozen known-systems registry
2198
- * (and not one the binding's value set draws from). Always `information` (`code-invalid`): an
2199
- * unknown system may be a legitimate local/proprietary one, so it is never a defect, it only means
2200
- * the library cannot validate codes drawn from it. Content-free, so it can never flip validity.
2474
+ * @param locations - The FHIRPath locations that forced the refusal (value-free).
2201
2475
  */
2202
- CODE_SYSTEM_UNKNOWN: "CODE_SYSTEM_UNKNOWN",
2476
+ constructor(locations) {
2477
+ super(
2478
+ `Resource cannot be safely summarized: an unhandled modifierExtension, a modifier element, a repeated property name, an array-wrapped single-valued element, an array inside an array, dropped XML element text, a boolean value this library cannot read, a code that spells a negation bar its case or its surrounding whitespace, content where a code belongs, a use on an identifier, name, address or contact point this library cannot read, a declared absence whose reason this library cannot read, or a declared absence beside a value leaves an element this library must not flatten (${String(locations.length)} location(s)).`
2479
+ );
2480
+ this.name = "FhirSafetyError";
2481
+ this.locations = locations;
2482
+ }
2483
+ };
2484
+ function assertSafeToSummarize(resource) {
2485
+ const readout = "unhandledModifierExtensions" in resource ? resource : readSafety(resource);
2486
+ const locations = [
2487
+ ...readout.unhandledModifierExtensions,
2488
+ ...readout.modifierElements.map((report) => report.location),
2489
+ ...readout.shadowedProperties,
2490
+ ...readout.arrayWrappedScalars,
2491
+ ...readout.nestedArrays,
2492
+ ...readout.droppedText,
2493
+ ...readout.unreadableBooleans,
2494
+ ...readout.nearMissNegationCodes,
2495
+ ...readout.unreadableNegationCodes,
2496
+ // `intents` is deliberately absent, exactly as it is absent from `safeToSummarize`'s
2497
+ // conjunction: a readable intent is surfaced for the caller, and only an unreadable one refuses.
2498
+ ...readout.unreadableIntents,
2499
+ // `datatypeUses` is absent for the same reason `intents` is: a readable `use` is surfaced for
2500
+ // the caller, and only one this library cannot read refuses.
2501
+ ...readout.unreadableDatatypeUses,
2502
+ // The two absence channels that withdraw the affirmation. `absenceMarkers` is deliberately
2503
+ // absent from this list, exactly as it is absent from `safeToSummarize`'s conjunction: a
2504
+ // declaration the caller can read is a disclosure, not a loss.
2505
+ ...readout.unreadableAbsenceMarkers,
2506
+ ...readout.conflictingAbsenceMarkers
2507
+ ];
2508
+ if (locations.length > 0) throw new FhirSafetyError(locations);
2509
+ }
2510
+
2511
+ // src/codec/serialize-guard.ts
2512
+ var SERIALIZE_ERROR_CODES = {
2203
2513
  /**
2204
- * Terminology, a bound coding uses a **known** code `system` that is not one the
2205
- * binding's value set draws from (e.g. an ICD-10-CM code where the binding expects RxNorm + SNOMED).
2206
- * This is the content-free "wrong system for this binding" check, decided from the `system` alone,
2207
- * with no value-set content. Severity follows the binding strength (`required` → `error`;
2208
- * `extensible`/`preferred` → `warning`, since a different system may be a legitimate extension;
2209
- * `example` → none). Compared on the `system` URI, never a code value.
2514
+ * The model carries character data the XML reader dropped, at one or more locations. There is no
2515
+ * conformant encoding of it in either wire format, and emitting the element as unfilled would
2516
+ * launder the `DROPPED_ELEMENT_TEXT` finding across a round trip.
2210
2517
  */
2211
- CODE_SYSTEM_UNEXPECTED: "CODE_SYSTEM_UNEXPECTED",
2518
+ DROPPED_ELEMENT_TEXT: "DROPPED_ELEMENT_TEXT",
2212
2519
  /**
2213
- * Terminology, a configured terminology service reported that a bound coding's
2214
- * `(system, code)` is **not a member** of the binding's value set. Severity follows the binding
2215
- * strength (`required`/`extensible` → `error`; `preferred` → `warning`; `example` → `information`,
2216
- * never an error). Emitted **only** when a service definitively answers `not-in`; with no service,
2217
- * or an `unknown` answer, the library degrades to the content-free system checks and never
2218
- * false-errors (fail-safe). Value-free, the coding location, never the code itself.
2520
+ * The model carries a name that cannot occupy the `Name` slot of an XML start tag, so writing it
2521
+ * would emit markup that does not re-read as the element the model holds. **XML only**: JSON
2522
+ * escapes a member name, so this refusal never reaches it and that route stays open. **Narrowed
2523
+ * 2026-08-07 from "encodes every one of these correctly", which was false and shipped**: a model
2524
+ * refused here can carry one of `serializeResource`'s own declared exceptions and emit that.
2525
+ *
2526
+ * See {@link breaksTag} for the exact predicate and for what is deliberately NOT refused.
2219
2527
  */
2220
- CODE_NOT_IN_VALUESET: "CODE_NOT_IN_VALUESET",
2528
+ UNSERIALIZABLE_ELEMENT_NAME: "UNSERIALIZABLE_ELEMENT_NAME",
2221
2529
  /**
2222
- * Profile, an instance element is present under a **closed** slicing whose discriminators
2223
- * matched none of the profile's defined slices. A `structure` `error`: `closed` slicing forbids
2224
- * content outside the named slices. (Under `open` slicing an unmatched element is allowed and draws
2225
- * nothing; under `openAtEnd` it is allowed only in the trailing position, this library flags a
2226
- * closed-slicing miss and leaves the ordering nuance unenforced.)
2530
+ * A `div` property carries a string the XML writer would emit as raw markup, and that string does
2531
+ * not contribute exactly one element named `div` to the document. **XML only**: `serializeResource`
2532
+ * carries the string as a string, so this refusal never reaches it and that route stays open.
2533
+ *
2534
+ * **That is a statement about the `div` string, not about the whole model.** `serializeResource`
2535
+ * has its own declared non-spec-clean exceptions, so a model refused here can still route through
2536
+ * it and emit one of those: `{"text":{"div":""},"name":[[{"family":"X"}]]}` is refused here and
2537
+ * `serializeResource` emits the array inside an array unchanged.
2538
+ *
2539
+ * See `emitsOneDivElement` in `../xml/write.js` for the exact predicate and what it does not cover.
2227
2540
  */
2228
- PROFILE_SLICE_UNMATCHED: "PROFILE_SLICE_UNMATCHED",
2541
+ UNSERIALIZABLE_DIV_MARKUP: "UNSERIALIZABLE_DIV_MARKUP",
2229
2542
  /**
2230
- * Profile, a slicing whose discriminator this library cannot evaluate (a `profile`
2231
- * discriminator, which needs recursive profile resolution, or the R5-only `position`). Emitted as
2232
- * `information` so slice membership is reported **unchecked, never silently passed** (the
2233
- * fail-safe): the library does not guess a slice assignment it cannot justify.
2543
+ * The model carries, at one or more locations, a shape the JSON reader marked because FHIR JSON
2544
+ * gives that position no meaning: an array inside an array, a scalar or `null` where FHIR JSON has
2545
+ * an object (a complex element's own position or a primitive's `_`-sibling), or a `null` in a
2546
+ * primitive's value channel that padded nothing. **XML only**: this refusal does not reach
2547
+ * `serializeResource`, which writes these back from the text the reader preserved at every position
2548
+ * that writer walks. **It does not walk a member a repeated property name shadowed, and this
2549
+ * refusal does reach one** -- `serializeResource` refuses that model on
2550
+ * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_SHADOWED_PROPERTY} rather than emitting it, so it is
2551
+ * still not a route the shape survives. The XML writer keeps reporting THIS code on such a model,
2552
+ * because this one is raised first. See {@link assertXmlSerializable}.
2553
+ *
2554
+ * XML has no array-of-arrays, no `_`-sibling and no `null`, so the XML writer has nowhere to put
2555
+ * any of it and emits the element the reader was left holding: an empty one, or none. That output
2556
+ * re-reads with an empty issue list, so the finding the reader raised is gone after one trip.
2557
+ *
2558
+ * See {@link assertXmlSerializable} for the exact set of markers and for what it does NOT cover.
2234
2559
  */
2235
- PROFILE_SLICE_UNCHECKED: "PROFILE_SLICE_UNCHECKED",
2560
+ UNSERIALIZABLE_JSON_ONLY_SHAPE: "UNSERIALIZABLE_JSON_ONLY_SHAPE",
2236
2561
  /**
2237
- * Profile, an element the profile marks **must-support** is absent from the instance.
2238
- * **Always `information`, never an error** (the fail-safe, and the single most important
2239
- * must-support rule): must-support is a *system obligation* on the sender to be able to populate the
2240
- * element and on the receiver to tolerate its absence, it is **not** an instance-presence
2241
- * requirement. A strict client that errors on an absent must-support element is the classic bug this
2242
- * code exists to avoid.
2562
+ * The model carries, at a location this library already reports as an array-wrapped `0..1` safety
2563
+ * element, a wrapper FHIR XML has no repetition to spell back: one holding fewer than two items,
2564
+ * or any wrapper at all on `resourceType`. **XML only**: `serializeResource` writes the list back
2565
+ * and the re-read reports the same location, so that route stays open.
2566
+ *
2567
+ * XML spells a repeat by repeating the element and has no other mark for one, so a wrapper of
2568
+ * fewer than two items emits at most one element and re-reads as an ordinary single-valued
2569
+ * element. The encoding complaint the reader raised is then gone, and with it an `error`-severity
2570
+ * `ARRAY_WRAPPED_SCALAR` and a `safeToSummarize: false`.
2571
+ *
2572
+ * See {@link assertXmlArrayWrapper} for the exact predicate and for what it deliberately leaves.
2243
2573
  */
2244
- MUST_SUPPORT_ABSENT: "MUST_SUPPORT_ABSENT",
2574
+ UNSERIALIZABLE_ARRAY_WRAPPER: "UNSERIALIZABLE_ARRAY_WRAPPER",
2245
2575
  /**
2246
- * Profile, the instance's `meta.profile` declares a profile at a version the supplied
2247
- * profile set does not carry (`canonical|version` with a different version, or an unresolvable
2248
- * canonical). A `warning` (`business-rule`): an unknown profile
2249
- * version is flagged rather than silently best-effort-validating against a different one.
2576
+ * The model carries, at a location this library already reports as an array-wrapped
2577
+ * `Observation.value[x]` choice, a wrapper FHIR XML has no repetition to spell back: one holding
2578
+ * fewer than two items. **XML only**: `serializeResource` writes the list back and the re-read
2579
+ * reports the same location, so that route stays open.
2580
+ *
2581
+ * The same harm as {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ARRAY_WRAPPER}, at the position
2582
+ * where the number is a DOSE: `{"resourceType":"Observation","status":"final","valueQuantity":
2583
+ * [{"value":5,"system":"http://unitsofmeasure.org","code":"mg"}]}` reads with the encoding
2584
+ * reported and no magnitude handed out, and used to come back from XML as an unambiguous 5 mg
2585
+ * nothing had complained about.
2586
+ *
2587
+ * See {@link assertXmlValueChoiceWrapper} for the window and for what it deliberately leaves.
2250
2588
  */
2251
- PROFILE_VERSION_MISMATCH: "PROFILE_VERSION_MISMATCH",
2589
+ UNSERIALIZABLE_CHOICE_WRAPPER: "UNSERIALIZABLE_CHOICE_WRAPPER",
2252
2590
  /**
2253
- * Profile, an element carries a value that is not **exactly** the profile's `fixed[x]`.
2254
- * A `value` `error`: `fixed[x]` is an equality constraint (the element SHALL match the fixed value
2255
- * exactly, including every nested property). Compared structurally and precision-exactly (decimals
2256
- * via {@link ../model/decimal.js}), never by echoing the value.
2591
+ * The model carries, at one or more object elements, a member a repeated property name shadowed
2592
+ * ({@link ../model/node.js} `duplicates`). **Both writers**, unlike the four refusals above it:
2593
+ * each walks `properties` only, so each wrote one member per name and the second value left the
2594
+ * document with no diagnostic on it.
2595
+ *
2596
+ * See {@link assertNoShadowedProperty} for the window, the two routes weighed, and what it leaves.
2257
2597
  */
2258
- PROFILE_FIXED_MISMATCH: "PROFILE_FIXED_MISMATCH",
2598
+ UNSERIALIZABLE_SHADOWED_PROPERTY: "UNSERIALIZABLE_SHADOWED_PROPERTY",
2259
2599
  /**
2260
- * Profile, an element does not match the profile's `pattern[x]`. A `value` `error`:
2261
- * `pattern[x]` is a **subset** constraint (the element SHALL contain *at least* the pattern's
2262
- * properties and values, but may carry more), the weaker sibling of `fixed[x]`. Value-free.
2600
+ * The model carries, at one or more elements, a first `resourceType` that is not a string. FHIR
2601
+ * XML has no `resourceType` element at all -- the type IS the tag -- so the XML writer skipped the
2602
+ * property, and at the root, with no string to name the tag, it wrote `Resource` instead: the
2603
+ * member deleted and the element named a type nobody wrote. **XML only**: `serializeResource` emits a
2604
+ * non-string `resourceType` through its ordinary path, so this refusal never reaches it and that
2605
+ * route stays open.
2606
+ *
2607
+ * See {@link assertXmlResourceType} for the window and for what it deliberately leaves.
2263
2608
  */
2264
- PROFILE_PATTERN_MISMATCH: "PROFILE_PATTERN_MISMATCH",
2609
+ UNSERIALIZABLE_RESOURCE_TYPE: "UNSERIALIZABLE_RESOURCE_TYPE",
2265
2610
  /**
2266
- * Bundle, a `Reference` inside a Bundle entry (or a `#fragment` inside a resource's
2267
- * `contained`) that could not be resolved within the resolution closure: a fragment whose target
2268
- * contained resource is absent, or a relative `Type/id` reference naming no entry in the Bundle. A
2269
- * `warning` (`not-found`) and **never fatal**, the target may legitimately live outside the
2270
- * supplied closure (a partial Bundle, an external server), so the reference is **preserved**, only
2271
- * flagged. An absolute/logical reference that is simply external to the Bundle draws **no** finding.
2272
- * Value-free, the FHIRPath location of the reference, never the reference string itself.
2611
+ * The model carries, at one or more elements, a root the XML reader read out of a vocabulary that
2612
+ * resolved to something other than FHIR's. **XML only**: `serializeResource` spells a member name
2613
+ * and never a namespace, so it emits the model exactly as it did before this refusal existed and
2614
+ * that route stays open -- which is a statement about that writer's output, **not** a claim that
2615
+ * the JSON channel keeps the flag. It does not, and that half is declared open.
2616
+ *
2617
+ * FHIR XML puts the resource in the FHIR namespace, and the writer has no vendor binding to write
2618
+ * instead, so it emitted a document in the FHIR namespace with no trace of the one the sender
2619
+ * wrote. That output re-reads with an **empty** issue list, so the one warning that said the
2620
+ * document came from another vocabulary is gone after a single trip and the vendor document is
2621
+ * indistinguishable from FHIR.
2622
+ *
2623
+ * See {@link assertXmlForeignRoot} for the window, the two routes weighed, and what it costs.
2273
2624
  */
2274
- REFERENCE_UNRESOLVED: "REFERENCE_UNRESOLVED",
2625
+ UNSERIALIZABLE_FOREIGN_ROOT: "UNSERIALIZABLE_FOREIGN_ROOT",
2275
2626
  /**
2276
- * Bundle, the `#fragment` references among a resource's `contained` resources form a
2277
- * **cycle** (a → b → a, or a self-reference). An `error` (`structure`): a containment cycle is
2278
- * malformed and, to a naive transitive resolver, a denial-of-service (an unbounded loop / stack
2279
- * blow-up). The bounded, iterative cycle guard detects it and reports it here rather than looping,
2280
- * DoS-safe by construction. Value-free, the location of the `contained` element, never a value.
2627
+ * The model holds, at one or more tag positions, a name carrying a colon. XML reads the colon as
2628
+ * a namespace prefix, and the model carries no namespace binding for the writer to declare one
2629
+ * with, so the document would not be namespace-well-formed. **XML only**: `serializeResource`
2630
+ * spells a member name as a JSON string, so this refusal never reaches it and that route stays
2631
+ * open.
2632
+ *
2633
+ * Written anyway, the output named a prefix nothing declared, which a conformant XML parser
2634
+ * rejects, and a prefix rebound between siblings (`<p:x xmlns:p="urn:a"/>` beside
2635
+ * `<p:x xmlns:p="urn:b"/>`) lost its `MIXED_XML_SPELLING` report across one write and one re-read.
2636
+ * **Not {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ELEMENT_NAME}**, whose line is whether this
2637
+ * library's own round trip survives a name: a colon-bearing name survives that round trip, so the
2638
+ * two are different questions and keep different codes. A name that trips both keeps that one.
2639
+ *
2640
+ * See {@link carriesUndeclarablePrefix} for the exact predicate, its one exemption, and what it
2641
+ * leaves.
2281
2642
  */
2282
- CONTAINED_CYCLE: "CONTAINED_CYCLE",
2643
+ UNSERIALIZABLE_PREFIXED_NAME: "UNSERIALIZABLE_PREFIXED_NAME",
2283
2644
  /**
2284
- * Bundle, a Bundle entry's `fullUrl` is a RESTful URL (relative `Type/id` or an absolute
2285
- * URL ending in `Type/id`) whose id disagrees with the entry `resource.id`. An `error`
2286
- * (`business-rule`): FHIR requires a RESTful `fullUrl` to be consistent with the resource it wraps,
2287
- * and a disagreement can cause a reference to resolve to the wrong resource. A `urn:uuid:` (logical)
2288
- * `fullUrl` places no constraint on `resource.id`, so it never triggers this. Value-free, the
2289
- * location of the `fullUrl`, never either id.
2290
- */
2291
- FULLURL_ID_MISMATCH: "FULLURL_ID_MISMATCH"
2292
- };
2293
- var ISSUE_TYPE_OF = {
2294
- UNKNOWN_ELEMENT: ISSUE_TYPES.STRUCTURE,
2295
- RESOURCE_TYPE_UNKNOWN: ISSUE_TYPES.STRUCTURE,
2296
- RESOURCE_NOT_MODELED: ISSUE_TYPES.INFORMATIONAL,
2297
- TYPE_MISMATCH: ISSUE_TYPES.STRUCTURE,
2298
- CHOICE_AMBIGUOUS: ISSUE_TYPES.STRUCTURE,
2299
- CARDINALITY_MIN: ISSUE_TYPES.REQUIRED,
2300
- CARDINALITY_MAX: ISSUE_TYPES.STRUCTURE,
2301
- PRIMITIVE_INVALID: ISSUE_TYPES.VALUE,
2302
- CODE_INVALID: ISSUE_TYPES.CODE_INVALID,
2303
- UNHANDLED_MODIFIER_EXTENSION: ISSUE_TYPES.NOT_SUPPORTED,
2304
- DUPLICATE_PROPERTY: ISSUE_TYPES.STRUCTURE,
2305
- ARRAY_WRAPPED_SCALAR: ISSUE_TYPES.STRUCTURE,
2306
- NESTED_ARRAY: ISSUE_TYPES.STRUCTURE,
2307
- DROPPED_ELEMENT_TEXT: ISSUE_TYPES.STRUCTURE,
2308
- ABSENCE_MARKER_CONFLICT: ISSUE_TYPES.STRUCTURE,
2309
- ABSENCE_MARKER_UNREADABLE: ISSUE_TYPES.CODE_INVALID,
2310
- RETRACTED_RESOURCE: ISSUE_TYPES.INFORMATIONAL,
2311
- INVARIANT_VIOLATED: ISSUE_TYPES.INVARIANT,
2312
- INVARIANT_UNCHECKED: ISSUE_TYPES.INFORMATIONAL,
2313
- UCUM_UNIT_UNRECOGNIZED: ISSUE_TYPES.VALUE,
2314
- VITAL_SIGN_UNIT_NONCONFORMANT: ISSUE_TYPES.CODE_INVALID,
2315
- VALUE_TYPE_UNEXPECTED: ISSUE_TYPES.VALUE,
2316
- CODE_SYSTEM_UNKNOWN: ISSUE_TYPES.CODE_INVALID,
2317
- CODE_SYSTEM_UNEXPECTED: ISSUE_TYPES.CODE_INVALID,
2318
- CODE_NOT_IN_VALUESET: ISSUE_TYPES.CODE_INVALID,
2319
- PROFILE_SLICE_UNMATCHED: ISSUE_TYPES.STRUCTURE,
2320
- PROFILE_SLICE_UNCHECKED: ISSUE_TYPES.INFORMATIONAL,
2321
- MUST_SUPPORT_ABSENT: ISSUE_TYPES.INFORMATIONAL,
2322
- PROFILE_VERSION_MISMATCH: ISSUE_TYPES.BUSINESS_RULE,
2323
- PROFILE_FIXED_MISMATCH: ISSUE_TYPES.VALUE,
2324
- PROFILE_PATTERN_MISMATCH: ISSUE_TYPES.VALUE,
2325
- REFERENCE_UNRESOLVED: ISSUE_TYPES.NOT_FOUND,
2326
- CONTAINED_CYCLE: ISSUE_TYPES.STRUCTURE,
2327
- FULLURL_ID_MISMATCH: ISSUE_TYPES.BUSINESS_RULE
2645
+ * The model holds, at one or more `div` locations, a string the XML writer would splice in as raw
2646
+ * markup that spells the one `div` element the property names, and whose markup names a namespace
2647
+ * prefix, on an element or on an attribute, that no declaration inside the string binds. **XML
2648
+ * only**: `serializeResource` carries the string as a string, so this refusal never reaches it and
2649
+ * that route stays open.
2650
+ *
2651
+ * Written anyway, `<v:div>x</v:div>` put a prefix nothing declared into the output, which a
2652
+ * conformant XML parser rejects, and this library's own re-read of that output turned the narrative
2653
+ * into a property named `v:div`: the narrative gone after one write and one read, with no
2654
+ * diagnostic at either end. **Not {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_PREFIXED_NAME}**,
2655
+ * whose published meaning is a name at a tag position, and **not
2656
+ * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_DIV_MARKUP}**, whose question is which elements the
2657
+ * string contributes: a string that fails that question keeps that code whatever prefix it names.
2658
+ *
2659
+ * See `bindsEveryPrefix` in `../xml/write.js` for the exact predicate and what it leaves.
2660
+ *
2661
+ * @example
2662
+ * ```ts
2663
+ * import {
2664
+ * FhirSerializeError,
2665
+ * SERIALIZE_ERROR_CODES,
2666
+ * parseResource,
2667
+ * serializeResourceXml,
2668
+ * } from "@cosyte/fhir";
2669
+ * const { resource } = parseResource(
2670
+ * '{"resourceType":"Patient","text":{"status":"generated","div":"<v:div>x</v:div>"}}',
2671
+ * );
2672
+ * try {
2673
+ * serializeResourceXml(resource);
2674
+ * } catch (err) {
2675
+ * if (err instanceof FhirSerializeError && err.code === SERIALIZE_ERROR_CODES.UNSERIALIZABLE_DIV_PREFIX) {
2676
+ * err.locations; // ["Patient.text.div"]
2677
+ * }
2678
+ * }
2679
+ * ```
2680
+ */
2681
+ UNSERIALIZABLE_DIV_PREFIX: "UNSERIALIZABLE_DIV_PREFIX",
2682
+ /**
2683
+ * The model holds, at one or more tag positions, a name that is not an XML 1.0 `Name` (production
2684
+ * [5]): `a&b`, `1abc`, `-x`, a name carrying U+0000 or an unpaired surrogate. A conforming XML
2685
+ * processor must reject such a document as a fatal error. **XML only**: `serializeResource` spells
2686
+ * a member name as a JSON string, so this refusal never reaches it and that route stays open.
2687
+ *
2688
+ * Written anyway, `<a&b value="v"/>` was a document this library's own reader read back unchanged
2689
+ * and a third-party parser rejected, which is why the tag-breaking refusal, whose line is this
2690
+ * library's own round trip, never reached it. **Not
2691
+ * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ELEMENT_NAME}** and **not
2692
+ * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_PREFIXED_NAME}**: a name that breaks the tag, or
2693
+ * carries a colon, keeps the code it drew before this one existed, whether or not it is a `Name`.
2694
+ * A name carrying a character outside `Char` is not a `Name`, so it draws this code rather than
2695
+ * {@link SERIALIZE_ERROR_CODES.UNSERIALIZABLE_XML_CHARACTER}.
2696
+ *
2697
+ * See {@link isXmlName} for the exact predicate and what it leaves.
2698
+ *
2699
+ * @example
2700
+ * ```ts
2701
+ * import {
2702
+ * FhirSerializeError,
2703
+ * SERIALIZE_ERROR_CODES,
2704
+ * parseResource,
2705
+ * serializeResourceXml,
2706
+ * } from "@cosyte/fhir";
2707
+ * const { resource } = parseResource('{"resourceType":"Patient","1abc":"v"}');
2708
+ * try {
2709
+ * serializeResourceXml(resource);
2710
+ * } catch (err) {
2711
+ * if (err instanceof FhirSerializeError && err.code === SERIALIZE_ERROR_CODES.UNSERIALIZABLE_XML_NAME) {
2712
+ * err.locations; // ["Patient.<withheld>"]
2713
+ * }
2714
+ * }
2715
+ * ```
2716
+ */
2717
+ UNSERIALIZABLE_XML_NAME: "UNSERIALIZABLE_XML_NAME",
2718
+ /**
2719
+ * The model holds, at one or more locations, a value the XML writer would emit as an attribute
2720
+ * value (a primitive's `value`, an `id` written as an attribute, an `Extension.url`), or a `div`
2721
+ * string it would splice in, carrying a code point outside XML 1.0 `Char` (production [2]): U+0000
2722
+ * to U+0008, U+000B, U+000C, U+000E to U+001F, an unpaired surrogate, U+FFFE or U+FFFF. In a `div`
2723
+ * string that includes a numeric character reference denoting one (`&#0;`), which the Legal
2724
+ * Character constraint makes as fatal as the raw character; each reference is judged on its own, so
2725
+ * `&#xD83D;&#xDE00;` is two unpaired surrogates, not one character. **XML only**: `serializeResource`
2726
+ * writes these values as JSON strings, so this refusal never reaches it and that route stays open.
2727
+ *
2728
+ * Written anyway, a U+0000 went into the output raw and a conforming processor rejected the
2729
+ * document. **Refused, never repaired**: a reference to a non-`Char` is itself a fatal error, and
2730
+ * replacing or dropping the character would change a value the sender wrote, so the model is left
2731
+ * exactly as it was and no string is returned. Raised after every other refusal, the name code
2732
+ * included.
2733
+ *
2734
+ * See {@link carriesNonXmlCharacter} for the exact predicate.
2735
+ *
2736
+ * @example
2737
+ * ```ts
2738
+ * import {
2739
+ * FhirSerializeError,
2740
+ * SERIALIZE_ERROR_CODES,
2741
+ * parseResource,
2742
+ * serializeResourceXml,
2743
+ * } from "@cosyte/fhir";
2744
+ * const { resource } = parseResource('{"resourceType":"Patient","gender":"a\\u0000"}');
2745
+ * try {
2746
+ * serializeResourceXml(resource);
2747
+ * } catch (err) {
2748
+ * if (err instanceof FhirSerializeError && err.code === SERIALIZE_ERROR_CODES.UNSERIALIZABLE_XML_CHARACTER) {
2749
+ * err.locations; // ["Patient.gender"]
2750
+ * }
2751
+ * }
2752
+ * ```
2753
+ */
2754
+ UNSERIALIZABLE_XML_CHARACTER: "UNSERIALIZABLE_XML_CHARACTER"
2328
2755
  };
2329
- var DIAGNOSTIC_OF = {
2330
- UNKNOWN_ELEMENT: "Element is not defined at this location in the resource structure.",
2331
- RESOURCE_TYPE_UNKNOWN: "Resource is missing a resourceType and cannot be structurally validated.",
2332
- RESOURCE_NOT_MODELED: "No structural schema is available for this resource type; structural validation was skipped.",
2333
- TYPE_MISMATCH: "Element value is not of the shape its datatype requires.",
2334
- CHOICE_AMBIGUOUS: "More than one variant of a choice element is present.",
2335
- CARDINALITY_MIN: "Required element is missing.",
2336
- CARDINALITY_MAX: "Element appears more times than its maximum cardinality allows.",
2337
- PRIMITIVE_INVALID: "Primitive value does not match the required lexical form for its datatype.",
2338
- CODE_INVALID: "Code is not in the required value set for this element.",
2339
- UNHANDLED_MODIFIER_EXTENSION: "Element carries a modifierExtension this processor does not understand; it cannot be safely processed and is rejected (fail-closed).",
2340
- DUPLICATE_PROPERTY: "Property name appears more than once on this object; FHIR requires unique property names and uses an array for a repeating element, so the element's value is ambiguous.",
2341
- ARRAY_WRAPPED_SCALAR: "Single-valued element is wrapped in an array; FHIR JSON writes a 0..1 element as a name/value pair and uses an array only for a repeating element, so the element's encoding is ambiguous.",
2342
- NESTED_ARRAY: "A JSON array appears inside another array; FHIR JSON uses an array only for a repeating element, so this shape has no meaning and its contents were not read. Content the sender wrote is missing from the model at this position.",
2343
- DROPPED_ELEMENT_TEXT: "Character data was written directly on this element; FHIR XML carries a primitive's value in the value attribute, so an element has no slot for text and it was not read. Content the sender wrote is missing from the model at this position.",
2344
- ABSENCE_MARKER_CONFLICT: "Element carries a data-absent-reason extension and a value of its own; the document asserts both that the element holds that value and that it holds none, and neither is preferred here. Both are preserved.",
2345
- ABSENCE_MARKER_UNREADABLE: "Element carries a data-absent-reason extension whose reason could not be read: it is absent, empty, written more than once, or outside the value set the extension requires. The reason is not guessed and the element is not read as populated.",
2346
- RETRACTED_RESOURCE: "Resource is marked entered-in-error; it is retracted and must not be treated as active data.",
2347
- INVARIANT_VIOLATED: "A resource invariant (content-validation constraint) was violated.",
2348
- INVARIANT_UNCHECKED: "A resource invariant could not be evaluated by the bounded FHIRPath engine; it is reported unchecked rather than assumed to pass.",
2349
- UCUM_UNIT_UNRECOGNIZED: "Quantity declares the UCUM system but its unit code is absent or not a well-formed UCUM expression; the unit is preserved verbatim and never converted.",
2350
- VITAL_SIGN_UNIT_NONCONFORMANT: "Vital-signs measurement carries a unit the vital-signs profile does not allow for this code.",
2351
- VALUE_TYPE_UNEXPECTED: "Observation value is present but not the expected type for this profile.",
2352
- CODE_SYSTEM_UNKNOWN: "Coding uses a code system that is not in the known-systems registry; its codes cannot be validated (an unrecognized system is not itself an error).",
2353
- CODE_SYSTEM_UNEXPECTED: "Coding uses a code system that the bound value set does not draw from.",
2354
- CODE_NOT_IN_VALUESET: "Coding is not a member of the value set required by this element's binding.",
2355
- PROFILE_SLICE_UNMATCHED: "Element is present under closed slicing but matches none of the profile's defined slices.",
2356
- PROFILE_SLICE_UNCHECKED: "Slice membership could not be evaluated (an unsupported discriminator); it is reported unchecked rather than assumed to pass.",
2357
- MUST_SUPPORT_ABSENT: "Profile marks this element must-support; it is absent. Must-support is a system obligation, not an instance-presence requirement, this is informational, never an error.",
2358
- PROFILE_VERSION_MISMATCH: "Instance declares a profile at a version the supplied profile set does not carry; it was not validated against that exact version.",
2359
- PROFILE_FIXED_MISMATCH: "Element value does not exactly match the value the profile fixes for it.",
2360
- PROFILE_PATTERN_MISMATCH: "Element does not match the pattern the profile requires for it.",
2361
- REFERENCE_UNRESOLVED: "Reference could not be resolved within the supplied closure; it is preserved and flagged, never dropped (the target may live outside this Bundle).",
2362
- CONTAINED_CYCLE: "Contained resources reference each other in a cycle; the containment graph is malformed and was reported by the bounded cycle guard rather than followed into an unbounded loop.",
2363
- FULLURL_ID_MISMATCH: "Bundle entry fullUrl is a RESTful URL whose id disagrees with the wrapped resource's id."
2756
+ var FhirSerializeError = class extends Error {
2757
+ /** Which refusal this is. */
2758
+ code;
2759
+ /** The bounded FHIRPath locations the refusal is about, in walk order. Never document content. */
2760
+ locations;
2761
+ /**
2762
+ * @param message - A value-free description of the refusal.
2763
+ * @param code - Which refusal this is.
2764
+ * @param locations - The bounded FHIRPath locations it is about.
2765
+ */
2766
+ constructor(message, code, locations) {
2767
+ super(message);
2768
+ this.name = "FhirSerializeError";
2769
+ this.code = code;
2770
+ this.locations = locations;
2771
+ }
2364
2772
  };
2365
- function diagnosticFor(code) {
2366
- return DIAGNOSTIC_OF[code];
2773
+ function assertSerializable(node) {
2774
+ const locations = droppedText(node, rootPath(typeOf(node) ?? "Resource"));
2775
+ if (locations.length === 0) return;
2776
+ throw new FhirSerializeError(
2777
+ `cannot serialize: the reader dropped character data at ${String(locations.length)} location(s), which this model cannot encode`,
2778
+ SERIALIZE_ERROR_CODES.DROPPED_ELEMENT_TEXT,
2779
+ locations
2780
+ );
2781
+ }
2782
+ function carriesJsonOnlyShape(node) {
2783
+ if (node.kind === "list") return false;
2784
+ if (node.nestedArray === true || node.nestedArraySource !== void 0) return true;
2785
+ if (node.kind === "complex") return node.nonObjectSource !== void 0;
2786
+ return node.undefinedNull === true || node.nonObjectMetaSource !== void 0 || node.nestedArrayMetaSource !== void 0;
2787
+ }
2788
+ function assertXmlSerializable(node) {
2789
+ const locations = collectMarked(node, rootPath(typeOf(node) ?? "Resource"), carriesJsonOnlyShape);
2790
+ if (locations.length === 0) return;
2791
+ throw new FhirSerializeError(
2792
+ // Says only what this refusal does NOT reach, which is the wording the two refusals beside it
2793
+ // were narrowed to. It deliberately does not promise that `serializeResource` writes the shape
2794
+ // back: it does for every position that writer walks, and a marked node inside a member a
2795
+ // repeated property name shadowed is dropped by it as well.
2796
+ `cannot serialize to XML: ${String(locations.length)} location(s) carry a shape only FHIR JSON can spell, which XML has no channel for; this refusal does not reach serializeResource`,
2797
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_JSON_ONLY_SHAPE,
2798
+ locations
2799
+ );
2800
+ }
2801
+ function assertXmlArrayWrapper(node) {
2802
+ const locations = unspellableXmlWrappers(node, rootPath(typeOf(node) ?? "Resource"));
2803
+ if (locations.length === 0) return;
2804
+ throw new FhirSerializeError(
2805
+ // Says what this refusal does not reach, in the wording the three refusals beside it were
2806
+ // narrowed to. `serializeResource` does write these wrappers back -- pinned by the JSON half of
2807
+ // the same test -- with the shadowed-member limit stated in the docblock rather than here.
2808
+ `cannot serialize to XML: ${String(locations.length)} location(s) carry an array wrapper around a 0..1 element that XML has no repeated element to spell back; this refusal does not reach serializeResource`,
2809
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ARRAY_WRAPPER,
2810
+ locations
2811
+ );
2812
+ }
2813
+ function assertXmlValueChoiceWrapper(node) {
2814
+ const locations = unspellableXmlValueChoices(node, rootPath(typeOf(node) ?? "Resource"));
2815
+ if (locations.length === 0) return;
2816
+ throw new FhirSerializeError(
2817
+ // Says what this refusal does not reach, in the wording the refusals beside it were narrowed to.
2818
+ // `serializeResource` writes the wrapper back and the re-read reports the same location, pinned
2819
+ // by the JSON half of the same test.
2820
+ `cannot serialize to XML: ${String(locations.length)} location(s) carry an array wrapper around a 0..1 value[x] choice that XML has no repeated element to spell back; this refusal does not reach serializeResource`,
2821
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_CHOICE_WRAPPER,
2822
+ locations
2823
+ );
2824
+ }
2825
+ function assertNoShadowedProperty(node) {
2826
+ const locations = shadowedProperties(node, rootPath(typeOf(node) ?? "Resource"));
2827
+ if (locations.length === 0) return;
2828
+ throw new FhirSerializeError(
2829
+ // Names both writers, because both drop it: there is no route here that keeps the member, and a
2830
+ // message pointing at one would point an operator at a silent drop. That is the wording the four
2831
+ // refusals above were narrowed to, at the one refusal where the answer is "neither".
2832
+ `cannot serialize: ${String(locations.length)} location(s) carry a member a repeated property name shadowed, which neither serializeResource nor serializeResourceXml can write`,
2833
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_SHADOWED_PROPERTY,
2834
+ locations
2835
+ );
2836
+ }
2837
+ function lacksTaggableResourceType(node) {
2838
+ if (node.kind !== "complex") return false;
2839
+ const written = node.properties.find((property) => property.name === "resourceType");
2840
+ if (written === void 0) return false;
2841
+ return !(isPrimitive(written.value) && typeof written.value.value === "string");
2842
+ }
2843
+ function assertXmlResourceType(node) {
2844
+ const elements = collectMarked(
2845
+ node,
2846
+ rootPath(typeOf(node) ?? "Resource"),
2847
+ lacksTaggableResourceType
2848
+ );
2849
+ if (elements.length === 0) return;
2850
+ const locations = elements.map((element) => childPath(element, "resourceType"));
2851
+ throw new FhirSerializeError(
2852
+ // Says what this refusal does not reach, in the wording the refusals beside it were narrowed to.
2853
+ // `serializeResource` emits a non-string `resourceType` through its ordinary path -- pinned by
2854
+ // the JSON half of the same test -- which is a statement about this shape, not about the rest of
2855
+ // the model: that writer's own declared exceptions still apply to everything else it carries.
2856
+ // It does NOT say "while naming the element Resource": that substitution happens at the root,
2857
+ // and this refusal reports depths where the tag comes from the property name instead.
2858
+ `cannot serialize to XML: ${String(locations.length)} location(s) carry a resourceType with no string to name a tag with, which this writer would delete; this refusal does not reach serializeResource`,
2859
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_RESOURCE_TYPE,
2860
+ locations
2861
+ );
2862
+ }
2863
+ function assertXmlForeignRoot(node) {
2864
+ const locations = collectMarked(node, rootPath(typeOf(node) ?? "Resource"), isForeignRoot);
2865
+ if (locations.length === 0) return;
2866
+ throw new FhirSerializeError(
2867
+ // Says what this refusal does not reach, in the wording the refusals beside it were narrowed to.
2868
+ // It names no namespace, no tag and no value: the marker it fires on carries none of them, and
2869
+ // the vendor URI is the one piece of document content this whole residual is about.
2870
+ `cannot serialize to XML: ${String(locations.length)} location(s) hold a resource whose root named another vocabulary, which this writer would emit in the FHIR namespace; this refusal does not reach serializeResource`,
2871
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_FOREIGN_ROOT,
2872
+ locations
2873
+ );
2874
+ }
2875
+ var TAG_BREAKING = /[ \t\n\r/>=<]/u;
2876
+ var TAG_OPENER_STEALING = /* @__PURE__ */ new Set(["!", "?"]);
2877
+ function breaksTag(name) {
2878
+ return name === "" || TAG_BREAKING.test(name) || TAG_OPENER_STEALING.has(name.slice(0, 1));
2879
+ }
2880
+ function refuseUnserializableNames(locations) {
2881
+ throw new FhirSerializeError(
2882
+ `cannot serialize to XML: ${String(locations.length)} location(s) carry a name that cannot be written as an XML tag without changing which elements the document holds; serializeResource escapes a member name, so this refusal never reaches it`,
2883
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_ELEMENT_NAME,
2884
+ locations
2885
+ );
2886
+ }
2887
+ function refuseUnserializableDivMarkup(locations) {
2888
+ throw new FhirSerializeError(
2889
+ `cannot serialize to XML: ${String(locations.length)} div location(s) carry markup that would not be written as the one div element the model names; serializeResource carries the string as a string`,
2890
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_DIV_MARKUP,
2891
+ locations
2892
+ );
2893
+ }
2894
+ var XML_PREFIX = "xml:";
2895
+ function carriesUndeclarablePrefix(name) {
2896
+ if (!name.includes(":")) return false;
2897
+ const local = name.slice(XML_PREFIX.length);
2898
+ return !(name.startsWith(XML_PREFIX) && local !== "" && !local.includes(":"));
2899
+ }
2900
+ function refuseUndeclarablePrefixes(locations) {
2901
+ throw new FhirSerializeError(
2902
+ `cannot serialize to XML, because ${String(locations.length)} location(s) carry a name with a colon, which XML reads as a namespace prefix and this model carries no binding to declare, so the output would not be namespace-well-formed; this refusal does not reach serializeResource`,
2903
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_PREFIXED_NAME,
2904
+ locations
2905
+ );
2906
+ }
2907
+ function refuseUnboundDivPrefixes(locations) {
2908
+ throw new FhirSerializeError(
2909
+ `cannot serialize to XML, because ${String(locations.length)} div location(s) carry markup naming a namespace prefix that no declaration inside the string binds, so the output would not be namespace-well-formed; serializeResource carries the string as a string, so this refusal never reaches it`,
2910
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_DIV_PREFIX,
2911
+ locations
2912
+ );
2913
+ }
2914
+ var XML_NAME = /^[:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}][\u{300}-\u{36F}\-.0-9:A-Z_a-z\u{B7}\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{203F}-\u{2040}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}]*$/u;
2915
+ function isXmlName(name) {
2916
+ return XML_NAME.test(name);
2917
+ }
2918
+ function refuseNonXmlNames(locations) {
2919
+ throw new FhirSerializeError(
2920
+ `cannot serialize to XML, because ${String(locations.length)} location(s) carry a name that is not an XML 1.0 Name, so a conforming XML processor must reject the output; serializeResource escapes a member name, so this refusal never reaches it`,
2921
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_XML_NAME,
2922
+ locations
2923
+ );
2924
+ }
2925
+ var NON_XML_CHARACTER = /[^\t\n\r\u{20}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}]/u;
2926
+ function carriesNonXmlCharacter(text) {
2927
+ return NON_XML_CHARACTER.test(text);
2928
+ }
2929
+ function refuseNonXmlCharacters(locations) {
2930
+ throw new FhirSerializeError(
2931
+ `cannot serialize to XML, because ${String(locations.length)} location(s) carry a character outside the XML 1.0 Char production, which a conforming XML processor must reject and which this writer neither escapes, replaces nor drops; this refusal does not reach serializeResource`,
2932
+ SERIALIZE_ERROR_CODES.UNSERIALIZABLE_XML_CHARACTER,
2933
+ locations
2934
+ );
2935
+ }
2936
+
2937
+ // src/codec/write.ts
2938
+ function emitScalar(value) {
2939
+ if (value === void 0) return "null";
2940
+ if (typeof value === "string") return JSON.stringify(value);
2941
+ if (typeof value === "boolean") return value ? "true" : "false";
2942
+ return value.raw;
2943
+ }
2944
+ function emitPrimitiveValue(node) {
2945
+ return node.nestedArraySource ?? emitScalar(node.value);
2946
+ }
2947
+ function hasValue(node) {
2948
+ return node.value !== void 0 || node.nestedArraySource !== void 0 || node.undefinedNull === true;
2949
+ }
2950
+ function hasMeta(node) {
2951
+ return node.id !== void 0 || node.extension !== void 0 && node.extension.length > 0 || node.nestedArrayMetaSource !== void 0 || node.nonObjectMetaSource !== void 0;
2952
+ }
2953
+ function emitMeta(node) {
2954
+ if (node.nestedArrayMetaSource !== void 0) return node.nestedArrayMetaSource;
2955
+ if (node.nonObjectMetaSource !== void 0) return node.nonObjectMetaSource;
2956
+ const parts = [];
2957
+ if (node.id !== void 0) parts.push(`"id":${JSON.stringify(node.id)}`);
2958
+ if (node.extension !== void 0 && node.extension.length > 0) {
2959
+ parts.push(`"extension":[${node.extension.map(emitComplex).join(",")}]`);
2960
+ }
2961
+ return `{${parts.join(",")}}`;
2962
+ }
2963
+ function emitPrimitiveProperty(name, node) {
2964
+ const entries = [];
2965
+ if (hasValue(node)) {
2966
+ entries.push(`${JSON.stringify(name)}:${emitPrimitiveValue(node)}`);
2967
+ }
2968
+ if (hasMeta(node)) {
2969
+ entries.push(`${JSON.stringify(`_${name}`)}:${emitMeta(node)}`);
2970
+ }
2971
+ return entries;
2972
+ }
2973
+ function emitListProperty(name, node) {
2974
+ if (node.items.length === 0) return [];
2975
+ const allPrimitive = node.items.every(isPrimitive);
2976
+ if (!allPrimitive) {
2977
+ return [`${JSON.stringify(name)}:[${node.items.map(emitNode).join(",")}]`];
2978
+ }
2979
+ const primitives = node.items.filter(isPrimitive);
2980
+ const anyValue = primitives.some(hasValue);
2981
+ const anyMeta = primitives.some(hasMeta);
2982
+ const entries = [];
2983
+ if (anyValue) {
2984
+ const values = primitives.map(emitPrimitiveValue).join(",");
2985
+ entries.push(`${JSON.stringify(name)}:[${values}]`);
2986
+ }
2987
+ if (anyMeta) {
2988
+ const metas = primitives.map((item) => hasMeta(item) ? emitMeta(item) : "null").join(",");
2989
+ entries.push(`${JSON.stringify(`_${name}`)}:[${metas}]`);
2990
+ }
2991
+ return entries;
2992
+ }
2993
+ function emitNode(node) {
2994
+ switch (node.kind) {
2995
+ case "complex":
2996
+ return emitComplex(node);
2997
+ case "list":
2998
+ return `[${node.items.map(emitNode).join(",")}]`;
2999
+ case "primitive":
3000
+ return emitPrimitiveValue(node);
3001
+ }
2367
3002
  }
2368
- function validationIssue(code, severity, expression, constraint) {
2369
- const issue = { code, severity, type: ISSUE_TYPE_OF[code], expression };
2370
- return constraint === void 0 ? issue : { ...issue, constraint };
3003
+ function emitComplex(node) {
3004
+ if (node.nestedArraySource !== void 0) return node.nestedArraySource;
3005
+ if (node.nonObjectSource !== void 0) return node.nonObjectSource;
3006
+ const parts = [];
3007
+ const rt = node.properties.find((p) => p.name === "resourceType");
3008
+ const hoisted = rt !== void 0 && isPrimitive(rt.value) && typeof rt.value.value === "string";
3009
+ if (hoisted) parts.push(`"resourceType":${JSON.stringify(rt.value.value)}`);
3010
+ for (const property of node.properties) {
3011
+ if (property.name === "resourceType" && hoisted) {
3012
+ if (isPrimitive(property.value) && hasMeta(property.value)) {
3013
+ parts.push(`"_resourceType":${emitMeta(property.value)}`);
3014
+ }
3015
+ continue;
3016
+ }
3017
+ const value = property.value;
3018
+ switch (value.kind) {
3019
+ case "primitive":
3020
+ parts.push(...emitPrimitiveProperty(property.name, value));
3021
+ break;
3022
+ case "list":
3023
+ parts.push(...emitListProperty(property.name, value));
3024
+ break;
3025
+ case "complex":
3026
+ parts.push(`${JSON.stringify(property.name)}:${emitComplex(value)}`);
3027
+ break;
3028
+ }
3029
+ }
3030
+ return `{${parts.join(",")}}`;
3031
+ }
3032
+ function serializeResource(node) {
3033
+ assertSerializable(node);
3034
+ assertNoShadowedProperty(node);
3035
+ return emitComplex(node);
2371
3036
  }
2372
3037
 
2373
3038
  // src/validate/operation-outcome.ts
@@ -2378,14 +3043,26 @@ function issueNode(issue) {
2378
3043
  { name: "diagnostics", value: primitive(diagnosticFor(issue.code)) },
2379
3044
  { name: "expression", value: list([primitive(issue.expression)]) }
2380
3045
  ];
2381
- if (issue.constraint !== void 0) {
2382
- properties.splice(3, 0, {
2383
- name: "details",
2384
- value: complex([{ name: "text", value: primitive(issue.constraint) }])
2385
- });
2386
- }
3046
+ const details = detailsNode(issue);
3047
+ if (details !== void 0) properties.splice(3, 0, { name: "details", value: details });
2387
3048
  return complex(properties);
2388
3049
  }
3050
+ function detailsNode(issue) {
3051
+ const members = [];
3052
+ const record = issue.codeSystemVersion;
3053
+ if (record !== void 0) members.push({ name: "coding", value: list([recordCoding(record)]) });
3054
+ let text = issue.constraint;
3055
+ if (record !== void 0) text = record.declared ? record.version : void 0;
3056
+ if (text !== void 0) members.push({ name: "text", value: primitive(text) });
3057
+ return members.length === 0 ? void 0 : complex(members);
3058
+ }
3059
+ function recordCoding(record) {
3060
+ const marker = record.declared ? CODE_SYSTEM_VERSION_RECORD_CODES.DECLARED : CODE_SYSTEM_VERSION_RECORD_CODES.UNDECLARED;
3061
+ return complex([
3062
+ { name: "system", value: primitive(CODE_SYSTEM_VERSION_RECORD_SYSTEM) },
3063
+ { name: "code", value: primitive(marker) }
3064
+ ]);
3065
+ }
2389
3066
  function allClearNode() {
2390
3067
  return complex([
2391
3068
  { name: "severity", value: primitive(ISSUE_SEVERITIES.INFORMATION) },
@@ -2739,76 +3416,6 @@ function collectBundleIssues(resource) {
2739
3416
  return issues;
2740
3417
  }
2741
3418
 
2742
- // src/quantity/ucum.ts
2743
- var UCUM_SYSTEM = "http://unitsofmeasure.org";
2744
- var LOINC_SYSTEM = "http://loinc.org";
2745
- var OBSERVATION_CATEGORY_SYSTEM = "http://terminology.hl7.org/CodeSystem/observation-category";
2746
- var VITAL_SIGNS_CATEGORY = "vital-signs";
2747
- var VITAL_SIGNS_PROFILE = "http://hl7.org/fhir/StructureDefinition/vitalsigns";
2748
- var VITAL_SIGN_UNITS = /* @__PURE__ */ new Map([
2749
- ["29463-7", ["g", "kg", "[lb_av]"]],
2750
- // Body weight
2751
- ["8302-2", ["cm", "[in_i]"]],
2752
- // Body height
2753
- ["8306-3", ["cm", "[in_i]"]],
2754
- // Body height (lying) / body length
2755
- ["8287-5", ["cm", "[in_i]"]],
2756
- // Head circumference (Occipital-frontal, by tape)
2757
- ["9843-4", ["cm", "[in_i]"]],
2758
- // Head circumference
2759
- ["8310-5", ["Cel", "[degF]"]],
2760
- // Body temperature
2761
- ["8867-4", ["/min"]],
2762
- // Heart rate
2763
- ["9279-1", ["/min"]],
2764
- // Respiratory rate
2765
- ["8480-6", ["mm[Hg]"]],
2766
- // Systolic blood pressure
2767
- ["8462-4", ["mm[Hg]"]],
2768
- // Diastolic blood pressure
2769
- ["2708-6", ["%"]],
2770
- // Oxygen saturation in Arterial blood
2771
- ["59408-5", ["%"]],
2772
- // Oxygen saturation (SpO2) by pulse oximetry
2773
- ["39156-5", ["kg/m2"]]
2774
- // Body mass index
2775
- ]);
2776
- function requiredVitalSignUnits(loincCode) {
2777
- return VITAL_SIGN_UNITS.get(loincCode);
2778
- }
2779
- function decimalValue(node) {
2780
- if (node === void 0 || !isPrimitive(node)) return void 0;
2781
- const { value } = node;
2782
- if (typeof value === "object") return value;
2783
- return typeof value === "string" ? decimalFromLexical(value) : void 0;
2784
- }
2785
- function readQuantity(node) {
2786
- if (node === void 0 || node.kind !== "complex") return void 0;
2787
- const q = node;
2788
- return {
2789
- value: decimalValue(getProperty(q, "value")),
2790
- comparator: primitiveString(getProperty(q, "comparator")),
2791
- unit: primitiveString(getProperty(q, "unit")),
2792
- system: primitiveString(getProperty(q, "system")),
2793
- code: primitiveString(getProperty(q, "code"))
2794
- };
2795
- }
2796
- function validateUcumShape(code) {
2797
- if (code.length === 0) return "invalid";
2798
- const skeleton = code.replace(/\{[^{}]*\}/g, "");
2799
- if (skeleton.includes("{") || skeleton.includes("}")) return "invalid";
2800
- if (/\s/.test(skeleton)) return "invalid";
2801
- const pairs = { "]": "[", ")": "(" };
2802
- const stack = [];
2803
- for (const ch of skeleton) {
2804
- if (ch === "[" || ch === "(") stack.push(ch);
2805
- else if (ch === "]" || ch === ")") {
2806
- if (stack.pop() !== pairs[ch]) return "invalid";
2807
- }
2808
- }
2809
- return stack.length === 0 ? "ok" : "invalid";
2810
- }
2811
-
2812
3419
  // src/quantity/dose.ts
2813
3420
  function dosageProperty(rt) {
2814
3421
  if (rt === "MedicationRequest") return "dosageInstruction";
@@ -2849,60 +3456,6 @@ function readMedicationDoses(resource, rt) {
2849
3456
  return doses;
2850
3457
  }
2851
3458
 
2852
- // src/quantity/value.ts
2853
- var OBSERVATION_VALUE_TYPES = [
2854
- "Quantity",
2855
- "CodeableConcept",
2856
- "String",
2857
- "Boolean",
2858
- "Integer",
2859
- "Range",
2860
- "Ratio",
2861
- "SampledData",
2862
- "Time",
2863
- "DateTime",
2864
- "Period"
2865
- ];
2866
- function presentValues(node) {
2867
- const byName = new Map(node.properties.map((p) => [p.name, p.value]));
2868
- const out = [];
2869
- for (const type of OBSERVATION_VALUE_TYPES) {
2870
- const value = byName.get(`value${type}`);
2871
- if (value !== void 0) out.push({ type, node: value });
2872
- }
2873
- return out;
2874
- }
2875
- function readObservationValue(node) {
2876
- const [first, ...rest] = presentValues(node);
2877
- if (first === void 0) return void 0;
2878
- return {
2879
- type: first.type,
2880
- property: `value${first.type}`,
2881
- node: first.node,
2882
- quantity: first.type === "Quantity" ? readQuantity(first.node) : void 0,
2883
- ambiguous: rest.map((r) => r.type)
2884
- };
2885
- }
2886
- function readReferenceRanges(observation) {
2887
- const node = getProperty(observation, "referenceRange");
2888
- if (node === void 0) return [];
2889
- const items = isList(node) ? node.items : [node];
2890
- const ranges = [];
2891
- for (const item of items) {
2892
- if (item.kind !== "complex") continue;
2893
- ranges.push({
2894
- low: readQuantity(getProperty(item, "low")),
2895
- high: readQuantity(getProperty(item, "high")),
2896
- type: codingsOf(getProperty(item, "type")),
2897
- text: primitiveString(getProperty(item, "text"))
2898
- });
2899
- }
2900
- return ranges;
2901
- }
2902
- function readInterpretations(observation) {
2903
- return codingsOf(getProperty(observation, "interpretation"));
2904
- }
2905
-
2906
3459
  // src/validate/quantity.ts
2907
3460
  function collectQuantityIssues(resource, rt) {
2908
3461
  const issues = [];
@@ -2912,13 +3465,14 @@ function collectQuantityIssues(resource, rt) {
2912
3465
  forEachComponent(resource, (component, path) => {
2913
3466
  checkValueChoice(component, path, vital, issues);
2914
3467
  });
2915
- return issues;
2916
- }
2917
- if (rt === "MedicationRequest" || rt === "MedicationStatement") {
3468
+ } else if (rt === "MedicationRequest" || rt === "MedicationStatement") {
2918
3469
  for (const { node, path } of locateDoseQuantities(resource, rt)) {
2919
3470
  checkUcumShape(readQuantity(node), path, issues);
2920
3471
  }
2921
3472
  }
3473
+ for (const location of arrayWrappedValueChoices(resource, rootPath(rt))) {
3474
+ issues.push(validationIssue("ARRAY_WRAPPED_CHOICE", ISSUE_SEVERITIES.ERROR, location));
3475
+ }
2922
3476
  return issues;
2923
3477
  }
2924
3478
  function isVitalSign(observation) {
@@ -3153,13 +3707,13 @@ function buildBindingRegistry(extra = []) {
3153
3707
 
3154
3708
  // src/validate/terminology.ts
3155
3709
  function collectTerminologyIssues(resource, rt, options = {}) {
3156
- const registry = buildBindingRegistry(options.bindings ?? []);
3710
+ const registry2 = buildBindingRegistry(options.bindings ?? []);
3157
3711
  const service = options.terminology;
3158
3712
  const issues = [];
3159
3713
  const root = rootPath(rt);
3160
3714
  for (const property of resource.properties) {
3161
3715
  if (property.name === "resourceType") continue;
3162
- const binding = registry(`${rt}.${property.name}`);
3716
+ const binding = registry2(`${rt}.${property.name}`);
3163
3717
  if (binding === void 0) continue;
3164
3718
  for (const coding of locatedCodings(property.value, childPath(root, property.name))) {
3165
3719
  checkCoding(coding, binding, service, issues);
@@ -3211,13 +3765,20 @@ function checkCoding(coding, binding, service, issues) {
3211
3765
  return;
3212
3766
  }
3213
3767
  if (service === void 0 || code === void 0) return;
3214
- const { membership: membership2 } = service.validateCode({ valueSet: binding.valueSet, system, code });
3215
- if (membership2 !== "not-in") return;
3768
+ const answer = service.validateCode({ valueSet: binding.valueSet, system, code });
3769
+ if (answer.membership !== "not-in") return;
3216
3770
  const severity = notInSeverity(binding.strength);
3217
3771
  if (severity !== void 0) {
3218
- issues.push(validationIssue("CODE_NOT_IN_VALUESET", severity, path));
3772
+ issues.push(
3773
+ validationIssue("CODE_NOT_IN_VALUESET", severity, path, void 0, declaredRelease(answer))
3774
+ );
3219
3775
  }
3220
3776
  }
3777
+ function declaredRelease(answer) {
3778
+ const declared = answer.systemVersion;
3779
+ if (typeof declared !== "string" || declared.trim() === "") return { declared: false };
3780
+ return { declared: true, version: declared };
3781
+ }
3221
3782
  function systemUnexpectedSeverity(strength) {
3222
3783
  switch (strength) {
3223
3784
  case "required":
@@ -3335,11 +3896,249 @@ var OBSERVATION_ELEMENTS = {
3335
3896
  referenceRange: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
3336
3897
  hasMember: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3337
3898
  derivedFrom: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3338
- component: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] }
3899
+ component: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] }
3900
+ };
3901
+ var DIAGNOSTIC_REPORT_STATUS = [
3902
+ "registered",
3903
+ "partial",
3904
+ "preliminary",
3905
+ "final",
3906
+ "amended",
3907
+ "corrected",
3908
+ "appended",
3909
+ "cancelled",
3910
+ "entered-in-error",
3911
+ "unknown"
3912
+ ];
3913
+ var DIAGNOSTIC_REPORT_ELEMENTS = {
3914
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
3915
+ basedOn: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3916
+ status: {
3917
+ min: 1,
3918
+ max: 1,
3919
+ types: ["code"],
3920
+ binding: { strength: "required", codes: [...DIAGNOSTIC_REPORT_STATUS] }
3921
+ },
3922
+ category: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
3923
+ code: { min: 1, max: 1, types: ["CodeableConcept"] },
3924
+ subject: { min: 0, max: 1, types: ["Reference"] },
3925
+ encounter: { min: 0, max: 1, types: ["Reference"] },
3926
+ effective: { min: 0, max: 1, types: ["dateTime", "Period"] },
3927
+ issued: { min: 0, max: 1, types: ["instant"] },
3928
+ performer: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3929
+ resultsInterpreter: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3930
+ specimen: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3931
+ result: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3932
+ imagingStudy: { min: 0, max: UNBOUNDED, types: ["Reference"] },
3933
+ media: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
3934
+ conclusion: { min: 0, max: 1, types: ["string"] },
3935
+ conclusionCode: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
3936
+ presentedForm: { min: 0, max: UNBOUNDED, types: ["Attachment"] }
3937
+ };
3938
+ var CONDITION_ELEMENTS = {
3939
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
3940
+ clinicalStatus: { min: 0, max: 1, types: ["CodeableConcept"] },
3941
+ verificationStatus: { min: 0, max: 1, types: ["CodeableConcept"] },
3942
+ category: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
3943
+ severity: { min: 0, max: 1, types: ["CodeableConcept"] },
3944
+ code: { min: 0, max: 1, types: ["CodeableConcept"] },
3945
+ bodySite: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
3946
+ subject: { min: 1, max: 1, types: ["Reference"] },
3947
+ encounter: { min: 0, max: 1, types: ["Reference"] },
3948
+ onset: { min: 0, max: 1, types: ["dateTime", "Age", "Period", "Range", "string"] },
3949
+ abatement: { min: 0, max: 1, types: ["dateTime", "Age", "Period", "Range", "string"] },
3950
+ recordedDate: { min: 0, max: 1, types: ["dateTime"] },
3951
+ recorder: { min: 0, max: 1, types: ["Reference"] },
3952
+ asserter: { min: 0, max: 1, types: ["Reference"] },
3953
+ stage: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
3954
+ evidence: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
3955
+ note: { min: 0, max: UNBOUNDED, types: ["Annotation"] }
3956
+ };
3957
+ var MEDICATION_REQUEST_STATUS = [
3958
+ "active",
3959
+ "on-hold",
3960
+ "cancelled",
3961
+ "completed",
3962
+ "entered-in-error",
3963
+ "stopped",
3964
+ "draft",
3965
+ "unknown"
3966
+ ];
3967
+ var MEDICATION_REQUEST_INTENT = [
3968
+ "proposal",
3969
+ "plan",
3970
+ "order",
3971
+ "original-order",
3972
+ "reflex-order",
3973
+ "filler-order",
3974
+ "instance-order",
3975
+ "option"
3976
+ ];
3977
+ var REQUEST_PRIORITY = ["routine", "urgent", "asap", "stat"];
3978
+ var MEDICATION_REQUEST_ELEMENTS = {
3979
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
3980
+ status: {
3981
+ min: 1,
3982
+ max: 1,
3983
+ types: ["code"],
3984
+ binding: { strength: "required", codes: [...MEDICATION_REQUEST_STATUS] }
3985
+ },
3986
+ statusReason: { min: 0, max: 1, types: ["CodeableConcept"] },
3987
+ intent: {
3988
+ min: 1,
3989
+ max: 1,
3990
+ types: ["code"],
3991
+ binding: { strength: "required", codes: [...MEDICATION_REQUEST_INTENT] }
3992
+ },
3993
+ category: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
3994
+ priority: {
3995
+ min: 0,
3996
+ max: 1,
3997
+ types: ["code"],
3998
+ binding: { strength: "required", codes: [...REQUEST_PRIORITY] }
3999
+ },
4000
+ doNotPerform: { min: 0, max: 1, types: ["boolean"] },
4001
+ reported: { min: 0, max: 1, types: ["boolean", "Reference"] },
4002
+ medication: { min: 1, max: 1, types: ["CodeableConcept", "Reference"] },
4003
+ subject: { min: 1, max: 1, types: ["Reference"] },
4004
+ encounter: { min: 0, max: 1, types: ["Reference"] },
4005
+ supportingInformation: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4006
+ authoredOn: { min: 0, max: 1, types: ["dateTime"] },
4007
+ requester: { min: 0, max: 1, types: ["Reference"] },
4008
+ performer: { min: 0, max: 1, types: ["Reference"] },
4009
+ performerType: { min: 0, max: 1, types: ["CodeableConcept"] },
4010
+ recorder: { min: 0, max: 1, types: ["Reference"] },
4011
+ reasonCode: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4012
+ reasonReference: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4013
+ instantiatesCanonical: { min: 0, max: UNBOUNDED, types: ["canonical"] },
4014
+ instantiatesUri: { min: 0, max: UNBOUNDED, types: ["uri"] },
4015
+ basedOn: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4016
+ groupIdentifier: { min: 0, max: 1, types: ["Identifier"] },
4017
+ courseOfTherapyType: { min: 0, max: 1, types: ["CodeableConcept"] },
4018
+ insurance: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4019
+ note: { min: 0, max: UNBOUNDED, types: ["Annotation"] },
4020
+ dosageInstruction: { min: 0, max: UNBOUNDED, types: ["Dosage"] },
4021
+ dispenseRequest: { min: 0, max: 1, types: ["BackboneElement"] },
4022
+ substitution: { min: 0, max: 1, types: ["BackboneElement"] },
4023
+ priorPrescription: { min: 0, max: 1, types: ["Reference"] },
4024
+ detectedIssue: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4025
+ eventHistory: { min: 0, max: UNBOUNDED, types: ["Reference"] }
4026
+ };
4027
+ var ALLERGY_INTOLERANCE_TYPE = ["allergy", "intolerance"];
4028
+ var ALLERGY_INTOLERANCE_CATEGORY = ["food", "medication", "environment", "biologic"];
4029
+ var ALLERGY_INTOLERANCE_CRITICALITY = ["low", "high", "unable-to-assess"];
4030
+ var ALLERGY_INTOLERANCE_ELEMENTS = {
4031
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
4032
+ clinicalStatus: { min: 0, max: 1, types: ["CodeableConcept"] },
4033
+ verificationStatus: { min: 0, max: 1, types: ["CodeableConcept"] },
4034
+ type: {
4035
+ min: 0,
4036
+ max: 1,
4037
+ types: ["code"],
4038
+ binding: { strength: "required", codes: [...ALLERGY_INTOLERANCE_TYPE] }
4039
+ },
4040
+ category: {
4041
+ min: 0,
4042
+ max: UNBOUNDED,
4043
+ types: ["code"],
4044
+ binding: { strength: "required", codes: [...ALLERGY_INTOLERANCE_CATEGORY] }
4045
+ },
4046
+ criticality: {
4047
+ min: 0,
4048
+ max: 1,
4049
+ types: ["code"],
4050
+ binding: { strength: "required", codes: [...ALLERGY_INTOLERANCE_CRITICALITY] }
4051
+ },
4052
+ code: { min: 0, max: 1, types: ["CodeableConcept"] },
4053
+ patient: { min: 1, max: 1, types: ["Reference"] },
4054
+ encounter: { min: 0, max: 1, types: ["Reference"] },
4055
+ onset: { min: 0, max: 1, types: ["dateTime", "Age", "Period", "Range", "string"] },
4056
+ recordedDate: { min: 0, max: 1, types: ["dateTime"] },
4057
+ recorder: { min: 0, max: 1, types: ["Reference"] },
4058
+ asserter: { min: 0, max: 1, types: ["Reference"] },
4059
+ lastOccurrence: { min: 0, max: 1, types: ["dateTime"] },
4060
+ note: { min: 0, max: UNBOUNDED, types: ["Annotation"] },
4061
+ reaction: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] }
4062
+ };
4063
+ var IMMUNIZATION_STATUS = ["completed", "entered-in-error", "not-done"];
4064
+ var IMMUNIZATION_ELEMENTS = {
4065
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
4066
+ status: {
4067
+ min: 1,
4068
+ max: 1,
4069
+ types: ["code"],
4070
+ binding: { strength: "required", codes: [...IMMUNIZATION_STATUS] }
4071
+ },
4072
+ statusReason: { min: 0, max: 1, types: ["CodeableConcept"] },
4073
+ vaccineCode: { min: 1, max: 1, types: ["CodeableConcept"] },
4074
+ patient: { min: 1, max: 1, types: ["Reference"] },
4075
+ encounter: { min: 0, max: 1, types: ["Reference"] },
4076
+ occurrence: { min: 1, max: 1, types: ["dateTime", "string"] },
4077
+ recorded: { min: 0, max: 1, types: ["dateTime"] },
4078
+ primarySource: { min: 0, max: 1, types: ["boolean"] },
4079
+ reportOrigin: { min: 0, max: 1, types: ["CodeableConcept"] },
4080
+ location: { min: 0, max: 1, types: ["Reference"] },
4081
+ manufacturer: { min: 0, max: 1, types: ["Reference"] },
4082
+ lotNumber: { min: 0, max: 1, types: ["string"] },
4083
+ expirationDate: { min: 0, max: 1, types: ["date"] },
4084
+ site: { min: 0, max: 1, types: ["CodeableConcept"] },
4085
+ route: { min: 0, max: 1, types: ["CodeableConcept"] },
4086
+ doseQuantity: { min: 0, max: 1, types: ["Quantity"] },
4087
+ performer: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
4088
+ note: { min: 0, max: UNBOUNDED, types: ["Annotation"] },
4089
+ reasonCode: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4090
+ reasonReference: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4091
+ isSubpotent: { min: 0, max: 1, types: ["boolean"] },
4092
+ subpotentReason: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4093
+ education: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
4094
+ programEligibility: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4095
+ fundingSource: { min: 0, max: 1, types: ["CodeableConcept"] },
4096
+ reaction: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] },
4097
+ protocolApplied: { min: 0, max: UNBOUNDED, types: ["BackboneElement"] }
4098
+ };
4099
+ var MEDICATION_STATEMENT_STATUS = [
4100
+ "active",
4101
+ "completed",
4102
+ "entered-in-error",
4103
+ "intended",
4104
+ "stopped",
4105
+ "on-hold",
4106
+ "unknown",
4107
+ "not-taken"
4108
+ ];
4109
+ var MEDICATION_STATEMENT_ELEMENTS = {
4110
+ identifier: { min: 0, max: UNBOUNDED, types: ["Identifier"] },
4111
+ basedOn: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4112
+ partOf: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4113
+ status: {
4114
+ min: 1,
4115
+ max: 1,
4116
+ types: ["code"],
4117
+ binding: { strength: "required", codes: [...MEDICATION_STATEMENT_STATUS] }
4118
+ },
4119
+ statusReason: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4120
+ category: { min: 0, max: 1, types: ["CodeableConcept"] },
4121
+ medication: { min: 1, max: 1, types: ["CodeableConcept", "Reference"] },
4122
+ subject: { min: 1, max: 1, types: ["Reference"] },
4123
+ context: { min: 0, max: 1, types: ["Reference"] },
4124
+ effective: { min: 0, max: 1, types: ["dateTime", "Period"] },
4125
+ dateAsserted: { min: 0, max: 1, types: ["dateTime"] },
4126
+ informationSource: { min: 0, max: 1, types: ["Reference"] },
4127
+ derivedFrom: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4128
+ reasonCode: { min: 0, max: UNBOUNDED, types: ["CodeableConcept"] },
4129
+ reasonReference: { min: 0, max: UNBOUNDED, types: ["Reference"] },
4130
+ note: { min: 0, max: UNBOUNDED, types: ["Annotation"] },
4131
+ dosage: { min: 0, max: UNBOUNDED, types: ["Dosage"] }
3339
4132
  };
3340
4133
  var BUILTIN_SCHEMAS = [
3341
4134
  { type: "Patient", elements: PATIENT_ELEMENTS },
3342
- { type: "Observation", elements: OBSERVATION_ELEMENTS }
4135
+ { type: "Observation", elements: OBSERVATION_ELEMENTS },
4136
+ { type: "DiagnosticReport", elements: DIAGNOSTIC_REPORT_ELEMENTS },
4137
+ { type: "Condition", elements: CONDITION_ELEMENTS },
4138
+ { type: "MedicationRequest", elements: MEDICATION_REQUEST_ELEMENTS },
4139
+ { type: "AllergyIntolerance", elements: ALLERGY_INTOLERANCE_ELEMENTS },
4140
+ { type: "Immunization", elements: IMMUNIZATION_ELEMENTS },
4141
+ { type: "MedicationStatement", elements: MEDICATION_STATEMENT_ELEMENTS }
3343
4142
  ];
3344
4143
  function withBase(schema) {
3345
4144
  return { type: schema.type, elements: { ...BASE_ELEMENTS, ...schema.elements } };
@@ -3748,6 +4547,370 @@ var UnsupportedFhirPathError = class extends Error {
3748
4547
  }
3749
4548
  };
3750
4549
 
4550
+ // src/fhirpath/matches.ts
4551
+ var META = /* @__PURE__ */ new Set([
4552
+ "\\",
4553
+ "^",
4554
+ "$",
4555
+ ".",
4556
+ "|",
4557
+ "?",
4558
+ "*",
4559
+ "+",
4560
+ "(",
4561
+ ")",
4562
+ "[",
4563
+ "]",
4564
+ "{",
4565
+ "}"
4566
+ ]);
4567
+ var CLASS_ESCAPABLE = /* @__PURE__ */ new Set(["\\", "]", "[", "-", "^"]);
4568
+ var MAX_REPEAT = 1e3;
4569
+ function refuse() {
4570
+ throw new UnsupportedFhirPathError("matches() pattern outside the portable subset");
4571
+ }
4572
+ var PatternCheck = class {
4573
+ constructor(chars) {
4574
+ this.chars = chars;
4575
+ }
4576
+ chars;
4577
+ pos = 0;
4578
+ run() {
4579
+ this.alternation();
4580
+ if (this.pos !== this.chars.length) refuse();
4581
+ }
4582
+ peek() {
4583
+ return this.chars[this.pos];
4584
+ }
4585
+ alternation() {
4586
+ let shape = this.sequence();
4587
+ let alternates = false;
4588
+ while (this.peek() === "|") {
4589
+ this.pos += 1;
4590
+ alternates = true;
4591
+ const next = this.sequence();
4592
+ shape = { quantified: shape.quantified || next.quantified, alternates: true };
4593
+ }
4594
+ return { quantified: shape.quantified, alternates: alternates || shape.alternates };
4595
+ }
4596
+ sequence() {
4597
+ let quantified = false;
4598
+ let alternates = false;
4599
+ for (let c = this.peek(); c !== void 0 && c !== "|" && c !== ")"; c = this.peek()) {
4600
+ if (c === "^" || c === "$") {
4601
+ this.pos += 1;
4602
+ if (this.isQuantifierStart()) refuse();
4603
+ continue;
4604
+ }
4605
+ const atom = this.atom();
4606
+ if (this.isQuantifierStart()) {
4607
+ if (atom.quantified || atom.alternates) refuse();
4608
+ this.quantifier();
4609
+ quantified = true;
4610
+ }
4611
+ quantified ||= atom.quantified;
4612
+ alternates ||= atom.alternates;
4613
+ }
4614
+ return { quantified, alternates };
4615
+ }
4616
+ isQuantifierStart() {
4617
+ const c = this.peek();
4618
+ return c === "*" || c === "+" || c === "?" || c === "{";
4619
+ }
4620
+ atom() {
4621
+ const c = this.peek();
4622
+ if (c === void 0) refuse();
4623
+ if (c === "(") {
4624
+ this.pos += 1;
4625
+ if (this.peek() === "?") refuse();
4626
+ const inner = this.alternation();
4627
+ if (this.peek() !== ")") refuse();
4628
+ this.pos += 1;
4629
+ return inner;
4630
+ }
4631
+ if (c === "[") {
4632
+ this.characterClass();
4633
+ return { quantified: false, alternates: false };
4634
+ }
4635
+ if (c === "\\") {
4636
+ const escaped = this.chars[this.pos + 1];
4637
+ if (escaped === void 0 || !(META.has(escaped) || escaped === "/")) refuse();
4638
+ this.pos += 2;
4639
+ return { quantified: false, alternates: false };
4640
+ }
4641
+ if (c === ".") {
4642
+ this.pos += 1;
4643
+ return { quantified: false, alternates: false };
4644
+ }
4645
+ if (META.has(c)) refuse();
4646
+ this.pos += 1;
4647
+ return { quantified: false, alternates: false };
4648
+ }
4649
+ quantifier() {
4650
+ const c = this.peek();
4651
+ this.pos += 1;
4652
+ if (c === "{") {
4653
+ const low = this.count();
4654
+ let high = low;
4655
+ if (this.peek() === ",") {
4656
+ this.pos += 1;
4657
+ high = this.peek() === "}" ? MAX_REPEAT : this.count();
4658
+ }
4659
+ if (this.peek() !== "}" || high < low) refuse();
4660
+ this.pos += 1;
4661
+ }
4662
+ if (this.isQuantifierStart()) refuse();
4663
+ }
4664
+ count() {
4665
+ let digits = "";
4666
+ for (let c = this.peek(); c !== void 0 && c >= "0" && c <= "9"; c = this.peek()) {
4667
+ digits += c;
4668
+ this.pos += 1;
4669
+ }
4670
+ const value = digits === "" ? Number.NaN : Number(digits);
4671
+ if (!(value <= MAX_REPEAT)) refuse();
4672
+ return value;
4673
+ }
4674
+ characterClass() {
4675
+ this.pos += 1;
4676
+ if (this.peek() === "^") this.pos += 1;
4677
+ let members = 0;
4678
+ while (this.peek() !== "]") {
4679
+ this.classCharacter();
4680
+ if (this.peek() === "-" && this.chars[this.pos + 1] !== "]") {
4681
+ this.pos += 1;
4682
+ this.classCharacter();
4683
+ }
4684
+ members += 1;
4685
+ }
4686
+ if (members === 0) refuse();
4687
+ this.pos += 1;
4688
+ }
4689
+ classCharacter() {
4690
+ const c = this.peek();
4691
+ if (c === void 0 || c === "[" || c === "-") refuse();
4692
+ if (c === "&" && this.chars[this.pos + 1] === "&") refuse();
4693
+ if (c === "\\") {
4694
+ const escaped = this.chars[this.pos + 1];
4695
+ if (escaped === void 0 || !CLASS_ESCAPABLE.has(escaped)) refuse();
4696
+ this.pos += 2;
4697
+ return;
4698
+ }
4699
+ this.pos += 1;
4700
+ }
4701
+ };
4702
+ function compileMatchesPattern(pattern) {
4703
+ new PatternCheck([...pattern]).run();
4704
+ try {
4705
+ return new RegExp(pattern, "su");
4706
+ } catch {
4707
+ return refuse();
4708
+ }
4709
+ }
4710
+
4711
+ // src/fhirpath/types.ts
4712
+ var FHIR_PRIMITIVES = /* @__PURE__ */ new Set([
4713
+ "boolean",
4714
+ "integer",
4715
+ "string",
4716
+ "decimal",
4717
+ "uri",
4718
+ "url",
4719
+ "canonical",
4720
+ "base64Binary",
4721
+ "instant",
4722
+ "date",
4723
+ "dateTime",
4724
+ "time",
4725
+ "code",
4726
+ "oid",
4727
+ "id",
4728
+ "markdown",
4729
+ "unsignedInt",
4730
+ "positiveInt",
4731
+ "uuid"
4732
+ ]);
4733
+ var FHIR_COMPLEX = /* @__PURE__ */ new Set([
4734
+ "Address",
4735
+ "Age",
4736
+ "Annotation",
4737
+ "Attachment",
4738
+ "CodeableConcept",
4739
+ "Coding",
4740
+ "ContactPoint",
4741
+ "Count",
4742
+ "Distance",
4743
+ "Duration",
4744
+ "Extension",
4745
+ "HumanName",
4746
+ "Identifier",
4747
+ "Money",
4748
+ "Narrative",
4749
+ "Period",
4750
+ "Quantity",
4751
+ "Range",
4752
+ "Ratio",
4753
+ "Reference",
4754
+ "SampledData",
4755
+ "Signature",
4756
+ "Timing"
4757
+ ]);
4758
+ var SPECIALIZES = /* @__PURE__ */ new Map([
4759
+ ["Age", "Quantity"],
4760
+ ["Count", "Quantity"],
4761
+ ["Distance", "Quantity"],
4762
+ ["Duration", "Quantity"]
4763
+ ]);
4764
+ var FHIR_ABSTRACT = /* @__PURE__ */ new Set(["Resource"]);
4765
+ var SYSTEM_TYPES = /* @__PURE__ */ new Set([
4766
+ "Boolean",
4767
+ "String",
4768
+ "Integer",
4769
+ "Decimal",
4770
+ "Date",
4771
+ "DateTime",
4772
+ "Time",
4773
+ "Quantity"
4774
+ ]);
4775
+ var registry;
4776
+ function tables() {
4777
+ registry ??= buildRegistry();
4778
+ return registry;
4779
+ }
4780
+ function fhirKind(name) {
4781
+ if (FHIR_PRIMITIVES.has(name)) return "primitive";
4782
+ if (FHIR_COMPLEX.has(name)) return "complex";
4783
+ if (FHIR_ABSTRACT.has(name)) return "abstract";
4784
+ if (tables()(name) !== void 0) return "resource";
4785
+ return void 0;
4786
+ }
4787
+ function resolveTypeSpecifier(specifier) {
4788
+ const dot = specifier.indexOf(".");
4789
+ if (dot >= 0) {
4790
+ const model = specifier.slice(0, dot);
4791
+ const name = specifier.slice(dot + 1);
4792
+ if (model === "FHIR" && fhirKind(name) !== void 0) return { model, name };
4793
+ if (model === "System" && SYSTEM_TYPES.has(name)) return { model, name };
4794
+ return void 0;
4795
+ }
4796
+ if (fhirKind(specifier) !== void 0) return { model: "FHIR", name: specifier };
4797
+ if (SYSTEM_TYPES.has(specifier)) return { model: "System", name: specifier };
4798
+ return void 0;
4799
+ }
4800
+ function typeSpecifierOf(arg) {
4801
+ const names = [];
4802
+ let at = arg;
4803
+ while (at !== null) {
4804
+ if (at.kind !== "member") return void 0;
4805
+ names.unshift(at.name);
4806
+ at = at.target;
4807
+ }
4808
+ return names.join(".");
4809
+ }
4810
+ var POSITIONS = /* @__PURE__ */ new WeakMap();
4811
+ function positionsIn(resource) {
4812
+ const cached = POSITIONS.get(resource);
4813
+ if (cached !== void 0) return cached;
4814
+ const out = /* @__PURE__ */ new Map();
4815
+ const pending = [resource];
4816
+ const seen = /* @__PURE__ */ new Set([resource]);
4817
+ const record = (value, parent, property, inList) => {
4818
+ if (isList(value)) {
4819
+ for (const item of value.items) record(item, parent, property, true);
4820
+ return;
4821
+ }
4822
+ const list2 = out.get(value);
4823
+ if (list2 === void 0) out.set(value, [{ parent, property, inList }]);
4824
+ else list2.push({ parent, property, inList });
4825
+ if (!seen.has(value)) {
4826
+ seen.add(value);
4827
+ pending.push(value);
4828
+ }
4829
+ };
4830
+ for (let node = pending.pop(); node !== void 0; node = pending.pop()) {
4831
+ if (isComplex(node)) {
4832
+ for (const p of node.properties) record(p.value, node, p.name, false);
4833
+ for (const p of node.duplicates ?? []) record(p.value, node, p.name, false);
4834
+ } else if (isPrimitive(node)) {
4835
+ for (const ext of node.extension ?? []) record(ext, node, "extension", true);
4836
+ }
4837
+ }
4838
+ POSITIONS.set(resource, out);
4839
+ return out;
4840
+ }
4841
+ function suffixContradicts(property, node) {
4842
+ for (let i = 1; i < property.length; i += 1) {
4843
+ const first = property.charAt(i);
4844
+ if (first < "A" || first > "Z") continue;
4845
+ const suffix = property.slice(i);
4846
+ const kind = fhirKind(suffix);
4847
+ if (isPrimitive(node) && (kind === "complex" || kind === "resource")) return true;
4848
+ if (isComplex(node) && FHIR_PRIMITIVES.has(first.toLowerCase() + suffix.slice(1))) return true;
4849
+ }
4850
+ return false;
4851
+ }
4852
+ function choiceVariantType(position, resource) {
4853
+ if (position.inList || position.parent !== resource || !isComplex(resource)) return void 0;
4854
+ const type = resourceType(resource);
4855
+ const schema = type === void 0 ? void 0 : tables()(type);
4856
+ if (schema === void 0) return void 0;
4857
+ const resolved = resolveElement(schema.elements, position.property);
4858
+ if (resolved === void 0 || !isChoice(resolved.element)) return void 0;
4859
+ return resolved.datatype;
4860
+ }
4861
+ function establishedType(node, resource) {
4862
+ const positions = positionsIn(resource).get(node) ?? [];
4863
+ if (node === resource) {
4864
+ if (positions.length > 0 || !isComplex(node)) return void 0;
4865
+ const type = resourceType(node);
4866
+ return type !== void 0 && fhirKind(type) === "resource" ? type : void 0;
4867
+ }
4868
+ let established;
4869
+ for (const position of positions) {
4870
+ const type = choiceVariantType(position, resource);
4871
+ if (type === void 0 || established !== void 0 && established !== type) {
4872
+ return void 0;
4873
+ }
4874
+ established = type;
4875
+ }
4876
+ return established;
4877
+ }
4878
+ function isOfType(actual, tested) {
4879
+ if (actual === tested) return true;
4880
+ const actualKind = fhirKind(actual);
4881
+ const testedKind = fhirKind(tested);
4882
+ if (actualKind === void 0 || testedKind === void 0 || testedKind === "abstract") {
4883
+ throw new UnsupportedFhirPathError("type test between types this engine cannot relate");
4884
+ }
4885
+ if (actualKind === "primitive" || testedKind === "primitive") {
4886
+ throw new UnsupportedFhirPathError("type test between two different FHIR primitive types");
4887
+ }
4888
+ return SPECIALIZES.get(actual) === tested;
4889
+ }
4890
+ function nodeIsOfType(node, type, resource) {
4891
+ if (isList(node)) throw new UnsupportedFhirPathError("type test on a repeating element");
4892
+ const positions = positionsIn(resource).get(node) ?? [];
4893
+ if (positions.some((p) => suffixContradicts(p.property, node))) {
4894
+ throw new UnsupportedFhirPathError(
4895
+ "type test on an element whose property name contradicts its shape"
4896
+ );
4897
+ }
4898
+ if (type.model === "System") {
4899
+ if (isComplex(node)) return false;
4900
+ throw new UnsupportedFhirPathError(`type test '${type.name}' on a model primitive`);
4901
+ }
4902
+ const kind = fhirKind(type.name);
4903
+ if (isPrimitive(node) && kind !== "primitive") return false;
4904
+ if (isComplex(node) && kind === "primitive") return false;
4905
+ const actual = establishedType(node, resource);
4906
+ if (actual === void 0) {
4907
+ throw new UnsupportedFhirPathError(
4908
+ "type test on an element whose type the instance does not establish"
4909
+ );
4910
+ }
4911
+ return isOfType(actual, type.name);
4912
+ }
4913
+
3751
4914
  // src/fhirpath/evaluate.ts
3752
4915
  function wrap(node) {
3753
4916
  if (isList(node)) return node.items.flatMap(wrap);
@@ -3841,7 +5004,12 @@ function childrenOf(item) {
3841
5004
  return item.node.properties.filter((p) => p.name !== "resourceType").flatMap((p) => wrap(p.value));
3842
5005
  }
3843
5006
  if (isPrimitive(item.node)) {
3844
- return (item.node.extension ?? []).map((ext) => ({ t: "node", node: ext }));
5007
+ const id = item.node.id === void 0 ? [] : [{ t: "str", value: item.node.id }];
5008
+ const extensions = (item.node.extension ?? []).map((ext) => ({
5009
+ t: "node",
5010
+ node: ext
5011
+ }));
5012
+ return [...id, ...extensions];
3845
5013
  }
3846
5014
  return [];
3847
5015
  }
@@ -3994,28 +5162,114 @@ function systemTypeOf(item) {
3994
5162
  return void 0;
3995
5163
  }
3996
5164
  var SYSTEM_TYPE_NAMES = /* @__PURE__ */ new Set(["Boolean", "String", "Integer", "Decimal"]);
3997
- function itemIsType(item, typeName) {
3998
- const normalized = typeName.replace(/^System\./, "");
3999
- if (!SYSTEM_TYPE_NAMES.has(normalized)) {
4000
- throw new UnsupportedFhirPathError(
4001
- `type test '${typeName}' outside the System primitive types`
4002
- );
4003
- }
4004
- const actual = systemTypeOf(item);
4005
- if (actual === void 0) {
4006
- throw new UnsupportedFhirPathError(`type test '${typeName}' on a non-System value`);
4007
- }
4008
- if (normalized === "Decimal" && actual === "Integer") return true;
4009
- return actual === normalized;
5165
+ function itemIsType(item, typeName, ctx) {
5166
+ const resolved = resolveTypeSpecifier(typeName);
5167
+ const complexNode = item.t === "node" && isComplex(item.node);
5168
+ if (resolved?.model === "System" && SYSTEM_TYPE_NAMES.has(resolved.name) && !complexNode) {
5169
+ const actual = systemTypeOf(item);
5170
+ if (actual === void 0) {
5171
+ throw new UnsupportedFhirPathError(`type test '${typeName}' on a non-System value`);
5172
+ }
5173
+ if (resolved.name === "Decimal" && actual === "Integer") return true;
5174
+ return actual === resolved.name;
5175
+ }
5176
+ return fhirTypeTest(item, typeName, resolved, ctx);
5177
+ }
5178
+ function fhirTypeTest(item, typeName, resolved, ctx) {
5179
+ if (resolved === void 0) {
5180
+ throw new UnsupportedFhirPathError(`type test '${typeName}' names no known type`);
5181
+ }
5182
+ if (item.t !== "node") {
5183
+ throw new UnsupportedFhirPathError(`type test '${typeName}' on a computed value`);
5184
+ }
5185
+ return nodeIsOfType(item.node, resolved, ctx.resource);
5186
+ }
5187
+ function functionFormTypeTest(input, arg, construct, ctx) {
5188
+ const typeName = typeNameOf(req(arg));
5189
+ const resolved = resolveTypeSpecifier(typeName);
5190
+ if (resolved === void 0) {
5191
+ throw new UnsupportedFhirPathError(`type test '${typeName}' names no known type`);
5192
+ }
5193
+ if (input.length === 0) return "empty";
5194
+ const item = singleItem(input, construct);
5195
+ if (resolved.model === "System" && !(item.t === "node" && isComplex(item.node))) {
5196
+ throw new UnsupportedFhirPathError(`function-form type test '${typeName}' on a primitive`);
5197
+ }
5198
+ return fhirTypeTest(item, typeName, resolved, ctx) ? item : void 0;
5199
+ }
5200
+ function ofTypeKeeps(item, arg, ctx) {
5201
+ const typeName = typeNameOf(arg);
5202
+ const qualified = arg.kind === "member" && arg.target !== null;
5203
+ const complexNode = item.t === "node" && isComplex(item.node);
5204
+ if (qualified && resolveTypeSpecifier(typeName)?.model === "System" && !complexNode) {
5205
+ throw new UnsupportedFhirPathError(`qualified type test '${typeName}' on a primitive`);
5206
+ }
5207
+ return itemIsType(item, typeName, ctx);
5208
+ }
5209
+ function singleItem(input, construct) {
5210
+ if (input.length !== 1) {
5211
+ throw new UnsupportedFhirPathError(`${construct} over more than one item`);
5212
+ }
5213
+ return input[0];
5214
+ }
5215
+ function applyMatches(input, args, callFocus, ctx) {
5216
+ if (input.length === 0) return [];
5217
+ const patternColl = evaluate(req(args[0]), callFocus, ctx);
5218
+ if (patternColl.length === 0) return [];
5219
+ const value = stringOf2(singleItem(input, "matches()"));
5220
+ if (value === void 0) throw new UnsupportedFhirPathError("matches() on a non-string value");
5221
+ const pattern = patternColl.length === 1 ? stringOf2(patternColl[0]) : void 0;
5222
+ if (pattern === void 0)
5223
+ throw new UnsupportedFhirPathError("matches() needs one string pattern");
5224
+ return [{ t: "bool", value: compileMatchesPattern(pattern).test(value) }];
4010
5225
  }
4011
5226
  function typeNameOf(arg) {
4012
- if (arg.kind === "member" && arg.target === null) return arg.name;
4013
- throw new UnsupportedFhirPathError("expected a type name argument");
5227
+ const specifier = typeSpecifierOf(arg);
5228
+ if (specifier === void 0) throw new UnsupportedFhirPathError("expected a type name argument");
5229
+ return specifier;
4014
5230
  }
4015
5231
  function filterWhere(focus, criteria, ctx) {
4016
5232
  return focus.filter((item) => convertToBoolean(evaluate(criteria, [item], ctx)));
4017
5233
  }
5234
+ var SUBSET_FUNCTIONS = /* @__PURE__ */ new Set([
5235
+ "exists",
5236
+ "empty",
5237
+ "not",
5238
+ "where",
5239
+ "select",
5240
+ "all",
5241
+ "count",
5242
+ "first",
5243
+ "last",
5244
+ "distinct",
5245
+ "hasValue",
5246
+ "children",
5247
+ "extension",
5248
+ "intersect",
5249
+ "ofType",
5250
+ "is",
5251
+ "as",
5252
+ "matches"
5253
+ ]);
5254
+ var SUBSET_OPERATORS = /* @__PURE__ */ new Set([
5255
+ "and",
5256
+ "or",
5257
+ "xor",
5258
+ "implies",
5259
+ "=",
5260
+ "!=",
5261
+ "<",
5262
+ ">",
5263
+ "<=",
5264
+ ">=",
5265
+ "|",
5266
+ "in",
5267
+ "contains"
5268
+ ]);
4018
5269
  function applyFunction(name, input, args, callFocus, ctx) {
5270
+ if (!SUBSET_FUNCTIONS.has(name)) {
5271
+ throw new UnsupportedFhirPathError(`unsupported function '${name}()'`);
5272
+ }
4019
5273
  switch (name) {
4020
5274
  case "exists":
4021
5275
  return [
@@ -4059,7 +5313,17 @@ function applyFunction(name, input, args, callFocus, ctx) {
4059
5313
  return distinctItems(input).filter((item) => other.some((o) => itemsEqual(item, o)));
4060
5314
  }
4061
5315
  case "ofType":
4062
- return input.filter((item) => itemIsType(item, typeNameOf(req(args[0]))));
5316
+ return input.filter((item) => ofTypeKeeps(item, req(args[0]), ctx));
5317
+ case "is": {
5318
+ const matched = functionFormTypeTest(input, args[0], "is()", ctx);
5319
+ return matched === "empty" ? [] : [{ t: "bool", value: matched !== void 0 }];
5320
+ }
5321
+ case "as": {
5322
+ const matched = functionFormTypeTest(input, args[0], "as()", ctx);
5323
+ return matched === "empty" || matched === void 0 ? [] : [matched];
5324
+ }
5325
+ case "matches":
5326
+ return applyMatches(input, args, callFocus, ctx);
4063
5327
  default:
4064
5328
  throw new UnsupportedFhirPathError(`unsupported function '${name}()'`);
4065
5329
  }
@@ -4082,6 +5346,7 @@ function req(arg) {
4082
5346
  return arg;
4083
5347
  }
4084
5348
  function evaluateBinary(op, node, focus, ctx) {
5349
+ if (!SUBSET_OPERATORS.has(op)) throw new UnsupportedFhirPathError(`unsupported operator '${op}'`);
4085
5350
  if (op === "and" || op === "or" || op === "xor" || op === "implies") {
4086
5351
  const a = toTrit(evaluate(node.left, focus, ctx));
4087
5352
  const b = toTrit(evaluate(node.right, focus, ctx));
@@ -4189,9 +5454,9 @@ function evaluate(expr, focus, ctx) {
4189
5454
  if (expr.op === "is") {
4190
5455
  if (operand.length === 0) return [];
4191
5456
  if (operand.length !== 1) return [{ t: "bool", value: false }];
4192
- return [{ t: "bool", value: itemIsType(operand[0], expr.type) }];
5457
+ return [{ t: "bool", value: itemIsType(operand[0], expr.type, ctx) }];
4193
5458
  }
4194
- return operand.filter((item) => itemIsType(item, expr.type));
5459
+ return operand.filter((item) => itemIsType(item, expr.type, ctx));
4195
5460
  }
4196
5461
  }
4197
5462
  }
@@ -4656,11 +5921,14 @@ function fociFor(resource, rt, root, elementPath) {
4656
5921
  }));
4657
5922
  }
4658
5923
  function collectInvariantIssues(resource, profile, options = {}) {
5924
+ return invariantFindings(resource, profile, options).map((finding) => finding.issue);
5925
+ }
5926
+ function invariantFindings(resource, profile, options = {}) {
4659
5927
  const rt = resourceType(resource);
4660
5928
  if (rt === void 0 || rt !== profile.type) return [];
4661
5929
  const root = rootPath(rt);
4662
5930
  const snapshot = snapshotElements(profile, options.resolve ?? (() => void 0));
4663
- const issues = [];
5931
+ const findings = [];
4664
5932
  for (const el of snapshot) {
4665
5933
  if (el.constraint === void 0 || el.constraint.length === 0) continue;
4666
5934
  if (el.sliceName !== void 0) continue;
@@ -4676,21 +5944,217 @@ function collectInvariantIssues(resource, profile, options = {}) {
4676
5944
  resource
4677
5945
  );
4678
5946
  if (unchecked) {
4679
- issues.push(
4680
- validationIssue(
5947
+ findings.push({
5948
+ issue: validationIssue(
4681
5949
  "INVARIANT_UNCHECKED",
4682
5950
  ISSUE_SEVERITIES.INFORMATION,
4683
5951
  focus.path,
4684
5952
  constraint.key
4685
- )
4686
- );
5953
+ ),
5954
+ focus: focus.node,
5955
+ expression: constraint.expression
5956
+ });
4687
5957
  } else if (!satisfied) {
4688
- issues.push(validationIssue("INVARIANT_VIOLATED", severity, focus.path, constraint.key));
5958
+ findings.push({
5959
+ issue: validationIssue("INVARIANT_VIOLATED", severity, focus.path, constraint.key),
5960
+ focus: focus.node,
5961
+ expression: constraint.expression
5962
+ });
4689
5963
  }
4690
5964
  }
4691
5965
  }
4692
5966
  }
4693
- return issues;
5967
+ return findings;
5968
+ }
5969
+
5970
+ // src/validate/base-invariants.ts
5971
+ var BASE_CONSTRAINT_TYPES = /* @__PURE__ */ new Set([
5972
+ "AllergyIntolerance",
5973
+ "Condition",
5974
+ "DiagnosticReport",
5975
+ "Immunization",
5976
+ "MedicationRequest",
5977
+ "MedicationStatement",
5978
+ "Observation",
5979
+ "Patient"
5980
+ ]);
5981
+ var BASE_CONSTRAINTS = [
5982
+ {
5983
+ key: "dom-2",
5984
+ anchor: { kind: "root" },
5985
+ expression: "contained.contained.empty()"
5986
+ },
5987
+ {
5988
+ key: "dom-3",
5989
+ anchor: { kind: "root" },
5990
+ expression: "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()"
5991
+ },
5992
+ {
5993
+ key: "dom-4",
5994
+ anchor: { kind: "root" },
5995
+ expression: "contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()"
5996
+ },
5997
+ {
5998
+ key: "dom-5",
5999
+ anchor: { kind: "root" },
6000
+ expression: "contained.meta.security.empty()"
6001
+ },
6002
+ {
6003
+ key: "pat-1",
6004
+ anchor: { kind: "element", type: "Patient", element: "contact" },
6005
+ expression: "name.exists() or telecom.exists() or address.exists() or organization.exists()"
6006
+ },
6007
+ {
6008
+ key: "obs-3",
6009
+ anchor: { kind: "element", type: "Observation", element: "referenceRange" },
6010
+ expression: "low.exists() or high.exists() or text.exists()"
6011
+ },
6012
+ {
6013
+ key: "con-1",
6014
+ anchor: { kind: "element", type: "Condition", element: "stage" },
6015
+ expression: "summary.exists() or assessment.exists()"
6016
+ },
6017
+ {
6018
+ key: "con-2",
6019
+ anchor: { kind: "element", type: "Condition", element: "evidence" },
6020
+ expression: "code.exists() or detail.exists()"
6021
+ },
6022
+ {
6023
+ key: "imm-1",
6024
+ anchor: { kind: "element", type: "Immunization", element: "education" },
6025
+ expression: "documentType.exists() or reference.exists()"
6026
+ },
6027
+ {
6028
+ key: "ele-1",
6029
+ anchor: { kind: "every-element" },
6030
+ expression: "hasValue() or (children().count() > id.count())"
6031
+ },
6032
+ {
6033
+ key: "ext-1",
6034
+ anchor: { kind: "every-extension" },
6035
+ expression: "extension.exists() != value.exists()"
6036
+ }
6037
+ ];
6038
+ function compile(expression) {
6039
+ try {
6040
+ return parseFhirPath(expression);
6041
+ } catch {
6042
+ return void 0;
6043
+ }
6044
+ }
6045
+ var COMPILED = BASE_CONSTRAINTS.map((row) => ({
6046
+ row,
6047
+ ast: compile(row.expression)
6048
+ }));
6049
+ var EXPRESSION_OF = new Map(
6050
+ BASE_CONSTRAINTS.map((row) => [row.key, row.expression])
6051
+ );
6052
+ var CONTAINED_SELECTION = compile("contained");
6053
+ function outcomeOf(ast, focus, resource) {
6054
+ if (ast === void 0) return "unchecked";
6055
+ try {
6056
+ const at = [{ t: "node", node: focus }];
6057
+ return convertToBoolean(evaluate(ast, at, { resource, context: at })) ? "satisfied" : "violated";
6058
+ } catch {
6059
+ return "unchecked";
6060
+ }
6061
+ }
6062
+ function decideDom3(resource) {
6063
+ if (CONTAINED_SELECTION === void 0) return "unchecked";
6064
+ try {
6065
+ const at = [{ t: "node", node: resource }];
6066
+ const selected = evaluate(CONTAINED_SELECTION, at, { resource, context: at });
6067
+ return selected.length === 0 ? "satisfied" : "unchecked";
6068
+ } catch {
6069
+ return "unchecked";
6070
+ }
6071
+ }
6072
+ function holdsExtensions(name) {
6073
+ return name === "extension" || name === "modifierExtension";
6074
+ }
6075
+ function occurrencesOf2(node, path) {
6076
+ if (!isList(node)) return [{ node, path }];
6077
+ return node.items.flatMap((item, i) => occurrencesOf2(item, `${path}[${String(i)}]`));
6078
+ }
6079
+ function walkElements(value, path, extension, typedElement, visit2) {
6080
+ for (const occurrence of occurrencesOf2(value, path)) {
6081
+ const { node } = occurrence;
6082
+ visit2(node, occurrence.path, extension, typedElement);
6083
+ if (isComplex(node)) {
6084
+ walkChildren(node, occurrence.path, extension, visit2);
6085
+ } else if (!isList(node)) {
6086
+ (node.extension ?? []).forEach((ext, i) => {
6087
+ walkElements(ext, `${occurrence.path}.extension[${String(i)}]`, true, true, visit2);
6088
+ });
6089
+ }
6090
+ }
6091
+ }
6092
+ function walkChildren(node, path, isExtension, visit2) {
6093
+ for (const property of node.properties) {
6094
+ if (property.name === "resourceType") continue;
6095
+ const plainString = property.name === "id" || isExtension && property.name === "url";
6096
+ walkElements(
6097
+ property.value,
6098
+ childPath(path, property.name),
6099
+ holdsExtensions(property.name),
6100
+ !plainString,
6101
+ visit2
6102
+ );
6103
+ }
6104
+ }
6105
+ function collectBaseInvariantFindings(resource, rt) {
6106
+ const findings = [];
6107
+ const decided = /* @__PURE__ */ new Map();
6108
+ const result = {
6109
+ findings,
6110
+ decides: (focus, key, expression) => EXPRESSION_OF.get(key) === expression && decided.get(focus)?.has(key) === true
6111
+ };
6112
+ if (!BASE_CONSTRAINT_TYPES.has(rt)) return result;
6113
+ const root = rootPath(rt);
6114
+ const report = (outcome, key, focus, path) => {
6115
+ if (outcome !== "unchecked") {
6116
+ const keys = decided.get(focus) ?? /* @__PURE__ */ new Set();
6117
+ keys.add(key);
6118
+ decided.set(focus, keys);
6119
+ }
6120
+ if (outcome === "satisfied") return;
6121
+ const issue = outcome === "violated" ? validationIssue("INVARIANT_VIOLATED", ISSUE_SEVERITIES.ERROR, path, key) : validationIssue("INVARIANT_UNCHECKED", ISSUE_SEVERITIES.INFORMATION, path, key);
6122
+ findings.push({ issue, focus });
6123
+ };
6124
+ for (const { row, ast } of COMPILED) {
6125
+ const { anchor } = row;
6126
+ if (anchor.kind === "root") {
6127
+ const outcome = row.key === "dom-3" ? decideDom3(resource) : outcomeOf(ast, resource, resource);
6128
+ report(outcome, row.key, resource, root);
6129
+ } else if (anchor.kind === "element" && anchor.type === rt) {
6130
+ const value = getProperty(resource, anchor.element);
6131
+ if (value === void 0) continue;
6132
+ for (const { node, path } of occurrencesOf2(value, childPath(root, anchor.element))) {
6133
+ report(outcomeOf(ast, node, resource), row.key, node, path);
6134
+ }
6135
+ }
6136
+ }
6137
+ const elementRow = COMPILED.find(({ row }) => row.anchor.kind === "every-element");
6138
+ const extensionRow = COMPILED.find(({ row }) => row.anchor.kind === "every-extension");
6139
+ const visit2 = (node, path, extension, typedElement) => {
6140
+ if (elementRow !== void 0 && typedElement) {
6141
+ report(outcomeOf(elementRow.ast, node, resource), elementRow.row.key, node, path);
6142
+ }
6143
+ if (extensionRow !== void 0 && extension) {
6144
+ report(outcomeOf(extensionRow.ast, node, resource), extensionRow.row.key, node, path);
6145
+ }
6146
+ };
6147
+ for (const property of resource.properties) {
6148
+ if (property.name === "resourceType" || property.name === "contained") continue;
6149
+ walkElements(
6150
+ property.value,
6151
+ childPath(root, property.name),
6152
+ holdsExtensions(property.name),
6153
+ property.name !== "id",
6154
+ visit2
6155
+ );
6156
+ }
6157
+ return result;
4694
6158
  }
4695
6159
 
4696
6160
  // src/validate/validate.ts
@@ -4748,8 +6212,8 @@ function validateResource(resource, options = {}) {
4748
6212
  return finalize(ctx);
4749
6213
  }
4750
6214
  const root = rootPath(rt);
4751
- const registry = buildRegistry(options.schemas ?? []);
4752
- const modeled = registry(rt);
6215
+ const registry2 = buildRegistry(options.schemas ?? []);
6216
+ const modeled = registry2(rt);
4753
6217
  const schema = modeled ?? baseSchema(rt);
4754
6218
  if (modeled === void 0) emit(ctx, "RESOURCE_NOT_MODELED", root);
4755
6219
  const counts = /* @__PURE__ */ new Map();
@@ -4762,19 +6226,19 @@ function validateResource(resource, options = {}) {
4762
6226
  if (modeled !== void 0) emit(ctx, "UNKNOWN_ELEMENT", path);
4763
6227
  continue;
4764
6228
  }
4765
- const { element, datatype, base } = match;
6229
+ const { element, datatype, base: base2 } = match;
4766
6230
  const occ = occurrences(property.value, path);
4767
- counts.set(base, (counts.get(base) ?? 0) + occ.length);
6231
+ counts.set(base2, (counts.get(base2) ?? 0) + occ.length);
4768
6232
  if (isChoice(element)) {
4769
- const seen = choiceVariants.get(base) ?? /* @__PURE__ */ new Set();
6233
+ const seen = choiceVariants.get(base2) ?? /* @__PURE__ */ new Set();
4770
6234
  seen.add(property.name);
4771
- choiceVariants.set(base, seen);
6235
+ choiceVariants.set(base2, seen);
4772
6236
  }
4773
6237
  if (occ.length > element.max) emit(ctx, "CARDINALITY_MAX", path);
4774
6238
  for (const { leaf, path: leafPath } of occ) checkLeaf(ctx, leaf, datatype, element, leafPath);
4775
6239
  }
4776
- for (const [base, seen] of choiceVariants) {
4777
- if (seen.size > 1) emit(ctx, "CHOICE_AMBIGUOUS", `${root}.${base}[x]`);
6240
+ for (const [base2, seen] of choiceVariants) {
6241
+ if (seen.size > 1) emit(ctx, "CHOICE_AMBIGUOUS", `${root}.${base2}[x]`);
4778
6242
  }
4779
6243
  for (const [name, element] of Object.entries(schema.elements)) {
4780
6244
  if (element.min >= 1 && (counts.get(name) ?? 0) < element.min) {
@@ -4788,6 +6252,9 @@ function validateResource(resource, options = {}) {
4788
6252
  for (const issue of collectBundleIssues(resource)) ctx.issues.push(issue);
4789
6253
  }
4790
6254
  for (const issue of collectTerminologyIssues(resource, rt, options)) ctx.issues.push(issue);
6255
+ const base = collectBaseInvariantFindings(resource, rt);
6256
+ for (const finding of base.findings) ctx.issues.push(finding.issue);
6257
+ const reportedByBase = alreadyReported(base.findings);
4791
6258
  if (options.profiles !== void 0 && options.profiles.length > 0) {
4792
6259
  const profileOptions = options.resolveBase === void 0 ? {} : { resolve: options.resolveBase };
4793
6260
  for (const profile of options.profiles) {
@@ -4795,8 +6262,12 @@ function validateResource(resource, options = {}) {
4795
6262
  for (const issue of collectProfileIssues(resource, profile, profileOptions)) {
4796
6263
  ctx.issues.push(issue);
4797
6264
  }
4798
- for (const issue of collectInvariantIssues(resource, profile, profileOptions)) {
4799
- ctx.issues.push(issue);
6265
+ for (const finding of invariantFindings(resource, profile, profileOptions)) {
6266
+ if (reportedByBase(finding.focus, finding.issue)) continue;
6267
+ if (finding.issue.code === "INVARIANT_UNCHECKED" && base.decides(finding.focus, finding.issue.constraint ?? "", finding.expression)) {
6268
+ continue;
6269
+ }
6270
+ ctx.issues.push(finding.issue);
4800
6271
  }
4801
6272
  }
4802
6273
  for (const issue of collectProfileVersionIssues(resource, options.profiles)) {
@@ -4805,6 +6276,15 @@ function validateResource(resource, options = {}) {
4805
6276
  }
4806
6277
  return finalize(ctx);
4807
6278
  }
6279
+ function alreadyReported(base) {
6280
+ const byFocus = /* @__PURE__ */ new Map();
6281
+ for (const { focus, issue } of base) {
6282
+ const seen = byFocus.get(focus) ?? /* @__PURE__ */ new Set();
6283
+ seen.add(`${issue.code} ${issue.constraint ?? ""}`);
6284
+ byFocus.set(focus, seen);
6285
+ }
6286
+ return (focus, issue) => byFocus.get(focus)?.has(`${issue.code} ${issue.constraint ?? ""}`) === true;
6287
+ }
4808
6288
  function finalize(ctx) {
4809
6289
  const issues = [...ctx.issues];
4810
6290
  const valid = !issues.some(
@@ -5201,9 +6681,12 @@ function isWs(c) {
5201
6681
  }
5202
6682
  var RawXmlReader = class {
5203
6683
  #src;
6684
+ /** Told the code point of each numeric character reference as it is decoded; never alters the parse. */
6685
+ #onReference;
5204
6686
  #pos = 0;
5205
- constructor(src) {
6687
+ constructor(src, onReference) {
5206
6688
  this.#src = src;
6689
+ this.#onReference = onReference;
5207
6690
  }
5208
6691
  /** Parse the whole document: optional prolog, exactly one root element, optional trailing misc. */
5209
6692
  parse() {
@@ -5451,6 +6934,7 @@ var RawXmlReader = class {
5451
6934
  start
5452
6935
  );
5453
6936
  }
6937
+ this.#onReference?.(code);
5454
6938
  return String.fromCodePoint(code);
5455
6939
  }
5456
6940
  fail(message) {
@@ -5460,6 +6944,13 @@ var RawXmlReader = class {
5460
6944
  function readRawXml(src) {
5461
6945
  return new RawXmlReader(src).parse();
5462
6946
  }
6947
+ function readRawXmlReferences(src) {
6948
+ const references = [];
6949
+ const root = new RawXmlReader(src, (codePoint) => {
6950
+ references.push(codePoint);
6951
+ }).parse();
6952
+ return { root, references };
6953
+ }
5463
6954
 
5464
6955
  // src/xml/read.ts
5465
6956
  var FHIR_XML_NAMESPACE = "http://hl7.org/fhir";
@@ -5501,6 +6992,9 @@ function isForeign(resolved, parentNamespace) {
5501
6992
  function isNarrativeDiv(resolved) {
5502
6993
  return resolved.name === "div" && resolved.namespace === XHTML_NAMESPACE;
5503
6994
  }
6995
+ function rootVocabularyIsForeign(resolved) {
6996
+ return !resolved.unboundPrefix && resolved.namespace !== FHIR_XML_NAMESPACE && resolved.namespace !== NO_NAMESPACE;
6997
+ }
5504
6998
  function modelNameOf(element, resolved, parentNamespace) {
5505
6999
  if (isNarrativeDiv(resolved)) return resolved.name;
5506
7000
  return isForeign(resolved, parentNamespace) ? element.name : resolved.name;
@@ -5630,6 +7124,12 @@ function readComplex2(self, path, issues, opts) {
5630
7124
  function hasStrayText(children) {
5631
7125
  return children.some((node) => node.type === "text" && node.value.trim() !== "");
5632
7126
  }
7127
+ function elementTextValue(children) {
7128
+ const runs = children.flatMap(
7129
+ (node) => node.type === "text" && node.value.trim() !== "" ? [node.value.trim()] : []
7130
+ );
7131
+ return runs.length === 1 ? runs[0] : void 0;
7132
+ }
5633
7133
  function unexpectedXmlContentAt(issues, path) {
5634
7134
  return issues.some((i) => i.code === ISSUE_CODES.UNEXPECTED_XML_CONTENT && i.expression === path);
5635
7135
  }
@@ -5689,7 +7189,7 @@ function buildSingle(self, path, issues, parentNamespace) {
5689
7189
  }
5690
7190
  }
5691
7191
  const dropped = flagStrayText(element.children, path, issues);
5692
- const node = primitive(valueAttribute(element), meta);
7192
+ const node = primitive(valueAttribute(element) ?? elementTextValue(element.children), meta);
5693
7193
  return dropped ? markDroppedText(node) : node;
5694
7194
  }
5695
7195
  return readComplex2(self, path, issues, { isResource: false });
@@ -5701,26 +7201,69 @@ function parseResourceXml(input) {
5701
7201
  const resolved = resolveName(root.name, scope);
5702
7202
  const self = { element: root, scope, resolved, modelName: resolved.name };
5703
7203
  const path = rootPath(self.modelName);
5704
- const rootIsForeign = resolved.unboundPrefix || resolved.namespace !== FHIR_XML_NAMESPACE && resolved.namespace !== NO_NAMESPACE;
7204
+ const rootIsForeign = resolved.unboundPrefix || rootVocabularyIsForeign(resolved);
5705
7205
  if (rootIsForeign) issues.push(unexpectedXmlContent(path));
5706
- const resource = readComplex2(self, path, issues, { isResource: true });
7206
+ const read = readComplex2(self, path, issues, { isResource: true });
7207
+ const resource = rootVocabularyIsForeign(resolved) ? markForeignRoot(read) : read;
5707
7208
  return { resource, issues };
5708
7209
  }
5709
7210
 
5710
7211
  // src/xml/write.ts
5711
7212
  function tag(name, path, sink) {
5712
7213
  if (breaksTag(name)) sink.refusedNames.push(path);
7214
+ else if (carriesUndeclarablePrefix(name)) sink.refusedPrefixes.push(path);
7215
+ else if (!isXmlName(name)) sink.refusedXmlNames.push(path);
5713
7216
  return name;
5714
7217
  }
7218
+ function attributeValue(text, path, sink) {
7219
+ if (carriesNonXmlCharacter(text)) sink.refusedCharacters.push(path);
7220
+ return escapeAttr(text);
7221
+ }
7222
+ function refersToNonXmlCharacter(references) {
7223
+ return references.some((codePoint) => carriesNonXmlCharacter(String.fromCodePoint(codePoint)));
7224
+ }
5715
7225
  function emitsOneDivElement(value) {
5716
- let name;
7226
+ let parse;
5717
7227
  try {
5718
- name = readRawXml(value).name;
7228
+ parse = readRawXmlReferences(value);
5719
7229
  } catch {
5720
- return false;
7230
+ return void 0;
5721
7231
  }
7232
+ const { name } = parse.root;
5722
7233
  const colon = name.indexOf(":");
5723
- return (colon === -1 ? name : name.slice(colon + 1)) === "div";
7234
+ return (colon === -1 ? name : name.slice(colon + 1)) === "div" ? parse : void 0;
7235
+ }
7236
+ var XML_PREFIX2 = "xml";
7237
+ var XMLNS_PREFIX = "xmlns";
7238
+ var DECLARATION = `${XMLNS_PREFIX}:`;
7239
+ var NOTHING_BOUND = /* @__PURE__ */ new Set();
7240
+ function prefixesInScope(element, inherited) {
7241
+ const declarations = element.attributes.filter(({ name }) => name.startsWith(DECLARATION));
7242
+ if (declarations.length === 0) return inherited;
7243
+ const scope = new Set(inherited);
7244
+ for (const { name, value } of declarations) {
7245
+ const prefix = name.slice(DECLARATION.length);
7246
+ if (value === "") scope.delete(prefix);
7247
+ else scope.add(prefix);
7248
+ }
7249
+ return scope;
7250
+ }
7251
+ function nameIsBound(name, scope) {
7252
+ const colon = name.indexOf(":");
7253
+ if (colon === -1) return true;
7254
+ const prefix = name.slice(0, colon);
7255
+ const local = name.slice(colon + 1);
7256
+ if (prefix === "" || prefix === XMLNS_PREFIX || local === "" || local.includes(":")) return false;
7257
+ return prefix === XML_PREFIX2 || scope.has(prefix);
7258
+ }
7259
+ function bindsEveryPrefix(element, inherited = NOTHING_BOUND) {
7260
+ const scope = prefixesInScope(element, inherited);
7261
+ if (!nameIsBound(element.name, scope)) return false;
7262
+ for (const { name } of element.attributes) {
7263
+ if (name === XMLNS_PREFIX || name.startsWith(DECLARATION)) continue;
7264
+ if (!nameIsBound(name, scope)) return false;
7265
+ }
7266
+ return element.children.every((child) => child.type === "text" || bindsEveryPrefix(child, scope));
5724
7267
  }
5725
7268
  function scalarText(value) {
5726
7269
  if (typeof value === "string") return value;
@@ -5738,14 +7281,18 @@ function resourceTypeOf(node) {
5738
7281
  return void 0;
5739
7282
  }
5740
7283
  function attributeText(node) {
5741
- if (isPrimitive(node) && node.value !== void 0) return escapeAttr(scalarText(node.value));
7284
+ if (isPrimitive(node) && node.value !== void 0) return scalarText(node.value);
5742
7285
  return void 0;
5743
7286
  }
5744
7287
  function writePrimitiveElement(path, name, node, sink) {
5745
7288
  const tagName = tag(name, path, sink);
5746
7289
  let attrs = "";
5747
- if (node.id !== void 0) attrs += ` id="${escapeAttr(node.id)}"`;
5748
- if (node.value !== void 0) attrs += ` value="${escapeAttr(scalarText(node.value))}"`;
7290
+ if (node.id !== void 0) {
7291
+ attrs += ` id="${attributeValue(node.id, childPath(path, "id"), sink)}"`;
7292
+ }
7293
+ if (node.value !== void 0) {
7294
+ attrs += ` value="${attributeValue(scalarText(node.value), path, sink)}"`;
7295
+ }
5749
7296
  const extensions = node.extension ?? [];
5750
7297
  if (extensions.length === 0) return `<${tagName}${attrs}/>`;
5751
7298
  const extensionPath = childPath(path, "extension");
@@ -5756,9 +7303,20 @@ function writePrimitiveElement(path, name, node, sink) {
5756
7303
  }
5757
7304
  function writeItem(path, name, node, inExtension, sink) {
5758
7305
  if (name === "div" && node.kind === "primitive" && typeof node.value === "string") {
5759
- if (emitsOneDivElement(node.value)) return node.value;
5760
- sink.refusedDivs.push(path);
5761
- return "";
7306
+ const parse = emitsOneDivElement(node.value);
7307
+ if (parse === void 0) {
7308
+ sink.refusedDivs.push(path);
7309
+ return "";
7310
+ }
7311
+ if (!bindsEveryPrefix(parse.root)) {
7312
+ sink.refusedDivPrefixes.push(path);
7313
+ return "";
7314
+ }
7315
+ if (carriesNonXmlCharacter(node.value) || refersToNonXmlCharacter(parse.references)) {
7316
+ sink.refusedCharacters.push(path);
7317
+ return "";
7318
+ }
7319
+ return node.value;
5762
7320
  }
5763
7321
  if (node.kind === "primitive") return writePrimitiveElement(path, name, node, sink);
5764
7322
  if (node.kind === "list")
@@ -5779,27 +7337,29 @@ function writeProperty(parentPath, name, node, inExtension, sink) {
5779
7337
  function writeElement(path, tagName, node, isRoot, inExtension, sink) {
5780
7338
  const written = tag(tagName, path, sink);
5781
7339
  const isResource = resourceTypeOf(node) !== void 0;
7340
+ const asId = (name) => name === "id" && !isResource;
7341
+ const asUrl = (name) => name === "url" && inExtension;
5782
7342
  let attrs = isRoot ? ` xmlns="${FHIR_XML_NAMESPACE}"` : "";
5783
- let idAttr = "";
5784
- let urlAttr = "";
7343
+ let idText;
7344
+ let urlText;
7345
+ for (const { name, value } of node.properties) {
7346
+ const text = attributeText(value);
7347
+ if (text === void 0) continue;
7348
+ if (asId(name)) idText = text;
7349
+ else if (asUrl(name)) urlText = text;
7350
+ }
7351
+ if (idText !== void 0) {
7352
+ attrs += ` id="${attributeValue(idText, childPath(path, "id"), sink)}"`;
7353
+ }
7354
+ if (urlText !== void 0) {
7355
+ attrs += ` url="${attributeValue(urlText, childPath(path, "url"), sink)}"`;
7356
+ }
5785
7357
  const children = [];
5786
- for (const property of node.properties) {
5787
- const { name, value } = property;
5788
- if (name === "resourceType") continue;
5789
- if (name === "id" && !isResource) {
5790
- const text = attributeText(value);
5791
- if (text !== void 0) idAttr = ` id="${text}"`;
5792
- continue;
5793
- }
5794
- if (name === "url" && inExtension) {
5795
- const text = attributeText(value);
5796
- if (text !== void 0) urlAttr = ` url="${text}"`;
5797
- continue;
5798
- }
7358
+ for (const { name, value } of node.properties) {
7359
+ if (name === "resourceType" || asId(name) || asUrl(name)) continue;
5799
7360
  const childInExtension = name === "extension" || name === "modifierExtension";
5800
7361
  children.push(writeProperty(path, name, value, childInExtension, sink));
5801
7362
  }
5802
- attrs += idAttr + urlAttr;
5803
7363
  const inner = children.join("");
5804
7364
  return inner === "" ? `<${written}${attrs}/>` : `<${written}${attrs}>${inner}</${written}>`;
5805
7365
  }
@@ -5807,7 +7367,14 @@ function serializeResourceXml(node) {
5807
7367
  assertSerializable(node);
5808
7368
  const rt = resourceTypeOf(node);
5809
7369
  const tagName = rt ?? "Resource";
5810
- const sink = { refusedNames: [], refusedDivs: [] };
7370
+ const sink = {
7371
+ refusedNames: [],
7372
+ refusedDivs: [],
7373
+ refusedPrefixes: [],
7374
+ refusedDivPrefixes: [],
7375
+ refusedXmlNames: [],
7376
+ refusedCharacters: []
7377
+ };
5811
7378
  const xml = writeElement(rootPath(tagName), tagName, node, true, false, sink);
5812
7379
  if (sink.refusedNames.length > 0) refuseUnserializableNames([...new Set(sink.refusedNames)]);
5813
7380
  if (sink.refusedDivs.length > 0) refuseUnserializableDivMarkup([...new Set(sink.refusedDivs)]);
@@ -5815,6 +7382,18 @@ function serializeResourceXml(node) {
5815
7382
  assertXmlArrayWrapper(node);
5816
7383
  assertNoShadowedProperty(node);
5817
7384
  assertXmlResourceType(node);
7385
+ assertXmlForeignRoot(node);
7386
+ assertXmlValueChoiceWrapper(node);
7387
+ if (sink.refusedPrefixes.length > 0) {
7388
+ refuseUndeclarablePrefixes([...new Set(sink.refusedPrefixes)]);
7389
+ }
7390
+ if (sink.refusedDivPrefixes.length > 0) {
7391
+ refuseUnboundDivPrefixes([...new Set(sink.refusedDivPrefixes)]);
7392
+ }
7393
+ if (sink.refusedXmlNames.length > 0) refuseNonXmlNames([...new Set(sink.refusedXmlNames)]);
7394
+ if (sink.refusedCharacters.length > 0) {
7395
+ refuseNonXmlCharacters([...new Set(sink.refusedCharacters)]);
7396
+ }
5818
7397
  return xml;
5819
7398
  }
5820
7399
 
@@ -5969,8 +7548,8 @@ async function* streamNdjson(source, options = {}) {
5969
7548
  }
5970
7549
 
5971
7550
  // src/index.ts
5972
- var VERSION = "0.0.10";
7551
+ var VERSION = "0.1.1";
5973
7552
 
5974
- export { ABSENCE_CODES, ALLERGY_CLINICAL_SYSTEM, ALLERGY_SUBSTANCE_VALUESET, ALLERGY_VERIFICATION_SYSTEM, BINDING_STRENGTHS, BUNDLE_TYPES, CONDITION_CATEGORY_SYSTEM, CONDITION_CLINICAL_SYSTEM, CONDITION_VERIFICATION_SYSTEM, CPT_SYSTEM, CVX_SYSTEM, DATA_ABSENT_REASON_URL, DISCRIMINATOR_TYPES, ENTERED_IN_ERROR, FATAL_CODES, FHIR_XML_NAMESPACE, FhirCodecError, FhirDecimal, FhirInteger64, FhirProfileError, FhirSafetyError, FhirSerializeError, FhirXmlError, ICD10CM_SYSTEM, ICD9CM_SYSTEM, ISSUE_CODES, ISSUE_SEVERITIES, ISSUE_TYPES, InvalidProfileError, KNOWN_MODIFIER_EXTENSION_URLS, KNOWN_SYSTEMS, LOINC_SYSTEM, MAX_REFERENCE_DEPTH, MEDICATION_VALUESET, MODIFIER_ELEMENT_ROOT_TYPES, NDC_SYSTEM, NDJSON_ERROR_CODES, NOT_DONE, NOT_TAKEN, NO_KNOWN_ALLERGY, OBSERVATION_CATEGORY_SYSTEM, OBSERVATION_VALUE_TYPES, PATIENT_IDENTIFIER_STARTER, PRIMITIVE_TYPES, REFUTED, RXNORM_SYSTEM, SAFETY_RESOURCE_TYPES, SERIALIZE_ERROR_CODES, SNOMED_SCT, STARTER_PROFILES, STARTER_PROFILE_BASE_URL, TERMINOLOGY_BINDINGS, UCUM_SYSTEM, UNBOUNDED, UnsupportedFhirPathError, VALIDATION_CODES, VERSION, VITAL_SIGNS_CATEGORY, VITAL_SIGNS_PROFILE, VITAL_SIGN_OBSERVATION_STARTER, VITAL_SIGN_UNITS, WITHHELD, XHTML_NAMESPACE, XML_FATAL_CODES, absenceMarkers, arrayWrappedScalars, assertSafeToSummarize, baseSchema, buildBindingRegistry, buildBundleIndex, buildRegistry, codeOf, codingsOf, collectBundleIssues, collectInvariantIssues, collectProfileIssues, collectProfileVersionIssues, collectQuantityIssues, collectSafetyIssues, collectTerminologyIssues, complex, conflictingAbsenceMarkers, containedIndex, convertToBoolean, decimal, decimalPrecisionAtRisk, defineProfile, diagnosticFor, droppedText, duplicateProperty, entryProcessing, evaluateInvariant, generateSnapshot, getAllProperties, getProperty, hasCodeAnySystem, hasCoding, hasContainedCycle, integer64, isAbsenceCode, isAtomicBundle, isChoice, isComplex, isDroppedText, isKnownSystem, isList, isNestedArray, isPrimitive, isPrimitiveType, isRetracted, isUndefinedNull, list, loadStructureDefinition, locateDoseQuantities, matchSlices, matchesFixed, matchesPattern, misplacedPrimitiveExtension, mixedXmlSpelling, modifierElements, nearMissNegationCodes, nestedArray, nestedArrayContent, nestedArrays, nodesEquivalent, parseFhirPath, parseNdjsonLine, parseReference, parseResource, parseResourceXml, pathExists, primitive, readBundle, readInterpretations, readMedicationDoses, readObservationValue, readQuantity, readRawJson, readRawXml, readReferenceRanges, readSafety, requiredVitalSignUnits, resolveElement, resolvePath, resolveReference, resolveSlices, resourceType, serializeResource, serializeResourceXml, shadowedProperties, snapshotElements, starterProfile, streamNdjson, toOperationOutcome, tokenize, undefinedJsonNull, unexpectedXmlContent, unhandledModifierExtensions, unknownProperty, unreadableAbsenceMarkers, unreadableBooleans, unreadableNegationCodes, validatePrimitiveValue, validateResource, validateUcumShape, validationIssue, wouldLosePrecisionAsDouble };
7553
+ export { ABSENCE_CODES, ALLERGY_CLINICAL_SYSTEM, ALLERGY_SUBSTANCE_VALUESET, ALLERGY_VERIFICATION_SYSTEM, BINDING_STRENGTHS, BUNDLE_TYPES, CODE_SYSTEM_VERSION_RECORD_CODES, CODE_SYSTEM_VERSION_RECORD_SYSTEM, CONDITION_CATEGORY_SYSTEM, CONDITION_CLINICAL_SYSTEM, CONDITION_VERIFICATION_SYSTEM, CPT_SYSTEM, CVX_SYSTEM, DATA_ABSENT_REASON_URL, DISCRIMINATOR_TYPES, ENTERED_IN_ERROR, FATAL_CODES, FHIR_XML_NAMESPACE, FhirCodecError, FhirDecimal, FhirInteger64, FhirProfileError, FhirSafetyError, FhirSerializeError, FhirXmlError, ICD10CM_SYSTEM, ICD9CM_SYSTEM, ISSUE_CODES, ISSUE_SEVERITIES, ISSUE_TYPES, InvalidProfileError, KNOWN_MODIFIER_EXTENSION_URLS, KNOWN_SYSTEMS, LOINC_SYSTEM, MAX_REFERENCE_DEPTH, MEDICATION_VALUESET, MODIFIER_ELEMENT_ROOT_TYPES, NDC_SYSTEM, NDJSON_ERROR_CODES, NOT_DONE, NOT_TAKEN, NO_KNOWN_ALLERGY, OBSERVATION_CATEGORY_SYSTEM, OBSERVATION_VALUE_TYPES, PATIENT_IDENTIFIER_STARTER, PRIMITIVE_TYPES, REFUTED, RXNORM_SYSTEM, SAFETY_RESOURCE_TYPES, SERIALIZE_ERROR_CODES, SNOMED_SCT, STARTER_PROFILES, STARTER_PROFILE_BASE_URL, TERMINOLOGY_BINDINGS, UCUM_SYSTEM, UNBOUNDED, UnsupportedFhirPathError, VALIDATION_CODES, VERSION, VITAL_SIGNS_CATEGORY, VITAL_SIGNS_PROFILE, VITAL_SIGN_OBSERVATION_STARTER, VITAL_SIGN_UNITS, WITHHELD, XHTML_NAMESPACE, XML_FATAL_CODES, absenceMarkers, arrayWrappedScalars, assertSafeToSummarize, baseSchema, buildBindingRegistry, buildBundleIndex, buildRegistry, codeOf, codingsOf, collectBundleIssues, collectInvariantIssues, collectProfileIssues, collectProfileVersionIssues, collectQuantityIssues, collectSafetyIssues, collectTerminologyIssues, complex, conflictingAbsenceMarkers, containedIndex, convertToBoolean, decimal, decimalPrecisionAtRisk, defineProfile, diagnosticFor, droppedText, duplicateProperty, entryProcessing, evaluateInvariant, generateSnapshot, getAllProperties, getProperty, hasCodeAnySystem, hasCoding, hasContainedCycle, integer64, isAbsenceCode, isAtomicBundle, isChoice, isComplex, isDroppedText, isForeignRoot, isKnownSystem, isList, isNestedArray, isPrimitive, isPrimitiveType, isRetracted, isUndefinedNull, list, loadStructureDefinition, locateDoseQuantities, matchSlices, matchesFixed, matchesPattern, misplacedPrimitiveExtension, mixedXmlSpelling, modifierElements, nearMissNegationCodes, nestedArray, nestedArrayContent, nestedArrays, nodesEquivalent, parseFhirPath, parseNdjsonLine, parseReference, parseResource, parseResourceXml, pathExists, primitive, readBundle, readInterpretations, readMedicationDoses, readObservationValue, readQuantity, readRawJson, readRawXml, readReferenceRanges, readSafety, requiredVitalSignUnits, resolveElement, resolvePath, resolveReference, resolveSlices, resourceType, serializeResource, serializeResourceXml, shadowedProperties, snapshotElements, starterProfile, streamNdjson, toOperationOutcome, tokenize, undefinedJsonNull, unexpectedXmlContent, unhandledModifierExtensions, unknownProperty, unreadableAbsenceMarkers, unreadableBooleans, unreadableNegationCodes, validatePrimitiveValue, validateResource, validateUcumShape, validationIssue, wouldLosePrecisionAsDouble };
5975
7554
  //# sourceMappingURL=index.mjs.map
5976
7555
  //# sourceMappingURL=index.mjs.map