@fhirfly-io/shl 0.3.2 → 0.5.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 (48) hide show
  1. package/README.md +53 -0
  2. package/dist/{chunk-63Q54EKN.cjs → chunk-CN44QKWJ.cjs} +185 -36
  3. package/dist/chunk-CN44QKWJ.cjs.map +1 -0
  4. package/dist/{chunk-QXSWM5QV.cjs → chunk-H37YQWF2.cjs} +90 -11
  5. package/dist/chunk-H37YQWF2.cjs.map +1 -0
  6. package/dist/{chunk-ZEE5RXIS.js → chunk-IYRQRY4A.js} +90 -12
  7. package/dist/chunk-IYRQRY4A.js.map +1 -0
  8. package/dist/{chunk-YBDRWUQU.js → chunk-YUMCDN7I.js} +185 -36
  9. package/dist/chunk-YUMCDN7I.js.map +1 -0
  10. package/dist/cli.cjs +11 -11
  11. package/dist/cli.js +2 -2
  12. package/dist/express.cjs +12 -3
  13. package/dist/express.cjs.map +1 -1
  14. package/dist/express.d.cts +3 -2
  15. package/dist/express.d.ts +3 -2
  16. package/dist/express.js +11 -2
  17. package/dist/express.js.map +1 -1
  18. package/dist/fastify.cjs +22 -5
  19. package/dist/fastify.cjs.map +1 -1
  20. package/dist/fastify.d.cts +3 -2
  21. package/dist/fastify.d.ts +3 -2
  22. package/dist/fastify.js +21 -4
  23. package/dist/fastify.js.map +1 -1
  24. package/dist/index.cjs +3 -3
  25. package/dist/index.d.cts +15 -5
  26. package/dist/index.d.ts +15 -5
  27. package/dist/index.js +1 -1
  28. package/dist/lambda.cjs +4 -3
  29. package/dist/lambda.cjs.map +1 -1
  30. package/dist/lambda.d.cts +3 -2
  31. package/dist/lambda.d.ts +3 -2
  32. package/dist/lambda.js +3 -2
  33. package/dist/lambda.js.map +1 -1
  34. package/dist/server.cjs +6 -2
  35. package/dist/server.d.cts +4 -4
  36. package/dist/server.d.ts +4 -4
  37. package/dist/server.js +1 -1
  38. package/dist/{storage-B3GyJD2y.d.ts → storage-CvsOM1Eu.d.ts} +1 -1
  39. package/dist/{storage-BwszYwFo.d.cts → storage-DggeMhOI.d.cts} +1 -1
  40. package/dist/{types-BegxU0wQ.d.ts → types--SjcaaWT.d.ts} +26 -2
  41. package/dist/{types-hHf-a3hH.d.cts → types-BcfxBDTA.d.cts} +26 -2
  42. package/dist/{types-Doq5cGNm.d.ts → types-CmeXnyth.d.cts} +31 -3
  43. package/dist/{types-Doq5cGNm.d.cts → types-CmeXnyth.d.ts} +31 -3
  44. package/package.json +1 -1
  45. package/dist/chunk-63Q54EKN.cjs.map +0 -1
  46. package/dist/chunk-QXSWM5QV.cjs.map +0 -1
  47. package/dist/chunk-YBDRWUQU.js.map +0 -1
  48. package/dist/chunk-ZEE5RXIS.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/types.ts","../src/server/handler.ts"],"names":["createHash","timingSafeEqual"],"mappings":"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","file":"chunk-H37YQWF2.cjs","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type { SHLStorage, SHLMetadata } from \"../shl/types.js\";\n\n/**\n * Framework-agnostic incoming request.\n */\nexport interface HandlerRequest {\n /** HTTP method (uppercase) */\n method: string;\n /** Path relative to mount point, e.g., \"/{shlId}\" or \"/{shlId}/content\" */\n path: string;\n /** Parsed JSON body (for POST requests) */\n body?: unknown;\n /** Request headers (lowercase keys) */\n headers: Record<string, string | undefined>;\n /** Query parameters (e.g., { recipient: \"Dr. Smith\" }) */\n query?: Record<string, string | undefined>;\n}\n\n/**\n * Framework-agnostic outgoing response.\n */\nexport interface HandlerResponse {\n /** HTTP status code */\n status: number;\n /** Response headers */\n headers: Record<string, string>;\n /** Response body (string for JSON, Uint8Array for binary) */\n body: string | Uint8Array;\n}\n\n/**\n * Extended storage interface for server-side operations.\n *\n * Adds `read` and `updateMetadata` to the base `SHLStorage` interface.\n * Server storage needs to read files and atomically update metadata\n * (e.g., increment access counts).\n */\nexport interface SHLServerStorage extends SHLStorage {\n /** Read a file by key. Returns null if not found. */\n read(key: string): Promise<string | Uint8Array | null>;\n\n /**\n * Atomically read-modify-write metadata for an SHL.\n *\n * The `updater` function receives the current metadata and returns\n * the updated metadata (or `null` to signal no update should occur).\n *\n * @param shlId - The SHL identifier\n * @param updater - Function that transforms metadata\n * @returns The updated metadata, or null if the SHL was not found or updater returned null\n */\n updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null>;\n}\n\n/**\n * Extended storage interface that includes audit logging.\n *\n * When a storage backend implements `AuditableStorage`, the server handler\n * will automatically call `onAccess()` after each successful retrieval.\n * This is in addition to the `onAccess` callback on `SHLHandlerConfig`.\n *\n * Existing `SHLServerStorage` implementations continue to work unchanged —\n * audit logging is opt-in.\n */\nexport interface AuditableStorage extends SHLServerStorage {\n /** Called after each successful SHL retrieval. */\n onAccess(shlId: string, event: AccessEvent): Promise<void>;\n}\n\n/**\n * Type guard: checks if a storage backend implements AuditableStorage.\n */\nexport function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage {\n return typeof (storage as AuditableStorage).onAccess === \"function\";\n}\n\n/**\n * CORS configuration for the SHL server handler.\n *\n * By default, the handler adds permissive CORS headers to all responses\n * so that browser-based SHL viewers can access self-hosted servers.\n * Set `cors: false` to disable, or provide an object to customize.\n */\nexport interface CorsConfig {\n /** Allowed origin(s). Default: `\"*\"` */\n origin?: string;\n /** Allowed methods. Default: `\"GET, POST, OPTIONS\"` */\n methods?: string;\n /** Allowed headers. Default: `\"Content-Type, Authorization\"` */\n headers?: string;\n}\n\n/**\n * Configuration for the SHL server handler.\n */\nexport interface SHLHandlerConfig {\n /** Server storage backend (must implement SHLServerStorage) */\n storage: SHLServerStorage;\n\n /**\n * Optional callback invoked on each successful manifest access.\n * Useful for logging, analytics, or custom access control.\n */\n onAccess?: (event: AccessEvent) => void | Promise<void>;\n\n /**\n * CORS configuration. Defaults to permissive headers (`Access-Control-Allow-Origin: *`).\n * Set to `false` to disable CORS headers entirely.\n */\n cors?: CorsConfig | false;\n}\n\n/**\n * Event emitted on each successful manifest access.\n */\nexport interface AccessEvent {\n /** The SHL identifier */\n shlId: string;\n /** Current access count (after increment) */\n accessCount: number;\n /** Timestamp of the access */\n timestamp: Date;\n /** Recipient identifier from query parameter (e.g., provider name) */\n recipient?: string;\n /** Retrieval mode used for this access */\n mode?: \"manifest\" | \"direct\";\n}\n","// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} from \"./types.js\";\nimport { isAuditableStorage } from \"./types.js\";\nimport type { SHLMetadata, Manifest } from \"../shl/types.js\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Create a framework-agnostic SHL request handler.\n *\n * Returns an async function that processes incoming requests and returns\n * responses. This handler implements three routes:\n *\n * - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)\n * - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)\n * - `GET /{shlId}/attachment/{index}` — Attachment endpoint (serves encrypted attachment)\n *\n * By default, CORS headers are added to all responses so browser-based SHL\n * viewers can access self-hosted servers. Set `cors: false` to disable.\n *\n * Framework adapters (Express, Fastify, Lambda) translate their native\n * request/response types to/from `HandlerRequest`/`HandlerResponse`.\n *\n * @example\n * ```ts\n * const handle = createHandler({ storage });\n * const response = await handle({\n * method: \"POST\",\n * path: \"/abc123\",\n * body: { passcode: \"1234\" },\n * headers: { \"content-type\": \"application/json\" },\n * });\n * ```\n */\nexport function createHandler(\n config: SHLHandlerConfig,\n): (req: HandlerRequest) => Promise<HandlerResponse> {\n const { storage, onAccess } = config;\n const corsHeaders = resolveCorsHeaders(config.cors);\n\n return async (req: HandlerRequest): Promise<HandlerResponse> => {\n // Handle CORS preflight\n if (req.method === \"OPTIONS\") {\n return {\n status: 204,\n headers: { ...corsHeaders },\n body: \"\",\n };\n }\n\n // Normalize path: strip leading slash, split into segments\n const path = req.path.replace(/^\\/+/, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n\n let response: HandlerResponse;\n\n // Route: POST /{shlId} → manifest\n if (segments.length === 1 && req.method === \"POST\") {\n response = await handleManifest(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId} → direct access (flag U)\n else if (segments.length === 1 && req.method === \"GET\") {\n response = await handleDirectAccess(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId}/content → serve encrypted content\n else if (segments.length === 2 && segments[1] === \"content\" && req.method === \"GET\") {\n response = await handleContent(segments[0]!, storage);\n }\n // Route: GET /{shlId}/attachment/{index} → serve encrypted attachment\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method === \"GET\") {\n response = await handleAttachment(segments[0]!, segments[2]!, storage);\n }\n // Method not allowed for known paths\n else if (segments.length === 2 && segments[1] === \"content\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for content requests.\" });\n }\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for attachment requests.\" });\n }\n else {\n response = jsonResponse(404, { error: \"Not found\" });\n }\n\n // Inject CORS headers into every response\n response.headers = { ...response.headers, ...corsHeaders };\n return response;\n };\n}\n\n/** Resolve CORS headers from config. Returns empty object if disabled. */\nfunction resolveCorsHeaders(cors: SHLHandlerConfig[\"cors\"]): Record<string, string> {\n if (cors === false) return {};\n const c: CorsConfig = cors ?? {};\n return {\n \"access-control-allow-origin\": c.origin ?? \"*\",\n \"access-control-allow-methods\": c.methods ?? \"GET, POST, OPTIONS\",\n \"access-control-allow-headers\": c.headers ?? \"Content-Type, Authorization\",\n };\n}\n\nasync function handleManifest(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read manifest to verify the SHL exists\n const manifestRaw = await storage.read(`${shlId}/manifest.json`);\n if (manifestRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Atomically check access control + increment counter\n let updatedMetadata: SHLMetadata | null = null;\n let accessDeniedReason: \"expired\" | \"exhausted\" | \"passcode\" | null = null;\n const reqBody = (req.body && typeof req.body === \"object\" ? req.body : {}) as Record<string, unknown>;\n const providedPasscode = typeof reqBody[\"passcode\"] === \"string\" ? reqBody[\"passcode\"] : undefined;\n\n updatedMetadata = await storage.updateMetadata(shlId, (metadata) => {\n // Check expiration\n if (metadata.expiresAt) {\n const expiresAt = new Date(metadata.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n // Check access count\n const currentCount = metadata.accessCount ?? 0;\n if (metadata.maxAccesses !== undefined && currentCount >= metadata.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n // Check passcode (timing-safe comparison with SHA-256 hash)\n if (metadata.passcode) {\n if (!providedPasscode) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n const providedHash = createHash(\"sha256\").update(providedPasscode).digest(\"hex\");\n const storedHash = metadata.passcode;\n const a = Buffer.from(providedHash);\n const b = Buffer.from(storedHash);\n // Constant-time comparison: compare with self if lengths differ to avoid timing leak\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n }\n\n // Access granted — increment count\n return {\n ...metadata,\n accessCount: currentCount + 1,\n };\n });\n\n // Handle access control failures\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (accessDeniedReason === \"passcode\") {\n return jsonResponse(401, { error: \"Invalid passcode\" });\n }\n\n // If updateMetadata returned null but no denied reason, metadata file is missing\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const accessEvent = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n if (onAccess) {\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(accessEvent)).catch(() => {});\n }\n // If storage is auditable, also fire the storage-level audit hook\n if (isAuditableStorage(storage)) {\n Promise.resolve(storage.onAccess(shlId, accessEvent)).catch(() => {});\n }\n\n // Return manifest\n const manifestStr = typeof manifestRaw === \"string\"\n ? manifestRaw\n : new TextDecoder().decode(manifestRaw);\n const manifest = JSON.parse(manifestStr) as Manifest;\n\n return jsonResponse(200, manifest);\n}\n\nasync function handleDirectAccess(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read metadata to check if this is a direct-mode SHL\n const metadataRaw = await storage.read(`${shlId}/metadata.json`);\n if (metadataRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n const metadataStr = typeof metadataRaw === \"string\" ? metadataRaw : new TextDecoder().decode(metadataRaw);\n const metadata = JSON.parse(metadataStr) as SHLMetadata;\n\n // Only direct-mode SHLs support GET retrieval\n if (metadata.mode !== \"direct\") {\n return jsonResponse(405, { error: \"Method not allowed. Use POST for manifest requests.\" });\n }\n\n // Access control: expiration, access count (atomic)\n let accessDeniedReason: \"expired\" | \"exhausted\" | null = null;\n const updatedMetadata = await storage.updateMetadata(shlId, (current) => {\n if (current.expiresAt) {\n const expiresAt = new Date(current.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n const currentCount = current.accessCount ?? 0;\n if (current.maxAccesses !== undefined && currentCount >= current.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n return {\n ...current,\n accessCount: currentCount + 1,\n };\n });\n\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const directAccessEvent = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n if (onAccess) {\n Promise.resolve(onAccess(directAccessEvent)).catch(() => {});\n }\n // If storage is auditable, also fire the storage-level audit hook\n if (isAuditableStorage(storage)) {\n Promise.resolve(storage.onAccess(shlId, directAccessEvent)).catch(() => {});\n }\n\n // Serve the encrypted content directly\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\" ? content : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleContent(\n shlId: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleAttachment(\n shlId: string,\n index: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n if (!/^\\d+$/.test(index)) {\n return jsonResponse(400, { error: \"Invalid attachment index\" });\n }\n const content = await storage.read(`${shlId}/attachment-${index}.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Attachment not found\" });\n }\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nfunction jsonResponse(status: number, body: unknown): HandlerResponse {\n return {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n body: JSON.stringify(body),\n };\n}\n"]}
