@fhirfly-io/shl 0.4.0 → 0.6.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 (42) hide show
  1. package/README.md +53 -0
  2. package/dist/{chunk-SK77O3SG.cjs → chunk-CN44QKWJ.cjs} +37 -4
  3. package/dist/chunk-CN44QKWJ.cjs.map +1 -0
  4. package/dist/{chunk-JOZ6XZPO.cjs → chunk-H37YQWF2.cjs} +33 -21
  5. package/dist/chunk-H37YQWF2.cjs.map +1 -0
  6. package/dist/{chunk-KGEFZQ6W.js → chunk-IYRQRY4A.js} +33 -22
  7. package/dist/chunk-IYRQRY4A.js.map +1 -0
  8. package/dist/{chunk-UU434UFQ.js → chunk-YUMCDN7I.js} +37 -4
  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 +2 -2
  13. package/dist/express.d.cts +2 -2
  14. package/dist/express.d.ts +2 -2
  15. package/dist/express.js +1 -1
  16. package/dist/fastify.cjs +2 -2
  17. package/dist/fastify.d.cts +2 -2
  18. package/dist/fastify.d.ts +2 -2
  19. package/dist/fastify.js +1 -1
  20. package/dist/index.cjs +3 -3
  21. package/dist/index.d.cts +5 -3
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.js +1 -1
  24. package/dist/lambda.cjs +2 -2
  25. package/dist/lambda.d.cts +2 -2
  26. package/dist/lambda.d.ts +2 -2
  27. package/dist/lambda.js +1 -1
  28. package/dist/server.cjs +6 -2
  29. package/dist/server.d.cts +4 -4
  30. package/dist/server.d.ts +4 -4
  31. package/dist/server.js +1 -1
  32. package/dist/{storage-Cx7uXUl8.d.ts → storage-CvsOM1Eu.d.ts} +1 -1
  33. package/dist/{storage-BbzK-kFf.d.cts → storage-DggeMhOI.d.cts} +1 -1
  34. package/dist/{types-6Vw5fiat.d.ts → types--SjcaaWT.d.ts} +20 -2
  35. package/dist/{types-Cdi4IkC9.d.cts → types-BcfxBDTA.d.cts} +20 -2
  36. package/dist/{types-BLLJeWe_.d.ts → types-CmeXnyth.d.cts} +17 -3
  37. package/dist/{types-BLLJeWe_.d.cts → types-CmeXnyth.d.ts} +17 -3
  38. package/package.json +3 -3
  39. package/dist/chunk-JOZ6XZPO.cjs.map +0 -1
  40. package/dist/chunk-KGEFZQ6W.js.map +0 -1
  41. package/dist/chunk-SK77O3SG.cjs.map +0 -1
  42. package/dist/chunk-UU434UFQ.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkSK77O3SG_cjs = require('./chunk-SK77O3SG.cjs');
3
+ var chunkCN44QKWJ_cjs = require('./chunk-CN44QKWJ.cjs');
4
4
  var chunkUDS6UJAL_cjs = require('./chunk-UDS6UJAL.cjs');
5
5
  require('./chunk-Q7SFCCGT.cjs');
6
6
 
@@ -8,11 +8,11 @@ require('./chunk-Q7SFCCGT.cjs');
8
8
 
9
9
  Object.defineProperty(exports, "IPS", {
10
10
  enumerable: true,
11
- get: function () { return chunkSK77O3SG_cjs.ips_exports; }
11
+ get: function () { return chunkCN44QKWJ_cjs.ips_exports; }
12
12
  });
13
13
  Object.defineProperty(exports, "SHL", {
14
14
  enumerable: true,
15
- get: function () { return chunkSK77O3SG_cjs.shl_exports; }
15
+ get: function () { return chunkCN44QKWJ_cjs.shl_exports; }
16
16
  });
17
17
  Object.defineProperty(exports, "EncryptionError", {
18
18
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, d as Manifest, e as SHLAttachment, a as SHLMetadata } from './types-BLLJeWe_.cjs';
2
- import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-BbzK-kFf.cjs';
1
+ import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, E as EXPIRATION_PRESETS, d as ExpirationPreset, e as Manifest, f as SHLAttachment, a as SHLMetadata } from './types-CmeXnyth.cjs';
2
+ import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-DggeMhOI.cjs';
3
3
 
