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