@@ -1,6 +1,9 @@
1
1
  import { createHash, timingSafeEqual } from 'crypto';
2
2
 
3
- // src/server/handler.ts
3
+ // src/server/types.ts
4
+ function isAuditableStorage(storage) {
5
+ return typeof storage.onAccess === "function";
6
+ }
4
7
  function createHandler(config) {
5
8
  const { storage, onAccess } = config;
6
9
  const corsHeaders = resolveCorsHeaders(config.cors);
@@ -17,12 +20,12 @@ function createHandler(config) {
17
20
  let response;
18
21
  if (segments.length === 1 && req.method === "POST") {
19
22
  response = await handleManifest(segments[0], req, storage, onAccess);
23
+ } else if (segments.length === 1 && req.method === "GET") {
24
+ response = await handleDirectAccess(segments[0], req, storage, onAccess);
20
25
  } else if (segments.length === 2 && segments[1] === "content" && req.method === "GET") {
21
26
  response = await handleContent(segments[0], storage);
22
27
  } else if (segments.length === 3 && segments[1] === "attachment" && req.method === "GET") {
23
28
  response = await handleAttachment(segments[0], segments[2], storage);
24
- } else if (segments.length === 1 && req.method !== "POST") {
25
- response = jsonResponse(405, { error: "Method not allowed. Use POST for manifest requests." });
26
29
  } else if (segments.length === 2 && segments[1] === "content" && req.method !== "GET") {
27
30
  response = jsonResponse(405, { error: "Method not allowed. Use GET for content requests." });
28
31
  } else if (segments.length === 3 && segments[1] === "attachment" && req.method !== "GET") {
@@ -96,19 +99,94 @@ async function handleManifest(shlId, req, storage, onAccess) {
96
99
  if (updatedMetadata === null) {
97
100
  return jsonResponse(404, { error: "SHL not found" });
98
101
  }
102
+ const recipient = typeof req.query?.["recipient"] === "string" ? req.query["recipient"] : void 0;
103
+ const accessEvent = {
104
+ shlId,
105
+ accessCount: updatedMetadata.accessCount ?? 1,
106
+ timestamp: /* @__PURE__ */ new Date(),
107
+ mode: "manifest",
108
+ ...recipient ? { recipient } : {}
109
+ };
99
110
  if (onAccess) {
100
- const event = {
101
- shlId,
102
- accessCount: updatedMetadata.accessCount ?? 1,
103
- timestamp: /* @__PURE__ */ new Date()
104
- };
105
- Promise.resolve(onAccess(event)).catch(() => {
111
+ Promise.resolve(onAccess(accessEvent)).catch(() => {
112
+ });
113
+ }
114
+ if (isAuditableStorage(storage)) {
115
+ Promise.resolve(storage.onAccess(shlId, accessEvent)).catch(() => {
106
116
  });
107
117
  }
108
118
  const manifestStr = typeof manifestRaw === "string" ? manifestRaw : new TextDecoder().decode(manifestRaw);
109
119
  const manifest = JSON.parse(manifestStr);
110
120
  return jsonResponse(200, manifest);
111
121
  }
122
+ async function handleDirectAccess(shlId, req, storage, onAccess) {
123
+ const metadataRaw = await storage.read(`${shlId}/metadata.json`);
124
+ if (metadataRaw === null) {
125
+ return jsonResponse(404, { error: "SHL not found" });
126
+ }
127
+ const metadataStr = typeof metadataRaw === "string" ? metadataRaw : new TextDecoder().decode(metadataRaw);
128
+ const metadata = JSON.parse(metadataStr);
129
+ if (metadata.mode !== "direct") {
130
+ return jsonResponse(405, { error: "Method not allowed. Use POST for manifest requests." });
131
+ }
132
+ let accessDeniedReason = null;
133
+ const updatedMetadata = await storage.updateMetadata(shlId, (current) => {
134
+ if (current.expiresAt) {
135
+ const expiresAt = new Date(current.expiresAt);
136
+ if (expiresAt.getTime() <= Date.now()) {
137
+ accessDeniedReason = "expired";
138
+ return null;
139
+ }
140
+ }
141
+ const currentCount = current.accessCount ?? 0;
142
+ if (current.maxAccesses !== void 0 && currentCount >= current.maxAccesses) {
143
+ accessDeniedReason = "exhausted";
144
+ return null;
145
+ }
146
+ return {
147
+ ...current,
148
+ accessCount: currentCount + 1
149
+ };
150
+ });
151
+ if (accessDeniedReason === "expired") {
152
+ return jsonResponse(410, { error: "SHL has expired" });
153
+ }
154
+ if (accessDeniedReason === "exhausted") {
155
+ return jsonResponse(410, { error: "SHL access limit reached" });
156
+ }
157
+ if (updatedMetadata === null) {
158
+ return jsonResponse(404, { error: "SHL not found" });
159
+ }
160
+ const recipient = typeof req.query?.["recipient"] === "string" ? req.query["recipient"] : void 0;
161
+ const directAccessEvent = {
162
+ shlId,
163
+ accessCount: updatedMetadata.accessCount ?? 1,
164
+ timestamp: /* @__PURE__ */ new Date(),
165
+ mode: "direct",
166
+ ...recipient ? { recipient } : {}
167
+ };
168
+ if (onAccess) {
169
+ Promise.resolve(onAccess(directAccessEvent)).catch(() => {
170
+ });
171
+ }
172
+ if (isAuditableStorage(storage)) {
173
+ Promise.resolve(storage.onAccess(shlId, directAccessEvent)).catch(() => {
174
+ });
175
+ }
176
+ const content = await storage.read(`${shlId}/content.jwe`);
177
+ if (content === null) {
178
+ return jsonResponse(404, { error: "Content not found" });
179
+ }
180
+ const body = typeof content === "string" ? content : new TextDecoder().decode(content);
181
+ return {
182
+ status: 200,
183
+ headers: {
184
+ "content-type": "application/jose",
185
+ "cache-control": "no-store"
186
+ },
187
+ body
188
+ };
189
+ }
112
190
  async function handleContent(shlId, storage) {
113
191
  const content = await storage.read(`${shlId}/content.jwe`);
114
192
  if (content === null) {
@@ -153,6 +231,6 @@ function jsonResponse(status, body) {
153
231
  };
154
232
  }
155
233
 
156
- export { createHandler };
157
- //# sourceMappingURL=chunk-ZEE5RXIS.js.map
158
- //# sourceMappingURL=chunk-ZEE5RXIS.js.map
234
+ export { createHandler, isAuditableStorage };
235
+ //# sourceMappingURL=chunk-IYRQRY4A.js.map
236
+ //# sourceMappingURL=chunk-IYRQRY4A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/types.ts","../src/server/handler.ts"],"names":[],"mappings":"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","file":"chunk-IYRQRY4A.js","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type { SHLStorage, SHLMetadata } from \"../shl/types.js\";\n\n/**\n * Framework-agnostic incoming request.\n */\nexport interface HandlerRequest {\n /** HTTP method (uppercase) */\n method: string;\n /** Path relative to mount point, e.g., \"/{shlId}\" or \"/{shlId}/content\" */\n path: string;\n /** Parsed JSON body (for POST requests) */\n body?: unknown;\n /** Request headers (lowercase keys) */\n headers: Record<string, string | undefined>;\n /** Query parameters (e.g., { recipient: \"Dr. Smith\" }) */\n query?: Record<string, string | undefined>;\n}\n\n/**\n * Framework-agnostic outgoing response.\n */\nexport interface HandlerResponse {\n /** HTTP status code */\n status: number;\n /** Response headers */\n headers: Record<string, string>;\n /** Response body (string for JSON, Uint8Array for binary) */\n body: string | Uint8Array;\n}\n\n/**\n * Extended storage interface for server-side operations.\n *\n * Adds `read` and `updateMetadata` to the base `SHLStorage` interface.\n * Server storage needs to read files and atomically update metadata\n * (e.g., increment access counts).\n */\nexport interface SHLServerStorage extends SHLStorage {\n /** Read a file by key. Returns null if not found. */\n read(key: string): Promise<string | Uint8Array | null>;\n\n /**\n * Atomically read-modify-write metadata for an SHL.\n *\n * The `updater` function receives the current metadata and returns\n * the updated metadata (or `null` to signal no update should occur).\n *\n * @param shlId - The SHL identifier\n * @param updater - Function that transforms metadata\n * @returns The updated metadata, or null if the SHL was not found or updater returned null\n */\n updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null>;\n}\n\n/**\n * Extended storage interface that includes audit logging.\n *\n * When a storage backend implements `AuditableStorage`, the server handler\n * will automatically call `onAccess()` after each successful retrieval.\n * This is in addition to the `onAccess` callback on `SHLHandlerConfig`.\n *\n * Existing `SHLServerStorage` implementations continue to work unchanged —\n * audit logging is opt-in.\n */\nexport interface AuditableStorage extends SHLServerStorage {\n /** Called after each successful SHL retrieval. */\n onAccess(shlId: string, event: AccessEvent): Promise<void>;\n}\n\n/**\n * Type guard: checks if a storage backend implements AuditableStorage.\n */\nexport function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage {\n return typeof (storage as AuditableStorage).onAccess === \"function\";\n}\n\n/**\n * CORS configuration for the SHL server handler.\n *\n * By default, the handler adds permissive CORS headers to all responses\n * so that browser-based SHL viewers can access self-hosted servers.\n * Set `cors: false` to disable, or provide an object to customize.\n */\nexport interface CorsConfig {\n /** Allowed origin(s). Default: `\"*\"` */\n origin?: string;\n /** Allowed methods. Default: `\"GET, POST, OPTIONS\"` */\n methods?: string;\n /** Allowed headers. Default: `\"Content-Type, Authorization\"` */\n headers?: string;\n}\n\n/**\n * Configuration for the SHL server handler.\n */\nexport interface SHLHandlerConfig {\n /** Server storage backend (must implement SHLServerStorage) */\n storage: SHLServerStorage;\n\n /**\n * Optional callback invoked on each successful manifest access.\n * Useful for logging, analytics, or custom access control.\n */\n onAccess?: (event: AccessEvent) => void | Promise<void>;\n\n /**\n * CORS configuration. Defaults to permissive headers (`Access-Control-Allow-Origin: *`).\n * Set to `false` to disable CORS headers entirely.\n */\n cors?: CorsConfig | false;\n}\n\n/**\n * Event emitted on each successful manifest access.\n */\nexport interface AccessEvent {\n /** The SHL identifier */\n shlId: string;\n /** Current access count (after increment) */\n accessCount: number;\n /** Timestamp of the access */\n timestamp: Date;\n /** Recipient identifier from query parameter (e.g., provider name) */\n recipient?: string;\n /** Retrieval mode used for this access */\n mode?: \"manifest\" | \"direct\";\n}\n","// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} from \"./types.js\";\nimport { isAuditableStorage } from \"./types.js\";\nimport type { SHLMetadata, Manifest } from \"../shl/types.js\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Create a framework-agnostic SHL request handler.\n *\n * Returns an async function that processes incoming requests and returns\n * responses. This handler implements three routes:\n *\n * - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)\n * - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)\n * - `GET /{shlId}/attachment/{index}` — Attachment endpoint (serves encrypted attachment)\n *\n * By default, CORS headers are added to all responses so browser-based SHL\n * viewers can access self-hosted servers. Set `cors: false` to disable.\n *\n * Framework adapters (Express, Fastify, Lambda) translate their native\n * request/response types to/from `HandlerRequest`/`HandlerResponse`.\n *\n * @example\n * ```ts\n * const handle = createHandler({ storage });\n * const response = await handle({\n * method: \"POST\",\n * path: \"/abc123\",\n * body: { passcode: \"1234\" },\n * headers: { \"content-type\": \"application/json\" },\n * });\n * ```\n */\nexport function createHandler(\n config: SHLHandlerConfig,\n): (req: HandlerRequest) => Promise<HandlerResponse> {\n const { storage, onAccess } = config;\n const corsHeaders = resolveCorsHeaders(config.cors);\n\n return async (req: HandlerRequest): Promise<HandlerResponse> => {\n // Handle CORS preflight\n if (req.method === \"OPTIONS\") {\n return {\n status: 204,\n headers: { ...corsHeaders },\n body: \"\",\n };\n }\n\n // Normalize path: strip leading slash, split into segments\n const path = req.path.replace(/^\\/+/, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n\n let response: HandlerResponse;\n\n // Route: POST /{shlId} → manifest\n if (segments.length === 1 && req.method === \"POST\") {\n response = await handleManifest(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId} → direct access (flag U)\n else if (segments.length === 1 && req.method === \"GET\") {\n response = await handleDirectAccess(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId}/content → serve encrypted content\n else if (segments.length === 2 && segments[1] === \"content\" && req.method === \"GET\") {\n response = await handleContent(segments[0]!, storage);\n }\n // Route: GET /{shlId}/attachment/{index} → serve encrypted attachment\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method === \"GET\") {\n response = await handleAttachment(segments[0]!, segments[2]!, storage);\n }\n // Method not allowed for known paths\n else if (segments.length === 2 && segments[1] === \"content\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for content requests.\" });\n }\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for attachment requests.\" });\n }\n else {\n response = jsonResponse(404, { error: \"Not found\" });\n }\n\n // Inject CORS headers into every response\n response.headers = { ...response.headers, ...corsHeaders };\n return response;\n };\n}\n\n/** Resolve CORS headers from config. Returns empty object if disabled. */\nfunction resolveCorsHeaders(cors: SHLHandlerConfig[\"cors\"]): Record<string, string> {\n if (cors === false) return {};\n const c: CorsConfig = cors ?? {};\n return {\n \"access-control-allow-origin\": c.origin ?? \"*\",\n \"access-control-allow-methods\": c.methods ?? \"GET, POST, OPTIONS\",\n \"access-control-allow-headers\": c.headers ?? \"Content-Type, Authorization\",\n };\n}\n\nasync function handleManifest(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read manifest to verify the SHL exists\n const manifestRaw = await storage.read(`${shlId}/manifest.json`);\n if (manifestRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Atomically check access control + increment counter\n let updatedMetadata: SHLMetadata | null = null;\n let accessDeniedReason: \"expired\" | \"exhausted\" | \"passcode\" | null = null;\n const reqBody = (req.body && typeof req.body === \"object\" ? req.body : {}) as Record<string, unknown>;\n const providedPasscode = typeof reqBody[\"passcode\"] === \"string\" ? reqBody[\"passcode\"] : undefined;\n\n updatedMetadata = await storage.updateMetadata(shlId, (metadata) => {\n // Check expiration\n if (metadata.expiresAt) {\n const expiresAt = new Date(metadata.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n // Check access count\n const currentCount = metadata.accessCount ?? 0;\n if (metadata.maxAccesses !== undefined && currentCount >= metadata.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n // Check passcode (timing-safe comparison with SHA-256 hash)\n if (metadata.passcode) {\n if (!providedPasscode) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n const providedHash = createHash(\"sha256\").update(providedPasscode).digest(\"hex\");\n const storedHash = metadata.passcode;\n const a = Buffer.from(providedHash);\n const b = Buffer.from(storedHash);\n // Constant-time comparison: compare with self if lengths differ to avoid timing leak\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n }\n\n // Access granted — increment count\n return {\n ...metadata,\n accessCount: currentCount + 1,\n };\n });\n\n // Handle access control failures\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (accessDeniedReason === \"passcode\") {\n return jsonResponse(401, { error: \"Invalid passcode\" });\n }\n\n // If updateMetadata returned null but no denied reason, metadata file is missing\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const accessEvent = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n if (onAccess) {\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(accessEvent)).catch(() => {});\n }\n // If storage is auditable, also fire the storage-level audit hook\n if (isAuditableStorage(storage)) {\n Promise.resolve(storage.onAccess(shlId, accessEvent)).catch(() => {});\n }\n\n // Return manifest\n const manifestStr = typeof manifestRaw === \"string\"\n ? manifestRaw\n : new TextDecoder().decode(manifestRaw);\n const manifest = JSON.parse(manifestStr) as Manifest;\n\n return jsonResponse(200, manifest);\n}\n\nasync function handleDirectAccess(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read metadata to check if this is a direct-mode SHL\n const metadataRaw = await storage.read(`${shlId}/metadata.json`);\n if (metadataRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n const metadataStr = typeof metadataRaw === \"string\" ? metadataRaw : new TextDecoder().decode(metadataRaw);\n const metadata = JSON.parse(metadataStr) as SHLMetadata;\n\n // Only direct-mode SHLs support GET retrieval\n if (metadata.mode !== \"direct\") {\n return jsonResponse(405, { error: \"Method not allowed. Use POST for manifest requests.\" });\n }\n\n // Access control: expiration, access count (atomic)\n let accessDeniedReason: \"expired\" | \"exhausted\" | null = null;\n const updatedMetadata = await storage.updateMetadata(shlId, (current) => {\n if (current.expiresAt) {\n const expiresAt = new Date(current.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n const currentCount = current.accessCount ?? 0;\n if (current.maxAccesses !== undefined && currentCount >= current.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n return {\n ...current,\n accessCount: currentCount + 1,\n };\n });\n\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const directAccessEvent = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n if (onAccess) {\n Promise.resolve(onAccess(directAccessEvent)).catch(() => {});\n }\n // If storage is auditable, also fire the storage-level audit hook\n if (isAuditableStorage(storage)) {\n Promise.resolve(storage.onAccess(shlId, directAccessEvent)).catch(() => {});\n }\n\n // Serve the encrypted content directly\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\" ? content : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleContent(\n shlId: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleAttachment(\n shlId: string,\n index: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n if (!/^\\d+$/.test(index)) {\n return jsonResponse(400, { error: \"Invalid attachment index\" });\n }\n const content = await storage.read(`${shlId}/attachment-${index}.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Attachment not found\" });\n }\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nfunction jsonResponse(status: number, body: unknown): HandlerResponse {\n return {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n body: JSON.stringify(body),\n };\n}\n"]}
@@ -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,37 @@ 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
+ for (const entry of entries) {
1562
+ const meta = entry.resource.meta;
1563
+ if (meta?.profile) {
1564
+ delete meta.profile;
1565
+ if (Object.keys(meta).length === 0) {
1566
+ delete entry.resource.meta;
1567
+ }
1568
+ }
1569
+ }
1570
+ return {
1571
+ resourceType: "Bundle",
1572
+ id: bundleId,
1573
+ identifier: {
1574
+ system: "urn:ietf:rfc:3986",
1575
+ value: `urn:uuid:${bundleId}`
1576
+ },
1577
+ type: "collection",
1578
+ timestamp,
1579
+ entry: entries
1580
+ };
1581
+ }
1479
1582
  buildComposition(id, patientRef, date, profile, medRefs, allergyRefs, condRefs, immRefs, resultRefs) {
1480
1583
  const composition = {
1481
1584
  resourceType: "Composition",
@@ -1604,6 +1707,7 @@ function generateUuid() {
1604
1707
  var shl_exports = {};
1605
1708
  __export(shl_exports, {
1606
1709
  AzureStorage: () => AzureStorage,
1710
+ EXPIRATION_PRESETS: () => EXPIRATION_PRESETS,
1607
1711
  FhirflyStorage: () => FhirflyStorage,
1608
1712
  GCSStorage: () => GCSStorage,
1609
1713
  LocalStorage: () => LocalStorage,
@@ -1615,6 +1719,18 @@ __export(shl_exports, {
1615
1719
  getEntryContent: () => getEntryContent,
1616
1720
  revoke: () => revoke
1617
1721
  });
1722
+
1723
+ // src/shl/types.ts
1724
+ var EXPIRATION_PRESETS = {
1725
+ "point-of-care": 15 * 60 * 1e3,
1726
+ // 15 minutes
1727
+ "appointment": 24 * 60 * 60 * 1e3,
1728
+ // 24 hours
1729
+ "travel": 90 * 24 * 60 * 60 * 1e3,
1730
+ // 90 days
1731
+ "permanent": 0
1732
+ // no expiration
1733
+ };
1618
1734
  function base64url(data) {
1619
1735
  return data.toString("base64url");
1620
1736
  }
@@ -1702,13 +1818,43 @@ async function generateQRCode(url) {
1702
1818
  });
1703
1819
  }
1704
1820
  async function create(options) {
1705
- const { bundle, passcode, expiresAt, maxAccesses, label, storage, debug } = options;
1821
+ const { bundle, passcode, maxAccesses, label, storage, debug } = options;
1822
+ let expiresAt;
1823
+ if (typeof options.expiresAt === "string") {
1824
+ const preset = options.expiresAt;
1825
+ const durationMs = EXPIRATION_PRESETS[preset];
1826
+ if (durationMs === void 0) {
1827
+ throw new ValidationError(`Unknown expiration preset: "${preset}"`);
1828
+ }
1829
+ expiresAt = durationMs > 0 ? new Date(Date.now() + durationMs) : void 0;
1830
+ } else {
1831
+ expiresAt = options.expiresAt;
1832
+ }
1706
1833
  if (!bundle || typeof bundle !== "object") {
1707
1834
  throw new ValidationError("bundle is required and must be an object");
1708
1835
  }
1709
1836
  if (!storage?.baseUrl) {
1710
1837
  throw new ValidationError("storage with baseUrl is required");
1711
1838
  }
1839
+ let mode = options.mode ?? "manifest";
1840
+ if (options.compliance === "pshd") {
1841
+ mode = "direct";
1842
+ if (passcode) {
1843
+ throw new ValidationError(
1844
+ "PSHD compliance forbids passcode (flag U is incompatible with flag P)"
1845
+ );
1846
+ }
1847
+ if (!expiresAt && options.expiresAt !== "permanent") {
1848
+ throw new ValidationError(
1849
+ "PSHD compliance requires expiresAt (short-lived links for point-of-care)"
1850
+ );
1851
+ }
1852
+ }
1853
+ if (mode === "direct" && passcode) {
1854
+ throw new ValidationError(
1855
+ "Direct mode (flag U) is incompatible with passcode (flag P)"
1856
+ );
1857
+ }
1712
1858
  const key = generateKey();
1713
1859
  const shlId = generateShlId();
1714
1860
  let jwe;
@@ -1768,31 +1914,34 @@ async function create(options) {
1768
1914
  );
1769
1915
  }
1770
1916
  }
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
- );
1917
+ if (mode === "manifest") {
1918
+ const manifest = {
1919
+ files: [
1920
+ {
1921
+ contentType: "application/fhir+json;fhirVersion=4.0.1",
1922
+ location: `${baseUrl}/${shlId}/content`
1923
+ },
1924
+ ...attachments.map((att, i) => ({
1925
+ contentType: att.contentType,
1926
+ location: `${baseUrl}/${shlId}/attachment/${i}`
1927
+ }))
1928
+ ],
1929
+ status: "finalized",
1930
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1931
+ };
1932
+ try {
1933
+ await storage.store(`${shlId}/manifest.json`, JSON.stringify(manifest));
1934
+ } catch (err) {
1935
+ throw new StorageError(
1936
+ `Failed to store manifest: ${err instanceof Error ? err.message : String(err)}`,
1937
+ "store"
1938
+ );
1939
+ }
1792
1940
  }
1793
1941
  const metadata = {
1794
1942
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1795
1943
  };
1944
+ if (mode === "direct") metadata.mode = "direct";
1796
1945
  if (passcode) {
1797
1946
  metadata.passcode = createHash("sha256").update(passcode).digest("hex");
1798
1947
  }
@@ -1806,7 +1955,7 @@ async function create(options) {
1806
1955
  "store"
1807
1956
  );
1808
1957
  }
1809
- const flags = buildFlags(passcode);
1958
+ const flags = buildFlags(mode, passcode);
1810
1959
  const shlPayload = {
1811
1960
  url: `${baseUrl}/${shlId}`,
1812
1961
  key: base64url(key),
@@ -1833,8 +1982,8 @@ async function create(options) {
1833
1982
  if (debug) result.debugBundlePath = `${shlId}/bundle.json`;
1834
1983
  return result;
1835
1984
  }
1836
- function buildFlags(passcode) {
1837
- const flags = ["L"];
1985
+ function buildFlags(mode, passcode) {
1986
+ const flags = [mode === "direct" ? "U" : "L"];
1838
1987
  if (passcode) flags.push("P");
1839
1988
  return flags.sort().join("");
1840
1989
  }
@@ -1941,5 +2090,5 @@ async function revoke(shlId, storage) {
1941
2090
  }
1942
2091
 
1943
2092
  export { ips_exports, shl_exports };
1944
- //# sourceMappingURL=chunk-YBDRWUQU.js.map
1945
- //# sourceMappingURL=chunk-YBDRWUQU.js.map
2093
+ //# sourceMappingURL=chunk-YUMCDN7I.js.map
2094
+ //# sourceMappingURL=chunk-YUMCDN7I.js.map