4
4
  /**
5
5
  * Shared FHIR R4 datatypes used across IPS bundle construction.
@@ -848,6 +848,8 @@ declare function revoke(shlId: string, storage: SHLStorage): Promise<void>;
848
848
  declare const index_AzureStorage: typeof AzureStorage;
849
849
  declare const index_AzureStorageConfig: typeof AzureStorageConfig;
850
850
  type index_DecodedSHL = DecodedSHL;
851
+ declare const index_EXPIRATION_PRESETS: typeof EXPIRATION_PRESETS;
852
+ declare const index_ExpirationPreset: typeof ExpirationPreset;
851
853
  declare const index_FhirflyStorage: typeof FhirflyStorage;
852
854
  declare const index_FhirflyStorageConfig: typeof FhirflyStorageConfig;
853
855
  declare const index_GCSStorage: typeof GCSStorage;
@@ -870,7 +872,7 @@ declare const index_decryptContent: typeof decryptContent;
870
872
  declare const index_getEntryContent: typeof getEntryContent;
871
873
  declare const index_revoke: typeof revoke;
872
874
  declare namespace index {
873
- export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
875
+ export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_EXPIRATION_PRESETS as EXPIRATION_PRESETS, index_ExpirationPreset as ExpirationPreset, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
874
876
  }
875
877
 
876
878
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, d as Manifest, e as SHLAttachment, a as SHLMetadata } from './types-BLLJeWe_.js';
2
- import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-Cx7uXUl8.js';
1
+ import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, E as EXPIRATION_PRESETS, d as ExpirationPreset, e as Manifest, f as SHLAttachment, a as SHLMetadata } from './types-CmeXnyth.js';
2
+ import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-CvsOM1Eu.js';
3
3
 
4
4
  /**
5
5
  * Shared FHIR R4 datatypes used across IPS bundle construction.
@@ -848,6 +848,8 @@ declare function revoke(shlId: string, storage: SHLStorage): Promise<void>;
848
848
  declare const index_AzureStorage: typeof AzureStorage;
849
849
  declare const index_AzureStorageConfig: typeof AzureStorageConfig;
850
850
  type index_DecodedSHL = DecodedSHL;
851
+ declare const index_EXPIRATION_PRESETS: typeof EXPIRATION_PRESETS;
852
+ declare const index_ExpirationPreset: typeof ExpirationPreset;
851
853
  declare const index_FhirflyStorage: typeof FhirflyStorage;
852
854
  declare const index_FhirflyStorageConfig: typeof FhirflyStorageConfig;
853
855
  declare const index_GCSStorage: typeof GCSStorage;
@@ -870,7 +872,7 @@ declare const index_decryptContent: typeof decryptContent;
870
872
  declare const index_getEntryContent: typeof getEntryContent;
871
873
  declare const index_revoke: typeof revoke;
872
874
  declare namespace index {
873
- export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
875
+ export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_EXPIRATION_PRESETS as EXPIRATION_PRESETS, index_ExpirationPreset as ExpirationPreset, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
874
876
  }
875
877
 
876
878
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { ips_exports as IPS, shl_exports as SHL } from './chunk-UU434UFQ.js';
1
+ export { ips_exports as IPS, shl_exports as SHL } from './chunk-YUMCDN7I.js';
2
2
  export { EncryptionError, ShlError, StorageError, ValidationError } from './chunk-VKB3ESIV.js';
3
3
  import './chunk-PZ5AY32C.js';
4
4
  //# sourceMappingURL=index.js.map
package/dist/lambda.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunkJOZ6XZPO_cjs = require('./chunk-JOZ6XZPO.cjs');
3
+ var chunkH37YQWF2_cjs = require('./chunk-H37YQWF2.cjs');
4
4
  require('./chunk-Q7SFCCGT.cjs');
5
5
 
6
6
  // src/adapters/lambda.ts
7
7
  function lambdaHandler(config) {
8
- const handle = chunkJOZ6XZPO_cjs.createHandler(config);
8
+ const handle = chunkH37YQWF2_cjs.createHandler(config);
9
9
  const prefix = config.pathPrefix?.replace(/\/+$/, "") ?? "";
10
10
  return async (event) => {
11
11
  let path = event.requestContext.http.path;
package/dist/lambda.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SHLHandlerConfig } from './types-Cdi4IkC9.cjs';
2
- import './types-BLLJeWe_.cjs';
1
+ import { S as SHLHandlerConfig } from './types-BcfxBDTA.cjs';
2
+ import './types-CmeXnyth.cjs';
3
3
 
4
4
  interface APIGatewayProxyEventV2 {
5
5
  requestContext: {
package/dist/lambda.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SHLHandlerConfig } from './types-6Vw5fiat.js';
2
- import './types-BLLJeWe_.js';
1
+ import { S as SHLHandlerConfig } from './types--SjcaaWT.js';
2
+ import './types-CmeXnyth.js';
3
3
 
4
4
  interface APIGatewayProxyEventV2 {
5
5
  requestContext: {
package/dist/lambda.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createHandler } from './chunk-KGEFZQ6W.js';
1
+ import { createHandler } from './chunk-IYRQRY4A.js';
2
2
  import './chunk-PZ5AY32C.js';
3
3
 
4
4
  // src/adapters/lambda.ts
package/dist/server.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var chunkLXJB46WB_cjs = require('./chunk-LXJB46WB.cjs');
4
4
  require('./chunk-UDS6UJAL.cjs');
5
- var chunkJOZ6XZPO_cjs = require('./chunk-JOZ6XZPO.cjs');
5
+ var chunkH37YQWF2_cjs = require('./chunk-H37YQWF2.cjs');
6
6
  require('./chunk-Q7SFCCGT.cjs');
7
7
 
8
8
 
@@ -25,7 +25,11 @@ Object.defineProperty(exports, "ServerS3Storage", {
25
25
  });
26
26
  Object.defineProperty(exports, "createHandler", {
27
27
  enumerable: true,
28
- get: function () { return chunkJOZ6XZPO_cjs.createHandler; }
28
+ get: function () { return chunkH37YQWF2_cjs.createHandler; }
29
+ });
30
+ Object.defineProperty(exports, "isAuditableStorage", {
31
+ enumerable: true,
32
+ get: function () { return chunkH37YQWF2_cjs.isAuditableStorage; }
29
33
  });
30
34
  //# sourceMappingURL=server.cjs.map
31
35
  //# sourceMappingURL=server.cjs.map
package/dist/server.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-Cdi4IkC9.cjs';
2
- export { A as AccessEvent, C as CorsConfig } from './types-Cdi4IkC9.cjs';
3
- import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-BbzK-kFf.cjs';
4
- import { a as SHLMetadata } from './types-BLLJeWe_.cjs';
1
+ import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-BcfxBDTA.cjs';
2
+ export { A as AccessEvent, c as AuditableStorage, C as CorsConfig, i as isAuditableStorage } from './types-BcfxBDTA.cjs';
3
+ import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-DggeMhOI.cjs';
4
+ import { a as SHLMetadata } from './types-CmeXnyth.cjs';
5
5
 
6
6
  /**
7
7
  * Create a framework-agnostic SHL request handler.
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-6Vw5fiat.js';
2
- export { A as AccessEvent, C as CorsConfig } from './types-6Vw5fiat.js';
3
- import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-Cx7uXUl8.js';
4
- import { a as SHLMetadata } from './types-BLLJeWe_.js';
1
+ import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types--SjcaaWT.js';
2
+ export { A as AccessEvent, c as AuditableStorage, C as CorsConfig, i as isAuditableStorage } from './types--SjcaaWT.js';
3
+ import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-CvsOM1Eu.js';
4
+ import { a as SHLMetadata } from './types-CmeXnyth.js';
5
5
 
6
6
  /**
7
7
  * Create a framework-agnostic SHL request handler.
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ServerAzureStorage, ServerGCSStorage, ServerLocalStorage, ServerS3Storage } from './chunk-YV7ZD6OM.js';
2
2
  import './chunk-VKB3ESIV.js';
3
- export { createHandler } from './chunk-KGEFZQ6W.js';
3
+ export { createHandler, isAuditableStorage } from './chunk-IYRQRY4A.js';
4
4
  import './chunk-PZ5AY32C.js';
5
5
  //# sourceMappingURL=server.js.map
6
6
  //# sourceMappingURL=server.js.map
@@ -1,4 +1,4 @@
1
- import { S as SHLStorage } from './types-BLLJeWe_.js';
1
+ import { S as SHLStorage } from './types-CmeXnyth.js';
2
2
 
3
3
  /**
4
4
  * Configuration for local filesystem SHL storage.
@@ -1,4 +1,4 @@
1
- import { S as SHLStorage } from './types-BLLJeWe_.cjs';
1
+ import { S as SHLStorage } from './types-CmeXnyth.cjs';
2
2
 
3
3
  /**
4
4
  * Configuration for local filesystem SHL storage.
@@ -1,4 +1,4 @@
1
- import { S as SHLStorage, a as SHLMetadata } from './types-BLLJeWe_.js';
1
+ import { S as SHLStorage, a as SHLMetadata } from './types-CmeXnyth.js';
2
2
 
3
3
  /**
4
4
  * Framework-agnostic incoming request.
@@ -48,6 +48,24 @@ interface SHLServerStorage extends SHLStorage {
48
48
  */
