@haau3/sdk 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,886 @@
1
+ //#region src/errors.ts
2
+ /** What every error this package throws has. */
3
+ var Haau3Error = class extends Error {
4
+ name = "Haau3Error";
5
+ /** The `X-Request-Id` the API sent, when it answered. Quote it when you ask for help. */
6
+ requestId;
7
+ constructor(message, requestId, options) {
8
+ super(message, options);
9
+ this.requestId = requestId;
10
+ }
11
+ };
12
+ /**
13
+ * Attaches the body the API answered with, readable by code but never printed: the property is
14
+ * not enumerable, so `console.log(error)`, `util.inspect` and `JSON.stringify` leave it out. A body
15
+ * can repeat what the request carried, and for some operations that is a patient's data.
16
+ */
17
+ function attachBody(error, body) {
18
+ if (!body) return;
19
+ const key = body.kind === "outcome" ? "outcome" : "problem";
20
+ Object.defineProperty(error, key, {
21
+ value: body.value,
22
+ enumerable: false,
23
+ writable: false,
24
+ configurable: false
25
+ });
26
+ }
27
+ /** The API refused the key: none was sent, it is not one the API knows, or it was revoked (401, 403). */
28
+ var AuthError = class extends Haau3Error {
29
+ name = "AuthError";
30
+ status;
31
+ constructor(message, status, requestId, body) {
32
+ super(message, requestId);
33
+ this.status = status;
34
+ attachBody(this, body);
35
+ }
36
+ };
37
+ /**
38
+ * The API answered and refused the request: a parameter it does not take, a record that does not
39
+ * exist (404), a page that no longer exists (410), a body that is too large (413). `status` says
40
+ * which, and the body says why.
41
+ */
42
+ var RefusedError = class extends Haau3Error {
43
+ name = "RefusedError";
44
+ status;
45
+ constructor(message, status, requestId, body) {
46
+ super(message, requestId);
47
+ this.status = status;
48
+ attachBody(this, body);
49
+ }
50
+ };
51
+ /**
52
+ * The request got no answer, after every attempt the client was allowed. This never means "no
53
+ * results": an empty search answers with an empty Bundle or list, not with this error.
54
+ */
55
+ var NoAnswerError = class extends Haau3Error {
56
+ name = "NoAnswerError";
57
+ reason;
58
+ /** The last status the API answered with, when it answered at all. */
59
+ status;
60
+ /** Seconds to wait before trying again, when the API said. */
61
+ retryAfter;
62
+ /** How many attempts were made. */
63
+ attempts;
64
+ constructor(message, details) {
65
+ super(message, details.requestId, details.cause === void 0 ? void 0 : { cause: details.cause });
66
+ this.reason = details.reason;
67
+ this.status = details.status;
68
+ this.retryAfter = details.retryAfter;
69
+ this.attempts = details.attempts;
70
+ attachBody(this, details.body);
71
+ }
72
+ };
73
+ //#endregion
74
+ //#region src/version.ts
75
+ /** This package's version, sent in the `User-Agent` header. A test holds it to package.json. */
76
+ const VERSION = "0.1.0";
77
+ //#endregion
78
+ //#region src/transport.ts
79
+ const DEFAULT_BASE_URL = "https://api.haau3.com";
80
+ const DEFAULT_TIMEOUT = 65e3;
81
+ const DEFAULT_MAX_RETRIES = 1;
82
+ /** The longest `Retry-After` the client waits out. A longer one fails at once and says when. */
83
+ const LONGEST_WAIT_SECONDS = 60;
84
+ const FHIR_JSON = "application/fhir+json";
85
+ /**
86
+ * True in a browser. A key in a browser is public, and the API answers no browser anyway (it sends
87
+ * no CORS headers). jsdom, which test runners use to render components on a server, is not one.
88
+ */
89
+ function inBrowser() {
90
+ const g = globalThis;
91
+ return typeof g.window !== "undefined" && typeof g.document !== "undefined" && !(g.navigator?.userAgent ?? "").includes("jsdom");
92
+ }
93
+ function envKey() {
94
+ return globalThis.process?.env?.HAAU3_API_KEY || void 0;
95
+ }
96
+ /** Seconds from a `Retry-After` header, when it holds a number of seconds. */
97
+ function retryAfterSeconds(headers) {
98
+ const value = headers.get("retry-after");
99
+ if (value === null || !/^\d+$/.test(value.trim())) return void 0;
100
+ return Number(value.trim());
101
+ }
102
+ /** Exponential backoff with jitter: about 0.5 s, then 1 s, 2 s and so on, never past 8 s. */
103
+ function backoff(attempt) {
104
+ return Math.min(8e3, 500 * 2 ** (attempt - 1)) * (.5 + Math.random() * .5);
105
+ }
106
+ function sleep(ms, signal) {
107
+ return new Promise((resolve, reject) => {
108
+ if (signal?.aborted) return reject(signal.reason);
109
+ const timer = setTimeout(() => {
110
+ signal?.removeEventListener("abort", onAbort);
111
+ resolve();
112
+ }, ms);
113
+ const onAbort = () => {
114
+ clearTimeout(timer);
115
+ reject(signal.reason);
116
+ };
117
+ signal?.addEventListener("abort", onAbort, { once: true });
118
+ });
119
+ }
120
+ function parseBody(text, contentType) {
121
+ if (!contentType || !/json/i.test(contentType)) return void 0;
122
+ try {
123
+ return JSON.parse(text);
124
+ } catch {
125
+ return;
126
+ }
127
+ }
128
+ function readErrorBody(body) {
129
+ if (!body || typeof body !== "object") return void 0;
130
+ const record = body;
131
+ if (record.resourceType === "OperationOutcome") return {
132
+ kind: "outcome",
133
+ value: body
134
+ };
135
+ if (typeof record.type === "string" && typeof record.status === "number") return {
136
+ kind: "problem",
137
+ value: body
138
+ };
139
+ }
140
+ function noAnswerReason(status) {
141
+ if (status === 429) return "rate-limited";
142
+ if (status === 502 || status === 503) return "unavailable";
143
+ if (status === 504) return "timeout";
144
+ return "server-error";
145
+ }
146
+ /** The connection, retries and errors every operation shares. */
147
+ var Transport = class {
148
+ #apiKey;
149
+ #fetch;
150
+ /** Where requests go. */
151
+ baseURL;
152
+ /** Milliseconds one attempt may take. */
153
+ timeout;
154
+ /** How many times a request is retried. */
155
+ maxRetries;
156
+ constructor(options = {}) {
157
+ if (inBrowser()) throw new Haau3Error("@haau3/sdk runs on a server only: an API key in a browser is public. Call the API from your server.");
158
+ const apiKey = options.apiKey ?? envKey();
159
+ if (!apiKey) throw new Haau3Error("No API key: pass apiKey, or set HAAU3_API_KEY.");
160
+ const fetchImpl = options.fetch ?? globalThis.fetch;
161
+ if (!fetchImpl) throw new Haau3Error("No fetch: pass one as the fetch option. Node 22 and newer have one.");
162
+ this.#apiKey = apiKey;
163
+ this.#fetch = fetchImpl;
164
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
165
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
166
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
167
+ }
168
+ /**
169
+ * Sends any request and returns what the API answered, whatever its status. Throws only when no
170
+ * answer arrived ({@link NoAnswerError}), or for a URL that is not on the client's own `baseURL`.
171
+ */
172
+ async request(request) {
173
+ const url = this.#url(request.path, request.query);
174
+ const sent = await this.#send(request.method, url, request.body, request, url.pathname);
175
+ return {
176
+ status: sent.response.status,
177
+ headers: sent.response.headers,
178
+ body: parseBody(sent.text, sent.response.headers.get("content-type")) ?? sent.text,
179
+ requestId: sent.response.headers.get("x-request-id") ?? void 0,
180
+ attempts: sent.attempts,
181
+ durationMs: sent.durationMs
182
+ };
183
+ }
184
+ /** Runs one operation of the spec and returns its answer, or throws one of the three errors. */
185
+ async operation(call, options = {}) {
186
+ let path = call.path;
187
+ for (const [name, value] of Object.entries(call.pathParams ?? {})) path = path.replace(`{${name}}`, encodeURIComponent(value));
188
+ return this.answer(call.method, this.#url(path, call.query), call.body, options, call.path);
189
+ }
190
+ /**
191
+ * Sends a request and reads its answer. `label` names the request in an error message: the
192
+ * operation's path with its `{id}` left in, so no value a caller sent ends up in a message.
193
+ */
194
+ async answer(method, url, body, options, label) {
195
+ const { response, text, attempts } = await this.#send(method, url, body, options, label);
196
+ const status = response.status;
197
+ const requestId = response.headers.get("x-request-id") ?? void 0;
198
+ const parsed = parseBody(text, response.headers.get("content-type"));
199
+ const name = `${method} ${label}`;
200
+ if (status >= 200 && status < 300) {
201
+ if (parsed === void 0) throw new NoAnswerError(`${name} answered ${status} with a body that is not JSON.`, {
202
+ reason: "unreadable",
203
+ attempts,
204
+ status,
205
+ requestId
206
+ });
207
+ return parsed;
208
+ }
209
+ const errorBody = readErrorBody(parsed);
210
+ if (status === 401 || status === 403) throw new AuthError(`${name} was refused: the API key was not accepted (${status}).`, status, requestId, errorBody);
211
+ if (status === 429 || status >= 500) {
212
+ const reason = noAnswerReason(status);
213
+ throw new NoAnswerError(`${name} got no answer (${reason}, ${status}) after ${attempts} attempt${attempts === 1 ? "" : "s"}.`, {
214
+ reason,
215
+ attempts,
216
+ status,
217
+ retryAfter: retryAfterSeconds(response.headers),
218
+ requestId,
219
+ body: errorBody
220
+ });
221
+ }
222
+ if (status >= 400) throw new RefusedError(`${name} was refused (${status}).`, status, requestId, errorBody);
223
+ throw new NoAnswerError(`${name} answered ${status}, which is not an answer.`, {
224
+ reason: "unreadable",
225
+ attempts,
226
+ status,
227
+ requestId
228
+ });
229
+ }
230
+ /** A URL on this client's base, refusing any other host so the key is never sent to one. */
231
+ url(pathOrUrl) {
232
+ return this.#url(pathOrUrl, void 0);
233
+ }
234
+ #url(pathOrUrl, query) {
235
+ const base = new URL(this.baseURL);
236
+ const url = /^https?:\/\//i.test(pathOrUrl) ? new URL(pathOrUrl) : new URL(base.pathname.replace(/\/$/, "") + (pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`), base);
237
+ if (url.origin !== base.origin) throw new Haau3Error(`Refusing to send the API key to ${url.origin}: the client's baseURL is ${base.origin}.`);
238
+ for (const [name, value] of Object.entries(query ?? {})) {
239
+ if (value === void 0) continue;
240
+ for (const item of Array.isArray(value) ? value : [value]) url.searchParams.append(name, String(item));
241
+ }
242
+ return url;
243
+ }
244
+ async #send(method, url, body, options, label) {
245
+ const fhir = url.pathname.startsWith("/v1/fhir/") || url.pathname === "/v1/fhir";
246
+ const headers = {
247
+ accept: fhir ? FHIR_JSON : "application/json",
248
+ authorization: `Bearer ${this.#apiKey}`,
249
+ "user-agent": `haau3-sdk/${VERSION}`
250
+ };
251
+ let payload;
252
+ if (body !== void 0) {
253
+ payload = typeof body === "string" ? body : JSON.stringify(body);
254
+ headers["content-type"] = fhir ? FHIR_JSON : "application/json";
255
+ }
256
+ for (const [name, value] of Object.entries(options.headers ?? {})) headers[name.toLowerCase()] = value;
257
+ const timeout = options.timeout ?? this.timeout;
258
+ const maxRetries = options.maxRetries ?? this.maxRetries;
259
+ const started = Date.now();
260
+ let attempt = 0;
261
+ for (;;) {
262
+ attempt += 1;
263
+ const attemptSignal = AbortSignal.timeout(timeout);
264
+ const signal = options.signal ? AbortSignal.any([options.signal, attemptSignal]) : attemptSignal;
265
+ const canRetry = attempt <= maxRetries;
266
+ let response;
267
+ let text;
268
+ try {
269
+ response = await this.#fetch(url, {
270
+ method,
271
+ headers,
272
+ body: payload,
273
+ signal,
274
+ redirect: "manual"
275
+ });
276
+ text = await response.text();
277
+ } catch (error) {
278
+ if (options.signal?.aborted) throw options.signal.reason;
279
+ const reason = attemptSignal.aborted ? "timeout" : "network";
280
+ if (canRetry) {
281
+ await sleep(backoff(attempt), options.signal);
282
+ continue;
283
+ }
284
+ throw new NoAnswerError(`${method} ${label} got no answer (${reason}) after ${attempt} attempt${attempt === 1 ? "" : "s"}.`, {
285
+ reason,
286
+ attempts: attempt,
287
+ cause: error
288
+ });
289
+ }
290
+ if ((response.status === 429 || response.status >= 500) && canRetry) {
291
+ const wait = retryAfterSeconds(response.headers);
292
+ if (wait === void 0 || wait <= LONGEST_WAIT_SECONDS) {
293
+ await sleep(wait === void 0 ? backoff(attempt) : wait * 1e3, options.signal);
294
+ continue;
295
+ }
296
+ }
297
+ return {
298
+ response,
299
+ text,
300
+ attempts: attempt,
301
+ durationMs: Date.now() - started
302
+ };
303
+ }
304
+ }
305
+ };
306
+ //#endregion
307
+ //#region src/generated/methods.ts
308
+ /** Every operation of the haau3 platform API. {@link Haau3} is the class to construct. */
309
+ var Operations = class extends Transport {
310
+ /**
311
+ * Check providers against a plan's network
312
+ *
313
+ * FHIR instance-level operation `$in-network` on InsurancePlan: for each NPI, whether the CMS National Provider Directory lists the provider in a network this plan names, for the plan's year. The answer is a `Parameters` resource: the `plan`; the directory's `data-date`; one `network` per network checked, with the number of organizations it lists; a `reason` when there is no network data; a Marketplace plan's own `directory`; one `provider` per NPI, in the order sent, with its `status` (`in-network`, `not-found`, `not-in-directory` or `no-network-data`) and, when listed, `evidence` references to the directory's records on this base; then a `source` when the directory was asked. `not-found` never means out of network: the directory's network data is incomplete, and a listing is not a statement of a contract. All or nothing: a lookup that fails fails the whole answer. GET only. Defined by the haau3 implementation guide (`https://haau3.com/fhir/OperationDefinition/InsurancePlan-in-network`).
314
+ *
315
+ * `GET /v1/fhir/r4/InsurancePlan/{id}/$in-network`
316
+ */
317
+ checkInsurancePlanNetwork(id, params, options) {
318
+ return this.operation({
319
+ method: "GET",
320
+ path: "/v1/fhir/r4/InsurancePlan/{id}/$in-network",
321
+ pathParams: { id },
322
+ query: params
323
+ }, options);
324
+ }
325
+ /**
326
+ * Look up a code
327
+ *
328
+ * FHIR `CodeSystem/$lookup`. Returns a code's display and requested properties. On POST, a `coding` parameter may stand in for `system` + `code`.
329
+ *
330
+ * `GET /v1/fhir/r4/CodeSystem/$lookup`
331
+ */
332
+ codeSystemLookup(params, options) {
333
+ return this.operation({
334
+ method: "GET",
335
+ path: "/v1/fhir/r4/CodeSystem/$lookup",
336
+ query: params
337
+ }, options);
338
+ }
339
+ /**
340
+ * Look up a code
341
+ *
342
+ * FHIR `CodeSystem/$lookup`. Returns a code's display and requested properties. On POST, a `coding` parameter may stand in for `system` + `code`.
343
+ *
344
+ * `POST /v1/fhir/r4/CodeSystem/$lookup`
345
+ */
346
+ codeSystemLookupPost(body, options) {
347
+ return this.operation({
348
+ method: "POST",
349
+ path: "/v1/fhir/r4/CodeSystem/$lookup",
350
+ body
351
+ }, options);
352
+ }
353
+ /**
354
+ * Validate a code in a code system
355
+ *
356
+ * FHIR `CodeSystem/$validate-code`. Asks whether a code exists in a code system — distinct from the ValueSet operation of the same name, which asks about membership of a particular set. On POST, a `coding` or `codeableConcept` parameter may stand in for `url` + `code`; a codeableConcept validates if any of its codings does.
357
+ *
358
+ * `GET /v1/fhir/r4/CodeSystem/$validate-code`
359
+ */
360
+ codeSystemValidateCode(params, options) {
361
+ return this.operation({
362
+ method: "GET",
363
+ path: "/v1/fhir/r4/CodeSystem/$validate-code",
364
+ query: params
365
+ }, options);
366
+ }
367
+ /**
368
+ * Validate a code in a code system
369
+ *
370
+ * FHIR `CodeSystem/$validate-code`. Asks whether a code exists in a code system — distinct from the ValueSet operation of the same name, which asks about membership of a particular set. On POST, a `coding` or `codeableConcept` parameter may stand in for `url` + `code`; a codeableConcept validates if any of its codings does.
371
+ *
372
+ * `POST /v1/fhir/r4/CodeSystem/$validate-code`
373
+ */
374
+ codeSystemValidateCodePost(body, options) {
375
+ return this.operation({
376
+ method: "POST",
377
+ path: "/v1/fhir/r4/CodeSystem/$validate-code",
378
+ body
379
+ }, options);
380
+ }
381
+ /**
382
+ * Run several operations in one request (batch)
383
+ *
384
+ * POST a FHIR `Bundle` with `type: batch` to send several requests in one round trip. Each entry's `request.url` is relative to this base and may be any read, search or operation of any service on it, except `$reconcile-medications`, which a batch refuses by name. Entries are independent: one that fails, such as a lookup of an unknown code, does not fail the others, and each carries its own status in the `batch-response`. For example, a batch of 50 terminology lookups measured about 4.8 ms per code, against 181 ms each when sent one at a time. Transaction bundles are not accepted: nothing on this base writes, so all or nothing would only turn one bad entry into a wholly failed request. A batch carries at most 100 entries in at most 1 MiB, and has 60 seconds: an entry still running then answers `504` inside the `200`. See https://docs.haau3.com/errors#limits.
385
+ *
386
+ * `POST /v1/fhir/r4`
387
+ */
388
+ fhirBatch(body, options) {
389
+ return this.operation({
390
+ method: "POST",
391
+ path: "/v1/fhir/r4",
392
+ body
393
+ }, options);
394
+ }
395
+ /**
396
+ * Capability statement
397
+ *
398
+ * FHIR `[base]/metadata`. Returns the `CapabilityStatement` for this service, or a `TerminologyCapabilities` when called with `?mode=terminology` — which lists the code systems served, and names those that are recognised but not served. `mode` is the only parameter it takes.
399
+ *
400
+ * `GET /v1/fhir/r4/metadata`
401
+ */
402
+ fhirMetadata(params, options) {
403
+ return this.operation({
404
+ method: "GET",
405
+ path: "/v1/fhir/r4/metadata",
406
+ query: params
407
+ }, options);
408
+ }
409
+ /**
410
+ * List a state's counties
411
+ *
412
+ * FHIR type-level operation `$counties` on InsurancePlan: the counties and county equivalents of a state, the District of Columbia or a territory, sorted by name, each with the five-digit FIPS code `coverage-area-county` takes. The answer is a `Parameters` resource: one `county` parameter per county, whose `code` part is a Coding in `http://terminology.hl7.org/CodeSystem/FIPS64` with the county's name as its display and whose `state` part is the state's code, then a `source` parameter naming the list: the U.S. Census Bureau's 2020 county codes. It answers whether or not a plan is sold in a county. GET only. Defined by the haau3 implementation guide (`https://haau3.com/fhir/OperationDefinition/InsurancePlan-counties`).
413
+ *
414
+ * `GET /v1/fhir/r4/InsurancePlan/$counties`
415
+ */
416
+ listInsurancePlanCounties(params, options) {
417
+ return this.operation({
418
+ method: "GET",
419
+ path: "/v1/fhir/r4/InsurancePlan/$counties",
420
+ query: params
421
+ }, options);
422
+ }
423
+ /**
424
+ * List scheduling publishers
425
+ *
426
+ * Returns the SMART Scheduling Links publishers (sources) the platform ingests. Requires a valid API key.
427
+ *
428
+ * `GET /v1/scheduling/publishers`
429
+ */
430
+ listPublishers(options) {
431
+ return this.operation({
432
+ method: "GET",
433
+ path: "/v1/scheduling/publishers"
434
+ }, options);
435
+ }
436
+ /**
437
+ * List service types
438
+ *
439
+ * Returns the distinct service types observed across served slots, each with its `system` and a `count` of the slots carrying it. It is a discovery aid for the `serviceTypeCode` filter on `GET /v1/scheduling/slots`. Requires a valid API key.
440
+ *
441
+ * `GET /v1/scheduling/service-types`
442
+ */
443
+ listServiceTypes(params, options) {
444
+ return this.operation({
445
+ method: "GET",
446
+ path: "/v1/scheduling/service-types",
447
+ query: params
448
+ }, options);
449
+ }
450
+ /**
451
+ * List specialties
452
+ *
453
+ * Returns the distinct specialties observed across served slots, from the provider's `specialties` and the schedule's, each with its `system` and a `count` of the slots carrying it. It is a discovery aid for the `specialty` filter on `GET /v1/scheduling/slots`. Codes are each publisher's own. Requires a valid API key.
454
+ *
455
+ * `GET /v1/scheduling/specialties`
456
+ */
457
+ listSpecialties(params, options) {
458
+ return this.operation({
459
+ method: "GET",
460
+ path: "/v1/scheduling/specialties",
461
+ query: params
462
+ }, options);
463
+ }
464
+ /**
465
+ * Read an Endpoint
466
+ *
467
+ * Read one endpoint by id, as returned by a search on this service.
468
+ *
469
+ * `GET /v1/fhir/r4/Endpoint/{id}`
470
+ */
471
+ readEndpoint(id, options) {
472
+ return this.operation({
473
+ method: "GET",
474
+ path: "/v1/fhir/r4/Endpoint/{id}",
475
+ pathParams: { id }
476
+ }, options);
477
+ }
478
+ /**
479
+ * Read a HealthcareService
480
+ *
481
+ * Read one healthcare service by id, as returned by a search on this service.
482
+ *
483
+ * `GET /v1/fhir/r4/HealthcareService/{id}`
484
+ */
485
+ readHealthcareService(id, options) {
486
+ return this.operation({
487
+ method: "GET",
488
+ path: "/v1/fhir/r4/HealthcareService/{id}",
489
+ pathParams: { id }
490
+ }, options);
491
+ }
492
+ /**
493
+ * Read a plan
494
+ *
495
+ * Read one plan by its id. A Medicare Advantage plan's id is CMS's plan id with the segment in three digits (`H2406-013-000`); a Marketplace plan's is its HIOS plan id with the variant (`19898FL0340001-01`). The plan conforms to the haau3 Plan Benefits profile and to the SBC InsurancePlan profile of the CARIN Digital Insurance Card guide, 2.0.0-ballot, and names the CMS release it was read from.
496
+ *
497
+ * `GET /v1/fhir/r4/InsurancePlan/{id}`
498
+ */
499
+ readInsurancePlan(id, options) {
500
+ return this.operation({
501
+ method: "GET",
502
+ path: "/v1/fhir/r4/InsurancePlan/{id}",
503
+ pathParams: { id }
504
+ }, options);
505
+ }
506
+ /**
507
+ * Read a Location
508
+ *
509
+ * Read one location by id, as returned by a search on this service.
510
+ *
511
+ * `GET /v1/fhir/r4/Location/{id}`
512
+ */
513
+ readLocation(id, options) {
514
+ return this.operation({
515
+ method: "GET",
516
+ path: "/v1/fhir/r4/Location/{id}",
517
+ pathParams: { id }
518
+ }, options);
519
+ }
520
+ /**
521
+ * Read an Organization
522
+ *
523
+ * Read one organization, payer or network by id, as returned by a search on this service.
524
+ *
525
+ * `GET /v1/fhir/r4/Organization/{id}`
526
+ */
527
+ readOrganization(id, options) {
528
+ return this.operation({
529
+ method: "GET",
530
+ path: "/v1/fhir/r4/Organization/{id}",
531
+ pathParams: { id }
532
+ }, options);
533
+ }
534
+ /**
535
+ * Read an OrganizationAffiliation
536
+ *
537
+ * Read one organization affiliation by id, as returned by a search on this service.
538
+ *
539
+ * `GET /v1/fhir/r4/OrganizationAffiliation/{id}`
540
+ */
541
+ readOrganizationAffiliation(id, options) {
542
+ return this.operation({
543
+ method: "GET",
544
+ path: "/v1/fhir/r4/OrganizationAffiliation/{id}",
545
+ pathParams: { id }
546
+ }, options);
547
+ }
548
+ /**
549
+ * Read a Practitioner
550
+ *
551
+ * Read one practitioner by id, as returned by a search on this service.
552
+ *
553
+ * `GET /v1/fhir/r4/Practitioner/{id}`
554
+ */
555
+ readPractitioner(id, options) {
556
+ return this.operation({
557
+ method: "GET",
558
+ path: "/v1/fhir/r4/Practitioner/{id}",
559
+ pathParams: { id }
560
+ }, options);
561
+ }
562
+ /**
563
+ * Read a PractitionerRole
564
+ *
565
+ * Read one practitioner role by id, as returned by a search on this service.
566
+ *
567
+ * `GET /v1/fhir/r4/PractitionerRole/{id}`
568
+ */
569
+ readPractitionerRole(id, options) {
570
+ return this.operation({
571
+ method: "GET",
572
+ path: "/v1/fhir/r4/PractitionerRole/{id}",
573
+ pathParams: { id }
574
+ }, options);
575
+ }
576
+ /**
577
+ * Read a questionnaire
578
+ *
579
+ * Read one questionnaire by id. Ids are the identifier its source gives the instrument, for example `44249-1`. A search with no parameters lists the ids this service holds copies of; any other id its source publishes is fetched live.
580
+ *
581
+ * The resource is returned exactly as its source publishes it, including its `copyright` and the order of every answer option.
582
+ *
583
+ * `GET /v1/fhir/r4/Questionnaire/{id}`
584
+ */
585
+ readQuestionnaire(id, options) {
586
+ return this.operation({
587
+ method: "GET",
588
+ path: "/v1/fhir/r4/Questionnaire/{id}",
589
+ pathParams: { id }
590
+ }, options);
591
+ }
592
+ /**
593
+ * Reconcile one patient's medication records
594
+ *
595
+ * FHIR system-level operation `$reconcile-medications`. POST one patient's MedicationRequest, MedicationStatement, MedicationDispense and MedicationAdministration records, with the Medications they reference, as a `collection` or `searchset` Bundle; every entry needs a `fullUrl`. The response is one reconciled medication list: a `collection` Bundle holding one reconciled MedicationRequest per clinical drug, a Provenance for each naming the records behind it, and an OperationOutcome when a record was skipped. To evaluate the list as of another date, POST a Parameters resource with `resource` parts, or one `bundle` part, and an `asOf` part. This operation receives patient data, so send it only under an executed agreement. Nothing a request carries is stored or logged, apart from the request's ids, and the operation is not offered inside a batch. The contract is published at https://fhir.haau3.com/OperationDefinition-reconcile-medications.html.
596
+ *
597
+ * `POST /v1/fhir/r4/$reconcile-medications`
598
+ */
599
+ reconcileMedications(body, options) {
600
+ return this.operation({
601
+ method: "POST",
602
+ path: "/v1/fhir/r4/$reconcile-medications",
603
+ body
604
+ }, options);
605
+ }
606
+ /**
607
+ * Find endpoints
608
+ *
609
+ * Search `Endpoint` by managing organization; `connection-type`, `status` and `name` narrow a search but cannot stand alone.
610
+ *
611
+ * `GET /v1/fhir/r4/Endpoint`
612
+ */
613
+ searchEndpoint(params, options) {
614
+ return this.operation({
615
+ method: "GET",
616
+ path: "/v1/fhir/r4/Endpoint",
617
+ query: params
618
+ }, options);
619
+ }
620
+ /**
621
+ * Find healthcare services
622
+ *
623
+ * Search `HealthcareService` by location, or by network beside a location. The directory's service records name a network and, for a few, a location; none names an organization, so `organization` is refused rather than answered with nothing.
624
+ *
625
+ * `GET /v1/fhir/r4/HealthcareService`
626
+ */
627
+ searchHealthcareService(params, options) {
628
+ return this.operation({
629
+ method: "GET",
630
+ path: "/v1/fhir/r4/HealthcareService",
631
+ query: params
632
+ }, options);
633
+ }
634
+ /**
635
+ * Find a plan
636
+ *
637
+ * Search the Medicare Advantage and ACA Marketplace plans being served, by identifier, name, or the state or county a plan is sold in. A search needs at least one of `identifier`, a form of `name`, `coverage-area-state` or `coverage-area-county`; `type` and `sold-to` narrow a search but are not one. Every result is the whole plan, sorted by name and then id, and every page states the total. Follow the `next` link to continue.
638
+ *
639
+ * A plan sold in only part of a county matches the whole county. `GET /v1/fhir/r4/InsurancePlan/$counties?state=FL` lists a state's counties and their codes.
640
+ *
641
+ * Every other parameter is refused by name rather than ignored, so a search never quietly answers a different question than the one asked. `address-state` and `address-postalcode` are refused with a pointer to the coverage-area parameters, because in FHIR R4 they search a plan's contact address.
642
+ *
643
+ * Drug-only and dental-only plans are not served. An identifier search that finds one answers no plan and an `OperationOutcome` entry saying why.
644
+ *
645
+ * `GET /v1/fhir/r4/InsurancePlan`
646
+ */
647
+ searchInsurancePlan(params, options) {
648
+ return this.operation({
649
+ method: "GET",
650
+ path: "/v1/fhir/r4/InsurancePlan",
651
+ query: params
652
+ }, options);
653
+ }
654
+ /**
655
+ * Find practice locations
656
+ *
657
+ * Search `Location` by managing organization or name; the address parameters and `status` narrow a search but cannot stand alone. A distance search (`near`) is refused because the directory does not apply it.
658
+ *
659
+ * `GET /v1/fhir/r4/Location`
660
+ */
661
+ searchLocation(params, options) {
662
+ return this.operation({
663
+ method: "GET",
664
+ path: "/v1/fhir/r4/Location",
665
+ query: params
666
+ }, options);
667
+ }
668
+ /**
669
+ * Find organizations, payers and networks
670
+ *
671
+ * Search `Organization` by name, NPI, parent (`partof`) or type. `type=ntwk` lists the insurance networks the directory holds and `type=pay` its payers; any other type, `active` and the address parameters narrow a search but cannot stand alone.
672
+ *
673
+ * The directory records networks by the text of their type rather than a code. This service searches that text for `type=ntwk` and confirms every answer is a network; if the directory changes how it records them, the search answers 502 rather than serve something else.
674
+ *
675
+ * An NPI search can return a parent organization beside the practice, when the parent lists the NPI among several. `_revinclude` brings the roles, affiliations, locations and endpoints that point at each match, up to 100 per page.
676
+ *
677
+ * `GET /v1/fhir/r4/Organization`
678
+ */
679
+ searchOrganization(params, options) {
680
+ return this.operation({
681
+ method: "GET",
682
+ path: "/v1/fhir/r4/Organization",
683
+ query: params
684
+ }, options);
685
+ }
686
+ /**
687
+ * Find the organizations in a network
688
+ *
689
+ * Search `OrganizationAffiliation`: which organizations take part in which networks. `network` lists a network's members; with `participating-organization` beside it, it asks whether the directory lists that organization in that network. The directory's network data is sparse, so an organization a network's records do not list is not shown to be out of network. For a network, the primary organization is the network itself.
690
+ *
691
+ * `GET /v1/fhir/r4/OrganizationAffiliation`
692
+ */
693
+ searchOrganizationAffiliation(params, options) {
694
+ return this.operation({
695
+ method: "GET",
696
+ path: "/v1/fhir/r4/OrganizationAffiliation",
697
+ query: params
698
+ }, options);
699
+ }
700
+ /**
701
+ * Find a practitioner by NPI or name
702
+ *
703
+ * Search `Practitioner` by `identifier` to resolve an NPI you already hold, or by name to find someone. A search needs one or the other (or an `_id`): `address-city`, `address-state` and `active` narrow a search but cannot stand alone. Specialties are searched on `PractitionerRole`.
704
+ *
705
+ * Three identifier systems are accepted, `http://terminology.hl7.org/NamingSystem/npi`, `http://hl7.org/fhir/sid/us-npi` and `urn:oid:2.16.840.1.113883.4.6`, as well as a bare 10-digit value. All of them resolve the same provider. **Responses carry the directory's own system, `http://hl7.org/fhir/sid/us-npi`**, which may differ from the one you sent. Any other identifier system is refused by name: a system the directory does not index would answer with an empty result, which reads as 'no such provider'.
706
+ *
707
+ * One NPI per search. To resolve several, POST a batch `Bundle` with one entry each.
708
+ *
709
+ * `GET /v1/fhir/r4/Practitioner`
710
+ */
711
+ searchPractitioner(params, options) {
712
+ return this.operation({
713
+ method: "GET",
714
+ path: "/v1/fhir/r4/Practitioner",
715
+ query: params
716
+ }, options);
717
+ }
718
+ /**
719
+ * Find practitioner roles
720
+ *
721
+ * Search `PractitionerRole`: the organizations, specialties, locations and networks practitioners are associated with. A search names a practitioner, an organization, a location or an endpoint; `specialty` (NUCC), `new-patient` and `active` narrow it.
722
+ *
723
+ * `network` and `new-patient-from-network` follow the directory's own rule: each needs a practitioner, organization, location or specialty beside it, and a search without one is refused before the directory is asked. A network search beside a practitioner or an organization answers in under a second; beside a specialty or a busy location the directory can take 10 to 15 seconds. The directory's network data is sparse, so a role a network's records do not list is not shown to be out of network.
724
+ *
725
+ * `identifier` is not offered because the directory does not carry identifiers on these resources.
726
+ *
727
+ * `GET /v1/fhir/r4/PractitionerRole`
728
+ */
729
+ searchPractitionerRole(params, options) {
730
+ return this.operation({
731
+ method: "GET",
732
+ path: "/v1/fhir/r4/PractitionerRole",
733
+ query: params
734
+ }, options);
735
+ }
736
+ /**
737
+ * Search provider locations
738
+ *
739
+ * Returns the matching slots grouped by provider location: each item in `providerLocations` is one provider at one location, or a location with no provider, with its total slot count and soonest availability, paginated by provider location. Its opaque `key` drills down to its individual slots via `GET /v1/scheduling/slots?providerLocationKey=<key>`. Same filters as `/slots`. Requires a valid API key.
740
+ *
741
+ * `GET /v1/scheduling/provider-locations`
742
+ */
743
+ searchProviderLocations(params, options) {
744
+ return this.operation({
745
+ method: "GET",
746
+ path: "/v1/scheduling/provider-locations",
747
+ query: params
748
+ }, options);
749
+ }
750
+ /**
751
+ * List or find a questionnaire
752
+ *
753
+ * With no parameters, this lists the questionnaires this service holds copies of. Those render the same way on every request, and that list is what you can rely on.
754
+ *
755
+ * Naming a canonical `url` reaches any questionnaire the source publishes, including ones this service holds no copy of. Those depend on the source being reachable, and when it cannot answer you get a `502`, `503` or `504` carrying an `OperationOutcome`, so a failure never arrives looking like an empty result.
756
+ *
757
+ * A canonical that is the address of a form published at its own address, under `https://smart-health-checkin.org/connectathon/Questionnaire/` or another place this service is set up to reach, is fetched from that address and served exactly as published, its entry's `fullUrl` the address it was fetched from. Any other canonical matches nothing.
758
+ *
759
+ * Two parameters are offered: `url` (the canonical, optionally as `{url}|{version}`) and `version`. Every other parameter, `status` included, is refused by name rather than ignored, so a search never quietly answers a different question than the one asked. Results are never paged: one page, with `total` set.
760
+ *
761
+ * Questionnaires are served exactly as their source publishes them. Each carries its own `copyright`, which must be kept with the form wherever it goes.
762
+ *
763
+ * `GET /v1/fhir/r4/Questionnaire`
764
+ */
765
+ searchQuestionnaire(params, options) {
766
+ return this.operation({
767
+ method: "GET",
768
+ path: "/v1/fhir/r4/Questionnaire",
769
+ query: params
770
+ }, options);
771
+ }
772
+ /**
773
+ * Search appointment slots
774
+ *
775
+ * Returns provider availability slots matching the filters, with pagination. Requires a valid API key.
776
+ *
777
+ * `GET /v1/scheduling/slots`
778
+ */
779
+ searchSlots(params, options) {
780
+ return this.operation({
781
+ method: "GET",
782
+ path: "/v1/scheduling/slots",
783
+ query: params
784
+ }, options);
785
+ }
786
+ /**
787
+ * Expand a value set
788
+ *
789
+ * FHIR `ValueSet/$expand`. Expands a value set canonical to its member codes, with an optional text filter and paging (`count=0` returns the total only).
790
+ *
791
+ * `GET /v1/fhir/r4/ValueSet/$expand`
792
+ */
793
+ valueSetExpand(params, options) {
794
+ return this.operation({
795
+ method: "GET",
796
+ path: "/v1/fhir/r4/ValueSet/$expand",
797
+ query: params
798
+ }, options);
799
+ }
800
+ /**
801
+ * Expand a value set
802
+ *
803
+ * FHIR `ValueSet/$expand`. Expands a value set canonical to its member codes, with an optional text filter and paging (`count=0` returns the total only).
804
+ *
805
+ * `POST /v1/fhir/r4/ValueSet/$expand`
806
+ */
807
+ valueSetExpandPost(body, options) {
808
+ return this.operation({
809
+ method: "POST",
810
+ path: "/v1/fhir/r4/ValueSet/$expand",
811
+ body
812
+ }, options);
813
+ }
814
+ /**
815
+ * Validate a code against a value set
816
+ *
817
+ * FHIR `ValueSet/$validate-code`. A code that is not in the set is a successful `200` with `result: false`, not an error. On POST, a `coding` or `codeableConcept` parameter may stand in for `system` + `code`; a codeableConcept validates if any of its codings is in the set — useful when an element carries a vendor-local coding alongside a standard one.
818
+ *
819
+ * `GET /v1/fhir/r4/ValueSet/$validate-code`
820
+ */
821
+ valueSetValidateCode(params, options) {
822
+ return this.operation({
823
+ method: "GET",
824
+ path: "/v1/fhir/r4/ValueSet/$validate-code",
825
+ query: params
826
+ }, options);
827
+ }
828
+ /**
829
+ * Validate a code against a value set
830
+ *
831
+ * FHIR `ValueSet/$validate-code`. A code that is not in the set is a successful `200` with `result: false`, not an error. On POST, a `coding` or `codeableConcept` parameter may stand in for `system` + `code`; a codeableConcept validates if any of its codings is in the set — useful when an element carries a vendor-local coding alongside a standard one.
832
+ *
833
+ * `POST /v1/fhir/r4/ValueSet/$validate-code`
834
+ */
835
+ valueSetValidateCodePost(body, options) {
836
+ return this.operation({
837
+ method: "POST",
838
+ path: "/v1/fhir/r4/ValueSet/$validate-code",
839
+ body
840
+ }, options);
841
+ }
842
+ };
843
+ //#endregion
844
+ //#region src/client.ts
845
+ /**
846
+ * The haau3 platform API client.
847
+ *
848
+ * ```ts
849
+ * import { Haau3 } from "@haau3/sdk";
850
+ *
851
+ * const haau3 = new Haau3(); // reads HAAU3_API_KEY
852
+ * const plan = await haau3.readInsurancePlan("H2406-013-000");
853
+ * ```
854
+ *
855
+ * Every operation of the API is a method named by its operationId. Server-side only: the
856
+ * constructor refuses to run in a browser, where a key would be public.
857
+ */
858
+ var Haau3 = class extends Operations {
859
+ constructor(options = {}) {
860
+ super(options);
861
+ }
862
+ /**
863
+ * Every page of a FHIR search, starting with the one you have, following each `next` link
864
+ * until there is none. A link to any host but the client's own `baseURL` is refused, so the key
865
+ * is never sent elsewhere.
866
+ *
867
+ * ```ts
868
+ * const first = await haau3.searchInsurancePlan({ "coverage-area-state": "NC" });
869
+ * for await (const page of haau3.pages(first)) {
870
+ * for (const entry of page.entry ?? []) console.log(entry.resource?.id);
871
+ * }
872
+ * ```
873
+ */
874
+ async *pages(first, options = {}) {
875
+ let page = first;
876
+ while (page) {
877
+ yield page;
878
+ const next = page.link?.find((link) => link.relation === "next")?.url;
879
+ if (!next) return;
880
+ const url = this.url(next);
881
+ page = await this.answer("GET", url, void 0, options, url.pathname);
882
+ }
883
+ }
884
+ };
885
+ //#endregion
886
+ export { AuthError, Haau3, Haau3Error, NoAnswerError, RefusedError, VERSION };