fhirstarterjs 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -1,24 +1,44 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <https://unlicense.org>
1
+ # Kopimi License
2
+
3
+ This software is released under the Kopimi License.
4
+
5
+ ## Permission
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ this software and associated documentation files (the "Software"), to copy,
9
+ modify, merge, publish, distribute, sublicense, sell, use, study,
10
+ reverse-engineer, decompile, and otherwise deal in the Software without
11
+ restriction, including without limitation for commercial or non-commercial
12
+ purposes.
13
+
14
+ Commercial use, sale, resale, or distribution for profit must include clear
15
+ attribution to the original author(s) in the distributed copy and in any
16
+ customary public-facing product notices, documentation, credits, or legal
17
+ notices associated with that use.
18
+
19
+ ## Kopimi Propagation
20
+
21
+ The Software is Kopimi. Copies, substantial portions, and derivative works must
22
+ preserve the freedom to copy, share, modify, remix, and redistribute the
23
+ Software. No copy, substantial portion, or derivative work may be distributed
24
+ under terms that prohibit those freedoms.
25
+
26
+ License notices, author notices, and Kopimi notices included with the Software
27
+ may not be removed from redistributed copies, substantial portions, or
28
+ derivative works.
29
+
30
+ Attribution to the original author(s) is appreciated for non-commercial use and
31
+ required for commercial use as described above.
32
+
33
+ ## Disclaimer
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
37
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
38
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
39
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
40
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
+
42
+ This license is itself Kopimi. Copy it, use it, and remix it.
43
+
44
+ For more information, please refer to <https://kopimi.com>
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🔥 fhirstarterjs
1
+ # 🔥 fhirStarter
2
2
 
3
3
  SMART on FHIR Backend Services auth lifecycle for any FHIR client.
4
4
 
@@ -10,13 +10,13 @@ npm install fhirstarterjs
10
10
 
11
11
  ## Usage
12
12
 
13
- This example uses the official `fhirclient` package as the FHIR client; `fhirstarterjs` only manages auth.
13
+ This example uses the official `fhirclient` package as the FHIR client; `fhirStarter` only manages auth.
14
14
 
15
15
  ```ts
16
16
  import FHIR from "fhirclient"
17
- import FHIRStarter from "fhirstarterjs"
17
+ import fhirStarter from "fhirstarterjs"
18
18
 
19
- const auth = new FHIRStarter({
19
+ const auth = new fhirStarter({
20
20
  clientId: "your-client-id",
21
21
  privateKey: "./privatekey.pem",
22
22
  tokenEndpointUrl: "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
@@ -35,7 +35,7 @@ const bundle = await client.request("Patient?family=Smith")
35
35
 
36
36
  `auth.start()` fetches the first token and starts the proactive refresh loop. `auth.tokenResponse()` returns a live getter-backed object — `fhirclient` reads `access_token` dynamically per request, so it always picks up the latest token.
37
37
 
38
- `fhirstarterjs` does not fetch FHIR resources and does not bundle a FHIR client. It manages the auth lifecycle; the FHIR client does the rest.
38
+ `fhirStarter` does not fetch FHIR resources and does not bundle a FHIR client. It manages the auth lifecycle; the FHIR client does the rest.
39
39
 
40
40
  `privateKey` can be PEM text, a `Buffer` from `readFileSync`, or a path to a PKCS#8 PEM file.
41
41
 
@@ -68,7 +68,7 @@ const res = await fetch(url, {
68
68
 
69
69
  ## API
70
70
 
71
- `new FHIRStarter(config)`
71
+ `new fhirStarter(config)`
72
72
 
73
73
  | Member | Returns | Description |
74
74
  |---|---|---|
@@ -90,9 +90,9 @@ Some SMART Backend Services registrations require a public JWKS URL when using `
90
90
 