49
49
  updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
50
50
  }
51
+ /**
52
+ * Extended storage interface that includes audit logging.
53
+ *
54
+ * When a storage backend implements `AuditableStorage`, the server handler
55
+ * will automatically call `onAccess()` after each successful retrieval.
56
+ * This is in addition to the `onAccess` callback on `SHLHandlerConfig`.
57
+ *
58
+ * Existing `SHLServerStorage` implementations continue to work unchanged —
59
+ * audit logging is opt-in.
60
+ */
61
+ interface AuditableStorage extends SHLServerStorage {
62
+ /** Called after each successful SHL retrieval. */
63
+ onAccess(shlId: string, event: AccessEvent): Promise<void>;
64
+ }
65
+ /**
66
+ * Type guard: checks if a storage backend implements AuditableStorage.
67
+ */
68
+ declare function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage;
51
69
  /**
52
70
  * CORS configuration for the SHL server handler.
53
71
  *
@@ -96,4 +114,4 @@ interface AccessEvent {
96
114
  mode?: "manifest" | "direct";
97
115
  }
98
116
 
99
- export type { AccessEvent as A, CorsConfig as C, HandlerRequest as H, SHLHandlerConfig as S, HandlerResponse as a, SHLServerStorage as b };
117
+ export { type AccessEvent as A, type CorsConfig as C, type HandlerRequest as H, type SHLHandlerConfig as S, type HandlerResponse as a, type SHLServerStorage as b, type AuditableStorage as c, isAuditableStorage as i };
@@ -1,4 +1,4 @@
1
- import { S as SHLStorage, a as SHLMetadata } from './types-BLLJeWe_.cjs';
1
+ import { S as SHLStorage, a as SHLMetadata } from './types-CmeXnyth.cjs';
2
2
 
3
3
  /**
4
4
  * Framework-agnostic incoming request.
@@ -48,6 +48,24 @@ interface SHLServerStorage extends SHLStorage {
48
48
  */
