@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.
Files changed (47) hide show
  1. package/dist/{chunk-QXSWM5QV.cjs → chunk-JOZ6XZPO.cjs} +72 -5
  2. package/dist/chunk-JOZ6XZPO.cjs.map +1 -0
  3. package/dist/{chunk-ZEE5RXIS.js → chunk-KGEFZQ6W.js} +72 -5
  4. package/dist/chunk-KGEFZQ6W.js.map +1 -0
  5. package/dist/{chunk-63Q54EKN.cjs → chunk-SK77O3SG.cjs} +151 -35
  6. package/dist/chunk-SK77O3SG.cjs.map +1 -0
  7. package/dist/{chunk-YBDRWUQU.js → chunk-UU434UFQ.js} +151 -35
  8. package/dist/chunk-UU434UFQ.js.map +1 -0
  9. package/dist/cli.cjs +11 -11
  10. package/dist/cli.js +2 -2
  11. package/dist/express.cjs +12 -3
  12. package/dist/express.cjs.map +1 -1
  13. package/dist/express.d.cts +3 -2
  14. package/dist/express.d.ts +3 -2
  15. package/dist/express.js +11 -2
  16. package/dist/express.js.map +1 -1
  17. package/dist/fastify.cjs +22 -5
  18. package/dist/fastify.cjs.map +1 -1
  19. package/dist/fastify.d.cts +3 -2
  20. package/dist/fastify.d.ts +3 -2
  21. package/dist/fastify.js +21 -4
  22. package/dist/fastify.js.map +1 -1
  23. package/dist/index.cjs +3 -3
  24. package/dist/index.d.cts +12 -4
  25. package/dist/index.d.ts +12 -4
  26. package/dist/index.js +1 -1
  27. package/dist/lambda.cjs +4 -3
  28. package/dist/lambda.cjs.map +1 -1
  29. package/dist/lambda.d.cts +3 -2
  30. package/dist/lambda.d.ts +3 -2
  31. package/dist/lambda.js +3 -2
  32. package/dist/lambda.js.map +1 -1
  33. package/dist/server.cjs +2 -2
  34. package/dist/server.d.cts +4 -4
  35. package/dist/server.d.ts +4 -4
  36. package/dist/server.js +1 -1
  37. package/dist/{storage-BwszYwFo.d.cts → storage-BbzK-kFf.d.cts} +1 -1
  38. package/dist/{storage-B3GyJD2y.d.ts → storage-Cx7uXUl8.d.ts} +1 -1
  39. package/dist/{types-BegxU0wQ.d.ts → types-6Vw5fiat.d.ts} +7 -1
  40. package/dist/{types-Doq5cGNm.d.ts → types-BLLJeWe_.d.cts} +14 -0
  41. package/dist/{types-Doq5cGNm.d.cts → types-BLLJeWe_.d.ts} +14 -0
  42. package/dist/{types-hHf-a3hH.d.cts → types-Cdi4IkC9.d.cts} +7 -1
  43. package/package.json +3 -3
  44. package/dist/chunk-63Q54EKN.cjs.map +0 -1
  45. package/dist/chunk-QXSWM5QV.cjs.map +0 -1
  46. package/dist/chunk-YBDRWUQU.js.map +0 -1
  47. 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 typeCode = doc.typeCode ?? "34133-9";
1104
- const typeDisplay = doc.typeDisplay ?? "Summarization of episode note";
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 (profile === "ips") {
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 compositionDate = options?.compositionDate ?? (/* @__PURE__ */ new Date()).toISOString();
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
- compositionDate,
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: compositionDate,
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
- const manifest = {
1772
- files: [
1773
- {
1774
- contentType: "application/fhir+json;fhirVersion=4.0.1",
1775
- location: `${baseUrl}/${shlId}/content`
1776
- },
1777
- ...attachments.map((att, i) => ({
1778
- contentType: att.contentType,
1779
- location: `${baseUrl}/${shlId}/attachment/${i}`
1780
- }))
1781
- ],
1782
- status: "finalized",
1783
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1784
- };
1785
- try {
1786
- await storage.store(`${shlId}/manifest.json`, JSON.stringify(manifest));
1787
- } catch (err) {
1788
- throw new StorageError(
1789
- `Failed to store manifest: ${err instanceof Error ? err.message : String(err)}`,
1790
- "store"
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-YBDRWUQU.js.map
1945
- //# sourceMappingURL=chunk-YBDRWUQU.js.map
2060
+ //# sourceMappingURL=chunk-UU434UFQ.js.map
2061
+ //# sourceMappingURL=chunk-UU434UFQ.js.map