91
91
  ```ts
92
92
  import { writeFileSync } from "node:fs"
93
- import FHIRStarter from "fhirstarterjs"
93
+ import fhirStarter from "fhirstarterjs"
94
94
 
95
- const auth = new FHIRStarter({
95
+ const auth = new fhirStarter({
96
96
  clientId: "your-client-id",
97
97
  privateKey: "./privatekey.pem",
98
98
  tokenEndpointUrl: "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
@@ -7,7 +7,7 @@ import { readFileSync } from "node:fs";
7
7
  * Manages client-credentials token acquisition and proactive refresh
8
8
  * using a private RSA key and the JWT Bearer client-assertion flow (RFC 7523).
9
9
  */
10
- export default class FHIRStarter {
10
+ export default class fhirStarter {
11
11
  config;
12
12
  privateKeyText;
13
13
  refreshCallbacks = new Set();
@@ -30,7 +30,7 @@ export default class FHIRStarter {
30
30
  throw new Error("AuthConfig: privateKey is required");
31
31
  if (!config.tokenEndpointUrl)
32
32
  throw new Error("AuthConfig: tokenEndpointUrl is required");
33
- const scopes = FHIRStarter.normalizeScopes(config.scopes);
33
+ const scopes = fhirStarter.normalizeScopes(config.scopes);
34
34
  if (scopes.length === 0)
35
35
  throw new Error("AuthConfig: at least one scope is required");
36
36
  try {
@@ -40,7 +40,7 @@ export default class FHIRStarter {
40
40
  throw new Error(`AuthConfig: tokenEndpointUrl is not a valid URL: ${config.tokenEndpointUrl}`);
41
41
  }
42
42
  this.config = config;
43
- this.privateKeyText = FHIRStarter.resolvePrivateKey(config.privateKey);
43
+ this.privateKeyText = fhirStarter.resolvePrivateKey(config.privateKey);
44
44
  }
45
45
  // --- Sync getters ---
46
46
  /** The current access token, or `null` if no valid token is cached. */
@@ -108,6 +108,9 @@ export default class FHIRStarter {
108
108
  get expires_in() {
109
109
  return auth.expiresIn ?? undefined;
110
110
  },
111
+ get scope() {
112
+ return auth.cache?.scope;
113
+ },
111
114
  };
112
115
  };
113
116
  /**
@@ -216,7 +219,7 @@ export default class FHIRStarter {
216
219
  }
217
220
  };
218
221
  refreshAccessToken = async () => {
219
- const jwt = await this.buildJwt(), scopes = FHIRStarter.normalizeScopes(this.config.scopes), body = new URLSearchParams({
222
+ const jwt = await this.buildJwt(), scopes = fhirStarter.normalizeScopes(this.config.scopes), body = new URLSearchParams({
220
223
  grant_type: "client_credentials",
221
224
  client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
222
225
  client_assertion: jwt,
@@ -246,6 +249,7 @@ export default class FHIRStarter {
246
249
  accessToken: data.access_token,
247
250
  refreshAt,
248
251
  expiresAt,
252
+ ...(typeof data.scope === "string" && { scope: data.scope }),
249
253
  };
250
254
  this.cache = cache;
251
255
  this.notifyRefresh(cache.accessToken);
@@ -272,7 +276,6 @@ export default class FHIRStarter {
272
276
  })
273
277
  .setProtectedHeader(header)
274
278
  .setIssuedAt()
275
- .setNotBefore(0)
276
279
  .setExpirationTime("5m")
277
280
  .sign(privateKey);
278
281
  };
package/package.json CHANGED
@@ -1,16 +1,42 @@
1
1
  {
2
2
  "name": "fhirstarterjs",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "SMART Backend Services auth lifecycle for any FHIR client",
5
+ "author": "Joshua Faulkenberry <j@joshuafaulkenberry.com> (https://joshuafaulkenberry.com/)",
6
+ "license": "Kopimi",
7
+ "homepage": "https://github.com/FaulkJ/fhirstarter#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/FaulkJ/fhirstarter.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/FaulkJ/fhirstarter/issues"
14
+ },
15
+ "keywords": [
16
+ "fhir",
17
+ "smart",
18
+ "smart-on-fhir",
19
+ "backend-services",
20
+ "oauth",
21
+ "jwt",
22
+ "hl7",
23
+ "healthcare",
24
+ "ehr",
25
+ "epic",
26
+ "fhirclient",
27
+ "token",
28
+ "jwks"
29
+ ],
5
30
  "type": "module",
6
31
  "main": "./dist/fhirstarter.js",
7
- "types": "./ts/fhirstarter.ts",
32
+ "types": "./types/index.d.ts",
8
33
  "exports": {
9
34
  ".": {
10
35
  "types": "./types/index.d.ts",
11
36
  "import": "./dist/fhirstarter.js"
12
37
  }
13
38
  },
39
+ "sideEffects": false,
14
40
  "files": [
15
41
  "dist",
16
42
  "ts",
@@ -21,8 +47,6 @@
21
47
  "engines": {
22
48
  "node": ">=20"
23
49
  },
24
- "author": "Joshua Faulkenberry <j@joshuafaulkenberry.com> (https://joshuafaulkenberry.com/)",
25
- "license": "Kopimi",
26
50
  "scripts": {
27
51
  "check": "tsc --noEmit",
28
52
  "build": "tsc -p tsconfig.build.json",
@@ -32,7 +56,7 @@
32
56
  "jose": "^6.2.3"
33
57
  },
34
58
  "devDependencies": {
35
- "@types/node": "^25.9.2",
59
+ "@types/node": "^25.9.3",
36
60
  "typescript": "^6.0.3"
37
61
  }
38
62
  }
package/ts/fhirstarter.ts CHANGED
@@ -13,7 +13,7 @@ import { readFileSync } from "node:fs";
13
13
  * Manages client-credentials token acquisition and proactive refresh
14
14
  * using a private RSA key and the JWT Bearer client-assertion flow (RFC 7523).
15
15
  */
16
- export default class FHIRStarter implements Provider {
16
+ export default class fhirStarter implements Provider {
17
17
  private readonly config: AuthConfig;
18
18
  private readonly privateKeyText: string;
19
19
  private readonly refreshCallbacks: Set<RefreshCallback> = new Set();
@@ -39,7 +39,7 @@ export default class FHIRStarter implements Provider {
39
39
  if (!config.tokenEndpointUrl)
40
40
  throw new Error("AuthConfig: tokenEndpointUrl is required");
41
41
 
42
- const scopes = FHIRStarter.normalizeScopes(config.scopes);
42
+ const scopes = fhirStarter.normalizeScopes(config.scopes);
43
43
  if (scopes.length === 0)
44
44
  throw new Error("AuthConfig: at least one scope is required");
45
45
 
@@ -52,7 +52,7 @@ export default class FHIRStarter implements Provider {
52
52
  }
53
53
 
54
54
  this.config = config;
55
- this.privateKeyText = FHIRStarter.resolvePrivateKey(config.privateKey);
55
+ this.privateKeyText = fhirStarter.resolvePrivateKey(config.privateKey);
56
56
  }
57
57
 
58
58
  // --- Sync getters ---
@@ -127,6 +127,9 @@ export default class FHIRStarter implements Provider {
127
127
  get expires_in() {
128
128
  return auth.expiresIn ?? undefined;
129
129
  },
130
+ get scope() {
131
+ return auth.cache?.scope;
132
+ },
130
133
  };
131
134
  };
132
135
 
@@ -244,7 +247,7 @@ export default class FHIRStarter implements Provider {
244
247
 
245
248
  private refreshAccessToken = async (): Promise<TokenCache> => {
246
249
  const jwt = await this.buildJwt(),
247
- scopes = FHIRStarter.normalizeScopes(this.config.scopes),
250
+ scopes = fhirStarter.normalizeScopes(this.config.scopes),
248
251
  body = new URLSearchParams({
249
252
  grant_type: "client_credentials",
250
253
  client_assertion_type:
@@ -285,6 +288,7 @@ export default class FHIRStarter implements Provider {
285
288
  accessToken: data.access_token,
286
289
  refreshAt,
287
290
  expiresAt,
291
+ ...(typeof data.scope === "string" && { scope: data.scope }),
288
292
  };
289
293
 
290
294
  this.cache = cache;
@@ -317,7 +321,6 @@ export default class FHIRStarter implements Provider {
317
321
  })
318
322
  .setProtectedHeader(header)
319
323
  .setIssuedAt()
320
- .setNotBefore(0)
321
324
  .setExpirationTime("5m")
322
325
  .sign(privateKey);
323
326
  };
package/types/api.d.ts CHANGED
@@ -21,7 +21,7 @@ declare global {
21
21
  keys: import("jose").JWK[]
22
22
  }
23
23
 
24
- /** Auth lifecycle provider implemented by {@link FHIRStarter}. */
24
+ /** Auth lifecycle provider implemented by {@link fhirStarter}. */
25
25
  interface Provider {
26
26
  /** Current valid token, or null if no unexpired token is cached. */
27
27
  readonly token: string | null
@@ -48,6 +48,7 @@ declare global {
48
48
  token_type: "bearer"
49
49
  readonly access_token: string | undefined
50
50
  readonly expires_in: number | undefined
51
+ readonly scope: string | undefined
51
52
  }
52
53
  }
53
54
 
@@ -14,6 +14,8 @@ declare global {
14
14
  refreshAt: number
15
15
  /** Epoch ms — actual token expiry. Never treat as expired before this. */
16
16
  expiresAt: number
17
+ /** Granted scopes returned by the token endpoint, if any. */
18
+ scope?: string
17
19
  }
18
20
 
19
21
  type RefreshCallback = (token: string) => void