49
49
  updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
50
50
  }
51
+ /**
52
+ * Extended storage interface that includes audit logging.
53
+ *
54
+ * When a storage backend implements `AuditableStorage`, the server handler
55
+ * will automatically call `onAccess()` after each successful retrieval.
56
+ * This is in addition to the `onAccess` callback on `SHLHandlerConfig`.
57
+ *
58
+ * Existing `SHLServerStorage` implementations continue to work unchanged —
59
+ * audit logging is opt-in.
60
+ */
61
+ interface AuditableStorage extends SHLServerStorage {
62
+ /** Called after each successful SHL retrieval. */
63
+ onAccess(shlId: string, event: AccessEvent): Promise<void>;
64
+ }
65
+ /**
66
+ * Type guard: checks if a storage backend implements AuditableStorage.
67
+ */
68
+ declare function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage;
51
69
  /**
52
70
  * CORS configuration for the SHL server handler.
53
71
  *
@@ -96,4 +114,4 @@ interface AccessEvent {
96
114
  mode?: "manifest" | "direct";
97
115
  }
98
116
 
99
- export type { AccessEvent as A, CorsConfig as C, HandlerRequest as H, SHLHandlerConfig as S, HandlerResponse as a, SHLServerStorage as b };
117
+ export { type AccessEvent as A, type CorsConfig as C, type HandlerRequest as H, type SHLHandlerConfig as S, type HandlerResponse as a, type SHLServerStorage as b, type AuditableStorage as c, isAuditableStorage as i };
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Named expiration presets for common SHL use cases.
3
+ *
4
+ * - `"point-of-care"` — 15 minutes (in-person sharing at a clinic or pharmacy)
5
+ * - `"appointment"` — 24 hours (share before a scheduled visit)
6
+ * - `"travel"` — 90 days (carry health records while traveling)
7
+ * - `"permanent"` — no expiration (long-lived link)
8
+ */
9
+ type ExpirationPreset = "point-of-care" | "appointment" | "travel" | "permanent";
10
+ /** Duration in milliseconds for each expiration preset. 0 = no expiration. */
11
+ declare const EXPIRATION_PRESETS: Record<ExpirationPreset, number>;
1
12
  /**
2
13
  * Options for creating a SMART Health Link.
3
14
  */
