@fhirfly-io/shl 0.3.1 → 0.4.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/dist/{chunk-QXSWM5QV.cjs → chunk-JOZ6XZPO.cjs} +72 -5
- package/dist/chunk-JOZ6XZPO.cjs.map +1 -0
- package/dist/{chunk-ZEE5RXIS.js → chunk-KGEFZQ6W.js} +72 -5
- package/dist/chunk-KGEFZQ6W.js.map +1 -0
- package/dist/{chunk-63Q54EKN.cjs → chunk-SK77O3SG.cjs} +151 -35
- package/dist/chunk-SK77O3SG.cjs.map +1 -0
- package/dist/{chunk-YBDRWUQU.js → chunk-UU434UFQ.js} +151 -35
- package/dist/chunk-UU434UFQ.js.map +1 -0
- package/dist/cli.cjs +11 -11
- package/dist/cli.js +2 -2
- package/dist/express.cjs +12 -3
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +3 -2
- package/dist/express.d.ts +3 -2
- package/dist/express.js +11 -2
- package/dist/express.js.map +1 -1
- package/dist/fastify.cjs +22 -5
- package/dist/fastify.cjs.map +1 -1
- package/dist/fastify.d.cts +3 -2
- package/dist/fastify.d.ts +3 -2
- package/dist/fastify.js +21 -4
- package/dist/fastify.js.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1 -1
- package/dist/lambda.cjs +4 -3
- package/dist/lambda.cjs.map +1 -1
- package/dist/lambda.d.cts +3 -2
- package/dist/lambda.d.ts +3 -2
- package/dist/lambda.js +3 -2
- package/dist/lambda.js.map +1 -1
- package/dist/server.cjs +2 -2
- package/dist/server.d.cts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +1 -1
- package/dist/{storage-BwszYwFo.d.cts → storage-BbzK-kFf.d.cts} +1 -1
- package/dist/{storage-B3GyJD2y.d.ts → storage-Cx7uXUl8.d.ts} +1 -1
- package/dist/{types-BegxU0wQ.d.ts → types-6Vw5fiat.d.ts} +7 -1
- package/dist/{types-Doq5cGNm.d.ts → types-BLLJeWe_.d.cts} +14 -0
- package/dist/{types-Doq5cGNm.d.cts → types-BLLJeWe_.d.ts} +14 -0
- package/dist/{types-hHf-a3hH.d.cts → types-Cdi4IkC9.d.cts} +7 -1
- package/package.json +3 -3
- package/dist/chunk-63Q54EKN.cjs.map +0 -1
- package/dist/chunk-QXSWM5QV.cjs.map +0 -1
- package/dist/chunk-YBDRWUQU.js.map +0 -1
- package/dist/chunk-ZEE5RXIS.js.map +0 -1
|
@@ -174,7 +174,11 @@ var CODE_SYSTEMS = {
|
|
|
174
174
|
CVX: "http://hl7.org/fhir/sid/cvx",
|
|
175
175
|
ICD10CM: "http://hl7.org/fhir/sid/icd-10-cm",
|
|
176
176
|
CONDITION_CLINICAL: "http://terminology.hl7.org/CodeSystem/condition-clinical",
|
|
177
|
-
ALLERGY_CLINICAL: "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical"
|
|
177
|
+
ALLERGY_CLINICAL: "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
|
|
178
|
+
/** CMS Patient-Shared Health Document category code system */
|
|
179
|
+
CMS_PATIENT_SHARED_CATEGORY: "https://cms.gov/fhir/CodeSystem/patient-shared-category",
|
|
180
|
+
/** V3 ActCode security label for patient-asserted data */
|
|
181
|
+
SECURITY_PATAST: "PATAST"
|
|
178
182
|
};
|
|
179
183
|
|
|
180
184
|
// src/ips/medication.ts
|
|
@@ -1100,8 +1104,9 @@ function resolveDocuments(documents, patientRef, profile, generateUuid2) {
|
|
|
1100
1104
|
contentType,
|
|
1101
1105
|
data: base64Content
|
|
1102
1106
|
};
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1107
|
+
const isPshd = profile === "pshd";
|
|
1108
|
+
const typeCode = isPshd ? "60591-5" : doc.typeCode ?? "34133-9";
|
|
1109
|
+
const typeDisplay = isPshd ? "Patient summary Document" : doc.typeDisplay ?? "Summarization of episode note";
|
|
1105
1110
|
const docRefResource = {
|
|
1106
1111
|
resourceType: "DocumentReference",
|
|
1107
1112
|
id: docRefId,
|
|
@@ -1127,7 +1132,29 @@ function resolveDocuments(documents, patientRef, profile, generateUuid2) {
|
|
|
1127
1132
|
}
|
|
1128
1133
|
]
|
|
1129
1134
|
};
|
|
1130
|
-
if (
|
|
1135
|
+
if (isPshd) {
|
|
1136
|
+
docRefResource.category = [
|
|
1137
|
+
{
|
|
1138
|
+
coding: [
|
|
1139
|
+
{
|
|
1140
|
+
system: CODE_SYSTEMS.CMS_PATIENT_SHARED_CATEGORY,
|
|
1141
|
+
code: "patient-shared",
|
|
1142
|
+
display: "Patient Shared"
|
|
1143
|
+
}
|
|
1144
|
+
]
|
|
1145
|
+
}
|
|
1146
|
+
];
|
|
1147
|
+
docRefResource.author = [{ reference: patientRef }];
|
|
1148
|
+
docRefResource.meta = {
|
|
1149
|
+
security: [
|
|
1150
|
+
{
|
|
1151
|
+
system: "http://terminology.hl7.org/CodeSystem/v3-ActCode",
|
|
1152
|
+
code: CODE_SYSTEMS.SECURITY_PATAST,
|
|
1153
|
+
display: "patient asserted"
|
|
1154
|
+
}
|
|
1155
|
+
]
|
|
1156
|
+
};
|
|
1157
|
+
} else if (profile === "ips") {
|
|
1131
1158
|
docRefResource.meta = {
|
|
1132
1159
|
profile: ["http://hl7.org/fhir/uv/ips/StructureDefinition/DocumentReference-uv-ips"]
|
|
1133
1160
|
};
|
|
@@ -1326,11 +1353,9 @@ var Bundle = class {
|
|
|
1326
1353
|
async build(options) {
|
|
1327
1354
|
const profile = options?.profile ?? "ips";
|
|
1328
1355
|
const bundleId = options?.bundleId ?? generateUuid();
|
|
1329
|
-
const compositionId = generateUuid();
|
|
1330
1356
|
const patientId = generateUuid();
|
|
1331
|
-
const
|
|
1357
|
+
const timestamp = options?.compositionDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1332
1358
|
const patientFullUrl = `urn:uuid:${patientId}`;
|
|
1333
|
-
const compositionFullUrl = `urn:uuid:${compositionId}`;
|
|
1334
1359
|
const patientResource = normalizePatient(this._patient, patientId, profile);
|
|
1335
1360
|
const [medResult, condResult, allergyResult, immResult, resultResult] = await Promise.all([
|
|
1336
1361
|
resolveMedications(this._medications, patientFullUrl, profile, generateUuid),
|
|
@@ -1347,6 +1372,22 @@ var Bundle = class {
|
|
|
1347
1372
|
...immResult.warnings,
|
|
1348
1373
|
...resultResult.warnings
|
|
1349
1374
|
];
|
|
1375
|
+
if (profile === "pshd") {
|
|
1376
|
+
return this.buildPshdBundle(
|
|
1377
|
+
bundleId,
|
|
1378
|
+
timestamp,
|
|
1379
|
+
patientFullUrl,
|
|
1380
|
+
patientResource,
|
|
1381
|
+
medResult.entries,
|
|
1382
|
+
condResult.entries,
|
|
1383
|
+
allergyResult.entries,
|
|
1384
|
+
immResult.entries,
|
|
1385
|
+
resultResult.entries,
|
|
1386
|
+
docResult.entries
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
const compositionId = generateUuid();
|
|
1390
|
+
const compositionFullUrl = `urn:uuid:${compositionId}`;
|
|
1350
1391
|
const medRefs = medResult.entries.map((e) => ({ reference: e.fullUrl }));
|
|
1351
1392
|
const condRefs = condResult.entries.map((e) => ({ reference: e.fullUrl }));
|
|
1352
1393
|
const allergyRefs = allergyResult.entries.map((e) => ({ reference: e.fullUrl }));
|
|
@@ -1355,7 +1396,7 @@ var Bundle = class {
|
|
|
1355
1396
|
const composition = this.buildComposition(
|
|
1356
1397
|
compositionId,
|
|
1357
1398
|
patientFullUrl,
|
|
1358
|
-
|
|
1399
|
+
timestamp,
|
|
1359
1400
|
profile,
|
|
1360
1401
|
medRefs,
|
|
1361
1402
|
allergyRefs,
|
|
@@ -1381,7 +1422,7 @@ var Bundle = class {
|
|
|
1381
1422
|
value: `urn:uuid:${bundleId}`
|
|
1382
1423
|
},
|
|
1383
1424
|
type: "document",
|
|
1384
|
-
timestamp
|
|
1425
|
+
timestamp,
|
|
1385
1426
|
entry: entries
|
|
1386
1427
|
};
|
|
1387
1428
|
return bundle;
|
|
@@ -1401,6 +1442,37 @@ var Bundle = class {
|
|
|
1401
1442
|
path: "Patient.birthDate"
|
|
1402
1443
|
});
|
|
1403
1444
|
}
|
|
1445
|
+
if (profile === "pshd") {
|
|
1446
|
+
if (this._documents.length === 0) {
|
|
1447
|
+
issues.push({
|
|
1448
|
+
severity: "error",
|
|
1449
|
+
message: "PSHD requires at least one DocumentReference (1..1)",
|
|
1450
|
+
path: "Bundle.entry:DocumentReference"
|
|
1451
|
+
});
|
|
1452
|
+
} else {
|
|
1453
|
+
const hasPdf = this._documents.some(
|
|
1454
|
+
(d) => (d.contentType ?? "application/pdf") === "application/pdf"
|
|
1455
|
+
);
|
|
1456
|
+
if (!hasPdf) {
|
|
1457
|
+
issues.push({
|
|
1458
|
+
severity: "error",
|
|
1459
|
+
message: "PSHD requires at least one PDF document (contentType application/pdf)",
|
|
1460
|
+
path: "DocumentReference.content.attachment.contentType"
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (!this._patient.gender) {
|
|
1465
|
+
issues.push({
|
|
1466
|
+
severity: "warning",
|
|
1467
|
+
message: "Patient.gender recommended for PSHD demographic matching",
|
|
1468
|
+
path: "Patient.gender"
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
valid: issues.filter((i) => i.severity === "error").length === 0,
|
|
1473
|
+
issues
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1404
1476
|
if (profile === "ips") {
|
|
1405
1477
|
if (!this.hasValidName()) {
|
|
1406
1478
|
issues.push({
|
|
@@ -1476,6 +1548,28 @@ var Bundle = class {
|
|
|
1476
1548
|
const s = this._patient;
|
|
1477
1549
|
return !!(s.given || s.family || s.name);
|
|
1478
1550
|
}
|
|
1551
|
+
buildPshdBundle(bundleId, timestamp, patientFullUrl, patientResource, medEntries, condEntries, allergyEntries, immEntries, resultEntries, docEntries) {
|
|
1552
|
+
const entries = [
|
|
1553
|
+
{ fullUrl: patientFullUrl, resource: patientResource },
|
|
1554
|
+
...medEntries,
|
|
1555
|
+
...condEntries,
|
|
1556
|
+
...allergyEntries,
|
|
1557
|
+
...immEntries,
|
|
1558
|
+
...resultEntries,
|
|
1559
|
+
...docEntries
|
|
1560
|
+
];
|
|
1561
|
+
return {
|
|
1562
|
+
resourceType: "Bundle",
|
|
1563
|
+
id: bundleId,
|
|
1564
|
+
identifier: {
|
|
1565
|
+
system: "urn:ietf:rfc:3986",
|
|
1566
|
+
value: `urn:uuid:${bundleId}`
|
|
1567
|
+
},
|
|
1568
|
+
type: "collection",
|
|
1569
|
+
timestamp,
|
|
1570
|
+
entry: entries
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1479
1573
|
buildComposition(id, patientRef, date, profile, medRefs, allergyRefs, condRefs, immRefs, resultRefs) {
|
|
1480
1574
|
const composition = {
|
|
1481
1575
|
resourceType: "Composition",
|
|
@@ -1709,6 +1803,25 @@ async function create(options) {
|
|
|
1709
1803
|
if (!storage?.baseUrl) {
|
|
1710
1804
|
throw new ValidationError("storage with baseUrl is required");
|
|
1711
1805
|
}
|
|
1806
|
+
let mode = options.mode ?? "manifest";
|
|
1807
|
+
if (options.compliance === "pshd") {
|
|
1808
|
+
mode = "direct";
|
|
1809
|
+
if (passcode) {
|
|
1810
|
+
throw new ValidationError(
|
|
1811
|
+
"PSHD compliance forbids passcode (flag U is incompatible with flag P)"
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
if (!expiresAt) {
|
|
1815
|
+
throw new ValidationError(
|
|
1816
|
+
"PSHD compliance requires expiresAt (short-lived links for point-of-care)"
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (mode === "direct" && passcode) {
|
|
1821
|
+
throw new ValidationError(
|
|
1822
|
+
"Direct mode (flag U) is incompatible with passcode (flag P)"
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1712
1825
|
const key = generateKey();
|
|
1713
1826
|
const shlId = generateShlId();
|
|
1714
1827
|
let jwe;
|
|
@@ -1768,31 +1881,34 @@ async function create(options) {
|
|
|
1768
1881
|
);
|
|
1769
1882
|
}
|
|
1770
1883
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1884
|
+
if (mode === "manifest") {
|
|
1885
|
+
const manifest = {
|
|
1886
|
+
files: [
|
|
1887
|
+
{
|
|
1888
|
+
contentType: "application/fhir+json;fhirVersion=4.0.1",
|
|
1889
|
+
location: `${baseUrl}/${shlId}/content`
|
|
1890
|
+
},
|
|
1891
|
+
...attachments.map((att, i) => ({
|
|
1892
|
+
contentType: att.contentType,
|
|
1893
|
+
location: `${baseUrl}/${shlId}/attachment/${i}`
|
|
1894
|
+
}))
|
|
1895
|
+
],
|
|
1896
|
+
status: "finalized",
|
|
1897
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1898
|
+
};
|
|
1899
|
+
try {
|
|
1900
|
+
await storage.store(`${shlId}/manifest.json`, JSON.stringify(manifest));
|
|
1901
|
+
} catch (err) {
|
|
1902
|
+
throw new StorageError(
|
|
1903
|
+
`Failed to store manifest: ${err instanceof Error ? err.message : String(err)}`,
|
|
1904
|
+
"store"
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1792
1907
|
}
|
|
1793
1908
|
const metadata = {
|
|
1794
1909
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1795
1910
|
};
|
|
1911
|
+
if (mode === "direct") metadata.mode = "direct";
|
|
1796
1912
|
if (passcode) {
|
|
1797
1913
|
metadata.passcode = createHash("sha256").update(passcode).digest("hex");
|
|
1798
1914
|
}
|
|
@@ -1806,7 +1922,7 @@ async function create(options) {
|
|
|
1806
1922
|
"store"
|
|
1807
1923
|
);
|
|
1808
1924
|
}
|
|
1809
|
-
const flags = buildFlags(passcode);
|
|
1925
|
+
const flags = buildFlags(mode, passcode);
|
|
1810
1926
|
const shlPayload = {
|
|
1811
1927
|
url: `${baseUrl}/${shlId}`,
|
|
1812
1928
|
key: base64url(key),
|
|
@@ -1833,8 +1949,8 @@ async function create(options) {
|
|
|
1833
1949
|
if (debug) result.debugBundlePath = `${shlId}/bundle.json`;
|
|
1834
1950
|
return result;
|
|
1835
1951
|
}
|
|
1836
|
-
function buildFlags(passcode) {
|
|
1837
|
-
const flags = ["L"];
|
|
1952
|
+
function buildFlags(mode, passcode) {
|
|
1953
|
+
const flags = [mode === "direct" ? "U" : "L"];
|
|
1838
1954
|
if (passcode) flags.push("P");
|
|
1839
1955
|
return flags.sort().join("");
|
|
1840
1956
|
}
|
|
@@ -1941,5 +2057,5 @@ async function revoke(shlId, storage) {
|
|
|
1941
2057
|
}
|
|
1942
2058
|
|
|
1943
2059
|
export { ips_exports, shl_exports };
|
|
1944
|
-
//# sourceMappingURL=chunk-
|
|
1945
|
-
//# sourceMappingURL=chunk-
|
|
2060
|
+
//# sourceMappingURL=chunk-UU434UFQ.js.map
|
|
2061
|
+
//# sourceMappingURL=chunk-UU434UFQ.js.map
|