@@ -8,8 +19,11 @@ interface SHLOptions {
8
19
  attachments?: SHLAttachment[];
9
20
  /** Optional passcode to protect the link */
10
21
  passcode?: string;
11
- /** Expiration date for the link */
12
- expiresAt?: Date;
22
+ /**
23
+ * Expiration for the link. Accepts a `Date` for an exact time,
24
+ * or an `ExpirationPreset` string for a named duration from now.
25
+ */
26
+ expiresAt?: Date | ExpirationPreset;
13
27
  /** Maximum number of times the link can be accessed */
14
28
  maxAccesses?: number;
15
29
  /** Label for the SHL (shown in viewer apps, max 80 chars) */
@@ -114,4 +128,4 @@ interface SHLStorage {
114
128
  delete(prefix: string): Promise<void>;
115
129
  }
116
130
 
117
- export type { ManifestEntry as M, SHLStorage as S, SHLMetadata as a, SHLOptions as b, SHLResult as c, Manifest as d, SHLAttachment as e };
131
+ export { EXPIRATION_PRESETS as E, type ManifestEntry as M, type SHLStorage as S, type SHLMetadata as a, type SHLOptions as b, type SHLResult as c, type ExpirationPreset as d, type Manifest as e, type SHLAttachment as f };
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Named expiration presets for common SHL use cases.
3
+ *
4
+ * - `"point-of-care"` — 15 minutes (in-person sharing at a clinic or pharmacy)
5
+ * - `"appointment"` — 24 hours (share before a scheduled visit)
6
+ * - `"travel"` — 90 days (carry health records while traveling)
7
+ * - `"permanent"` — no expiration (long-lived link)
8
+ */
9
+ type ExpirationPreset = "point-of-care" | "appointment" | "travel" | "permanent";
10
+ /** Duration in milliseconds for each expiration preset. 0 = no expiration. */
11
+ declare const EXPIRATION_PRESETS: Record<ExpirationPreset, number>;
1
12
  /**
2
13
  * Options for creating a SMART Health Link.
3
14
  */
@@ -8,8 +19,11 @@ interface SHLOptions {
8
19
  attachments?: SHLAttachment[];
9
20
  /** Optional passcode to protect the link */
10
21
  passcode?: string;
11
- /** Expiration date for the link */
12
- expiresAt?: Date;
22
+ /**
23
+ * Expiration for the link. Accepts a `Date` for an exact time,
24
+ * or an `ExpirationPreset` string for a named duration from now.
25
+ */
26
+ expiresAt?: Date | ExpirationPreset;
13
27
  /** Maximum number of times the link can be accessed */
14
28
  maxAccesses?: number;
15
29
  /** Label for the SHL (shown in viewer apps, max 80 chars) */
@@ -114,4 +128,4 @@ interface SHLStorage {
114
128
  delete(prefix: string): Promise<void>;
115
129
  }
116
130
 
117
- export type { ManifestEntry as M, SHLStorage as S, SHLMetadata as a, SHLOptions as b, SHLResult as c, Manifest as d, SHLAttachment as e };
131
+ export { EXPIRATION_PRESETS as E, type ManifestEntry as M, type SHLStorage as S, type SHLMetadata as a, type SHLOptions as b, type SHLResult as c, type ExpirationPreset as d, type Manifest as e, type SHLAttachment as f };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fhirfly-io/shl",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Official FHIRfly SDK for SMART Health Links - IPS bundle creation and SHL sharing",
5
5
  "author": "FHIRfly.io LLC <admin@fhirfly.io>",
6
6
  "license": "MIT",
@@ -8,10 +8,10 @@
8
8
  "homepage": "https://fhirfly.io",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://gitlab.com/fhirfly-io/fhirfly-shl.git"
11
+ "url": "git+https://github.com/FHIRfly-io/fhirfly-shl.git"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://gitlab.com/fhirfly-io/fhirfly-shl/-/issues"
14
+ "url": "https://github.com/FHIRfly-io/fhirfly-shl/issues"
15
15
  },
16
16
  "keywords": [
17
17
  "fhirfly",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/handler.ts"],"names":["createHash","timingSafeEqual"],"mappings":"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","file":"chunk-JOZ6XZPO.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 {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} 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 if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(event)).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 if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n Promise.resolve(onAccess(event)).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 +0,0 @@
1
- {"version":3,"sources":["../src/server/handler.ts"],"names":[],"mappings":"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","file":"chunk-KGEFZQ6W.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 {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} 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 if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(event)).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 if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n Promise.resolve(onAccess(event)).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"]}